chore: merge master into feature

This commit is contained in:
栗嘉男 2024-02-24 15:51:51 +08:00
commit cb789d9d14
18 changed files with 81 additions and 53 deletions

View File

@ -112,6 +112,10 @@ const GlobalLayout: React.FC = () => {
direction: _direction === 'rtl' ? 'rtl' : 'ltr',
// bannerVisible: storedBannerVisibleLastTime ? !!storedBannerVisible : true,
});
document.documentElement.setAttribute(
'data-prefers-color',
_theme.includes('dark') ? 'dark' : 'light',
);
// Handle isMobile
updateMobileMode();

View File

@ -1,11 +1,8 @@
import React from 'react';
import { isValidElement, cloneElement, isFragment, replaceElement } from '../reactNode';
import { cloneElement, isFragment, replaceElement } from '../reactNode';
describe('reactNode test', () => {
it('isValidElement', () => {
expect(isValidElement(null)).toBe(false);
expect(isValidElement(<p>test</p>)).toBe(true);
});
it('isFragment', () => {
expect(isFragment(<p>test</p>)).toBe(false);
expect(isFragment(<>test</>)).toBe(true);

View File

@ -1,28 +1,26 @@
import * as React from 'react';
import React from 'react';
import type { AnyObject } from './type';
export const { isValidElement } = React;
export function isFragment(child: any): boolean {
return child && isValidElement(child) && child.type === React.Fragment;
return child && React.isValidElement(child) && child.type === React.Fragment;
}
type RenderProps = AnyObject | ((originProps: AnyObject) => AnyObject | void);
export function replaceElement<P>(
export const replaceElement = <P>(
element: React.ReactNode,
replacement: React.ReactNode,
props?: RenderProps,
) {
if (!isValidElement<P>(element)) {
) => {
if (!React.isValidElement<P>(element)) {
return replacement;
}
return React.cloneElement<P>(
element,
typeof props === 'function' ? props(element.props || {}) : props,
);
}
};
export function cloneElement<P>(element: React.ReactNode, props?: RenderProps) {
return replaceElement<P>(element, element, props) as React.ReactElement;

View File

@ -1,7 +1,8 @@
import classNames from 'classnames';
import { composeRef, supportRef } from 'rc-util/lib/ref';
import isVisible from 'rc-util/lib/Dom/isVisible';
import React, { useContext, useRef } from 'react';
import classNames from 'classnames';
import isVisible from 'rc-util/lib/Dom/isVisible';
import { composeRef, supportRef } from 'rc-util/lib/ref';
import type { ConfigConsumerProps } from '../../config-provider';
import { ConfigContext } from '../../config-provider';
import { cloneElement } from '../reactNode';
@ -60,7 +61,7 @@ const Wave: React.FC<WaveProps> = (props) => {
// ============================== Render ==============================
if (!React.isValidElement(children)) {
return (children ?? null) as unknown as React.ReactElement;
return children ?? null;
}
const ref = supportRef(children) ? composeRef((children as any).ref, containerRef) : containerRef;

View File

@ -6,7 +6,6 @@ import omit from 'rc-util/lib/omit';
import { useZIndex } from '../_util/hooks/useZIndex';
import genPurePanel from '../_util/PurePanel';
import { isValidElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
import type { ConfigConsumerProps } from '../config-provider';
@ -69,7 +68,7 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
if (
childNodes.length === 1 &&
isValidElement(childNodes[0]) &&
React.isValidElement(childNodes[0]) &&
!isSelectOptionOrSelectOptGroup(childNodes[0])
) {
[customizeInput] = childNodes;
@ -86,7 +85,7 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
} else {
optionChildren = dataSource
? dataSource.map((item) => {
if (isValidElement(item)) {
if (React.isValidElement(item)) {
return item;
}
switch (typeof item) {

View File

@ -6,7 +6,7 @@ import type { InternalNamePath, Meta } from 'rc-field-form/lib/interface';
import useState from 'rc-util/lib/hooks/useState';
import { supportRef } from 'rc-util/lib/ref';
import { cloneElement, isValidElement } from '../../_util/reactNode';
import { cloneElement } from '../../_util/reactNode';
import { devUseWarning } from '../../_util/warning';
import { ConfigContext } from '../../config-provider';
import useCSSVarCls from '../../config-provider/hooks/useCSSVarCls';
@ -358,7 +358,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
'usage',
'Must set `name` or use a render function when `dependencies` is set.',
);
} else if (isValidElement(mergedChildren)) {
} else if (React.isValidElement(mergedChildren)) {
warning(
mergedChildren.props.defaultValue === undefined,
'usage',

View File

@ -1,14 +1,15 @@
import * as React from 'react';
import classNames from 'classnames';
import type { MenuItemProps as RcMenuItemProps } from 'rc-menu';
import { Item } from 'rc-menu';
import toArray from 'rc-util/lib/Children/toArray';
import omit from 'rc-util/lib/omit';
import * as React from 'react';
import { cloneElement } from '../_util/reactNode';
import type { SiderContextProps } from '../layout/Sider';
import { SiderContext } from '../layout/Sider';
import type { TooltipProps } from '../tooltip';
import Tooltip from '../tooltip';
import { cloneElement, isValidElement } from '../_util/reactNode';
import type { MenuContextProps } from './MenuContext';
import MenuContext from './MenuContext';
@ -48,7 +49,7 @@ const MenuItem: GenericComponent = (props) => {
const wrapNode = <span className={`${prefixCls}-title-content`}>{children}</span>;
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
// ref: https://github.com/ant-design/ant-design/pull/23456
if (!icon || (isValidElement(children) && children.type === 'span')) {
if (!icon || (React.isValidElement(children) && children.type === 'span')) {
if (children && inlineCollapsed && firstLevel && typeof children === 'string') {
return <div className={`${prefixCls}-inline-collapsed-noicon`}>{children.charAt(0)}</div>;
}
@ -91,7 +92,7 @@ const MenuItem: GenericComponent = (props) => {
>
{cloneElement(icon, {
className: classNames(
isValidElement(icon) ? icon.props?.className : '',
React.isValidElement(icon) ? icon.props?.className : '',
`${prefixCls}-item-icon`,
),
})}

View File

@ -4,7 +4,7 @@ import { SubMenu as RcSubMenu, useFullPath } from 'rc-menu';
import omit from 'rc-util/lib/omit';
import { useZIndex } from '../_util/hooks/useZIndex';
import { cloneElement, isValidElement } from '../_util/reactNode';
import { cloneElement } from '../_util/reactNode';
import type { MenuContextProps, MenuTheme } from './MenuContext';
import MenuContext from './MenuContext';
@ -48,12 +48,12 @@ const SubMenu: React.FC<SubMenuProps> = (props) => {
} else {
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
// ref: https://github.com/ant-design/ant-design/pull/23456
const titleIsSpan = isValidElement(title) && title.type === 'span';
const titleIsSpan = React.isValidElement(title) && title.type === 'span';
titleNode = (
<>
{cloneElement(icon, {
className: classNames(
isValidElement(icon) ? icon.props?.className : '',
React.isValidElement(icon) ? icon.props?.className : '',
`${prefixCls}-item-icon`,
),
})}

View File

@ -8,9 +8,10 @@ import { useEvent } from 'rc-util';
import omit from 'rc-util/lib/omit';
import initCollapseMotion from '../_util/motion';
import { cloneElement, isValidElement } from '../_util/reactNode';
import { cloneElement } from '../_util/reactNode';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import type { SiderContextProps } from '../layout/Sider';
import type { ItemType } from './hooks/useItems';
import useItems from './hooks/useItems';
@ -18,7 +19,6 @@ import type { MenuContextProps, MenuTheme } from './MenuContext';
import MenuContext from './MenuContext';
import OverrideContext from './OverrideContext';
import useStyle from './style';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
export interface MenuProps extends Omit<RcMenuProps, 'items'> {
theme?: MenuTheme;
@ -138,7 +138,7 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
mergedExpandIcon = cloneElement(beClone, {
className: classNames(
`${prefixCls}-submenu-expand-icon`,
isValidElement(beClone) ? beClone.props?.className : '',
React.isValidElement(beClone) ? beClone.props?.className : '',
),
});
}

View File

@ -3,7 +3,7 @@ import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import { debounce } from 'throttle-debounce';
import { cloneElement, isValidElement } from '../_util/reactNode';
import { cloneElement } from '../_util/reactNode';
import { devUseWarning } from '../_util/warning';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
@ -44,13 +44,13 @@ function renderIndicator(prefixCls: string, props: SpinProps): React.ReactNode {
return null;
}
if (isValidElement(indicator)) {
if (React.isValidElement(indicator)) {
return cloneElement(indicator, {
className: classNames(indicator.props.className, dotClassName),
});
}
if (isValidElement(defaultIndicator)) {
if (React.isValidElement(defaultIndicator)) {
return cloneElement(defaultIndicator, {
className: classNames(defaultIndicator.props.className, dotClassName),
});
@ -118,7 +118,11 @@ const Spin: SpinType = (props) => {
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Spin');
warning(!tip || isNestedPattern || fullscreen, 'usage', '`tip` only work in nest or fullscreen pattern.');
warning(
!tip || isNestedPattern || fullscreen,
'usage',
'`tip` only work in nest or fullscreen pattern.',
);
}
const { direction, spin } = React.useContext<ConfigConsumerProps>(ConfigContext);

View File

@ -15,7 +15,7 @@ import { useZIndex } from '../_util/hooks/useZIndex';
import { getTransitionName } from '../_util/motion';
import type { AdjustOverflow, PlacementsConfig } from '../_util/placements';
import getPlacements from '../_util/placements';
import { cloneElement, isFragment, isValidElement } from '../_util/reactNode';
import { cloneElement, isFragment } from '../_util/reactNode';
import type { LiteralUnion } from '../_util/type';
import { devUseWarning } from '../_util/warning';
import zIndexContext from '../_util/zindexContext';
@ -266,7 +266,7 @@ const Tooltip = React.forwardRef<TooltipRef, TooltipProps>((props, ref) => {
// ============================= Render =============================
const child =
isValidElement(children) && !isFragment(children) ? children : <span>{children}</span>;
React.isValidElement(children) && !isFragment(children) ? children : <span>{children}</span>;
const childProps = child.props;
const childCls =
!childProps.className || typeof childProps.className === 'string'

View File

@ -3,7 +3,6 @@ import DownOutlined from '@ant-design/icons/DownOutlined';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import { isValidElement } from '../_util/reactNode';
import { groupKeysMap } from '../_util/transKeys';
import Checkbox from '../checkbox';
import Dropdown from '../dropdown';
@ -26,7 +25,7 @@ const defaultRender = () => null;
function isRenderResultPlainObject(result: RenderResult): result is RenderResultObject {
return !!(
result &&
!isValidElement(result) &&
!React.isValidElement(result) &&
Object.prototype.toString.call(result) === '[object Object]'
);
}

View File

@ -65,7 +65,7 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
keyEntities,
);
} else {
initExpandedKeys = (props.expandedKeys || defaultExpandedKeys)!;
initExpandedKeys = props.expandedKeys || defaultExpandedKeys || [];
}
return initExpandedKeys;
};

View File

@ -123,6 +123,30 @@ describe('Directory Tree', () => {
expect(asFragment().firstChild).toMatchSnapshot();
});
it('select multi nodes when shift key down', () => {
const treeData = [
{ title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
{ title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
{ title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
{ title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
];
const { container } = render(
<DirectoryTree multiple defaultExpandAll={false} treeData={treeData} />,
);
expect(container.querySelectorAll('.ant-tree-node-content-wrapper').length).toBe(4);
expect(container.querySelectorAll('.ant-tree-node-selected').length).toBe(0);
const leaf0 = container.querySelectorAll('.ant-tree-node-content-wrapper')[0];
const leaf1 = container.querySelectorAll('.ant-tree-node-content-wrapper')[1];
const leaf2 = container.querySelectorAll('.ant-tree-node-content-wrapper')[2];
const leaf3 = container.querySelectorAll('.ant-tree-node-content-wrapper')[3];
fireEvent.click(leaf2);
fireEvent.click(leaf0, { shiftKey: true });
expect(leaf0).toHaveClass('ant-tree-node-selected');
expect(leaf1).toHaveClass('ant-tree-node-selected');
expect(leaf2).toHaveClass('ant-tree-node-selected');
expect(leaf3).not.toHaveClass('ant-tree-node-selected');
});
it('DirectoryTree should expend all when use treeData and defaultExpandAll is true', () => {
const treeData = [
{

View File

@ -1,11 +1,12 @@
import * as React from 'react';
import CaretDownFilled from '@ant-design/icons/CaretDownFilled';
import FileOutlined from '@ant-design/icons/FileOutlined';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import MinusSquareOutlined from '@ant-design/icons/MinusSquareOutlined';
import PlusSquareOutlined from '@ant-design/icons/PlusSquareOutlined';
import classNames from 'classnames';
import * as React from 'react';
import { cloneElement, isValidElement } from '../../_util/reactNode';
import { cloneElement } from '../../_util/reactNode';
import type { AntTreeNodeProps, SwitcherIcon, TreeLeafIcon } from '../Tree';
interface SwitcherIconProps {
@ -38,7 +39,7 @@ const SwitcherIconCom: React.FC<SwitcherIconProps> = (props) => {
typeof showLeafIcon === 'function' ? showLeafIcon(treeNodeProps) : showLeafIcon;
const leafCls = `${prefixCls}-switcher-line-custom-icon`;
if (isValidElement(leafIcon)) {
if (React.isValidElement(leafIcon)) {
return cloneElement(leafIcon, {
className: classNames(leafIcon.props.className || '', leafCls),
});
@ -58,7 +59,7 @@ const SwitcherIconCom: React.FC<SwitcherIconProps> = (props) => {
const switcher = typeof switcherIcon === 'function' ? switcherIcon(treeNodeProps) : switcherIcon;
if (isValidElement(switcher)) {
if (React.isValidElement(switcher)) {
return cloneElement(switcher, {
className: classNames(switcher.props.className || '', switcherCls),
});

View File

@ -9,7 +9,7 @@ import CSSMotion, { CSSMotionList } from 'rc-motion';
import useForceUpdate from '../../_util/hooks/useForceUpdate';
import initCollapseMotion from '../../_util/motion';
import { cloneElement, isValidElement } from '../../_util/reactNode';
import { cloneElement } from '../../_util/reactNode';
import type { ButtonProps } from '../../button';
import Button from '../../button';
import { ConfigContext } from '../../config-provider';
@ -132,8 +132,8 @@ const InternalUploadList: React.ForwardRefRenderFunction<UploadListRef, UploadLi
title,
onClick: (e: React.MouseEvent<HTMLElement>) => {
callback();
if (isValidElement(customIcon) && customIcon.props.onClick) {
customIcon.props.onClick(e);
if (React.isValidElement(customIcon)) {
customIcon.props.onClick?.(e);
}
},
className: `${prefixCls}-list-item-action`,
@ -141,7 +141,7 @@ const InternalUploadList: React.ForwardRefRenderFunction<UploadListRef, UploadLi
if (acceptUploadDisabled) {
btnProps.disabled = disabled;
}
if (isValidElement(customIcon)) {
if (React.isValidElement(customIcon)) {
const btnIcon = cloneElement(customIcon, {
...customIcon.props,
onClick: () => {},

View File

@ -12,7 +12,7 @@ So we proposed [[RFC] StaticTable for fast perf & virtual scroll support](https:
## TL;DR
Table supports virtual scrolling by setting the `virtual` prop. At the same time, the original Table's functions except `components.body` can be used normally:
Table supports virtual scrolling by setting the `virtual` prop. At the same time, the original Table's functions can be used normally:
```tsx
<Table virtual scroll={{ x: 2000, y: 500 }} {...otherProps} />
@ -156,6 +156,6 @@ Of course, this implementation is based on the assumption that `rowSpan > 1` and
## Finally
Virtual scrolling is a very complex feature, and there are many factors to consider. But we believe that it is worth spending this effort, and developers no longer need to choose between functionality and performance. Instead, you can have both. However, it should be noted that since we have implemented virtual scrolling through `components.body`, developers cannot override the `body` part of the component.
Virtual scrolling is a very complex feature, and there are many factors to consider. But we believe that it is worth spending this effort, and developers no longer need to choose between functionality and performance. Instead, you can have both.
That's all.

View File

@ -12,7 +12,7 @@ author: zombieJ
## 太长不看
Table 通过 `virtual` 属性即可开启虚拟滚动能力。同时,原 Table 的功能(除自定义 `components.body` 外)都能正常使用:
Table 通过 `virtual` 属性即可开启虚拟滚动能力。同时,原 Table 的功能都能正常使用:
```tsx
<Table virtual scroll={{ x: 2000, y: 500 }} {...otherProps} />
@ -156,6 +156,6 @@ const extraRender = ({ start, end }) => {
## 总结
虚拟滚动是一个非常复杂的功能,它需要考虑的因素非常多。但是我们相信花费这些精力是值得的,开发者不用再在功能和性能之间做取舍。而是可以同时拥有两者。不过需要注意的是,由于我们是通过 `components.body` 进行了虚拟滚动支持。这也意味着开发者不能覆盖 `body` 部分的组件
虚拟滚动是一个非常复杂的功能,它需要考虑的因素非常多。但是我们相信花费这些精力是值得的,开发者不用再在功能和性能之间做取舍,而是可以同时拥有两者
以上。