Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/portal": "^2.0.0",
"@rc-component/portal": "^2.1.0",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
Expand Down
4 changes: 4 additions & 0 deletions src/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Mask from './Mask';
import PopupContent from './PopupContent';
import useOffsetStyle from '../hooks/useOffsetStyle';
import { useEvent } from '@rc-component/util';
import type { PortalProps } from '@rc-component/portal';

export interface MobileConfig {
mask?: boolean;
Expand All @@ -24,6 +25,7 @@ export interface MobileConfig {
}

export interface PopupProps {
onEsc?: PortalProps['onEsc'];
prefixCls: string;
className?: string;
style?: React.CSSProperties;
Expand Down Expand Up @@ -87,6 +89,7 @@ export interface PopupProps {

const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
const {
onEsc,
popup,
className,
prefixCls,
Expand Down Expand Up @@ -234,6 +237,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
open={forceRender || isNodeVisible}
getContainer={getPopupContainer && (() => getPopupContainer(target))}
autoDestroy={autoDestroy}
onEsc={onEsc}
>
<Mask
prefixCls={prefixCls}
Expand Down
12 changes: 11 additions & 1 deletion src/UniqueProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import Portal from '@rc-component/portal';
import Portal, { type PortalProps } from '@rc-component/portal';
import TriggerContext, {
UniqueContext,
type UniqueContextProps,
Expand All @@ -18,13 +18,15 @@ import { getAlignPopupClassName } from '../util';

export interface UniqueProviderProps {
children: React.ReactNode;
onKeyDown?: (event: KeyboardEvent) => void;
/** Additional handle options data to do the customize info */
postTriggerProps?: (options: UniqueShowOptions) => UniqueShowOptions;
}

const UniqueProvider = ({
children,
postTriggerProps,
onKeyDown,
}: UniqueProviderProps) => {
const [trigger, open, options, onTargetVisibleChanged] = useTargetState();

Expand Down Expand Up @@ -91,6 +93,13 @@ const UniqueProvider = ({
onTargetVisibleChanged(visible);
});

const onEsc: PortalProps['onEsc'] = ({ top, event }) => {
if (top) {
trigger(false);
}
onKeyDown?.(event);
};

// =========================== Align ============================
const [
ready,
Expand Down Expand Up @@ -184,6 +193,7 @@ const UniqueProvider = ({
<Popup
ref={setPopupRef}
portal={Portal}
onEsc={onEsc}
prefixCls={prefixCls}
popup={mergedOptions.popup}
className={clsx(
Expand Down
12 changes: 12 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import useAlign from './hooks/useAlign';
import useDelay from './hooks/useDelay';
import useWatch from './hooks/useWatch';
import useWinClick from './hooks/useWinClick';
import type { PortalProps } from '@rc-component/portal';

import type {
ActionType,
AlignType,
Expand Down Expand Up @@ -51,6 +53,7 @@ export interface TriggerRef {
// New version will not wrap popup with `rc-trigger-popup-content` when multiple children

export interface TriggerProps {
onKeyDown?: (event: KeyboardEvent) => void;
children:
| React.ReactElement<any>
| ((info: { open: boolean }) => React.ReactElement<any>);
Expand Down Expand Up @@ -146,6 +149,7 @@ export function generateTrigger(
const {
prefixCls = 'rc-trigger-popup',
children,
onKeyDown,

// Action
action = 'hover',
Expand Down Expand Up @@ -419,6 +423,13 @@ export function generateTrigger(
}, delay);
};

const onEsc: PortalProps['onEsc'] = ({ top, event }) => {
if (top) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

还需要判断一下是否是 click 触发的,如果是 hover 触发的 esc 关闭是不合理的

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里我理解是不对的,Esc应该始终能够触发Tooltip关闭。这也是无障碍规范里提到过的,然后就是我观察了其它主流的a11y友好的组件库中,Tooltip的行为也都是支持hover后Esc关闭的

triggerOpen(false);
}
onKeyDown?.(event);
};

// ========================== Motion ============================
const [inMotion, setInMotion] = React.useState(false);

Expand Down Expand Up @@ -830,6 +841,7 @@ export function generateTrigger(
forceRender={forceRender}
autoDestroy={mergedAutoDestroy}
getPopupContainer={getPopupContainer}
onEsc={onEsc}
// Arrow
align={alignInfo}
arrow={innerArrow}
Expand Down
77 changes: 77 additions & 0 deletions tests/basic.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import ReactDOM, { createPortal } from 'react-dom';
import Trigger from '../src';
import { awaitFakeTimer, placementAlignMap } from './util';

jest.mock('@rc-component/util/lib/hooks/useId', () => {
const origin = jest.requireActual('react');
return origin.useId;
});

describe('Trigger.Basic', () => {
beforeAll(() => {
spyElementPrototypes(HTMLElement, {
Expand Down Expand Up @@ -1200,4 +1205,76 @@ describe('Trigger.Basic', () => {
await awaitFakeTimer();
expect(isPopupHidden()).toBeTruthy();
});

describe('keyboard', () => {
it('esc should close popup', async () => {
const { container } = render(
<Trigger action="click" popup={<strong>trigger</strong>}>
<div className="target" />
</Trigger>,
);

trigger(container, '.target');
expect(isPopupHidden()).toBeFalsy();

fireEvent.keyDown(window, { key: 'Escape' });
await awaitFakeTimer();
expect(isPopupHidden()).toBeTruthy();
});

it('non-escape key should not close popup', async () => {
const { container } = render(
<Trigger action="click" popup={<strong>trigger</strong>}>
<div className="target" />
</Trigger>,
);

trigger(container, '.target');
expect(isPopupHidden()).toBeFalsy();

fireEvent.keyDown(window, { key: 'Enter' });
expect(isPopupHidden()).toBeFalsy();
});

it('esc should close nested popup from inside out', async () => {
const NestedPopup = () => (
<Trigger
action="click"
popupClassName="inner-popup"
popup={<div>Inner Content</div>}
>
<button type="button" className="inner-target">
Inner Target
</button>
</Trigger>
);

const { container } = render(
<Trigger
action="click"
popupClassName="outer-popup"
popup={
<div className="outer-popup-content">
<NestedPopup />
</div>
}
>
<div className="outer-target" />
</Trigger>,
);

trigger(container, '.outer-target');
expect(isPopupClassHidden('.outer-popup')).toBeFalsy();

fireEvent.click(document.querySelector('.inner-target'));
expect(isPopupClassHidden('.inner-popup')).toBeFalsy();

fireEvent.keyDown(window, { key: 'Escape' });
expect(isPopupClassHidden('.inner-popup')).toBeTruthy();
expect(isPopupClassHidden('.outer-popup')).toBeFalsy();

fireEvent.keyDown(window, { key: 'Escape' });
expect(isPopupClassHidden('.outer-popup')).toBeTruthy();
});
});
});
17 changes: 17 additions & 0 deletions tests/unique.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,21 @@ describe('Trigger.Unique', () => {
// Verify onAlign was called due to target change
expect(mockOnAlign).toHaveBeenCalled();
});

it('esc should close unique popup', async () => {
const { container,baseElement } = render(
<UniqueProvider>
<Trigger action={['click']} popup={<div>Popup</div>} unique>
<div className="target" />
</Trigger>
</UniqueProvider>,
);
fireEvent.click(container.querySelector('.target'));
await awaitFakeTimer();
expect(baseElement.querySelector('.rc-trigger-popup-hidden')).toBeFalsy();

fireEvent.keyDown(window, { key: 'Escape' });
await awaitFakeTimer();
expect(baseElement.querySelector('.rc-trigger-popup-hidden')).toBeTruthy();
});
});
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"types": ["@testing-library/jest-dom", "node"],
"paths": {
"@/*": ["src/*"],
"@@/*": [".dumi/tmp/*"],
Expand Down
Loading