@milaboratories/uikit 2.16.3 → 2.17.1
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/.turbo/turbo-build.log +31 -23
- package/.turbo/turbo-formatter$colon$check.log +1 -1
- package/.turbo/turbo-linter$colon$check.log +1 -1
- package/CHANGELOG.md +32 -0
- package/dist/components/PlAlert/PlAlert.js.map +1 -1
- package/dist/components/PlAlert/PlAlert.vue.d.ts +4 -1
- package/dist/components/PlAlert/PlAlert.vue.d.ts.map +1 -1
- package/dist/components/PlAlert/PlAlert.vue2.js +27 -24
- package/dist/components/PlAlert/PlAlert.vue2.js.map +1 -1
- package/dist/components/PlAlert/pl-alert.css +1 -1
- package/dist/components/PlDropdown/PlDropdown.js +5 -4
- package/dist/components/PlDropdown/PlDropdown.js.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.style.css +1 -0
- package/dist/components/PlDropdown/PlDropdown.style.js +5 -0
- package/dist/components/PlDropdown/PlDropdown.style.js.map +1 -0
- package/dist/components/PlDropdown/PlDropdown.vue.d.ts +10 -2
- package/dist/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.vue2.js +136 -110
- package/dist/components/PlDropdown/PlDropdown.vue2.js.map +1 -1
- package/dist/components/PlDropdownMulti/PlDropdownMulti.js.map +1 -1
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts +10 -2
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue2.js +123 -84
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue2.js.map +1 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.js.map +1 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts +12 -5
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue2.js +8 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue2.js.map +1 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.js.map +1 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts +8 -2
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.vue2.js +8 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.vue2.js.map +1 -1
- package/dist/components/PlTooltip/PlTooltip.js.map +1 -1
- package/dist/components/PlTooltip/PlTooltip.style.css +1 -1
- package/dist/components/PlTooltip/PlTooltip.style.js +1 -1
- package/dist/components/PlTooltip/PlTooltip.style.js.map +1 -1
- package/dist/components/PlTooltip/PlTooltip.vue.d.ts.map +1 -1
- package/dist/components/PlTooltip/PlTooltip.vue2.js +1 -1
- package/dist/components/PlTooltip/PlTooltip.vue2.js.map +1 -1
- package/dist/components/PlValueLabel/PlValueLabel.js +7 -0
- package/dist/components/PlValueLabel/PlValueLabel.js.map +1 -0
- package/dist/components/PlValueLabel/PlValueLabel.style.css +1 -0
- package/dist/components/PlValueLabel/PlValueLabel.style.js +5 -0
- package/dist/components/PlValueLabel/PlValueLabel.style.js.map +1 -0
- package/dist/components/PlValueLabel/PlValueLabel.vue.d.ts +45 -0
- package/dist/components/PlValueLabel/PlValueLabel.vue.d.ts.map +1 -0
- package/dist/components/PlValueLabel/PlValueLabel.vue2.js +37 -0
- package/dist/components/PlValueLabel/PlValueLabel.vue2.js.map +1 -0
- package/dist/components/PlValueLabel/__tests__/PlValueLabel.spec.d.ts +2 -0
- package/dist/components/PlValueLabel/__tests__/PlValueLabel.spec.d.ts.map +1 -0
- package/dist/components/PlValueLabel/index.d.ts +2 -0
- package/dist/components/PlValueLabel/index.d.ts.map +1 -0
- package/dist/components/PlValueLabel/index.js +1 -0
- package/dist/helpers/columnValue.d.ts +25 -0
- package/dist/helpers/columnValue.d.ts.map +1 -0
- package/dist/helpers/columnValue.js +17 -0
- package/dist/helpers/columnValue.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/src/__tests__/setup.ts +10 -2
- package/src/components/PlAlert/PlAlert.vue +23 -5
- package/src/components/PlAlert/pl-alert.scss +6 -0
- package/src/components/PlDropdown/PlDropdown.vue +49 -8
- package/src/components/PlDropdown/__tests__/PlDropdown.spec.ts +95 -0
- package/src/components/PlDropdownMulti/PlDropdownMulti.vue +50 -9
- package/src/components/PlDropdownMulti/__tests__/PlDropdownMulti.spec.ts +47 -0
- package/src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue +10 -3
- package/src/components/PlDropdownMultiRef/__tests__/PlDropdownMultiRef.spec.ts +2 -2
- package/src/components/PlDropdownRef/PlDropdownRef.vue +10 -3
- package/src/components/PlDropdownRef/__tests__/PlDropdownRef.spec.ts +2 -2
- package/src/components/PlTooltip/PlTooltip.vue +1 -0
- package/src/components/PlValueLabel/PlValueLabel.vue +80 -0
- package/src/components/PlValueLabel/__tests__/PlValueLabel.spec.ts +64 -0
- package/src/components/PlValueLabel/index.ts +1 -0
- package/src/helpers/columnValue.ts +43 -0
- package/src/index.ts +2 -0
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
watchPostEffect,
|
|
20
20
|
} from "vue";
|
|
21
21
|
import SvgRequired from "../../assets/images/required.svg?raw";
|
|
22
|
+
import { missingValueText, missingValueTooltip, pickedValueText } from "../../helpers/columnValue";
|
|
23
|
+
import type { MissingValueLabel, MissingValueTooltip } from "../../helpers/columnValue";
|
|
22
24
|
import { getErrorMessage } from "../../helpers/error.ts";
|
|
23
25
|
import { deepEqual, deepIncludes } from "../../helpers/objects";
|
|
24
26
|
import { normalizeListOptions } from "../../helpers/utils";
|
|
@@ -69,9 +71,16 @@ const props = withDefaults(
|
|
|
69
71
|
placeholder?: string;
|
|
70
72
|
/**
|
|
71
73
|
* Text shown in a chip when a `modelValue` entry is missing from `options` —
|
|
72
|
-
* e.g. its upstream source was deleted. Rendered italic in error color.
|
|
74
|
+
* e.g. its upstream source was deleted. Rendered italic in error color. One text for every
|
|
75
|
+
* such value, or a function giving it per value. By default, the value a column value id
|
|
76
|
+
* picks, marked as not available, or "Value not available".
|
|
73
77
|
*/
|
|
74
|
-
missingValueLabel?:
|
|
78
|
+
missingValueLabel?: MissingValueLabel;
|
|
79
|
+
/**
|
|
80
|
+
* Explains a selected value missing from the options in a tooltip over it: one text for every
|
|
81
|
+
* such value, or a function giving the content per value.
|
|
82
|
+
*/
|
|
83
|
+
missingValueTooltip?: MissingValueTooltip;
|
|
75
84
|
/**
|
|
76
85
|
* If `true`, the dropdown component is marked as required.
|
|
77
86
|
*/
|
|
@@ -100,7 +109,8 @@ const props = withDefaults(
|
|
|
100
109
|
helper: undefined,
|
|
101
110
|
error: undefined,
|
|
102
111
|
placeholder: "...",
|
|
103
|
-
missingValueLabel:
|
|
112
|
+
missingValueLabel: undefined,
|
|
113
|
+
missingValueTooltip: undefined,
|
|
104
114
|
required: false,
|
|
105
115
|
disabled: false,
|
|
106
116
|
options: undefined,
|
|
@@ -134,7 +144,8 @@ const normalizedOptionsRef = computed(() => normalizeListOptions(props.options ?
|
|
|
134
144
|
|
|
135
145
|
const selectedOptionsRef = computed(() => {
|
|
136
146
|
// While options load (`options === undefined`), hide chips for unresolved values
|
|
137
|
-
// to avoid a "missing" flash that resolves once options arrive.
|
|
147
|
+
// to avoid a "missing" flash that resolves once options arrive. A picked column value
|
|
148
|
+
// is shown by its own label meanwhile.
|
|
138
149
|
const optionsLoaded = props.options !== undefined;
|
|
139
150
|
return selectedValuesRef.value
|
|
140
151
|
.map((v) => {
|
|
@@ -143,16 +154,20 @@ const selectedOptionsRef = computed(() => {
|
|
|
143
154
|
// Found: render the option's own (normalized) label verbatim — matches
|
|
144
155
|
// PlDropdown. `opt.label` is always a string (possibly ""), never an object,
|
|
145
156
|
// so there is no raw-value leak to guard against.
|
|
146
|
-
return { ...opt, isMissing: false };
|
|
157
|
+
return { ...opt, isMissing: false, hint: undefined };
|
|
147
158
|
}
|
|
148
159
|
if (!optionsLoaded) {
|
|
149
|
-
|
|
160
|
+
const label = pickedValueText(v);
|
|
161
|
+
return label === undefined
|
|
162
|
+
? undefined
|
|
163
|
+
: { value: v, label, isMissing: false, hint: undefined };
|
|
150
164
|
}
|
|
151
165
|
// Honor empty `missingValueLabel` verbatim — caller's explicit "render nothing".
|
|
152
166
|
return {
|
|
153
167
|
value: v,
|
|
154
|
-
label: props.missingValueLabel,
|
|
168
|
+
label: missingValueText(v, props.missingValueLabel),
|
|
155
169
|
isMissing: true,
|
|
170
|
+
hint: missingValueTooltip(v, props.missingValueTooltip),
|
|
156
171
|
};
|
|
157
172
|
})
|
|
158
173
|
.filter((v): v is NonNullable<typeof v> => v !== undefined);
|
|
@@ -327,7 +342,20 @@ watchPostEffect(() => {
|
|
|
327
342
|
@click.stop="data.open = true"
|
|
328
343
|
@close="unselectOption(opt.value)"
|
|
329
344
|
>
|
|
330
|
-
|
|
345
|
+
<!-- The hover stops here: the tooltip of the chip itself would close this one. -->
|
|
346
|
+
<PlTooltip
|
|
347
|
+
v-if="opt.hint !== undefined"
|
|
348
|
+
element="span"
|
|
349
|
+
position="top"
|
|
350
|
+
data-testid="pl-dropdown-multi-chip-hint"
|
|
351
|
+
@mouseover.stop
|
|
352
|
+
>
|
|
353
|
+
<span>{{ opt.label }}</span>
|
|
354
|
+
<template #tooltip>
|
|
355
|
+
<component :is="opt.hint" />
|
|
356
|
+
</template>
|
|
357
|
+
</PlTooltip>
|
|
358
|
+
<template v-else>{{ opt.label }}</template>
|
|
331
359
|
</PlChip>
|
|
332
360
|
</div>
|
|
333
361
|
|
|
@@ -384,7 +412,20 @@ watchPostEffect(() => {
|
|
|
384
412
|
small
|
|
385
413
|
@close="unselectOption(opt.value)"
|
|
386
414
|
>
|
|
387
|
-
|
|
415
|
+
<!-- The hover stops here: the tooltip of the chip itself would close this one. -->
|
|
416
|
+
<PlTooltip
|
|
417
|
+
v-if="opt.hint !== undefined"
|
|
418
|
+
element="span"
|
|
419
|
+
position="top"
|
|
420
|
+
data-testid="pl-dropdown-multi-chip-hint"
|
|
421
|
+
@mouseover.stop
|
|
422
|
+
>
|
|
423
|
+
<span>{{ opt.label }}</span>
|
|
424
|
+
<template #tooltip>
|
|
425
|
+
<component :is="opt.hint" />
|
|
426
|
+
</template>
|
|
427
|
+
</PlTooltip>
|
|
428
|
+
<template v-else>{{ opt.label }}</template>
|
|
388
429
|
</PlChip>
|
|
389
430
|
</div>
|
|
390
431
|
<DropdownListItem
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { mount, flushPromises } from "@vue/test-utils";
|
|
4
|
+
import { createColumnValueId, createGlobalPObjectId } from "@platforma-sdk/model";
|
|
5
|
+
import type { MissingValueTooltip } from "../../../helpers/columnValue";
|
|
4
6
|
import PlDropdownMulti from "../PlDropdownMulti.vue";
|
|
5
7
|
|
|
6
8
|
describe("PlDropdownMulti", () => {
|
|
@@ -167,4 +169,49 @@ describe("PlDropdownMulti", () => {
|
|
|
167
169
|
expect(wrapper.findAll(".pl-dropdown-multi__chip--missing").length).toBe(0);
|
|
168
170
|
expect(wrapper.findAll(".pl-chip").length).toBe(1);
|
|
169
171
|
});
|
|
172
|
+
|
|
173
|
+
describe("with column value ids", () => {
|
|
174
|
+
const valueOf = (value: string) =>
|
|
175
|
+
createColumnValueId({
|
|
176
|
+
column: createGlobalPObjectId("samples", "metadata.antigen"),
|
|
177
|
+
value,
|
|
178
|
+
});
|
|
179
|
+
const bsa = valueOf("BSA");
|
|
180
|
+
const plastic = valueOf("Plastic");
|
|
181
|
+
|
|
182
|
+
it("shows picked values while options are loading", async () => {
|
|
183
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
184
|
+
props: { modelValue: [bsa, plastic], options: undefined },
|
|
185
|
+
});
|
|
186
|
+
await flushPromises();
|
|
187
|
+
expect(wrapper.findAll(".pl-chip").map((chip) => chip.text())).toEqual(["BSA", "Plastic"]);
|
|
188
|
+
expect(wrapper.findAll(".pl-dropdown-multi__chip--missing").length).toBe(0);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("marks each picked value the options lack as not available", async () => {
|
|
192
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
193
|
+
props: { modelValue: [bsa, plastic], options: [{ label: "BSA", value: bsa }] },
|
|
194
|
+
});
|
|
195
|
+
await flushPromises();
|
|
196
|
+
const missingChips = wrapper.findAll(".pl-dropdown-multi__chip--missing");
|
|
197
|
+
expect(missingChips.map((chip) => chip.text())).toEqual(["Plastic — not available"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("explains each missing value by the hint of the app", async () => {
|
|
201
|
+
const hint: MissingValueTooltip = (value) =>
|
|
202
|
+
value === plastic ? () => "Samples has no Plastic" : undefined;
|
|
203
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
204
|
+
props: { modelValue: [bsa, plastic], options: [], missingValueTooltip: hint },
|
|
205
|
+
attachTo: document.body,
|
|
206
|
+
});
|
|
207
|
+
await flushPromises();
|
|
208
|
+
const hinted = wrapper.findAll("[data-testid='pl-dropdown-multi-chip-hint']");
|
|
209
|
+
expect(hinted.map((chip) => chip.text())).toEqual(["Plastic — not available"]);
|
|
210
|
+
|
|
211
|
+
await hinted[0].trigger("mouseover");
|
|
212
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
213
|
+
expect(document.body.textContent).toContain("Samples has no Plastic");
|
|
214
|
+
wrapper.unmount();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
170
217
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
+
import type { MissingValueLabel, MissingValueTooltip } from "../../helpers/columnValue";
|
|
2
3
|
import type { ModelRef, RefOption } from "../../types";
|
|
3
4
|
import PlDropdownMulti from "../PlDropdownMulti/PlDropdownMulti.vue";
|
|
4
5
|
import { computed, useSlots } from "vue";
|
|
@@ -41,9 +42,14 @@ const props = withDefaults(
|
|
|
41
42
|
placeholder?: string;
|
|
42
43
|
/**
|
|
43
44
|
* Text shown in a chip when a `modelValue` entry's upstream block no longer
|
|
44
|
-
* exists. Defaults to "
|
|
45
|
+
* exists: one text, or a function giving it per value. Defaults to "Value not available".
|
|
45
46
|
*/
|
|
46
|
-
missingValueLabel?:
|
|
47
|
+
missingValueLabel?: MissingValueLabel;
|
|
48
|
+
/**
|
|
49
|
+
* Explains a selected value missing from the options in a tooltip over it: one text for every
|
|
50
|
+
* such value, or a function giving the content per value.
|
|
51
|
+
*/
|
|
52
|
+
missingValueTooltip?: MissingValueTooltip;
|
|
47
53
|
/**
|
|
48
54
|
* If `true`, the dropdown component is marked as required.
|
|
49
55
|
*/
|
|
@@ -59,7 +65,8 @@ const props = withDefaults(
|
|
|
59
65
|
helper: undefined,
|
|
60
66
|
error: undefined,
|
|
61
67
|
placeholder: "...",
|
|
62
|
-
missingValueLabel:
|
|
68
|
+
missingValueLabel: undefined,
|
|
69
|
+
missingValueTooltip: undefined,
|
|
63
70
|
required: false,
|
|
64
71
|
disabled: false,
|
|
65
72
|
options: undefined,
|
|
@@ -84,10 +84,10 @@ describe("PlDropdownMultiRef", () => {
|
|
|
84
84
|
|
|
85
85
|
const missing = wrapper.findAll(".pl-dropdown-multi__chip--missing");
|
|
86
86
|
expect(missing.length).toBe(1);
|
|
87
|
-
expect(missing[0].text()).toContain("
|
|
87
|
+
expect(missing[0].text()).toContain("Value not available");
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
it("forwards a caller-supplied missingValueLabel that overrides the
|
|
90
|
+
it("forwards a caller-supplied missingValueLabel that overrides the default", async () => {
|
|
91
91
|
const wrapper = mount(PlDropdownMultiRef, {
|
|
92
92
|
props: {
|
|
93
93
|
modelValue: [{ __isRef: true as const, blockId: "deleted", name: "Ref to deleted block" }],
|
|
@@ -9,6 +9,7 @@ export default {
|
|
|
9
9
|
|
|
10
10
|
<script lang="ts" setup generic="M = ModelRef">
|
|
11
11
|
import { computed, useSlots } from "vue";
|
|
12
|
+
import type { MissingValueLabel, MissingValueTooltip } from "../../helpers/columnValue";
|
|
12
13
|
import type { ListOption, ModelRef, RefOption } from "../../types";
|
|
13
14
|
import { PlDropdown } from "../PlDropdown";
|
|
14
15
|
|
|
@@ -54,9 +55,14 @@ const props = withDefaults(
|
|
|
54
55
|
placeholder?: string;
|
|
55
56
|
/**
|
|
56
57
|
* Text shown in the field when the referenced upstream block / output no longer
|
|
57
|
-
* exists. Defaults to "
|
|
58
|
+
* exists: one text, or a function giving it per value. Defaults to "Value not available".
|
|
58
59
|
*/
|
|
59
|
-
missingValueLabel?:
|
|
60
|
+
missingValueLabel?: MissingValueLabel;
|
|
61
|
+
/**
|
|
62
|
+
* Explains a selected value missing from the options in a tooltip over it: one text for every
|
|
63
|
+
* such value, or a function giving the content per value.
|
|
64
|
+
*/
|
|
65
|
+
missingValueTooltip?: MissingValueTooltip;
|
|
60
66
|
/**
|
|
61
67
|
* Enables a button to clear the selected value (default: false)
|
|
62
68
|
*/
|
|
@@ -80,7 +86,8 @@ const props = withDefaults(
|
|
|
80
86
|
loadingOptionsHelper: undefined,
|
|
81
87
|
error: undefined,
|
|
82
88
|
placeholder: "...",
|
|
83
|
-
missingValueLabel:
|
|
89
|
+
missingValueLabel: undefined,
|
|
90
|
+
missingValueTooltip: undefined,
|
|
84
91
|
clearable: false,
|
|
85
92
|
required: false,
|
|
86
93
|
disabled: false,
|
|
@@ -77,10 +77,10 @@ describe("PlDropdownRef", () => {
|
|
|
77
77
|
|
|
78
78
|
const missing = wrapper.find(".input-value--missing");
|
|
79
79
|
expect(missing.exists()).toBe(true);
|
|
80
|
-
expect(missing.text()).toBe("
|
|
80
|
+
expect(missing.text()).toBe("Value not available");
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it("forwards a caller-supplied missingValueLabel that overrides the
|
|
83
|
+
it("forwards a caller-supplied missingValueLabel that overrides the default", async () => {
|
|
84
84
|
const wrapper = mount(PlDropdownRef, {
|
|
85
85
|
props: {
|
|
86
86
|
modelValue: {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A selected value shown as text, the way a dropdown shows it: by the label of its option, or,
|
|
4
|
+
* for a column value id, by the value it picks. A value the options lack is marked as not
|
|
5
|
+
* available, in error color, with the reason in a tooltip when there is a hint for it.
|
|
6
|
+
*/
|
|
7
|
+
export default {
|
|
8
|
+
name: "PlValueLabel",
|
|
9
|
+
};
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts" setup generic="M = unknown">
|
|
13
|
+
import { computed } from "vue";
|
|
14
|
+
import { missingValueText, missingValueTooltip, pickedValueText } from "../../helpers/columnValue";
|
|
15
|
+
import type { MissingValueLabel, MissingValueTooltip } from "../../helpers/columnValue";
|
|
16
|
+
import { deepEqual } from "../../helpers/objects";
|
|
17
|
+
import { normalizeListOptions } from "../../helpers/utils";
|
|
18
|
+
import type { ListOption } from "../../types";
|
|
19
|
+
import { PlTooltip } from "../PlTooltip";
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
/** The value to show. */
|
|
23
|
+
value: M;
|
|
24
|
+
/**
|
|
25
|
+
* Options the value is picked from. While they are `undefined` — not loaded yet — the value
|
|
26
|
+
* is never marked as not available.
|
|
27
|
+
*/
|
|
28
|
+
options?: Readonly<ListOption<M>[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Text shown when the options lack the value: one text, or a function giving it per value. By
|
|
31
|
+
* default, the value a column value id picks, marked as not available, or "Value not
|
|
32
|
+
* available".
|
|
33
|
+
*/
|
|
34
|
+
missingValueLabel?: MissingValueLabel;
|
|
35
|
+
/**
|
|
36
|
+
* Explains the value in a tooltip over it while the options lack it: one text, or a function
|
|
37
|
+
* giving the content per value.
|
|
38
|
+
*/
|
|
39
|
+
missingValueTooltip?: MissingValueTooltip;
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
const option = computed(() =>
|
|
43
|
+
props.options === undefined
|
|
44
|
+
? undefined
|
|
45
|
+
: normalizeListOptions(props.options).find((o) => deepEqual(o.value, props.value)),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const isMissing = computed(() => props.options !== undefined && option.value === undefined);
|
|
49
|
+
|
|
50
|
+
const hint = computed(() =>
|
|
51
|
+
isMissing.value ? missingValueTooltip(props.value, props.missingValueTooltip) : undefined,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const text = computed(() => {
|
|
55
|
+
if (option.value !== undefined) return option.value.label;
|
|
56
|
+
if (isMissing.value) {
|
|
57
|
+
return missingValueText(props.value, props.missingValueLabel);
|
|
58
|
+
}
|
|
59
|
+
return pickedValueText(props.value) ?? String(props.value);
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<PlTooltip v-if="hint !== undefined" element="span" position="top">
|
|
65
|
+
<span :class="$style.missing" data-testid="pl-value-label">{{ text }}</span>
|
|
66
|
+
<template #tooltip>
|
|
67
|
+
<component :is="hint" />
|
|
68
|
+
</template>
|
|
69
|
+
</PlTooltip>
|
|
70
|
+
<span v-else :class="{ [$style.missing]: isMissing }" data-testid="pl-value-label">{{
|
|
71
|
+
text
|
|
72
|
+
}}</span>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<style module>
|
|
76
|
+
.missing {
|
|
77
|
+
font-style: italic;
|
|
78
|
+
color: var(--txt-error);
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { flushPromises, mount } from "@vue/test-utils";
|
|
4
|
+
import { createColumnValueId, createGlobalPObjectId } from "@platforma-sdk/model";
|
|
5
|
+
import type { MissingValueTooltip } from "../../../helpers/columnValue";
|
|
6
|
+
import PlValueLabel from "../PlValueLabel.vue";
|
|
7
|
+
|
|
8
|
+
const treated = createColumnValueId({
|
|
9
|
+
column: createGlobalPObjectId("samples", "metadata.condition"),
|
|
10
|
+
value: "treated",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const isMarkedMissing = (wrapper: ReturnType<typeof mount>) =>
|
|
14
|
+
wrapper.find("[data-testid='pl-value-label']").classes().length > 0;
|
|
15
|
+
|
|
16
|
+
describe("PlValueLabel", () => {
|
|
17
|
+
it("shows the label of the value's option", () => {
|
|
18
|
+
const wrapper = mount(PlValueLabel, {
|
|
19
|
+
props: { value: treated, options: [{ label: "Treated", value: treated }] },
|
|
20
|
+
});
|
|
21
|
+
expect(wrapper.text()).toBe("Treated");
|
|
22
|
+
expect(isMarkedMissing(wrapper)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("shows the value a column value id picks while options are not loaded", () => {
|
|
26
|
+
const wrapper = mount(PlValueLabel, { props: { value: treated } });
|
|
27
|
+
expect(wrapper.text()).toBe("treated");
|
|
28
|
+
expect(isMarkedMissing(wrapper)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("marks a value the options lack as not available", () => {
|
|
32
|
+
const wrapper = mount(PlValueLabel, { props: { value: treated, options: [] } });
|
|
33
|
+
expect(wrapper.text()).toBe("treated — not available");
|
|
34
|
+
expect(isMarkedMissing(wrapper)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("keeps the caller's missingValueLabel", () => {
|
|
38
|
+
const wrapper = mount(PlValueLabel, {
|
|
39
|
+
props: { value: treated, options: [], missingValueLabel: "Gone" },
|
|
40
|
+
});
|
|
41
|
+
expect(wrapper.text()).toBe("Gone");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("explains a missing value by the hint of the app in a tooltip", async () => {
|
|
45
|
+
const hint: MissingValueTooltip = () => () => "Samples does not provide it";
|
|
46
|
+
const wrapper = mount(PlValueLabel, {
|
|
47
|
+
props: { value: treated, options: [], missingValueTooltip: hint },
|
|
48
|
+
attachTo: document.body,
|
|
49
|
+
});
|
|
50
|
+
expect(wrapper.text()).toBe("treated — not available");
|
|
51
|
+
expect(isMarkedMissing(wrapper)).toBe(true);
|
|
52
|
+
|
|
53
|
+
await wrapper.find("[data-testid='pl-value-label']").trigger("mouseover");
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
55
|
+
await flushPromises();
|
|
56
|
+
expect(document.body.textContent).toContain("Samples does not provide it");
|
|
57
|
+
wrapper.unmount();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("shows a plain value as is", () => {
|
|
61
|
+
const wrapper = mount(PlValueLabel, { props: { value: "R1" } });
|
|
62
|
+
expect(wrapper.text()).toBe("R1");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PlValueLabel } from "./PlValueLabel.vue";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { columnValueLabel, isColumnValueId } from "@platforma-sdk/model";
|
|
2
|
+
import type { FunctionalComponent } from "vue";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Text shown in place of a selected value missing from its options: one for every such value,
|
|
6
|
+
* or one per value, `undefined` leaving the usual text.
|
|
7
|
+
*/
|
|
8
|
+
export type MissingValueLabel = string | ((value: unknown) => string | undefined);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tooltip over a selected value missing from its options: one text for every such value, or
|
|
12
|
+
* content per value, `undefined` when there is nothing to tell about it.
|
|
13
|
+
*/
|
|
14
|
+
export type MissingValueTooltip = string | ((value: unknown) => FunctionalComponent | undefined);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The value a column value id picks, as text; `undefined` for any other value. A picked value
|
|
18
|
+
* keeps a readable label while the options that would carry it are not loaded or no longer
|
|
19
|
+
* hold it.
|
|
20
|
+
*/
|
|
21
|
+
export function pickedValueText(value: unknown): string | undefined {
|
|
22
|
+
return isColumnValueId(value) ? columnValueLabel(value) : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Text shown for a selected value missing from the options: the one `label` gives for it, else
|
|
27
|
+
* the picked value of a column value id marked as not available, else a generic one.
|
|
28
|
+
*/
|
|
29
|
+
export function missingValueText(value: unknown, label: MissingValueLabel | undefined): string {
|
|
30
|
+
const given = typeof label === "function" ? label(value) : label;
|
|
31
|
+
if (given !== undefined) return given;
|
|
32
|
+
const picked = pickedValueText(value);
|
|
33
|
+
return picked === undefined ? "Value not available" : `${picked} — not available`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Content of the tooltip `tooltip` gives over a selected value missing from the options. */
|
|
37
|
+
export function missingValueTooltip(
|
|
38
|
+
value: unknown,
|
|
39
|
+
tooltip: MissingValueTooltip | undefined,
|
|
40
|
+
): FunctionalComponent | undefined {
|
|
41
|
+
if (typeof tooltip === "string") return () => tooltip;
|
|
42
|
+
return tooltip?.(value);
|
|
43
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -74,6 +74,8 @@ export * from "./components/PlTextField";
|
|
|
74
74
|
export * from "./components/PlToggleSwitch";
|
|
75
75
|
export * from "./components/PlTooltip";
|
|
76
76
|
export * from "./components/PlColumnLabel";
|
|
77
|
+
export * from "./components/PlValueLabel";
|
|
78
|
+
export type { MissingValueLabel, MissingValueTooltip } from "./helpers/columnValue";
|
|
77
79
|
|
|
78
80
|
export * from "./components/PlFileDialog";
|
|
79
81
|
export * from "./components/PlFileInput";
|