From 8a0a85e0fa68b6e400d508b61d97621ebb9bff29 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:03:34 +0100 Subject: [PATCH 1/5] Added filter input to combos --- web/extensions/core/contextMenuFilter.js | 66 ++++++++++++++++++++++ web/extensions/core/invertMenuScrolling.js | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 web/extensions/core/contextMenuFilter.js diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js new file mode 100644 index 0000000..4867a30 --- /dev/null +++ b/web/extensions/core/contextMenuFilter.js @@ -0,0 +1,66 @@ +import { app } from "/scripts/app.js"; + +// Adds filtering to context menus + +const id = "Comfy.ContextMenuFilter"; +app.registerExtension({ + name: id, + init() { + const ctxMenu = LiteGraph.ContextMenu; + LiteGraph.ContextMenu = function (values, options) { + const ctx = ctxMenu.call(this, values, options); + + // If we are a dark menu (only used for combo boxes) then add a filter input + if (options?.className === "dark" && values?.length > 10) { + const filter = document.createElement("input"); + Object.assign(filter.style, { + width: "calc(100% - 10px)", + border: "0", + boxSizing: "border-box", + background: "#333", + border: "1px solid #999", + margin: "0 0 5px 5px", + color: "#fff", + }); + filter.placeholder = "Filter list"; + this.root.prepend(filter); + + filter.addEventListener("input", () => { + // Hide all items that dont match our filter + const term = filter.value.toLocaleLowerCase(); + const items = this.root.querySelectorAll(".litemenu-entry"); + for (const item of items) { + item.style.display = !term || item.textContent.toLocaleLowerCase().includes(term) ? "block" : "none"; + } + + // If we have an event then we can try and position the list under the source + if (options.event) { + let top = options.event.clientY - 10; + + const bodyRect = document.body.getBoundingClientRect(); + const rootRect = this.root.getBoundingClientRect(); + if (bodyRect.height && top > bodyRect.height - rootRect.height - 10) { + top = Math.max(0, bodyRect.height - rootRect.height - 10); + } + + this.root.style.top = top + "px"; + } + }); + + requestAnimationFrame(() => { + // Focus the filter box when opening + filter.focus(); + + // If the top is off screen then shift the element + if (parseInt(this.root.style.top) < 0) { + this.root.style.top = 0; + } + }); + } + + return ctx; + }; + + LiteGraph.ContextMenu.prototype = ctxMenu.prototype; + }, +}); diff --git a/web/extensions/core/invertMenuScrolling.js b/web/extensions/core/invertMenuScrolling.js index 34523d5..f900fcc 100644 --- a/web/extensions/core/invertMenuScrolling.js +++ b/web/extensions/core/invertMenuScrolling.js @@ -3,10 +3,10 @@ import { app } from "/scripts/app.js"; // Inverts the scrolling of context menus const id = "Comfy.InvertMenuScrolling"; -const ctxMenu = LiteGraph.ContextMenu; app.registerExtension({ name: id, init() { + const ctxMenu = LiteGraph.ContextMenu; const replace = () => { LiteGraph.ContextMenu = function (values, options) { options = options || {}; From 74893be1ce6b8350d1eafea823450e9a002380e8 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:01:39 +0100 Subject: [PATCH 2/5] Added keyboard navigation + selection --- web/extensions/core/contextMenuFilter.js | 64 +++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index 4867a30..ced5a0a 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -25,13 +25,73 @@ app.registerExtension({ filter.placeholder = "Filter list"; this.root.prepend(filter); + let selectedIndex = 0; + let items = this.root.querySelectorAll(".litemenu-entry"); + let itemCount = items.length; + let selectedItem; + + // Apply highlighting to the selected item + function updateSelected() { + if (selectedItem) { + selectedItem.style.setProperty("background-color", ""); + selectedItem.style.setProperty("color", ""); + } + selectedItem = items[selectedIndex]; + if (selectedItem) { + selectedItem.style.setProperty("background-color", "#ccc", "important"); + selectedItem.style.setProperty("color", "#000", "important"); + } + } + + updateSelected(); + + // Arrow up/down to select items + filter.addEventListener("keydown", (e) => { + if (e.key === "ArrowUp") { + if (selectedIndex === 0) { + selectedIndex = itemCount - 1; + } else { + selectedIndex--; + } + updateSelected(); + e.preventDefault(); + } else if (e.key === "ArrowDown") { + if (selectedIndex === itemCount - 1) { + selectedIndex = 0; + } else { + selectedIndex++; + } + updateSelected(); + e.preventDefault(); + } else if ((selectedItem && e.key === "Enter") || e.keyCode === 13 || e.keyCode === 10) { + selectedItem.click(); + } + }); + filter.addEventListener("input", () => { // Hide all items that dont match our filter const term = filter.value.toLocaleLowerCase(); - const items = this.root.querySelectorAll(".litemenu-entry"); + items = this.root.querySelectorAll(".litemenu-entry"); + // When filtering recompute which items are visible for arrow up/down + // Try and maintain selection + let visibleItems = []; for (const item of items) { - item.style.display = !term || item.textContent.toLocaleLowerCase().includes(term) ? "block" : "none"; + const visible = !term || item.textContent.toLocaleLowerCase().includes(term); + if (visible) { + item.style.display = "block"; + if (item === selectedItem) { + selectedIndex = visibleItems.length; + } + visibleItems.push(item); + } else { + item.style.display = "none"; + if (item === selectedItem) { + selectedIndex = 0; + } + } } + items = visibleItems; + updateSelected(); // If we have an event then we can try and position the list under the source if (options.event) { From 32fd39b4245a50757fd8257ea199f74ade348b9a Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:02:40 +0100 Subject: [PATCH 3/5] Update comment --- web/extensions/core/contextMenuFilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index ced5a0a..8aac84d 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -1,6 +1,6 @@ import { app } from "/scripts/app.js"; -// Adds filtering to context menus +// Adds filtering to combo context menus const id = "Comfy.ContextMenuFilter"; app.registerExtension({ From 1a322ca67a29cedd8b33da85fbae0c27a99cd24b Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:37:24 +0100 Subject: [PATCH 4/5] Fix scaled position --- web/extensions/core/contextMenuFilter.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index 8aac84d..fa4cb24 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -111,9 +111,13 @@ app.registerExtension({ // Focus the filter box when opening filter.focus(); - // If the top is off screen then shift the element - if (parseInt(this.root.style.top) < 0) { - this.root.style.top = 0; + const rect = this.root.getBoundingClientRect(); + + // If the top is off screen then shift the element with scaling applied + if (rect.top < 0) { + const scale = 1 - this.root.getBoundingClientRect().height / this.root.clientHeight; + const shift = (this.root.clientHeight * scale) / 2; + this.root.style.top = -shift + "px"; } }); } From 028e1f7ad2a50efea8391ea54b606cf865d788db Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Mon, 3 Apr 2023 08:11:44 +0100 Subject: [PATCH 5/5] Fix scaled position when filtering Add esc to close --- web/extensions/core/contextMenuFilter.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js index fa4cb24..51e66f9 100644 --- a/web/extensions/core/contextMenuFilter.js +++ b/web/extensions/core/contextMenuFilter.js @@ -43,6 +43,17 @@ app.registerExtension({ } } + const positionList = () => { + const rect = this.root.getBoundingClientRect(); + + // If the top is off screen then shift the element with scaling applied + if (rect.top < 0) { + const scale = 1 - this.root.getBoundingClientRect().height / this.root.clientHeight; + const shift = (this.root.clientHeight * scale) / 2; + this.root.style.top = -shift + "px"; + } + } + updateSelected(); // Arrow up/down to select items @@ -65,6 +76,8 @@ app.registerExtension({ e.preventDefault(); } else if ((selectedItem && e.key === "Enter") || e.keyCode === 13 || e.keyCode === 10) { selectedItem.click(); + } else if(e.key === "Escape") { + this.close(); } }); @@ -104,6 +117,7 @@ app.registerExtension({ } this.root.style.top = top + "px"; + positionList(); } }); @@ -111,14 +125,7 @@ app.registerExtension({ // Focus the filter box when opening filter.focus(); - const rect = this.root.getBoundingClientRect(); - - // If the top is off screen then shift the element with scaling applied - if (rect.top < 0) { - const scale = 1 - this.root.getBoundingClientRect().height / this.root.clientHeight; - const shift = (this.root.clientHeight * scale) / 2; - this.root.style.top = -shift + "px"; - } + positionList(); }); }