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.initialized = false;
|
||||||
this.toSearch = '';
|
this.toSearch = '';
|
||||||
this.highlighted = 0;
|
this.highlighted = 0;
|
||||||
this.onFocusOutHandler = this.onFocusOut.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
@ -26,47 +25,14 @@ class Multiselect extends HTMLDivElement {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.addEventListener('focusin', this.onFocusOutHandler);
|
if (this.initialized) {
|
||||||
document.addEventListener('click', this.onFocusOutHandler);
|
this.onClickHandler = () => this.toggle();
|
||||||
}
|
this.tags.addEventListener('click', this.onClickHandler);
|
||||||
|
this.onSearchInputHandler = (e) => {
|
||||||
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) => {
|
|
||||||
this.open();
|
this.open();
|
||||||
this.filter(e.target.value);
|
this.filter(e.target.value);
|
||||||
});
|
}
|
||||||
this.search.addEventListener('keydown', (e) => {
|
this.onSearchKeydownHandler = (e) => {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
case "Enter":
|
case "Enter":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -97,7 +63,57 @@ class Multiselect extends HTMLDivElement {
|
||||||
this.close();
|
this.close();
|
||||||
break;
|
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
|
// Must come after the search input
|
||||||
this.tags.append(this.label);
|
this.tags.append(this.label);
|
||||||
|
@ -109,8 +125,6 @@ class Multiselect extends HTMLDivElement {
|
||||||
this.options.classList.add('options');
|
this.options.classList.add('options');
|
||||||
this.options.setAttribute('role', 'listbox');
|
this.options.setAttribute('role', 'listbox');
|
||||||
this.close();
|
this.close();
|
||||||
|
|
||||||
this.rebuild();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildOptions() {
|
rebuildOptions() {
|
||||||
|
@ -214,8 +228,12 @@ class Multiselect extends HTMLDivElement {
|
||||||
this.rebuildOptions();
|
this.rebuildOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildTags() {
|
removeTags() {
|
||||||
this.tags.querySelectorAll('.tag').forEach((tag) => tag.remove());
|
this.tags.querySelectorAll('.tag').forEach((tag) => tag.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuildTags() {
|
||||||
|
this.removeTags();
|
||||||
let empty = true;
|
let empty = true;
|
||||||
for (const option of this.select.options) {
|
for (const option of this.select.options) {
|
||||||
if (!option.selected) {
|
if (!option.selected) {
|
||||||
|
@ -284,7 +302,6 @@ class Tags extends HTMLDivElement {
|
||||||
super();
|
super();
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
this.onFocusOutHandler = this.onFocusOut.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
@ -306,18 +323,46 @@ class Tags extends HTMLDivElement {
|
||||||
this.init();
|
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;
|
||||||
disconnectedCallback() {
|
case "Backspace":
|
||||||
window.removeEventListener('focusin', this.onFocusOutHandler);
|
if (e.target.value === '') {
|
||||||
|
this.removeLastTag();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
onFocusOut(e) {
|
}
|
||||||
|
}
|
||||||
|
this.search.addEventListener('keydown', this.onSearchKeydownHandler);
|
||||||
|
this.onFocusOutHandler = (e) => {
|
||||||
if (this.contains(e.target)) return;
|
if (this.contains(e.target)) return;
|
||||||
if (e.target.value && e.target.value.trim() !== '') {
|
if (e.target.value && e.target.value.trim() !== '') {
|
||||||
this.createTag();
|
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() {
|
init() {
|
||||||
|
@ -343,29 +388,13 @@ class Tags extends HTMLDivElement {
|
||||||
this.input.setAttribute('aria-hidden', 'true');
|
this.input.setAttribute('aria-hidden', 'true');
|
||||||
this.search.setAttribute('spellcheck', 'false');
|
this.search.setAttribute('spellcheck', 'false');
|
||||||
this.search.setAttribute('autocomplete', '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
|
// Must come after the search input
|
||||||
this.tagList.append(this.label);
|
this.tagList.append(this.label);
|
||||||
|
}
|
||||||
|
|
||||||
this.rebuild();
|
removeTags() {
|
||||||
|
this.tagList.querySelectorAll('.tag').forEach((tag) => tag.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuild() {
|
rebuild() {
|
||||||
|
@ -374,7 +403,7 @@ class Tags extends HTMLDivElement {
|
||||||
this.input.value = newValue;
|
this.input.value = newValue;
|
||||||
this.input.dispatchEvent(new Event('change', {bubbles: true}));
|
this.input.dispatchEvent(new Event('change', {bubbles: true}));
|
||||||
}
|
}
|
||||||
this.tagList.querySelectorAll('.tag').forEach((tag) => tag.remove());
|
this.removeTags();
|
||||||
if (this.tags.length === 0) {
|
if (this.tags.length === 0) {
|
||||||
this.search.setAttribute('placeholder', this.label.textContent);
|
this.search.setAttribute('placeholder', this.label.textContent);
|
||||||
} else {
|
} else {
|
||||||
|
@ -440,9 +469,9 @@ htmx.onLoad((target) => {
|
||||||
target.showModal();
|
target.showModal();
|
||||||
const button = target.querySelector('.close-dialog');
|
const button = target.querySelector('.close-dialog');
|
||||||
if (button) {
|
if (button) {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', (e) => {
|
||||||
htmx.trigger(target, 'closeModal');
|
htmx.trigger(e.target, 'closeModal');
|
||||||
});
|
}, {once: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue