Browse Source

Fix lighting color picker interactions

TEST-x
xuzhijie 2 months ago
parent
commit
29312647fb
  1. 44
      src/components/lighting/LightingEffectModal.test.tsx
  2. 27
      src/components/lighting/LightingEffectModal.tsx

44
src/components/lighting/LightingEffectModal.test.tsx

@ -57,7 +57,7 @@ describe("LightingEffectModal preview", () => {
expect(subject.style.top).toBe("50%"); expect(subject.style.top).toBe("50%");
expect(subject.style.transform).toContain("perspective(420px)"); expect(subject.style.transform).toContain("perspective(420px)");
expect(subject.style.transform).toContain("rotateX(-8deg)"); expect(subject.style.transform).toContain("rotateX(-8deg)");
expect(subject.style.transform).toContain("rotateY(58deg)"); expect(subject.style.transform).toContain("rotateY(-58deg)");
expect(beam.style.transform).toContain("rotate(16.53deg)"); expect(beam.style.transform).toContain("rotate(16.53deg)");
}); });
@ -118,6 +118,48 @@ describe("LightingEffectModal preview", () => {
expect(screen.getByTestId("lighting-hue-thumb").style.left).toBe("50%"); expect(screen.getByTestId("lighting-hue-thumb").style.left).toBe("50%");
}); });
it("keeps the color picker open after dragging the hue slider", () => {
renderLightingModal({
color: "#ffffff",
});
fireEvent.click(screen.getByLabelText("打光颜色"));
const hueSlider = screen.getByLabelText("打光颜色色相");
hueSlider.getBoundingClientRect = () =>
({
left: 0,
top: 0,
width: 180,
height: 12,
right: 180,
bottom: 12,
x: 0,
y: 0,
toJSON: () => ({}),
}) as DOMRect;
fireEvent.pointerDown(hueSlider, { clientX: 90, pointerId: 1 });
fireEvent.pointerUp(hueSlider, { clientX: 90, pointerId: 1 });
fireEvent.click(hueSlider);
expect(screen.queryByTestId("lighting-color-picker-popover")).not.toBeNull();
});
it("closes the color picker when clicking outside", () => {
renderLightingModal({
color: "#ffffff",
});
fireEvent.click(screen.getByLabelText("打光颜色"));
expect(screen.queryByTestId("lighting-color-picker-popover")).not.toBeNull();
fireEvent.pointerDown(document.body);
expect(screen.queryByTestId("lighting-color-picker-popover")).toBeNull();
});
it("applies smart preset parameters and light position", () => { it("applies smart preset parameters and light position", () => {
const { onApply } = renderLightingModal({ const { onApply } = renderLightingModal({
smartMode: true, smartMode: true,

27
src/components/lighting/LightingEffectModal.tsx

@ -211,7 +211,7 @@ function getSubjectProjection(viewMode: LightingViewMode) {
left: 50, left: 50,
top: 50, top: 50,
rotateX: -8, rotateX: -8,
rotateY: 58, rotateY: -58,
rotateZ: 0, rotateZ: 0,
scale: 0.98, scale: 0.98,
width: 45, width: 45,
@ -451,6 +451,7 @@ function LightingColorPicker({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const hsv = useMemo(() => hexToHsv(value), [value]); const hsv = useMemo(() => hexToHsv(value), [value]);
const [hue, setHue] = useState(hsv.h); const [hue, setHue] = useState(hsv.h);
const pickerRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
if (hsv.s > 0) { if (hsv.s > 0) {
@ -458,6 +459,19 @@ function LightingColorPicker({
} }
}, [hsv.h, hsv.s]); }, [hsv.h, hsv.s]);
useEffect(() => {
if (!open) return;
const handlePointerDown = (event: PointerEvent) => {
const target = event.target;
if (target instanceof Node && pickerRef.current?.contains(target)) return;
setOpen(false);
};
document.addEventListener("pointerdown", handlePointerDown);
return () => document.removeEventListener("pointerdown", handlePointerDown);
}, [open]);
const updateSaturationValue = (event: ReactPointerEvent<HTMLDivElement>) => { const updateSaturationValue = (event: ReactPointerEvent<HTMLDivElement>) => {
const rect = event.currentTarget.getBoundingClientRect(); const rect = event.currentTarget.getBoundingClientRect();
const s = clamp((event.clientX - rect.left) / rect.width, 0, 1); const s = clamp((event.clientX - rect.left) / rect.width, 0, 1);
@ -483,7 +497,7 @@ function LightingColorPicker({
}; };
return ( return (
<div className="relative"> <div ref={pickerRef} className="relative">
<button <button
type="button" type="button"
onClick={() => setOpen((current) => !current)} onClick={() => setOpen((current) => !current)}
@ -494,7 +508,10 @@ function LightingColorPicker({
</button> </button>
{open && ( {open && (
<div className="absolute left-0 top-10 z-[70] w-44 rounded-lg border border-neutral-600 bg-neutral-900 p-2 shadow-2xl shadow-black/60"> <div
data-testid="lighting-color-picker-popover"
className="absolute left-0 top-10 z-[70] w-44 rounded-lg border border-neutral-600 bg-neutral-900 p-2 shadow-2xl shadow-black/60"
>
<div <div
className="relative h-28 cursor-crosshair overflow-hidden rounded-md" className="relative h-28 cursor-crosshair overflow-hidden rounded-md"
style={{ style={{
@ -737,13 +754,13 @@ export function LightingEffectModal({
</div> </div>
</label> </label>
<label className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<span className="text-neutral-400"></span> <span className="text-neutral-400"></span>
<LightingColorPicker <LightingColorPicker
value={settings.color} value={settings.color}
onChange={(color) => patchSettings({ color })} onChange={(color) => patchSettings({ color })}
/> />
</label> </div>
<div className="space-y-2"> <div className="space-y-2">
<span className="text-neutral-400"></span> <span className="text-neutral-400"></span>

Loading…
Cancel
Save