mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
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:
parent
769331dec4
commit
82b05b209d
@ -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);
|
||||
}
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
`;
|
||||
|
@ -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)',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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}
|
||||
|
@ -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`.
|
||||
|
@ -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;
|
||||
|
@ -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 |
|
||||
|
@ -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 |
|
||||
|
Loading…
Reference in New Issue
Block a user