diff --git a/.github/workflows/release-helper.yml b/.github/workflows/release-helper.yml index 61723624cc..f2ab3d29a9 100644 --- a/.github/workflows/release-helper.yml +++ b/.github/workflows/release-helper.yml @@ -28,6 +28,7 @@ jobs: branch: 'master' dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }} ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }} dingding-msg: 'CHANGELOG.zh-CN.md' + msg-title: '# Ant Design {{v}} 发布日志' msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ' msg-footer: '💬 前往 [**Ant Design Releases**]({{url}}) 查看更新日志' prettier: true diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 31d1467860..2faa7ac10a 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -15,6 +15,13 @@ timeline: true --- +## 4.20.5 + +`2022-05-15` + +- 🤖 Deprecated Table `rowSelection.onSelectNone` and `rowSelection.onSelectMultiple` in TS type. [#35545](https://github.com/ant-design/ant-design/pull/35545) +- 🐞 Ignore the decimal part in InputNumber when `precision` is negative. [#35520](https://github.com/ant-design/ant-design/pull/35520) [@ty888](https://github.com/ty888) + ## 4.20.4 `2022-05-11` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index d4b6b25157..c487a2b774 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -15,6 +15,13 @@ timeline: true --- +## 4.20.5 + +`2022-05-15 + +- 🤖 在 TypeScript 定义中废弃 Table `rowSelection.onSelectNone` 和 `rowSelection.onSelectMultiple`。[#35545](https://github.com/ant-design/ant-design/pull/35545) +- 🐞 InputNumber 当精度为负数时忽略小数部分。[#35520](https://github.com/ant-design/ant-design/pull/35520) [@ty888](https://github.com/ty888)` + ## 4.20.4 `2022-05-11` diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index 5f4cd15530..788f7856a1 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -16,6 +16,7 @@ import LeftOutlined from '@ant-design/icons/LeftOutlined'; import { useContext } from 'react'; import warning from '../_util/warning'; import { ConfigContext } from '../config-provider'; +import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; import type { SizeType } from '../config-provider/SizeContext'; import SizeContext from '../config-provider/SizeContext'; import DisabledContext from '../config-provider/DisabledContext'; @@ -177,7 +178,7 @@ const Cascader = React.forwardRef((props: CascaderProps, ref: React.Ref -
- - - - - - - - - - - - - - - - -
-
- No Data -
- -`; diff --git a/components/config-provider/__tests__/index.test.js b/components/config-provider/__tests__/index.test.js index aecf780620..6189748304 100644 --- a/components/config-provider/__tests__/index.test.js +++ b/components/config-provider/__tests__/index.test.js @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import { mount } from 'enzyme'; import { SmileOutlined } from '@ant-design/icons'; -import { fireEvent, render } from '@testing-library/react'; import ConfigProvider, { ConfigContext } from '..'; import Button from '../../button'; import Table from '../../table'; import Input from '../../input'; import mountTest from '../../../tests/shared/mountTest'; +import { render, fireEvent } from '../../../tests/utils'; describe('ConfigProvider', () => { mountTest(() => ( @@ -104,16 +104,23 @@ describe('ConfigProvider', () => { }); it('render empty', () => { + let rendered = false; + let cacheRenderEmpty; + const App = () => { const { renderEmpty } = React.useContext(ConfigContext); - return renderEmpty(); + rendered = true; + cacheRenderEmpty = renderEmpty; + return null; }; - const wrapper = mount( + + render( , ); - expect(wrapper.render()).toMatchSnapshot(); + expect(rendered).toBeTruthy(); + expect(cacheRenderEmpty).toBeFalsy(); }); }); diff --git a/components/config-provider/context.tsx b/components/config-provider/context.tsx index a56b62a1be..823b7ff765 100644 --- a/components/config-provider/context.tsx +++ b/components/config-provider/context.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import type { SeedToken } from '../_util/theme'; import type { OverrideToken } from '../_util/theme/interface'; -import type { RenderEmptyHandler } from './renderEmpty'; -import defaultRenderEmpty from './renderEmpty'; +import type { RenderEmptyHandler } from './defaultRenderEmpty'; import type { Locale } from '../locale-provider'; import type { SizeType } from './SizeContext'; import type { RequiredMark } from '../form/Form'; @@ -36,7 +35,7 @@ export interface ConfigConsumerProps { rootPrefixCls?: string; iconPrefixCls: string; getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string; - renderEmpty: RenderEmptyHandler; + renderEmpty?: RenderEmptyHandler; csp?: CSPConfig; autoInsertSpaceInButton?: boolean; input?: { @@ -65,12 +64,10 @@ const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => return suffixCls ? `ant-${suffixCls}` : 'ant'; }; +// zombieJ: 🚨 Do not pass `defaultRenderEmpty` here since it will case circular dependency. export const ConfigContext = React.createContext({ // We provide a default function for Context without provider getPrefixCls: defaultGetPrefixCls, - - renderEmpty: defaultRenderEmpty, - iconPrefixCls: defaultIconPrefixCls, }); diff --git a/components/config-provider/renderEmpty.tsx b/components/config-provider/defaultRenderEmpty.tsx similarity index 71% rename from components/config-provider/renderEmpty.tsx rename to components/config-provider/defaultRenderEmpty.tsx index 86830f9be1..b81a8f7124 100644 --- a/components/config-provider/renderEmpty.tsx +++ b/components/config-provider/defaultRenderEmpty.tsx @@ -3,7 +3,7 @@ import Empty from '../empty'; import type { ConfigConsumerProps } from '.'; import { ConfigConsumer } from '.'; -const renderEmpty = (componentName?: string): React.ReactNode => ( +const defaultRenderEmpty = (componentName?: string): React.ReactNode => ( {({ getPrefixCls }: ConfigConsumerProps) => { const prefix = getPrefixCls('empty'); @@ -19,13 +19,16 @@ const renderEmpty = (componentName?: string): React.ReactNode => ( case 'Transfer': case 'Mentions': return ; + + /* istanbul ignore next */ default: + // Should never hit if we take all the component into consider. return ; } }} ); -export type RenderEmptyHandler = typeof renderEmpty; +export type RenderEmptyHandler = typeof defaultRenderEmpty; -export default renderEmpty; +export default defaultRenderEmpty; diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index b702d0a8eb..9cb99e2c56 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -3,7 +3,7 @@ import IconContext from '@ant-design/icons/lib/components/Context'; import { FormProvider as RcFormProvider } from 'rc-field-form'; import type { ValidateMessages } from 'rc-field-form/lib/interface'; import useMemo from 'rc-util/lib/hooks/useMemo'; -import { RenderEmptyHandler } from './renderEmpty'; +import { RenderEmptyHandler } from './defaultRenderEmpty'; import type { Locale } from '../locale-provider'; import LocaleProvider, { ANT_MARK } from '../locale-provider'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; diff --git a/components/date-picker/generatePicker/generateRangePicker.tsx b/components/date-picker/generatePicker/generateRangePicker.tsx index 10c0044a68..85cb861aed 100644 --- a/components/date-picker/generatePicker/generateRangePicker.tsx +++ b/components/date-picker/generatePicker/generateRangePicker.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { forwardRef, useContext } from 'react'; +import { forwardRef, useContext, useImperativeHandle } from 'react'; import classNames from 'classnames'; import CalendarOutlined from '@ant-design/icons/CalendarOutlined'; import ClockCircleOutlined from '@ant-design/icons/ClockCircleOutlined'; @@ -18,131 +18,122 @@ import { Components, getTimeProps } from '.'; import { FormItemInputContext } from '../../form/context'; import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils'; import useStyle from '../style'; -import type { PickerComponentClass } from './interface'; +import type { PickerComponentClass, CommonPickerMethods } from './interface'; export default function generateRangePicker( generateConfig: GenerateConfig, ): PickerComponentClass> { type InternalRangePickerProps = RangePickerProps & {}; - const RangePicker = forwardRef>( - (props, ref) => { - const { - prefixCls: customizePrefixCls, - getPopupContainer: customGetPopupContainer, - className, - placement, - size: customizeSize, - disabled: customDisabled, - bordered = true, - placeholder, - status: customStatus, - onBlur, - onFocus, - dropdownClassName, - ...restProps - } = props; + const RangePicker = forwardRef< + InternalRangePickerProps | CommonPickerMethods, + RangePickerProps + >((props, ref) => { + const { + prefixCls: customizePrefixCls, + getPopupContainer: customGetPopupContainer, + className, + placement, + size: customizeSize, + disabled: customDisabled, + bordered = true, + placeholder, + status: customStatus, + dropdownClassName, + ...restProps + } = props; - const pickerRef = (ref as any) || React.createRef>(); - const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext); - const prefixCls = getPrefixCls('picker', customizePrefixCls); - const { format, showTime, picker } = props as any; - const rootPrefixCls = getPrefixCls(); + const innerRef = React.useRef>(null); + const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext); + const prefixCls = getPrefixCls('picker', customizePrefixCls); + const { format, showTime, picker } = props as any; + const rootPrefixCls = getPrefixCls(); - const [wrapSSR, hashId] = useStyle(prefixCls); + const [wrapSSR, hashId] = useStyle(prefixCls); - let additionalOverrideProps: any = {}; - additionalOverrideProps = { - ...additionalOverrideProps, - ...(showTime ? getTimeProps({ format, picker, ...showTime }) : {}), - ...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}), - }; + let additionalOverrideProps: any = {}; + additionalOverrideProps = { + ...additionalOverrideProps, + ...(showTime ? getTimeProps({ format, picker, ...showTime }) : {}), + ...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}), + }; - // ===================== Size ===================== - const size = React.useContext(SizeContext); - const mergedSize = customizeSize || size; + // ===================== Size ===================== + const size = React.useContext(SizeContext); + const mergedSize = customizeSize || size; - // ===================== Disabled ===================== - const disabled = React.useContext(DisabledContext); - const mergedDisabled = customDisabled || disabled; - // ===================== FormItemInput ===================== - const formItemContext = useContext(FormItemInputContext); - const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext; + // ===================== Disabled ===================== + const disabled = React.useContext(DisabledContext); + const mergedDisabled = customDisabled || disabled; - const suffixNode = ( - <> - {picker === 'time' ? : } - {hasFeedback && feedbackIcon} - - ); + // ===================== FormItemInput ===================== + const formItemContext = useContext(FormItemInputContext); + const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext; - const focus = () => { - if (pickerRef.current) { - pickerRef.current.focus(); - } - }; + const suffixNode = ( + <> + {picker === 'time' ? : } + {hasFeedback && feedbackIcon} + + ); - const blur = () => { - if (pickerRef.current) { - pickerRef.current.blur(); - } - }; + useImperativeHandle(ref, () => ({ + focus: () => innerRef.current?.focus(), + blur: () => innerRef.current?.blur(), + })); - return wrapSSR( - - {(contextLocale: PickerLocale) => { - const locale = { ...contextLocale, ...props.locale }; + return wrapSSR( + + {(contextLocale: PickerLocale) => { + const locale = { ...contextLocale, ...props.locale }; - return ( - - separator={ - - - - } - disabled={mergedDisabled} - ref={pickerRef} - onBlur={onBlur || blur} - onFocus={onFocus || focus} - dropdownAlign={transPlacement2DropdownAlign(direction, placement)} - placeholder={getRangePlaceholder(picker, locale, placeholder)} - suffixIcon={suffixNode} - clearIcon={} - prevIcon={} - nextIcon={} - superPrevIcon={} - superNextIcon={} - allowClear - transitionName={`${rootPrefixCls}-slide-up`} - {...restProps} - {...additionalOverrideProps} - className={classNames( - { - [`${prefixCls}-${mergedSize}`]: mergedSize, - [`${prefixCls}-borderless`]: !bordered, - }, - getStatusClassNames( - prefixCls as string, - getMergedStatus(contextStatus, customStatus), - hasFeedback, - ), - hashId, - className, - )} - locale={locale!.lang} - prefixCls={prefixCls} - getPopupContainer={customGetPopupContainer || getPopupContainer} - generateConfig={generateConfig} - components={Components} - direction={direction} - dropdownClassName={classNames(hashId, dropdownClassName)} - /> - ); - }} - , - ); - }, - ); + return ( + + separator={ + + + + } + disabled={mergedDisabled} + ref={innerRef} + dropdownAlign={transPlacement2DropdownAlign(direction, placement)} + placeholder={getRangePlaceholder(picker, locale, placeholder)} + suffixIcon={suffixNode} + clearIcon={} + prevIcon={} + nextIcon={} + superPrevIcon={} + superNextIcon={} + allowClear + transitionName={`${rootPrefixCls}-slide-up`} + {...restProps} + {...additionalOverrideProps} + className={classNames( + { + [`${prefixCls}-${mergedSize}`]: mergedSize, + [`${prefixCls}-borderless`]: !bordered, + }, + getStatusClassNames( + prefixCls as string, + getMergedStatus(contextStatus, customStatus), + hasFeedback, + ), + hashId, + className, + )} + locale={locale!.lang} + prefixCls={prefixCls} + getPopupContainer={customGetPopupContainer || getPopupContainer} + generateConfig={generateConfig} + components={Components} + direction={direction} + dropdownClassName={classNames(hashId, dropdownClassName)} + /> + ); + }} + , + ); + }); return RangePicker as unknown as PickerComponentClass>; } diff --git a/components/date-picker/generatePicker/generateSinglePicker.tsx b/components/date-picker/generatePicker/generateSinglePicker.tsx index 06ea50a710..7917776fab 100644 --- a/components/date-picker/generatePicker/generateSinglePicker.tsx +++ b/components/date-picker/generatePicker/generateSinglePicker.tsx @@ -6,7 +6,7 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled'; import RCPicker from 'rc-picker'; import type { PickerMode } from 'rc-picker/lib/interface'; import type { GenerateConfig } from 'rc-picker/lib/generate/index'; -import { forwardRef, useContext } from 'react'; +import { forwardRef, useContext, useImperativeHandle } from 'react'; import enUS from '../locale/en_US'; import { getPlaceholder, transPlacement2DropdownAlign } from '../util'; import warning from '../../_util/warning'; @@ -20,7 +20,7 @@ import { getTimeProps, Components } from '.'; import { FormItemInputContext } from '../../form/context'; import type { InputStatus } from '../../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils'; -import type { DatePickRef, PickerComponentClass } from './interface'; +import type { DatePickRef, PickerComponentClass, CommonPickerMethods } from './interface'; export default function generatePicker(generateConfig: GenerateConfig) { type DatePickerProps = PickerProps & { @@ -32,136 +32,127 @@ export default function generatePicker(generateConfig: GenerateConfig< picker?: PickerMode, displayName?: string, ) { - const Picker = forwardRef, InnerPickerProps>((props, ref) => { - const { - prefixCls: customizePrefixCls, - getPopupContainer: customizeGetPopupContainer, - className, - size: customizeSize, - bordered = true, - placement, - placeholder, - disabled: customDisabled, - status: customStatus, - onBlur, - onFocus, - dropdownClassName, - ...restProps - } = props; + const Picker = forwardRef | CommonPickerMethods, InnerPickerProps>( + (props, ref) => { + const { + prefixCls: customizePrefixCls, + getPopupContainer: customizeGetPopupContainer, + className, + size: customizeSize, + bordered = true, + placement, + placeholder, + disabled: customDisabled, + status: customStatus, + dropdownClassName, + ...restProps + } = props; - warning( - picker !== 'quarter', - displayName!, - `DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`, - ); + warning( + picker !== 'quarter', + displayName!, + `DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`, + ); - const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext); - const prefixCls = getPrefixCls('picker', customizePrefixCls); - const pickerRef = (ref as any) || React.createRef>(); - const { format, showTime } = props as any; + const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext); + const prefixCls = getPrefixCls('picker', customizePrefixCls); + const innerRef = React.useRef>(null); + const { format, showTime } = props as any; - const [wrapSSR, hashId] = useStyle(prefixCls); + const [wrapSSR, hashId] = useStyle(prefixCls); - const additionalProps = { - showToday: true, - }; + useImperativeHandle(ref, () => ({ + focus: () => innerRef.current?.focus(), + blur: () => innerRef.current?.blur(), + })); - let additionalOverrideProps: any = {}; - if (picker) { - additionalOverrideProps.picker = picker; - } - const mergedPicker = picker || props.picker; + const additionalProps = { + showToday: true, + }; - additionalOverrideProps = { - ...additionalOverrideProps, - ...(showTime ? getTimeProps({ format, picker: mergedPicker, ...showTime }) : {}), - ...(mergedPicker === 'time' - ? getTimeProps({ format, ...props, picker: mergedPicker }) - : {}), - }; - const rootPrefixCls = getPrefixCls(); - - // ===================== Size ===================== - const size = React.useContext(SizeContext); - const mergedSize = customizeSize || size; - - // ===================== Disabled ===================== - const disabled = React.useContext(DisabledContext); - const mergedDisabled = customDisabled || disabled; - - // ===================== FormItemInput ===================== - const formItemContext = useContext(FormItemInputContext); - const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext; - - const suffixNode = ( - <> - {mergedPicker === 'time' ? : } - {hasFeedback && feedbackIcon} - - ); - - const focus = () => { - if (pickerRef.current) { - pickerRef.current.focus(); + let additionalOverrideProps: any = {}; + if (picker) { + additionalOverrideProps.picker = picker; } - }; + const mergedPicker = picker || props.picker; - const blur = () => { - if (pickerRef.current) { - pickerRef.current.blur(); - } - }; + additionalOverrideProps = { + ...additionalOverrideProps, + ...(showTime ? getTimeProps({ format, picker: mergedPicker, ...showTime }) : {}), + ...(mergedPicker === 'time' + ? getTimeProps({ format, ...props, picker: mergedPicker }) + : {}), + }; + const rootPrefixCls = getPrefixCls(); - return wrapSSR( - - {(contextLocale: PickerLocale) => { - const locale = { ...contextLocale, ...props.locale }; + // ===================== Size ===================== + const size = React.useContext(SizeContext); + const mergedSize = customizeSize || size; - return ( - - ref={pickerRef} - placeholder={getPlaceholder(mergedPicker, locale, placeholder)} - suffixIcon={suffixNode} - dropdownAlign={transPlacement2DropdownAlign(direction, placement)} - clearIcon={} - prevIcon={} - nextIcon={} - superPrevIcon={} - superNextIcon={} - allowClear - transitionName={`${rootPrefixCls}-slide-up`} - onBlur={onBlur || blur} - onFocus={onFocus || focus} - {...additionalProps} - {...restProps} - {...additionalOverrideProps} - locale={locale!.lang} - className={classNames( - { - [`${prefixCls}-${mergedSize}`]: mergedSize, - [`${prefixCls}-borderless`]: !bordered, - }, - getStatusClassNames( - prefixCls as string, - getMergedStatus(contextStatus, customStatus), - hasFeedback, - ), - hashId, - className, - )} - prefixCls={prefixCls} - getPopupContainer={customizeGetPopupContainer || getPopupContainer} - generateConfig={generateConfig} - components={Components} - direction={direction} - disabled={mergedDisabled} - dropdownClassName={classNames(hashId, dropdownClassName)} - /> - ); - }} - , - ); - }); + // ===================== Disabled ===================== + const disabled = React.useContext(DisabledContext); + const mergedDisabled = customDisabled || disabled; + + // ===================== FormItemInput ===================== + const formItemContext = useContext(FormItemInputContext); + const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext; + + const suffixNode = ( + <> + {mergedPicker === 'time' ? : } + {hasFeedback && feedbackIcon} + + ); + + return wrapSSR( + + {(contextLocale: PickerLocale) => { + const locale = { ...contextLocale, ...props.locale }; + + return ( + + ref={innerRef} + placeholder={getPlaceholder(mergedPicker, locale, placeholder)} + suffixIcon={suffixNode} + dropdownAlign={transPlacement2DropdownAlign(direction, placement)} + clearIcon={} + prevIcon={} + nextIcon={} + superPrevIcon={} + superNextIcon={} + allowClear + transitionName={`${rootPrefixCls}-slide-up`} + {...additionalProps} + {...restProps} + {...additionalOverrideProps} + locale={locale!.lang} + className={classNames( + { + [`${prefixCls}-${mergedSize}`]: mergedSize, + [`${prefixCls}-borderless`]: !bordered, + }, + getStatusClassNames( + prefixCls as string, + getMergedStatus(contextStatus, customStatus), + hasFeedback, + ), + hashId, + className, + )} + prefixCls={prefixCls} + getPopupContainer={customizeGetPopupContainer || getPopupContainer} + generateConfig={generateConfig} + components={Components} + direction={direction} + disabled={mergedDisabled} + dropdownClassName={classNames(hashId, dropdownClassName)} + /> + ); + }} + , + ); + }, + ); if (displayName) { Picker.displayName = displayName; diff --git a/components/drawer/__tests__/DrawerEvent.test.js b/components/drawer/__tests__/DrawerEvent.test.js index 4e39699478..e7973683a8 100644 --- a/components/drawer/__tests__/DrawerEvent.test.js +++ b/components/drawer/__tests__/DrawerEvent.test.js @@ -50,7 +50,9 @@ describe('Drawer', () => { const { container, rerender } = render(getDrawer({ destroyOnClose: true })); rerender(getDrawer({ destroyOnClose: true, visible: false })); - fireEvent.transitionEnd(container.querySelector('.ant-drawer-wrapper-body')); + const ev = new TransitionEvent('transitionend', { bubbles: true }); + ev.propertyName = 'transform'; + fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev); expect(container.querySelector('.ant-drawer-wrapper-body')).toBeFalsy(); }); @@ -60,8 +62,19 @@ describe('Drawer', () => { expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy(); rerender(getDrawer({ visible: false })); - fireEvent.transitionEnd(container.querySelector('.ant-drawer-wrapper-body')); + const ev = new TransitionEvent('transitionend', { bubbles: true }); + ev.propertyName = 'transform'; + fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev); expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy(); }); + it('test afterVisibleChange', async () => { + const afterVisibleChange = jest.fn(); + const { rerender } = render(getDrawer({ afterVisibleChange, visible: true })); + rerender(getDrawer({ afterVisibleChange, visible: false })); + const ev = new TransitionEvent('transitionend', { bubbles: true }); + ev.propertyName = 'transform'; + fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev); + expect(afterVisibleChange).toBeCalledTimes(1); + }); }); diff --git a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap index 70de5f365c..5b2c71c7d7 100644 --- a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap @@ -20,7 +20,6 @@ exports[`Drawer className is test_drawer 1`] = ` >
( bodyStyle, drawerStyle, className, - visible, + visible: propsVisible, + forceRender, children, zIndex, destroyOnClose, @@ -103,14 +102,31 @@ const Drawer = React.forwardRef( prefixCls: customizePrefixCls, getContainer: customizeGetContainer, extra, + afterVisibleChange, ...rest }, ref, ) => { - const forceUpdate = useForceUpdate(); const [internalPush, setPush] = React.useState(false); const parentDrawer = React.useContext(DrawerContext); - const destroyClose = React.useRef(false); + const destroyCloseRef = React.useRef(false); + + const [load, setLoad] = React.useState(false); + const [visible, setVisible] = React.useState(false); + + React.useEffect(() => { + if (propsVisible) { + setLoad(true); + } else { + setVisible(false); + } + }, [propsVisible]); + + React.useEffect(() => { + if (load && propsVisible) { + setVisible(true); + } + }, [load, propsVisible]); const { getPopupContainer, getPrefixCls, direction } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('drawer', customizePrefixCls); @@ -127,7 +143,7 @@ const Drawer = React.forwardRef( React.useEffect(() => { // fix: delete drawer in child and re-render, no push started. // {show && } - if (visible && parentDrawer) { + if (propsVisible && parentDrawer) { parentDrawer.push(); } @@ -167,18 +183,6 @@ const Drawer = React.forwardRef( React.useImperativeHandle(ref, () => operations, [operations]); - const isDestroyOnClose = destroyOnClose && !visible; - - const onDestroyTransitionEnd = () => { - if (!isDestroyOnClose) { - return; - } - if (!visible) { - destroyClose.current = true; - forceUpdate(); - } - }; - const getOffsetStyle = () => { // https://github.com/ant-design/ant-design/issues/24287 if (!visible && !mask) { @@ -267,28 +271,13 @@ const Drawer = React.forwardRef( // render drawer body dom const renderBody = () => { - if (destroyClose.current && !visible) { + // destroyCloseRef.current =false Load the body only once by default + if (destroyCloseRef.current && !forceRender && !load) { return null; } - destroyClose.current = false; - - const containerStyle: React.CSSProperties = {}; - - if (isDestroyOnClose) { - // Increase the opacity transition, delete children after closing. - containerStyle.opacity = 0; - containerStyle.transition = 'opacity .3s'; - } return ( -
+
{renderHeader()}
{children} @@ -320,6 +309,7 @@ const Drawer = React.forwardRef( keyboard, children, onClose, + forceRender, ...rest, }} {...offsetStyle} @@ -328,6 +318,18 @@ const Drawer = React.forwardRef( style={getRcDrawerStyle()} className={drawerClassName} getContainer={getContainer} + afterVisibleChange={open => { + if (!open) { + if (destroyCloseRef.current === false) { + // set true only once + destroyCloseRef.current = true; + } + if (destroyOnClose) { + setLoad(false); + } + } + afterVisibleChange?.(open); + }} > {renderBody()} diff --git a/components/list/index.tsx b/components/list/index.tsx index 5054e9df1a..8ec266bc6a 100644 --- a/components/list/index.tsx +++ b/components/list/index.tsx @@ -12,6 +12,8 @@ import type { PaginationConfig } from '../pagination'; import Pagination from '../pagination'; import { Row } from '../grid'; import Item from './Item'; +import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; + // CSSINJS import useStyle from './style'; @@ -266,7 +268,7 @@ function List({
    {items}
); } else if (!children && !isLoading) { - childrenContent = renderEmptyFunc(prefixCls, renderEmpty); + childrenContent = renderEmptyFunc(prefixCls, renderEmpty || defaultRenderEmpty); } const paginationPosition = paginationProps.position || 'bottom'; diff --git a/components/mentions/index.tsx b/components/mentions/index.tsx index 1531077a38..f3997fde1a 100644 --- a/components/mentions/index.tsx +++ b/components/mentions/index.tsx @@ -10,6 +10,7 @@ import { FormItemInputContext } from '../form/context'; import useStyle from './style'; import type { InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; +import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; export const { Option } = RcMentions; @@ -96,7 +97,7 @@ const InternalMentions: React.ForwardRefRenderFunction = return notFoundContent; } - return renderEmpty('Select'); + return (renderEmpty || defaultRenderEmpty)('Select'); }; const getOptions = () => { diff --git a/components/select/index.tsx b/components/select/index.tsx index cd255b4be3..cd2de263d0 100755 --- a/components/select/index.tsx +++ b/components/select/index.tsx @@ -18,6 +18,7 @@ import type { InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; import type { SelectCommonPlacement } from '../_util/motion'; import { getTransitionName, getTransitionDirection } from '../_util/motion'; +import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; import useStyle from './style'; @@ -126,7 +127,7 @@ const InternalSelect = ( data={pageData} rowKey={getRowKey} rowClassName={internalRowClassName} - emptyText={(locale && locale.emptyText) || renderEmpty('Table')} + emptyText={(locale && locale.emptyText) || (renderEmpty || defaultRenderEmpty)('Table')} // Internal internalHooks={INTERNAL_HOOKS} internalRefs={internalRefs as any} diff --git a/components/transfer/index.tsx b/components/transfer/index.tsx index b8fc53f545..73ba362d7d 100644 --- a/components/transfer/index.tsx +++ b/components/transfer/index.tsx @@ -14,6 +14,8 @@ import warning from '../_util/warning'; import { FormItemInputContext } from '../form/context'; import type { InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; +import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; + import useStyle from './style'; export { TransferListProps } from './list'; @@ -395,7 +397,7 @@ class Transfer extends React.Com status: customStatus, } = this.props; const prefixCls = getPrefixCls('transfer', customizePrefixCls); - const locale = this.getLocale(transferLocale, renderEmpty); + const locale = this.getLocale(transferLocale, renderEmpty || defaultRenderEmpty); const { sourceSelectedKeys, targetSelectedKeys } = this.state; const mergedStatus = getMergedStatus(contextStatus, customStatus); diff --git a/components/tree-select/index.tsx b/components/tree-select/index.tsx index b0c94ebfb0..f69fe8fc18 100644 --- a/components/tree-select/index.tsx +++ b/components/tree-select/index.tsx @@ -22,6 +22,7 @@ import { getTransitionName, getTransitionDirection } from '../_util/motion'; import { FormItemInputContext } from '../form/context'; import type { InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; +import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; type RawValue = string | number; @@ -142,7 +143,7 @@ const InternalTreeSelect =