refactor: ColorPicker panel free for uncontrolled (#50785)

* refactor: change on drag will change panel color

* docs: update demo

* test: update snapshot

* chore: wrap with useEvent

* fix: order of events

* fix: sync logic
This commit is contained in:
二货爱吃白萝卜 2024-09-11 10:46:30 +08:00 committed by GitHub
parent 769331dec4
commit 82b05b209d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 824 additions and 325 deletions

View File

@ -108,7 +108,7 @@ const ColorPicker: CompoundedComponent = (props) => {
}
};
const onInternalChange: ColorPickerPanelProps['onChange'] = (data, pickColor) => {
const onInternalChange: ColorPickerPanelProps['onChange'] = (data, changeFromPickerDrag) => {
let color: AggregationColor = generateColor(data as AggregationColor);
// ignore alpha color
@ -125,7 +125,7 @@ const ColorPicker: CompoundedComponent = (props) => {
}
// Only for drag-and-drop color picking
if (!pickColor) {
if (!changeFromPickerDrag) {
onInternalChangeComplete(color);
}
};

View File

@ -32,15 +32,39 @@ exports[`renders components/color-picker/demo/base.tsx correctly 1`] = `
exports[`renders components/color-picker/demo/controlled.tsx correctly 1`] = `
<div
class="ant-color-picker-trigger"
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
>
<div
class="ant-color-picker-color-block"
class="ant-space-item"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22,119,255)"
/>
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22,119,255)"
/>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22,119,255)"
/>
</div>
</div>
</div>
</div>
`;

View File

@ -20,6 +20,7 @@ function doMouseMove(
start: number,
end: number,
element: string | HTMLElement = 'ant-color-picker-handler',
fireMouseUp = true,
) {
const ele =
element instanceof HTMLElement ? element : container.getElementsByClassName(element)[0];
@ -44,8 +45,10 @@ function doMouseMove(
fireEvent(document, mouseMove);
}
const mouseUp = createEvent.mouseUp(document);
fireEvent(document, mouseUp);
if (fireMouseUp) {
const mouseUp = createEvent.mouseUp(document);
fireEvent(document, mouseUp);
}
}
describe('ColorPicker', () => {
@ -879,4 +882,58 @@ describe('ColorPicker', () => {
spyRect.mockRestore();
});
describe('controlled with `onChangeComplete`', () => {
let spyRect: ReturnType<typeof spyElementPrototypes>;
beforeEach(() => {
spyRect = spyElementPrototypes(HTMLElement, {
getBoundingClientRect: () => ({
x: 0,
y: 100,
width: 100,
height: 100,
}),
});
});
afterEach(() => {
spyRect.mockRestore();
});
it('lock value', async () => {
const onChange = jest.fn();
const onChangeComplete = jest.fn();
const { container } = render(
<ColorPicker value="#F00" open onChange={onChange} onChangeComplete={onChangeComplete} />,
);
doMouseMove(container, 0, 50, 'ant-color-picker-slider-handle', false);
expect(onChange).toHaveBeenCalledWith(
expect.anything(),
// Safe to change with any value but (255/0/0)
'rgb(0,255,255)',
);
expect(onChangeComplete).not.toHaveBeenCalled();
// Inline Color Block (locked)
expect(container.querySelectorAll('.ant-color-picker-color-block-inner')[0]).toHaveStyle({
background: 'rgb(255, 0, 0)',
});
// Popup Color Block (follow operation)
expect(container.querySelectorAll('.ant-color-picker-color-block-inner')[1]).toHaveStyle({
background: 'rgb(0, 255, 255)',
});
// Mouse up
fireEvent.mouseUp(document);
// Lock color back
expect(container.querySelectorAll('.ant-color-picker-color-block-inner')[1]).toHaveStyle({
background: 'rgb(255, 0, 0)',
});
});
});
});

View File

@ -85,6 +85,16 @@ const PanelPicker: FC = () => {
return colors[activeIndex]?.color;
}, [value, activeIndex, isSingle, lockedColor, gradientDragging]);
// ========================= Picker Color =========================
const [pickerColor, setPickerColor] = React.useState<AggregationColor | null>(activeColor);
const [forceSync, setForceSync] = React.useState(0);
const mergedPickerColor = pickerColor?.equals(activeColor) ? activeColor : pickerColor;
useLayoutEffect(() => {
setPickerColor(activeColor);
}, [forceSync, activeColor?.toHexString()]);
// ============================ Change ============================
const fillColor = (nextColor: AggregationColor | Color, info?: Info) => {
let submitColor = generateColor(nextColor);
@ -121,16 +131,28 @@ const PanelPicker: FC = () => {
return new AggregationColor(nextColors);
};
const onInternalChange = (
const onPickerChange = (
colorValue: AggregationColor | Color,
fromPicker?: boolean,
fromPicker: boolean,
info?: Info,
) => {
onChange(fillColor(colorValue, info), fromPicker);
const nextColor = fillColor(colorValue, info);
setPickerColor(nextColor);
onChange(nextColor, fromPicker);
};
const onInternalChangeComplete = (nextColor: Color, info?: Info) => {
// Trigger complete event
onChangeComplete(fillColor(nextColor, info));
// Back of origin color in case in controlled
// This will set after `onChangeComplete` to avoid `setState` trigger rerender
// which will make `fillColor` get wrong `color.cleared` state
setForceSync((ori) => ori + 1);
};
const onInputChange = (colorValue: AggregationColor) => {
onChange(fillColor(colorValue));
};
// ============================ Render ============================
@ -166,10 +188,10 @@ const PanelPicker: FC = () => {
<RcColorPicker
prefixCls={prefixCls}
value={activeColor?.toHsb()}
value={mergedPickerColor?.toHsb()}
disabledAlpha={disabledAlpha}
onChange={(colorValue, info) => {
onInternalChange(colorValue, true, info);
onPickerChange(colorValue, true, info);
}}
onChangeComplete={(colorValue, info) => {
onInternalChangeComplete(colorValue, info);
@ -178,7 +200,7 @@ const PanelPicker: FC = () => {
/>
<ColorInput
value={activeColor}
onChange={onInternalChange}
onChange={onInputChange}
prefixCls={prefixCls}
disabledAlpha={disabledAlpha}
{...injectProps}

View File

@ -1,7 +1,7 @@
## zh-CN
通过 `value``onChange` 设置组件为受控模式。
通过 `value``onChange` 设置组件为受控模式,如果通过 `onChangeComplete` 受控则会锁定展示颜色
## en-US
Set the component to controlled mode.
Set the component to controlled mode. Will lock the display color if controlled by `onChangeComplete`.

View File

@ -1,12 +1,18 @@
import React, { useState } from 'react';
import { ColorPicker } from 'antd';
import { ColorPicker, Space } from 'antd';
import type { ColorPickerProps, GetProp } from 'antd';
type Color = GetProp<ColorPickerProps, 'value'>;
const Demo: React.FC = () => {
const [color, setColor] = useState<Color>('#1677ff');
return <ColorPicker value={color} onChange={setColor} />;
return (
<Space>
<ColorPicker value={color} onChange={setColor} />
<ColorPicker value={color} onChangeComplete={setColor} />
</Space>
);
};
export default Demo;

View File

@ -61,7 +61,7 @@ Common props ref[Common props](/docs/react/common-props)
| trigger | ColorPicker trigger mode | `hover` \| `click` | `click` | |
| value | Value of color | string \| `Color` | - | |
| onChange | Callback when `value` is changed | `(value: Color, css: string) => void` | - | |
| onChangeComplete | Called when color pick ends | `(value: Color) => void` | - | 5.7.0 |
| onChangeComplete | Called when color pick ends. Will not change the display color when `value` controlled by `onChangeComplete` | `(value: Color) => void` | - | 5.7.0 |
| onFormatChange | Callback when `format` is changed | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - | |
| onOpenChange | Callback when `open` is changed | `(open: boolean) => void` | - | |
| onClear | Called when clear | `() => void` | - | 5.6.0 |

View File

@ -62,7 +62,7 @@ group:
| trigger | 颜色选择器的触发模式 | `hover` \| `click` | `click` | |
| value | 颜色的值 | string \| `Color` | - | |
| onChange | 颜色变化的回调 | `(value: Color, css: string) => void` | - | |
| onChangeComplete | 颜色选择完成的回调 | `(value: Color) => void` | - | 5.7.0 |
| onChangeComplete | 颜色选择完成的回调,通过 `onChangeComplete``value` 受控时拖拽不会改变展示颜色 | `(value: Color) => void` | - | 5.7.0 |
| onFormatChange | 颜色格式变化的回调 | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - | |
| onOpenChange | 当 `open` 被改变时的回调 | `(open: boolean) => void` | - | |
| onClear | 清除的回调 | `() => void` | - | 5.6.0 |