feat(ui): add keyboard navigation for LoRA strength inputs, #432

This commit is contained in:
Will Miao
2025-10-18 08:36:10 +08:00
parent cdf21e813c
commit bf3d706bf4
2 changed files with 112 additions and 33 deletions

View File

@@ -75,6 +75,61 @@ export function addLorasWidget(node, name, opts, callback) {
// Parse the loras data // Parse the loras data
const lorasData = parseLoraValue(value); const lorasData = parseLoraValue(value);
const focusSequence = [];
const createFocusEntry = (loraName, type) => {
const entry = { name: loraName, type };
focusSequence.push(entry);
return entry;
};
const escapeLoraName = (loraName) => {
const css =
(typeof window !== "undefined" && window.CSS) ||
(typeof globalThis !== "undefined" && globalThis.CSS);
if (css && typeof css.escape === "function") {
return css.escape(loraName);
}
return loraName.replace(/"|\\/g, "\\$&");
};
const focusAdjacentFrom = (currentEntry, direction) => {
const currentIndex = focusSequence.indexOf(currentEntry);
if (currentIndex === -1) {
return false;
}
const targetEntry = focusSequence[currentIndex + direction];
if (!targetEntry) {
return false;
}
requestAnimationFrame(() => {
const safeName = escapeLoraName(targetEntry.name);
let selector = "";
if (targetEntry.type === "strength") {
selector = `.comfy-lora-entry[data-lora-name="${safeName}"] .comfy-lora-strength-input`;
} else if (targetEntry.type === "clip") {
selector = `.comfy-lora-clip-entry[data-lora-name="${safeName}"] .comfy-lora-clip-strength-input`;
}
if (!selector) {
return;
}
const targetInput = container.querySelector(selector);
if (targetInput) {
targetInput.focus();
if (typeof targetInput.select === "function") {
targetInput.select();
}
selectLora(targetEntry.name);
}
});
return true;
};
if (lorasData.length === 0) { if (lorasData.length === 0) {
// Show message when no loras are added // Show message when no loras are added
@@ -194,6 +249,7 @@ export function addLorasWidget(node, name, opts, callback) {
// Determine expansion state using our helper function // Determine expansion state using our helper function
const isExpanded = shouldShowClipEntry(loraData); const isExpanded = shouldShowClipEntry(loraData);
const strengthFocusEntry = createFocusEntry(name, "strength");
// Create the main LoRA entry // Create the main LoRA entry
const loraEl = document.createElement("div"); const loraEl = document.createElement("div");
@@ -351,6 +407,7 @@ export function addLorasWidget(node, name, opts, callback) {
// Strength display // Strength display
const strengthEl = document.createElement("input"); const strengthEl = document.createElement("input");
strengthEl.classList.add("comfy-lora-strength-input");
strengthEl.type = "text"; strengthEl.type = "text";
strengthEl.value = typeof strength === 'number' ? strength.toFixed(2) : Number(strength).toFixed(2); strengthEl.value = typeof strength === 'number' ? strength.toFixed(2) : Number(strength).toFixed(2);
Object.assign(strengthEl.style, { Object.assign(strengthEl.style, {
@@ -383,6 +440,7 @@ export function addLorasWidget(node, name, opts, callback) {
strengthEl.style.background = "rgba(0, 0, 0, 0.2)"; strengthEl.style.background = "rgba(0, 0, 0, 0.2)";
// Auto-select all content // Auto-select all content
strengthEl.select(); strengthEl.select();
selectLora(name);
}); });
strengthEl.addEventListener('blur', () => { strengthEl.addEventListener('blur', () => {
@@ -391,33 +449,41 @@ export function addLorasWidget(node, name, opts, callback) {
}); });
// Handle input changes // Handle input changes
strengthEl.addEventListener('change', () => { const commitStrengthValue = () => {
let newValue = parseFloat(strengthEl.value); let parsedValue = parseFloat(strengthEl.value);
if (isNaN(parsedValue)) {
// Validate input parsedValue = 1.0;
if (isNaN(newValue)) {
newValue = 1.0;
} }
const normalizedValue = parsedValue.toFixed(2);
// Update value
const lorasData = parseLoraValue(widget.value); const currentLoras = parseLoraValue(widget.value);
const loraIndex = lorasData.findIndex(l => l.name === name); const loraIndex = currentLoras.findIndex(l => l.name === name);
if (loraIndex >= 0) { if (loraIndex >= 0) {
lorasData[loraIndex].strength = newValue.toFixed(2); currentLoras[loraIndex].strength = normalizedValue;
// Sync clipStrength if collapsed // Sync clipStrength if collapsed
syncClipStrengthIfCollapsed(lorasData[loraIndex]); syncClipStrengthIfCollapsed(currentLoras[loraIndex]);
// Update value and trigger callback strengthEl.value = normalizedValue;
const newLorasValue = formatLoraValue(lorasData); const newLorasValue = formatLoraValue(currentLoras);
widget.value = newLorasValue; widget.value = newLorasValue;
} else {
strengthEl.value = normalizedValue;
} }
}); };
strengthEl.addEventListener('change', commitStrengthValue);
// Handle key events // Handle key events
strengthEl.addEventListener('keydown', (e) => { strengthEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
strengthEl.blur(); strengthEl.blur();
} else if (e.key === 'Tab') {
commitStrengthValue();
const moved = focusAdjacentFrom(strengthFocusEntry, e.shiftKey ? -1 : 1);
if (moved) {
e.preventDefault();
}
} }
}); });
@@ -524,6 +590,7 @@ export function addLorasWidget(node, name, opts, callback) {
// Clip strength display // Clip strength display
const clipStrengthEl = document.createElement("input"); const clipStrengthEl = document.createElement("input");
clipStrengthEl.classList.add("comfy-lora-strength-input", "comfy-lora-clip-strength-input");
clipStrengthEl.type = "text"; clipStrengthEl.type = "text";
clipStrengthEl.value = typeof clipStrength === 'number' ? clipStrength.toFixed(2) : Number(clipStrength).toFixed(2); clipStrengthEl.value = typeof clipStrength === 'number' ? clipStrength.toFixed(2) : Number(clipStrength).toFixed(2);
Object.assign(clipStrengthEl.style, { Object.assign(clipStrengthEl.style, {
@@ -556,6 +623,7 @@ export function addLorasWidget(node, name, opts, callback) {
clipStrengthEl.style.background = "rgba(0, 0, 0, 0.2)"; clipStrengthEl.style.background = "rgba(0, 0, 0, 0.2)";
// Auto-select all content // Auto-select all content
clipStrengthEl.select(); clipStrengthEl.select();
selectLora(name);
}); });
clipStrengthEl.addEventListener('blur', () => { clipStrengthEl.addEventListener('blur', () => {
@@ -564,31 +632,41 @@ export function addLorasWidget(node, name, opts, callback) {
}); });
// Handle input changes // Handle input changes
clipStrengthEl.addEventListener('change', () => { const clipFocusEntry = createFocusEntry(name, "clip");
let newValue = parseFloat(clipStrengthEl.value);
const commitClipStrengthValue = () => {
// Validate input let parsedValue = parseFloat(clipStrengthEl.value);
if (isNaN(newValue)) { if (isNaN(parsedValue)) {
newValue = 1.0; parsedValue = 1.0;
} }
const normalizedValue = parsedValue.toFixed(2);
// Update value
const lorasData = parseLoraValue(widget.value); const currentLoras = parseLoraValue(widget.value);
const loraIndex = lorasData.findIndex(l => l.name === name); const loraIndex = currentLoras.findIndex(l => l.name === name);
if (loraIndex >= 0) { if (loraIndex >= 0) {
lorasData[loraIndex].clipStrength = newValue.toFixed(2); currentLoras[loraIndex].clipStrength = normalizedValue;
clipStrengthEl.value = normalizedValue;
// Update value and trigger callback
const newLorasValue = formatLoraValue(lorasData); const newLorasValue = formatLoraValue(currentLoras);
widget.value = newLorasValue; widget.value = newLorasValue;
} else {
clipStrengthEl.value = normalizedValue;
} }
}); };
clipStrengthEl.addEventListener('change', commitClipStrengthValue);
// Handle key events // Handle key events
clipStrengthEl.addEventListener('keydown', (e) => { clipStrengthEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
clipStrengthEl.blur(); clipStrengthEl.blur();
} else if (e.key === 'Tab') {
commitClipStrengthValue();
const moved = focusAdjacentFrom(clipFocusEntry, e.shiftKey ? -1 : 1);
if (moved) {
e.preventDefault();
}
} }
}); });

View File

@@ -220,6 +220,7 @@ export function createExpandButton(isExpanded, onClick) {
const button = document.createElement("button"); const button = document.createElement("button");
button.className = "comfy-lora-expand-button"; button.className = "comfy-lora-expand-button";
button.type = "button"; button.type = "button";
button.tabIndex = -1;
Object.assign(button.style, { Object.assign(button.style, {
width: "20px", width: "20px",