我正在使用LitElements制作一个自定义下拉web组件,在实现点击外部下拉关闭功能时,我注意到一些意外的行为正在阻止我。在下面的async firstUpdated()
函数中,我正在按照他们的文档中的建议创建事件侦听器。注销dropdown
按预期工作,但注销target
每次都返回根<custom-drop-down>...</custom-drop-down>
元素,虽然在一定程度上是准确的,但不够具体。如果有更好的方法来处理外部点击事件,我会洗耳恭听。
作为参考,以下是我正在尝试复制的已经构建的内容(尽管源代码是隐藏的):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>
`;
}
点击事件的target
属性设置为最上面的事件目标。
最上面的事件目标必须是能够成为事件目标的呈现顺序中最高的元素。
因为shadow DOM封装了它的内部结构,所以事件被重定向到host元素。在您的示例中,click
事件的target
被重定向到custom-drop-down
,因为它是能够成为目标的第一个也是最高的元素。
如果您需要针对自定义元素中的某个元素,可以采取以下方法之一:
- 在元素中注册一个事件侦听器
- 使用
<slot>
元素用在light DOM中声明的元素填充自定义元素 - 使用事件上的
composedPath
和composed: true
来确定事件源自哪个内部元素
下面是一个我相信您正在寻找的功能示例。如果单击Custom Element
,它会切换其活动状态(类似于切换下拉菜单)。如果单击页面上的任何其他元素,自定义元素会自行停用(类似于在失去焦点时隐藏下拉列表)。
// 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>