Multi-Select Comb Box: item label generator for chip

I have encountered the following problem while binding a Multi-Select Combo Box:

  • The value is a string[], but the items are objects, like { value: 'A', label: '1A' }.
  • The bean’s attribute requires a list of string UUIDs, not objects, so I need to set only the values/UUIDs as items, like ['A'].
  • I need to display the label because a UUID is not readable.
  • When using the multiSelectComboBoxRenderer, the overlay list displays nicely, but the chip still shows the UUID instead of a user-friendly label.
  • item-label-path does not work because the items are not objects.

While investigating the component sources, I found that I can override the _getItemLabel method of MultiSelectComboBoxMixin.

To integrate with the existing structure, I wrote a Lit directive for setting a custom item label generator:

export type ItemLabelGenerator = (item: any) => string

export class MultiSelectComboxBoxLabelGeneratorDirective extends Directive {
    render(_callback: ItemLabelGenerator) { }

    update(part: ElementPart, [callback]: [ItemLabelGenerator]) {
        if (part.type !== PartType.ELEMENT) {
            throw new Error('MultiSelectComboxBoxLabelGeneratorDirective can only be used on elements');
        }
        const el = part.element as HTMLElement;
        if (el.tagName.toLowerCase() !== 'vaadin-multi-select-combo-box') {
            throw new Error('MultiSelectComboxBoxLabelGeneratorDirective can only be used on vaadin-multi-select-combo-box');
        }
        (el as any)._getItemLabel = callback;
    }
}

export const multiSelectComboBoxLabelGenerator = directive(MultiSelectComboxBoxLabelGeneratorDirective);

You can use the directive like this (Tested with Vaadin 24.6.12):

@customElement("test-view")
export class TestView extends View {
    @state()
    items: any[] = [{ value: 'A', label: "1A" }, { value: 'B', label: "2B" }, { value: 'C', label: "3C" }];
    @state()
    selectedItems: String[] = ["B"];
    labelGenerator: ItemLabelGenerator = (item: any) => this.items.find(i => i.value === item).label

    render() {
        return html`
        <vaadin-multi-select-combo-box
            .selectedItems=${this.selectedItems}
            .items=${this.items.map(i => i.value)}
            @selected-items-changed=${(e: CustomEvent) => this.onSelectedItemsChanged(e.detail.value)}
            ${multiSelectComboBoxLabelGenerator(this.labelGenerator)}
            ${multiSelectComboBoxRenderer(item => html`${this.labelGenerator(item)}`, [this.items])}
            >
        </vaadin-multi-select-combo-box>

        <p>${JSON.stringify(this.selectedItems)}</p>
        `
    }

    onSelectedItemsChanged(value: any) {
        this.selectedItems = value
    }
}