@conduction/nextcloud-vue 2.111.0 → 2.112.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.
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +32 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +31 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +166 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +19 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
- package/dist/esm/composables/useRefLabels.js +26 -6
- package/dist/esm/composables/useRefLabels.js.map +1 -1
- package/dist/esm/integrations/registry.js +4 -0
- package/dist/esm/integrations/registry.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +533 -273
- package/dist/esm/store/plugins/liveUpdates.js +3 -1
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/plugins/logs.js +2 -1
- package/dist/esm/store/plugins/logs.js.map +1 -1
- package/dist/esm/utils/detailActionModel.js +28 -1
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +13 -5
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerFieldChips.js +21 -2
- package/dist/esm/utils/headerFieldChips.js.map +1 -1
- package/dist/esm/utils/mapList.js +26 -11
- package/dist/esm/utils/mapList.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +12 -5
- package/dist/esm/utils/savedViewMenu.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2562 -1886
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +533 -273
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
- package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
- package/src/components/CnBoardView/CnBoardView.vue +58 -5
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
- package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +42 -9
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +174 -4
- package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
- package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
- package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
- package/src/composables/useRefLabels.js +25 -5
- package/src/css/dialog.css +53 -3
- package/src/css/index-page.css +26 -1
- package/src/css/look-board-detail.css +36 -15
- package/src/css/look-board-index.css +69 -3
- package/src/css/look-board.css +30 -2
- package/src/integrations/registry.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +11 -5
- package/src/store/plugins/liveUpdates.js +3 -1
- package/src/store/plugins/logs.js +2 -1
- package/src/utils/detailActionModel.js +27 -0
- package/src/utils/fillTemplate.js +13 -5
- package/src/utils/headerFieldChips.js +21 -2
- package/src/utils/mapList.js +26 -11
- package/src/utils/savedViewMenu.js +12 -5
- 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.
|
|
3
|
+
"version": "2.112.0",
|
|
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": {
|
|
@@ -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:
|
|
608
|
-
font-weight:
|
|
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(
|
|
614
|
-
gap:
|
|
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:
|
|
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
|
|
493
|
-
* a list of fields joined by a middle dot; `pillColors` maps a
|
|
494
|
-
* value to a badge variant
|
|
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
|
-
/**
|
|
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
|
}
|
|
@@ -104,7 +104,11 @@
|
|
|
104
104
|
:colorKey="pill.colorKey"
|
|
105
105
|
:colorMap="pill.colorMap"
|
|
106
106
|
:variant="pill.variant"
|
|
107
|
-
:data-testid="`cn-detail-page-pill-${pill.key}`"
|
|
107
|
+
:data-testid="`cn-detail-page-pill-${pill.key}`">
|
|
108
|
+
<template v-if="pill.icon" #icon>
|
|
109
|
+
<CnIcon :name="pill.icon" :size="14" class="cn-detail-page__pill-icon" />
|
|
110
|
+
</template>
|
|
111
|
+
</CnStatusBadge>
|
|
108
112
|
</div>
|
|
109
113
|
<!-- Type name as a small eyebrow above the record name, so
|
|
110
114
|
the header reads "the record" not "the kind of record"
|
|
@@ -485,7 +489,11 @@
|
|
|
485
489
|
:colorKey="pill.colorKey"
|
|
486
490
|
:colorMap="pill.colorMap"
|
|
487
491
|
:variant="pill.variant"
|
|
488
|
-
:data-testid="`cn-detail-page-pill-${pill.key}`"
|
|
492
|
+
:data-testid="`cn-detail-page-pill-${pill.key}`">
|
|
493
|
+
<template v-if="pill.icon" #icon>
|
|
494
|
+
<CnIcon :name="pill.icon" :size="14" class="cn-detail-page__pill-icon" />
|
|
495
|
+
</template>
|
|
496
|
+
</CnStatusBadge>
|
|
489
497
|
</div>
|
|
490
498
|
<CnBreadcrumbs
|
|
491
499
|
v-if="breadcrumbCrumbs.length > 0"
|
|
@@ -1257,6 +1265,7 @@ import {
|
|
|
1257
1265
|
MAX_QUICK_ACTIONS,
|
|
1258
1266
|
normalisePinnedAction,
|
|
1259
1267
|
pillCountSource,
|
|
1268
|
+
pillLook,
|
|
1260
1269
|
resolveCountPill,
|
|
1261
1270
|
resolveNextStep,
|
|
1262
1271
|
resolvePill,
|
|
@@ -2497,9 +2506,12 @@ export default {
|
|
|
2497
2506
|
* `field`: `text`, a constant ("Inwoner"), or `countFrom`
|
|
2498
2507
|
* (`{ register, schema, filter? }`) with `countLabel` (`{ one, other }`
|
|
2499
2508
|
* with `{count}`), a count of a related list ("1 lopende zaak") that
|
|
2500
|
-
* shows nothing at 0.
|
|
2509
|
+
* shows nothing at 0. With `resolve: { register, schema, labelField?,
|
|
2510
|
+
* colorField?, iconField? }` the pill shows the referenced record's
|
|
2511
|
+
* label, takes its tone from that record's colour (an NL Design System
|
|
2512
|
+
* hue such as `red`, or a badge role) and shows its icon.
|
|
2501
2513
|
*
|
|
2502
|
-
* @type {{field?: string, text?: string, countFrom?: object, countLabel?: object, colorMap?: object, labels?: object, variant?: string}|null}
|
|
2514
|
+
* @type {{field?: string, text?: string, countFrom?: object, countLabel?: object, colorMap?: object, labels?: object, variant?: string, resolve?: object}|null}
|
|
2503
2515
|
*/
|
|
2504
2516
|
typePill: {
|
|
2505
2517
|
type: Object,
|
|
@@ -2940,6 +2952,8 @@ export default {
|
|
|
2940
2952
|
pillCountSeq: 0,
|
|
2941
2953
|
/** The labels of the records pills with `resolve` point at, by pill key. */
|
|
2942
2954
|
pillRefLabels: {},
|
|
2955
|
+
/** The colour and icon names read off the records pills with `resolve` point at, by pill key. */
|
|
2956
|
+
pillRefLooks: {},
|
|
2943
2957
|
/** Sequence of the last pill label lookup, so a late answer for another record is dropped. */
|
|
2944
2958
|
pillRefSeq: 0,
|
|
2945
2959
|
/** `register/schema/id` the read-state call was last made for, so it is sent once per load. */
|
|
@@ -3488,10 +3502,11 @@ export default {
|
|
|
3488
3502
|
// `resolve`: the referenced record's label replaces the id, and
|
|
3489
3503
|
// keys the colour too ("Woo-verzoek", not a uuid).
|
|
3490
3504
|
const resolved = this.pillRefLabels[key]
|
|
3505
|
+
const look = pillLook(this.pillRefLooks[key])
|
|
3491
3506
|
if (typeof resolved === 'string' && resolved !== '') {
|
|
3492
|
-
return { ...pill, key, label: this.effectiveTranslate(resolved), colorKey: resolved }
|
|
3507
|
+
return { ...pill, ...look, key, label: this.effectiveTranslate(resolved), colorKey: resolved }
|
|
3493
3508
|
}
|
|
3494
|
-
return { ...pill, key, label: this.effectiveTranslate(pill.label) }
|
|
3509
|
+
return { ...pill, ...look, key, label: this.effectiveTranslate(pill.label) }
|
|
3495
3510
|
})
|
|
3496
3511
|
.filter(Boolean)
|
|
3497
3512
|
},
|
|
@@ -3526,7 +3541,8 @@ export default {
|
|
|
3526
3541
|
.filter(([, config]) => config && config.resolve && config.resolve.register && config.resolve.schema && typeof config.field === 'string')
|
|
3527
3542
|
.map(([key, config]) => {
|
|
3528
3543
|
const pill = resolvePill({ field: config.field }, this.resolvedObject)
|
|
3529
|
-
|
|
3544
|
+
const { register, schema, labelField, colorField, iconField } = config.resolve
|
|
3545
|
+
return pill ? [key, register, schema, labelField || '', pill.colorKey, colorField || '', iconField || ''] : null
|
|
3530
3546
|
})
|
|
3531
3547
|
.filter(Boolean)
|
|
3532
3548
|
},
|
|
@@ -5340,6 +5356,7 @@ export default {
|
|
|
5340
5356
|
const seq = ++this.pillRefSeq
|
|
5341
5357
|
if (targets.length === 0) {
|
|
5342
5358
|
this.pillRefLabels = {}
|
|
5359
|
+
this.pillRefLooks = {}
|
|
5343
5360
|
return
|
|
5344
5361
|
}
|
|
5345
5362
|
if (!this._pillRefResolver) {
|
|
@@ -5352,14 +5369,30 @@ export default {
|
|
|
5352
5369
|
})
|
|
5353
5370
|
}
|
|
5354
5371
|
const labels = {}
|
|
5355
|
-
|
|
5356
|
-
|
|
5372
|
+
const looks = {}
|
|
5373
|
+
const field = async (register, schema, id, name) => {
|
|
5374
|
+
if (!name) {
|
|
5375
|
+
return null
|
|
5376
|
+
}
|
|
5377
|
+
const found = await this._pillRefResolver.resolve(register, schema, [id], name, { strict: true }).catch(() => ({}))
|
|
5378
|
+
return found && typeof found[id] === 'string' ? found[id] : null
|
|
5379
|
+
}
|
|
5380
|
+
await Promise.all(targets.map(async ([key, register, schema, labelField, id, colorField, iconField]) => {
|
|
5381
|
+
const [found, color, icon] = await Promise.all([
|
|
5382
|
+
this._pillRefResolver.resolve(register, schema, [id], labelField).catch(() => ({})),
|
|
5383
|
+
field(register, schema, id, colorField),
|
|
5384
|
+
field(register, schema, id, iconField),
|
|
5385
|
+
])
|
|
5357
5386
|
if (found && typeof found[id] === 'string') {
|
|
5358
5387
|
labels[key] = found[id]
|
|
5359
5388
|
}
|
|
5389
|
+
if (color || icon) {
|
|
5390
|
+
looks[key] = { color, icon }
|
|
5391
|
+
}
|
|
5360
5392
|
}))
|
|
5361
5393
|
if (seq === this.pillRefSeq) {
|
|
5362
5394
|
this.pillRefLabels = labels
|
|
5395
|
+
this.pillRefLooks = looks
|
|
5363
5396
|
}
|
|
5364
5397
|
},
|
|
5365
5398
|
|
|
@@ -147,6 +147,39 @@
|
|
|
147
147
|
hidden
|
|
148
148
|
data-testid="cn-detail-widget-props-pending" />
|
|
149
149
|
|
|
150
|
+
<!-- A mount-mode leaf whose descriptor asks for the library card
|
|
151
|
+
(`chrome: "card"`): the host draws the card, its title with the
|
|
152
|
+
count the leaf reports, the leaf's actions in the title row and the
|
|
153
|
+
library empty state. The leaf reports them through the `onHeader`
|
|
154
|
+
mount prop and renders only its rows. -->
|
|
155
|
+
<CnWidgetWrapper
|
|
156
|
+
v-else-if="wrapsMountLeaf"
|
|
157
|
+
:title="leafCardTitle"
|
|
158
|
+
titleIconPosition="left"
|
|
159
|
+
:showActions="false"
|
|
160
|
+
:showRefresh="false"
|
|
161
|
+
:showRequestFeature="false"
|
|
162
|
+
:headerLink="cardHeaderLink"
|
|
163
|
+
class="cn-detail-page__leaf-card"
|
|
164
|
+
data-testid="cn-detail-widget-leaf-card">
|
|
165
|
+
<template v-if="leafCardIcon" #title-icon>
|
|
166
|
+
<CnIcon :name="leafCardIcon" :size="20" />
|
|
167
|
+
</template>
|
|
168
|
+
<template v-if="rendererHeader.count !== null" #title-meta>
|
|
169
|
+
<span class="cn-detail-page__catalog-count" data-testid="cn-detail-widget-header-count">{{ rendererHeader.count }}</span>
|
|
170
|
+
</template>
|
|
171
|
+
<template v-if="hasRendererHeaderItems" #actions>
|
|
172
|
+
<CnDetailWidgetHeaderItems :header="rendererHeader" />
|
|
173
|
+
</template>
|
|
174
|
+
<CnWidgetEmptyState
|
|
175
|
+
v-if="rendererHeader.empty"
|
|
176
|
+
:name="rendererHeader.empty.name"
|
|
177
|
+
:description="rendererHeader.empty.description" />
|
|
178
|
+
<CnLeafMountHost
|
|
179
|
+
:provider="integrationProvider"
|
|
180
|
+
:mountProps="integrationMountProps" />
|
|
181
|
+
</CnWidgetWrapper>
|
|
182
|
+
|
|
150
183
|
<CnLeafMountHost
|
|
151
184
|
v-else-if="isMountIntegration"
|
|
152
185
|
:provider="integrationProvider"
|
|
@@ -293,6 +326,7 @@ import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
|
|
|
293
326
|
import CnObjectFilesWidget from '../CnObjectFilesWidget/CnObjectFilesWidget.vue'
|
|
294
327
|
import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
|
|
295
328
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
329
|
+
import CnWidgetEmptyState from '../CnWidgetEmptyState/CnWidgetEmptyState.vue'
|
|
296
330
|
import CnDetailWidgetHeaderItems from './CnDetailWidgetHeaderItems.vue'
|
|
297
331
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
298
332
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
@@ -373,6 +407,7 @@ export default {
|
|
|
373
407
|
CnObjectDataWidget,
|
|
374
408
|
CnObjectGeoWidget,
|
|
375
409
|
CnRelatedObjectsWidget,
|
|
410
|
+
CnWidgetEmptyState,
|
|
376
411
|
CnWidgetWrapper,
|
|
377
412
|
NcActionButton,
|
|
378
413
|
CnEmptyContent,
|
|
@@ -628,7 +663,7 @@ export default {
|
|
|
628
663
|
* `header` event (screens-relation-pages): a link and a note,
|
|
629
664
|
* with the list's total already filled in.
|
|
630
665
|
*/
|
|
631
|
-
rendererHeader: { link: null, note: '', actions: [], pill: null },
|
|
666
|
+
rendererHeader: { link: null, note: '', actions: [], pill: null, count: null, empty: null },
|
|
632
667
|
}
|
|
633
668
|
},
|
|
634
669
|
|
|
@@ -844,6 +879,34 @@ export default {
|
|
|
844
879
|
&& typeof provider.unmount === 'function'
|
|
845
880
|
},
|
|
846
881
|
|
|
882
|
+
/**
|
|
883
|
+
* Whether a mount-mode leaf gets the library card: on a card surface,
|
|
884
|
+
* grid cell included, when its descriptor asks for it (`chrome:
|
|
885
|
+
* "card"`). Opt-in, because a leaf that draws its own card would
|
|
886
|
+
* otherwise sit in two.
|
|
887
|
+
*
|
|
888
|
+
* @return {boolean}
|
|
889
|
+
* @spec openspec/changes/integration-leaf-card/specs/integration-leaf-card/spec.md#requirement-a-mount-mode-leaf-can-take-the-library-card
|
|
890
|
+
*/
|
|
891
|
+
wrapsMountLeaf() {
|
|
892
|
+
return !this.isBare && this.isMountIntegration && this.integrationProvider.chrome === 'card'
|
|
893
|
+
},
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* The leaf card's title: the manifest's, else the descriptor's label.
|
|
897
|
+
*
|
|
898
|
+
* @return {string} The title in the user's language.
|
|
899
|
+
*/
|
|
900
|
+
leafCardTitle() {
|
|
901
|
+
const title = widgetTitleOf(this.widget)
|
|
902
|
+
return title ? this.translateLabel(title) : (this.integrationProvider?.label || '')
|
|
903
|
+
},
|
|
904
|
+
|
|
905
|
+
/** @return {string} The leaf card's icon: the manifest's, else the descriptor's. */
|
|
906
|
+
leafCardIcon() {
|
|
907
|
+
return this.widget?.icon || this.integrationProvider?.icon || ''
|
|
908
|
+
},
|
|
909
|
+
|
|
847
910
|
/**
|
|
848
911
|
* Whether this render is using a provider's widget as its bare surface.
|
|
849
912
|
*
|
|
@@ -1017,6 +1080,8 @@ export default {
|
|
|
1017
1080
|
// one that reads `objectId`.
|
|
1018
1081
|
integrationContext: this.widgetIntegrationContext,
|
|
1019
1082
|
...this.resolvedWidgetProps,
|
|
1083
|
+
// A carded leaf reports its title row and empty state here.
|
|
1084
|
+
...(this.wrapsMountLeaf ? { onHeader: this.onRendererHeader } : {}),
|
|
1020
1085
|
}
|
|
1021
1086
|
},
|
|
1022
1087
|
|
|
@@ -1221,11 +1286,12 @@ export default {
|
|
|
1221
1286
|
|
|
1222
1287
|
methods: {
|
|
1223
1288
|
/**
|
|
1224
|
-
* Take the header a content-only renderer,
|
|
1225
|
-
* titled card, reports.
|
|
1289
|
+
* Take the header a content-only renderer, an app widget in the
|
|
1290
|
+
* titled card, or a carded mount-mode leaf (`onHeader`) reports.
|
|
1226
1291
|
*
|
|
1227
|
-
* @param {{link?: object|null, note?: string, action?: {label: string, icon?: string, run: () => unknown}|null, actions?: Array<{label: string, icon?: string, run: () => unknown}
|
|
1292
|
+
* @param {{link?: object|null, note?: string, action?: {label: string, icon?: string, run: () => unknown}|null, actions?: Array<{label: string, icon?: string, run: () => unknown}>, count?: number, empty?: {name: string, description?: string}|null}} header The header parts.
|
|
1228
1293
|
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
|
|
1294
|
+
* @spec openspec/changes/integration-leaf-card/specs/integration-leaf-card/spec.md#requirement-a-carded-leaf-reports-its-count-actions-and-empty-state
|
|
1229
1295
|
*/
|
|
1230
1296
|
onRendererHeader(header) {
|
|
1231
1297
|
const link = header && header.link && typeof header.link === 'object' ? header.link : null
|
|
@@ -1234,7 +1300,11 @@ export default {
|
|
|
1234
1300
|
// `action` (one) and `actions` (several) both count, `action` first.
|
|
1235
1301
|
const actions = [header && header.action, ...((header && Array.isArray(header.actions)) ? header.actions : [])].filter(usable)
|
|
1236
1302
|
const pill = header && header.pill && typeof header.pill.label === 'string' && header.pill.label !== '' ? header.pill : null
|
|
1237
|
-
|
|
1303
|
+
const count = header && Number.isFinite(header.count) ? header.count : null
|
|
1304
|
+
const empty = header && header.empty && typeof header.empty.name === 'string' && header.empty.name !== ''
|
|
1305
|
+
? { name: header.empty.name, description: typeof header.empty.description === 'string' ? header.empty.description : '' }
|
|
1306
|
+
: null
|
|
1307
|
+
this.rendererHeader = { link, note, actions, pill, count, empty }
|
|
1238
1308
|
},
|
|
1239
1309
|
|
|
1240
1310
|
/**
|
|
@@ -1372,6 +1442,31 @@ export default {
|
|
|
1372
1442
|
white-space: nowrap;
|
|
1373
1443
|
}
|
|
1374
1444
|
|
|
1445
|
+
/* A leaf card in a narrow cell keeps its title and count: the title row
|
|
1446
|
+
wraps and the actions move under it instead of squeezing the title out. */
|
|
1447
|
+
.cn-detail-page__leaf-card :deep(.cn-widget-wrapper__header) {
|
|
1448
|
+
flex-wrap: wrap;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.cn-detail-page__leaf-card :deep(.cn-widget-wrapper__header-left) {
|
|
1452
|
+
flex: 1 0 auto;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/* The count beside a card title: the grey 20px badge of the tab strip. */
|
|
1456
|
+
.cn-detail-page__catalog-count {
|
|
1457
|
+
background-color: var(--color-border);
|
|
1458
|
+
border-radius: 10px;
|
|
1459
|
+
box-sizing: border-box;
|
|
1460
|
+
color: var(--color-main-text);
|
|
1461
|
+
display: inline-block;
|
|
1462
|
+
font-size: 12px;
|
|
1463
|
+
font-weight: 600;
|
|
1464
|
+
line-height: 20px;
|
|
1465
|
+
min-width: 20px;
|
|
1466
|
+
padding: 0 6px;
|
|
1467
|
+
text-align: center;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1375
1470
|
.cn-detail-page__catalog-note {
|
|
1376
1471
|
color: var(--color-text-maxcontrast);
|
|
1377
1472
|
font-size: 14px;
|