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);
|
let color: AggregationColor = generateColor(data as AggregationColor);
|
||||||
|
|
||||||
// ignore alpha color
|
// ignore alpha color
|
||||||
@ -125,7 +125,7 @@ const ColorPicker: CompoundedComponent = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only for drag-and-drop color picking
|
// Only for drag-and-drop color picking
|
||||||
if (!pickColor) {
|
if (!changeFromPickerDrag) {
|
||||||
onInternalChangeComplete(color);
|
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`] = `
|
exports[`renders components/color-picker/demo/controlled.tsx correctly 1`] = `
|
||||||
<div
|
<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
|
<div
|
||||||
class="ant-color-picker-color-block"
|
class="ant-space-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-color-picker-color-block-inner"
|
class="ant-color-picker-trigger"
|
||||||
style="background:rgb(22,119,255)"
|
>
|
||||||
/>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -20,6 +20,7 @@ function doMouseMove(
|
|||||||
start: number,
|
start: number,
|
||||||
end: number,
|
end: number,
|
||||||
element: string | HTMLElement = 'ant-color-picker-handler',
|
element: string | HTMLElement = 'ant-color-picker-handler',
|
||||||
|
fireMouseUp = true,
|
||||||
) {
|
) {
|
||||||
const ele =
|
const ele =
|
||||||
element instanceof HTMLElement ? element : container.getElementsByClassName(element)[0];
|
element instanceof HTMLElement ? element : container.getElementsByClassName(element)[0];
|
||||||
@ -44,8 +45,10 @@ function doMouseMove(
|
|||||||
fireEvent(document, mouseMove);
|
fireEvent(document, mouseMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mouseUp = createEvent.mouseUp(document);
|
if (fireMouseUp) {
|
||||||
fireEvent(document, mouseUp);
|
const mouseUp = createEvent.mouseUp(document);
|
||||||
|
fireEvent(document, mouseUp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ColorPicker', () => {
|
describe('ColorPicker', () => {
|
||||||
@ -879,4 +882,58 @@ describe('ColorPicker', () => {
|
|||||||
|
|
||||||
spyRect.mockRestore();
|
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;
|
return colors[activeIndex]?.color;
|
||||||
}, [value, activeIndex, isSingle, lockedColor, gradientDragging]);
|
}, [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 ============================
|
// ============================ Change ============================
|
||||||
const fillColor = (nextColor: AggregationColor | Color, info?: Info) => {
|
const fillColor = (nextColor: AggregationColor | Color, info?: Info) => {
|
||||||
let submitColor = generateColor(nextColor);
|
let submitColor = generateColor(nextColor);
|
||||||
@ -121,16 +131,28 @@ const PanelPicker: FC = () => {
|
|||||||
return new AggregationColor(nextColors);
|
return new AggregationColor(nextColors);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInternalChange = (
|
const onPickerChange = (
|
||||||
colorValue: AggregationColor | Color,
|
colorValue: AggregationColor | Color,
|
||||||
fromPicker?: boolean,
|
fromPicker: boolean,
|
||||||
info?: Info,
|
info?: Info,
|
||||||
) => {
|
) => {
|
||||||
onChange(fillColor(colorValue, info), fromPicker);
|
const nextColor = fillColor(colorValue, info);
|
||||||
|
setPickerColor(nextColor);
|
||||||
|
onChange(nextColor, fromPicker);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInternalChangeComplete = (nextColor: Color, info?: Info) => {
|
const onInternalChangeComplete = (nextColor: Color, info?: Info) => {
|
||||||
|
// Trigger complete event
|
||||||
onChangeComplete(fillColor(nextColor, info));
|
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 ============================
|
// ============================ Render ============================
|
||||||
@ -166,10 +188,10 @@ const PanelPicker: FC = () => {
|
|||||||
|
|
||||||
<RcColorPicker
|
<RcColorPicker
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
value={activeColor?.toHsb()}
|
value={mergedPickerColor?.toHsb()}
|
||||||
disabledAlpha={disabledAlpha}
|
disabledAlpha={disabledAlpha}
|
||||||
onChange={(colorValue, info) => {
|
onChange={(colorValue, info) => {
|
||||||
onInternalChange(colorValue, true, info);
|
onPickerChange(colorValue, true, info);
|
||||||
}}
|
}}
|
||||||
onChangeComplete={(colorValue, info) => {
|
onChangeComplete={(colorValue, info) => {
|
||||||
onInternalChangeComplete(colorValue, info);
|
onInternalChangeComplete(colorValue, info);
|
||||||
@ -178,7 +200,7 @@ const PanelPicker: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
value={activeColor}
|
value={activeColor}
|
||||||
onChange={onInternalChange}
|
onChange={onInputChange}
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
disabledAlpha={disabledAlpha}
|
disabledAlpha={disabledAlpha}
|
||||||
{...injectProps}
|
{...injectProps}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
## zh-CN
|
## zh-CN
|
||||||
|
|
||||||
通过 `value` 和 `onChange` 设置组件为受控模式。
|
通过 `value` 和 `onChange` 设置组件为受控模式,如果通过 `onChangeComplete` 受控则会锁定展示颜色。
|
||||||
|
|
||||||
## en-US
|
## 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 React, { useState } from 'react';
|
||||||
import { ColorPicker } from 'antd';
|
import { ColorPicker, Space } from 'antd';
|
||||||
import type { ColorPickerProps, GetProp } from 'antd';
|
import type { ColorPickerProps, GetProp } from 'antd';
|
||||||
|
|
||||||
type Color = GetProp<ColorPickerProps, 'value'>;
|
type Color = GetProp<ColorPickerProps, 'value'>;
|
||||||
|
|
||||||
const Demo: React.FC = () => {
|
const Demo: React.FC = () => {
|
||||||
const [color, setColor] = useState<Color>('#1677ff');
|
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;
|
export default Demo;
|
||||||
|
@ -61,7 +61,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
|||||||
| trigger | ColorPicker trigger mode | `hover` \| `click` | `click` | |
|
| trigger | ColorPicker trigger mode | `hover` \| `click` | `click` | |
|
||||||
| value | Value of color | string \| `Color` | - | |
|
| value | Value of color | string \| `Color` | - | |
|
||||||
| onChange | Callback when `value` is changed | `(value: Color, css: string) => void` | - | |
|
| 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` | - | |
|
| onFormatChange | Callback when `format` is changed | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - | |
|
||||||
| onOpenChange | Callback when `open` is changed | `(open: boolean) => void` | - | |
|
| onOpenChange | Callback when `open` is changed | `(open: boolean) => void` | - | |
|
||||||
| onClear | Called when clear | `() => void` | - | 5.6.0 |
|
| onClear | Called when clear | `() => void` | - | 5.6.0 |
|
||||||
|
@ -62,7 +62,7 @@ group:
|
|||||||
| trigger | 颜色选择器的触发模式 | `hover` \| `click` | `click` | |
|
| trigger | 颜色选择器的触发模式 | `hover` \| `click` | `click` | |
|
||||||
| value | 颜色的值 | string \| `Color` | - | |
|
| value | 颜色的值 | string \| `Color` | - | |
|
||||||
| onChange | 颜色变化的回调 | `(value: Color, css: string) => void` | - | |
|
| 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` | - | |
|
| onFormatChange | 颜色格式变化的回调 | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - | |
|
||||||
| onOpenChange | 当 `open` 被改变时的回调 | `(open: boolean) => void` | - | |
|
| onOpenChange | 当 `open` 被改变时的回调 | `(open: boolean) => void` | - | |
|
||||||
| onClear | 清除的回调 | `() => void` | - | 5.6.0 |
|
| onClear | 清除的回调 | `() => void` | - | 5.6.0 |
|
||||||
|
Loading…
Reference in New Issue
Block a user