feat(sidebar): add editable hex code input to color picker
- Replaced read-only hex code span with an editable text input in `createColorPicker` - Implemented bidirectional sync between text input and color picker - Added validation for hex codes and auto-correction on blur - Ensured keydown propagation is stopped to prevent triggering global shortcuts - Added aria-label for accessibility - Updated unit tests to verify sync logic and event handling - Added CSS styles for `magnify-color-preview` input to match UI design Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com>
This commit is contained in:
co-authored by
AEmotionStudio
parent
7ac655da1c
commit
6bd509bdf7
@@ -381,4 +381,19 @@
|
||||
font-size: 11px;
|
||||
font-family: monospace;
|
||||
color: var(--descrip-text, #999);
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
width: 60px;
|
||||
transition: all 0.2s;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.magnify-color-preview:hover,
|
||||
.magnify-color-preview:focus {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-color: var(--border-color, #444);
|
||||
color: var(--fg-color, #ddd);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -237,12 +237,44 @@ export function createColorPicker(
|
||||
colorInput.className = 'magnify-color-input';
|
||||
colorInput.value = value;
|
||||
|
||||
const colorPreview = document.createElement('span');
|
||||
const colorPreview = document.createElement('input');
|
||||
colorPreview.type = 'text';
|
||||
colorPreview.className = 'magnify-color-preview';
|
||||
colorPreview.textContent = value;
|
||||
colorPreview.value = value;
|
||||
colorPreview.maxLength = 7;
|
||||
colorPreview.setAttribute('aria-label', `Hex code for ${label}`);
|
||||
|
||||
// Stop propagation of keys to prevent global hotkeys while typing
|
||||
colorPreview.addEventListener('keydown', (e) => e.stopPropagation());
|
||||
|
||||
// Select all on focus for easy copy/paste
|
||||
colorPreview.addEventListener('focus', () => colorPreview.select());
|
||||
|
||||
// Sync from text input to color picker
|
||||
const updateFromText = () => {
|
||||
let val = colorPreview.value;
|
||||
if (!val.startsWith('#') && /^[0-9A-Fa-f]{6}$/.test(val)) {
|
||||
val = '#' + val;
|
||||
}
|
||||
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(val)) {
|
||||
colorPreview.value = val; // Ensure normalized format
|
||||
colorInput.value = val;
|
||||
if (onInput) onInput(val);
|
||||
onChange(val);
|
||||
}
|
||||
};
|
||||
|
||||
colorPreview.addEventListener('change', updateFromText);
|
||||
colorPreview.addEventListener('blur', () => {
|
||||
// Revert to valid value on blur if invalid
|
||||
if (!/^#[0-9A-Fa-f]{6}$/.test(colorPreview.value)) {
|
||||
colorPreview.value = colorInput.value;
|
||||
}
|
||||
});
|
||||
|
||||
colorInput.addEventListener('input', () => {
|
||||
colorPreview.textContent = colorInput.value;
|
||||
colorPreview.value = colorInput.value;
|
||||
if (onInput) onInput(colorInput.value);
|
||||
});
|
||||
|
||||
|
||||
@@ -141,5 +141,55 @@ describe('Sidebar Controls', () => {
|
||||
expect(labelEl.getAttribute('for')).toBe(input.id);
|
||||
expect(input.id).toMatch(/^magnify-color-/);
|
||||
});
|
||||
|
||||
it('should sync text input with color picker', () => {
|
||||
const onChange = vi.fn();
|
||||
const colorRow = createColorPicker('Test Color', '#000000', onChange);
|
||||
|
||||
const colorInput = colorRow.querySelector('input[type="color"]') as HTMLInputElement;
|
||||
const textInput = colorRow.querySelector('input.magnify-color-preview') as HTMLInputElement;
|
||||
|
||||
expect(textInput).toBeTruthy();
|
||||
expect(textInput.value).toBe('#000000');
|
||||
|
||||
// Simulate typing a valid hex code
|
||||
textInput.value = '#ffffff';
|
||||
textInput.dispatchEvent(new Event('change'));
|
||||
|
||||
expect(colorInput.value).toBe('#ffffff');
|
||||
expect(onChange).toHaveBeenCalledWith('#ffffff');
|
||||
|
||||
// Simulate typing a hex code without hash
|
||||
textInput.value = 'ff0000';
|
||||
textInput.dispatchEvent(new Event('change'));
|
||||
|
||||
expect(textInput.value).toBe('#ff0000'); // Should normalize
|
||||
expect(colorInput.value).toBe('#ff0000');
|
||||
expect(onChange).toHaveBeenCalledWith('#ff0000');
|
||||
|
||||
// Simulate typing invalid hex code
|
||||
textInput.value = 'invalid';
|
||||
textInput.dispatchEvent(new Event('change'));
|
||||
|
||||
// Should not update color input (keeps previous value)
|
||||
expect(colorInput.value).toBe('#ff0000');
|
||||
|
||||
// Simulate blur with invalid code -> should revert
|
||||
textInput.dispatchEvent(new Event('blur'));
|
||||
expect(textInput.value).toBe('#ff0000');
|
||||
});
|
||||
|
||||
it('should stop keydown propagation on text input', () => {
|
||||
const onChange = vi.fn();
|
||||
const colorRow = createColorPicker('Test Color', '#000000', onChange);
|
||||
const textInput = colorRow.querySelector('input.magnify-color-preview') as HTMLInputElement;
|
||||
|
||||
const event = new KeyboardEvent('keydown', { key: 'a' });
|
||||
const stopPropagationSpy = vi.spyOn(event, 'stopPropagation');
|
||||
|
||||
textInput.dispatchEvent(event);
|
||||
|
||||
expect(stopPropagationSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user