chore: auto merge branches (#38618)

chore: next merge master
This commit is contained in:
github-actions[bot] 2022-11-17 06:39:45 +00:00 committed by GitHub
commit e6d58f30df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 345 additions and 166 deletions

View File

@ -110,7 +110,7 @@ timeline: true
- 💄 修复 RTL 下 Input.Search 边框样式问题。[#37980](https://github.com/ant-design/ant-design/pull/37980) [@foryuki](https://github.com/foryuki)
- 🐞 修复 AutoComplete 会报未使用的废弃属性 `dropdownClassName` 的问题。[#37974](https://github.com/ant-design/ant-design/pull/37974) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 修复 Typography 省略算法在计算一些元素 fontSize 时为空字符串的情况[#37928](https://github.com/ant-design/ant-design/pull/37928) [@zheeeng](https://github.com/zheeeng)
- 🐞 Fix Tabs 添加按钮在某些边界情况下无法展示的问题。[#37937](https://github.com/ant-design/ant-design/pull/37937)
- 🐞 修复 Tabs 添加按钮在某些边界情况下无法展示的问题。[#37937](https://github.com/ant-design/ant-design/pull/37937)
- 🐞 修复 RangePicker 在某些情况下面板会闪烁的问题。[#439](https://github.com/react-component/picker/pull/439)
- 🛠 重构 Spin 为 Function Component。[#37969](https://github.com/ant-design/ant-design/pull/37969) [@li-jia-nan](https://github.com/li-jia-nan)
- 🛠 重构 Statistic.Countdown 为 Function Component.[#37938](https://github.com/ant-design/ant-design/pull/37938) [@li-jia-nan](https://github.com/li-jia-nan)
@ -1463,7 +1463,7 @@ timeline: true
- Dropdown
- 🐞 修复 Dropdown 带图标的菜单项禁用样式丢失的问题。[#29433](https://github.com/ant-design/ant-design/pull/29433)
- 🐞 修复 Dropdown 内 Menu 不支持 `expandIcon` 的问题。[#29338](https://github.com/ant-design/ant-design/pull/29338)
- 🐞 Fix 在本地开发时会报 tree-shaking 警告信息的问题。[#29378](https://github.com/ant-design/ant-design/pull/29378)
- 🐞 修复在本地开发时会报 tree-shaking 警告信息的问题。[#29378](https://github.com/ant-design/ant-design/pull/29378)
- 🇰🇷 修复 TimePicker 本地化。[#29540](https://github.com/ant-design/ant-design/pull/29540)
- TypeScript
- 🤖 修复 Form.Item 的泛型定义问题。[#29397](https://github.com/ant-design/ant-design/pull/29397) [@mumiao](https://github.com/mumiao)
@ -1860,7 +1860,7 @@ timeline: true
- Modal
- 🆕 `modal.update()` 支持函数式更新。[#27163](https://github.com/ant-design/ant-design/pull/27163) [@Mongkii](https://github.com/Mongkii)
- 🆕 Modal method 增加 `bodyStyle` 属性。[#27292](https://github.com/ant-design/ant-design/pull/27292)
- 🐞 Fix Modal missing `modalRender` prop。[#27272](https://github.com/ant-design/ant-design/pull/27272) [@jieny](https://github.com/jieny)
- 🐞 修复 Modal 丢失 `modalRender` 属性 TS 定义。[#27272](https://github.com/ant-design/ant-design/pull/27272) [@jieny](https://github.com/jieny)
- 🐞 `Modal.config` 中设置的 `rootPrefixCls` 可以对 `title``content` 下使用的 antd 组件生效。[#27376](https://github.com/ant-design/ant-design/pull/27376) [@Chersquwn](https://github.com/Chersquwn)
- Input
- 🆕 Input.Textarea 支持 `size` 属性。[#27110](https://github.com/ant-design/ant-design/pull/27110)

View File

@ -2,9 +2,11 @@ import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import type { DropdownProps } from '../dropdown';
import Menu from '../menu';
import { cloneElement } from '../_util/reactNode';
import warning from '../_util/warning';
import type { BreadcrumbItemProps } from './BreadcrumbItem';
import BreadcrumbItem from './BreadcrumbItem';
import BreadcrumbSeparator from './BreadcrumbSeparator';
@ -52,7 +54,7 @@ function defaultItemRender(route: Route, params: any, routes: Route[], paths: st
const getPath = (path: string, params: any) => {
path = (path || '').replace(/^\//, '');
Object.keys(params).forEach(key => {
Object.keys(params).forEach((key) => {
path = path.replace(`:${key}`, params[key]);
});
return path;
@ -92,18 +94,18 @@ const Breadcrumb: BreadcrumbInterface = ({
if (routes && routes.length > 0) {
// generated by route
const paths: string[] = [];
crumbs = routes.map(route => {
crumbs = routes.map((route) => {
const path = getPath(route.path, params);
if (path) {
paths.push(path);
}
// generated overlay by route.children
let overlay;
let overlay: DropdownProps['overlay'];
if (route.children && route.children.length) {
overlay = (
<Menu
items={route.children.map(child => ({
items={route.children.map((child) => ({
key: child.path || child.breadcrumbName,
label: itemRender(child, params, routes, addChildPath(paths, child.path, params)),
}))}
@ -111,8 +113,14 @@ const Breadcrumb: BreadcrumbInterface = ({
);
}
const itemProps: BreadcrumbItemProps = { separator };
if (overlay) {
itemProps.overlay = overlay;
}
return (
<BreadcrumbItem overlay={overlay} separator={separator} key={path || route.breadcrumbName}>
<BreadcrumbItem {...itemProps} key={path || route.breadcrumbName}>
{itemRender(route, params, routes, paths)}
</BreadcrumbItem>
);

View File

@ -1,6 +1,5 @@
import DownOutlined from '@ant-design/icons/DownOutlined';
import * as React from 'react';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { DropdownProps } from '../dropdown/dropdown';
@ -23,7 +22,7 @@ export interface BreadcrumbItemProps {
interface BreadcrumbItemInterface extends React.FC<BreadcrumbItemProps> {
__ANT_BREADCRUMB_ITEM: boolean;
}
const BreadcrumbItem: BreadcrumbItemInterface = props => {
const BreadcrumbItem: BreadcrumbItemInterface = (props) => {
const {
prefixCls: customizePrefixCls,
separator = '/',

View File

@ -181,4 +181,24 @@ describe('Breadcrumb', () => {
expect(container.querySelectorAll('.ant-breadcrumb-link')[1].textContent).toBe('0');
expect(container.firstChild).toMatchSnapshot();
});
it('should console Error when `overlay` in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(
<Breadcrumb>
<Breadcrumb.Item overlay={<div>test</div>} />
</Breadcrumb>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Breadcrumb.Item] `overlay` is deprecated. Please use `menu` instead.',
);
errSpy.mockRestore();
});
it('should not console Error when `overlay` not in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Breadcrumb routes={[{ path: '/', breadcrumbName: 'Test' }]} />);
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
});

View File

@ -131,7 +131,7 @@ describe('DropdownButton', () => {
'ant-btn',
);
});
it('should console Error then `overlay` in props', () => {
it('should console Error when `overlay` in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<DropdownButton overlay={<div>test</div>} />);
expect(errSpy).toHaveBeenCalledWith(
@ -139,7 +139,7 @@ describe('DropdownButton', () => {
);
errSpy.mockRestore();
});
it('should not console Error then `overlay` not in props', () => {
it('should not console Error when `overlay` not in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<DropdownButton />);
expect(errSpy).not.toHaveBeenCalled();

View File

@ -165,7 +165,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
// >>>>> Collect noStyle Field error to the top FormItem
const onSubItemMetaChange = (subMeta: Meta & { destroy: boolean }, uniqueKeys: React.Key[]) => {
// Only `noStyle` sub item will trigger
setSubFieldErrors(prevSubFieldErrors => {
setSubFieldErrors((prevSubFieldErrors) => {
const clone = {
...prevSubFieldErrors,
};
@ -191,7 +191,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
const errorList: string[] = [...meta.errors];
const warningList: string[] = [...meta.warnings];
Object.values(subFieldErrors).forEach(subFieldError => {
Object.values(subFieldErrors).forEach((subFieldError) => {
errorList.push(...(subFieldError.errors || []));
warningList.push(...(subFieldError.warnings || []));
});
@ -262,7 +262,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
? required
: !!(
rules &&
rules.some(rule => {
rules.some((rule) => {
if (rule && typeof rule === 'object' && rule.required && !rule.warningOnly) {
return true;
}
@ -284,13 +284,13 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
warning(
!(shouldUpdate && dependencies),
'Form.Item',
"`shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/#form-deps.",
"`shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/form-deps.",
);
if (Array.isArray(children) && hasName) {
warning(
false,
'Form.Item',
'A `Form.Item` with a `name` prop must have a single child element. For information on how to render more complex form items, see https://u.ant.design/#complex-form-item.',
'A `Form.Item` with a `name` prop must have a single child element. For information on how to render more complex form items, see https://u.ant.design/complex-form-item.',
);
childNode = children;
} else if (isRenderProps && (!(shouldUpdate || dependencies) || hasName)) {
@ -351,7 +351,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
...toArray(mergedValidateTrigger),
]);
triggers.forEach(eventName => {
triggers.forEach((eventName) => {
childProps[eventName] = (...args: any[]) => {
mergedControl[eventName]?.(...args);
children.props[eventName]?.(...args);

View File

@ -212,7 +212,7 @@ describe('Form', () => {
</Form>,
);
expect(errorSpy).toHaveBeenCalledWith(
"Warning: [antd: Form.Item] `shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/#form-deps.",
"Warning: [antd: Form.Item] `shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/form-deps.",
);
});
@ -239,7 +239,7 @@ describe('Form', () => {
</Form>,
);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Form.Item] A `Form.Item` with a `name` prop must have a single child element. For information on how to render more complex form items, see https://u.ant.design/#complex-form-item.',
'Warning: [antd: Form.Item] A `Form.Item` with a `name` prop must have a single child element. For information on how to render more complex form items, see https://u.ant.design/complex-form-item.',
);
});
@ -1494,7 +1494,7 @@ describe('Form', () => {
it('item customize margin', async () => {
const computeSpy = jest
.spyOn(window, 'getComputedStyle')
.mockImplementation(() => ({ marginBottom: 24 } as unknown as CSSStyleDeclaration));
.mockImplementation(() => ({ marginBottom: 24 }) as unknown as CSSStyleDeclaration);
const { container } = render(
<Form>

View File

@ -62,7 +62,7 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
}
};
const onMouseDown: React.MouseEventHandler<HTMLElement> = e => {
const onMouseDown: React.MouseEventHandler<HTMLElement> = (e) => {
if (document.activeElement === inputRef.current?.input) {
e.preventDefault();
}
@ -75,7 +75,7 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
};
const onPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (composedRef.current) {
if (composedRef.current || loading) {
return;
}
onSearch(e);
@ -140,12 +140,12 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
className,
);
const handleOnCompositionStart: React.CompositionEventHandler<HTMLInputElement> = e => {
const handleOnCompositionStart: React.CompositionEventHandler<HTMLInputElement> = (e) => {
composedRef.current = true;
onCompositionStart?.(e);
};
const handleOnCompositionEnd: React.CompositionEventHandler<HTMLInputElement> = e => {
const handleOnCompositionEnd: React.CompositionEventHandler<HTMLInputElement> = (e) => {
composedRef.current = false;
onCompositionEnd?.(e);
};

View File

@ -161,6 +161,13 @@ describe('Input.Search', () => {
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
it('should not trigger onSearch when press enter while loading', () => {
const onSearch = jest.fn();
const { container } = render(<Search loading onSearch={onSearch} />);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled();
});
it('should support addonAfter and suffix for loading', () => {
const { asFragment } = render(<Search loading suffix="suffix" addonAfter="addonAfter" />);
const { asFragment: asFragmentWithEnterButton } = render(

View File

@ -420,7 +420,7 @@ exports[`renders ./components/layout/demo/fixed.tsx extend context correctly 1`]
>
<header
class="ant-layout-header"
style="position:fixed;z-index:1;width:100%"
style="position:sticky;top:0;z-index:1;width:100%"
>
<div
class="logo"

View File

@ -287,7 +287,7 @@ exports[`renders ./components/layout/demo/fixed.tsx correctly 1`] = `
>
<header
class="ant-layout-header"
style="position:fixed;z-index:1;width:100%"
style="position:sticky;top:0;z-index:1;width:100%"
>
<div
class="logo"

View File

@ -5,7 +5,7 @@ const { Header, Content, Footer } = Layout;
const App: React.FC = () => (
<Layout>
<Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}>
<Header style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%' }}>
<div className="logo" />
<Menu
theme="dark"

View File

@ -11,7 +11,9 @@ import warning from '../_util/warning';
import { renderCloseIcon, renderFooter } from './PurePanel';
import useStyle from './style';
let mousePosition: { x: number; y: number } | null;
type MousePosition = { x: number; y: number } | null;
let mousePosition: MousePosition;
// ref: https://github.com/ant-design/ant-design/issues/15795
const getClickPosition = (e: MouseEvent) => {
@ -82,6 +84,7 @@ export interface ModalProps {
modalRender?: (node: React.ReactNode) => React.ReactNode;
focusTriggerAfterClose?: boolean;
children?: React.ReactNode;
mousePosition?: MousePosition;
// Legacy
/** @deprecated Please use `open` instead. */
@ -137,7 +140,7 @@ export interface ModalLocale {
justOkText: string;
}
const Modal: React.FC<ModalProps> = props => {
const Modal: React.FC<ModalProps> = (props) => {
const {
getPopupContainer: getContextPopupContainer,
getPrefixCls,
@ -208,7 +211,7 @@ const Modal: React.FC<ModalProps> = props => {
onCancel: handleCancel,
})}
visible={open ?? visible}
mousePosition={mousePosition}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel}
closeIcon={renderCloseIcon(prefixCls, closeIcon)}
focusTriggerAfterClose={focusTriggerAfterClose}

View File

@ -92,6 +92,25 @@ describe('Modal', () => {
).toBeTruthy();
});
it('custom mouse position', () => {
const Demo = () => {
const containerRef = React.useRef<HTMLDivElement>(null);
return (
<div ref={containerRef}>
<Modal
open
getContainer={() => containerRef.current!}
mousePosition={{ x: 100, y: 100 }}
/>
</div>
);
};
const { container } = render(<Demo />);
expect(
(container.querySelectorAll('.ant-modal')[0] as HTMLDivElement).style.transformOrigin,
).toBe('100px 100px');
});
it('deprecated warning', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

View File

@ -104,6 +104,17 @@ exports[`renders ./components/modal/demo/confirm-router.tsx extend context corre
</button>
`;
exports[`renders ./components/modal/demo/custom-mouse-position.tsx extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal
</span>
</button>
`;
exports[`renders ./components/modal/demo/dark.tsx extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"

View File

@ -104,6 +104,17 @@ exports[`renders ./components/modal/demo/confirm-router.tsx correctly 1`] = `
</button>
`;
exports[`renders ./components/modal/demo/custom-mouse-position.tsx correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal
</span>
</button>
`;
exports[`renders ./components/modal/demo/dark.tsx correctly 1`] = `
<button
class="ant-btn ant-btn-primary"

View File

@ -0,0 +1,7 @@
## zh-CN
通过 `mousePosition` 控制弹框动画原点.
## en-US
pass `mousePosition` to control modal's animation origin position

View File

@ -0,0 +1,39 @@
import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
mousePosition={{ x: 300, y: 300 }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;

View File

@ -29,41 +29,42 @@ When requiring users to interact with the application, but without jumping to a
<code src="./demo/hooks.tsx">Use hooks to get context</code>
<code src="./demo/modal-render.tsx">Custom modal content render</code>
<code src="./demo/width.tsx">To customize the width of modal</code>
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/custom-mouse-position.tsx" debug>控制弹框动画原点</code>
## API
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| afterClose | Specify a function that will be called when modal is closed completely | function | - | |
| bodyStyle | Body style for modal body element. Such as height, padding etc | CSSProperties | | |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
| cancelText | Text of the Cancel button | ReactNode | `Cancel` | |
| centered | Centered Modal | boolean | false | |
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true | |
| closeIcon | Custom close icon | ReactNode | &lt;CloseOutlined /> | |
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | |
| destroyOnClose | Whether to unmount child components on onClose | boolean | false | |
| focusTriggerAfterClose | Whether need to focus trigger element after dialog is closed | boolean | true | 4.9.0 |
| footer | Footer content, set as `footer={null}` when you don't need default buttons | ReactNode | (OK and Cancel buttons) | |
| forceRender | Force render Modal | boolean | false | |
| getContainer | The mounted node for Modal but still display at fullscreen | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not | boolean | true | |
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true | |
| maskStyle | Style for modal's mask element | CSSProperties | | |
| modalRender | Custom modal content render | (node: ReactNode) => ReactNode | - | 4.7.0 |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - | |
| okText | Text of the OK button | ReactNode | `OK` | |
| okType | Button `type` of the OK button | string | `primary` | |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - | |
| title | The modal dialog's title | ReactNode | - | |
| open | Whether the modal dialog is visible or not | boolean | false | |
| width | Width of the modal dialog | string \| number | 520 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | |
| zIndex | The `z-index` of the Modal | number | 1000 | |
| onCancel | Specify a function that will be called when a user clicks mask, close button on top right or Cancel button | function(e) | - | |
| onOk | Specify a function that will be called when a user clicks the OK button | function(e) | - | |
| Property | Description | Type | Default | Version |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ----------------------- | ------- |
| afterClose | Specify a function that will be called when modal is closed completely | function | - | |
| bodyStyle | Body style for modal body element. Such as height, padding etc | CSSProperties | | |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
| cancelText | Text of the Cancel button | ReactNode | `Cancel` | |
| centered | Centered Modal | boolean | false | |
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true | |
| closeIcon | Custom close icon | ReactNode | &lt;CloseOutlined /> | |
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | |
| destroyOnClose | Whether to unmount child components on onClose | boolean | false | |
| focusTriggerAfterClose | Whether need to focus trigger element after dialog is closed | boolean | true | 4.9.0 |
| footer | Footer content, set as `footer={null}` when you don't need default buttons | ReactNode | (OK and Cancel buttons) | |
| forceRender | Force render Modal | boolean | false | |
| getContainer | The mounted node for Modal but still display at fullscreen | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not | boolean | true | |
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true | |
| maskStyle | Style for modal's mask element | CSSProperties | | |
| modalRender | Custom modal content render | (node: ReactNode) => ReactNode | - | 4.7.0 |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - | |
| okText | Text of the OK button | ReactNode | `OK` | |
| okType | Button `type` of the OK button | string | `primary` | |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - | |
| title | The modal dialog's title | ReactNode | - | |
| open | Whether the modal dialog is visible or not | boolean | false | |
| width | Width of the modal dialog | string \| number | 520 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | |
| zIndex | The `z-index` of the Modal | number | 1000 | |
| onCancel | Specify a function that will be called when a user clicks mask, close button on top right or Cancel button | function(e) | - | |
| onOk | Specify a function that will be called when a user clicks the OK button | function(e) | - | |
#### Note
@ -83,34 +84,34 @@ There are five ways to display the information based on the content's nature:
The items listed above are all functions, expecting a settings object as parameter. The properties of the object are follows:
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| afterClose | Specify a function that will be called when modal is closed completely | function | - | 4.9.0 |
| autoFocusButton | Specify which button to autofocus | null \| `ok` \| `cancel` | `ok` | |
| bodyStyle | Body style for modal body element. Such as height, padding etc | CSSProperties | | 4.8.0 |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
| cancelText | Text of the Cancel button with Modal.confirm | string | `Cancel` | |
| centered | Centered Modal | boolean | false | |
| className | The className of container | string | - | |
| closable | Whether a close (x) button is visible on top right of the confirm dialog or not | boolean | false | 4.9.0 |
| closeIcon | Custom close icon | ReactNode | undefined | 4.9.0 |
| content | Content | ReactNode | - | |
| getContainer | Return the mount node for Modal | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| icon | Custom icon | ReactNode | &lt;QuestionCircle /> | |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not. | boolean | true | |
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | false | |
| maskStyle | Style for modal's mask element | object | {} | |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - | |
| okText | Text of the OK button | string | `OK` | |
| okType | Button `type` of the OK button | string | `primary` | |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - | |
| title | Title | ReactNode | - | |
| width | Width of the modal dialog | string \| number | 416 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | 4.18.0 |
| zIndex | The `z-index` of the Modal | number | 1000 | |
| onCancel | Specify a function that will be called when the user clicks the Cancel button. The parameter of this function is a function whose execution should include closing the dialog. If the function does not take any parameter (`!onCancel.length`) then modal dialog will be closed unless returned value is `true` (`!!onCancel()`). You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
| onOk | Specify a function that will be called when the user clicks the OK button. The parameter of this function is a function whose execution should include closing the dialog. If the function does not take any parameter (`!onOk.length`) then modal dialog will be closed unless returned value is `true` (`!!onOk()`). You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
| Property | Description | Type | Default | Version |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------- | ------- |
| afterClose | Specify a function that will be called when modal is closed completely | function | - | 4.9.0 |
| autoFocusButton | Specify which button to autofocus | null \| `ok` \| `cancel` | `ok` | |
| bodyStyle | Body style for modal body element. Such as height, padding etc | CSSProperties | | 4.8.0 |
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
| cancelText | Text of the Cancel button with Modal.confirm | string | `Cancel` | |
| centered | Centered Modal | boolean | false | |
| className | The className of container | string | - | |
| closable | Whether a close (x) button is visible on top right of the confirm dialog or not | boolean | false | 4.9.0 |
| closeIcon | Custom close icon | ReactNode | undefined | 4.9.0 |
| content | Content | ReactNode | - | |
| getContainer | Return the mount node for Modal | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| icon | Custom icon | ReactNode | &lt;QuestionCircle /> | |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not. | boolean | true | |
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | false | |
| maskStyle | Style for modal's mask element | object | {} | |
| okButtonProps | The ok button props | [ButtonProps](/components/button/#API) | - | |
| okText | Text of the OK button | string | `OK` | |
| okType | Button `type` of the OK button | string | `primary` | |
| style | Style of floating layer, typically used at least for adjusting the position | CSSProperties | - | |
| title | Title | ReactNode | - | |
| width | Width of the modal dialog | string \| number | 416 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | 4.18.0 |
| zIndex | The `z-index` of the Modal | number | 1000 | |
| onCancel | Specify a function that will be called when the user clicks the Cancel button. The parameter of this function is a function whose execution should include closing the dialog. If the function does not take any parameter (`!onCancel.length`) then modal dialog will be closed unless returned value is `true` (`!!onCancel()`). You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
| onOk | Specify a function that will be called when the user clicks the OK button. The parameter of this function is a function whose execution should include closing the dialog. If the function does not take any parameter (`!onOk.length`) then modal dialog will be closed unless returned value is `true` (`!!onOk()`). You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - | |
All the `Modal.method`s will return a reference, and then we can update and close the modal dialog by the reference.

View File

@ -32,41 +32,42 @@ demo:
<code src="./demo/hooks.tsx">使用 hooks 获得上下文</code>
<code src="./demo/modal-render.tsx">自定义渲染对话框</code>
<code src="./demo/width.tsx">自定义模态的宽度</code>
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/custom-mouse-position.tsx" debug>control modal's animation origin position</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| afterClose | Modal 完全关闭后的回调 | function | - | |
| bodyStyle | Modal body 样式 | CSSProperties | | |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
| cancelText | 取消按钮文字 | ReactNode | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | |
| closable | 是否显示右上角的关闭按钮 | boolean | true | |
| closeIcon | 自定义关闭图标 | ReactNode | &lt;CloseOutlined /> | |
| confirmLoading | 确定按钮 loading | boolean | false | |
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | |
| focusTriggerAfterClose | 对话框关闭后是否需要聚焦触发元素 | boolean | true | 4.9.0 |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer={null}` | ReactNode | (确定取消按钮) | |
| forceRender | 强制渲染 Modal | boolean | false | |
| getContainer | 指定 Modal 挂载的节点,但依旧为全局展示,`false` 为挂载在当前位置 | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | boolean | true | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
| maskStyle | 遮罩样式 | CSSProperties | | |
| modalRender | 自定义渲染对话框 | (node: ReactNode) => ReactNode | - | 4.7.0 |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - | |
| okText | 确认按钮文字 | ReactNode | `确定` | |
| okType | 确认按钮类型 | string | `primary` | |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - | |
| title | 标题 | ReactNode | - | |
| open | 对话框是否可见 | boolean | - | |
| width | 宽度 | string \| number | 520 | |
| wrapClassName | 对话框外层容器的类名 | string | - | |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
| onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | - | |
| onOk | 点击确定回调 | function(e) | - | |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ---------------------- | ----------------------------------------------------------------- | ------------------------------------------------------ | -------------------- | ----- |
| afterClose | Modal 完全关闭后的回调 | function | - | |
| bodyStyle | Modal body 样式 | CSSProperties | | |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
| cancelText | 取消按钮文字 | ReactNode | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | |
| closable | 是否显示右上角的关闭按钮 | boolean | true | |
| closeIcon | 自定义关闭图标 | ReactNode | &lt;CloseOutlined /> | |
| confirmLoading | 确定按钮 loading | boolean | false | |
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | |
| focusTriggerAfterClose | 对话框关闭后是否需要聚焦触发元素 | boolean | true | 4.9.0 |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer={null}` | ReactNode | (确定取消按钮) | |
| forceRender | 强制渲染 Modal | boolean | false | |
| getContainer | 指定 Modal 挂载的节点,但依旧为全局展示,`false` 为挂载在当前位置 | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | boolean | true | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
| maskStyle | 遮罩样式 | CSSProperties | | |
| modalRender | 自定义渲染对话框 | (node: ReactNode) => ReactNode | - | 4.7.0 |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - | |
| okText | 确认按钮文字 | ReactNode | `确定` | |
| okType | 确认按钮类型 | string | `primary` | |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - | |
| title | 标题 | ReactNode | - | |
| open | 对话框是否可见 | boolean | - | |
| width | 宽度 | string \| number | 520 | |
| wrapClassName | 对话框外层容器的类名 | string | - | |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
| onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | - | |
| onOk | 点击确定回调 | function(e) | - | |
#### 注意
@ -86,34 +87,34 @@ demo:
以上均为一个函数,参数为 object具体属性如下
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| afterClose | Modal 完全关闭后的回调 | function | - | 4.9.0 |
| autoFocusButton | 指定自动获得焦点的按钮 | null \| `ok` \| `cancel` | `ok` | |
| bodyStyle | Modal body 样式 | CSSProperties | | 4.8.0 |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
| cancelText | 设置 Modal.confirm 取消按钮文字 | string | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | |
| className | 容器类名 | string | - | |
| closable | 是否显示右上角的关闭按钮 | boolean | false | 4.9.0 |
| closeIcon | 自定义关闭图标 | ReactNode | undefined | 4.9.0 |
| content | 内容 | ReactNode | - | |
| getContainer | 指定 Modal 挂载的 HTML 节点, false 为挂载在当前 dom | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| icon | 自定义图标 | ReactNode | &lt;QuestionCircle /> | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | boolean | true | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | false | |
| maskStyle | 遮罩样式 | object | {} | |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - | |
| okText | 确认按钮文字 | string | `确定` | |
| okType | 确认按钮类型 | string | `primary` | |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - | |
| title | 标题 | ReactNode | - | |
| width | 宽度 | string \| number | 416 | |
| wrapClassName | 对话框外层容器的类名 | string | - | 4.18.0 |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------ | --------------------- | ------ |
| afterClose | Modal 完全关闭后的回调 | function | - | 4.9.0 |
| autoFocusButton | 指定自动获得焦点的按钮 | null \| `ok` \| `cancel` | `ok` | |
| bodyStyle | Modal body 样式 | CSSProperties | | 4.8.0 |
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
| cancelText | 设置 Modal.confirm 取消按钮文字 | string | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | |
| className | 容器类名 | string | - | |
| closable | 是否显示右上角的关闭按钮 | boolean | false | 4.9.0 |
| closeIcon | 自定义关闭图标 | ReactNode | undefined | 4.9.0 |
| content | 内容 | ReactNode | - | |
| getContainer | 指定 Modal 挂载的 HTML 节点, false 为挂载在当前 dom | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
| icon | 自定义图标 | ReactNode | &lt;QuestionCircle /> | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | boolean | true | |
| maskClosable | 点击蒙层是否允许关闭 | boolean | false | |
| maskStyle | 遮罩样式 | object | {} | |
| okButtonProps | ok 按钮 props | [ButtonProps](/components/button/#API) | - | |
| okText | 确认按钮文字 | string | `确定` | |
| okType | 确认按钮类型 | string | `primary` | |
| style | 可用于设置浮层的样式,调整浮层位置等 | CSSProperties | - | |
| title | 标题 | ReactNode | - | |
| width | 宽度 | string \| number | 416 | |
| wrapClassName | 对话框外层容器的类名 | string | - | 4.18.0 |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - | |
以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。

View File

@ -41,7 +41,7 @@ describe('Table.pagination', () => {
container
.querySelector('.ant-table-tbody')
?.querySelectorAll('tr')
?.forEach(tr => {
?.forEach((tr) => {
namesList.push(tr.querySelector('td')?.textContent);
});
return namesList;
@ -352,7 +352,7 @@ describe('Table.pagination', () => {
rerender(
createTable({
pagination: { current: 10, pageSize: 10, onChange },
dataSource: longData.filter(item => item.key < 60),
dataSource: longData.filter((item) => item.key < 60),
}),
);
@ -418,7 +418,7 @@ describe('Table.pagination', () => {
* to `pagination`, since they misunderstand that `pagination` can accept a boolean value.
*/
it('Accepts pagination as true', () => {
const { asFragment } = render(createTable({ pagination: true } as TableProps<any>));
const { asFragment } = render(createTable({ pagination: true as any } as TableProps<any>));
expect(asFragment().firstChild).toMatchSnapshot();
});
@ -603,7 +603,7 @@ describe('Table.pagination', () => {
const dataProp = { data: [] } as any;
const Demo: React.FC = () => {
const [p, setP] = React.useState<TablePaginationConfig>({
showTotal: t => `>${t}<`,
showTotal: (t) => `>${t}<`,
total: 200,
current: 1,
pageSize: 10,
@ -613,7 +613,7 @@ describe('Table.pagination', () => {
{...dataProp}
columns={[]}
pagination={p}
onChange={pg => {
onChange={(pg) => {
setP({
...pg,
total: 23,

View File

@ -39,7 +39,7 @@ describe('Table.sorter', () => {
container
?.querySelector('.ant-table-tbody')
?.querySelectorAll('tr')
?.forEach(tr => {
?.forEach((tr) => {
namesList.push(tr.querySelector('td')?.textContent);
});
return namesList;
@ -129,7 +129,7 @@ describe('Table.sorter', () => {
expect(getNameColumn()?.getAttribute('aria-sort')).toEqual('descending');
});
it('sort records with keydown', () => {
it('sort records when press enter', () => {
const { container } = render(createTable());
// ascend
@ -141,6 +141,30 @@ describe('Table.sorter', () => {
expect(renderedNames(container)).toEqual(['Tom', 'Lucy', 'Jack', 'Jerry']);
});
// https://github.com/ant-design/ant-design/issues/38579
it('should not sort records when press enter in filter dropdown', () => {
const { container } = render(
createTable({
columns: [
{
...column,
filters: [{ text: 'J', value: 'J' }],
onFilter: (value: any, record: any) => record.name.includes(value),
filterDropdownOpen: true,
},
],
}),
);
// don't trigger ascend
fireEvent.keyDown(container.querySelector('.ant-table-filter-dropdown')!, { keyCode: 13 });
expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
// don't trigger descend
fireEvent.keyDown(container.querySelector('.ant-table-filter-dropdown')!, { keyCode: 13 });
expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
});
describe('can be controlled by sortOrder', () => {
it('single', () => {
const { container } = render(
@ -434,7 +458,7 @@ describe('Table.sorter', () => {
class TableTest extends React.Component {
state = { pagination: {} };
onChange: TableProps<any>['onChange'] = pagination => {
onChange: TableProps<any>['onChange'] = (pagination) => {
this.setState({ pagination });
};
@ -494,7 +518,7 @@ describe('Table.sorter', () => {
pagination: {},
};
onChange: TableProps<any>['onChange'] = pagination => {
onChange: TableProps<any>['onChange'] = (pagination) => {
this.setState({ pagination });
};
@ -561,7 +585,7 @@ describe('Table.sorter', () => {
pagination: {},
};
onChange: TableProps<any>['onChange'] = pagination => {
onChange: TableProps<any>['onChange'] = (pagination) => {
this.setState({ pagination });
};

View File

@ -1,12 +1,20 @@
import * as React from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
export interface FilterDropdownMenuWrapperProps {
children?: React.ReactNode;
className?: string;
}
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) {
event.stopPropagation();
}
};
const FilterDropdownMenuWrapper = (props: FilterDropdownMenuWrapperProps) => (
<div className={props.className} onClick={e => e.stopPropagation()}>
<div className={props.className} onClick={(e) => e.stopPropagation()} onKeyDown={onKeyDown}>
{props.children}
</div>
);

View File

@ -277,7 +277,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const cssLineClamp = mergedEnableEllipsis && rows > 1 && cssEllipsis;
// >>>>> Expand
const onExpandClick: React.MouseEventHandler<HTMLElement> = e => {
const onExpandClick: React.MouseEventHandler<HTMLElement> = (e) => {
setExpanded(true);
ellipsisConfig.onExpand?.(e);
};
@ -510,6 +510,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
},
className,
)}
prefixCls={customizePrefixCls}
style={{
...style,
WebkitLineClamp: cssLineClamp ? rows : undefined,

View File

@ -0,0 +1,20 @@
import React from 'react';
import { render } from '../../../tests/utils';
import Base from '../Base';
describe('Typography keep prefixCls', () => {
describe('Base', () => {
it('should support className when has prefix', () => {
const { container: wrapper } = render(
<Base component="p" prefixCls="custom-prefixCls" className="custom-class">
test prefixCls
</Base>,
);
expect(
(wrapper.firstChild as HTMLElement)?.className.includes('custom-prefixCls'),
).toBeTruthy();
expect((wrapper.firstChild as HTMLElement)?.className.includes('custom-class')).toBeTruthy();
});
});
});

View File

@ -297,7 +297,7 @@
"sylvanas": "^0.6.1",
"theme-switcher": "^1.0.2",
"ts-node": "^10.8.2",
"typescript": "~4.8.4",
"typescript": "~4.9.3",
"webpack-bundle-analyzer": "^4.1.0",
"xhr-mock": "^2.4.1",
"yaml-front-matter": "^4.0.0"
@ -325,7 +325,7 @@
"mode": "npm"
},
"lint-staged": {
"*.{ts,tsx,js,json,less}": "rome format --write",
"*.{md}": "prettier --ignore-unknown --write"
"*.{ts,tsx,js}": "rome format --write",
"*.{json,less,md}": "prettier --ignore-unknown --write"
}
}