feat: switch visible to open for Tooltip & Popover & Popconfirm (#36807)

* feat: switch visible to open for Tooltip

* feat: switch visible to open for Popover

* feat: switch visible to open for Popconfirm

* fix

* chore: resolve conflict

* test: fix test case

Co-authored-by: 二货机器人 <smith3816@gmail.com>
This commit is contained in:
yykoypj 2022-08-24 21:34:17 +08:00 committed by GitHub
parent 41a8003241
commit 43d45330c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 192 additions and 181 deletions

View File

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

View File

@ -252,7 +252,7 @@ const App = () => (
<DatePicker open />
<TimePicker open defaultOpenValue={dayjs()} />
<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 => 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

@ -24,56 +24,56 @@ export interface PopconfirmProps extends AbstractTooltipProps {
cancelButtonProps?: ButtonProps;
showCancel?: boolean;
icon?: React.ReactNode;
onVisibleChange?: (
visible: boolean,
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,
defaultValue: props.defaultOpen,
});
// const isDestroyed = useDestroyed();
const settingVisible = (
const settingOpen = (
value: boolean,
e?: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLDivElement>,
) => {
setVisible(value, true);
props.onVisibleChange?.(value, e);
setOpen(value, true);
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 {
@ -92,8 +92,8 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
<Popover
{...restProps}
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

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

View File

@ -173,7 +173,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

@ -891,7 +891,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,8 +21,8 @@ describe('Tooltip', () => {
});
});
it('check `onVisibleChange` arguments', () => {
const onVisibleChange = jest.fn();
it('check `onOpenChange` arguments', () => {
const onOpenChange = jest.fn();
const ref = React.createRef();
const { container, rerender } = render(
@ -30,7 +30,7 @@ describe('Tooltip', () => {
title=""
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
ref={ref}
>
<div id="hello">Hello world!</div>
@ -40,12 +40,12 @@ describe('Tooltip', () => {
// `title` is empty.
const divElement = container.querySelector('#hello');
fireEvent.mouseEnter(divElement);
expect(onVisibleChange).not.toHaveBeenCalled();
expect(onOpenChange).not.toHaveBeenCalled();
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
fireEvent.mouseLeave(divElement);
expect(onVisibleChange).not.toHaveBeenCalled();
expect(onOpenChange).not.toHaveBeenCalled();
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
@ -55,50 +55,50 @@ describe('Tooltip', () => {
title="Have a nice day!"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
ref={ref}
>
<div id="hello">Hello world!</div>
</Tooltip>,
);
fireEvent.mouseEnter(divElement);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(divElement);
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
expect(onOpenChange).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}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
ref={ref}
visible={false}
open={false}
>
<div id="hello">Hello world!</div>
</Tooltip>,
);
fireEvent.mouseEnter(divElement);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
const lastCount = onVisibleChange.mock.calls.length;
expect(onOpenChange).toHaveBeenLastCalledWith(true);
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(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
it('should hide when mouse leave native disabled button', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const ref = React.createRef();
const { container } = render(
@ -106,7 +106,7 @@ describe('Tooltip', () => {
title="xxxxx"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
ref={ref}
>
<button type="button" disabled>
@ -119,12 +119,12 @@ describe('Tooltip', () => {
const button = container.getElementsByTagName('span')[0];
fireEvent.mouseEnter(button);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(button);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
@ -132,14 +132,14 @@ describe('Tooltip', () => {
describe('should hide when mouse leave antd disabled component', () => {
function testComponent(name, Component) {
it(name, () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const ref = React.createRef();
const { container } = render(
<Tooltip
title="xxxxx"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
ref={ref}
>
<Component disabled />
@ -150,12 +150,12 @@ describe('Tooltip', () => {
const button = container.getElementsByTagName('span')[0];
fireEvent.mouseEnter(button);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(button);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
@ -235,11 +235,11 @@ describe('Tooltip', () => {
});
it('should works for date picker', async () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const ref = React.createRef();
const { container } = render(
<Tooltip title="date picker" onVisibleChange={onVisibleChange} ref={ref}>
<Tooltip title="date picker" onOpenChange={onOpenChange} ref={ref}>
<DatePicker />
</Tooltip>,
);
@ -249,22 +249,22 @@ describe('Tooltip', () => {
fireEvent.mouseEnter(picker);
await sleep(100);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(picker);
await sleep(100);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
it('should works for input group', async () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const ref = React.createRef();
const { container } = render(
<Tooltip title="hello" onVisibleChange={onVisibleChange} ref={ref}>
<Tooltip title="hello" onOpenChange={onOpenChange} ref={ref}>
<Group>
<Input style={{ width: '50%' }} />
<Input style={{ width: '50%' }} />
@ -276,13 +276,13 @@ describe('Tooltip', () => {
const inputGroup = container.getElementsByClassName('ant-input-group')[0];
fireEvent.mouseEnter(inputGroup);
await sleep(100);
expect(onVisibleChange).toHaveBeenCalledWith(true);
expect(onOpenChange).toHaveBeenCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(inputGroup);
await sleep(100);
expect(onVisibleChange).toHaveBeenCalledWith(false);
expect(onOpenChange).toHaveBeenCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});
@ -290,7 +290,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 +300,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 +308,7 @@ describe('Tooltip', () => {
expect(() => {
render(
<Tooltip title={0} visible autoAdjustOverflow={undefined}>
<Tooltip title={0} open autoAdjustOverflow={undefined}>
<div />
</Tooltip>,
);
@ -378,7 +378,7 @@ describe('Tooltip', () => {
it('should pass overlayInnerStyle through to the inner component', () => {
const { container } = render(
<Tooltip overlayInnerStyle={{ color: 'red' }} title="xxxxx" visible>
<Tooltip overlayInnerStyle={{ color: 'red' }} title="xxxxx" open>
<div />
</Tooltip>,
);
@ -386,13 +386,13 @@ describe('Tooltip', () => {
});
it('should work with loading switch', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const { container } = render(
<Tooltip
title="loading tips"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
>
<Switch loading defaultChecked />
</Tooltip>,
@ -400,18 +400,18 @@ describe('Tooltip', () => {
const wrapperEl = container.querySelectorAll('.ant-tooltip-disabled-compatible-wrapper');
expect(wrapperEl).toHaveLength(1);
fireEvent.mouseEnter(container.getElementsByTagName('span')[0]);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
});
it('should work with disabled Radio', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const { container } = render(
<Tooltip
title="loading tips"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
>
<Radio disabled />
</Tooltip>,
@ -419,12 +419,12 @@ describe('Tooltip', () => {
const wrapperEl = container.querySelectorAll('.ant-tooltip-disabled-compatible-wrapper');
expect(wrapperEl).toHaveLength(1);
fireEvent.mouseEnter(container.getElementsByTagName('span')[0]);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
});
it('should work with Fragment children', () => {
const onVisibleChange = jest.fn();
const onOpenChange = jest.fn();
const ref = React.createRef();
const { container } = render(
@ -432,7 +432,7 @@ describe('Tooltip', () => {
title="Have a nice day!"
mouseEnterDelay={0}
mouseLeaveDelay={0}
onVisibleChange={onVisibleChange}
onOpenChange={onOpenChange}
ref={ref}
>
<>
@ -444,12 +444,12 @@ describe('Tooltip', () => {
const divElement = container.querySelector('.hello');
fireEvent.mouseEnter(divElement);
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
expect(onOpenChange).toHaveBeenLastCalledWith(true);
expect(ref.current.props.visible).toBe(true);
expect(container.querySelector('.ant-tooltip-open')).not.toBeNull();
fireEvent.mouseLeave(divElement);
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
expect(onOpenChange).toHaveBeenLastCalledWith(false);
expect(ref.current.props.visible).toBe(false);
expect(container.querySelector('.ant-tooltip-open')).toBeNull();
});

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 | |
| 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 | |
| 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 | - | |
## 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';
import PurePanel from './PurePanel';
import useStyle from './style';
@ -43,7 +44,21 @@ export interface TooltipAlignConfig {
useCssTransform?: boolean;
}
export interface AbstractTooltipProps extends Partial<Omit<RcTooltipProps, 'children'>> {
// remove this after RcTooltip switch visible to open.
interface TempTooltipProps
extends Partial<
Omit<
RcTooltipProps,
'children' | 'visible' | 'defaultVisible' | 'onVisibleChange' | 'afterVisibleChange'
>
> {
open?: RcTooltipProps['visible'];
defaultOpen?: RcTooltipProps['defaultVisible'];
onOpenChange?: RcTooltipProps['onVisibleChange'];
afterOpenChange?: RcTooltipProps['afterVisibleChange'];
}
export interface AbstractTooltipProps extends TempTooltipProps {
style?: React.CSSProperties;
className?: string;
color?: LiteralUnion<PresetColorType, string>;
@ -138,9 +153,22 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
direction,
} = React.useContext(ConfigContext);
const [visible, setVisible] = useMergedState(false, {
value: props.visible,
defaultValue: props.defaultVisible,
[
['visible', 'open'],
['defaultVisible', 'defaultOpen'],
['onVisibleChange', 'onOpenChange'],
['afterVisibleChange', 'afterOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Tooltip',
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
);
});
const [open, setOpen] = useMergedState(false, {
value: props.open,
defaultValue: props.defaultOpen,
});
const isNoTitle = () => {
@ -148,11 +176,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.onVisibleChange?.(vis);
props.onOpenChange?.(vis);
}
};
@ -222,10 +250,10 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
const injectFromPopover = (props as any)['data-popover-inject'];
let tempVisible = visible;
let tempOpen = open;
// Hide tooltip when there is no title
if (!('visible' in props) && isNoTitle()) {
tempVisible = false;
if (!('open' in props) && isNoTitle()) {
tempOpen = false;
}
const child = getDisabledCompatibleChildren(
@ -270,8 +298,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`} />}
@ -280,7 +308,7 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
motionDeadline: 1000,
}}
>
{tempVisible ? cloneElement(child, { className: childCls }) : child}
{tempOpen ? cloneElement(child, { className: childCls }) : child}
</RcTooltip>,
);
}) as React.ForwardRefExoticComponent<

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 | |
| 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 | |
| zIndex | 设置 Tooltip 的 `z-index` | number | - | |
| onVisibleChange | 显示隐藏的回调 | (visible) => void | - | |
| onOpenChange | 显示隐藏的回调 | (open) => void | - | |
## 注意

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>
@ -447,16 +447,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.onCopyTooltipVisibleChange}
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>