Try to remove as many leaky references from event listeners as possible
This commit is contained in:
parent
f945051f4a
commit
33277454fa
|
@ -4,7 +4,6 @@ class Multiselect extends HTMLDivElement {
|
|||
this.initialized = false;
|
||||
this.toSearch = '';
|
||||
this.highlighted = 0;
|
||||
this.onFocusOutHandler = this.onFocusOut.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
@ -26,47 +25,14 @@ class Multiselect extends HTMLDivElement {
|
|||
this.init();
|
||||
}
|
||||
}
|
||||
window.addEventListener('focusin', this.onFocusOutHandler);
|
||||
document.addEventListener('click', this.onFocusOutHandler);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initialized = true;
|
||||
|
||||
this.select.style.display = 'none';
|
||||
for (const option of this.select.options) {
|
||||
option.dataset.numerusSearch = Multiselect.normalize(option.textContent);
|
||||
}
|
||||
|
||||
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);
|
||||
this.search.id = this.select.id;
|
||||
this.select.removeAttribute('id');
|
||||
this.select.setAttribute('aria-hidden', 'true');
|
||||
this.search.setAttribute('spellcheck', 'false');
|
||||
this.search.setAttribute('autocomplete', 'false');
|
||||
this.search.setAttribute('role', 'combobox');
|
||||
this.search.addEventListener('input', (e) => {
|
||||
if (this.initialized) {
|
||||
this.onClickHandler = () => this.toggle();
|
||||
this.tags.addEventListener('click', this.onClickHandler);
|
||||
this.onSearchInputHandler = (e) => {
|
||||
this.open();
|
||||
this.filter(e.target.value);
|
||||
});
|
||||
this.search.addEventListener('keydown', (e) => {
|
||||
}
|
||||
this.onSearchKeydownHandler = (e) => {
|
||||
switch (e.code) {
|
||||
case "Enter":
|
||||
e.preventDefault();
|
||||
|
@ -97,7 +63,57 @@ class Multiselect extends HTMLDivElement {
|
|||
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() {
|
||||
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() {
|
||||
this.initialized = true;
|
||||
|
||||
this.select.style.display = 'none';
|
||||
for (const option of this.select.options) {
|
||||
option.dataset.numerusSearch = Multiselect.normalize(option.textContent);
|
||||
}
|
||||
|
||||
this.tags = document.createElement('div');
|
||||
this.append(this.tags);
|
||||
this.tags.classList.add('tags');
|
||||
|
||||
this.search = document.createElement('input');
|
||||
this.tags.append(this.search);
|
||||
this.search.id = this.select.id;
|
||||
this.select.removeAttribute('id');
|
||||
this.select.setAttribute('aria-hidden', 'true');
|
||||
this.search.setAttribute('spellcheck', 'false');
|
||||
this.search.setAttribute('autocomplete', 'false');
|
||||
this.search.setAttribute('role', 'combobox');
|
||||
|
||||
// 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,18 +323,46 @@ 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();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('focusin', this.onFocusOutHandler);
|
||||
break;
|
||||
case "Backspace":
|
||||
if (e.target.value === '') {
|
||||
this.removeLastTag();
|
||||
}
|
||||
|
||||
onFocusOut(e) {
|
||||
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() {
|
||||
if (this.initialized) {
|
||||
this.removeTags();
|
||||
window.removeEventListener('focusin', this.onFocusOutHandler);
|
||||
this.onFocusOutHandler = null;
|
||||
this.search.removeEventListener('keydown', this.onSearchKeydownHandler);
|
||||
this.onSearchKeydownHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue