Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
218 lines
8.0 KiB
JavaScript
218 lines
8.0 KiB
JavaScript
let activeDropdown = null;
|
|
|
|
export function removeDropdown() {
|
|
if (activeDropdown) {
|
|
activeDropdown.removeEventListeners();
|
|
activeDropdown.dropdown.remove();
|
|
activeDropdown = null;
|
|
}
|
|
}
|
|
export function createDropdown(inputEl, suggestions, onSelect, isDict = false) {
|
|
removeDropdown();
|
|
new Dropdown(inputEl, suggestions, onSelect, isDict);
|
|
}
|
|
|
|
class Dropdown {
|
|
constructor(inputEl, suggestions, onSelect, isDict = false) {
|
|
this.dropdown = document.createElement('ul');
|
|
this.dropdown.setAttribute('role', 'listbox');
|
|
this.dropdown.classList.add('easy-dropdown');
|
|
this.selectedIndex = -1;
|
|
this.inputEl = inputEl;
|
|
this.suggestions = suggestions;
|
|
this.onSelect = onSelect;
|
|
this.isDict = isDict;
|
|
|
|
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.suggestions, this.dropdown);
|
|
} else {
|
|
this.suggestions.forEach((suggestion, index) => {
|
|
this.addListItem(suggestion, index, this.dropdown);
|
|
});
|
|
}
|
|
|
|
const inputRect = this.inputEl.getBoundingClientRect();
|
|
this.dropdown.style.top = (inputRect.top + inputRect.height - 10) + 'px';
|
|
this.dropdown.style.left = inputRect.left + 'px';
|
|
|
|
document.body.appendChild(this.dropdown);
|
|
activeDropdown = this;
|
|
}
|
|
|
|
buildNestedDropdown(dictionary, parentElement) {
|
|
let index = 0;
|
|
Object.keys(dictionary).forEach((key) => {
|
|
const item = dictionary[key];
|
|
if (typeof item === "object" && item !== null) {
|
|
const nestedDropdown = document.createElement('ul');
|
|
nestedDropdown.setAttribute('role', 'listbox');
|
|
nestedDropdown.classList.add('easy-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);
|
|
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', this.onMouseDown.bind(this, key));
|
|
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', this.onMouseOver.bind(this, index));
|
|
listItem.addEventListener('mousedown', this.onMouseDown.bind(this, item));
|
|
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) {
|
|
if (parentElement) {
|
|
this.focusedDropdown = parentElement;
|
|
}
|
|
this.selectedIndex = index;
|
|
this.updateSelection();
|
|
}
|
|
|
|
onMouseOut() {
|
|
this.selectedIndex = -1;
|
|
this.updateSelection();
|
|
}
|
|
|
|
onMouseDown(suggestion, event) {
|
|
event.preventDefault();
|
|
this.onSelect(suggestion);
|
|
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) {
|
|
event.preventDefault();
|
|
if (selectedItem && selectedItem.classList.contains('folder')) {
|
|
const nestedDropdown = selectedItem.querySelector('.easy-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('.easy-dropdown, .easy-nested-dropdown').parentNode.closest('.easy-dropdown, .easy-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('.easy-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() {
|
|
Array.from(this.focusedDropdown.children).forEach((li, index) => {
|
|
if (index === this.selectedIndex) {
|
|
li.classList.add('selected');
|
|
} else {
|
|
li.classList.remove('selected');
|
|
}
|
|
});
|
|
}
|
|
} |