You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2023/02/14 16:05:17 UTC

[myfaces-tobago] 01/02: refactor(cleanup): select[One/Many]List

This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit 452e16cfbb2c8f3595613da498db201b710edb33
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Fri Feb 10 22:27:31 2023 +0100

    refactor(cleanup): select[One/Many]List
    
    * naming, ordering, reduce code
---
 .../src/main/ts/tobago-select-list-base.ts         | 214 ++++++++++++++-------
 .../src/main/ts/tobago-select-many-list.ts         | 159 +++------------
 .../src/main/ts/tobago-select-one-list.ts          | 123 +++---------
 3 files changed, 207 insertions(+), 289 deletions(-)

diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-list-base.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-list-base.ts
index 21ca1bdfc8..28abdb16c8 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-list-base.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-list-base.ts
@@ -17,14 +17,32 @@
 
 import {createPopper, Instance} from "@popperjs/core";
 import {Css} from "./tobago-css";
+import {TobagoFilterRegistry} from "./tobago-filter-registry";
+import {Key} from "./tobago-key";
 
-export class SelectListBase extends HTMLElement {
+export abstract class SelectListBase extends HTMLElement {
   private popper: Instance;
 
   get disabled(): boolean {
     return this.classList.contains(Css.TOBAGO_DISABLED);
   }
 
+  get focused(): boolean {
+    return this.classList.contains(Css.TOBAGO_FOCUS);
+  }
+
+  set focused(focused: boolean) {
+    if (focused) {
+      this.classList.add(Css.TOBAGO_FOCUS);
+    } else {
+      this.classList.remove(Css.TOBAGO_FOCUS);
+    }
+  }
+
+  get filter(): string {
+    return this.getAttribute("filter");
+  }
+
   get hiddenSelect(): HTMLSelectElement {
     return this.querySelector("select");
   }
@@ -37,14 +55,6 @@ export class SelectListBase extends HTMLElement {
     return this.querySelector(".tobago-select-field");
   }
 
-  get filter(): string {
-    return this.getAttribute("filter");
-  }
-
-  get filterEnabled(): boolean {
-    return this.filter?.length > 0;
-  }
-
   get filterInput(): HTMLInputElement {
     return this.querySelector(".tobago-filter");
   }
@@ -54,11 +64,6 @@ export class SelectListBase extends HTMLElement {
     return root.querySelector(`.dropdown-menu[name='${this.id}']`);
   }
 
-  get menuStore(): HTMLDivElement {
-    const root = this.getRootNode() as ShadowRoot | Document;
-    return root.querySelector(".tobago-page-menuStore");
-  }
-
   get tbody(): HTMLElement {
     const root = this.getRootNode() as ShadowRoot | Document;
     return root.querySelector(`.tobago-options[name='${this.id}'] tbody`);
@@ -76,30 +81,111 @@ export class SelectListBase extends HTMLElement {
     return this.tbody.querySelector<HTMLTableRowElement>("." + Css.TOBAGO_PRESELECT);
   }
 
+  get menuStore(): HTMLDivElement {
+    const root = this.getRootNode() as ShadowRoot | Document;
+    return root.querySelector(".tobago-page-menuStore");
+  }
+
   connectedCallback(): void {
     if (this.dropdownMenu) {
       this.popper = createPopper(this.selectField, this.dropdownMenu, {});
-      window.addEventListener("resize", this.resizeEvent.bind(this));
+      window.addEventListener("resize", () => this.updateDropdownMenuWidth());
+    }
+    document.addEventListener("click", this.globalClickEvent.bind(this));
+    this.selectField.addEventListener("keydown", this.keydownEventBase.bind(this));
+    this.filterInput.addEventListener("focus", this.focusEvent.bind(this));
+    this.filterInput.addEventListener("blur", this.blurEvent.bind(this));
+    if (this.filter) {
+      this.filterInput.addEventListener("input", this.filterEvent.bind(this));
+    }
+    this.tbody.addEventListener("click", this.tbodyClickEvent.bind(this));
+
+    // handle autofocus; trigger focus event
+    if (document.activeElement.id === this.filterInput.id) {
+      this.focusEvent();
+    }
+  }
+
+  protected abstract globalClickEvent(event: MouseEvent): void;
+
+  private keydownEventBase(event: KeyboardEvent) {
+    switch (event.key) {
+      case Key.ESCAPE:
+        this.hideDropdown();
+        this.removePreselection();
+        break;
+      case Key.ARROW_DOWN:
+        event.preventDefault();
+        this.showDropdown();
+        this.preselectNextRow();
+        break;
+      case Key.ARROW_UP:
+        event.preventDefault();
+        this.showDropdown();
+        this.preselectPreviousRow();
+        break;
+      case Key.ENTER:
+      case Key.SPACE:
+        if (this.preselectedRow) {
+          event.preventDefault();
+          const row = this.tbody.querySelector<HTMLTableRowElement>("." + Css.TOBAGO_PRESELECT);
+          this.select(row);
+        } else if (document.activeElement.id === this.filterInput.id) {
+          this.showDropdown();
+        }
+        break;
+      case Key.TAB:
+        this.removePreselection();
+        break;
     }
   }
 
   protected focusEvent(): void {
-    if (!this.hiddenSelect.disabled) {
-      if (!this.classList.contains(Css.TOBAGO_FOCUS)) {
-        this.setFocus(true);
+    if (!this.disabled) {
+      this.focused = true;
+    }
+  }
+
+  protected blurEvent(event: FocusEvent): void {
+    if (event.relatedTarget !== null) {
+      //relatedTarget is the new focused element; null indicate a mouseclick or an inactive browser window
+      if (!this.isPartOfSelectField(event.relatedTarget as Element)
+          && !this.isPartOfTobagoOptions(event.relatedTarget as Element)) {
+        this.leaveComponent();
       }
     }
   }
 
-  protected setFocus(focus: boolean): void {
-    if (focus) {
-      this.classList.add(Css.TOBAGO_FOCUS);
-    } else {
-      this.classList.remove(Css.TOBAGO_FOCUS);
+  private filterEvent(event: Event): void {
+    const input = event.currentTarget as HTMLInputElement;
+    const searchString = input.value;
+    this.showDropdown();
+    const filterFunction = TobagoFilterRegistry.get(this.filter);
+    // XXX todo: if filterFunction not found?
+    if (filterFunction != null) {
+      this.querySelectorAll("tr").forEach(row => {
+        const itemValue = row.dataset.tobagoValue;
+        if (filterFunction(itemValue, searchString)) {
+          row.classList.remove(Css.D_NONE);
+        } else {
+          row.classList.add(Css.D_NONE);
+          row.classList.remove(Css.TOBAGO_PRESELECT);
+        }
+      });
     }
   }
 
-  protected preselectNextTableRow(): void {
+  private tbodyClickEvent(event: MouseEvent): void {
+    const target = <HTMLElement>event.target;
+    const row = target.closest("tr");
+    this.select(row);
+  }
+
+  protected abstract leaveComponent(): void;
+
+  protected abstract select(row: HTMLTableRowElement): void;
+
+  protected preselectNextRow(): void {
     const rows = this.enabledRows;
     const index = this.preselectIndex(rows);
     if (index >= 0) {
@@ -115,7 +201,7 @@ export class SelectListBase extends HTMLElement {
     }
   }
 
-  protected preselectPreviousTableRow(): void {
+  protected preselectPreviousRow(): void {
     const rows = this.enabledRows;
     const index = this.preselectIndex(rows);
     if (index >= 0) {
@@ -131,33 +217,27 @@ export class SelectListBase extends HTMLElement {
     }
   }
 
-  protected removePreselection(): void {
-    this.preselectedRow?.classList.remove(Css.TOBAGO_PRESELECT);
-  }
-
-  protected isPartOfSelectField(element: Element): boolean {
-    if (element) {
-      if (this.selectField.id === element.id) {
-        return true;
-      } else {
-        return element.parentElement ? this.isPartOfSelectField(element.parentElement) : false;
+  private preselectIndex(rows: NodeListOf<HTMLTableRowElement>): number {
+    for (let i = 0; i < rows.length; i++) {
+      if (rows.item(i).classList.contains(Css.TOBAGO_PRESELECT)) {
+        return i;
       }
-    } else {
-      return false;
     }
+    return -1;
   }
 
-  protected isPartOfTobagoOptions(element: Element): boolean {
-    if (element) {
-      if (element.classList.contains(Css.TOBAGO_OPTIONS)
-          && this.id === element.getAttribute("name")) {
-        return true;
-      } else {
-        return element.parentElement ? this.isPartOfTobagoOptions(element.parentElement) : false;
-      }
-    } else {
-      return false;
+  private preselect(row: HTMLTableRowElement): void {
+    row.classList.add(Css.TOBAGO_PRESELECT);
+    if (!this.dropdownMenu) {
+      row.scrollIntoView({block: "center"});
     }
+
+    this.filterInput.disabled = false;
+    this.filterInput.focus({preventScroll: true});
+  }
+
+  protected removePreselection(): void {
+    this.preselectedRow?.classList.remove(Css.TOBAGO_PRESELECT);
   }
 
   protected showDropdown(): void {
@@ -178,32 +258,34 @@ export class SelectListBase extends HTMLElement {
     }
   }
 
-  private preselect(row: HTMLTableRowElement): void {
-    row.classList.add(Css.TOBAGO_PRESELECT);
-    if (!this.dropdownMenu) {
-      row.scrollIntoView({block: "center"});
+  private updateDropdownMenuWidth(): void {
+    if (this.dropdownMenu) {
+      this.dropdownMenu.style.width = `${this.selectField.offsetWidth}px`;
     }
-
-    this.filterInput.disabled = false;
-    this.filterInput.focus({preventScroll: true});
   }
 
-  private preselectIndex(rows: NodeListOf<HTMLTableRowElement>): number {
-    for (let i = 0; i < rows.length; i++) {
-      if (rows.item(i).classList.contains(Css.TOBAGO_PRESELECT)) {
-        return i;
+  protected isPartOfSelectField(element: Element): boolean {
+    if (element) {
+      if (this.selectField.id === element.id) {
+        return true;
+      } else {
+        return element.parentElement ? this.isPartOfSelectField(element.parentElement) : false;
       }
+    } else {
+      return false;
     }
-    return -1;
   }
 
-  private resizeEvent(event: UIEvent): void {
-    this.updateDropdownMenuWidth();
-  }
-
-  private updateDropdownMenuWidth(): void {
-    if (this.dropdownMenu) {
-      this.dropdownMenu.style.width = `${this.selectField.offsetWidth}px`;
+  protected isPartOfTobagoOptions(element: Element): boolean {
+    if (element) {
+      if (element.classList.contains(Css.TOBAGO_OPTIONS)
+          && this.id === element.getAttribute("name")) {
+        return true;
+      } else {
+        return element.parentElement ? this.isPartOfTobagoOptions(element.parentElement) : false;
+      }
+    } else {
+      return false;
     }
   }
 }
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts
index a19e50dad9..8d72cce5ca 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts
@@ -15,9 +15,7 @@
  * limitations under the License.
  */
 
-import {TobagoFilterRegistry} from "./tobago-filter-registry";
 import {SelectListBase} from "./tobago-select-list-base";
-import {Key} from "./tobago-key";
 import {Css} from "./tobago-css";
 
 class SelectManyList extends SelectListBase {
@@ -31,51 +29,40 @@ class SelectManyList extends SelectListBase {
 
   connectedCallback(): void {
     super.connectedCallback();
-    document.addEventListener("click", this.clickEvent.bind(this));
-    this.filterInput.addEventListener("focus", this.focusEvent.bind(this));
-    this.filterInput.addEventListener("blur", this.blurEvent.bind(this));
-    this.selectField.addEventListener("keydown", this.keydownEvent.bind(this));
 
     // init badges
     this.querySelectorAll("option:checked").forEach(
         option => this.sync(<HTMLOptionElement>option)
     );
+  }
 
-    this.initList();
+  protected globalClickEvent(event: MouseEvent): void {
+    if (!this.disabled) {
+      if (this.isDeleted(event.target as Element)) {
+        // do nothing, this is probably a removed badge
+      } else if (this.isPartOfSelectField(event.target as Element)
+          || this.isPartOfTobagoOptions(event.target as Element)) {
 
-    if (this.filter) {
-      this.filterInput.addEventListener("input", this.filterEvent.bind(this));
-    }
+        if (!this.filterInput.disabled) {
+          this.filterInput.focus();
+        } else if (this.badgeCloseButtons.length > 0) {
+          this.badgeCloseButtons[0].focus();
+        }
+        this.showDropdown();
 
-    // handle autofocus; trigger focus event
-    if (document.activeElement.id === this.filterInput.id) {
-      this.focusEvent();
+      } else {
+        this.leaveComponent();
+      }
     }
   }
 
-  select(event: MouseEvent): void {
-    const target = <HTMLElement>event.target;
-    const row = target.closest("tr");
-    this.selectRow(row);
-  }
-
-  selectRow(row: HTMLTableRowElement): void {
+  protected select(row: HTMLTableRowElement): void {
     const itemValue = row.dataset.tobagoValue;
-    console.info("itemValue", itemValue);
-    const select = this.hiddenSelect;
-    const option: HTMLOptionElement = select.querySelector(`[value="${itemValue}"]`);
+    const option: HTMLOptionElement = this.hiddenSelect.querySelector(`[value="${itemValue}"]`);
     option.selected = !option.selected;
     this.sync(option);
-  }
-
-  removeBadge(event: MouseEvent): void {
-    const target = <HTMLElement>event.target;
-    const group: HTMLElement = target.closest(".btn-group");
-    const itemValue = group.dataset.tobagoValue;
-    const select = this.hiddenSelect;
-    const option: HTMLOptionElement = select.querySelector(`[value="${itemValue}"]`);
-    option.selected = false;
-    this.sync(option);
+    this.filterInput.disabled = false;
+    this.filterInput.focus({preventScroll: true});
   }
 
   sync(option: HTMLOptionElement) {
@@ -94,8 +81,7 @@ class SelectManyList extends SelectListBase {
       closeButton?.addEventListener("focus", this.focusEvent.bind(this));
       closeButton?.addEventListener("blur", this.blurEvent.bind(this));
 
-      // highlight list row
-      row.classList.add(Css.TABLE_PRIMARY);
+      row.classList.add(Css.TABLE_PRIMARY); // highlight list row
     } else {
       // remove badge
       const badge = this.selectField.querySelector(`[data-tobago-value="${itemValue}"]`);
@@ -111,8 +97,7 @@ class SelectManyList extends SelectListBase {
         this.filterInput.focus();
       }
 
-      // remove highlight list row
-      row.classList.remove(Css.TABLE_PRIMARY);
+      row.classList.remove(Css.TABLE_PRIMARY); // remove highlight list row
     }
 
     if (!this.disabled && !this.filter) {
@@ -138,81 +123,17 @@ class SelectManyList extends SelectListBase {
 </span>`;
   }
 
-  filterEvent(event: Event): void {
-    const input = event.currentTarget as HTMLInputElement;
-    const searchString = input.value;
-    console.info("searchString", searchString);
-    const filterFunction = TobagoFilterRegistry.get(this.filter);
-    // XXX todo: if filterFunction not found?
-    if (filterFunction != null) {
-      this.querySelectorAll("tr").forEach(row => {
-        const itemValue = row.dataset.tobagoValue;
-        if (filterFunction(itemValue, searchString)) {
-          row.classList.remove(Css.D_NONE);
-        } else {
-          row.classList.add(Css.D_NONE);
-          row.classList.remove(Css.TOBAGO_PRESELECT);
-        }
-      });
-    }
-  }
-
-  private clickEvent(event: MouseEvent): void {
-    if (!this.disabled) {
-      if (this.isDeleted(event.target as Element)) {
-        // do nothing, this is probably a removed badge
-      } else if (this.isPartOfSelectField(event.target as Element)
-          || this.isPartOfTobagoOptions(event.target as Element)) {
-
-        if (!this.filterInput.disabled) {
-          this.filterInput.focus();
-        } else if (this.badgeCloseButtons.length > 0) {
-          this.badgeCloseButtons[0].focus();
-        }
-        this.showDropdown();
-
-      } else {
-        this.leaveComponent();
-      }
-    }
-  }
-
-  private keydownEvent(event: KeyboardEvent) {
-    switch (event.key) {
-      case Key.ESCAPE:
-        this.hideDropdown();
-        this.removePreselection();
-        break;
-      case Key.ARROW_DOWN:
-        event.preventDefault();
-        this.showDropdown();
-        this.preselectNextTableRow();
-        break;
-      case Key.ARROW_UP:
-        event.preventDefault();
-        this.showDropdown();
-        this.preselectPreviousTableRow();
-        break;
-      case Key.ENTER:
-      case Key.SPACE:
-        if (this.preselectedRow) {
-          event.preventDefault();
-          const row = this.tbody.querySelector<HTMLTableRowElement>("." + Css.TOBAGO_PRESELECT);
-          this.selectRow(row);
-          this.filterInput.disabled = false;
-          this.filterInput.focus({preventScroll: true});
-        } else if (document.activeElement.id === this.filterInput.id) {
-          this.showDropdown();
-        }
-        break;
-      case Key.TAB:
-        this.removePreselection();
-        break;
-    }
+  removeBadge(event: MouseEvent): void {
+    const target = <HTMLElement>event.target;
+    const group: HTMLElement = target.closest(".btn-group");
+    const itemValue = group.dataset.tobagoValue;
+    const option: HTMLOptionElement = this.hiddenSelect.querySelector(`[value="${itemValue}"]`);
+    option.selected = false;
+    this.sync(option);
   }
 
-  private leaveComponent(): void {
-    this.setFocus(false);
+  protected leaveComponent(): void {
+    this.focused = false;
     this.filterInput.value = null;
     this.filterInput.dispatchEvent(new Event("input"));
     this.hideDropdown();
@@ -221,24 +142,6 @@ class SelectManyList extends SelectListBase {
   private isDeleted(element: Element): boolean {
     return element.closest("html") === null;
   }
-
-  private blurEvent(event: FocusEvent): void {
-    if (event.relatedTarget !== null) {
-      //relatedTarget is the new focused element; null indicate a mouseclick or an inactive browser window
-      if (!this.isPartOfSelectField(event.relatedTarget as Element)
-          && !this.isPartOfTobagoOptions(event.relatedTarget as Element)) {
-        this.leaveComponent();
-      }
-    }
-  }
-
-  private initList() {
-    const tbody = this.tbody;
-    tbody.addEventListener("click", this.select.bind(this));
-    tbody.querySelectorAll("tr").forEach((row: HTMLTableRowElement) => {
-      // row stuff
-    });
-  }
 }
 
 document.addEventListener("tobago.init", function (event: Event): void {
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts
index 2039b338e0..4002e59f38 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts
@@ -15,7 +15,6 @@
  * limitations under the License.
  */
 
-import {TobagoFilterRegistry} from "./tobago-filter-registry";
 import {SelectListBase} from "./tobago-select-list-base";
 import {Key} from "./tobago-key";
 import {Css} from "./tobago-css";
@@ -35,72 +34,15 @@ class SelectOneList extends SelectListBase {
 
   connectedCallback(): void {
     super.connectedCallback();
-    document.addEventListener("click", this.clickEvent.bind(this));
-    this.filterInput.addEventListener("focus", this.focusEvent.bind(this));
-    this.filterInput.addEventListener("blur", this.blurEvent.bind(this));
     this.selectField.addEventListener("keydown", this.keydownEvent.bind(this));
-    this.tbody.addEventListener("click", this.select.bind(this));
-
-    this.sync();
-
     if (this.filter) {
-      this.filterInput.addEventListener("input", this.filterEvent.bind(this));
-    }
-
-    if (document.activeElement.id === this.filterInput.id) {
-      this.focusEvent();
+      this.filterInput.addEventListener("input", this.clearSpan.bind(this));
     }
-  }
-
-  select(event: MouseEvent): void {
-    const target = <HTMLElement>event.target;
-    const row = target.closest("tr");
-    this.selectRow(row);
-  }
 
-  selectRow(row: HTMLTableRowElement): void {
-    const itemValue = row.dataset.tobagoValue;
-    const select = this.hiddenSelect;
-    const option: HTMLOptionElement = select.querySelector(`[value="${itemValue}"]`);
-    option.selected = true;
-    this.filterInput.value = null;
     this.sync();
   }
 
-  sync() {
-    this.rows.forEach((row) => {
-      if (row.dataset.tobagoValue === this.hiddenSelect.value) {
-        this.spanText = this.hiddenSelect.value;
-        row.classList.add(Css.TABLE_PRIMARY); // highlight list row
-      } else {
-        row.classList.remove(Css.TABLE_PRIMARY); // remove highlight list row
-      }
-    });
-  }
-
-  filterEvent(event: Event): void {
-    const input = event.currentTarget as HTMLInputElement;
-    const searchString = input.value;
-    if (searchString !== null) {
-      this.spanText = null;
-      this.showDropdown();
-    }
-    const filterFunction = TobagoFilterRegistry.get(this.filter);
-    // XXX todo: if filterFunction not found?
-    if (filterFunction != null) {
-      this.querySelectorAll("tr").forEach(row => {
-        const itemValue = row.dataset.tobagoValue;
-        if (filterFunction(itemValue, searchString)) {
-          row.classList.remove(Css.D_NONE);
-        } else {
-          row.classList.add(Css.D_NONE);
-          row.classList.remove(Css.TOBAGO_PRESELECT);
-        }
-      });
-    }
-  }
-
-  private clickEvent(event: MouseEvent): void {
+  protected globalClickEvent(event: MouseEvent): void {
     if (!this.disabled) {
       if (this.isPartOfSelectField(event.target as Element) || this.isPartOfTobagoOptions(event.target as Element)) {
         if (!this.filterInput.disabled) {
@@ -116,54 +58,45 @@ class SelectOneList extends SelectListBase {
   private keydownEvent(event: KeyboardEvent) {
     switch (event.key) {
       case Key.ESCAPE:
-        this.hideDropdown();
-        this.removePreselection();
-        break;
-      case Key.ARROW_DOWN:
-        event.preventDefault();
-        this.showDropdown();
-        this.preselectNextTableRow();
-        break;
-      case Key.ARROW_UP:
-        event.preventDefault();
-        this.showDropdown();
-        this.preselectPreviousTableRow();
+        this.spanText = this.hiddenSelect.value;
         break;
       case Key.BACKSPACE:
         if (this.filterInput.value.length === 0) {
-          this.spanText = null;
           this.filterInput.dispatchEvent(new Event("input"));
         }
         break;
-      case Key.ENTER:
-      case Key.SPACE:
-        if (this.preselectedRow) {
-          event.preventDefault();
-          const row = this.tbody.querySelector<HTMLTableRowElement>("." + Css.TOBAGO_PRESELECT);
-          this.selectRow(row);
-        } else if (document.activeElement.id === this.filterInput.id) {
-          this.showDropdown();
-        }
-        break;
     }
   }
 
-  private leaveComponent(): void {
-    this.setFocus(false);
+  private clearSpan(): void {
+    this.spanText = null;
+  }
+
+  protected select(row: HTMLTableRowElement): void {
+    const itemValue = row.dataset.tobagoValue;
+    const option: HTMLOptionElement = this.hiddenSelect.querySelector(`[value="${itemValue}"]`);
+    option.selected = true;
     this.filterInput.value = null;
-    this.filterInput.dispatchEvent(new Event("input"));
-    this.spanText = this.hiddenSelect.value;
-    this.hideDropdown();
+    this.sync();
   }
 
-  private blurEvent(event: FocusEvent): void {
-    if (event.relatedTarget !== null) {
-      //relatedTarget is the new focused element; null indicate a mouseclick or an inactive browser window
-      if (!this.isPartOfSelectField(event.relatedTarget as Element)
-          && !this.isPartOfTobagoOptions(event.relatedTarget as Element)) {
-        this.leaveComponent();
+  sync() {
+    this.rows.forEach((row) => {
+      if (row.dataset.tobagoValue === this.hiddenSelect.value) {
+        this.spanText = this.hiddenSelect.value;
+        row.classList.add(Css.TABLE_PRIMARY); // highlight list row
+      } else {
+        row.classList.remove(Css.TABLE_PRIMARY); // remove highlight list row
       }
-    }
+    });
+  }
+
+  protected leaveComponent(): void {
+    this.focused = false;
+    this.filterInput.value = null;
+    this.filterInput.dispatchEvent(new Event("input"));
+    this.spanText = this.hiddenSelect.value;
+    this.hideDropdown();
   }
 }