@milaboratories/uikit 2.14.24 → 2.15.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/.turbo/turbo-build.log +21 -21
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +16 -0
- package/dist/components/PlAlert/pl-alert.css +1 -1
- package/dist/components/PlDropdown/PlDropdown.js.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.vue.d.ts +5 -0
- package/dist/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.vue2.js +66 -62
- package/dist/components/PlDropdown/PlDropdown.vue2.js.map +1 -1
- package/dist/components/PlDropdown/pl-dropdown.css +1 -1
- package/dist/components/PlDropdownMulti/PlDropdownMulti.js.map +1 -1
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts +5 -0
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue2.js +75 -58
- package/dist/components/PlDropdownMulti/PlDropdownMulti.vue2.js.map +1 -1
- package/dist/components/PlDropdownMulti/pl-dropdown-multi.css +1 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.js.map +1 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts +6 -0
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue2.js +1 -0
- 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 +5 -0
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.vue2.js +1 -0
- package/dist/components/PlDropdownRef/PlDropdownRef.vue2.js.map +1 -1
- package/package.json +4 -4
- package/src/components/PlAlert/pl-alert.scss +3 -1
- package/src/components/PlDropdown/PlDropdown.vue +25 -14
- package/src/components/PlDropdown/__tests__/PlDropdown.spec.ts +119 -0
- package/src/components/PlDropdown/pl-dropdown.scss +9 -0
- package/src/components/PlDropdownMulti/PlDropdownMulti.vue +32 -4
- package/src/components/PlDropdownMulti/__tests__/PlDropdownMulti.spec.ts +135 -0
- package/src/components/PlDropdownMulti/pl-dropdown-multi.scss +11 -0
- package/src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue +6 -0
- package/src/components/PlDropdownMultiRef/__tests__/PlDropdownMultiRef.spec.ts +42 -0
- package/src/components/PlDropdownRef/PlDropdownRef.vue +6 -0
- package/src/components/PlDropdownRef/__tests__/PlDropdownRef.spec.ts +53 -0
|
@@ -24,6 +24,11 @@ type __VLS_Props = {
|
|
|
24
24
|
* Placeholder text shown when no value is selected.
|
|
25
25
|
*/
|
|
26
26
|
placeholder?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Text shown in a chip when a `modelValue` entry's upstream block no longer
|
|
29
|
+
* exists. Defaults to "Upstream value removed".
|
|
30
|
+
*/
|
|
31
|
+
missingValueLabel?: string;
|
|
27
32
|
/**
|
|
28
33
|
* If `true`, the dropdown component is marked as required.
|
|
29
34
|
*/
|
|
@@ -65,6 +70,7 @@ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}
|
|
|
65
70
|
required: boolean;
|
|
66
71
|
disabled: boolean;
|
|
67
72
|
helper: string;
|
|
73
|
+
missingValueLabel: string;
|
|
68
74
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
69
75
|
declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
70
76
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownMultiRef.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlDropdownMultiRef.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue"],"names":[],"mappings":"AAyFA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAevD,KAAK,WAAW,GAAG;IACf;;OAEG;IACH,UAAU,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC;IACnC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAoCJ,iBAAS,cAAc;WAiDT,OAAO,IAA6B;;yBAVrB,GAAG;;;;EAe/B;AAaD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;aAlIP,QAAQ,CAAC,SAAS,EAAE,CAAC;WAJvB,MAAM;;gBAJF,QAAQ,EAAE,GAAG,SAAS;iBAoBpB,MAAM;cAST,OAAO;cAIP,OAAO;YArBT,MAAM;uBAaK,MAAM;6EA0H5B,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownMultiRef.vue_vue_type_script_setup_true_lang.js","names":["$emit"],"sources":["../../../src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ModelRef, RefOption } from \"../../types\";\nimport PlDropdownMulti from \"../PlDropdownMulti/PlDropdownMulti.vue\";\nimport { computed, useSlots } from \"vue\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst slots: any = useSlots();\n\ndefineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: ModelRef[] | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected values.\n */\n modelValue: ModelRef[] | undefined;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<RefOption[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n }>(),\n {\n modelValue: () => [],\n label: undefined,\n helper: undefined,\n error: undefined,\n placeholder: \"...\",\n required: false,\n disabled: false,\n options: undefined,\n },\n);\n\nconst options = computed(() =>\n props.options?.map((opt) => ({\n label: opt.label,\n value: opt.ref,\n })),\n);\n</script>\n\n<template>\n <PlDropdownMulti\n v-bind=\"props\"\n :options=\"options\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdownMulti>\n</template>\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlDropdownMultiRef.vue_vue_type_script_setup_true_lang.js","names":["$emit"],"sources":["../../../src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ModelRef, RefOption } from \"../../types\";\nimport PlDropdownMulti from \"../PlDropdownMulti/PlDropdownMulti.vue\";\nimport { computed, useSlots } from \"vue\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst slots: any = useSlots();\n\ndefineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: ModelRef[] | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected values.\n */\n modelValue: ModelRef[] | undefined;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available options for the dropdown\n */\n options?: Readonly<RefOption[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in a chip when a `modelValue` entry's upstream block no longer\n * exists. Defaults to \"Upstream value removed\".\n */\n missingValueLabel?: string;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n }>(),\n {\n modelValue: () => [],\n label: undefined,\n helper: undefined,\n error: undefined,\n placeholder: \"...\",\n missingValueLabel: \"Upstream value removed\",\n required: false,\n disabled: false,\n options: undefined,\n },\n);\n\nconst options = computed(() =>\n props.options?.map((opt) => ({\n label: opt.label,\n value: opt.ref,\n })),\n);\n</script>\n\n<template>\n <PlDropdownMulti\n v-bind=\"props\"\n :options=\"options\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdownMulti>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EAMA,IAAM,IAAa,GAAU,EASvB,IAAQ,GAqDR,IAAU,QACd,EAAM,SAAS,KAAK,OAAS;GAC3B,OAAO,EAAI;GACX,OAAO,EAAI;GACZ,EAAE,CACJ;yBAIC,EAQkB,GARlB,EACU,GAAK;GACZ,SAAS,EAAA;GACT,uBAAkB,AAAA,EAAA,QAAA,MAAEA,EAAAA,MAAK,qBAAsB,EAAM;mBAEtC,EAAA,EAAK,CAAC,UAAA;SAAU;eACP,CAAvB,EAAuB,EAAA,QAAA,UAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownRef.js","names":[],"sources":["../../../src/components/PlDropdownRef/PlDropdownRef.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdownRef\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = ModelRef\">\nimport { computed, useSlots } from \"vue\";\nimport type { ListOption, ModelRef, RefOption } from \"../../types\";\nimport { PlDropdown } from \"../PlDropdown\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst slots: any = useSlots();\n\ndefineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected ref of the dropdown.\n */\n modelValue: M | undefined;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available ref options for the dropdown\n */\n options?: Readonly<RefOption[] | ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n optionSize: \"small\",\n options: undefined,\n },\n);\n\nconst options = computed(() =>\n props.options?.map((opt) =>\n \"ref\" in opt\n ? ({\n label: opt.label,\n value: opt.ref,\n group: opt.group,\n } as ListOption<M>)\n : opt,\n ),\n);\n</script>\n\n<template>\n <PlDropdown\n v-bind=\"props\"\n :options=\"options\"\n :loading-options-helper=\"loadingOptionsHelper\"\n :arrow-icon-large=\"disabled ? 'link-disabled' : 'link'\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdown>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlDropdownRef.js","names":[],"sources":["../../../src/components/PlDropdownRef/PlDropdownRef.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdownRef\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = ModelRef\">\nimport { computed, useSlots } from \"vue\";\nimport type { ListOption, ModelRef, RefOption } from \"../../types\";\nimport { PlDropdown } from \"../PlDropdown\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst slots: any = useSlots();\n\ndefineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected ref of the dropdown.\n */\n modelValue: M | undefined;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available ref options for the dropdown\n */\n options?: Readonly<RefOption[] | ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in the field when the referenced upstream block / output no longer\n * exists. Defaults to \"Upstream value removed\".\n */\n missingValueLabel?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n missingValueLabel: \"Upstream value removed\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n optionSize: \"small\",\n options: undefined,\n },\n);\n\nconst options = computed(() =>\n props.options?.map((opt) =>\n \"ref\" in opt\n ? ({\n label: opt.label,\n value: opt.ref,\n group: opt.group,\n } as ListOption<M>)\n : opt,\n ),\n);\n</script>\n\n<template>\n <PlDropdown\n v-bind=\"props\"\n :options=\"options\"\n :loading-options-helper=\"loadingOptionsHelper\"\n :arrow-icon-large=\"disabled ? 'link-disabled' : 'link'\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdown>\n</template>\n"],"mappings":""}
|
|
@@ -34,6 +34,11 @@ declare const _default: <M = ModelRef>(__VLS_props: NonNullable<Awaited<typeof _
|
|
|
34
34
|
* Placeholder text shown when no value is selected.
|
|
35
35
|
*/
|
|
36
36
|
placeholder?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Text shown in the field when the referenced upstream block / output no longer
|
|
39
|
+
* exists. Defaults to "Upstream value removed".
|
|
40
|
+
*/
|
|
41
|
+
missingValueLabel?: string;
|
|
37
42
|
/**
|
|
38
43
|
* Enables a button to clear the selected value (default: false)
|
|
39
44
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownRef.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdownRef/PlDropdownRef.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlDropdownRef.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDropdownRef/PlDropdownRef.vue"],"names":[],"mappings":"AAyHA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAInE;;GAEG;yBACc,CAAC,GAAG,QAAQ,EAC5B,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,eAAe,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EACjE;WA6LO,mBAAmB,CAAC;;;QAhLxB;;WAEG;oBACS,CAAC,GAAG,SAAS;QACzB;;WAEG;gBACK,MAAM;QACd;;WAEG;kBACO,QAAQ,CAAC,SAAS,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD;;WAEG;iBACM,MAAM;QACf;;WAEG;+BACoB,MAAM;QAC7B;;WAEG;gBACK,OAAO;QACf;;WAEG;sBACW,MAAM;QACpB;;;WAGG;4BACiB,MAAM;QAC1B;;WAEG;oBACS,OAAO;QACnB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;qBACU,OAAO,GAAG,QAAQ;mBAgI2D,CAAC,4BAA2B;oBACzG,OAAO,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI;WAClD,GAAG;;yBAlCkB,GAAG;;cArJ1B,mBAAmB,SAAS,CAAC,GAAG,SAAS,KAAG,IAAI;EA2LlD,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAE;AAxMzE,wBAwM4E;AAG5E,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlDropdownRef.vue_vue_type_script_setup_true_lang.js","names":["$emit"],"sources":["../../../src/components/PlDropdownRef/PlDropdownRef.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdownRef\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = ModelRef\">\nimport { computed, useSlots } from \"vue\";\nimport type { ListOption, ModelRef, RefOption } from \"../../types\";\nimport { PlDropdown } from \"../PlDropdown\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst slots: any = useSlots();\n\ndefineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected ref of the dropdown.\n */\n modelValue: M | undefined;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available ref options for the dropdown\n */\n options?: Readonly<RefOption[] | ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n optionSize: \"small\",\n options: undefined,\n },\n);\n\nconst options = computed(() =>\n props.options?.map((opt) =>\n \"ref\" in opt\n ? ({\n label: opt.label,\n value: opt.ref,\n group: opt.group,\n } as ListOption<M>)\n : opt,\n ),\n);\n</script>\n\n<template>\n <PlDropdown\n v-bind=\"props\"\n :options=\"options\"\n :loading-options-helper=\"loadingOptionsHelper\"\n :arrow-icon-large=\"disabled ? 'link-disabled' : 'link'\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdown>\n</template>\n"],"mappings":";;;;CAKE,MAAM
|
|
1
|
+
{"version":3,"file":"PlDropdownRef.vue_vue_type_script_setup_true_lang.js","names":["$emit"],"sources":["../../../src/components/PlDropdownRef/PlDropdownRef.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A component for selecting one value from a list of options\n */\nexport default {\n name: \"PlDropdownRef\",\n};\n</script>\n\n<script lang=\"ts\" setup generic=\"M = ModelRef\">\nimport { computed, useSlots } from \"vue\";\nimport type { ListOption, ModelRef, RefOption } from \"../../types\";\nimport { PlDropdown } from \"../PlDropdown\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst slots: any = useSlots();\n\ndefineEmits<{\n /**\n * Emitted when the model value is updated.\n */\n (e: \"update:modelValue\", value: M | undefined): void;\n}>();\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The current selected ref of the dropdown.\n */\n modelValue: M | undefined;\n /**\n * The label text for the dropdown field (optional)\n */\n label?: string;\n /**\n * List of available ref options for the dropdown\n */\n options?: Readonly<RefOption[] | ListOption<M>[]>;\n /**\n * A helper text displayed below the dropdown when there are no errors (optional).\n */\n helper?: string;\n /**\n * A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).\n */\n loadingOptionsHelper?: string;\n /**\n * Error message displayed below the dropdown (optional)\n */\n error?: unknown;\n /**\n * Placeholder text shown when no value is selected.\n */\n placeholder?: string;\n /**\n * Text shown in the field when the referenced upstream block / output no longer\n * exists. Defaults to \"Upstream value removed\".\n */\n missingValueLabel?: string;\n /**\n * Enables a button to clear the selected value (default: false)\n */\n clearable?: boolean;\n /**\n * If `true`, the dropdown component is marked as required.\n */\n required?: boolean;\n /**\n * If `true`, the dropdown component is disabled and cannot be interacted with.\n */\n disabled?: boolean;\n /**\n * Option list item size\n */\n optionSize?: \"small\" | \"medium\";\n }>(),\n {\n label: \"\",\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n missingValueLabel: \"Upstream value removed\",\n clearable: false,\n required: false,\n disabled: false,\n arrowIcon: undefined,\n optionSize: \"small\",\n options: undefined,\n },\n);\n\nconst options = computed(() =>\n props.options?.map((opt) =>\n \"ref\" in opt\n ? ({\n label: opt.label,\n value: opt.ref,\n group: opt.group,\n } as ListOption<M>)\n : opt,\n ),\n);\n</script>\n\n<template>\n <PlDropdown\n v-bind=\"props\"\n :options=\"options\"\n :loading-options-helper=\"loadingOptionsHelper\"\n :arrow-icon-large=\"disabled ? 'link-disabled' : 'link'\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdown>\n</template>\n"],"mappings":";;;;CAKE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;EAUR,IAAM,IAAa,GAAU,EASvB,IAAQ,GAoER,IAAU,QACd,EAAM,SAAS,KAAK,MAClB,SAAS,IACJ;GACC,OAAO,EAAI;GACX,OAAO,EAAI;GACX,OAAO,EAAI;GACZ,GACD,EACL,CACF;yBAIC,EAUa,EAAA,EAAA,EAVb,EACU,GAAK;GACZ,SAAS,EAAA;GACT,0BAAwB,EAAA;GACxB,oBAAkB,EAAA,WAAQ,kBAAA;GAC1B,uBAAkB,AAAA,EAAA,QAAA,MAAEA,EAAAA,MAAK,qBAAsB,EAAM;mBAEtC,EAAA,EAAK,CAAC,UAAA;SAAU;eACP,CAAvB,EAAuB,EAAA,QAAA,UAAA,CAAA,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/uikit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"resize-observer-polyfill": "^1.5.1",
|
|
32
32
|
"sortablejs": "^1.15.6",
|
|
33
33
|
"vue": "3.5.24",
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
34
|
+
"@platforma-sdk/model": "1.78.9",
|
|
35
|
+
"@milaboratories/helpers": "1.14.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@vitest/coverage-istanbul": "^4.1.3",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"svgo": "^3.3.2",
|
|
41
41
|
"typescript": "~5.9.3",
|
|
42
42
|
"vitest": "^4.1.3",
|
|
43
|
-
"@milaboratories/ts-builder": "1.5.0",
|
|
44
43
|
"@milaboratories/build-configs": "2.0.0",
|
|
44
|
+
"@milaboratories/ts-builder": "1.5.0",
|
|
45
45
|
"@milaboratories/ts-configs": "1.2.3"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
@@ -70,7 +70,9 @@
|
|
|
70
70
|
|
|
71
71
|
&__close-btn {
|
|
72
72
|
position: absolute;
|
|
73
|
-
|
|
73
|
+
// Centre the 40px button on the first content row. The row's centre sits at
|
|
74
|
+
// 24px (12px padding + half the 24px row); top = 24 − 20 (half the button).
|
|
75
|
+
top: 4px;
|
|
74
76
|
right: 12px;
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -68,6 +68,11 @@ const props = withDefaults(
|
|
|
68
68
|
* Placeholder text shown when no value is selected.
|
|
69
69
|
*/
|
|
70
70
|
placeholder?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Text shown in the field when `modelValue` has no matching option — e.g. its
|
|
73
|
+
* upstream block was deleted. Rendered italic in error color.
|
|
74
|
+
*/
|
|
75
|
+
missingValueLabel?: string;
|
|
71
76
|
/**
|
|
72
77
|
* Enables a button to clear the selected value (default: false)
|
|
73
78
|
*/
|
|
@@ -113,6 +118,7 @@ const props = withDefaults(
|
|
|
113
118
|
error: undefined,
|
|
114
119
|
showErrorMessage: true,
|
|
115
120
|
placeholder: "...",
|
|
121
|
+
missingValueLabel: "Value not available",
|
|
116
122
|
clearable: false,
|
|
117
123
|
required: false,
|
|
118
124
|
disabled: false,
|
|
@@ -168,6 +174,14 @@ const selectedIndex = computed(() => {
|
|
|
168
174
|
return (props.options ?? []).findIndex((o) => deepEqual(o.value, props.modelValue));
|
|
169
175
|
});
|
|
170
176
|
|
|
177
|
+
const hasValue = computed(() => {
|
|
178
|
+
return props.modelValue !== undefined && props.modelValue !== null;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const isMissing = computed(() => {
|
|
182
|
+
return hasValue.value && selectedIndex.value === -1 && !isLoadingOptions.value;
|
|
183
|
+
});
|
|
184
|
+
|
|
171
185
|
const computedError = computed(() => {
|
|
172
186
|
if (isLoadingOptions.value) {
|
|
173
187
|
return undefined;
|
|
@@ -177,10 +191,8 @@ const computedError = computed(() => {
|
|
|
177
191
|
return getErrorMessage(props.error);
|
|
178
192
|
}
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
194
|
+
// The field renders the missing state via `missingValueLabel`; no need to
|
|
195
|
+
// duplicate it as a helper error.
|
|
184
196
|
return undefined;
|
|
185
197
|
});
|
|
186
198
|
|
|
@@ -195,22 +207,16 @@ const optionsRef = computed<LOption<M>[]>(() =>
|
|
|
195
207
|
|
|
196
208
|
const textValue = computed(() => {
|
|
197
209
|
const options = unref(optionsRef);
|
|
198
|
-
|
|
199
210
|
const item: ListOption | undefined = options.find((o) => deepEqual(o.value, props.modelValue));
|
|
200
|
-
|
|
201
|
-
return item?.label || props.modelValue; // @todo show inner value?
|
|
211
|
+
return item?.label;
|
|
202
212
|
});
|
|
203
213
|
|
|
204
214
|
const computedPlaceholder = computed(() => {
|
|
205
|
-
if (!data.open &&
|
|
215
|
+
if (!data.open && hasValue.value) {
|
|
206
216
|
return "";
|
|
207
217
|
}
|
|
208
218
|
|
|
209
|
-
return
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const hasValue = computed(() => {
|
|
213
|
-
return props.modelValue !== undefined && props.modelValue !== null;
|
|
219
|
+
return textValue.value ? String(textValue.value) : props.placeholder;
|
|
214
220
|
});
|
|
215
221
|
|
|
216
222
|
const filteredRef = computed(() => {
|
|
@@ -365,7 +371,12 @@ watchPostEffect(() => {
|
|
|
365
371
|
/>
|
|
366
372
|
|
|
367
373
|
<div v-if="!data.open" class="input-value">
|
|
368
|
-
<LongText
|
|
374
|
+
<LongText v-if="isMissing" class="input-value--missing">
|
|
375
|
+
{{ missingValueLabel }}
|
|
376
|
+
</LongText>
|
|
377
|
+
<LongText v-else-if="textValue !== undefined">
|
|
378
|
+
{{ textValue }}
|
|
379
|
+
</LongText>
|
|
369
380
|
</div>
|
|
370
381
|
|
|
371
382
|
<div class="pl-dropdown__controls">
|
|
@@ -29,4 +29,123 @@ describe("PlDropdown", () => {
|
|
|
29
29
|
|
|
30
30
|
expect(await wrapper.findAll(".dropdown-list-item").length).toBe(0); // options are closed after click
|
|
31
31
|
});
|
|
32
|
+
|
|
33
|
+
it("renders missingValueLabel when modelValue is not in options", async () => {
|
|
34
|
+
const wrapper = mount(PlDropdown, {
|
|
35
|
+
props: {
|
|
36
|
+
modelValue: { __isRef: true as const, blockId: "deleted", name: "out" },
|
|
37
|
+
"onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
|
|
38
|
+
options: [
|
|
39
|
+
{ text: "Option 1", value: 1 },
|
|
40
|
+
{ text: "Option 2", value: 2 },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await flushPromises();
|
|
46
|
+
|
|
47
|
+
const missing = wrapper.find(".input-value--missing");
|
|
48
|
+
expect(missing.exists()).toBe(true);
|
|
49
|
+
expect(missing.text()).toBe("Value not available");
|
|
50
|
+
|
|
51
|
+
// Raw value (JSON-stringified object) must not leak into the DOM.
|
|
52
|
+
expect(wrapper.html()).not.toContain('"blockId"');
|
|
53
|
+
expect(wrapper.html()).not.toContain('"__isRef"');
|
|
54
|
+
|
|
55
|
+
// The redundant generic error helper no longer appears.
|
|
56
|
+
const error = wrapper.find(".pl-dropdown__error");
|
|
57
|
+
expect(error.exists() ? error.text() : "").not.toContain(
|
|
58
|
+
"The selected value is not one of the options",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("uses custom missingValueLabel when provided", async () => {
|
|
63
|
+
const wrapper = mount(PlDropdown, {
|
|
64
|
+
props: {
|
|
65
|
+
modelValue: 99,
|
|
66
|
+
missingValueLabel: "Gone, kaput",
|
|
67
|
+
options: [{ text: "Option 1", value: 1 }],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await flushPromises();
|
|
72
|
+
expect(wrapper.find(".input-value--missing").text()).toBe("Gone, kaput");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("does not render missingValueLabel while options are loading (options undefined)", async () => {
|
|
76
|
+
const wrapper = mount(PlDropdown, {
|
|
77
|
+
props: { modelValue: 99, options: undefined },
|
|
78
|
+
});
|
|
79
|
+
await flushPromises();
|
|
80
|
+
expect(wrapper.find(".input-value--missing").exists()).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("renders missingValueLabel alongside .disabled root when disabled with missing value", async () => {
|
|
84
|
+
const wrapper = mount(PlDropdown, {
|
|
85
|
+
props: {
|
|
86
|
+
modelValue: 99,
|
|
87
|
+
disabled: true,
|
|
88
|
+
options: [{ text: "Option 1", value: 1 }],
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
await flushPromises();
|
|
92
|
+
// The SCSS disabled override (`color: var(--dis-01)`) needs both `.disabled`
|
|
93
|
+
// on the root and `.input-value--missing` on the label. jsdom doesn't resolve
|
|
94
|
+
// CSS specificity, so we only assert the hooks exist — not the computed color.
|
|
95
|
+
expect(wrapper.find(".pl-dropdown.disabled").exists()).toBe(true);
|
|
96
|
+
expect(wrapper.find(".input-value--missing").exists()).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("shows placeholder (not blank) when modelValue is null", async () => {
|
|
100
|
+
const wrapper = mount(PlDropdown, {
|
|
101
|
+
props: {
|
|
102
|
+
modelValue: null,
|
|
103
|
+
placeholder: "Pick one",
|
|
104
|
+
options: [{ text: "Option 1", value: 1 }],
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
await flushPromises();
|
|
108
|
+
// null means "no value selected" — must render the placeholder, not blank.
|
|
109
|
+
expect(wrapper.find("input").attributes("placeholder")).toBe("Pick one");
|
|
110
|
+
// Must not trigger the missing-value branch.
|
|
111
|
+
expect(wrapper.find(".input-value--missing").exists()).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("clears the missing-state label when options later contain the value", async () => {
|
|
115
|
+
const wrapper = mount(PlDropdown, {
|
|
116
|
+
props: {
|
|
117
|
+
modelValue: 1,
|
|
118
|
+
options: [{ text: "Option 2", value: 2 }],
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
await flushPromises();
|
|
122
|
+
expect(wrapper.find(".input-value--missing").exists()).toBe(true);
|
|
123
|
+
|
|
124
|
+
// Simulates the upstream block coming back: options now contain the value.
|
|
125
|
+
await wrapper.setProps({
|
|
126
|
+
options: [
|
|
127
|
+
{ text: "Option 1", value: 1 },
|
|
128
|
+
{ text: "Option 2", value: 2 },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
await flushPromises();
|
|
132
|
+
expect(wrapper.find(".input-value--missing").exists()).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("renders the clear button when clearable + missing, and clears the model on click", async () => {
|
|
136
|
+
const wrapper = mount(PlDropdown, {
|
|
137
|
+
props: {
|
|
138
|
+
modelValue: 99,
|
|
139
|
+
clearable: true,
|
|
140
|
+
"onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
|
|
141
|
+
options: [{ text: "Option 1", value: 1 }],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
await flushPromises();
|
|
145
|
+
const clearBtn = wrapper.find(".clear");
|
|
146
|
+
expect(clearBtn.exists()).toBe(true);
|
|
147
|
+
await clearBtn.trigger("click");
|
|
148
|
+
await flushPromises();
|
|
149
|
+
expect(wrapper.props("modelValue")).toBeUndefined();
|
|
150
|
+
});
|
|
32
151
|
});
|
|
@@ -108,6 +108,11 @@
|
|
|
108
108
|
cursor: inherit;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
.input-value--missing {
|
|
112
|
+
font-style: italic;
|
|
113
|
+
color: var(--txt-error);
|
|
114
|
+
}
|
|
115
|
+
|
|
111
116
|
input {
|
|
112
117
|
min-height: calc(var(--control-height) - 2px);
|
|
113
118
|
line-height: 20px;
|
|
@@ -234,5 +239,9 @@
|
|
|
234
239
|
.input-value {
|
|
235
240
|
color: var(--dis-01);
|
|
236
241
|
}
|
|
242
|
+
|
|
243
|
+
.input-value--missing {
|
|
244
|
+
color: var(--dis-01);
|
|
245
|
+
}
|
|
237
246
|
}
|
|
238
247
|
}
|
|
@@ -67,6 +67,11 @@ const props = withDefaults(
|
|
|
67
67
|
* Placeholder text shown when no value is selected.
|
|
68
68
|
*/
|
|
69
69
|
placeholder?: string;
|
|
70
|
+
/**
|
|
71
|
+
* 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.
|
|
73
|
+
*/
|
|
74
|
+
missingValueLabel?: string;
|
|
70
75
|
/**
|
|
71
76
|
* If `true`, the dropdown component is marked as required.
|
|
72
77
|
*/
|
|
@@ -95,6 +100,7 @@ const props = withDefaults(
|
|
|
95
100
|
helper: undefined,
|
|
96
101
|
error: undefined,
|
|
97
102
|
placeholder: "...",
|
|
103
|
+
missingValueLabel: "Value not available",
|
|
98
104
|
required: false,
|
|
99
105
|
disabled: false,
|
|
100
106
|
options: undefined,
|
|
@@ -127,9 +133,29 @@ const placeholderRef = computed(() => {
|
|
|
127
133
|
const normalizedOptionsRef = computed(() => normalizeListOptions(props.options ?? []));
|
|
128
134
|
|
|
129
135
|
const selectedOptionsRef = computed(() => {
|
|
136
|
+
// While options load (`options === undefined`), hide chips for unresolved values
|
|
137
|
+
// to avoid a "missing" flash that resolves once options arrive.
|
|
138
|
+
const optionsLoaded = props.options !== undefined;
|
|
130
139
|
return selectedValuesRef.value
|
|
131
|
-
.map((v) =>
|
|
132
|
-
|
|
140
|
+
.map((v) => {
|
|
141
|
+
const opt = normalizedOptionsRef.value.find((o) => deepEqual(o.value, v));
|
|
142
|
+
if (opt) {
|
|
143
|
+
// Found: render the option's own (normalized) label verbatim — matches
|
|
144
|
+
// PlDropdown. `opt.label` is always a string (possibly ""), never an object,
|
|
145
|
+
// so there is no raw-value leak to guard against.
|
|
146
|
+
return { ...opt, isMissing: false };
|
|
147
|
+
}
|
|
148
|
+
if (!optionsLoaded) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
// Honor empty `missingValueLabel` verbatim — caller's explicit "render nothing".
|
|
152
|
+
return {
|
|
153
|
+
value: v,
|
|
154
|
+
label: props.missingValueLabel,
|
|
155
|
+
isMissing: true,
|
|
156
|
+
};
|
|
157
|
+
})
|
|
158
|
+
.filter((v): v is NonNullable<typeof v> => v !== undefined);
|
|
133
159
|
});
|
|
134
160
|
|
|
135
161
|
const filteredOptionsRef = computed(() => {
|
|
@@ -292,12 +318,13 @@ watchPostEffect(() => {
|
|
|
292
318
|
<PlChip
|
|
293
319
|
v-for="(opt, i) in selectedOptionsRef"
|
|
294
320
|
:key="i"
|
|
321
|
+
:class="{ 'pl-dropdown-multi__chip--missing': opt.isMissing }"
|
|
295
322
|
closeable
|
|
296
323
|
small
|
|
297
324
|
@click.stop="data.open = true"
|
|
298
325
|
@close="unselectOption(opt.value)"
|
|
299
326
|
>
|
|
300
|
-
{{ opt.label
|
|
327
|
+
{{ opt.label }}
|
|
301
328
|
</PlChip>
|
|
302
329
|
</div>
|
|
303
330
|
|
|
@@ -331,11 +358,12 @@ watchPostEffect(() => {
|
|
|
331
358
|
<PlChip
|
|
332
359
|
v-for="(opt, i) in selectedOptionsRef"
|
|
333
360
|
:key="i"
|
|
361
|
+
:class="{ 'pl-dropdown-multi__chip--missing': opt.isMissing }"
|
|
334
362
|
closeable
|
|
335
363
|
small
|
|
336
364
|
@close="unselectOption(opt.value)"
|
|
337
365
|
>
|
|
338
|
-
{{ opt.label
|
|
366
|
+
{{ opt.label }}
|
|
339
367
|
</PlChip>
|
|
340
368
|
</div>
|
|
341
369
|
<DropdownListItem
|
|
@@ -32,4 +32,139 @@ describe("PlDropdownMulti", () => {
|
|
|
32
32
|
|
|
33
33
|
expect(getOptions().length).toBe(2); // options are not closed after click
|
|
34
34
|
});
|
|
35
|
+
|
|
36
|
+
it("renders a missing chip for a modelValue not present in options", async () => {
|
|
37
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
38
|
+
props: {
|
|
39
|
+
modelValue: [1, 999],
|
|
40
|
+
"onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
|
|
41
|
+
options: [
|
|
42
|
+
{ text: "Option 1", value: 1 },
|
|
43
|
+
{ text: "Option 2", value: 2 },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await flushPromises();
|
|
49
|
+
|
|
50
|
+
const missingChips = wrapper.findAll(".pl-dropdown-multi__chip--missing");
|
|
51
|
+
expect(missingChips.length).toBe(1);
|
|
52
|
+
expect(missingChips[0].text()).toContain("Value not available");
|
|
53
|
+
|
|
54
|
+
// Total chip count: one for valid (1), one for missing (999).
|
|
55
|
+
expect(wrapper.findAll(".pl-chip").length).toBe(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("emits update:modelValue without the missing value when its chip is closed", async () => {
|
|
59
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
60
|
+
props: {
|
|
61
|
+
modelValue: [1, 999],
|
|
62
|
+
"onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
|
|
63
|
+
options: [{ text: "Option 1", value: 1 }],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await flushPromises();
|
|
68
|
+
|
|
69
|
+
const missingChip = wrapper.find(".pl-dropdown-multi__chip--missing");
|
|
70
|
+
expect(missingChip.exists()).toBe(true);
|
|
71
|
+
|
|
72
|
+
await missingChip.find(".pl-chip__close").trigger("click");
|
|
73
|
+
await flushPromises();
|
|
74
|
+
|
|
75
|
+
expect(wrapper.props("modelValue")).toEqual([1]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("does not render missing chips while options are loading (options undefined)", async () => {
|
|
79
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
80
|
+
props: { modelValue: [99], options: undefined },
|
|
81
|
+
});
|
|
82
|
+
await flushPromises();
|
|
83
|
+
expect(wrapper.find(".pl-dropdown-multi__chip--missing").exists()).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("renders missing chip alongside .disabled root when disabled", async () => {
|
|
87
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
88
|
+
props: {
|
|
89
|
+
modelValue: [999],
|
|
90
|
+
disabled: true,
|
|
91
|
+
options: [{ text: "Option 1", value: 1 }],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
await flushPromises();
|
|
95
|
+
// The SCSS rule `.pl-dropdown-multi.disabled .pl-dropdown-multi__chip--missing
|
|
96
|
+
// .pl-chip__text` needs both selectors. jsdom doesn't resolve specificity, so
|
|
97
|
+
// we only assert the hooks exist.
|
|
98
|
+
expect(wrapper.find(".pl-dropdown-multi.disabled").exists()).toBe(true);
|
|
99
|
+
expect(wrapper.find(".pl-dropdown-multi__chip--missing").exists()).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("never renders raw object value in a chip even if missingValueLabel is empty string", async () => {
|
|
103
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
104
|
+
props: {
|
|
105
|
+
modelValue: [{ __isRef: true as const, blockId: "deleted", name: "out" }],
|
|
106
|
+
missingValueLabel: "",
|
|
107
|
+
options: [],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
await flushPromises();
|
|
111
|
+
// With empty label the chip must render empty text, not the raw PlRef JSON.
|
|
112
|
+
expect(wrapper.html()).not.toContain('"blockId"');
|
|
113
|
+
expect(wrapper.html()).not.toContain('"__isRef"');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("does not leak raw JSON when a found option has an empty label", async () => {
|
|
117
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
118
|
+
props: {
|
|
119
|
+
modelValue: [{ __isRef: true as const, blockId: "1", name: "Ref" }],
|
|
120
|
+
options: [{ label: "", value: { __isRef: true as const, blockId: "1", name: "Ref" } }],
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
await flushPromises();
|
|
124
|
+
// An empty `label` must not produce JSON output via `toDisplayString`.
|
|
125
|
+
expect(wrapper.html()).not.toContain('"blockId"');
|
|
126
|
+
expect(wrapper.html()).not.toContain('"__isRef"');
|
|
127
|
+
// The chip still renders (not the missing branch — option is found).
|
|
128
|
+
expect(wrapper.find(".pl-chip").exists()).toBe(true);
|
|
129
|
+
expect(wrapper.find(".pl-dropdown-multi__chip--missing").exists()).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("renders a found option's empty label as empty, never the missing label", async () => {
|
|
133
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
134
|
+
props: {
|
|
135
|
+
modelValue: [1],
|
|
136
|
+
// Distinctive text so a regression to the `|| missingValueLabel` fallback is obvious.
|
|
137
|
+
missingValueLabel: "SHOULD-NOT-APPEAR",
|
|
138
|
+
options: [{ label: "", value: 1 }],
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
await flushPromises();
|
|
142
|
+
// A found option renders its own (empty) label verbatim — matches PlDropdown. It must
|
|
143
|
+
// not borrow the missing-value text, which is reserved for values absent from options.
|
|
144
|
+
expect(wrapper.find(".pl-chip").exists()).toBe(true);
|
|
145
|
+
expect(wrapper.find(".pl-dropdown-multi__chip--missing").exists()).toBe(false);
|
|
146
|
+
expect(wrapper.html()).not.toContain("SHOULD-NOT-APPEAR");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("replaces missing chips with normal chips when options later contain the values", async () => {
|
|
150
|
+
const wrapper = mount(PlDropdownMulti, {
|
|
151
|
+
props: {
|
|
152
|
+
modelValue: [1],
|
|
153
|
+
options: [{ text: "Option 2", value: 2 }],
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
await flushPromises();
|
|
157
|
+
expect(wrapper.findAll(".pl-dropdown-multi__chip--missing").length).toBe(1);
|
|
158
|
+
|
|
159
|
+
// Simulates the upstream block coming back: options now contain the value.
|
|
160
|
+
await wrapper.setProps({
|
|
161
|
+
options: [
|
|
162
|
+
{ text: "Option 1", value: 1 },
|
|
163
|
+
{ text: "Option 2", value: 2 },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
await flushPromises();
|
|
167
|
+
expect(wrapper.findAll(".pl-dropdown-multi__chip--missing").length).toBe(0);
|
|
168
|
+
expect(wrapper.findAll(".pl-chip").length).toBe(1);
|
|
169
|
+
});
|
|
35
170
|
});
|
|
@@ -258,6 +258,10 @@
|
|
|
258
258
|
animation: spin 2.5s linear infinite;
|
|
259
259
|
--icon-color: var(--ic-accent);
|
|
260
260
|
}
|
|
261
|
+
|
|
262
|
+
.pl-dropdown-multi__chip--missing .pl-chip__text {
|
|
263
|
+
color: var(--dis-01);
|
|
264
|
+
}
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
&__open-chips-container {
|
|
@@ -269,3 +273,10 @@
|
|
|
269
273
|
}
|
|
270
274
|
}
|
|
271
275
|
}
|
|
276
|
+
|
|
277
|
+
.pl-dropdown-multi__chip--missing {
|
|
278
|
+
.pl-chip__text {
|
|
279
|
+
font-style: italic;
|
|
280
|
+
color: var(--txt-error);
|
|
281
|
+
}
|
|
282
|
+
}
|