Implementing outside clicks on Lit-Elements

itsclarke

I'm making a custom dropdown web component using LitElements and while implementing the click-outside-dropwdown-to-close feature I'm noticing some unexpected behavior that is blocking me. Below in the async firstUpdated() function I'm creating the event listener as recommended in their documentation. Logging out dropdown works as expected, but logging out target returns the root <custom-drop-down>...</custom-drop-down> element every time, which while accurate to an extent, is not specific enough. If there's a better way to handle outside click events I'm all ears.

For reference, here's something that's already built (although the source is hidden) that I'm trying to replicate: https://www.carbondesignsystem.com/components/dropdown/code/

async firstUpdated() {
  await new Promise((r) => setTimeout(r, 0));
  this.addEventListener('click', event => {
    const dropdown = <HTMLElement>this.shadowRoot?.getElementById('dropdown-select');
    const target = <HTMLElement>event.target;
    if (!dropdown?.contains(target)) {
      console.log('im outside');
    }
  });
}    


@customElement('custom-drop-down')
public render(): TemplateResult {
  return html`
    <div>
      <div id="dropdown-select" @action=${this.updateValue}>
        <div class="current-selection" @click=${this.toggleDropdown}>
          ${this.value}
        </div>
        <div class="dropdown-options" id="dropdown-options">
          ${this.dropdownItems(this.options)}
        </div>
      </div>
    </div>
  `;
}
D M

The click event's target property is set to the topmost event target.

The topmost event target MUST be the element highest in the rendering order which is capable of being an event target.

Because a shadow DOM encapsulates its internal structure, the event is retargeted to the host element. In your example, the click event's target is retargeted to the custom-drop-down because that is the first and highest element capable of being a target.

If you need to target an element inside your custom element, you can take one of the following approaches:

  1. Register an event listener inside your element.
  2. Use the <slot> element to fill your custom element with elements that are declared in the light DOM.
  3. Use the composedPath on events with composed: true to determine which internal element your event bubbled from.

Here's an example of the functionality I believe you are looking for. If you click on the Custom Element it toggles its active state (similar to toggling a dropdown). If you click on any other element on the page, the custom element deactivates itself (similar to hiding a dropdown when it loses focus).

// Define the custom element type
class MySpan extends HTMLSpanElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    const text = 'Custom Element.';
    const style = document.createElement('style');
    style.textContent = `
      span {
        padding: 5px;
        margin: 5px;
        background-color: gray;
        color: white;
      }
      
      .active {
        background-color: lightgreen;
        color: black;
      }      
    `;
    this.span = document.createElement('span');
    this.span.innerText = text;
    this.span.addEventListener('click', (event) => { 
      console.log('CUSTOM ELEMENT: Clicked inside custom element.'); 
      this.handleClick(event);
    });
    
    this.shadowRoot.append(style, this.span);
  }
  
  handleClick(event) {
    // If the event is from the internal event listener,
    // the target will be our span element and we can toggle it.
    // If the event is from outside the element, 
    // the target cannot be our span element and we should
    // deactiviate it.
    if (event.target === this.span) {
      this.span.classList.toggle('active');
    }
    else {
      this.span.classList.remove('active');
    }
  }
}
customElements.define('my-span', MySpan, { extends: 'span' });

// Insert an instance of the custom element
const div1 = document.querySelector('#target');
const mySpan = new MySpan();
div1.appendChild(mySpan);

// Add an event listener to the page
function handleClick(event) {
  // If we didn't click on the custom element, let it
  // know about the event so it can deactivate its span.
  if (event.target !== mySpan) {
    console.log(`PAGE: Clicked on ${ event.target }. Notifying custom element.`);
    mySpan.handleClick(event);
  }
}
document.body.addEventListener('click', (event) => handleClick(event));
p {
  padding: 5px;
  background-color: lightgray;
}
<p id="target">This is a paragraph.</p>
<p>This is a second paragraph.</p>
<p>This is a third paragraph.</p>
<p>This is a fourth paragraph.</p>

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Directives for Detecting Clicks outside Element

Implementing Ripple effect outside ImageButton

Detecting clicks outside a div and toggle menu

How to detect clicks outside of a css modal?

How to hide a dropdown when the user clicks outside of it

Close modal when user clicks outside of it, why is there == and not !=?

How to handle clicks on nested elements?

Can a lit-element track a variable outside its scope

Slider control of outside elements

Load ordinary JavaScripts in Polymer 3 elements or lit-elements

Implementing a Wifi-Aware application outside Android

Implementing a trait outside a package that is not part of the source tree

jQuery stops listening for clicks after rendering elements

jQuery not listening for clicks on dynamically generated elements

jQuery: Click on element excluding clicks on child elements

qTip2 prevents clicks on underlaying elements

jQuery: Count number of clicks for hundreds of elements

Trouble implementing repeating elements in docopt

Jquery drop down menu on click - Hide menu when user clicks menu button, clicks outside of menu OR clicks on a new drop

How to sound a bell when the user clicks outside a modal window?

Autocomplete jQuery Change is not triggered until the user clicks outside the input field

How to catch event when user clicks outside of bootstrap modal dialog?

Show a message when user clicks outside a window thats launched as ShowDialog

How to close a modal JDialog when user clicks outside of JDialog?

Detect clicks outside of specific div and all of it's children

Use jQuery to hide a DIV when the user clicks outside of it

hide a relative layout when user clicks outside it, Android

close a WPF Dialog Window when the user clicks outside it

c# Detect mouse clicks anywhere (Inside and Outside the Form)