chore: support deprecated visible in v5 but warning with this (#37422)

* chore: tags deprecated support

* chore: Slider Tooltip test

* chore: Table dropdown visible legacy

* chore: drawer warning of legacy

* chore: Modal legacy prop

* chore: dropdown open legacy prop

* chore: Tooltiop visible legacy info

* fix: format logic

* test: Update test for 18

* chore: fix lint
This commit is contained in:
二货爱吃白萝卜 2022-09-06 21:46:49 +08:00 committed by GitHub
parent f85c68f4be
commit 44d8076304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 382 additions and 147 deletions

View File

@ -106,7 +106,7 @@ describe('Drawer', () => {
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy(); expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
}); });
it('test afterVisibleChange', async () => { it('test afterOpenChange', async () => {
const afterOpenChange = jest.fn(); const afterOpenChange = jest.fn();
const { container, rerender } = render(<DrawerTest open afterOpenChange={afterOpenChange} />); const { container, rerender } = render(<DrawerTest open afterOpenChange={afterOpenChange} />);
rerender(<DrawerTest open={false} afterOpenChange={afterOpenChange} />); rerender(<DrawerTest open={false} afterOpenChange={afterOpenChange} />);
@ -119,6 +119,31 @@ describe('Drawer', () => {
expect(afterOpenChange).toHaveBeenCalledTimes(1); expect(afterOpenChange).toHaveBeenCalledTimes(1);
}); });
it('test legacy afterVisibleChange', async () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const afterVisibleChange = jest.fn();
const { container, rerender } = render(
<DrawerTest open afterVisibleChange={afterVisibleChange} />,
);
rerender(<DrawerTest visible={false} afterVisibleChange={afterVisibleChange} />);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper')!);
expect(afterVisibleChange).toHaveBeenCalledTimes(1);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Drawer] `visible` is deprecated, please use `open` instead.',
);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Drawer] `afterVisibleChange` is deprecated, please use `afterOpenChange` instead.',
);
errorSpy.mockRestore();
});
it('should support children ref', () => { it('should support children ref', () => {
const fn = jest.fn(); const fn = jest.fn();

View File

@ -39,6 +39,12 @@ export interface DrawerProps extends RcDrawerProps {
extra?: React.ReactNode; extra?: React.ReactNode;
afterOpenChange?: (open: boolean) => void; afterOpenChange?: (open: boolean) => void;
// Deprecated
/** @deprecated Please use `open` instead */
visible?: boolean;
/** @deprecated Please use `afterOpenChange` instead */
afterVisibleChange?: (open: boolean) => void;
} }
const defaultPushState: PushState = { distance: 180 }; const defaultPushState: PushState = { distance: 180 };
@ -56,6 +62,7 @@ function Drawer(props: DrawerProps) {
bodyStyle, bodyStyle,
drawerStyle, drawerStyle,
open, open,
afterOpenChange,
children, children,
title, title,
headerStyle, headerStyle,
@ -65,6 +72,11 @@ function Drawer(props: DrawerProps) {
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
getContainer: customizeGetContainer, getContainer: customizeGetContainer,
extra, extra,
// Deprecated
visible,
afterVisibleChange,
...rest ...rest
} = props; } = props;
@ -86,17 +98,6 @@ function Drawer(props: DrawerProps) {
</button> </button>
); );
[
['visible', 'open'],
['afterVisibleChange', 'afterOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Drawer',
`\`${deprecatedName}\` is deprecated which will be removed in next major version, please use \`${newName}\` instead.`,
);
});
function renderHeader() { function renderHeader() {
if (!title && !closable) { if (!title && !closable) {
return null; return null;
@ -149,7 +150,7 @@ function Drawer(props: DrawerProps) {
warning( warning(
!(deprecatedName in props), !(deprecatedName in props),
'Drawer', 'Drawer',
`\`${deprecatedName}\` is removed, please use \`${newName}\` instead.`, `\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
); );
}); });
} }
@ -187,13 +188,14 @@ function Drawer(props: DrawerProps) {
maskMotion={maskMotion} maskMotion={maskMotion}
motion={panelMotion} motion={panelMotion}
{...rest} {...rest}
open={open} open={open ?? visible}
mask={mask} mask={mask}
push={push} push={push}
width={mergedWidth} width={mergedWidth}
height={mergedHeight} height={mergedHeight}
rootClassName={drawerClassName} rootClassName={drawerClassName}
getContainer={getContainer} getContainer={getContainer}
afterOpenChange={afterOpenChange ?? afterVisibleChange}
> >
<div className={`${prefixCls}-wrapper-body`} style={{ ...drawerStyle }}> <div className={`${prefixCls}-wrapper-body`} style={{ ...drawerStyle }}>
{renderHeader()} {renderHeader()}

View File

@ -6,6 +6,7 @@ import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { act, fireEvent, render, sleep } from '../../../tests/utils'; import { act, fireEvent, render, sleep } from '../../../tests/utils';
import Menu from '../../menu'; import Menu from '../../menu';
import { resetWarned } from '../../_util/warning';
let triggerProps: TriggerProps; let triggerProps: TriggerProps;
@ -167,4 +168,37 @@ describe('Dropdown', () => {
jest.useRealTimers(); jest.useRealTimers();
}); });
it('legacy visible', () => {
resetWarned();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const onOpenChange = jest.fn();
const onVisibleChange = jest.fn();
const { container } = render(
<Dropdown
visible
onOpenChange={onOpenChange}
onVisibleChange={onVisibleChange}
trigger={['click']}
overlay={<div className="bamboo" />}
>
<a className="little" />
</Dropdown>,
);
expect(document.querySelector('.bamboo')).toBeTruthy();
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `visible` is deprecated, please use `open` instead.',
);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `onVisibleChange` is deprecated, please use `onOpenChange` instead.',
);
fireEvent.click(container.querySelector('.little')!);
expect(onOpenChange).toHaveBeenCalled();
expect(onVisibleChange).toHaveBeenCalled();
errorSpy.mockRestore();
});
}); });

View File

@ -68,6 +68,12 @@ export interface DropdownProps {
mouseLeaveDelay?: number; mouseLeaveDelay?: number;
openClassName?: string; openClassName?: string;
children?: React.ReactNode; children?: React.ReactNode;
// Deprecated
/** @deprecated Please use `open` instead */
visible?: boolean;
/** @deprecated Please use `onOpenChange` instead */
onVisibleChange?: (open: boolean) => void;
} }
interface DropdownInterface extends React.FC<DropdownProps> { interface DropdownInterface extends React.FC<DropdownProps> {
@ -82,17 +88,6 @@ const Dropdown: DropdownInterface = props => {
direction, direction,
} = React.useContext(ConfigContext); } = React.useContext(ConfigContext);
[
['visible', 'open'],
['onVisibleChange', 'onOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Dropdown',
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
);
});
const getTransitionName = () => { const getTransitionName = () => {
const rootPrefixCls = getPrefixCls(); const rootPrefixCls = getPrefixCls();
const { placement = '', transitionName } = props; const { placement = '', transitionName } = props;
@ -134,8 +129,25 @@ const Dropdown: DropdownInterface = props => {
overlayClassName, overlayClassName,
open, open,
onOpenChange, onOpenChange,
// Deprecated
visible,
onVisibleChange,
} = props; } = props;
if (process.env.NODE_ENV !== 'production') {
[
['visible', 'open'],
['onVisibleChange', 'onOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
!(deprecatedName in props),
'Dropdown',
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
);
});
}
const prefixCls = getPrefixCls('dropdown', customizePrefixCls); const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
@ -160,11 +172,12 @@ const Dropdown: DropdownInterface = props => {
// =========================== Open ============================ // =========================== Open ============================
const [mergedOpen, setOpen] = useMergedState(false, { const [mergedOpen, setOpen] = useMergedState(false, {
value: open, value: open ?? visible,
}); });
const onInnerOpenChange = useEvent((nextOpen: boolean) => { const onInnerOpenChange = useEvent((nextOpen: boolean) => {
onOpenChange?.(nextOpen); onOpenChange?.(nextOpen);
onVisibleChange?.(nextOpen);
setOpen(nextOpen); setOpen(nextOpen);
}); });

View File

@ -82,6 +82,10 @@ export interface ModalProps {
modalRender?: (node: React.ReactNode) => React.ReactNode; modalRender?: (node: React.ReactNode) => React.ReactNode;
focusTriggerAfterClose?: boolean; focusTriggerAfterClose?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
// Legacy
/** @deprecated Please use `open` instead. */
visible?: boolean;
} }
type getContainerFunc = () => HTMLElement; type getContainerFunc = () => HTMLElement;
@ -148,14 +152,6 @@ const Modal: React.FC<ModalProps> = props => {
onOk?.(e); onOk?.(e);
}; };
if (process.env.NODE_ENV !== 'production') {
warning(
!('visible' in props),
'Modal',
`\`visible\` is removed in v5, please use \`open\` instead.`,
);
}
const { const {
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
className, className,
@ -165,6 +161,10 @@ const Modal: React.FC<ModalProps> = props => {
getContainer, getContainer,
closeIcon, closeIcon,
focusTriggerAfterClose = true, focusTriggerAfterClose = true,
// Deprecated
visible,
...restProps ...restProps
} = props; } = props;
@ -178,11 +178,9 @@ const Modal: React.FC<ModalProps> = props => {
[`${prefixCls}-wrap-rtl`]: direction === 'rtl', [`${prefixCls}-wrap-rtl`]: direction === 'rtl',
}); });
warning( if (process.env.NODE_ENV !== 'production') {
!('visible' in props), warning(!('visible' in props), 'Modal', '`visible` is deprecated, please use `open` instead.');
'Modal', }
`\`visible\` is deprecated, please use \`open\` instead.`,
);
return wrapSSR( return wrapSSR(
<NoFormStyle status override> <NoFormStyle status override>
@ -199,7 +197,7 @@ const Modal: React.FC<ModalProps> = props => {
onOk: handleOk, onOk: handleOk,
onCancel: handleCancel, onCancel: handleCancel,
})} })}
visible={open} visible={open ?? visible}
mousePosition={mousePosition} mousePosition={mousePosition}
onClose={handleCancel} onClose={handleCancel}
closeIcon={renderCloseIcon(prefixCls, closeIcon)} closeIcon={renderCloseIcon(prefixCls, closeIcon)}
@ -215,7 +213,6 @@ const Modal: React.FC<ModalProps> = props => {
Modal.defaultProps = { Modal.defaultProps = {
width: 520, width: 520,
confirmLoading: false, confirmLoading: false,
open: false,
}; };
export default Modal; export default Modal;

View File

@ -96,11 +96,13 @@ describe('Modal', () => {
resetWarned(); resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Modal {...({ visible: true } as any)} />); render(<Modal visible />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Modal] `visible` is removed in v5, please use `open` instead.', 'Warning: [antd: Modal] `visible` is deprecated, please use `open` instead.',
); );
expect(document.querySelector('.ant-modal')).toBeTruthy();
errSpy.mockRestore(); errSpy.mockRestore();
}); });
}); });

View File

@ -168,27 +168,58 @@ describe('Slider', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { rerender } = render(<TSSlider tooltipPrefixCls="xxx" />); const { container, rerender } = render(<TSSlider tooltipPrefixCls="xxx" />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Slider] `tooltipPrefixCls` is removed in v5, please use `tooltip.prefixCls` instead.', 'Warning: [antd: Slider] `tooltipPrefixCls` is deprecated, please use `tooltip.prefixCls` instead.',
); );
rerender(<TSSlider getTooltipPopupContainer={() => document.body} />); rerender(<TSSlider getTooltipPopupContainer={() => document.body} />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Slider] `getTooltipPopupContainer` is removed in v5, please use `tooltip.getPopupContainer` instead.', 'Warning: [antd: Slider] `getTooltipPopupContainer` is deprecated, please use `tooltip.getPopupContainer` instead.',
); );
rerender(<TSSlider tipFormatter={(v: any) => v} />); rerender(<TSSlider tipFormatter={(v: any) => v} />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Slider] `tipFormatter` is removed in v5, please use `tooltip.formatter` instead.', 'Warning: [antd: Slider] `tipFormatter` is deprecated, please use `tooltip.formatter` instead.',
); );
rerender(<TSSlider tooltipVisible />); rerender(<TSSlider tooltipVisible />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Slider] `tooltipVisible` is removed in v5, please use `tooltip.open` instead.', 'Warning: [antd: Slider] `tooltipVisible` is deprecated, please use `tooltip.open` instead.',
); );
rerender(<TSSlider tooltipPlacement="left" />); rerender(<TSSlider tooltipPlacement="left" />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Slider] `tooltipPlacement` is removed in v5, please use `tooltip.placement` instead.', 'Warning: [antd: Slider] `tooltipPlacement` is deprecated, please use `tooltip.placement` instead.',
); );
// All should work
const holder = document.createElement('div');
holder.id = 'holder';
document.body.appendChild(holder);
const getTooltipPopupContainer = jest.fn(() => container);
rerender(
<TSSlider
tooltipPrefixCls="bamboo"
getTooltipPopupContainer={getTooltipPopupContainer}
tipFormatter={() => 'little'}
tooltipPlacement="bottom"
tooltipVisible
/>,
);
act(() => {
jest.runAllTimers();
});
expect(getTooltipPopupContainer).toHaveBeenCalled();
expect(container.querySelector('.bamboo')).toBeTruthy();
expect(container.querySelector('.bamboo-inner')!.textContent).toEqual('little');
holder.parentNode?.removeChild(holder);
errSpy.mockRestore(); errSpy.mockRestore();
}); });
}); });

View File

@ -23,12 +23,15 @@ export type HandleGeneratorFn = (config: {
info: HandleGeneratorInfo; info: HandleGeneratorInfo;
}) => React.ReactElement; }) => React.ReactElement;
export type Formatter = (value?: number) => React.ReactNode;
const defaultFormatter: Formatter = val => (typeof val === 'number' ? val.toString() : '');
export interface SliderTooltipProps { export interface SliderTooltipProps {
prefixCls?: string; prefixCls?: string;
open?: boolean; open?: boolean;
placement?: TooltipPlacement; placement?: TooltipPlacement;
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement; getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
formatter?: null | ((value?: number) => React.ReactNode); formatter?: null | Formatter;
} }
export interface SliderBaseProps { export interface SliderBaseProps {
@ -47,6 +50,21 @@ export interface SliderBaseProps {
style?: React.CSSProperties; style?: React.CSSProperties;
tooltip?: SliderTooltipProps; tooltip?: SliderTooltipProps;
autoFocus?: boolean; autoFocus?: boolean;
// Deprecated
/** @deprecated `tooltipPrefixCls` is deprecated. Please use `tooltip.prefixCls` instead. */
tooltipPrefixCls?: string;
/** @deprecated `tipFormatter` is deprecated. Please use `tooltip.formatter` instead. */
tipFormatter?: null | ((value?: number) => React.ReactNode);
/** @deprecated `tooltipVisible` is deprecated. Please use `tooltip.open` instead. */
tooltipVisible?: boolean;
/**
* @deprecated `getTooltipPopupContainer` is deprecated. Please use `tooltip.getPopupContainer`
* instead.
*/
getTooltipPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
/** @deprecated `tooltipPlacement` is deprecated. Please use `tooltip.placement` instead. */
tooltipPlacement?: TooltipPlacement;
} }
export interface SliderSingleProps extends SliderBaseProps { export interface SliderSingleProps extends SliderBaseProps {
@ -77,6 +95,21 @@ export type Opens = { [index: number]: boolean };
const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>( const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
(props, ref: any) => { (props, ref: any) => {
const {
prefixCls: customizePrefixCls,
range,
className,
// Deprecated Props
tooltipPrefixCls: legacyTooltipPrefixCls,
tipFormatter: legacyTipFormatter,
tooltipVisible: legacyTooltipVisible,
getTooltipPopupContainer: legacyGetTooltipPopupContainer,
tooltipPlacement: legacyTooltipPlacement,
...restProps
} = props;
const { getPrefixCls, direction, getPopupContainer } = React.useContext(ConfigContext); const { getPrefixCls, direction, getPopupContainer } = React.useContext(ConfigContext);
const [opens, setOpens] = React.useState<Opens>({}); const [opens, setOpens] = React.useState<Opens>({});
@ -84,9 +117,9 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
setOpens((prev: Opens) => ({ ...prev, [index]: open })); setOpens((prev: Opens) => ({ ...prev, [index]: open }));
}; };
const getTooltipPlacement = (tooltipPlacement?: TooltipPlacement, vertical?: boolean) => { const getTooltipPlacement = (placement?: TooltipPlacement, vertical?: boolean) => {
if (tooltipPlacement) { if (placement) {
return tooltipPlacement; return placement;
} }
if (!vertical) { if (!vertical) {
return 'top'; return 'top';
@ -94,7 +127,6 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
return direction === 'rtl' ? 'left' : 'right'; return direction === 'rtl' ? 'left' : 'right';
}; };
const { prefixCls: customizePrefixCls, range, className, ...restProps } = props;
const prefixCls = getPrefixCls('slider', customizePrefixCls); const prefixCls = getPrefixCls('slider', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);
@ -133,7 +165,7 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
warning( warning(
!(deprecatedName in props), !(deprecatedName in props),
'Slider', 'Slider',
`\`${deprecatedName}\` is removed in v5, please use \`tooltip.${newName}\` instead.`, `\`${deprecatedName}\` is deprecated, please use \`tooltip.${newName}\` instead.`,
); );
}); });
} }
@ -145,9 +177,6 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
const { tooltip = {}, vertical } = props; const { tooltip = {}, vertical } = props;
const tooltipProps: SliderTooltipProps = { const tooltipProps: SliderTooltipProps = {
formatter(value) {
return typeof value === 'number' ? value.toString() : '';
},
...tooltip, ...tooltip,
}; };
const { const {
@ -158,8 +187,18 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
formatter: tipFormatter, formatter: tipFormatter,
} = tooltipProps; } = tooltipProps;
const isTipFormatter = tipFormatter ? opens[index] || dragging : false; let mergedTipFormatter = tipFormatter;
const open = tooltipOpen || (tooltipOpen === undefined && isTipFormatter); if (tipFormatter || tipFormatter === null) {
mergedTipFormatter = tipFormatter;
} else if (legacyTipFormatter || legacyTipFormatter === null) {
mergedTipFormatter = legacyTipFormatter;
} else {
mergedTipFormatter = defaultFormatter;
}
const isTipFormatter = mergedTipFormatter ? opens[index] || dragging : false;
const open =
tooltipOpen ?? legacyTooltipVisible ?? (tooltipOpen === undefined && isTipFormatter);
const passedProps = { const passedProps = {
...node.props, ...node.props,
@ -167,18 +206,23 @@ const Slider = React.forwardRef<unknown, SliderSingleProps | SliderRangeProps>(
onMouseLeave: () => toggleTooltipOpen(index, false), onMouseLeave: () => toggleTooltipOpen(index, false),
}; };
const tooltipPrefixCls = getPrefixCls('tooltip', customizeTooltipPrefixCls); const tooltipPrefixCls = getPrefixCls(
'tooltip',
customizeTooltipPrefixCls ?? legacyTooltipPrefixCls,
);
return ( return (
<SliderTooltip <SliderTooltip
prefixCls={tooltipPrefixCls} prefixCls={tooltipPrefixCls}
title={tipFormatter ? tipFormatter(info.value) : ''} title={mergedTipFormatter ? mergedTipFormatter(info.value) : ''}
open={open} open={open}
placement={getTooltipPlacement(tooltipPlacement, vertical)} placement={getTooltipPlacement(tooltipPlacement ?? legacyTooltipPlacement, vertical)}
transitionName={`${rootPrefixCls}-zoom-down`} transitionName={`${rootPrefixCls}-zoom-down`}
key={index} key={index}
overlayClassName={`${prefixCls}-tooltip`} overlayClassName={`${prefixCls}-tooltip`}
getPopupContainer={getTooltipPopupContainer || getPopupContainer} getPopupContainer={
getTooltipPopupContainer || legacyGetTooltipPopupContainer || getPopupContainer
}
> >
{React.cloneElement(node, passedProps)} {React.cloneElement(node, passedProps)}
</SliderTooltip> </SliderTooltip>

View File

@ -140,28 +140,13 @@ function InternalTable<RecordType extends object = any>(
showSorterTooltip = true, showSorterTooltip = true,
} = props; } = props;
warning( if (process.env.NODE_ENV !== 'production') {
!(typeof rowKey === 'function' && rowKey.length > 1),
'Table',
'`index` parameter of `rowKey` function is deprecated. There is no guarantee that it will work as expected.',
);
warning(
!('filterDropdownVisible' in props || 'onFilterDropdownVisibleChange' in props),
'Table',
'`filterDropdownVisible` and `onFilterDropdownVisibleChange` is deprecated, ' +
'please use `filterDropdownOpen` and `onFilterDropdownOpenChange` instead.',
);
[
['filterDropdownVisible', 'filterDropdownOpen'],
['onFilterDropdownVisibleChange', 'onFilterDropdownOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning( warning(
!(deprecatedName in props), !(typeof rowKey === 'function' && rowKey.length > 1),
'Table', 'Table',
`\`${deprecatedName}\` is deprecated which will be removed in next major version.Please use \`${newName}\` instead. `, '`index` parameter of `rowKey` function is deprecated. There is no guarantee that it will work as expected.',
); );
}); }
const baseColumns = React.useMemo( const baseColumns = React.useMemo(
() => columns || (convertChildrenToColumns(children) as ColumnsType<RecordType>), () => columns || (convertChildrenToColumns(children) as ColumnsType<RecordType>),
@ -277,7 +262,8 @@ function InternalTable<RecordType extends object = any>(
/** /**
* Controlled state in `columns` is not a good idea that makes too many code (1000+ line?) to read * Controlled state in `columns` is not a good idea that makes too many code (1000+ line?) to read
* state out and then put it back to title render. Move these code into `hooks` but still too * state out and then put it back to title render. Move these code into `hooks` but still too
* complex. We should provides Table props like `sorter` & `filter` to handle control in next big version. * complex. We should provides Table props like `sorter` & `filter` to handle control in next big
* version.
*/ */
// ============================ Sorter ============================= // ============================ Sorter =============================

View File

@ -12,6 +12,7 @@ import Tooltip from '../../tooltip';
import type { SelectProps } from '../../select'; import type { SelectProps } from '../../select';
import type { ColumnGroupType, ColumnType, TableProps } from '..'; import type { ColumnGroupType, ColumnType, TableProps } from '..';
import type { ColumnFilterItem, FilterDropdownProps, FilterValue } from '../interface'; import type { ColumnFilterItem, FilterDropdownProps, FilterValue } from '../interface';
import { resetWarned } from '../../_util/warning';
// https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73 // https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73
const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } }; const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } };
@ -281,6 +282,7 @@ describe('Table.filter', () => {
} }
test('filterDropdownOpen'); test('filterDropdownOpen');
test('filterDropdownVisible');
}); });
it('if the filter is visible it should ignore the selectedKeys changes', () => { it('if the filter is visible it should ignore the selectedKeys changes', () => {
@ -333,19 +335,31 @@ describe('Table.filter', () => {
}); });
it('fires change event when visible change', () => { it('fires change event when visible change', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const onFilterDropdownOpenChange = jest.fn(); const onFilterDropdownOpenChange = jest.fn();
const onFilterDropdownVisibleChange = jest.fn();
const { container } = render( const { container } = render(
createTable({ createTable({
columns: [ columns: [
{ {
...column, ...column,
onFilterDropdownOpenChange, onFilterDropdownOpenChange,
onFilterDropdownVisibleChange,
}, },
], ],
}), }),
); );
fireEvent.click(container.querySelector('.ant-dropdown-trigger')!); fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
expect(onFilterDropdownOpenChange).toHaveBeenCalledWith(true); expect(onFilterDropdownOpenChange).toHaveBeenCalledWith(true);
expect(onFilterDropdownVisibleChange).toHaveBeenCalledWith(true);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Table] `onFilterDropdownVisibleChange` is deprecated. Please use `onFilterDropdownOpenChange` instead.',
);
errSpy.mockRestore();
}); });
it('can be controlled by filteredValue', () => { it('can be controlled by filteredValue', () => {

View File

@ -28,6 +28,7 @@ import type {
} from '../../interface'; } from '../../interface';
import FilterSearch from './FilterSearch'; import FilterSearch from './FilterSearch';
import FilterDropdownMenuWrapper from './FilterWrapper'; import FilterDropdownMenuWrapper from './FilterWrapper';
import warning from '../../../_util/warning';
type FilterTreeDataNode = FieldDataNode<{ title: React.ReactNode; key: React.Key }>; type FilterTreeDataNode = FieldDataNode<{ title: React.ReactNode; key: React.Key }>;
@ -141,6 +142,10 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
onFilterDropdownOpenChange, onFilterDropdownOpenChange,
filterResetToDefaultFilteredValue, filterResetToDefaultFilteredValue,
defaultFilteredValue, defaultFilteredValue,
// Deprecated
filterDropdownVisible,
onFilterDropdownVisibleChange,
} = column; } = column;
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
@ -151,9 +156,27 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
const triggerVisible = (newVisible: boolean) => { const triggerVisible = (newVisible: boolean) => {
setVisible(newVisible); setVisible(newVisible);
onFilterDropdownOpenChange?.(newVisible); onFilterDropdownOpenChange?.(newVisible);
onFilterDropdownVisibleChange?.(newVisible);
}; };
const mergedVisible = typeof filterDropdownOpen === 'boolean' ? filterDropdownOpen : visible; if (process.env.NODE_ENV !== 'production') {
[
['filterDropdownVisible', 'filterDropdownOpen', filterDropdownVisible],
[
'onFilterDropdownVisibleChange',
'onFilterDropdownOpenChange',
onFilterDropdownVisibleChange,
],
].forEach(([deprecatedName, newName, prop]) => {
warning(
prop === undefined || prop === null,
'Table',
`\`${deprecatedName}\` is deprecated. Please use \`${newName}\` instead.`,
);
});
}
const mergedVisible = filterDropdownOpen ?? filterDropdownVisible ?? visible;
// ===================== Select Keys ===================== // ===================== Select Keys =====================
const propFilteredKeys = filterState?.filteredKeys; const propFilteredKeys = filterState?.filteredKeys;

View File

@ -120,6 +120,12 @@ export interface ColumnType<RecordType> extends Omit<RcColumnType<RecordType>, '
// Responsive // Responsive
responsive?: Breakpoint[]; responsive?: Breakpoint[];
// Deprecated
/** @deprecated Please use `filterDropdownOpen` instead */
filterDropdownVisible?: boolean;
/** @deprecated Please use `onFilterDropdownOpenChange` instead */
onFilterDropdownVisibleChange?: (visible: boolean) => void;
} }
export interface ColumnGroupType<RecordType> extends Omit<ColumnType<RecordType>, 'dataIndex'> { export interface ColumnGroupType<RecordType> extends Omit<ColumnType<RecordType>, 'dataIndex'> {

View File

@ -86,10 +86,11 @@ describe('Tag', () => {
resetWarned(); resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Tag visible />); const { container } = render(<Tag visible={false} />);
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tag] `visible` is removed, please use `visible && <Tag />` instead.', 'Warning: [antd: Tag] `visible` is deprecated, please use `visible && <Tag />` instead.',
); );
expect(container.querySelector('.ant-tag-hidden')).toBeTruthy();
errSpy.mockRestore(); errSpy.mockRestore();
}); });

View File

@ -20,6 +20,8 @@ export interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
color?: LiteralUnion<PresetColorType | PresetStatusColorType, string>; color?: LiteralUnion<PresetColorType | PresetStatusColorType, string>;
closable?: boolean; closable?: boolean;
closeIcon?: React.ReactNode; closeIcon?: React.ReactNode;
/** @deprecated `visible` will be removed in next major version. */
visible?: boolean;
onClose?: (e: React.MouseEvent<HTMLElement>) => void; onClose?: (e: React.MouseEvent<HTMLElement>) => void;
style?: React.CSSProperties; style?: React.CSSProperties;
icon?: React.ReactNode; icon?: React.ReactNode;
@ -51,6 +53,21 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
const { getPrefixCls, direction } = React.useContext(ConfigContext); const { getPrefixCls, direction } = React.useContext(ConfigContext);
const [visible, setVisible] = React.useState(true); const [visible, setVisible] = React.useState(true);
// Warning for deprecated usage
if (process.env.NODE_ENV !== 'production') {
warning(
!('visible' in props),
'Tag',
'`visible` is deprecated, please use `visible && <Tag />` instead.',
);
}
React.useEffect(() => {
if ('visible' in props) {
setVisible(props.visible!);
}
}, [props.visible]);
const isPresetColor = (): boolean => { const isPresetColor = (): boolean => {
if (!color) { if (!color) {
return false; return false;
@ -103,12 +120,6 @@ const InternalTag: React.ForwardRefRenderFunction<HTMLSpanElement, TagProps> = (
return null; return null;
}; };
warning(
!('visible' in props),
'Tag',
'`visible` is removed, please use `visible && <Tag />` instead.',
);
const isNeedWave = const isNeedWave =
'onClick' in props || (children && (children as React.ReactElement<any>).type === 'a'); 'onClick' in props || (children && (children as React.ReactElement<any>).type === 'a');
const iconNode = icon || null; const iconNode = icon || null;

View File

@ -4,13 +4,14 @@ import type { TooltipPlacement } from '..';
import Tooltip from '..'; import Tooltip from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep, waitFor } from '../../../tests/utils'; import { fireEvent, render, sleep, waitFor, act } from '../../../tests/utils';
import Button from '../../button'; import Button from '../../button';
import DatePicker from '../../date-picker'; import DatePicker from '../../date-picker';
import Input from '../../input'; import Input from '../../input';
import Group from '../../input/Group'; import Group from '../../input/Group';
import Switch from '../../switch'; import Switch from '../../switch';
import Radio from '../../radio'; import Radio from '../../radio';
import { resetWarned } from '../../_util/warning';
describe('Tooltip', () => { describe('Tooltip', () => {
mountTest(Tooltip); mountTest(Tooltip);
@ -451,34 +452,61 @@ describe('Tooltip', () => {
}); });
it('deprecated warning', () => { it('deprecated warning', () => {
resetWarned();
jest.useFakeTimers();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { rerender } = render( // defaultVisible
<Tooltip visible> const { container, rerender } = render(
<Tooltip defaultVisible title="bamboo">
<a />
</Tooltip>,
);
act(() => {
jest.runAllTimers();
});
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `defaultVisible` is deprecated, please use `defaultOpen` instead.',
);
expect(document.querySelector('.ant-tooltip')).toBeTruthy();
// visible
rerender(
<Tooltip visible title="bamboo">
<a /> <a />
</Tooltip>, </Tooltip>,
); );
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `visible` is deprecated, please use `open` instead.', 'Warning: [antd: Tooltip] `visible` is deprecated, please use `open` instead.',
); );
rerender( rerender(
<Tooltip defaultVisible> <Tooltip visible={false} title="bamboo">
<a /> <a />
</Tooltip>, </Tooltip>,
); );
expect(errSpy).toHaveBeenCalledWith( act(() => {
'Warning: [antd: Tooltip] `defaultVisible` is deprecated, please use `defaultOpen` instead.', jest.runAllTimers();
); });
if (container.querySelector('.ant-zoom-big-fast-leave-active')) {
fireEvent.animationEnd(container.querySelector('.ant-zoom-big-fast-leave-active')!);
}
expect(document.querySelector('.ant-tooltip-hidden')).toBeTruthy();
// onVisibleChange
rerender( rerender(
<Tooltip onVisibleChange={() => {}}> <Tooltip onVisibleChange={() => {}} title="bamboo">
<a /> <a />
</Tooltip>, </Tooltip>,
); );
expect(errSpy).toHaveBeenCalledWith( expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Tooltip] `onVisibleChange` is deprecated, please use `onOpenChange` instead.', 'Warning: [antd: Tooltip] `onVisibleChange` is deprecated, please use `onOpenChange` instead.',
); );
// afterVisibleChange
rerender( rerender(
<Tooltip afterVisibleChange={() => {}}> <Tooltip afterVisibleChange={() => {}} title="bamboo">
<a /> <a />
</Tooltip>, </Tooltip>,
); );
@ -486,6 +514,28 @@ describe('Tooltip', () => {
'Warning: [antd: Tooltip] `afterVisibleChange` is deprecated, please use `afterOpenChange` instead.', 'Warning: [antd: Tooltip] `afterVisibleChange` is deprecated, please use `afterOpenChange` instead.',
); );
// Event Trigger
const onVisibleChange = jest.fn();
const afterVisibleChange = jest.fn();
rerender(
<Tooltip
visible
onVisibleChange={onVisibleChange}
afterVisibleChange={afterVisibleChange}
title="bamboo"
>
<a />
</Tooltip>,
);
fireEvent.mouseLeave(container.querySelector('a')!);
act(() => {
jest.runAllTimers();
});
expect(onVisibleChange).toHaveBeenCalled();
expect(afterVisibleChange).toHaveBeenCalled();
jest.useRealTimers();
errSpy.mockRestore(); errSpy.mockRestore();
}); });
}); });

View File

@ -51,30 +51,20 @@ interface LegacyTooltipProps
'children' | 'visible' | 'defaultVisible' | 'onVisibleChange' | 'afterVisibleChange' 'children' | 'visible' | 'defaultVisible' | 'onVisibleChange' | 'afterVisibleChange'
> >
> { > {
/**
* @deprecated `visible` is deprecated which will be removed in next major version. Please use
* `open` instead.
*/
visible?: RcTooltipProps['visible'];
open?: RcTooltipProps['visible']; open?: RcTooltipProps['visible'];
/**
* @deprecated `defaultVisible` is deprecated which will be removed in next major version. Please
* use `defaultOpen` instead.
*/
defaultVisible?: RcTooltipProps['defaultVisible'];
defaultOpen?: RcTooltipProps['defaultVisible']; defaultOpen?: RcTooltipProps['defaultVisible'];
/**
* @deprecated `onVisibleChange` is deprecated which will be removed in next major version. Please
* use `onOpenChange` instead.
*/
onVisibleChange?: RcTooltipProps['onVisibleChange'];
onOpenChange?: RcTooltipProps['onVisibleChange']; onOpenChange?: RcTooltipProps['onVisibleChange'];
/**
* @deprecated `afterVisibleChange` is deprecated which will be removed in next major version.
* Please use `afterOpenChange` instead.
*/
afterVisibleChange?: RcTooltipProps['afterVisibleChange'];
afterOpenChange?: RcTooltipProps['afterVisibleChange']; afterOpenChange?: RcTooltipProps['afterVisibleChange'];
// Legacy
/** @deprecated Please use `open` instead. */
visible?: RcTooltipProps['visible'];
/** @deprecated Please use `defaultOpen` instead. */
defaultVisible?: RcTooltipProps['defaultVisible'];
/** @deprecated Please use `onOpenChange` instead. */
onVisibleChange?: RcTooltipProps['onVisibleChange'];
/** @deprecated Please use `afterOpenChange` instead. */
afterVisibleChange?: RcTooltipProps['afterVisibleChange'];
} }
export interface AbstractTooltipProps extends LegacyTooltipProps { export interface AbstractTooltipProps extends LegacyTooltipProps {
@ -166,28 +156,42 @@ function getDisabledCompatibleChildren(element: React.ReactElement<any>, prefixC
} }
const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => { const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
openClassName,
getTooltipContainer,
overlayClassName,
color,
overlayInnerStyle,
children,
afterOpenChange,
afterVisibleChange,
} = props;
const { const {
getPopupContainer: getContextPopupContainer, getPopupContainer: getContextPopupContainer,
getPrefixCls, getPrefixCls,
direction, direction,
} = React.useContext(ConfigContext); } = React.useContext(ConfigContext);
[ if (process.env.NODE_ENV !== 'production') {
['visible', 'open'], [
['defaultVisible', 'defaultOpen'], ['visible', 'open'],
['onVisibleChange', 'onOpenChange'], ['defaultVisible', 'defaultOpen'],
['afterVisibleChange', 'afterOpenChange'], ['onVisibleChange', 'onOpenChange'],
].forEach(([deprecatedName, newName]) => { ['afterVisibleChange', 'afterOpenChange'],
warning( ].forEach(([deprecatedName, newName]) => {
!(deprecatedName in props), warning(
'Tooltip', !(deprecatedName in props),
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`, 'Tooltip',
); `\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
}); );
});
}
const [open, setOpen] = useMergedState(false, { const [open, setOpen] = useMergedState(false, {
value: props.open, value: props.open ?? props.visible,
defaultValue: props.defaultOpen, defaultValue: props.defaultOpen ?? props.defaultVisible,
}); });
const isNoTitle = () => { const isNoTitle = () => {
@ -256,15 +260,6 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
const { getPopupContainer, overlayStyle, ...otherProps } = props; const { getPopupContainer, overlayStyle, ...otherProps } = props;
const {
prefixCls: customizePrefixCls,
openClassName,
getTooltipContainer,
overlayClassName,
color,
overlayInnerStyle,
children,
} = props;
const prefixCls = getPrefixCls('tooltip', customizePrefixCls); const prefixCls = getPrefixCls('tooltip', customizePrefixCls);
const rootPrefixCls = getPrefixCls(); const rootPrefixCls = getPrefixCls();
@ -272,7 +267,7 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
let tempOpen = open; let tempOpen = open;
// Hide tooltip when there is no title // Hide tooltip when there is no title
if (!('open' in props) && isNoTitle()) { if (!('open' in props) && !('visible' in props) && isNoTitle()) {
tempOpen = false; tempOpen = false;
} }
@ -320,6 +315,7 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
overlay={getOverlay()} overlay={getOverlay()}
visible={tempOpen} visible={tempOpen}
onVisibleChange={onOpenChange} onVisibleChange={onOpenChange}
afterVisibleChange={afterOpenChange ?? afterVisibleChange}
onPopupAlign={onPopupAlign} onPopupAlign={onPopupAlign}
overlayInnerStyle={formattedOverlayInnerStyle} overlayInnerStyle={formattedOverlayInnerStyle}
arrowContent={<span className={`${prefixCls}-arrow-content`} />} arrowContent={<span className={`${prefixCls}-arrow-content`} />}

View File

@ -106,7 +106,7 @@
], ],
"dependencies": { "dependencies": {
"@ant-design/colors": "^6.0.0", "@ant-design/colors": "^6.0.0",
"@ant-design/cssinjs": "^0.0.0-alpha.40", "@ant-design/cssinjs": "^0.0.0-alpha.44",
"@ant-design/icons": "^4.7.0", "@ant-design/icons": "^4.7.0",
"@ant-design/react-slick": "~0.29.1", "@ant-design/react-slick": "~0.29.1",
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",