diff --git a/css/sidebar.css b/css/sidebar.css index 9be093d..3d622c9 100644 --- a/css/sidebar.css +++ b/css/sidebar.css @@ -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; } diff --git a/src/sidebar/SidebarSettings.ts b/src/sidebar/SidebarSettings.ts index d40a786..1b5fdd6 100644 --- a/src/sidebar/SidebarSettings.ts +++ b/src/sidebar/SidebarSettings.ts @@ -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); }); diff --git a/tests/unit/SidebarControls.test.ts b/tests/unit/SidebarControls.test.ts index 165c108..e9c925c 100644 --- a/tests/unit/SidebarControls.test.ts +++ b/tests/unit/SidebarControls.test.ts @@ -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(); + }); }); });