diff --git a/components/modal/ActionButton.tsx b/components/_util/ActionButton.tsx similarity index 71% rename from components/modal/ActionButton.tsx rename to components/_util/ActionButton.tsx index 853faef598..7c88bfcfd1 100644 --- a/components/modal/ActionButton.tsx +++ b/components/_util/ActionButton.tsx @@ -5,10 +5,16 @@ import { LegacyButtonType, ButtonProps, convertLegacyProps } from '../button/but export interface ActionButtonProps { type?: LegacyButtonType; actionFn?: (...args: any[]) => any | PromiseLike; - closeModal: Function; + close: Function; autoFocus?: boolean; prefixCls: string; buttonProps?: ButtonProps; + emitEvent?: boolean; + quitOnNullishReturnValue?: boolean; +} + +function isThenable(thing?: PromiseLike): boolean { + return !!(thing && !!thing.then); } const ActionButton: React.FC = props => { @@ -30,16 +36,16 @@ const ActionButton: React.FC = props => { }, []); const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike) => { - const { closeModal } = props; - if (!returnValueOfOnOk || !returnValueOfOnOk.then) { + const { close } = props; + if (!isThenable(returnValueOfOnOk)) { return; } setLoading(true); - returnValueOfOnOk.then( + returnValueOfOnOk!.then( (...args: any[]) => { - // It's unnecessary to set loading=false, for the Modal will be unmounted after close. - // setState({ loading: false }); - closeModal(...args); + setLoading(false); + close(...args); + clickedRef.current = false; }, (e: Error) => { // Emit error when catch promise reject @@ -52,25 +58,32 @@ const ActionButton: React.FC = props => { ); }; - const onClick = () => { - const { actionFn, closeModal } = props; + const onClick = (e: React.MouseEvent) => { + const { actionFn, close } = props; if (clickedRef.current) { return; } clickedRef.current = true; if (!actionFn) { - closeModal(); + close(); return; } let returnValueOfOnOk; - if (actionFn.length) { - returnValueOfOnOk = actionFn(closeModal); + if (props.emitEvent) { + returnValueOfOnOk = actionFn(e); + if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) { + clickedRef.current = false; + close(e); + return; + } + } else if (actionFn.length) { + returnValueOfOnOk = actionFn(close); // https://github.com/ant-design/ant-design/issues/23358 clickedRef.current = false; } else { returnValueOfOnOk = actionFn(); if (!returnValueOfOnOk) { - closeModal(); + close(); return; } } diff --git a/components/_util/motion.tsx b/components/_util/motion.tsx index 680925011b..1c69a9f05a 100644 --- a/components/_util/motion.tsx +++ b/components/_util/motion.tsx @@ -3,7 +3,10 @@ import { MotionEvent } from 'rc-motion/lib/interface'; // ================== Collapse Motion ================== const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 }); -const getRealHeight: MotionEventHandler = node => ({ height: node.scrollHeight, opacity: 1 }); +const getRealHeight: MotionEventHandler = node => { + const { scrollHeight } = node; + return { height: scrollHeight, opacity: 1 }; +}; const getCurrentHeight: MotionEventHandler = node => ({ height: node ? node.offsetHeight : 0 }); const skipOpacityTransition: MotionEndEventHandler = (_, event: MotionEvent) => event?.deadline === true || (event as TransitionEvent).propertyName === 'height'; diff --git a/components/avatar/__tests__/Avatar.test.js b/components/avatar/__tests__/Avatar.test.js index 962232b37d..6473e5c9b2 100644 --- a/components/avatar/__tests__/Avatar.test.js +++ b/components/avatar/__tests__/Avatar.test.js @@ -193,4 +193,22 @@ describe('Avatar Render', () => { wrapper.detach(); global.document.body.removeChild(div); }); + + it('should exist crossorigin attribute', () => { + const LOAD_SUCCESS_SRC = 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'; + const wrapper = mount( + + crossorigin + , + ); + expect(wrapper.html().includes('crossorigin')).toEqual(true); + expect(wrapper.find('img').prop('crossOrigin')).toEqual('anonymous'); + }); + + it('should not exist crossorigin attribute', () => { + const LOAD_SUCCESS_SRC = 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'; + const wrapper = mount(crossorigin); + expect(wrapper.html().includes('crossorigin')).toEqual(false); + expect(wrapper.find('img').prop('crossOrigin')).toEqual(undefined); + }); }); diff --git a/components/avatar/avatar.tsx b/components/avatar/avatar.tsx index 68241979fe..26028390dd 100644 --- a/components/avatar/avatar.tsx +++ b/components/avatar/avatar.tsx @@ -29,6 +29,7 @@ export interface AvatarProps { className?: string; children?: React.ReactNode; alt?: string; + crossOrigin?: '' | 'anonymous' | 'use-credentials'; /* callback when img load error */ /* return false to prevent Avatar show default fallback behavior, then you can do fallback by your self */ onError?: () => boolean; @@ -95,6 +96,7 @@ const InternalAvatar: React.ForwardRefRenderFunction = (pr alt, draggable, children, + crossOrigin, ...others } = props; @@ -158,7 +160,14 @@ const InternalAvatar: React.ForwardRefRenderFunction = (pr let childrenToRender; if (typeof src === 'string' && isImgExist) { childrenToRender = ( - {alt} + {alt} ); } else if (hasImageElement) { childrenToRender = src; diff --git a/components/avatar/index.en-US.md b/components/avatar/index.en-US.md index f7a67884e1..88e6c2e476 100644 --- a/components/avatar/index.en-US.md +++ b/components/avatar/index.en-US.md @@ -21,6 +21,7 @@ Avatars can be used to represent people or objects. It supports images, `Icon`s, | src | The address of the image for an image avatar or image element | string \| ReactNode | - | ReactNode: 4.8.0 | | srcSet | A list of sources to use for different screen resolutions | string | - | | | draggable | Whether the picture is allowed to be dragged | boolean \| `'true'` \| `'false'` | - | | +| crossOrigin | CORS settings attributes | `'anonymous'` \| `'use-credentials'` \| `''` | - | 4.17.0 | | onError | Handler when img load error, return false to prevent default fallback behavior | () => boolean | - | | > Tip: You can set `icon` or `children` as the fallback for image load error, with the priority of `icon` > `children` diff --git a/components/avatar/index.zh-CN.md b/components/avatar/index.zh-CN.md index f94ffae892..aabd34b311 100644 --- a/components/avatar/index.zh-CN.md +++ b/components/avatar/index.zh-CN.md @@ -26,6 +26,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/aBcnbw68hP/Avatar.svg | src | 图片类头像的资源地址或者图片元素 | string \| ReactNode | - | ReactNode: 4.8.0 | | srcSet | 设置图片类头像响应式资源地址 | string | - | | | draggable | 图片是否允许拖动 | boolean \| `'true'` \| `'false'` | - | | +| crossOrigin | CORS 属性设置 | `'anonymous'` \| `'use-credentials'` \| `''` | - | 4.17.0 | | onError | 图片加载失败的事件,返回 false 会关闭组件默认的 fallback 行为 | () => boolean | - | | > Tip:你可以设置 `icon` 或 `children` 作为图片加载失败的默认 fallback 行为,优先级为 `icon` > `children` diff --git a/components/calendar/locale/bn_BD.tsx b/components/calendar/locale/bn_BD.tsx new file mode 100644 index 0000000000..be32e7c32e --- /dev/null +++ b/components/calendar/locale/bn_BD.tsx @@ -0,0 +1,3 @@ +import bnBD from '../../date-picker/locale/bn_BD'; + +export default bnBD; diff --git a/components/calendar/locale/ml_IN.tsx b/components/calendar/locale/ml_IN.tsx new file mode 100644 index 0000000000..5ccb2a4492 --- /dev/null +++ b/components/calendar/locale/ml_IN.tsx @@ -0,0 +1,3 @@ +import mlIN from '../../date-picker/locale/ml_IN'; + +export default mlIN; diff --git a/components/calendar/locale/ur_PK.tsx b/components/calendar/locale/ur_PK.tsx new file mode 100644 index 0000000000..66b29e2da4 --- /dev/null +++ b/components/calendar/locale/ur_PK.tsx @@ -0,0 +1,3 @@ +import urPK from '../../date-picker/locale/ur_PK'; + +export default urPK; diff --git a/components/cascader/__tests__/__snapshots__/demo.test.js.snap b/components/cascader/__tests__/__snapshots__/demo.test.js.snap index e03a8e0158..14c593e25d 100644 --- a/components/cascader/__tests__/__snapshots__/demo.test.js.snap +++ b/components/cascader/__tests__/__snapshots__/demo.test.js.snap @@ -209,6 +209,7 @@ exports[`renders ./components/cascader/demo/default-value.md correctly 1`] = ` > Zhejiang / Hangzhou / West Lake diff --git a/components/cascader/__tests__/__snapshots__/index.test.js.snap b/components/cascader/__tests__/__snapshots__/index.test.js.snap index c39cdae51c..91b22f9010 100644 --- a/components/cascader/__tests__/__snapshots__/index.test.js.snap +++ b/components/cascader/__tests__/__snapshots__/index.test.js.snap @@ -1117,6 +1117,7 @@ exports[`Cascader support controlled mode 1`] = ` > Zhejiang / Hangzhou / West Lake diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index 517328f8bb..d5be7cc3e7 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -316,7 +316,7 @@ class Cascader extends React.Component { }; getLabel() { - const { options, displayRender = defaultDisplayRender as Function } = this.props; + const { options, displayRender = defaultDisplayRender } = this.props; const names = getFilledFieldNames(this.props); const { value } = this.state; const unwrappedValue = Array.isArray(value[0]) ? value[0] : value; @@ -618,9 +618,15 @@ class Cascader extends React.Component { inputIcon = ; } + const label = this.getLabel(); const input: React.ReactElement = children || ( - {this.getLabel()} + + {label} +
- + + + +
- + + + +
- + + + +
- + + + +
- + + + +
- + + + +