mirror of
https://github.com/ant-design/ant-design.git
synced 2025-08-06 16:06:28 +08:00
feat: Popover can be closed by ESC when trigger is focus or click (#47928)
* feat:Add keydown event to handle escape * fix * fix:use exact same logic with popconfirm * fix:move same logic from popconfirm to popover * fix * fix * add test * fix * fix * fix * test size-limit * fix * fix popconfirm test * fix * fix --------- Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
parent
0b00db48bd
commit
ca6c932b39
@ -2,11 +2,9 @@ import * as React from 'react';
|
||||
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
|
||||
import classNames from 'classnames';
|
||||
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||||
import KeyCode from 'rc-util/lib/KeyCode';
|
||||
import omit from 'rc-util/lib/omit';
|
||||
|
||||
import type { RenderFunction } from '../_util/getRenderPropValue';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import type { ButtonProps, LegacyButtonType } from '../button/button';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import Popover from '../popover';
|
||||
@ -78,18 +76,15 @@ const Popconfirm = React.forwardRef<TooltipRef, PopconfirmProps>((props, ref) =>
|
||||
props.onCancel?.call(this, e);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.keyCode === KeyCode.ESC && open) {
|
||||
settingOpen(false, e);
|
||||
}
|
||||
};
|
||||
|
||||
const onInternalOpenChange = (value: boolean) => {
|
||||
const onInternalOpenChange = (
|
||||
value: boolean,
|
||||
e?: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => {
|
||||
const { disabled = false } = props;
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
settingOpen(value);
|
||||
settingOpen(value, e);
|
||||
};
|
||||
|
||||
const prefixCls = getPrefixCls('popconfirm', customizePrefixCls);
|
||||
@ -119,14 +114,7 @@ const Popconfirm = React.forwardRef<TooltipRef, PopconfirmProps>((props, ref) =>
|
||||
}
|
||||
data-popover-inject
|
||||
>
|
||||
{cloneElement(children, {
|
||||
onKeyDown: (e: React.KeyboardEvent<any>) => {
|
||||
if (React.isValidElement(children)) {
|
||||
children?.props.onKeyDown?.(e);
|
||||
}
|
||||
onKeyDown(e);
|
||||
},
|
||||
})}
|
||||
{children}
|
||||
</Popover>,
|
||||
);
|
||||
}) as React.ForwardRefExoticComponent<
|
||||
|
@ -11,6 +11,11 @@ const { _InternalPanelDoNotUseOrYouWillBeFired: InternalPanelDoNotUseOrYouWillBe
|
||||
describe('Popover', () => {
|
||||
mountTest(Popover);
|
||||
|
||||
const eventObject = expect.objectContaining({
|
||||
target: expect.anything(),
|
||||
preventDefault: expect.any(Function),
|
||||
});
|
||||
|
||||
it('should show overlay when trigger is clicked', () => {
|
||||
const ref = React.createRef<TooltipRef>();
|
||||
const { container } = render(
|
||||
@ -94,4 +99,20 @@ describe('Popover', () => {
|
||||
render(<InternalPanelDoNotUseOrYouWillBeFired content={null} title={null} trigger="click" />);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should be closed by pressing ESC', () => {
|
||||
const onOpenChange = jest.fn((_, e) => {
|
||||
e?.persist?.();
|
||||
});
|
||||
const wrapper = render(
|
||||
<Popover title="Title" trigger="click" onOpenChange={onOpenChange}>
|
||||
<span>Delete</span>
|
||||
</Popover>,
|
||||
);
|
||||
const triggerNode = wrapper.container.querySelectorAll('span')[0];
|
||||
fireEvent.click(triggerNode);
|
||||
expect(onOpenChange).toHaveBeenLastCalledWith(true, undefined);
|
||||
fireEvent.keyDown(triggerNode, { key: 'Escape', keyCode: 27 });
|
||||
expect(onOpenChange).toHaveBeenLastCalledWith(false, eventObject);
|
||||
});
|
||||
});
|
||||
|
@ -11,9 +11,17 @@ import PurePanel from './PurePanel';
|
||||
// CSSINJS
|
||||
import useStyle from './style';
|
||||
|
||||
import KeyCode from 'rc-util/lib/KeyCode';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||||
|
||||
export interface PopoverProps extends AbstractTooltipProps {
|
||||
title?: React.ReactNode | RenderFunction;
|
||||
content?: React.ReactNode | RenderFunction;
|
||||
onOpenChange?: (
|
||||
open: boolean,
|
||||
e?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
}
|
||||
|
||||
interface OverlayProps {
|
||||
@ -37,8 +45,10 @@ const Popover = React.forwardRef<TooltipRef, PopoverProps>((props, ref) => {
|
||||
overlayClassName,
|
||||
placement = 'top',
|
||||
trigger = 'hover',
|
||||
children,
|
||||
mouseEnterDelay = 0.1,
|
||||
mouseLeaveDelay = 0.1,
|
||||
onOpenChange,
|
||||
overlayStyle = {},
|
||||
...otherProps
|
||||
} = props;
|
||||
@ -49,6 +59,27 @@ const Popover = React.forwardRef<TooltipRef, PopoverProps>((props, ref) => {
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
|
||||
const overlayCls = classNames(overlayClassName, hashId, cssVarCls);
|
||||
const [open, setOpen] = useMergedState(false, {
|
||||
value: props.open ?? props.visible,
|
||||
});
|
||||
|
||||
const settingOpen = (
|
||||
value: boolean,
|
||||
e?: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => {
|
||||
setOpen(value, true);
|
||||
onOpenChange?.(value, e);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.keyCode === KeyCode.ESC) {
|
||||
settingOpen(false, e);
|
||||
}
|
||||
};
|
||||
|
||||
const onInternalOpenChange = (value: boolean) => {
|
||||
settingOpen(value);
|
||||
};
|
||||
|
||||
return wrapCSSVar(
|
||||
<Tooltip
|
||||
@ -61,12 +92,23 @@ const Popover = React.forwardRef<TooltipRef, PopoverProps>((props, ref) => {
|
||||
prefixCls={prefixCls}
|
||||
overlayClassName={overlayCls}
|
||||
ref={ref}
|
||||
open={open}
|
||||
onOpenChange={onInternalOpenChange}
|
||||
overlay={
|
||||
title || content ? <Overlay prefixCls={prefixCls} title={title} content={content} /> : null
|
||||
}
|
||||
transitionName={getTransitionName(rootPrefixCls, 'zoom-big', otherProps.transitionName)}
|
||||
data-popover-inject
|
||||
/>,
|
||||
>
|
||||
{cloneElement(children, {
|
||||
onKeyDown: (e: React.KeyboardEvent<any>) => {
|
||||
if (React.isValidElement(children)) {
|
||||
children?.props.onKeyDown?.(e);
|
||||
}
|
||||
onKeyDown(e);
|
||||
},
|
||||
})}
|
||||
</Tooltip>,
|
||||
);
|
||||
}) as React.ForwardRefExoticComponent<
|
||||
React.PropsWithoutRef<PopoverProps> & React.RefAttributes<unknown>
|
||||
|
Loading…
Reference in New Issue
Block a user