Skip to content

Commit

Permalink
fix(sbb-autocomplete): delay origin look up (#2268)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB committed Dec 6, 2023
1 parent cc23aff commit e060531
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 32 deletions.
70 changes: 38 additions & 32 deletions src/components/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,22 @@ export class SbbAutocomplete extends LitElement {

private _overlay: HTMLElement;
private _optionContainer: HTMLElement;

/** Returns the element where autocomplete overlay is attached to. */
public get originElement(): HTMLElement {
if (!this._originElement) {
this._originElement = this._findOriginElement();
}
return this._originElement;
}
private _originElement: HTMLElement;

/** Returns the trigger element. */
public get triggerElement(): HTMLInputElement {
return this._triggerElement;
}
private _triggerElement: HTMLInputElement;

private _triggerEventsController: AbortController;
private _openPanelEventsController: AbortController;
private _overlayId = `sbb-autocomplete-${++nextId}`;
Expand All @@ -105,7 +119,7 @@ export class SbbAutocomplete extends LitElement {

/** The autocomplete should inherit 'readonly' state from the trigger. */
private get _readonly(): boolean {
return this._triggerElement && isValidAttribute(this._triggerElement, 'readonly');
return this.triggerElement && isValidAttribute(this.triggerElement, 'readonly');
}

private get _options(): SbbOption[] {
Expand Down Expand Up @@ -172,11 +186,11 @@ export class SbbAutocomplete extends LitElement {
.forEach((option) => (option.selected = false));

// Set the option value
this._triggerElement.value = target.value;
this.triggerElement.value = target.value;

// Manually trigger the change events
this._triggerElement.dispatchEvent(new Event('change', { bubbles: true }));
this._triggerElement.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.triggerElement.dispatchEvent(new Event('change', { bubbles: true }));
this.triggerElement.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));

this.close();
}
Expand Down Expand Up @@ -246,15 +260,21 @@ export class SbbAutocomplete extends LitElement {
this._triggerEventsController?.abort();
this._openPanelEventsController?.abort();

this._attachTo(this._getOriginElement());
this._originElement = undefined;
toggleDatasetEntry(
this,
'optionPanelOriginBorderless',
this.closest('sbb-form-field')?.hasAttribute('borderless'),
);

this._bindTo(this._getTriggerElement());
}

/**
* Retrieve the element where the autocomplete will be attached.
* @returns 'origin' or the first 'sbb-form-field' ancestor.
*/
private _getOriginElement(): HTMLElement {
private _findOriginElement(): HTMLElement {
let result: HTMLElement;

if (!this.origin) {
Expand Down Expand Up @@ -292,27 +312,13 @@ export class SbbAutocomplete extends LitElement {
return result;
}

private _attachTo(anchorElem: HTMLElement): void {
if (!anchorElem) {
return;
}

this._originElement = anchorElem;

toggleDatasetEntry(
this,
'optionPanelOriginBorderless',
this.closest('sbb-form-field')?.hasAttribute('borderless'),
);
}

private _bindTo(triggerElem: HTMLInputElement): void {
if (!triggerElem) {
return;
}

// Reset attributes to the old trigger and add them to the new one
this._removeTriggerAttributes(this._triggerElement);
this._removeTriggerAttributes(this.triggerElement);
this._setTriggerAttributes(triggerElem);

this._triggerElement = triggerElem;
Expand All @@ -324,21 +330,21 @@ export class SbbAutocomplete extends LitElement {
this._triggerEventsController = new AbortController();

// Open the overlay on focus, click, input and `ArrowDown` event
this._triggerElement.addEventListener('focus', () => this.open(), {
this.triggerElement.addEventListener('focus', () => this.open(), {
signal: this._triggerEventsController.signal,
});
this._triggerElement.addEventListener('click', () => this.open(), {
this.triggerElement.addEventListener('click', () => this.open(), {
signal: this._triggerEventsController.signal,
});
this._triggerElement.addEventListener(
this.triggerElement.addEventListener(
'input',
(event) => {
this.open();
this._highlightOptions((event.target as HTMLInputElement).value);
},
{ signal: this._triggerEventsController.signal },
);
this._triggerElement.addEventListener(
this.triggerElement.addEventListener(
'keydown',
(event: KeyboardEvent) => this._closedPanelKeyboardInteraction(event),
{ signal: this._triggerEventsController.signal },
Expand All @@ -347,7 +353,7 @@ export class SbbAutocomplete extends LitElement {

// Set overlay position, width and max height
private _setOverlayPosition(): void {
setOverlayPosition(this._overlay, this._originElement, this._optionContainer, this);
setOverlayPosition(this._overlay, this.originElement, this._optionContainer, this);
}

/** On open/close animation end.
Expand All @@ -365,13 +371,13 @@ export class SbbAutocomplete extends LitElement {
private _onOpenAnimationEnd(): void {
this._state = 'opened';
this._attachOpenPanelEvents();
this._triggerElement?.setAttribute('aria-expanded', 'true');
this.triggerElement?.setAttribute('aria-expanded', 'true');
this._didOpen.emit();
}

private _onCloseAnimationEnd(): void {
this._state = 'closed';
this._triggerElement?.setAttribute('aria-expanded', 'false');
this.triggerElement?.setAttribute('aria-expanded', 'false');
this._resetActiveElement();
this._optionContainer.scrollTop = 0;
this._didClose.emit();
Expand Down Expand Up @@ -399,7 +405,7 @@ export class SbbAutocomplete extends LitElement {
});

// Keyboard interactions
this._triggerElement.addEventListener(
this.triggerElement.addEventListener(
'keydown',
(event: KeyboardEvent) => this._openedPanelKeyboardInteraction(event),
{
Expand All @@ -418,7 +424,7 @@ export class SbbAutocomplete extends LitElement {
if (
!this._isPointerDownEventOnMenu &&
!isEventOnElement(this._overlay, event) &&
!isEventOnElement(this._originElement, event)
!isEventOnElement(this.originElement, event)
) {
this.close();
}
Expand Down Expand Up @@ -477,7 +483,7 @@ export class SbbAutocomplete extends LitElement {
const next = getNextElementIndex(event, this._activeItemIndex, filteredOptions.length);
const nextActiveOption = filteredOptions[next];
nextActiveOption.active = true;
this._triggerElement.setAttribute('aria-activedescendant', nextActiveOption.id);
this.triggerElement.setAttribute('aria-activedescendant', nextActiveOption.id);
nextActiveOption.scrollIntoView({ block: 'nearest' });

// Reset the previous active option
Expand All @@ -496,7 +502,7 @@ export class SbbAutocomplete extends LitElement {
activeElement.active = false;
}
this._activeItemIndex = -1;
this._triggerElement.removeAttribute('aria-activedescendant');
this.triggerElement.removeAttribute('aria-activedescendant');
}

/** Highlight the searched text on the options. */
Expand Down
2 changes: 2 additions & 0 deletions src/components/autocomplete/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ using `aria-activedescendant` to support navigation though the autocomplete opti
| `disableAnimation` | `disable-animation` | public | `boolean` | `false` | Whether the animation is disabled. |
| `preserveIconSpace` | `preserve-icon-space` | public | `boolean` | | Whether the icon space is preserved when no icon is set. |
| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. |
| `originElement` | - | public | `HTMLElement` | | Returns the element where autocomplete overlay is attached to. |
| `triggerElement` | - | public | `HTMLInputElement` | | Returns the trigger element. |

## Methods

Expand Down

0 comments on commit e060531

Please sign in to comment.