Skip to content

Commit 8741d04

Browse files
committed
fix(editor): keep a hex draft in the Custom background rows
ColorField emits #abc while a hex is typed. The Custom rows forwarded it, the value came back normalised and replaced the draft mid-typing. They now forward only complete #rrggbb, and the gradient row keeps the colour as picked instead of reading back the lightness-bounded first stop.
1 parent 611dbad commit 8741d04

2 files changed

Lines changed: 81 additions & 3 deletions

File tree

‎src/components/ai-edition/RightPanes.backgroundPicker.test.tsx‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import "@testing-library/jest-dom";
77
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
8+
import { useState } from "react";
89
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
910
import { I18nProvider } from "@/contexts/I18nContext";
1011
import { LOCALE_STORAGE_KEY } from "@/i18n/config";
@@ -82,6 +83,67 @@ describe("gradient tab", () => {
8283
});
8384
});
8485

86+
/** The picker wired like the pane: a live change comes back as the new value. */
87+
function Stateful({ initial, onLive }: { initial: string; onLive: (v: string) => void }) {
88+
const [value, setValue] = useState(initial);
89+
return (
90+
<WallpaperPicker
91+
value={value}
92+
hasDocument
93+
onChange={setValue}
94+
onLiveChange={(v) => {
95+
onLive(v);
96+
setValue(v);
97+
}}
98+
onCommit={() => undefined}
99+
onPickFile={() => undefined}
100+
updateNativeBackground={false}
101+
/>
102+
);
103+
}
104+
105+
describe("typing a hex in a Custom row", () => {
106+
function typeHex(button: string, initial: string, text: string) {
107+
const onLive = vi.fn();
108+
render(
109+
<I18nProvider>
110+
<Stateful initial={initial} onLive={onLive} />
111+
</I18nProvider>,
112+
);
113+
fireEvent.click(screen.getByRole("button", { name: button }));
114+
const input = screen.getByRole("textbox", { name: "Color wheel" }) as HTMLInputElement;
115+
fireEvent.change(input, { target: { value: text } });
116+
return { onLive, input };
117+
}
118+
119+
it("keeps a 3-digit draft on the gradient row instead of replacing it", () => {
120+
const { onLive, input } = typeHex(
121+
"Gradient from one color",
122+
"linear-gradient(135deg, #3b82f6, #8b5cf6)",
123+
"#abc",
124+
);
125+
expect(onLive).not.toHaveBeenCalled();
126+
expect(input.value).toBe("#abc");
127+
});
128+
129+
it("keeps a 3-digit draft on the colour row instead of replacing it", () => {
130+
const { onLive, input } = typeHex("Custom color", "#16171d", "#abc");
131+
expect(onLive).not.toHaveBeenCalled();
132+
expect(input.value).toBe("#abc");
133+
});
134+
135+
it("keeps a dark pick as typed, though the gradient lifts its lightness", () => {
136+
const { onLive, input } = typeHex(
137+
"Gradient from one color",
138+
"linear-gradient(135deg, #3b82f6, #8b5cf6)",
139+
"#000000",
140+
);
141+
expect(onLive).toHaveBeenCalledTimes(1);
142+
expect(parseCssGradient(onLive.mock.calls[0][0])?.stops[0].color).not.toBe("#000000");
143+
expect(input.value).toBe("#000000");
144+
});
145+
});
146+
85147
describe("color tab", () => {
86148
it("shows the palette before the custom colour, and writes what is picked there", () => {
87149
const { onLiveChange } = renderPicker("#16171d");

‎src/components/ai-edition/RightPanes.tsx‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ function useMemoCustomWallpapers(current: string): string[] {
442442

443443
/** The seed the one-colour gradient starts from when the wallpaper is not one of its own. */
444444
const GRADIENT_SEED_FALLBACK = "#3b82f6";
445+
const FULL_HEX = /^#[0-9a-f]{6}$/i;
445446

446447
/**
447448
* The free choice, folded behind the curated set: a "Custom" row under the swatches whose
@@ -472,7 +473,12 @@ function CustomBackgroundRow({
472473
value={value}
473474
swatch={swatch}
474475
disabled={!hasDocument}
475-
onChange={onChange}
476+
// Only a complete #rrggbb goes out. ColorField also emits "#abc" while the hex is
477+
// being typed; forwarding it wrote a value that came back normalised and replaced
478+
// the draft under the user's fingers.
479+
onChange={(hex) => {
480+
if (FULL_HEX.test(hex)) onChange(hex.toLowerCase());
481+
}}
476482
onCommit={onCommit}
477483
/>
478484
</div>
@@ -566,7 +572,14 @@ export function WallpaperPicker({
566572
const live = onLiveChange ?? onChange;
567573
const commit = () => void onCommit?.();
568574
const customUrls = useMemoCustomWallpapers(value);
569-
const seed = gradientSeedColor(value) ?? GRADIENT_SEED_FALLBACK;
575+
// The colour the user picked, kept as picked: the gradient's first stop is that colour held
576+
// inside 35–85% lightness, so reading it back would move a dark pick under the user. It only
577+
// stands while the wallpaper is still the gradient it made; a preset or undo replaces it.
578+
const [picked, setPicked] = useState<string | null>(null);
579+
const seed =
580+
picked && oneColorGradient(picked) === value
581+
? picked
582+
: (gradientSeedColor(value) ?? GRADIENT_SEED_FALLBACK);
570583

571584
const isSelected = (candidate: string) => value === candidate;
572585
const tabs = [
@@ -663,7 +676,10 @@ export function WallpaperPicker({
663676
value={seed}
664677
swatch={oneColorGradient(seed)}
665678
hasDocument={hasDocument}
666-
onChange={(hex) => live(oneColorGradient(hex))}
679+
onChange={(hex) => {
680+
setPicked(hex);
681+
live(oneColorGradient(hex));
682+
}}
667683
onCommit={commit}
668684
/>
669685
</>

0 commit comments

Comments
 (0)