Skip to content

Commit

Permalink
fix(sbb-select): update displayed value on option label change (#3300)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB authored Dec 16, 2024
1 parent 126f255 commit a089328
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 20 deletions.
8 changes: 7 additions & 1 deletion src/elements/option/option/option-base-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,19 @@ abstract class SbbOptionBaseElement extends SbbDisabledMixin(
return nothing;
}

private _handleSlotChange(): void {
this.handleHighlightState();
/** @internal */
this.dispatchEvent(new Event('optionLabelChanged', { bubbles: true }));
}

protected override render(): TemplateResult {
return html`
<div class="sbb-option__container">
<div class="sbb-option">
${this.renderIcon()}
<span class="sbb-option__label">
<slot @slotchange=${this.handleHighlightState}></slot>
<slot @slotchange=${this._handleSlotChange}></slot>
${this.renderLabel()}
${this._inertAriaGroups && this.getAttribute('data-group-label')
? html` <sbb-screen-reader-only>
Expand Down
50 changes: 50 additions & 0 deletions src/elements/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,56 @@ describe(`sbb-select`, () => {

expect(element).to.have.attribute('data-state', 'opened');
});

it('updates displayed value on option value change', async () => {
expect(displayValue.textContent!.trim()).to.be.equal('Placeholder');
firstOption.click();
await waitForLitRender(element);
displayValue = element.shadowRoot!.querySelector('.sbb-select__trigger')!;

expect(displayValue.textContent!.trim()).to.be.equal('First');

firstOption.textContent = 'First modified';
await waitForLitRender(element);
displayValue = element.shadowRoot!.querySelector('.sbb-select__trigger')!;

expect(displayValue.textContent!.trim()).to.be.equal('First modified');

// Deselection
element.value = '';
await waitForLitRender(element);
displayValue = element.shadowRoot!.querySelector('.sbb-select__trigger')!;

expect(displayValue.textContent!.trim()).to.be.equal('Placeholder');
});

it('updates displayed value on option value change if multiple', async () => {
element.multiple = true;
await waitForLitRender(element);

expect(displayValue.textContent!.trim()).to.be.equal('Placeholder');

firstOption.click();
secondOption.click();
await waitForLitRender(element);
displayValue = element.shadowRoot!.querySelector('.sbb-select__trigger')!;

expect(displayValue.textContent!.trim()).to.be.equal('First, Second');

firstOption.textContent = 'First modified';
await waitForLitRender(element);
displayValue = element.shadowRoot!.querySelector('.sbb-select__trigger')!;

expect(displayValue.textContent!.trim()).to.be.equal('First modified, Second');

// Deselection
firstOption.click();
secondOption.click();
await waitForLitRender(element);
displayValue = element.shadowRoot!.querySelector('.sbb-select__trigger')!;

expect(displayValue.textContent!.trim()).to.be.equal('Placeholder');
});
});

describe('form association', () => {
Expand Down
73 changes: 54 additions & 19 deletions src/elements/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,27 +265,53 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
}
}

/** Listens to option changes. */
private _onOptionLabelChanged(event: Event): void {
const target = event.target as SbbOptionElement;
const selected = this._getSelected();

if (
(!Array.isArray(selected) && target !== selected) ||
(Array.isArray(selected) && !selected.includes(target))
) {
return;
}

this._updateDisplayValue(selected);
}

private _updateDisplayValue(selected: SbbOptionElement | SbbOptionElement[] | null): void {
if (Array.isArray(selected)) {
this._displayValue = selected.map((o) => o.textContent).join(', ') || null;
} else if (selected) {
this._displayValue = selected?.textContent || null;
} else {
this._displayValue = null;
}
}

/** Sets the _displayValue by checking the internal sbb-options and setting the correct `selected` value on them. */
private _onValueChanged(newValue: string | string[]): void {
const options = this._filteredOptions;
if (!Array.isArray(newValue)) {
const optionElement = options.find((o) => (o.value ?? o.getAttribute('value')) === newValue);
const optionElement =
options.find((o) => (o.value ?? o.getAttribute('value')) === newValue) ?? null;
if (optionElement) {
optionElement.selected = true;
}
options
.filter((o) => (o.value ?? o.getAttribute('value')) !== newValue)
.forEach((o) => (o.selected = false));
this._displayValue = optionElement?.textContent || null;
this._updateDisplayValue(optionElement);
} else {
options
.filter((o) => !newValue.includes(o.value ?? o.getAttribute('value')))
.forEach((e) => (e.selected = false));
const selectedOptionElements = options.filter((o) =>
const selectedElements = options.filter((o) =>
newValue.includes(o.value ?? o.getAttribute('value')),
);
selectedOptionElements.forEach((o) => (o.selected = true));
this._displayValue = selectedOptionElements.map((o) => o.textContent).join(', ') || null;
selectedElements.forEach((o) => (o.selected = true));
this._updateDisplayValue(selectedElements);
}
this._stateChange.emit({ type: 'value', value: newValue });
}
Expand Down Expand Up @@ -352,6 +378,11 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
(e: CustomEvent<void>) => this._onOptionChanged(e),
{ signal },
);

this.addEventListener('optionLabelChanged', (e: Event) => this._onOptionLabelChanged(e), {
signal,
});

this.addEventListener(
'click',
(e: MouseEvent) => {
Expand Down Expand Up @@ -761,24 +792,28 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
}
};

private _setValueFromSelectedOption(): void {
if (!this.multiple) {
const selectedOption = this._filteredOptions.find((option) => option.selected);
if (selectedOption) {
this._activeItemIndex = this._filteredOptions.findIndex(
(option) => option === selectedOption,
);
this.value = selectedOption.value;
}
} else {
const options = this._filteredOptions.filter((option) => option.selected);
if (options && options.length > 0) {
private _setValueFromSelected(): void {
const selected = this._getSelected();

if (Array.isArray(selected)) {
if (selected && selected.length > 0) {
const value: string[] = [];
for (const option of options) {
for (const option of selected) {
value.push(option.value!);
}
this.value = value;
}
} else if (selected) {
this._activeItemIndex = this._filteredOptions.findIndex((option) => option === selected);
this.value = selected.value;
}
}

private _getSelected(): SbbOptionElement | SbbOptionElement[] | null {
if (this.multiple) {
return this._filteredOptions.filter((option) => option.selected);
} else {
return this._filteredOptions.find((option) => option.selected) ?? null;
}
}

Expand Down Expand Up @@ -860,7 +895,7 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
?aria-multiselectable=${this.multiple}
${ref((containerRef) => (this._optionContainer = containerRef as HTMLElement))}
>
<slot @slotchange=${this._setValueFromSelectedOption}></slot>
<slot @slotchange=${this._setValueFromSelected}></slot>
</div>
</div>
</div>
Expand Down

0 comments on commit a089328

Please sign in to comment.