Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(sbb-autocomplete): highlight option when options change #2317

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/components/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { customElement, property, state } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';

import { assignId, getNextElementIndex } from '../core/a11y';
import { SlotChildObserver } from '../core/common-behaviors';
import {
setAttribute,
getDocumentWritingMode,
Expand Down Expand Up @@ -37,7 +38,7 @@ let nextId = 0;
* @event {CustomEvent<void>} didClose - Emits whenever the `sbb-autocomplete` is closed.
*/
@customElement('sbb-autocomplete')
export class SbbAutocompleteElement extends LitElement {
export class SbbAutocompleteElement extends SlotChildObserver(LitElement) {
public static override styles: CSSResultGroup = style;
public static readonly events = {
willOpen: 'willOpen',
Expand Down Expand Up @@ -241,11 +242,16 @@ export class SbbAutocompleteElement extends LitElement {
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this._componentSetup();
this._didLoad = true;
}

public override checkChildren(): void {
this._highlightOptions(this.triggerElement?.value);
}

private _syncNegative(): void {
this.querySelectorAll?.('sbb-divider').forEach((divider) => (divider.negative = this.negative));

Expand Down
23 changes: 21 additions & 2 deletions src/components/option/optgroup/optgroup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CSSResultGroup, html, LitElement, TemplateResult, PropertyValues } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

import { SlotChildObserver } from '../../core/common-behaviors';
import { isSafari, isValidAttribute, toggleDatasetEntry, setAttribute } from '../../core/dom';
import { AgnosticMutationObserver } from '../../core/observers';
import type { SbbOptionElement, SbbOptionVariant } from '../option';
Expand All @@ -14,7 +15,7 @@ import '../../divider';
* @slot - Use the unnamed slot to add `sbb-option` elements to the `sbb-optgroup`.
*/
@customElement('sbb-optgroup')
export class SbbOptGroupElement extends LitElement {
export class SbbOptGroupElement extends SlotChildObserver(LitElement) {
public static override styles: CSSResultGroup = style;

/** Option group label. */
Expand Down Expand Up @@ -83,6 +84,12 @@ export class SbbOptGroupElement extends LitElement {
}
}

protected override checkChildren(): void {
this._proxyDisabledToOptions();
this._proxyGroupLabelToOptions();
this._highlightOptions();
}

private _proxyGroupLabelToOptions(): void {
if (!this._inertAriaGroups) {
return;
Expand All @@ -97,6 +104,18 @@ export class SbbOptGroupElement extends LitElement {
}
}

private _highlightOptions(): void {
const autocomplete = this.closest('sbb-autocomplete');
if (!autocomplete) {
return;
}
const value = autocomplete.triggerElement?.value;
if (!value) {
return;
}
this._options.forEach((opt) => opt.highlight(value));
}

private _onNegativeChange(): void {
this._negative = isValidAttribute(this, 'data-negative');
}
Expand All @@ -116,7 +135,7 @@ export class SbbOptGroupElement extends LitElement {
<div class="sbb-optgroup__icon-space"></div>
<span>${this.label}</span>
</div>
<slot @slotchange=${this._proxyDisabledToOptions}></slot>
<slot></slot>
`;
}
}
Expand Down
126 changes: 126 additions & 0 deletions src/components/option/option/option.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../../autocomplete';
import { waitForLitRender, EventSpy } from '../../core/testing';
import type { SbbFormFieldElement } from '../../form-field';
import '../../form-field';
import '../optgroup';

import { SbbOptionElement } from './option';

Expand Down Expand Up @@ -75,5 +76,130 @@ describe('sbb-option', () => {
</span>
`);
});

it('highlight after option label changed', async () => {
const input = element.querySelector('input');
const autocomplete = element.querySelector('sbb-autocomplete');
const options = element.querySelectorAll('sbb-option');
const optionOneLabel = options[0].shadowRoot.querySelector('.sbb-option__label');

input.focus();
await sendKeys({ type: 'Opt' });
await waitForLitRender(autocomplete);

expect(optionOneLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
<span class="sbb-option__label--highlight"></span>
<span>Opt</span>
<span class="sbb-option__label--highlight">ion 1</span>
</span>
`);

options[0].textContent = 'Other content';
await waitForLitRender(autocomplete);

expect(optionOneLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
Other content
</span>
`);

options[0].textContent = 'Option';
await waitForLitRender(autocomplete);

expect(optionOneLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
<span class="sbb-option__label--highlight"></span>
<span>Opt</span>
<span class="sbb-option__label--highlight">ion</span>
</span>
`);
});

it('highlight later added options', async () => {
const input = element.querySelector('input');
const autocomplete = element.querySelector('sbb-autocomplete');
const options = element.querySelectorAll('sbb-option');
const optionOneLabel = options[0].shadowRoot.querySelector('.sbb-option__label');

input.focus();
await sendKeys({ type: 'Opt' });
await waitForLitRender(autocomplete);

expect(optionOneLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
<span class="sbb-option__label--highlight"></span>
<span>Opt</span>
<span class="sbb-option__label--highlight">ion 1</span>
</span>
`);

const newOption = document.createElement('sbb-option');
newOption.innerText = 'Option 4';
autocomplete.append(newOption);
await waitForLitRender(autocomplete);

const newOptionLabel = newOption.shadowRoot.querySelector('.sbb-option__label');

expect(newOptionLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
<span class="sbb-option__label--highlight"></span>
<span>Opt</span>
<span class="sbb-option__label--highlight">ion 4</span>
</span>
`);
});

it('highlight later added options in sbb-optgroup', async () => {
element = await fixture(html`
<sbb-form-field>
<input />
<sbb-autocomplete>
<sbb-optgroup>
<sbb-option id="option-1" value="1">Option 1</sbb-option>
</sbb-optgroup>
</sbb-autocomplete>
</sbb-form-field>
`);

const input = element.querySelector('input');
const optgroup = element.querySelector('sbb-optgroup');
const options = element.querySelectorAll('sbb-option');
const optionOneLabel = options[0].shadowRoot.querySelector('.sbb-option__label');

input.focus();
await sendKeys({ type: 'Opt' });
await waitForLitRender(element);

expect(optionOneLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
<span class="sbb-option__label--highlight"></span>
<span>Opt</span>
<span class="sbb-option__label--highlight">ion 1</span>
</span>
`);

const newOption = document.createElement('sbb-option');
newOption.innerText = 'Option 2';
optgroup.append(newOption);
await waitForLitRender(element);

const newOptionLabel = newOption.shadowRoot.querySelector('.sbb-option__label');

expect(newOptionLabel).dom.to.be.equal(`
<span class="sbb-option__label">
<slot></slot>
<span class="sbb-option__label--highlight"></span>
<span>Opt</span>
<span class="sbb-option__label--highlight">ion 2</span>
</span>
`);
});
});
});