Try to remove as many leaky references from event listeners as possible

This commit is contained in:
jordi fita mas 2023-04-10 23:04:16 +02:00
parent f945051f4a
commit 33277454fa
1 changed files with 112 additions and 83 deletions

View File

@ -4,7 +4,6 @@ class Multiselect extends HTMLDivElement {
this.initialized = false;
this.toSearch = '';
this.highlighted = 0;
this.onFocusOutHandler = this.onFocusOut.bind(this);
}
connectedCallback() {
@ -26,19 +25,73 @@ class Multiselect extends HTMLDivElement {
this.init();
}
}
window.addEventListener('focusin', this.onFocusOutHandler);
document.addEventListener('click', this.onFocusOutHandler);
if (this.initialized) {
this.onClickHandler = () => this.toggle();
this.tags.addEventListener('click', this.onClickHandler);
this.onSearchInputHandler = (e) => {
this.open();
this.filter(e.target.value);
}
this.onSearchKeydownHandler = (e) => {
switch (e.code) {
case "Enter":
e.preventDefault();
this.selectHighlighted();
break;
case "ArrowDown":
if (e.altKey) {
this.open();
} else {
e.preventDefault();
this.highlightNext();
}
break;
case "ArrowUp":
if (e.altKey) {
this.close();
} else {
e.preventDefault();
this.highlightPrev();
}
break;
case "Backspace":
if (e.target.value === '') {
this.deselectLast();
}
break;
case "Escape":
this.close();
break;
}
};
this.search.addEventListener('input', this.onSearchInputHandler);
this.search.addEventListener('keydown', this.onSearchKeydownHandler);
this.onFocusOutHandler = (e) => {
if (this.contains(e.target)) return;
if (!e.target.isConnected) return;
this.close();
}
window.addEventListener('focusin', this.onFocusOutHandler);
document.addEventListener('click', this.onFocusOutHandler);
this.rebuild()
}
}
disconnectedCallback() {
document.removeEventListener('click', this.onFocusOutHandler);
window.removeEventListener('focusin', this.onFocusOutHandler);
}
onFocusOut(e) {
if (this.contains(e.target)) return;
if (!e.target.isConnected) return;
this.close();
if (this.initialized) {
this.removeTags();
this.options.innerHTML = '';
document.removeEventListener('click', this.onFocusOutHandler);
window.removeEventListener('focusin', this.onFocusOutHandler);
this.onFocusOutHandler = null;
this.search.removeEventListener('keydown', this.onSearchKeydownHandler);
this.search.removeEventListener('input', this.onSearchInputHandler);
this.onSearchInputHandler = null;
this.onSearchKeydownHandler = null;
this.tags.removeEventListener('click', this.onClickHandler);
this.onClickHandler = null;
}
}
init() {
@ -52,7 +105,6 @@ class Multiselect extends HTMLDivElement {
this.tags = document.createElement('div');
this.append(this.tags);
this.tags.classList.add('tags');
this.tags.addEventListener('click', () => this.toggle());
this.search = document.createElement('input');
this.tags.append(this.search);
@ -62,42 +114,6 @@ class Multiselect extends HTMLDivElement {
this.search.setAttribute('spellcheck', 'false');
this.search.setAttribute('autocomplete', 'false');
this.search.setAttribute('role', 'combobox');
this.search.addEventListener('input', (e) => {
this.open();
this.filter(e.target.value);
});
this.search.addEventListener('keydown', (e) => {
switch (e.code) {
case "Enter":
e.preventDefault();
this.selectHighlighted();
break;
case "ArrowDown":
if (e.altKey) {
this.open();
} else {
e.preventDefault();
this.highlightNext();
}
break;
case "ArrowUp":
if (e.altKey) {
this.close();
} else {
e.preventDefault();
this.highlightPrev();
}
break;
case "Backspace":
if (e.target.value === '') {
this.deselectLast();
}
break;
case "Escape":
this.close();
break;
}
});
// Must come after the search input
this.tags.append(this.label);
@ -109,8 +125,6 @@ class Multiselect extends HTMLDivElement {
this.options.classList.add('options');
this.options.setAttribute('role', 'listbox');
this.close();
this.rebuild();
}
rebuildOptions() {
@ -214,8 +228,12 @@ class Multiselect extends HTMLDivElement {
this.rebuildOptions();
}
rebuildTags() {
removeTags() {
this.tags.querySelectorAll('.tag').forEach((tag) => tag.remove());
}
rebuildTags() {
this.removeTags();
let empty = true;
for (const option of this.select.options) {
if (!option.selected) {
@ -284,7 +302,6 @@ class Tags extends HTMLDivElement {
super();
this.initialized = false;
this.tags = [];
this.onFocusOutHandler = this.onFocusOut.bind(this);
}
connectedCallback() {
@ -306,17 +323,45 @@ class Tags extends HTMLDivElement {
this.init();
}
}
window.addEventListener('focusin', this.onFocusOutHandler);
if (this.initialized) {
this.onSearchKeydownHandler = (e) => {
switch (e.code) {
case "Space":
e.preventDefault();
// fallthrough
case "Enter":
if (e.target.value.trim() !== '') {
e.preventDefault();
this.createTag();
}
break;
case "Backspace":
if (e.target.value === '') {
this.removeLastTag();
}
break;
}
}
this.search.addEventListener('keydown', this.onSearchKeydownHandler);
this.onFocusOutHandler = (e) => {
if (this.contains(e.target)) return;
if (e.target.value && e.target.value.trim() !== '') {
this.createTag();
}
};
window.addEventListener('focusin', this.onFocusOutHandler);
this.rebuild();
}
}
disconnectedCallback() {
window.removeEventListener('focusin', this.onFocusOutHandler);
}
onFocusOut(e) {
if (this.contains(e.target)) return;
if (e.target.value && e.target.value.trim() !== '') {
this.createTag();
if (this.initialized) {
this.removeTags();
window.removeEventListener('focusin', this.onFocusOutHandler);
this.onFocusOutHandler = null;
this.search.removeEventListener('keydown', this.onSearchKeydownHandler);
this.onSearchKeydownHandler = null;
}
}
@ -343,29 +388,13 @@ class Tags extends HTMLDivElement {
this.input.setAttribute('aria-hidden', 'true');
this.search.setAttribute('spellcheck', 'false');
this.search.setAttribute('autocomplete', 'false');
this.search.addEventListener('keydown', (e) => {
switch (e.code) {
case "Space":
e.preventDefault();
// fallthrough
case "Enter":
if (e.target.value.trim() !== '') {
e.preventDefault();
this.createTag();
}
break;
case "Backspace":
if (e.target.value === '') {
this.removeLastTag();
}
break;
}
});
// Must come after the search input
this.tagList.append(this.label);
}
this.rebuild();
removeTags() {
this.tagList.querySelectorAll('.tag').forEach((tag) => tag.remove());
}
rebuild() {
@ -374,7 +403,7 @@ class Tags extends HTMLDivElement {
this.input.value = newValue;
this.input.dispatchEvent(new Event('change', {bubbles: true}));
}
this.tagList.querySelectorAll('.tag').forEach((tag) => tag.remove());
this.removeTags();
if (this.tags.length === 0) {
this.search.setAttribute('placeholder', this.label.textContent);
} else {
@ -440,9 +469,9 @@ htmx.onLoad((target) => {
target.showModal();
const button = target.querySelector('.close-dialog');
if (button) {
button.addEventListener('click', () => {
htmx.trigger(target, 'closeModal');
});
button.addEventListener('click', (e) => {
htmx.trigger(e.target, 'closeModal');
}, {once: true});
}
}
})