fix: ColorPicker hover boundary issues (#42669)

* fix: fix hover boundary issues

* test: fix test case

* build: modify dependency scope

* Update package.json

Co-authored-by: MadCcc <1075746765@qq.com>

---------

Co-authored-by: afc163 <afc163@gmail.com>
Co-authored-by: MadCcc <1075746765@qq.com>
This commit is contained in:
红果汁 2023-05-29 16:50:12 +08:00 committed by GitHub
parent d3ff73a94f
commit 25883ca53a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 10 deletions

View File

@ -5,7 +5,7 @@ import type {
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { CSSProperties } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import genPurePanel from '../_util/PurePanel';
import type { ConfigConsumerProps } from '../config-provider/context';
import { ConfigContext } from '../config-provider/context';
@ -97,8 +97,9 @@ const ColorPicker: CompoundedComponent = (props) => {
[`${prefixCls}-rtl`]: direction,
});
const mergeCls = classNames(mergeRootCls, className, hashId);
const popupAllowCloseRef = useRef(true);
const handleChange = (data: Color, type?: HsbaColorType) => {
const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => {
let color: Color = generateColor(data);
if (colorCleared) {
setColorCleared(false);
@ -112,6 +113,10 @@ const ColorPicker: CompoundedComponent = (props) => {
if (!value) {
setColorValue(color);
}
// Only for drag-and-drop color picking
if (pickColor) {
popupAllowCloseRef.current = false;
}
onChange?.(color, color.toHexString());
};
@ -119,6 +124,10 @@ const ColorPicker: CompoundedComponent = (props) => {
setColorCleared(clear);
};
const handleChangeComplete = () => {
popupAllowCloseRef.current = true;
};
const popoverProps: PopoverProps = {
open: popupOpen,
trigger,
@ -149,9 +158,18 @@ const ColorPicker: CompoundedComponent = (props) => {
return wrapSSR(
<Popover
style={styles?.popup}
onOpenChange={setPopupOpen}
onOpenChange={(visible) => {
if (popupAllowCloseRef.current) {
setPopupOpen(visible);
}
}}
content={
<ColorPickerPanel {...colorBaseProps} onChange={handleChange} onClear={handleClear} />
<ColorPickerPanel
{...colorBaseProps}
onChange={handleChange}
onChangeComplete={handleChangeComplete}
onClear={handleClear}
/>
}
overlayClassName={prefixCls}
{...popoverProps}

View File

@ -10,12 +10,22 @@ import ColorPresets from './components/ColorPresets';
import type { ColorPickerBaseProps } from './interface';
interface ColorPickerPanelProps extends ColorPickerBaseProps {
onChange?: (value?: Color, type?: HsbaColorType) => void;
onChange?: (value?: Color, type?: HsbaColorType, pickColor?: boolean) => void;
onChangeComplete?: (type?: HsbaColorType) => void;
onClear?: (clear?: boolean) => void;
}
const ColorPickerPanel: FC<ColorPickerPanelProps> = (props) => {
const { prefixCls, allowClear, presets, onChange, onClear, color, ...injectProps } = props;
const {
prefixCls,
allowClear,
presets,
onChange,
onClear,
onChangeComplete,
color,
...injectProps
} = props;
const colorPickerPanelPrefixCls = `${prefixCls}-inner-panel`;
const extraPanelRender = (panel: React.ReactNode) => (
@ -45,8 +55,9 @@ const ColorPickerPanel: FC<ColorPickerPanelProps> = (props) => {
<RcColorPicker
prefixCls={prefixCls}
value={color?.toHsb()}
onChange={onChange}
onChange={(colorValue, type) => onChange?.(colorValue, type, true)}
panelRender={extraPanelRender}
onChangeComplete={onChangeComplete}
/>
);
};

View File

@ -1,4 +1,5 @@
import { fireEvent, render } from '@testing-library/react';
import { createEvent, fireEvent, render } from '@testing-library/react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import React, { useMemo, useState } from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -6,6 +7,28 @@ import { waitFakeTimer } from '../../../tests/utils';
import ColorPicker from '../ColorPicker';
import type { Color } from '../color';
function doMouseMove(
container: HTMLElement,
start: number,
end: number,
element = 'ant-color-picker-handler',
) {
const mouseDown = createEvent.mouseDown(container.getElementsByClassName(element)[0], {
pageX: start,
pageY: start,
});
fireEvent(container.getElementsByClassName(element)[0], mouseDown);
// Drag
const mouseMove: any = new Event('mousemove');
mouseMove.pageX = end;
mouseMove.pageY = end;
fireEvent(document, mouseMove);
const mouseUp = createEvent.mouseUp(document);
fireEvent(document, mouseUp);
}
describe('ColorPicker', () => {
mountTest(ColorPicker);
rtlTest(ColorPicker);
@ -257,4 +280,23 @@ describe('ColorPicker', () => {
container.querySelector('.ant-color-picker-color-block-inner')?.getAttribute('style'),
).toEqual('background: rgb(99, 22, 22);');
});
it('Should fix hover boundary issues', async () => {
spyElementPrototypes(HTMLElement, {
getBoundingClientRect: () => ({
x: 0,
y: 100,
width: 100,
height: 100,
}),
});
const { container } = render(<ColorPicker trigger="hover" />);
fireEvent.mouseEnter(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
doMouseMove(container, 0, 999);
expect(container.querySelector('.ant-popover-hidden')).toBeFalsy();
fireEvent.mouseLeave(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-popover-hidden')).toBeTruthy();
});
});

View File

@ -114,7 +114,7 @@
"@ant-design/react-slick": "~1.0.0",
"@babel/runtime": "^7.18.3",
"@ctrl/tinycolor": "^3.6.0",
"@rc-component/color-picker": "~1.1.1",
"@rc-component/color-picker": "~1.2.0",
"@rc-component/mutate-observer": "^1.0.0",
"@rc-component/tour": "~1.8.0",
"@rc-component/trigger": "^1.13.0",
@ -324,4 +324,4 @@
"*.{ts,tsx,js,jsx}": "prettier --ignore-unknown --write",
"*.{json,less,md}": "prettier --ignore-unknown --write"
}
}
}