chore: merge feature

This commit is contained in:
zombiej 2022-05-16 17:16:52 +08:00
commit 8613d2ff2e
21 changed files with 320 additions and 370 deletions

View File

@ -28,6 +28,7 @@ jobs:
branch: 'master' branch: 'master'
dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }} ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }} dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }} ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }}
dingding-msg: 'CHANGELOG.zh-CN.md' 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-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'
msg-footer: '💬 前往 [**Ant Design Releases**]({{url}}) 查看更新日志' msg-footer: '💬 前往 [**Ant Design Releases**]({{url}}) 查看更新日志'
prettier: true prettier: true

View File

@ -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 ## 4.20.4
`2022-05-11` `2022-05-11`

View File

@ -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 ## 4.20.4
`2022-05-11` `2022-05-11`

View File

@ -16,6 +16,7 @@ import LeftOutlined from '@ant-design/icons/LeftOutlined';
import { useContext } from 'react'; import { useContext } from 'react';
import warning from '../_util/warning'; import warning from '../_util/warning';
import { ConfigContext } from '../config-provider'; import { ConfigContext } from '../config-provider';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import type { SizeType } from '../config-provider/SizeContext'; import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext'; import SizeContext from '../config-provider/SizeContext';
import DisabledContext from '../config-provider/DisabledContext'; import DisabledContext from '../config-provider/DisabledContext';
@ -177,7 +178,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
); );
// =================== No Found ==================== // =================== No Found ====================
const mergedNotFoundContent = notFoundContent || renderEmpty('Cascader'); const mergedNotFoundContent = notFoundContent || (renderEmpty || defaultRenderEmpty)('Cascader');
// ==================== Prefix ===================== // ==================== Prefix =====================
const rootPrefixCls = getPrefixCls(); const rootPrefixCls = getPrefixCls();

View File

@ -1,76 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfigProvider render empty 1`] = `
<div
class="ant-empty"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-default"
height="152"
viewBox="0 0 184 152"
width="184"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
>
<g
transform="translate(24 31.67)"
>
<ellipse
class="ant-empty-img-default-ellipse"
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
/>
<path
class="ant-empty-img-default-path-1"
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
/>
<path
class="ant-empty-img-default-path-2"
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
transform="translate(13.56)"
/>
<path
class="ant-empty-img-default-path-3"
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
/>
<path
class="ant-empty-img-default-path-4"
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
/>
</g>
<path
class="ant-empty-img-default-path-5"
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
/>
<g
class="ant-empty-img-default-g"
transform="translate(149.65 15.383)"
>
<ellipse
cx="20.654"
cy="3.167"
rx="2.849"
ry="2.815"
/>
<path
d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"
/>
</g>
</g>
</svg>
</div>
<div
class="ant-empty-description"
>
No Data
</div>
</div>
`;

View File

@ -1,12 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { SmileOutlined } from '@ant-design/icons'; import { SmileOutlined } from '@ant-design/icons';
import { fireEvent, render } from '@testing-library/react';
import ConfigProvider, { ConfigContext } from '..'; import ConfigProvider, { ConfigContext } from '..';
import Button from '../../button'; import Button from '../../button';
import Table from '../../table'; import Table from '../../table';
import Input from '../../input'; import Input from '../../input';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import { render, fireEvent } from '../../../tests/utils';
describe('ConfigProvider', () => { describe('ConfigProvider', () => {
mountTest(() => ( mountTest(() => (
@ -104,16 +104,23 @@ describe('ConfigProvider', () => {
}); });
it('render empty', () => { it('render empty', () => {
let rendered = false;
let cacheRenderEmpty;
const App = () => { const App = () => {
const { renderEmpty } = React.useContext(ConfigContext); const { renderEmpty } = React.useContext(ConfigContext);
return renderEmpty(); rendered = true;
cacheRenderEmpty = renderEmpty;
return null;
}; };
const wrapper = mount(
render(
<ConfigProvider> <ConfigProvider>
<App /> <App />
</ConfigProvider>, </ConfigProvider>,
); );
expect(wrapper.render()).toMatchSnapshot(); expect(rendered).toBeTruthy();
expect(cacheRenderEmpty).toBeFalsy();
}); });
}); });

View File

@ -1,8 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import type { SeedToken } from '../_util/theme'; import type { SeedToken } from '../_util/theme';
import type { OverrideToken } from '../_util/theme/interface'; import type { OverrideToken } from '../_util/theme/interface';
import type { RenderEmptyHandler } from './renderEmpty'; import type { RenderEmptyHandler } from './defaultRenderEmpty';
import defaultRenderEmpty from './renderEmpty';
import type { Locale } from '../locale-provider'; import type { Locale } from '../locale-provider';
import type { SizeType } from './SizeContext'; import type { SizeType } from './SizeContext';
import type { RequiredMark } from '../form/Form'; import type { RequiredMark } from '../form/Form';
@ -36,7 +35,7 @@ export interface ConfigConsumerProps {
rootPrefixCls?: string; rootPrefixCls?: string;
iconPrefixCls: string; iconPrefixCls: string;
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string; getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string;
renderEmpty: RenderEmptyHandler; renderEmpty?: RenderEmptyHandler;
csp?: CSPConfig; csp?: CSPConfig;
autoInsertSpaceInButton?: boolean; autoInsertSpaceInButton?: boolean;
input?: { input?: {
@ -65,12 +64,10 @@ const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) =>
return suffixCls ? `ant-${suffixCls}` : 'ant'; return suffixCls ? `ant-${suffixCls}` : 'ant';
}; };
// zombieJ: 🚨 Do not pass `defaultRenderEmpty` here since it will case circular dependency.
export const ConfigContext = React.createContext<ConfigConsumerProps>({ export const ConfigContext = React.createContext<ConfigConsumerProps>({
// We provide a default function for Context without provider // We provide a default function for Context without provider
getPrefixCls: defaultGetPrefixCls, getPrefixCls: defaultGetPrefixCls,
renderEmpty: defaultRenderEmpty,
iconPrefixCls: defaultIconPrefixCls, iconPrefixCls: defaultIconPrefixCls,
}); });

View File

@ -3,7 +3,7 @@ import Empty from '../empty';
import type { ConfigConsumerProps } from '.'; import type { ConfigConsumerProps } from '.';
import { ConfigConsumer } from '.'; import { ConfigConsumer } from '.';
const renderEmpty = (componentName?: string): React.ReactNode => ( const defaultRenderEmpty = (componentName?: string): React.ReactNode => (
<ConfigConsumer> <ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => { {({ getPrefixCls }: ConfigConsumerProps) => {
const prefix = getPrefixCls('empty'); const prefix = getPrefixCls('empty');
@ -19,13 +19,16 @@ const renderEmpty = (componentName?: string): React.ReactNode => (
case 'Transfer': case 'Transfer':
case 'Mentions': case 'Mentions':
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className={`${prefix}-small`} />; return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className={`${prefix}-small`} />;
/* istanbul ignore next */
default: default:
// Should never hit if we take all the component into consider.
return <Empty />; return <Empty />;
} }
}} }}
</ConfigConsumer> </ConfigConsumer>
); );
export type RenderEmptyHandler = typeof renderEmpty; export type RenderEmptyHandler = typeof defaultRenderEmpty;
export default renderEmpty; export default defaultRenderEmpty;

View File

@ -3,7 +3,7 @@ import IconContext from '@ant-design/icons/lib/components/Context';
import { FormProvider as RcFormProvider } from 'rc-field-form'; import { FormProvider as RcFormProvider } from 'rc-field-form';
import type { ValidateMessages } from 'rc-field-form/lib/interface'; import type { ValidateMessages } from 'rc-field-form/lib/interface';
import useMemo from 'rc-util/lib/hooks/useMemo'; import useMemo from 'rc-util/lib/hooks/useMemo';
import { RenderEmptyHandler } from './renderEmpty'; import { RenderEmptyHandler } from './defaultRenderEmpty';
import type { Locale } from '../locale-provider'; import type { Locale } from '../locale-provider';
import LocaleProvider, { ANT_MARK } from '../locale-provider'; import LocaleProvider, { ANT_MARK } from '../locale-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver'; import LocaleReceiver from '../locale-provider/LocaleReceiver';

View File

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { forwardRef, useContext } from 'react'; import { forwardRef, useContext, useImperativeHandle } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import CalendarOutlined from '@ant-design/icons/CalendarOutlined'; import CalendarOutlined from '@ant-design/icons/CalendarOutlined';
import ClockCircleOutlined from '@ant-design/icons/ClockCircleOutlined'; import ClockCircleOutlined from '@ant-design/icons/ClockCircleOutlined';
@ -18,131 +18,122 @@ import { Components, getTimeProps } from '.';
import { FormItemInputContext } from '../../form/context'; import { FormItemInputContext } from '../../form/context';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import useStyle from '../style'; import useStyle from '../style';
import type { PickerComponentClass } from './interface'; import type { PickerComponentClass, CommonPickerMethods } from './interface';
export default function generateRangePicker<DateType>( export default function generateRangePicker<DateType>(
generateConfig: GenerateConfig<DateType>, generateConfig: GenerateConfig<DateType>,
): PickerComponentClass<RangePickerProps<DateType>> { ): PickerComponentClass<RangePickerProps<DateType>> {
type InternalRangePickerProps = RangePickerProps<DateType> & {}; type InternalRangePickerProps = RangePickerProps<DateType> & {};
const RangePicker = forwardRef<InternalRangePickerProps, RangePickerProps<DateType>>( const RangePicker = forwardRef<
(props, ref) => { InternalRangePickerProps | CommonPickerMethods,
const { RangePickerProps<DateType>
prefixCls: customizePrefixCls, >((props, ref) => {
getPopupContainer: customGetPopupContainer, const {
className, prefixCls: customizePrefixCls,
placement, getPopupContainer: customGetPopupContainer,
size: customizeSize, className,
disabled: customDisabled, placement,
bordered = true, size: customizeSize,
placeholder, disabled: customDisabled,
status: customStatus, bordered = true,
onBlur, placeholder,
onFocus, status: customStatus,
dropdownClassName, dropdownClassName,
...restProps ...restProps
} = props; } = props;
const pickerRef = (ref as any) || React.createRef<RCRangePicker<DateType>>(); const innerRef = React.useRef<RCRangePicker<DateType>>(null);
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext); const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls); const prefixCls = getPrefixCls('picker', customizePrefixCls);
const { format, showTime, picker } = props as any; const { format, showTime, picker } = props as any;
const rootPrefixCls = getPrefixCls(); const rootPrefixCls = getPrefixCls();
const [wrapSSR, hashId] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
let additionalOverrideProps: any = {}; let additionalOverrideProps: any = {};
additionalOverrideProps = { additionalOverrideProps = {
...additionalOverrideProps, ...additionalOverrideProps,
...(showTime ? getTimeProps({ format, picker, ...showTime }) : {}), ...(showTime ? getTimeProps({ format, picker, ...showTime }) : {}),
...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}), ...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}),
}; };
// ===================== Size ===================== // ===================== Size =====================
const size = React.useContext(SizeContext); const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size; const mergedSize = customizeSize || size;
// ===================== Disabled ===================== // ===================== Disabled =====================
const disabled = React.useContext(DisabledContext); const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled || disabled; const mergedDisabled = customDisabled || disabled;
// ===================== FormItemInput =====================
const formItemContext = useContext(FormItemInputContext);
const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext;
const suffixNode = ( // ===================== FormItemInput =====================
<> const formItemContext = useContext(FormItemInputContext);
{picker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />} const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext;
{hasFeedback && feedbackIcon}
</>
);
const focus = () => { const suffixNode = (
if (pickerRef.current) { <>
pickerRef.current.focus(); {picker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
} {hasFeedback && feedbackIcon}
}; </>
);
const blur = () => { useImperativeHandle(ref, () => ({
if (pickerRef.current) { focus: () => innerRef.current?.focus(),
pickerRef.current.blur(); blur: () => innerRef.current?.blur(),
} }));
};
return wrapSSR( return wrapSSR(
<LocaleReceiver componentName="DatePicker" defaultLocale={enUS}> <LocaleReceiver componentName="DatePicker" defaultLocale={enUS}>
{(contextLocale: PickerLocale) => { {(contextLocale: PickerLocale) => {
const locale = { ...contextLocale, ...props.locale }; const locale = { ...contextLocale, ...props.locale };
return ( return (
<RCRangePicker<DateType> <RCRangePicker<DateType>
separator={ separator={
<span aria-label="to" className={`${prefixCls}-separator`}> <span aria-label="to" className={`${prefixCls}-separator`}>
<SwapRightOutlined /> <SwapRightOutlined />
</span> </span>
} }
disabled={mergedDisabled} disabled={mergedDisabled}
ref={pickerRef} ref={innerRef}
onBlur={onBlur || blur} dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
onFocus={onFocus || focus} placeholder={getRangePlaceholder(picker, locale, placeholder)}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)} suffixIcon={suffixNode}
placeholder={getRangePlaceholder(picker, locale, placeholder)} clearIcon={<CloseCircleFilled />}
suffixIcon={suffixNode} prevIcon={<span className={`${prefixCls}-prev-icon`} />}
clearIcon={<CloseCircleFilled />} nextIcon={<span className={`${prefixCls}-next-icon`} />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />} superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />} superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />} allowClear
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />} transitionName={`${rootPrefixCls}-slide-up`}
allowClear {...restProps}
transitionName={`${rootPrefixCls}-slide-up`} {...additionalOverrideProps}
{...restProps} className={classNames(
{...additionalOverrideProps} {
className={classNames( [`${prefixCls}-${mergedSize}`]: mergedSize,
{ [`${prefixCls}-borderless`]: !bordered,
[`${prefixCls}-${mergedSize}`]: mergedSize, },
[`${prefixCls}-borderless`]: !bordered, getStatusClassNames(
}, prefixCls as string,
getStatusClassNames( getMergedStatus(contextStatus, customStatus),
prefixCls as string, hasFeedback,
getMergedStatus(contextStatus, customStatus), ),
hasFeedback, hashId,
), className,
hashId, )}
className, locale={locale!.lang}
)} prefixCls={prefixCls}
locale={locale!.lang} getPopupContainer={customGetPopupContainer || getPopupContainer}
prefixCls={prefixCls} generateConfig={generateConfig}
getPopupContainer={customGetPopupContainer || getPopupContainer} components={Components}
generateConfig={generateConfig} direction={direction}
components={Components} dropdownClassName={classNames(hashId, dropdownClassName)}
direction={direction} />
dropdownClassName={classNames(hashId, dropdownClassName)} );
/> }}
); </LocaleReceiver>,
}} );
</LocaleReceiver>, });
);
},
);
return RangePicker as unknown as PickerComponentClass<RangePickerProps<DateType>>; return RangePicker as unknown as PickerComponentClass<RangePickerProps<DateType>>;
} }

View File

@ -6,7 +6,7 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import RCPicker from 'rc-picker'; import RCPicker from 'rc-picker';
import type { PickerMode } from 'rc-picker/lib/interface'; import type { PickerMode } from 'rc-picker/lib/interface';
import type { GenerateConfig } from 'rc-picker/lib/generate/index'; 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 enUS from '../locale/en_US';
import { getPlaceholder, transPlacement2DropdownAlign } from '../util'; import { getPlaceholder, transPlacement2DropdownAlign } from '../util';
import warning from '../../_util/warning'; import warning from '../../_util/warning';
@ -20,7 +20,7 @@ import { getTimeProps, Components } from '.';
import { FormItemInputContext } from '../../form/context'; import { FormItemInputContext } from '../../form/context';
import type { InputStatus } from '../../_util/statusUtils'; import type { InputStatus } from '../../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } 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<DateType>(generateConfig: GenerateConfig<DateType>) { export default function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
type DatePickerProps = PickerProps<DateType> & { type DatePickerProps = PickerProps<DateType> & {
@ -32,136 +32,127 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
picker?: PickerMode, picker?: PickerMode,
displayName?: string, displayName?: string,
) { ) {
const Picker = forwardRef<DatePickRef<DateType>, InnerPickerProps>((props, ref) => { const Picker = forwardRef<DatePickRef<DateType> | CommonPickerMethods, InnerPickerProps>(
const { (props, ref) => {
prefixCls: customizePrefixCls, const {
getPopupContainer: customizeGetPopupContainer, prefixCls: customizePrefixCls,
className, getPopupContainer: customizeGetPopupContainer,
size: customizeSize, className,
bordered = true, size: customizeSize,
placement, bordered = true,
placeholder, placement,
disabled: customDisabled, placeholder,
status: customStatus, disabled: customDisabled,
onBlur, status: customStatus,
onFocus, dropdownClassName,
dropdownClassName, ...restProps
...restProps } = props;
} = props;
warning( warning(
picker !== 'quarter', picker !== 'quarter',
displayName!, displayName!,
`DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`, `DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`,
); );
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext); const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls); const prefixCls = getPrefixCls('picker', customizePrefixCls);
const pickerRef = (ref as any) || React.createRef<RCPicker<DateType>>(); const innerRef = React.useRef<RCPicker<DateType>>(null);
const { format, showTime } = props as any; const { format, showTime } = props as any;
const [wrapSSR, hashId] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
const additionalProps = { useImperativeHandle(ref, () => ({
showToday: true, focus: () => innerRef.current?.focus(),
}; blur: () => innerRef.current?.blur(),
}));
let additionalOverrideProps: any = {}; const additionalProps = {
if (picker) { showToday: true,
additionalOverrideProps.picker = picker; };
}
const mergedPicker = picker || props.picker;
additionalOverrideProps = { let additionalOverrideProps: any = {};
...additionalOverrideProps, if (picker) {
...(showTime ? getTimeProps({ format, picker: mergedPicker, ...showTime }) : {}), additionalOverrideProps.picker = picker;
...(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' ? <ClockCircleOutlined /> : <CalendarOutlined />}
{hasFeedback && feedbackIcon}
</>
);
const focus = () => {
if (pickerRef.current) {
pickerRef.current.focus();
} }
}; const mergedPicker = picker || props.picker;
const blur = () => { additionalOverrideProps = {
if (pickerRef.current) { ...additionalOverrideProps,
pickerRef.current.blur(); ...(showTime ? getTimeProps({ format, picker: mergedPicker, ...showTime }) : {}),
} ...(mergedPicker === 'time'
}; ? getTimeProps({ format, ...props, picker: mergedPicker })
: {}),
};
const rootPrefixCls = getPrefixCls();
return wrapSSR( // ===================== Size =====================
<LocaleReceiver componentName="DatePicker" defaultLocale={enUS}> const size = React.useContext(SizeContext);
{(contextLocale: PickerLocale) => { const mergedSize = customizeSize || size;
const locale = { ...contextLocale, ...props.locale };
return ( // ===================== Disabled =====================
<RCPicker<DateType> const disabled = React.useContext(DisabledContext);
ref={pickerRef} const mergedDisabled = customDisabled || disabled;
placeholder={getPlaceholder(mergedPicker, locale, placeholder)}
suffixIcon={suffixNode} // ===================== FormItemInput =====================
dropdownAlign={transPlacement2DropdownAlign(direction, placement)} const formItemContext = useContext(FormItemInputContext);
clearIcon={<CloseCircleFilled />} const { hasFeedback, status: contextStatus, feedbackIcon } = formItemContext;
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />} const suffixNode = (
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />} <>
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />} {mergedPicker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
allowClear {hasFeedback && feedbackIcon}
transitionName={`${rootPrefixCls}-slide-up`} </>
onBlur={onBlur || blur} );
onFocus={onFocus || focus}
{...additionalProps} return wrapSSR(
{...restProps} <LocaleReceiver componentName="DatePicker" defaultLocale={enUS}>
{...additionalOverrideProps} {(contextLocale: PickerLocale) => {
locale={locale!.lang} const locale = { ...contextLocale, ...props.locale };
className={classNames(
{ return (
[`${prefixCls}-${mergedSize}`]: mergedSize, <RCPicker<DateType>
[`${prefixCls}-borderless`]: !bordered, ref={innerRef}
}, placeholder={getPlaceholder(mergedPicker, locale, placeholder)}
getStatusClassNames( suffixIcon={suffixNode}
prefixCls as string, dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
getMergedStatus(contextStatus, customStatus), clearIcon={<CloseCircleFilled />}
hasFeedback, prevIcon={<span className={`${prefixCls}-prev-icon`} />}
), nextIcon={<span className={`${prefixCls}-next-icon`} />}
hashId, superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
className, superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
)} allowClear
prefixCls={prefixCls} transitionName={`${rootPrefixCls}-slide-up`}
getPopupContainer={customizeGetPopupContainer || getPopupContainer} {...additionalProps}
generateConfig={generateConfig} {...restProps}
components={Components} {...additionalOverrideProps}
direction={direction} locale={locale!.lang}
disabled={mergedDisabled} className={classNames(
dropdownClassName={classNames(hashId, dropdownClassName)} {
/> [`${prefixCls}-${mergedSize}`]: mergedSize,
); [`${prefixCls}-borderless`]: !bordered,
}} },
</LocaleReceiver>, 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)}
/>
);
}}
</LocaleReceiver>,
);
},
);
if (displayName) { if (displayName) {
Picker.displayName = displayName; Picker.displayName = displayName;

View File

@ -50,7 +50,9 @@ describe('Drawer', () => {
const { container, rerender } = render(getDrawer({ destroyOnClose: true })); const { container, rerender } = render(getDrawer({ destroyOnClose: true }));
rerender(getDrawer({ destroyOnClose: true, visible: false })); 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(); expect(container.querySelector('.ant-drawer-wrapper-body')).toBeFalsy();
}); });
@ -60,8 +62,19 @@ describe('Drawer', () => {
expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy(); expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy();
rerender(getDrawer({ visible: false })); 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(); 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);
});
}); });

View File

@ -20,7 +20,6 @@ exports[`Drawer className is test_drawer 1`] = `
> >
<div <div
class="ant-drawer-wrapper-body" class="ant-drawer-wrapper-body"
style="opacity: 0; transition: opacity .3s;"
> >
<div <div
class="ant-drawer-header ant-drawer-header-close-only" class="ant-drawer-header ant-drawer-header-close-only"
@ -120,7 +119,6 @@ exports[`Drawer destroyOnClose is true 1`] = `
> >
<div <div
class="ant-drawer-wrapper-body" class="ant-drawer-wrapper-body"
style="opacity: 0; transition: opacity .3s;"
> >
<div <div
class="ant-drawer-header ant-drawer-header-close-only" class="ant-drawer-header ant-drawer-header-close-only"

View File

@ -7,8 +7,6 @@ import { tuple } from '../_util/type';
// CSSINJS // CSSINJS
import useStyle from './style'; import useStyle from './style';
import useForceUpdate from '../_util/hooks/useForceUpdate';
type DrawerRef = { type DrawerRef = {
push(): void; push(): void;
pull(): void; pull(): void;
@ -90,7 +88,8 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
bodyStyle, bodyStyle,
drawerStyle, drawerStyle,
className, className,
visible, visible: propsVisible,
forceRender,
children, children,
zIndex, zIndex,
destroyOnClose, destroyOnClose,
@ -103,14 +102,31 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
getContainer: customizeGetContainer, getContainer: customizeGetContainer,
extra, extra,
afterVisibleChange,
...rest ...rest
}, },
ref, ref,
) => { ) => {
const forceUpdate = useForceUpdate();
const [internalPush, setPush] = React.useState(false); const [internalPush, setPush] = React.useState(false);
const parentDrawer = React.useContext(DrawerContext); const parentDrawer = React.useContext(DrawerContext);
const destroyClose = React.useRef<boolean>(false); const destroyCloseRef = React.useRef<boolean>(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 { getPopupContainer, getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('drawer', customizePrefixCls); const prefixCls = getPrefixCls('drawer', customizePrefixCls);
@ -127,7 +143,7 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
React.useEffect(() => { React.useEffect(() => {
// fix: delete drawer in child and re-render, no push started. // fix: delete drawer in child and re-render, no push started.
// <Drawer>{show && <Drawer />}</Drawer> // <Drawer>{show && <Drawer />}</Drawer>
if (visible && parentDrawer) { if (propsVisible && parentDrawer) {
parentDrawer.push(); parentDrawer.push();
} }
@ -167,18 +183,6 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
React.useImperativeHandle(ref, () => operations, [operations]); React.useImperativeHandle(ref, () => operations, [operations]);
const isDestroyOnClose = destroyOnClose && !visible;
const onDestroyTransitionEnd = () => {
if (!isDestroyOnClose) {
return;
}
if (!visible) {
destroyClose.current = true;
forceUpdate();
}
};
const getOffsetStyle = () => { const getOffsetStyle = () => {
// https://github.com/ant-design/ant-design/issues/24287 // https://github.com/ant-design/ant-design/issues/24287
if (!visible && !mask) { if (!visible && !mask) {
@ -267,28 +271,13 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
// render drawer body dom // render drawer body dom
const renderBody = () => { const renderBody = () => {
if (destroyClose.current && !visible) { // destroyCloseRef.current =false Load the body only once by default
if (destroyCloseRef.current && !forceRender && !load) {
return null; 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 ( return (
<div <div className={`${prefixCls}-wrapper-body`} style={{ ...drawerStyle }}>
className={`${prefixCls}-wrapper-body`}
style={{
...containerStyle,
...drawerStyle,
}}
onTransitionEnd={onDestroyTransitionEnd}
>
{renderHeader()} {renderHeader()}
<div className={`${prefixCls}-body`} style={bodyStyle}> <div className={`${prefixCls}-body`} style={bodyStyle}>
{children} {children}
@ -320,6 +309,7 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
keyboard, keyboard,
children, children,
onClose, onClose,
forceRender,
...rest, ...rest,
}} }}
{...offsetStyle} {...offsetStyle}
@ -328,6 +318,18 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
style={getRcDrawerStyle()} style={getRcDrawerStyle()}
className={drawerClassName} className={drawerClassName}
getContainer={getContainer} getContainer={getContainer}
afterVisibleChange={open => {
if (!open) {
if (destroyCloseRef.current === false) {
// set true only once
destroyCloseRef.current = true;
}
if (destroyOnClose) {
setLoad(false);
}
}
afterVisibleChange?.(open);
}}
> >
{renderBody()} {renderBody()}
</RcDrawer> </RcDrawer>

View File

@ -12,6 +12,8 @@ import type { PaginationConfig } from '../pagination';
import Pagination from '../pagination'; import Pagination from '../pagination';
import { Row } from '../grid'; import { Row } from '../grid';
import Item from './Item'; import Item from './Item';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
// CSSINJS // CSSINJS
import useStyle from './style'; import useStyle from './style';
@ -266,7 +268,7 @@ function List<T>({
<ul className={`${prefixCls}-items`}>{items}</ul> <ul className={`${prefixCls}-items`}>{items}</ul>
); );
} else if (!children && !isLoading) { } else if (!children && !isLoading) {
childrenContent = renderEmptyFunc(prefixCls, renderEmpty); childrenContent = renderEmptyFunc(prefixCls, renderEmpty || defaultRenderEmpty);
} }
const paginationPosition = paginationProps.position || 'bottom'; const paginationPosition = paginationProps.position || 'bottom';

View File

@ -10,6 +10,7 @@ import { FormItemInputContext } from '../form/context';
import useStyle from './style'; import useStyle from './style';
import type { InputStatus } from '../_util/statusUtils'; import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
export const { Option } = RcMentions; export const { Option } = RcMentions;
@ -96,7 +97,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
return notFoundContent; return notFoundContent;
} }
return renderEmpty('Select'); return (renderEmpty || defaultRenderEmpty)('Select');
}; };
const getOptions = () => { const getOptions = () => {

View File

@ -18,6 +18,7 @@ import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import type { SelectCommonPlacement } from '../_util/motion'; import type { SelectCommonPlacement } from '../_util/motion';
import { getTransitionName, getTransitionDirection } from '../_util/motion'; import { getTransitionName, getTransitionDirection } from '../_util/motion';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import useStyle from './style'; import useStyle from './style';
@ -126,7 +127,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
} else if (mode === 'combobox') { } else if (mode === 'combobox') {
mergedNotFound = null; mergedNotFound = null;
} else { } else {
mergedNotFound = renderEmpty('Select'); mergedNotFound = (renderEmpty || defaultRenderEmpty)('Select');
} }
// ===================== Icons ===================== // ===================== Icons =====================

View File

@ -49,6 +49,7 @@ import Column from './Column';
import ColumnGroup from './ColumnGroup'; import ColumnGroup from './ColumnGroup';
import warning from '../_util/warning'; import warning from '../_util/warning';
import useBreakpoint from '../grid/hooks/useBreakpoint'; import useBreakpoint from '../grid/hooks/useBreakpoint';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
export { ColumnsType, TablePaginationConfig }; export { ColumnsType, TablePaginationConfig };
@ -518,7 +519,7 @@ function InternalTable<RecordType extends object = any>(
data={pageData} data={pageData}
rowKey={getRowKey} rowKey={getRowKey}
rowClassName={internalRowClassName} rowClassName={internalRowClassName}
emptyText={(locale && locale.emptyText) || renderEmpty('Table')} emptyText={(locale && locale.emptyText) || (renderEmpty || defaultRenderEmpty)('Table')}
// Internal // Internal
internalHooks={INTERNAL_HOOKS} internalHooks={INTERNAL_HOOKS}
internalRefs={internalRefs as any} internalRefs={internalRefs as any}

View File

@ -14,6 +14,8 @@ import warning from '../_util/warning';
import { FormItemInputContext } from '../form/context'; import { FormItemInputContext } from '../form/context';
import type { InputStatus } from '../_util/statusUtils'; import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import useStyle from './style'; import useStyle from './style';
export { TransferListProps } from './list'; export { TransferListProps } from './list';
@ -395,7 +397,7 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
status: customStatus, status: customStatus,
} = this.props; } = this.props;
const prefixCls = getPrefixCls('transfer', customizePrefixCls); const prefixCls = getPrefixCls('transfer', customizePrefixCls);
const locale = this.getLocale(transferLocale, renderEmpty); const locale = this.getLocale(transferLocale, renderEmpty || defaultRenderEmpty);
const { sourceSelectedKeys, targetSelectedKeys } = this.state; const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const mergedStatus = getMergedStatus(contextStatus, customStatus); const mergedStatus = getMergedStatus(contextStatus, customStatus);

View File

@ -22,6 +22,7 @@ import { getTransitionName, getTransitionDirection } from '../_util/motion';
import { FormItemInputContext } from '../form/context'; import { FormItemInputContext } from '../form/context';
import type { InputStatus } from '../_util/statusUtils'; import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
type RawValue = string | number; type RawValue = string | number;
@ -142,7 +143,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
if (notFoundContent !== undefined) { if (notFoundContent !== undefined) {
mergedNotFound = notFoundContent; mergedNotFound = notFoundContent;
} else { } else {
mergedNotFound = renderEmpty('Select'); mergedNotFound = (renderEmpty || defaultRenderEmpty)('Select');
} }
// ==================== Render ===================== // ==================== Render =====================

View File

@ -1,6 +1,6 @@
{ {
"name": "antd", "name": "antd",
"version": "4.20.4", "version": "4.20.5",
"description": "An enterprise-class UI design language and React components implementation", "description": "An enterprise-class UI design language and React components implementation",
"title": "Ant Design", "title": "Ant Design",
"keywords": [ "keywords": [