feat: switch visible to open for Tooltip (#37241)

* feat: switch visible to open for Tooltip & Popover & Popconfirm

* fix lint

* fix: merge state

* chore: Update ts part

* test: add legacy test

* test: fix test case

Co-authored-by: 二货机器人 <smith3816@gmail.com>
This commit is contained in:
yykoypj 2022-08-26 17:17:13 +08:00 committed by GitHub
parent 8b93cf5b10
commit 6b658dab76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 270 additions and 143 deletions

View File

@ -417,7 +417,7 @@ describe('ConfigProvider', () => {
// Popconfirm
testPair('Popconfirm', props => (
<div>
<Popconfirm {...props} visible>
<Popconfirm {...props} open>
<span>Bamboo</span>
</Popconfirm>
</div>
@ -426,7 +426,7 @@ describe('ConfigProvider', () => {
// Popover
testPair('Popover', props => (
<div>
<Popover {...props} visible>
<Popover {...props} open>
<span>Light</span>
</Popover>
</div>
@ -555,7 +555,7 @@ describe('ConfigProvider', () => {
// Tooltip
testPair('Tooltip', props => (
<Tooltip {...props} title="Bamboo" visible>
<Tooltip {...props} title="Bamboo" open>
<span>Light</span>
</Tooltip>
));

View File

@ -177,7 +177,7 @@ const App: React.FC = () => (
<DatePicker open />
<TimePicker open defaultOpenValue={moment()} />
<RangePicker open style={{ width: 200 }} />
<Popconfirm title="Question?" visible>
<Popconfirm title="Question?" open>
<a>Click to confirm</a>
</Popconfirm>
<Transfer dataSource={[]} showSearch targetKeys={[]} render={(item: any) => item.title} />

View File

@ -55,9 +55,9 @@ export default class MenuItem extends React.Component<MenuItemProps> {
if (!siderCollapsed && !inlineCollapsed) {
tooltipProps.title = null;
// Reset `visible` to fix control mode tooltip display not correct
// Reset `open` to fix control mode tooltip display not correct
// ref: https://github.com/ant-design/ant-design/issues/16742
tooltipProps.visible = false;
tooltipProps.open = false;
}
const childrenLength = toArray(children).length;

View File

@ -23,7 +23,7 @@ describe('Popconfirm', () => {
});
it('should popup Popconfirm dialog', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const wrapper = mount(
<Popconfirm
@ -32,7 +32,7 @@ describe('Popconfirm', () => {
cancelText="No"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
>
<span>Delete</span>
</Popconfirm>,
@ -40,11 +40,11 @@ describe('Popconfirm', () => {
const triggerNode = wrapper.find('span').at(0);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenLastCalledWith(true, undefined);
expect(onOpenChange).toHaveBeenLastCalledWith(true, undefined);
expect(wrapper.find('.popconfirm-test').length).toBe(1);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenLastCalledWith(false, undefined);
expect(onOpenChange).toHaveBeenLastCalledWith(false, undefined);
});
it('should show overlay when trigger is clicked', async () => {
@ -88,7 +88,7 @@ describe('Popconfirm', () => {
expect(popup.innerHTML).toMatchSnapshot();
});
it('should be controlled by visible', () => {
it('should be controlled by open', () => {
const ref = React.createRef();
jest.useFakeTimers();
const popconfirm = mount(
@ -97,10 +97,10 @@ describe('Popconfirm', () => {
</Popconfirm>,
);
expect(ref.current.getPopupDomNode()).toBeFalsy();
popconfirm.setProps({ visible: true });
popconfirm.setProps({ open: true });
expect(ref.current.getPopupDomNode()).toBeTruthy();
expect(ref.current.getPopupDomNode().className).not.toContain('ant-popover-hidden');
popconfirm.setProps({ visible: false });
popconfirm.setProps({ open: false });
popconfirm.update(); // https://github.com/enzymejs/enzyme/issues/2305
act(() => {
jest.runAllTimers();
@ -112,14 +112,9 @@ describe('Popconfirm', () => {
it('should trigger onConfirm and onCancel', () => {
const confirm = jest.fn();
const cancel = jest.fn();
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const popconfirm = mount(
<Popconfirm
title="code"
onConfirm={confirm}
onCancel={cancel}
onVisibleChange={onVisibleChange}
>
<Popconfirm title="code" onConfirm={confirm} onCancel={cancel} onOpenChange={onOpenChange}>
<span>show me your code</span>
</Popconfirm>,
);
@ -127,11 +122,11 @@ describe('Popconfirm', () => {
triggerNode.simulate('click');
popconfirm.find('.ant-btn-primary').simulate('click');
expect(confirm).toHaveBeenCalled();
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
triggerNode.simulate('click');
popconfirm.find('.ant-btn').at(0).simulate('click');
expect(cancel).toHaveBeenCalled();
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
});
it('should support onConfirm to return Promise', async () => {
@ -139,20 +134,20 @@ describe('Popconfirm', () => {
new Promise(res => {
setTimeout(res, 300);
});
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const popconfirm = mount(
<Popconfirm title="code" onConfirm={confirm} onVisibleChange={onVisibleChange}>
<Popconfirm title="code" onConfirm={confirm} onOpenChange={onOpenChange}>
<span>show me your code</span>
</Popconfirm>,
);
const triggerNode = popconfirm.find('span').at(0);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenCalledTimes(1);
expect(onOpenChange).toHaveBeenCalledTimes(1);
popconfirm.find('.ant-btn').at(0).simulate('click');
await sleep(400);
expect(onVisibleChange).toHaveBeenCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenCalledWith(false, eventObject);
});
it('should support customize icon', () => {
@ -171,7 +166,7 @@ describe('Popconfirm', () => {
const btnPrefixCls = 'custom-btn';
const wrapper = mount(
<Popconfirm
visible
open
title="x"
prefixCls="custom-popconfirm"
okButtonProps={{ prefixCls: btnPrefixCls }}
@ -185,10 +180,10 @@ describe('Popconfirm', () => {
expect(wrapper.find('.custom-btn').length).toBeGreaterThan(0);
});
it('should support defaultVisible', () => {
it('should support defaultOpen', () => {
const ref = React.createRef();
mount(
<Popconfirm ref={ref} title="code" defaultVisible>
<Popconfirm ref={ref} title="code" defaultOpen>
<span>show me your code</span>
</Popconfirm>,
);
@ -209,22 +204,17 @@ describe('Popconfirm', () => {
});
it('should be closed by pressing ESC', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const wrapper = mount(
<Popconfirm
title="title"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
>
<Popconfirm title="title" mouseEnterDelay={0} mouseLeaveDelay={0} onOpenChange={onOpenChange}>
<span>Delete</span>
</Popconfirm>,
);
const triggerNode = wrapper.find('span').at(0);
triggerNode.simulate('click');
expect(onVisibleChange).toHaveBeenLastCalledWith(true, undefined);
expect(onOpenChange).toHaveBeenLastCalledWith(true, undefined);
triggerNode.simulate('keydown', { key: 'Escape', keyCode: 27 });
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
});
it('should not warn memory leaking if setState in async callback', async () => {

View File

@ -18,31 +18,31 @@ import { Button, Popconfirm } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const showPopconfirm = () => {
setVisible(true);
setOpen(true);
};
const handleOk = () => {
setConfirmLoading(true);
setTimeout(() => {
setVisible(false);
setOpen(false);
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
console.log('Clicked cancel button');
setVisible(false);
setOpen(false);
};
return (
<Popconfirm
title="Title"
visible={visible}
open={open}
onConfirm={handleOk}
okButtonProps={{ loading: confirmLoading }}
onCancel={handleCancel}

View File

@ -18,7 +18,7 @@ import { message, Popconfirm, Switch } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const [condition, setCondition] = useState(true);
const changeCondition = (checked: boolean) => {
@ -26,18 +26,18 @@ const App: React.FC = () => {
};
const confirm = () => {
setVisible(false);
setOpen(false);
message.success('Next step.');
};
const cancel = () => {
setVisible(false);
setOpen(false);
message.error('Click on cancel.');
};
const handleVisibleChange = (newVisible: boolean) => {
if (!newVisible) {
setVisible(newVisible);
const handleOpenChange = (newOpen: boolean) => {
if (!newOpen) {
setOpen(newOpen);
return;
}
// Determining condition before show the popconfirm.
@ -45,7 +45,7 @@ const App: React.FC = () => {
if (condition) {
confirm(); // next step
} else {
setVisible(newVisible);
setOpen(newOpen);
}
};
@ -53,8 +53,8 @@ const App: React.FC = () => {
<div>
<Popconfirm
title="Are you sure delete this task?"
visible={visible}
onVisibleChange={handleVisibleChange}
open={open}
onOpenChange={handleOpenChange}
onConfirm={confirm}
onCancel={cancel}
okText="Yes"

View File

@ -25,11 +25,7 @@ const App: React.FC = () => {
});
return (
<Popconfirm
title="Title"
onConfirm={confirm}
onVisibleChange={() => console.log('visible change')}
>
<Popconfirm title="Title" onConfirm={confirm} onOpenChange={() => console.log('open change')}>
<Button type="primary">Open Popconfirm with Promise</Button>
</Popconfirm>
);

View File

@ -23,56 +23,65 @@ export interface PopconfirmProps extends AbstractTooltipProps {
cancelButtonProps?: ButtonProps;
showCancel?: boolean;
icon?: React.ReactNode;
/**
* @deprecated `onVisibleChange` is deprecated which will be removed in next major version. Please
* use `onOpenChange` instead.
*/
onVisibleChange?: (
visible: boolean,
e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLDivElement>,
) => void;
onOpenChange?: (
open: boolean,
e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLDivElement>,
) => void;
}
export interface PopconfirmState {
visible?: boolean;
open?: boolean;
}
const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const [visible, setVisible] = useMergedState(false, {
value: props.visible,
defaultValue: props.defaultVisible,
const [open, setOpen] = useMergedState(false, {
value: props.open !== undefined ? props.open : props.visible,
defaultValue: props.defaultOpen !== undefined ? props.defaultOpen : props.defaultVisible,
});
// const isDestroyed = useDestroyed();
const settingVisible = (
const settingOpen = (
value: boolean,
e?: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLDivElement>,
) => {
setVisible(value, true);
setOpen(value, true);
props.onVisibleChange?.(value, e);
props.onOpenChange?.(value, e);
};
const close = (e: React.MouseEvent<HTMLButtonElement>) => {
settingVisible(false, e);
settingOpen(false, e);
};
const onConfirm = (e: React.MouseEvent<HTMLButtonElement>) => props.onConfirm?.call(this, e);
const onCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
settingVisible(false, e);
settingOpen(false, e);
props.onCancel?.call(this, e);
};
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.keyCode === KeyCode.ESC && visible) {
settingVisible(false, e);
if (e.keyCode === KeyCode.ESC && open) {
settingOpen(false, e);
}
};
const onVisibleChange = (value: boolean) => {
const onOpenChange = (value: boolean) => {
const { disabled } = props;
if (disabled) {
return;
}
settingVisible(value);
settingOpen(value);
};
const {
@ -91,8 +100,8 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
{...restProps}
prefixCls={prefixCls}
placement={placement}
onVisibleChange={onVisibleChange}
visible={visible}
onOpenChange={onOpenChange}
open={open}
_overlay={
<Overlay
{...props}

View File

@ -90,7 +90,7 @@ describe('Popover', () => {
it(`should be rendered correctly in RTL direction`, () => {
const wrapper = render(
<ConfigProvider direction="rtl">
<Popover title="RTL" visible>
<Popover title="RTL" open>
<span>show me your Rtl demo</span>
</Popover>
</ConfigProvider>,

View File

@ -7,25 +7,25 @@ title:
## zh-CN
使用 `visible` 属性控制浮层显示。
使用 `open` 属性控制浮层显示。
## en-US
Use `visible` prop to control the display of the card.
Use `open` prop to control the display of the card.
```tsx
import { Button, Popover } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [open, setOpen] = useState(false);
const hide = () => {
setVisible(false);
setOpen(false);
};
const handleVisibleChange = (newVisible: boolean) => {
setVisible(newVisible);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
return (
@ -33,8 +33,8 @@ const App: React.FC = () => {
content={<a onClick={hide}>Close</a>}
title="Title"
trigger="click"
visible={visible}
onVisibleChange={handleVisibleChange}
open={open}
onOpenChange={handleOpenChange}
>
<Button type="primary">Click me</Button>
</Popover>

View File

@ -26,14 +26,14 @@ const App: React.FC = () => {
setHovered(false);
};
const handleHoverChange = (visible: boolean) => {
setHovered(visible);
const handleHoverChange = (open: boolean) => {
setHovered(open);
setClicked(false);
};
const handleClickChange = (visible: boolean) => {
const handleClickChange = (open: boolean) => {
setHovered(false);
setClicked(visible);
setClicked(open);
};
const hoverContent = <div>This is hover content.</div>;
@ -44,8 +44,8 @@ const App: React.FC = () => {
content={hoverContent}
title="Hover title"
trigger="hover"
visible={hovered}
onVisibleChange={handleHoverChange}
open={hovered}
onOpenChange={handleHoverChange}
>
<Popover
content={
@ -56,8 +56,8 @@ const App: React.FC = () => {
}
title="Click title"
trigger="click"
visible={clicked}
onVisibleChange={handleClickChange}
open={clicked}
onOpenChange={handleClickChange}
>
<Button>Hover and click / 悬停并单击</Button>
</Popover>

View File

@ -6,7 +6,7 @@ import type { TooltipProps } from '../tooltip';
import Tooltip from '../tooltip';
const SliderTooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
const { visible } = props;
const { open } = props;
const innerRef = useRef<any>(null);
const rafRef = useRef<number | null>(null);
@ -24,14 +24,14 @@ const SliderTooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
}
React.useEffect(() => {
if (visible) {
if (open) {
keepAlign();
} else {
cancelKeepAlign();
}
return cancelKeepAlign;
}, [visible, props.title]);
}, [open, props.title]);
return <Tooltip ref={composeRef(innerRef, ref)} {...props} />;
});

View File

@ -161,7 +161,7 @@ describe('Slider', () => {
render(
<SliderTooltip
title="30"
visible
open
ref={node => {
ref = node;
}}

View File

@ -196,7 +196,7 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
<SliderTooltip
prefixCls={tooltipPrefixCls}
title={tipFormatter ? tipFormatter(info.value) : ''}
visible={open}
open={open}
placement={getTooltipPlacement(tooltipPlacement, vertical)}
transitionName={`${rootPrefixCls}-zoom-down`}
key={index}

View File

@ -895,7 +895,7 @@ describe('Table.filter', () => {
it('renders custom filter icon with right Tooltip title', () => {
const filterIcon = () => (
<Tooltip title="title" visible>
<Tooltip title="title" open>
Tooltip
</Tooltip>
);

View File

@ -21,7 +21,8 @@ describe('Tooltip', () => {
});
});
it('check `onVisibleChange` arguments', () => {
it('check `onOpenChange` arguments', () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const ref = React.createRef();
@ -30,6 +31,7 @@ describe('Tooltip', () => {
title=""
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
@ -40,11 +42,13 @@ describe('Tooltip', () => {
// `title` is empty.
const divElement = container.querySelector('#hello');
fireEvent.mouseEnter(divElement);
expect(onOpenChange).not.toHaveBeenCalled();
expect(onVisibleChange).not.toHaveBeenCalled();
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
fireEvent.mouseLeave(divElement);
expect(onOpenChange).not.toHaveBeenCalled();
expect(onVisibleChange).not.toHaveBeenCalled();
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
@ -55,6 +59,7 @@ describe('Tooltip', () => {
title="Have a nice day!"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
@ -62,42 +67,49 @@ describe('Tooltip', () => {
</Tooltip>,
);
fireEvent.mouseEnter(divElement);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(divElement);
expect(onOpenChange).toHaveBeenLastCalledWith(false);
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
// add `visible` props.
// add `open` props.
rerender(
<Tooltip
title="Have a nice day!"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
visible={false}
open={false}
>
<div id="hello">Hello world!</div>
</Tooltip>,
);
fireEvent.mouseEnter(divElement);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
const lastCount = onVisibleChange.mock.calls.length;
const lastCount = onOpenChange.mock.calls.length;
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
// always trigger onVisibleChange
// always trigger onOpenChange
fireEvent.mouseLeave(divElement);
expect(onVisibleChange.mock.calls.length).toBe(lastCount); // no change with lastCount
expect(onOpenChange.mock.calls.length).toBe(lastCount); // no change with lastCount
expect(onVisibleChange.mock.calls.length).toBe(lastCount);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
it('should hide when mouse leave native disabled button', () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const ref = React.createRef();
@ -106,6 +118,7 @@ describe('Tooltip', () => {
title="xxxxx"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
@ -119,11 +132,13 @@ describe('Tooltip', () => {
const button = container.getElementsByTagName('span')[0];
fireEvent.mouseEnter(button);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(button);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
@ -132,6 +147,7 @@ describe('Tooltip', () => {
describe('should hide when mouse leave antd disabled component', () => {
function testComponent(name, Component) {
it(name, () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const ref = React.createRef();
const { container } = render(
@ -139,6 +155,7 @@ describe('Tooltip', () => {
title="xxxxx"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
@ -150,11 +167,13 @@ describe('Tooltip', () => {
const button = container.getElementsByTagName('span')[0];
fireEvent.mouseEnter(button);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(button);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
@ -235,11 +254,17 @@ describe('Tooltip', () => {
});
it('should works for date picker', async () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const ref = React.createRef();
const { container } = render(
<Tooltip title="date picker" onVisibleChange={onVisibleChange} ref={ref}>
<Tooltip
title="date picker"
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
<DatePicker />
</Tooltip>,
);
@ -249,22 +274,30 @@ describe('Tooltip', () => {
fireEvent.mouseEnter(picker);
await sleep(100);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(picker);
await sleep(100);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
it('should works for input group', async () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const ref = React.createRef();
const { container } = render(
<Tooltip title="hello" onVisibleChange={onVisibleChange} ref={ref}>
<Tooltip
title="hello"
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
<Group>
<Input style={{ width: '50%' }} />
<Input style={{ width: '50%' }} />
@ -276,12 +309,14 @@ describe('Tooltip', () => {
const inputGroup = container.getElementsByClassName('ant-input-group')[0];
fireEvent.mouseEnter(inputGroup);
await sleep(100);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(inputGroup);
await sleep(100);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
@ -290,7 +325,7 @@ describe('Tooltip', () => {
// https://github.com/ant-design/ant-design/issues/20891
it('should display zero', () => {
const { container } = render(
<Tooltip title={0} visible>
<Tooltip title={0} open>
<div />
</Tooltip>,
);
@ -300,7 +335,7 @@ describe('Tooltip', () => {
it('autoAdjustOverflow should be object or undefined', () => {
expect(() => {
render(
<Tooltip title={0} visible autoAdjustOverflow={{ adjustX: 0, adjustY: 0 }}>
<Tooltip title={0} open autoAdjustOverflow={{ adjustX: 0, adjustY: 0 }}>
<div />
</Tooltip>,
);
@ -308,7 +343,7 @@ describe('Tooltip', () => {
expect(() => {
render(
<Tooltip title={0} visible autoAdjustOverflow={undefined}>
<Tooltip title={0} open autoAdjustOverflow={undefined}>
<div />
</Tooltip>,
);
@ -386,12 +421,14 @@ describe('Tooltip', () => {
});
it('should work with loading switch', () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const { container } = render(
<Tooltip
title="loading tips"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
>
<Switch loading defaultChecked />
@ -400,17 +437,20 @@ describe('Tooltip', () => {
const wrapperEl = container.querySelectorAll('.ant-tooltip-disabled-compatible-wrapper');
expect(wrapperEl).toHaveLength(1);
fireEvent.mouseEnter(container.getElementsByTagName('span')[0]);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
});
it('should work with disabled Radio', () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const { container } = render(
<Tooltip
title="loading tips"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
>
<Radio disabled />
@ -419,11 +459,13 @@ describe('Tooltip', () => {
const wrapperEl = container.querySelectorAll('.ant-tooltip-disabled-compatible-wrapper');
expect(wrapperEl).toHaveLength(1);
fireEvent.mouseEnter(container.getElementsByTagName('span')[0]);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
});
it('should work with Fragment children', () => {
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const ref = React.createRef();
@ -432,6 +474,7 @@ describe('Tooltip', () => {
title="Have a nice day!"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
ref={ref}
>
@ -444,13 +487,54 @@ describe('Tooltip', () => {
const divElement = container.querySelector('.hello');
fireEvent.mouseEnter(divElement);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(divElement);
expect(onOpenChange).toHaveBeenLastCalledWith(false);
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
it('deprecated warning', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { rerender } = render(
<Tooltip visible>
<a />
</Tooltip>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `visible` is deprecated which will be removed in next major version, please use `open` instead.',
);
rerender(
<Tooltip defaultVisible>
<a />
</Tooltip>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `defaultVisible` is deprecated which will be removed in next major version, please use `defaultOpen` instead.',
);
rerender(
<Tooltip onVisibleChange={() => {}}>
<a />
</Tooltip>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `onVisibleChange` is deprecated which will be removed in next major version, please use `onOpenChange` instead.',
);
rerender(
<Tooltip afterVisibleChange={() => {}}>
<a />
</Tooltip>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `afterVisibleChange` is deprecated which will be removed in next major version, please use `afterOpenChange` instead.',
);
errSpy.mockRestore();
});
});

View File

@ -28,7 +28,7 @@ The following APIs are shared by Tooltip, Popconfirm, Popover.
| arrowPointAtCenter | Whether the arrow is pointed at the center of target | boolean | false | |
| autoAdjustOverflow | Whether to adjust popup placement automatically when popup is off screen | boolean | true | |
| color | The background color | string | - | 4.3.0 |
| defaultVisible | Whether the floating tooltip card is visible by default | boolean | false | |
| defaultOpen | Whether the floating tooltip card is open by default | boolean | false | 4.23.0 |
| destroyTooltipOnHide | Whether destroy tooltip when hidden, parent container of tooltip will be destroyed when `keepParent` is false | boolean \| { keepParent?: boolean } | false | |
| getPopupContainer | The DOM container of the tip, the default behavior is to create a `div` element in `body` | function(triggerNode) | () => document.body | |
| mouseEnterDelay | Delay in seconds, before tooltip is shown on mouse enter | number | 0.1 | |
@ -38,9 +38,9 @@ The following APIs are shared by Tooltip, Popconfirm, Popover.
| overlayInnerStyle | Style of the tooltip inner content | object | - | |
| placement | The position of the tooltip relative to the target, which can be one of `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | `top` | |
| trigger | Tooltip trigger mode. Could be multiple by passing an array | `hover` \| `focus` \| `click` \| `contextMenu` \| Array&lt;string> | `hover` | |
| visible | Whether the floating tooltip card is visible or not | boolean | false | |
| open | Whether the floating tooltip card is open or not | boolean | false | 4.23.0 |
| zIndex | Config `z-index` of Tooltip | number | - | |
| onVisibleChange | Callback executed when visibility of the tooltip card is changed | (visible) => void | - | |
| onOpenChange | Callback executed when visibility of the tooltip card is changed | (open) => void | - | 4.23.0 |
## Note

View File

@ -11,6 +11,7 @@ import { getTransitionName } from '../_util/motion';
import getPlacements, { AdjustOverflow, PlacementsConfig } from '../_util/placements';
import { cloneElement, isValidElement, isFragment } from '../_util/reactNode';
import type { LiteralUnion } from '../_util/type';
import warning from '../_util/warning';
export { AdjustOverflow, PlacementsConfig };
@ -39,8 +40,41 @@ export interface TooltipAlignConfig {
useCssBottom?: boolean;
useCssTransform?: boolean;
}
// remove this after RcTooltip switch visible to open.
interface LegacyTooltipProps
extends Partial<
Omit<
RcTooltipProps,
'children' | 'visible' | 'defaultVisible' | 'onVisibleChange' | 'afterVisibleChange'
>
> {
/**
* @deprecated `visible` is deprecated which will be removed in next major version. Please use
* `open` instead.
*/
visible?: RcTooltipProps['visible'];
open?: RcTooltipProps['visible'];
/**
* @deprecated `defaultVisible` is deprecated which will be removed in next major version. Please
* use `defaultOpen` instead.
*/
defaultVisible?: RcTooltipProps['defaultVisible'];
defaultOpen?: RcTooltipProps['defaultVisible'];
/**
* @deprecated `onVisibleChange` is deprecated which will be removed in next major version. Please
* use `onOpenChange` instead.
*/
onVisibleChange?: RcTooltipProps['onVisibleChange'];
onOpenChange?: RcTooltipProps['onVisibleChange'];
/**
* @deprecated `afterVisibleChange` is deprecated which will be removed in next major version.
* Please use `afterOpenChange` instead.
*/
afterVisibleChange?: RcTooltipProps['afterVisibleChange'];
afterOpenChange?: RcTooltipProps['afterVisibleChange'];
}
export interface AbstractTooltipProps extends Partial<Omit<RcTooltipProps, 'children'>> {
export interface AbstractTooltipProps extends LegacyTooltipProps {
style?: React.CSSProperties;
className?: string;
color?: LiteralUnion<PresetColorType, string>;
@ -135,9 +169,25 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
direction,
} = React.useContext(ConfigContext);
const [visible, setVisible] = useMergedState(false, {
value: props.visible,
defaultValue: props.defaultVisible,
// Warning for deprecated usage
if (process.env.NODE_ENV !== 'production') {
[
['visible', 'open'],
['defaultVisible', 'defaultOpen'],
['onVisibleChange', 'onOpenChange'],
['afterVisibleChange', 'afterOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Tooltip',
`\`${deprecatedName}\` is deprecated which will be removed in next major version, please use \`${newName}\` instead.`,
);
});
}
const [open, setOpen] = useMergedState(false, {
value: props.open !== undefined ? props.open : props.visible,
defaultValue: props.defaultOpen !== undefined ? props.defaultOpen : props.defaultVisible,
});
const isNoTitle = () => {
@ -145,10 +195,11 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
return !title && !overlay && title !== 0; // overlay for old version compatibility
};
const onVisibleChange = (vis: boolean) => {
setVisible(isNoTitle() ? false : vis);
const onOpenChange = (vis: boolean) => {
setOpen(isNoTitle() ? false : vis);
if (!isNoTitle()) {
props.onOpenChange?.(vis);
props.onVisibleChange?.(vis);
}
};
@ -217,10 +268,10 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
const prefixCls = getPrefixCls('tooltip', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
let tempVisible = visible;
let tempOpen = open;
// Hide tooltip when there is no title
if (!('visible' in props) && isNoTitle()) {
tempVisible = false;
if (!('open' in props) && !('visible' in props) && isNoTitle()) {
tempOpen = false;
}
const child = getDisabledCompatibleChildren(
@ -254,8 +305,8 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
ref={ref}
builtinPlacements={getTooltipPlacements()}
overlay={getOverlay()}
visible={tempVisible}
onVisibleChange={onVisibleChange}
visible={tempOpen}
onVisibleChange={onOpenChange}
onPopupAlign={onPopupAlign}
overlayInnerStyle={formattedOverlayInnerStyle}
arrowContent={<span className={`${prefixCls}-arrow-content`} style={arrowContentStyle} />}
@ -264,7 +315,7 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
motionDeadline: 1000,
}}
>
{tempVisible ? cloneElement(child, { className: childCls }) : child}
{tempOpen ? cloneElement(child, { className: childCls }) : child}
</RcTooltip>
);
});

View File

@ -30,7 +30,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Vyyeu8jq2/Tooltp.svg
| arrowPointAtCenter | 箭头是否指向目标元素中心 | boolean | false | |
| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | true | |
| color | 背景颜色 | string | - | 4.3.0 |
| defaultVisible | 默认是否显隐 | boolean | false | |
| defaultOpen | 默认是否显隐 | boolean | false | 4.23.0 |
| destroyTooltipOnHide | 关闭后是否销毁 Tooltip`keepParent``false` 时销毁父容器 | boolean \| { keepParent?: boolean } | false | |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | function(triggerNode) | () => document.body | |
| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 | |
@ -40,9 +40,9 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Vyyeu8jq2/Tooltp.svg
| overlayInnerStyle | 卡片内容区域的样式对象 | object | - | |
| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | `top` | |
| trigger | 触发行为,可选 `hover` \| `focus` \| `click` \| `contextMenu`,可使用数组设置多个触发行为 | string \| string\[] | `hover` | |
| visible | 用于手动控制浮层显隐 | boolean | false | |
| open | 用于手动控制浮层显隐 | boolean | false | 4.23.0 |
| zIndex | 设置 Tooltip 的 `z-index` | number | - | |
| onVisibleChange | 显示隐藏的回调 | (visible) => void | - | |
| onOpenChange | 显示隐藏的回调 | (open) => void | - | 4.23.0 |
## 注意

View File

@ -20,7 +20,7 @@ const EllipsisTooltip = ({
}
return (
<Tooltip visible={isEllipsis ? undefined : false} {...tooltipProps}>
<Tooltip open={isEllipsis ? undefined : false} {...tooltipProps}>
{children}
</Tooltip>
);

View File

@ -36,7 +36,7 @@ class Demo extends React.Component {
state = {
codeExpand: false,
copied: false,
copyTooltipVisible: false,
copyTooltipOpen: false,
};
componentDidMount() {
@ -47,12 +47,12 @@ class Demo extends React.Component {
}
shouldComponentUpdate(nextProps, nextState) {
const { codeExpand, copied, copyTooltipVisible } = this.state;
const { codeExpand, copied, copyTooltipOpen } = this.state;
const { expand, theme, showRiddleButton, react18 } = this.props;
return (
(codeExpand || expand) !== (nextState.codeExpand || nextProps.expand) ||
copied !== nextState.copied ||
copyTooltipVisible !== nextState.copyTooltipVisible ||
copyTooltipOpen !== nextState.copyTooltipOpen ||
nextProps.theme !== theme ||
nextProps.showRiddleButton !== showRiddleButton ||
nextProps.react18 !== react18
@ -90,16 +90,16 @@ class Demo extends React.Component {
});
};
onCopyTooltipVisibleChange = visible => {
if (visible) {
onCopyTooltipOpenChange = open => {
if (open) {
this.setState({
copyTooltipVisible: visible,
copyTooltipOpen: open,
copied: false,
});
return;
}
this.setState({
copyTooltipVisible: visible,
copyTooltipOpen: open,
});
};
@ -139,7 +139,7 @@ class Demo extends React.Component {
showRiddleButton,
react18,
} = props;
const { copied, copyTooltipVisible } = state;
const { copied, copyTooltipOpen } = state;
if (!this.liveDemo) {
this.liveDemo = meta.iframe ? (
<BrowserFrame>
@ -448,16 +448,13 @@ ReactDOM.render(<Demo />, document.getElementById('container'));
</Tooltip>
<CopyToClipboard text={sourceCode} onCopy={() => this.handleCodeCopied(meta.id)}>
<Tooltip
visible={copyTooltipVisible}
onVisibleChange={this.onCopyTooltipVisibleChange}
open={copyTooltipOpen}
onOpenChange={this.onCopyTooltipOpenChange}
title={<FormattedMessage id={`app.demo.${copied ? 'copied' : 'copy'}`} />}
>
{React.createElement(
copied && copyTooltipVisible ? CheckOutlined : SnippetsOutlined,
{
className: 'code-box-code-copy code-box-code-action',
},
)}
{React.createElement(copied && copyTooltipOpen ? CheckOutlined : SnippetsOutlined, {
className: 'code-box-code-copy code-box-code-action',
})}
</Tooltip>
</CopyToClipboard>
<Tooltip

View File

@ -140,7 +140,7 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
<div className="icon-pic-searcher">
<Popover
content={messages[`app.docs.components.icon.pic-searcher.intro`]}
visible={state.popoverVisible}
open={state.popoverVisible}
>
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
</Popover>

View File

@ -302,9 +302,9 @@ const Header: React.FC<HeaderProps & WrappedComponentProps<'intl'>> = props => {
placement="bottomRight"
content={menu}
trigger="click"
visible={menuVisible}
open={menuVisible}
arrowPointAtCenter
onVisibleChange={onMenuVisibleChange}
onOpenChange={onMenuVisibleChange}
>
<MenuOutlined className="nav-phone-icon" onClick={handleShowMenu} />
</Popover>