mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-25 12:31:15 -03:00
feat(ui): add keyboard shortcut cue in search bar, fix clear button positioning
This commit is contained in:
@@ -149,7 +149,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
padding-left: 2.25rem !important;
|
padding-left: 2.25rem !important;
|
||||||
padding-right: 5rem !important;
|
padding-right: 6.75rem !important; /* clear room for options + filter + clear/cue toggles */
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
@@ -190,6 +190,81 @@
|
|||||||
right: 2.25rem;
|
right: 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Clear button: sit immediately left of the search-options toggle */
|
||||||
|
.header-search .search-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 4.25rem; /* 2.25rem (options toggle) + 28px toggle width + 4px gap */
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--border-radius-xs, 4px);
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
transition: background-color var(--transition-base), color var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search .search-clear.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search .search-clear:hover {
|
||||||
|
background: color-mix(in oklch, var(--text-muted) 15%, transparent);
|
||||||
|
color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard shortcut cue: shown when search is empty, hidden when typing */
|
||||||
|
.header-search .search-shortcut-cue {
|
||||||
|
position: absolute;
|
||||||
|
right: 4.25rem; /* same slot as clear button */
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
pointer-events: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search .search-shortcut-cue kbd {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-muted);
|
||||||
|
/* Subtle tint derived from text color so it adapts to both light & dark themes */
|
||||||
|
background: color-mix(in oklch, var(--text-muted) 12%, transparent);
|
||||||
|
border: 1px solid color-mix(in oklch, var(--text-muted) 25%, transparent);
|
||||||
|
border-radius: var(--border-radius-xs, 3px);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search .search-shortcut-cue.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-search.disabled .search-shortcut-cue {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.header-search .search-options-toggle:hover,
|
.header-search .search-options-toggle:hover,
|
||||||
.header-search .search-filter-toggle:hover,
|
.header-search .search-filter-toggle:hover,
|
||||||
.header-search .search-filter-toggle:focus-visible {
|
.header-search .search-filter-toggle:focus-visible {
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export class SearchManager {
|
|||||||
// Create clear button for search input
|
// Create clear button for search input
|
||||||
this.createClearButton();
|
this.createClearButton();
|
||||||
|
|
||||||
|
// Keyboard shortcut cue element (static, exists in the HTML)
|
||||||
|
this.searchShortcutCue = document.getElementById('searchShortcutCue');
|
||||||
|
|
||||||
this.initEventListeners();
|
this.initEventListeners();
|
||||||
this.loadSearchPreferences();
|
this.loadSearchPreferences();
|
||||||
this.setupKeyboardShortcuts();
|
this.setupKeyboardShortcuts();
|
||||||
@@ -163,8 +166,13 @@ export class SearchManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateClearButtonVisibility() {
|
updateClearButtonVisibility() {
|
||||||
|
const hasText = this.searchInput.value.length > 0;
|
||||||
if (this.clearButton) {
|
if (this.clearButton) {
|
||||||
this.clearButton.classList.toggle('visible', this.searchInput.value.length > 0);
|
this.clearButton.classList.toggle('visible', hasText);
|
||||||
|
}
|
||||||
|
// Toggle the keyboard shortcut cue: visible only when search is empty
|
||||||
|
if (this.searchShortcutCue) {
|
||||||
|
this.searchShortcutCue.classList.toggle('hidden', hasText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<input type="text" id="searchInput" placeholder="{{ t(search_placeholder_key) }}" {% if search_disabled %}
|
<input type="text" id="searchInput" placeholder="{{ t(search_placeholder_key) }}" {% if search_disabled %}
|
||||||
disabled{% endif %} />
|
disabled{% endif %} />
|
||||||
<i class="fas fa-search search-icon"></i>
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
<span class="search-shortcut-cue" id="searchShortcutCue"><kbd>Ctrl</kbd><kbd>F</kbd></span>
|
||||||
<button class="search-options-toggle" id="searchOptionsToggle" title="{{ t('header.search.options') }}" {% if
|
<button class="search-options-toggle" id="searchOptionsToggle" title="{{ t('header.search.options') }}" {% if
|
||||||
search_disabled %} disabled aria-disabled="true" {% endif %}>
|
search_disabled %} disabled aria-disabled="true" {% endif %}>
|
||||||
<i class="fas fa-sliders-h"></i>
|
<i class="fas fa-sliders-h"></i>
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ function renderControlsDom(pageKey) {
|
|||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<input id="searchInput" />
|
<input id="searchInput" />
|
||||||
<i class="fas fa-search search-icon"></i>
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
<span class="search-shortcut-cue" id="searchShortcutCue"><kbd>Ctrl</kbd><kbd>F</kbd></span>
|
||||||
<button id="searchOptionsToggle" class="search-options-toggle"></button>
|
<button id="searchOptionsToggle" class="search-options-toggle"></button>
|
||||||
<button id="filterButton" class="search-filter-toggle">
|
<button id="filterButton" class="search-filter-toggle">
|
||||||
<span id="activeFiltersCount" class="filter-badge" style="display: none">0</span>
|
<span id="activeFiltersCount" class="filter-badge" style="display: none">0</span>
|
||||||
@@ -215,6 +216,40 @@ describe('SearchManager filtering scenarios', () => {
|
|||||||
expect(loadMoreWithVirtualScrollMock).toHaveBeenCalledWith(true, false);
|
expect(loadMoreWithVirtualScrollMock).toHaveBeenCalledWith(true, false);
|
||||||
expect(loadMoreWithVirtualScrollMock).toHaveBeenCalledTimes(1);
|
expect(loadMoreWithVirtualScrollMock).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['loras'],
|
||||||
|
['checkpoints'],
|
||||||
|
])('toggles clear button and shortcut cue visibility for %s page', async (pageKey) => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
renderControlsDom(pageKey);
|
||||||
|
const stateModule = await import('../../../static/js/state/index.js');
|
||||||
|
stateModule.initPageState(pageKey);
|
||||||
|
const { SearchManager } = await import('../../../static/js/managers/SearchManager.js');
|
||||||
|
|
||||||
|
new SearchManager({ page: pageKey, searchDelay: 0 });
|
||||||
|
|
||||||
|
const input = document.getElementById('searchInput');
|
||||||
|
const cue = document.getElementById('searchShortcutCue');
|
||||||
|
const clearBtn = document.querySelector('.search-clear');
|
||||||
|
|
||||||
|
// Initially empty: cue visible, clear hidden
|
||||||
|
expect(cue.classList.contains('hidden')).toBe(false);
|
||||||
|
expect(clearBtn.classList.contains('visible')).toBe(false);
|
||||||
|
|
||||||
|
// Type something: cue hidden, clear visible
|
||||||
|
input.value = 'flux';
|
||||||
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
expect(cue.classList.contains('hidden')).toBe(true);
|
||||||
|
expect(clearBtn.classList.contains('visible')).toBe(true);
|
||||||
|
|
||||||
|
// Clear via click: cue visible, clear hidden
|
||||||
|
clearBtn.click();
|
||||||
|
expect(input.value).toBe('');
|
||||||
|
expect(cue.classList.contains('hidden')).toBe(false);
|
||||||
|
expect(clearBtn.classList.contains('visible')).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('FilterManager tag and base model filters', () => {
|
describe('FilterManager tag and base model filters', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user