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 2022/11/02 16:01:00 UTC

[myfaces-tobago] 02/02: feat(selectMany): focus

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

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

commit ad1a0aa7d30f5b1ab6df4c55f43e9032c5e91d85
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Wed Nov 2 16:37:29 2022 +0100

    feat(selectMany): focus
    
    Issue: TOBAGO-2159
---
 .../myfaces/tobago/renderkit/css/TobagoClass.java  |  1 +
 tobago-theme/src/main/scss/_tobago.scss            | 31 +++++++
 .../src/main/ts/tobago-select-many.ts              | 95 ++++++++++++++++++----
 3 files changed, 113 insertions(+), 14 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
index 8d503c4eee..1d9d4b4fb3 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
@@ -54,6 +54,7 @@ public enum TobagoClass implements CssItem {
   EXPANDED("tobago-expanded"),
 //  FILE("tobago-file"),
 //  FIGURE("tobago-figure"),
+  FOCUS("tobago-focus"),
   FOLDER("tobago-folder"),
   FILTER("tobago-filter"),
   FILTER__WRAPPER("tobago-filter-wrapper"),
diff --git a/tobago-theme/src/main/scss/_tobago.scss b/tobago-theme/src/main/scss/_tobago.scss
index 882bc1337e..8d66f11cd9 100644
--- a/tobago-theme/src/main/scss/_tobago.scss
+++ b/tobago-theme/src/main/scss/_tobago.scss
@@ -118,6 +118,20 @@ $tobago-flex-layout-spacing: 0.5rem;
   }
 }
 
+@mixin formControlFocus() {
+  //_form-control:focus from bootstrap
+  color: $input-focus-color;
+  background-color: $input-focus-bg;
+  border-color: $input-focus-border-color;
+  outline: 0;
+  @if $enable-shadows {
+    @include box-shadow($input-box-shadow, $input-focus-box-shadow);
+  } @else {
+    // Avoid using mixin so we can pass custom focus shadow properly
+    box-shadow: $input-focus-box-shadow;
+  }
+}
+
 @mixin formControlSelectListDisabled() {
   &:disabled option, option:disabled {
     color: rgba($input-color, $tobago-form-disabled-alpha);
@@ -1177,6 +1191,23 @@ tobago-select-one-radio {
 tobago-select-many {
   display: block;
 
+  &.list-group {
+    .tobago-options {
+      border-top: ($table-border-width * 2) solid $table-group-separator-color;
+      overflow: auto;
+    }
+  }
+
+  &.tobago-focus {
+    &.dropdown .tobago-filter-wrapper {
+      @include formControlFocus();
+    }
+
+    &.list-group {
+      @include formControlFocus();
+    }
+  }
+
   .tobago-filter-wrapper {
     display: flex;
     flex-wrap: wrap;
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many.ts
index bbea491888..cd2fef5fd8 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many.ts
@@ -23,6 +23,7 @@ class SelectMany extends HTMLElement {
   private readonly CssClass = {
     DROPDOWN_MENU: "dropdown-menu",
     SHOW: "show",
+    TOBAGO_FOCUS: "tobago-focus",
     TOBAGO_OPTIONS: "tobago-options"
   };
 
@@ -42,6 +43,10 @@ class SelectMany extends HTMLElement {
     return this.querySelector(".tobago-filter-wrapper");
   }
 
+  get badgeCloseButtons(): NodeListOf<HTMLButtonElement> {
+    return this.selectField.querySelectorAll("button.btn.badge");
+  }
+
   get filter(): string {
     return this.getAttribute("filter");
   }
@@ -72,13 +77,21 @@ class SelectMany extends HTMLElement {
 
     if (this.dropdownMenu) {
       window.addEventListener("resize", this.resizeEvent.bind(this));
-      document.addEventListener("click", this.clickEvent.bind(this));
       document.addEventListener("keydown", this.keydownEvent.bind(this));
       this.addEventListener(BootstrapEvents.DROPDOWN_SHOW, this.showDropdown.bind(this));
       this.addEventListener(BootstrapEvents.DROPDOWN_SHOWN, this.shownDropdown.bind(this));
       this.addEventListener(BootstrapEvents.DROPDOWN_HIDE, this.preventBootstrapHide.bind(this));
       this.addEventListener(BootstrapEvents.DROPDOWN_HIDDEN, this.hiddenDropdown.bind(this));
     }
+    document.addEventListener("click", this.clickEvent.bind(this));
+    this.filterInput.addEventListener("focus", this.focusEvent.bind(this));
+    this.filterInput.addEventListener("blur", this.blurEvent.bind(this));
+    this.badgeCloseButtons.forEach(
+      (closeButton) => {
+        closeButton.addEventListener("focus", this.focusEvent.bind(this));
+        closeButton.addEventListener("blur", this.blurEvent.bind(this));
+      }
+    );
 
     // init badges
     this.querySelectorAll("option:checked").forEach(
@@ -130,6 +143,8 @@ class SelectMany extends HTMLElement {
       // todo: nicer adding the @click with lit-html
       const current = this.filterInput.parentElement.querySelector(".btn-group[data-tobago-value='" + itemValue + "']");
       current.addEventListener("click", this.removeBadge.bind(this));
+      this.selectField.querySelector("button.btn.badge").addEventListener("focus", this.focusEvent.bind(this));
+      this.selectField.querySelector("button.btn.badge").addEventListener("blur", this.blurEvent.bind(this));
 
       // highlight list row
       row.classList.add("table-active");
@@ -137,7 +152,14 @@ class SelectMany extends HTMLElement {
       // remove badge
       const selectField1 = this.selectField;
       console.log("selectField1", selectField1);
-      selectField1.querySelector(`[data-tobago-value="${itemValue}"]`).remove();
+      const badge = selectField1.querySelector(`[data-tobago-value="${itemValue}"]`);
+      const previousElementSibling = badge.previousElementSibling;
+      badge.remove();
+      if (previousElementSibling) {
+        previousElementSibling.querySelector<HTMLButtonElement>("button.btn.badge").focus();
+      } else {
+        this.filterInput.focus();
+      }
 
       // remove highlight list row
       row.classList.remove("table-active");
@@ -185,26 +207,45 @@ class SelectMany extends HTMLElement {
   }
 
   private clickEvent(event: MouseEvent): void {
-    let hide = true;
+    if (this.isPartOfFilterWrapper(event.target as Element)) {
+      this.filterInput.focus();
+    } else if (this.isPartOfComponent(event.target as Element)) {
+      this.filterInput.focus();
+    } else {
+      this.hideDropdown();
+      this.setFocus(false);
+    }
+  }
 
-    for (const element of event.composedPath() as HTMLElement[]) {
+  private keydownEvent(event: KeyboardEvent) {
+    if (event.key === this.Key.ESCAPE) {
+      this.hideDropdown();
+    }
+  }
+
+  private isPartOfComponent(element: Element): boolean {
+    if (element) {
       if (this.id === element.id
         || (element.classList?.contains(this.CssClass.DROPDOWN_MENU)
           && this.id === element.getAttribute("name"))) {
-        hide = false;
-        break;
+        return true;
+      } else {
+        return element.parentElement ? this.isPartOfComponent(element.parentElement) : false;
       }
-    }
-
-    if (hide) {
-      this.hideDropdown();
+    } else {
+      return false;
     }
   }
 
-  private keydownEvent(event: KeyboardEvent) {
-    console.log("### keydownEvent");
-    if (event.key === this.Key.ESCAPE) {
-      this.hideDropdown();
+  private isPartOfFilterWrapper(element: Element): boolean {
+    if (element) {
+      if (this.selectField.id === element.id) {
+        return true;
+      } else {
+        return element.parentElement ? this.isPartOfFilterWrapper(element.parentElement) : false;
+      }
+    } else {
+      return false;
     }
   }
 
@@ -231,6 +272,32 @@ class SelectMany extends HTMLElement {
     }
   }
 
+  private focusEvent(event: FocusEvent): void {
+    this.setFocus(true);
+  }
+
+  private blurEvent(event: FocusEvent): void {
+    if (event.relatedTarget === null) {
+      //this must be a mouse click; if tabbed out the relatedTarget is the new focused element
+    } else {
+      if (this.isPartOfFilterWrapper(event.relatedTarget as Element)) {
+        //to nothing
+      } else if (this.isPartOfComponent(event.relatedTarget as Element)) {
+        this.filterInput.focus();
+      } else {
+        this.setFocus(false);
+      }
+    }
+  }
+
+  private setFocus(focus: boolean): void {
+    if (focus) {
+      this.classList.add(this.CssClass.TOBAGO_FOCUS);
+    } else {
+      this.classList.remove(this.CssClass.TOBAGO_FOCUS);
+    }
+  }
+
   private focusFilter(event: MouseEvent): void {
     // console.log("### focusFilter");
   }