@milaboratories/uikit 2.14.25 → 2.15.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.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +21 -21
  2. package/.turbo/turbo-formatter$colon$check.log +2 -2
  3. package/.turbo/turbo-linter$colon$check.log +2 -2
  4. package/.turbo/turbo-types$colon$check.log +1 -1
  5. package/CHANGELOG.md +16 -0
  6. package/dist/components/PlAlert/pl-alert.css +1 -1
  7. package/dist/components/PlDropdown/PlDropdown.js.map +1 -1
  8. package/dist/components/PlDropdown/PlDropdown.vue.d.ts +5 -0
  9. package/dist/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
  10. package/dist/components/PlDropdown/PlDropdown.vue2.js +66 -62
  11. package/dist/components/PlDropdown/PlDropdown.vue2.js.map +1 -1
  12. package/dist/components/PlDropdown/pl-dropdown.css +1 -1
  13. package/dist/components/PlDropdownMulti/PlDropdownMulti.js.map +1 -1
  14. package/dist/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts +5 -0
  15. package/dist/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts.map +1 -1
  16. package/dist/components/PlDropdownMulti/PlDropdownMulti.vue2.js +75 -58
  17. package/dist/components/PlDropdownMulti/PlDropdownMulti.vue2.js.map +1 -1
  18. package/dist/components/PlDropdownMulti/pl-dropdown-multi.css +1 -1
  19. package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.js.map +1 -1
  20. package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts +6 -0
  21. package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts.map +1 -1
  22. package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue2.js +1 -0
  23. package/dist/components/PlDropdownMultiRef/PlDropdownMultiRef.vue2.js.map +1 -1
  24. package/dist/components/PlDropdownRef/PlDropdownRef.js.map +1 -1
  25. package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts +5 -0
  26. package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts.map +1 -1
  27. package/dist/components/PlDropdownRef/PlDropdownRef.vue2.js +1 -0
  28. package/dist/components/PlDropdownRef/PlDropdownRef.vue2.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/components/PlAlert/pl-alert.scss +3 -1
  31. package/src/components/PlDropdown/PlDropdown.vue +25 -14
  32. package/src/components/PlDropdown/__tests__/PlDropdown.spec.ts +119 -0
  33. package/src/components/PlDropdown/pl-dropdown.scss +9 -0
  34. package/src/components/PlDropdownMulti/PlDropdownMulti.vue +32 -4
  35. package/src/components/PlDropdownMulti/__tests__/PlDropdownMulti.spec.ts +135 -0
  36. package/src/components/PlDropdownMulti/pl-dropdown-multi.scss +11 -0
  37. package/src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue +6 -0
  38. package/src/components/PlDropdownMultiRef/__tests__/PlDropdownMultiRef.spec.ts +42 -0
  39. package/src/components/PlDropdownRef/PlDropdownRef.vue +6 -0
  40. 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":"AAmFA,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;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAkCJ,iBAAS,cAAc;WAiDT,OAAO,IAA6B;;yBAVrB,GAAG;;;;EAe/B;AAaD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;aA3HP,QAAQ,CAAC,SAAS,EAAE,CAAC;WAJvB,MAAM;;gBAJF,QAAQ,EAAE,GAAG,SAAS;iBAoBpB,MAAM;cAIT,OAAO;cAIP,OAAO;YAhBT,MAAM;6EAgIjB,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
+ {"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"}
@@ -10,6 +10,7 @@ var d = /* @__PURE__ */ i({
10
10
  helper: { default: void 0 },
11
11
  error: { default: void 0 },
12
12
  placeholder: { default: "..." },
13
+ missingValueLabel: { default: "Upstream value removed" },
13
14
  required: {
14
15
  type: Boolean,
15
16
  default: !1
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;EAMA,IAAM,IAAa,GAAU,EASvB,IAAQ,GA+CR,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
+ {"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":"AAmHA,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;WAsLO,mBAAmB,CAAC;;;QAzKxB;;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;;WAEG;oBACS,OAAO;QACnB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;mBACQ,OAAO;QAClB;;WAEG;qBACU,OAAO,GAAG,QAAQ;mBA8H2D,CAAC,4BAA2B;oBACzG,OAAO,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI;WAClD,GAAG;;yBAlCkB,GAAG;;cA9I1B,mBAAmB,SAAS,CAAC,GAAG,SAAS,KAAG,IAAI;EAoLlD,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAE;AAjMzE,wBAiM4E;AAG5E,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG,EAAE,CAAC"}
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"}
@@ -11,6 +11,7 @@ var d = /* @__PURE__ */ i({
11
11
  loadingOptionsHelper: { default: void 0 },
12
12
  error: { default: void 0 },
13
13
  placeholder: { default: "..." },
14
+ missingValueLabel: { default: "Upstream value removed" },
14
15
  clearable: {
15
16
  type: Boolean,
16
17
  default: !1
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;EAUR,IAAM,IAAa,GAAU,EASvB,IAAQ,GA8DR,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"}
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.14.25",
3
+ "version": "2.15.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -32,7 +32,7 @@
32
32
  "sortablejs": "^1.15.6",
33
33
  "vue": "3.5.24",
34
34
  "@milaboratories/helpers": "1.14.2",
35
- "@platforma-sdk/model": "1.78.9"
35
+ "@platforma-sdk/model": "1.78.12"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@vitest/coverage-istanbul": "^4.1.3",
@@ -70,7 +70,9 @@
70
70
 
71
71
  &__close-btn {
72
72
  position: absolute;
73
- top: 10px;
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
- if (props.modelValue !== undefined && selectedIndex.value === -1) {
181
- return "The selected value is not one of the options";
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 && props.modelValue !== undefined) {
215
+ if (!data.open && hasValue.value) {
206
216
  return "";
207
217
  }
208
218
 
209
- return props.modelValue ? String(textValue.value) : props.placeholder;
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> {{ textValue }} </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) => normalizedOptionsRef.value.find((opt) => deepEqual(opt.value, v)))
132
- .filter((v) => v !== undefined);
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 || opt.value }}
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 || opt.value }}
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
+ }
@@ -39,6 +39,11 @@ const props = withDefaults(
39
39
  * Placeholder text shown when no value is selected.
40
40
  */
41
41
  placeholder?: string;
42
+ /**
43
+ * Text shown in a chip when a `modelValue` entry's upstream block no longer
44
+ * exists. Defaults to "Upstream value removed".
45
+ */
46
+ missingValueLabel?: string;
42
47
  /**
43
48
  * If `true`, the dropdown component is marked as required.
44
49
  */
@@ -54,6 +59,7 @@ const props = withDefaults(
54
59
  helper: undefined,
55
60
  error: undefined,
56
61
  placeholder: "...",
62
+ missingValueLabel: "Upstream value removed",
57
63
  required: false,
58
64
  disabled: false,
59
65
  options: undefined,