mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +08:00
chore: merge master into feature
This commit is contained in:
commit
cb789d9d14
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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',
|
||||
|
@ -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`,
|
||||
),
|
||||
})}
|
||||
|
@ -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`,
|
||||
),
|
||||
})}
|
||||
|
@ -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 : '',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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'
|
||||
|
@ -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]'
|
||||
);
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
|
||||
keyEntities,
|
||||
);
|
||||
} else {
|
||||
initExpandedKeys = (props.expandedKeys || defaultExpandedKeys)!;
|
||||
initExpandedKeys = props.expandedKeys || defaultExpandedKeys || [];
|
||||
}
|
||||
return initExpandedKeys;
|
||||
};
|
||||
|
@ -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 = [
|
||||
{
|
||||
|
@ -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),
|
||||
});
|
||||
|
@ -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: () => {},
|
||||
|
@ -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.
|
||||
|
@ -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` 部分的组件。
|
||||
虚拟滚动是一个非常复杂的功能,它需要考虑的因素非常多。但是我们相信花费这些精力是值得的,开发者不用再在功能和性能之间做取舍,而是可以同时拥有两者。
|
||||
|
||||
以上。
|
||||
|
Loading…
Reference in New Issue
Block a user