From 6bd509bdf7b02ae5da2fec022354015596a447d0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:09:24 +0000 Subject: [PATCH 1/3] 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> --- css/sidebar.css | 15 +++++++++ src/sidebar/SidebarSettings.ts | 38 +++++++++++++++++++++-- tests/unit/SidebarControls.test.ts | 50 ++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) 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(); + }); }); }); From 07374a83062bbc1c0810e5d033643cee137b231c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:27:45 +0000 Subject: [PATCH 2/3] 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 change/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> --- src/sidebar/SidebarSettings.ts | 12 ++++++------ tests/unit/SidebarControls.test.ts | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/sidebar/SidebarSettings.ts b/src/sidebar/SidebarSettings.ts index 1b5fdd6..e69c711 100644 --- a/src/sidebar/SidebarSettings.ts +++ b/src/sidebar/SidebarSettings.ts @@ -262,16 +262,16 @@ export function createColorPicker( colorInput.value = val; if (onInput) onInput(val); onChange(val); + } else { + // Revert to last valid value if invalid + colorPreview.value = colorInput.value; } }; 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; - } - }); + // Blur is handled by change for most cases, but keeping it as a fallback isn't harmful + // However, since change covers commit actions (Enter/Blur), the explicit blur handler is redundant if updateFromText handles reversion. + // Removing explicit blur handler to avoid double-handling, as change event suffices for committing value. colorInput.addEventListener('input', () => { colorPreview.value = colorInput.value; diff --git a/tests/unit/SidebarControls.test.ts b/tests/unit/SidebarControls.test.ts index e9c925c..62247b4 100644 --- a/tests/unit/SidebarControls.test.ts +++ b/tests/unit/SidebarControls.test.ts @@ -173,9 +173,7 @@ describe('Sidebar Controls', () => { // 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')); + // Should revert text input to last valid value immediately on change expect(textInput.value).toBe('#ff0000'); }); From ff56bbd11167b82b3ae9dfe04b0f4e2c599aa9de Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:47:38 +0000 Subject: [PATCH 3/3] 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 change/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 - Ran build script to update `web/` artifacts Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com> --- web/sidebar.css | 15 +++++++++++++++ web/sidebar/SidebarSettings.js | 26 +++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/web/sidebar.css b/web/sidebar.css index 9be093d..3d622c9 100644 --- a/web/sidebar.css +++ b/web/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/web/sidebar/SidebarSettings.js b/web/sidebar/SidebarSettings.js index bce8c55..024006f 100644 --- a/web/sidebar/SidebarSettings.js +++ b/web/sidebar/SidebarSettings.js @@ -127,11 +127,31 @@ function createColorPicker(label, value, onChange, tooltip, onInput) { colorInput.id = colorId; 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}`); + colorPreview.addEventListener("keydown", (e) => e.stopPropagation()); + colorPreview.addEventListener("focus", () => colorPreview.select()); + 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; + colorInput.value = val; + if (onInput) onInput(val); + onChange(val); + } else { + colorPreview.value = colorInput.value; + } + }; + colorPreview.addEventListener("change", updateFromText); colorInput.addEventListener("input", () => { - colorPreview.textContent = colorInput.value; + colorPreview.value = colorInput.value; if (onInput) onInput(colorInput.value); }); colorInput.addEventListener("change", () => {