3v324v23's picture
lfs
1e3b872
// ttN Dropdown
let activeDropdown = null;
class Dropdown {
constructor(inputEl, options, onSelect, isDict, manualOffset, hostElement) {
this.dropdown = document.createElement('ul');
this.dropdown.setAttribute('role', 'listbox');
this.dropdown.classList.add('ttN-dropdown');
this.selectedIndex = -1;
this.inputEl = inputEl;
this.options = options;
this.onSelect = onSelect;
this.isDict = isDict;
this.manualOffsetX = manualOffset[0];
this.manualOffsetY = manualOffset[1];
this.hostElement = hostElement;
this.focusedDropdown = this.dropdown;
this.buildDropdown();
this.onKeyDownBound = this.onKeyDown.bind(this);
this.onWheelBound = this.onWheel.bind(this);
this.onClickBound = this.onClick.bind(this);
this.addEventListeners();
}
buildDropdown() {
if (this.isDict) {
this.buildNestedDropdown(this.options, this.dropdown);
} else {
this.options.forEach((suggestion, index) => {
this.addListItem(suggestion, index, this.dropdown);
});
}
const inputRect = this.inputEl.getBoundingClientRect();
if (isNaN(this.manualOffsetX) && this.manualOffsetX.includes('%')) {
this.manualOffsetX = (inputRect.height * (parseInt(this.manualOffsetX) / 100))
}
if (isNaN(this.manualOffsetY) && this.manualOffsetY.includes('%')) {
this.manualOffsetY = (inputRect.width * (parseInt(this.manualOffsetY) / 100))
}
this.dropdown.style.top = (inputRect.top + inputRect.height - this.manualOffsetX) + 'px';
this.dropdown.style.left = (inputRect.left + inputRect.width - this.manualOffsetY) + 'px';
this.hostElement.appendChild(this.dropdown);
activeDropdown = this;
}
buildNestedDropdown(dictionary, parentElement, currentPath = '') {
let index = 0;
Object.keys(dictionary).forEach((key) => {
let extra_data;
const item = dictionary[key];
if (typeof item === 'string') { extra_data = item; }
let fullPath = currentPath ? `${currentPath}/${key}` : key;
if (extra_data) { fullPath = `${fullPath}###${extra_data}`; }
if (typeof item === "object" && item !== null) {
const nestedDropdown = document.createElement('ul');
nestedDropdown.setAttribute('role', 'listbox');
nestedDropdown.classList.add('ttN-nested-dropdown');
const parentListItem = document.createElement('li');
parentListItem.classList.add('folder');
parentListItem.textContent = key;
parentListItem.appendChild(nestedDropdown);
parentListItem.addEventListener('mouseover', this.onMouseOver.bind(this, index, parentElement));
parentElement.appendChild(parentListItem);
this.buildNestedDropdown(item, nestedDropdown, fullPath);
index = index + 1;
} else {
const listItem = document.createElement('li');
listItem.classList.add('item');
listItem.setAttribute('role', 'option');
listItem.textContent = key;
listItem.addEventListener('mouseover', this.onMouseOver.bind(this, index, parentElement));
listItem.addEventListener('mousedown', (e) => this.onMouseDown(key, e, fullPath));
parentElement.appendChild(listItem);
index = index + 1;
}
});
}
addListItem(item, index, parentElement) {
const listItem = document.createElement('li');
listItem.setAttribute('role', 'option');
listItem.textContent = item;
listItem.addEventListener('mouseover', (e) => this.onMouseOver(index));
listItem.addEventListener('mousedown', (e) => this.onMouseDown(item, e));
parentElement.appendChild(listItem);
}
addEventListeners() {
document.addEventListener('keydown', this.onKeyDownBound);
this.dropdown.addEventListener('wheel', this.onWheelBound);
document.addEventListener('click', this.onClickBound);
}
removeEventListeners() {
document.removeEventListener('keydown', this.onKeyDownBound);
this.dropdown.removeEventListener('wheel', this.onWheelBound);
document.removeEventListener('click', this.onClickBound);
}
onMouseOver(index, parentElement=null) {
if (parentElement) {
this.focusedDropdown = parentElement;
}
this.selectedIndex = index;
this.updateSelection();
}
onMouseOut() {
this.selectedIndex = -1;
this.updateSelection();
}
onMouseDown(suggestion, event, fullPath='') {
event.preventDefault();
this.onSelect(suggestion, fullPath);
this.dropdown.remove();
this.removeEventListeners();
}
onKeyDown(event) {
const enterKeyCode = 13;
const escKeyCode = 27;
const arrowUpKeyCode = 38;
const arrowDownKeyCode = 40;
const arrowRightKeyCode = 39;
const arrowLeftKeyCode = 37;
const tabKeyCode = 9;
const items = Array.from(this.focusedDropdown.children);
const selectedItem = items[this.selectedIndex];
if (activeDropdown) {
if (event.keyCode === arrowUpKeyCode) {
event.preventDefault();
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
this.updateSelection();
}
else if (event.keyCode === arrowDownKeyCode) {
event.preventDefault();
this.selectedIndex = Math.min(items.length - 1, this.selectedIndex + 1);
this.updateSelection();
}
else if (event.keyCode === arrowRightKeyCode && selectedItem) {
event.preventDefault();
if (selectedItem.classList.contains('folder')) {
const nestedDropdown = selectedItem.querySelector('.ttN-nested-dropdown');
if (nestedDropdown) {
this.focusedDropdown = nestedDropdown;
this.selectedIndex = 0;
this.updateSelection();
}
}
}
else if (event.keyCode === arrowLeftKeyCode && this.focusedDropdown !== this.dropdown) {
const parentDropdown = this.focusedDropdown.closest('.ttN-dropdown, .ttN-nested-dropdown').parentNode.closest('.ttN-dropdown, .ttN-nested-dropdown');
if (parentDropdown) {
this.focusedDropdown = parentDropdown;
this.selectedIndex = Array.from(parentDropdown.children).indexOf(this.focusedDropdown.parentNode);
this.updateSelection();
}
}
else if ((event.keyCode === enterKeyCode || event.keyCode === tabKeyCode) && this.selectedIndex >= 0) {
event.preventDefault();
if (selectedItem.classList.contains('item')) {
this.onSelect(items[this.selectedIndex].textContent);
this.dropdown.remove();
this.removeEventListeners();
}
const nestedDropdown = selectedItem.querySelector('.ttN-nested-dropdown');
if (nestedDropdown) {
this.focusedDropdown = nestedDropdown;
this.selectedIndex = 0;
this.updateSelection();
}
}
else if (event.keyCode === escKeyCode) {
this.dropdown.remove();
this.removeEventListeners();
}
}
}
onWheel(event) {
const top = parseInt(this.dropdown.style.top);
if (localStorage.getItem("Comfy.Settings.Comfy.InvertMenuScrolling")) {
this.dropdown.style.top = (top + (event.deltaY < 0 ? 10 : -10)) + "px";
} else {
this.dropdown.style.top = (top + (event.deltaY < 0 ? -10 : 10)) + "px";
}
}
onClick(event) {
if (!this.dropdown.contains(event.target) && event.target !== this.inputEl) {
this.dropdown.remove();
this.removeEventListeners();
}
}
updateSelection() {
if (!this.focusedDropdown.children) {
this.dropdown.classList.add('selected');
} else {
Array.from(this.focusedDropdown.children).forEach((li, index) => {
if (index === this.selectedIndex) {
li.classList.add('selected');
} else {
li.classList.remove('selected');
}
});
}
}
}
export function ttN_RemoveDropdown() {
if (activeDropdown) {
activeDropdown.removeEventListeners();
activeDropdown.dropdown.remove();
activeDropdown = null;
}
}
export function ttN_CreateDropdown(inputEl, options, onSelect, isDict = false, manualOffset = [10,'100%'], hostElement = document.body) {
ttN_RemoveDropdown();
new Dropdown(inputEl, options, onSelect, isDict, manualOffset, hostElement);
}