Skip to content

Commit

Permalink
fix(sbb-select): update displayed value on option label change
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB committed Dec 16, 2024
1 parent 7afc72f commit 2747a54
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class SbbAutocompleteGridOptionElement extends SbbOptionBaseElement {
public static readonly events = {
selectionChange: 'autocompleteOptionSelectionChange',
optionSelected: 'autocompleteOptionSelected',
optionLabelChanged: 'optionLabelChanged',
} as const;

protected optionId = autocompleteGridOptionId;
Expand All @@ -45,6 +46,15 @@ class SbbAutocompleteGridOptionElement extends SbbOptionBaseElement {
SbbAutocompleteGridOptionElement.events.optionSelected,
);

/**
* @internal
* Emits when the label changed.
*/
protected optionLabelChanged: EventEmitter = new EventEmitter(
this,
SbbAutocompleteGridOptionElement.events.optionLabelChanged,
);

protected override onOptionAttributesChange(mutationsList: MutationRecord[]): void {
super.onOptionAttributesChange(mutationsList);
this.closest?.('sbb-autocomplete-grid-row')?.toggleAttribute(
Expand Down
10 changes: 9 additions & 1 deletion src/elements/option/option/option-base-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ abstract class SbbOptionBaseElement extends SbbDisabledMixin(
/** Emits when an option was selected by user. */
protected abstract optionSelected: EventEmitter;

/** Emits when the label changes. */
protected abstract optionLabelChanged: EventEmitter;

/** Whether to apply the negative styling */
@state() protected accessor negative = false;

Expand Down Expand Up @@ -261,13 +264,18 @@ abstract class SbbOptionBaseElement extends SbbDisabledMixin(
return nothing;
}

private _handleSlotChange(): void {
this.handleHighlightState();
this.optionLabelChanged.emit();
}

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
10 changes: 10 additions & 0 deletions src/elements/option/option/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class SbbOptionElement extends SbbOptionBaseElement {
public static readonly events = {
selectionChange: 'optionSelectionChange',
optionSelected: 'optionSelected',
optionLabelChanged: 'optionLabelChanged',
} as const;

protected optionId = `sbb-option`;
Expand All @@ -47,6 +48,15 @@ class SbbOptionElement extends SbbOptionBaseElement {
SbbOptionElement.events.optionSelected,
);

/**
* @internal
* Emits when the label changed.
*/
protected optionLabelChanged: EventEmitter = new EventEmitter(
this,
SbbOptionElement.events.optionLabelChanged,
);

private set _variant(state: SbbOptionVariant) {
if (state) {
this.setAttribute('data-variant', state);
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
67 changes: 52 additions & 15 deletions src/elements/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,18 +265,44 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
}
}

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

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

this._updateDisplayValue(selectedOption);
}

private _updateDisplayValue(selectedOption: SbbOptionElement | SbbOptionElement[] | null): void {
if (Array.isArray(selectedOption)) {
this._displayValue = selectedOption.map((o) => o.textContent).join(', ') || null;
} else if (selectedOption) {
this._displayValue = selectedOption?.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')))
Expand All @@ -285,7 +311,7 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
newValue.includes(o.value ?? o.getAttribute('value')),
);
selectedOptionElements.forEach((o) => (o.selected = true));
this._displayValue = selectedOptionElements.map((o) => o.textContent).join(', ') || null;
this._updateDisplayValue(selectedOptionElements);
}
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 @@ -762,23 +793,29 @@ 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) {
const selectedOption = this._getSelectedOption();

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

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

Expand Down

0 comments on commit 2747a54

Please sign in to comment.