diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index 1208a107e9..ab1ab33f28 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -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(); diff --git a/components/_util/__tests__/reactNode.test.tsx b/components/_util/__tests__/reactNode.test.tsx index dc2cad71bc..b56c3e9077 100644 --- a/components/_util/__tests__/reactNode.test.tsx +++ b/components/_util/__tests__/reactNode.test.tsx @@ -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(

test

)).toBe(true); - }); it('isFragment', () => { expect(isFragment(

test

)).toBe(false); expect(isFragment(<>test)).toBe(true); diff --git a/components/_util/reactNode.ts b/components/_util/reactNode.ts index f2eb94882b..aafedf5011 100644 --- a/components/_util/reactNode.ts +++ b/components/_util/reactNode.ts @@ -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

( +export const replaceElement =

( element: React.ReactNode, replacement: React.ReactNode, props?: RenderProps, -) { - if (!isValidElement

(element)) { +) => { + if (!React.isValidElement

(element)) { return replacement; } return React.cloneElement

( element, typeof props === 'function' ? props(element.props || {}) : props, ); -} +}; export function cloneElement

(element: React.ReactNode, props?: RenderProps) { return replaceElement

(element, element, props) as React.ReactElement; diff --git a/components/_util/wave/index.ts b/components/_util/wave/index.ts index 24bf37a509..172733cf4e 100644 --- a/components/_util/wave/index.ts +++ b/components/_util/wave/index.ts @@ -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 = (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; diff --git a/components/auto-complete/index.tsx b/components/auto-complete/index.tsx index fe5b92ad6a..31cca78d9f 100755 --- a/components/auto-complete/index.tsx +++ b/components/auto-complete/index.tsx @@ -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 { - if (isValidElement(item)) { + if (React.isValidElement(item)) { return item; } switch (typeof item) { diff --git a/components/form/FormItem/index.tsx b/components/form/FormItem/index.tsx index 2d3aee414d..cd4a78dca6 100644 --- a/components/form/FormItem/index.tsx +++ b/components/form/FormItem/index.tsx @@ -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(props: FormItemProps): 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', diff --git a/components/menu/MenuItem.tsx b/components/menu/MenuItem.tsx index c5c34df500..d0c9598a46 100644 --- a/components/menu/MenuItem.tsx +++ b/components/menu/MenuItem.tsx @@ -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 = {children}; // 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

{children.charAt(0)}
; } @@ -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`, ), })} diff --git a/components/menu/SubMenu.tsx b/components/menu/SubMenu.tsx index ca85e4207e..7ca3a11ee1 100644 --- a/components/menu/SubMenu.tsx +++ b/components/menu/SubMenu.tsx @@ -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 = (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`, ), })} diff --git a/components/menu/menu.tsx b/components/menu/menu.tsx index be55b3379f..96dc38a676 100644 --- a/components/menu/menu.tsx +++ b/components/menu/menu.tsx @@ -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 { theme?: MenuTheme; @@ -138,7 +138,7 @@ const InternalMenu = forwardRef((props, ref) => { mergedExpandIcon = cloneElement(beClone, { className: classNames( `${prefixCls}-submenu-expand-icon`, - isValidElement(beClone) ? beClone.props?.className : '', + React.isValidElement(beClone) ? beClone.props?.className : '', ), }); } diff --git a/components/spin/index.tsx b/components/spin/index.tsx index 05062614e6..4c7d4010b0 100644 --- a/components/spin/index.tsx +++ b/components/spin/index.tsx @@ -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(ConfigContext); diff --git a/components/tooltip/index.tsx b/components/tooltip/index.tsx index ece825a564..ff2a5ed52b 100644 --- a/components/tooltip/index.tsx +++ b/components/tooltip/index.tsx @@ -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((props, ref) => { // ============================= Render ============================= const child = - isValidElement(children) && !isFragment(children) ? children : {children}; + React.isValidElement(children) && !isFragment(children) ? children : {children}; const childProps = child.props; const childCls = !childProps.className || typeof childProps.className === 'string' diff --git a/components/transfer/list.tsx b/components/transfer/list.tsx index 46c9e17725..91927bb1fd 100644 --- a/components/transfer/list.tsx +++ b/components/transfer/list.tsx @@ -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]' ); } diff --git a/components/tree/DirectoryTree.tsx b/components/tree/DirectoryTree.tsx index 85ad8b7233..7355e3328a 100644 --- a/components/tree/DirectoryTree.tsx +++ b/components/tree/DirectoryTree.tsx @@ -65,7 +65,7 @@ const DirectoryTree: React.ForwardRefRenderFunction keyEntities, ); } else { - initExpandedKeys = (props.expandedKeys || defaultExpandedKeys)!; + initExpandedKeys = props.expandedKeys || defaultExpandedKeys || []; } return initExpandedKeys; }; diff --git a/components/tree/__tests__/directory.test.tsx b/components/tree/__tests__/directory.test.tsx index e1503b9350..3e6179b82e 100644 --- a/components/tree/__tests__/directory.test.tsx +++ b/components/tree/__tests__/directory.test.tsx @@ -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( + , + ); + 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 = [ { diff --git a/components/tree/utils/iconUtil.tsx b/components/tree/utils/iconUtil.tsx index 83d78d5322..1777bea011 100644 --- a/components/tree/utils/iconUtil.tsx +++ b/components/tree/utils/iconUtil.tsx @@ -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 = (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 = (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), }); diff --git a/components/upload/UploadList/index.tsx b/components/upload/UploadList/index.tsx index a2074cfe7b..be3a2e2b4b 100644 --- a/components/upload/UploadList/index.tsx +++ b/components/upload/UploadList/index.tsx @@ -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) => { 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 {}, diff --git a/docs/blog/virtual-table.en-US.md b/docs/blog/virtual-table.en-US.md index 02ed7ad4b8..5d2a41f016 100644 --- a/docs/blog/virtual-table.en-US.md +++ b/docs/blog/virtual-table.en-US.md @@ -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 @@ -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. diff --git a/docs/blog/virtual-table.zh-CN.md b/docs/blog/virtual-table.zh-CN.md index d70f2343f3..8ed1973a44 100644 --- a/docs/blog/virtual-table.zh-CN.md +++ b/docs/blog/virtual-table.zh-CN.md @@ -12,7 +12,7 @@ author: zombieJ ## 太长不看 -Table 通过 `virtual` 属性即可开启虚拟滚动能力。同时,原 Table 的功能(除自定义 `components.body` 外)都能正常使用: +Table 通过 `virtual` 属性即可开启虚拟滚动能力。同时,原 Table 的功能都能正常使用: ```tsx
@@ -156,6 +156,6 @@ const extraRender = ({ start, end }) => { ## 总结 -虚拟滚动是一个非常复杂的功能,它需要考虑的因素非常多。但是我们相信花费这些精力是值得的,开发者不用再在功能和性能之间做取舍。而是可以同时拥有两者。不过需要注意的是,由于我们是通过 `components.body` 进行了虚拟滚动支持。这也意味着开发者不能覆盖 `body` 部分的组件。 +虚拟滚动是一个非常复杂的功能,它需要考虑的因素非常多。但是我们相信花费这些精力是值得的,开发者不用再在功能和性能之间做取舍,而是可以同时拥有两者。 以上。