mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-07 00:46:44 -03:00
fix(autocomplete): preserve manual accept-key selection
This commit is contained in:
@@ -372,6 +372,66 @@ describe('AutoComplete widget interactions', () => {
|
|||||||
expect(insertSelectionSpy).toHaveBeenCalledWith('loop');
|
expect(insertSelectionSpy).toHaveBeenCalledWith('loop');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves manual ArrowDown selection when Tab accepts a suggestion', async () => {
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('loop');
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = 'loop';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
input.focus = vi.fn();
|
||||||
|
input.setSelectionRange = vi.fn();
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'prompt', { showPreview: false, minChars: 1 });
|
||||||
|
|
||||||
|
autoComplete.searchType = 'custom_words';
|
||||||
|
autoComplete.items = [
|
||||||
|
{ tag_name: 'looking_to_the_side', category: 0, post_count: 1000 },
|
||||||
|
{ tag_name: 'loop', category: 0, post_count: 500 },
|
||||||
|
];
|
||||||
|
autoComplete.currentSearchTerm = 'loo';
|
||||||
|
autoComplete.selectedIndex = 0;
|
||||||
|
autoComplete.isVisible = true;
|
||||||
|
const insertSelectionSpy = vi.spyOn(autoComplete,'insertSelection').mockResolvedValue();
|
||||||
|
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true }));
|
||||||
|
|
||||||
|
expect(autoComplete.selectedIndex).toBe(1);
|
||||||
|
expect(insertSelectionSpy).toHaveBeenCalledWith('loop');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves manual ArrowDown selection when Enter accepts a suggestion', async () => {
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('loop');
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = 'loop';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
input.focus = vi.fn();
|
||||||
|
input.setSelectionRange = vi.fn();
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'prompt', { showPreview: false, minChars: 1 });
|
||||||
|
|
||||||
|
autoComplete.searchType = 'custom_words';
|
||||||
|
autoComplete.items = [
|
||||||
|
{ tag_name: 'looking_to_the_side', category: 0, post_count: 1000 },
|
||||||
|
{ tag_name: 'loop', category: 0, post_count: 500 },
|
||||||
|
];
|
||||||
|
autoComplete.currentSearchTerm = 'loo';
|
||||||
|
autoComplete.selectedIndex = 0;
|
||||||
|
autoComplete.isVisible = true;
|
||||||
|
const insertSelectionSpy = vi.spyOn(autoComplete,'insertSelection').mockResolvedValue();
|
||||||
|
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
|
||||||
|
|
||||||
|
expect(autoComplete.selectedIndex).toBe(1);
|
||||||
|
expect(insertSelectionSpy).toHaveBeenCalledWith('loop');
|
||||||
|
});
|
||||||
|
|
||||||
it('accepts the first available suggestion with Tab even if delayed auto-selection has not happened yet', async () => {
|
it('accepts the first available suggestion with Tab even if delayed auto-selection has not happened yet', async () => {
|
||||||
caretHelperInstance.getBeforeCursor.mockReturnValue('loop');
|
caretHelperInstance.getBeforeCursor.mockReturnValue('loop');
|
||||||
|
|
||||||
|
|||||||
@@ -358,6 +358,7 @@ class AutoComplete {
|
|||||||
|
|
||||||
this.dropdown = null;
|
this.dropdown = null;
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
|
this.hasManualSelection = false;
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.debounceTimer = null;
|
this.debounceTimer = null;
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
@@ -1139,6 +1140,14 @@ class AutoComplete {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getAcceptSelectionIndex(searchTerm = '') {
|
||||||
|
if (this.hasManualSelection && this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||||
|
return this.selectedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getPreferredSelectedIndex(searchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
async search(term = '', endpoint = null) {
|
async search(term = '', endpoint = null) {
|
||||||
try {
|
try {
|
||||||
this.currentSearchTerm = term;
|
this.currentSearchTerm = term;
|
||||||
@@ -1339,6 +1348,7 @@ class AutoComplete {
|
|||||||
this.dropdown.innerHTML = '';
|
this.dropdown.innerHTML = '';
|
||||||
}
|
}
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
|
this.hasManualSelection = false;
|
||||||
|
|
||||||
this.items.forEach((item, index) => {
|
this.items.forEach((item, index) => {
|
||||||
const itemEl = document.createElement('div');
|
const itemEl = document.createElement('div');
|
||||||
@@ -1374,7 +1384,7 @@ class AutoComplete {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
itemEl.addEventListener('mouseenter', () => {
|
itemEl.addEventListener('mouseenter', () => {
|
||||||
this.selectItem(index);
|
this.selectItem(index, { manual: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
itemEl.addEventListener('click', () => {
|
itemEl.addEventListener('click', () => {
|
||||||
@@ -1401,6 +1411,7 @@ class AutoComplete {
|
|||||||
// full command list with a partially virtualized slice.
|
// full command list with a partially virtualized slice.
|
||||||
if (this.items.length > 0) {
|
if (this.items.length > 0) {
|
||||||
this.selectedIndex = 0;
|
this.selectedIndex = 0;
|
||||||
|
this.hasManualSelection = false;
|
||||||
if (this.contentContainer) {
|
if (this.contentContainer) {
|
||||||
this._applyItemSelection(0);
|
this._applyItemSelection(0);
|
||||||
} else {
|
} else {
|
||||||
@@ -1443,6 +1454,7 @@ class AutoComplete {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
|
this.hasManualSelection = false;
|
||||||
|
|
||||||
// Reset virtual scroll state
|
// Reset virtual scroll state
|
||||||
this.virtualScrollOffset = 0;
|
this.virtualScrollOffset = 0;
|
||||||
@@ -1542,7 +1554,7 @@ class AutoComplete {
|
|||||||
|
|
||||||
// Hover and selection handlers
|
// Hover and selection handlers
|
||||||
item.addEventListener('mouseenter', () => {
|
item.addEventListener('mouseenter', () => {
|
||||||
this.selectItem(index);
|
this.selectItem(index, { manual: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
item.addEventListener('mouseleave', () => {
|
item.addEventListener('mouseleave', () => {
|
||||||
@@ -1991,7 +2003,7 @@ class AutoComplete {
|
|||||||
|
|
||||||
// Hover and selection handlers
|
// Hover and selection handlers
|
||||||
item.addEventListener('mouseenter', () => {
|
item.addEventListener('mouseenter', () => {
|
||||||
this.selectItem(index);
|
this.selectItem(index, { manual: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
item.addEventListener('mouseleave', () => {
|
item.addEventListener('mouseleave', () => {
|
||||||
@@ -2083,6 +2095,7 @@ class AutoComplete {
|
|||||||
this.dropdown.style.display = 'none';
|
this.dropdown.style.display = 'none';
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
|
this.hasManualSelection = false;
|
||||||
this.showingCommands = false;
|
this.showingCommands = false;
|
||||||
|
|
||||||
// Clear items to prevent stale data from being displayed
|
// Clear items to prevent stale data from being displayed
|
||||||
@@ -2121,7 +2134,7 @@ class AutoComplete {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectItem(index) {
|
selectItem(index, { manual = false } = {}) {
|
||||||
// Remove previous selection
|
// Remove previous selection
|
||||||
const container = this.options.enableVirtualScroll && this.contentContainer
|
const container = this.options.enableVirtualScroll && this.contentContainer
|
||||||
? this.contentContainer
|
? this.contentContainer
|
||||||
@@ -2135,6 +2148,7 @@ class AutoComplete {
|
|||||||
// Add new selection
|
// Add new selection
|
||||||
if (index >= 0 && index < this.items.length) {
|
if (index >= 0 && index < this.items.length) {
|
||||||
this.selectedIndex = index;
|
this.selectedIndex = index;
|
||||||
|
this.hasManualSelection = manual;
|
||||||
|
|
||||||
// For virtual scrolling, we need to ensure the item is rendered
|
// For virtual scrolling, we need to ensure the item is rendered
|
||||||
if (this.options.enableVirtualScroll && this.scrollContainer) {
|
if (this.options.enableVirtualScroll && this.scrollContainer) {
|
||||||
@@ -2228,15 +2242,15 @@ class AutoComplete {
|
|||||||
this.loadMoreItems().then(() => {
|
this.loadMoreItems().then(() => {
|
||||||
// After loading more, select the next item
|
// After loading more, select the next item
|
||||||
if (this.selectedIndex < this.items.length - 1) {
|
if (this.selectedIndex < this.items.length - 1) {
|
||||||
this.selectItem(this.selectedIndex + 1);
|
this.selectItem(this.selectedIndex + 1, { manual: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selectItem(this.selectedIndex + 1);
|
this.selectItem(this.selectedIndex + 1, { manual: true });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selectItem(Math.min(this.selectedIndex + 1, this.items.length - 1));
|
this.selectItem(Math.min(this.selectedIndex + 1, this.items.length - 1), { manual: true });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -2246,12 +2260,12 @@ class AutoComplete {
|
|||||||
// For virtual scrolling, handle top boundary
|
// For virtual scrolling, handle top boundary
|
||||||
if (this.selectedIndex <= 0) {
|
if (this.selectedIndex <= 0) {
|
||||||
// Already at first item, ensure it's selected
|
// Already at first item, ensure it's selected
|
||||||
this.selectItem(0);
|
this.selectItem(0, { manual: true });
|
||||||
} else {
|
} else {
|
||||||
this.selectItem(this.selectedIndex - 1);
|
this.selectItem(this.selectedIndex - 1, { manual: true });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selectItem(Math.max(this.selectedIndex - 1, 0));
|
this.selectItem(Math.max(this.selectedIndex - 1, 0), { manual: true });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -2263,9 +2277,9 @@ class AutoComplete {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const liveSearchTerm = this._getLiveSearchTermForAcceptance();
|
const liveSearchTerm = this._getLiveSearchTermForAcceptance();
|
||||||
const preferredIndex = this._getPreferredSelectedIndex(liveSearchTerm);
|
const acceptIndex = this._getAcceptSelectionIndex(liveSearchTerm);
|
||||||
if (preferredIndex !== -1 && preferredIndex !== this.selectedIndex) {
|
if (acceptIndex !== -1 && acceptIndex !== this.selectedIndex) {
|
||||||
this.selectItem(preferredIndex);
|
this.selectItem(acceptIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user