diff --git a/components/typography/Base/CopyBtn.tsx b/components/typography/Base/CopyBtn.tsx index 59d748d368..1b651e40e7 100644 --- a/components/typography/Base/CopyBtn.tsx +++ b/components/typography/Base/CopyBtn.tsx @@ -19,32 +19,26 @@ export interface CopyBtnProps extends Omit { loading: boolean; } -const CopyBtn: React.FC = (props) => { - const { - prefixCls, - copied, - locale, - iconOnly, - tooltips, - icon, - loading: btnLoading, - tabIndex, - onCopy, - } = props; - +const CopyBtn: React.FC = ({ + prefixCls, + copied, + locale, + iconOnly, + tooltips, + icon, + tabIndex, + onCopy, + loading: btnLoading, +}) => { const tooltipNodes = toList(tooltips); const iconNodes = toList(icon); - const { copied: copiedText, copy: copyText } = locale ?? {}; - - const copyTitle = copied - ? getNode(tooltipNodes[1], copiedText) - : getNode(tooltipNodes[0], copyText); const systemStr = copied ? copiedText : copyText; + const copyTitle = getNode(tooltipNodes[copied ? 1 : 0], systemStr); const ariaLabel = typeof copyTitle === 'string' ? copyTitle : systemStr; return ( - + ( }, ); -function cuttable(node: React.ReactElement) { - const type = typeof node; - return type === 'string' || type === 'number'; -} - -function getNodesLen(nodeList: React.ReactElement[]) { - let totalLen = 0; - - nodeList.forEach((node) => { - if (cuttable(node)) { - totalLen += String(node).length; - } else { - totalLen += 1; - } - }); - - return totalLen; -} +const getNodesLen = (nodeList: React.ReactElement[]) => + nodeList.reduce((totalLen, node) => totalLen + (isValidText(node) ? String(node).length : 1), 0); function sliceNodes(nodeList: React.ReactElement[], len: number) { let currLen = 0; @@ -74,7 +59,7 @@ function sliceNodes(nodeList: React.ReactElement[], len: number) { } const node = nodeList[i]; - const canCut = cuttable(node); + const canCut = isValidText(node); const nodeLen = canCut ? String(node).length : 1; const nextLen = currLen + nodeLen; @@ -97,7 +82,6 @@ export interface EllipsisProps { enableMeasure?: boolean; text?: React.ReactNode; width: number; - // fontSize: number; rows: number; children: ( cutChildren: React.ReactNode[], @@ -142,9 +126,7 @@ export default function EllipsisMeasure(props: EllipsisProps) { // ========================= NeedEllipsis ========================= const measureWhiteSpaceRef = React.useRef(null); - const needEllipsisRef = React.useRef(null); - // Measure for `rows-1` height, to avoid operation exceed the line height const descRowsEllipsisRef = React.useRef(null); const symbolRowEllipsisRef = React.useRef(null); @@ -187,9 +169,11 @@ export default function EllipsisMeasure(props: EllipsisProps) { // Get the height of `rows - 1` + symbol height const descRowsEllipsisHeight = rows === 1 ? 0 : descRowsEllipsisRef.current?.getHeight() || 0; const symbolRowEllipsisHeight = symbolRowEllipsisRef.current?.getHeight() || 0; - const rowsWithEllipsisHeight = descRowsEllipsisHeight + symbolRowEllipsisHeight; - - const maxRowsHeight = Math.max(baseRowsEllipsisHeight, rowsWithEllipsisHeight); + const maxRowsHeight = Math.max( + baseRowsEllipsisHeight, + // height of rows with ellipsis + descRowsEllipsisHeight + symbolRowEllipsisHeight, + ); setEllipsisHeight(maxRowsHeight + 1); @@ -209,16 +193,10 @@ export default function EllipsisMeasure(props: EllipsisProps) { const isOverflow = midHeight > ellipsisHeight; let targetMidIndex = cutMidIndex; - if (maxIndex - minIndex === 1) { targetMidIndex = isOverflow ? minIndex : maxIndex; } - - if (isOverflow) { - setEllipsisCutIndex([minIndex, targetMidIndex]); - } else { - setEllipsisCutIndex([targetMidIndex, maxIndex]); - } + setEllipsisCutIndex(isOverflow ? [minIndex, targetMidIndex] : [targetMidIndex, maxIndex]); } }, [ellipsisCutIndex, cutMidIndex]); @@ -235,26 +213,21 @@ export default function EllipsisMeasure(props: EllipsisProps) { ellipsisCutIndex[0] !== ellipsisCutIndex[1] ) { const content = children(nodeList, false); - - // Limit the max line count to avoid scrollbar blink + // Limit the max line count to avoid scrollbar blink unless no need ellipsis // https://github.com/ant-design/ant-design/issues/42958 - if ( - needEllipsis !== STATUS_MEASURE_NO_NEED_ELLIPSIS && - needEllipsis !== STATUS_MEASURE_NONE - ) { - return ( - - {content} - - ); + if ([STATUS_MEASURE_NO_NEED_ELLIPSIS, STATUS_MEASURE_NONE].includes(needEllipsis)) { + return content; } - - return content; + return ( + + {content} + + ); } return children(expanded ? nodeList : sliceNodes(nodeList, ellipsisCutIndex[0]), canEllipsis); diff --git a/components/typography/Base/index.tsx b/components/typography/Base/index.tsx index bd97df4da0..d628447a95 100644 --- a/components/typography/Base/index.tsx +++ b/components/typography/Base/index.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import ResizeObserver from 'rc-resize-observer'; import type { AutoSizeType } from 'rc-textarea'; import toArray from 'rc-util/lib/Children/toArray'; -import useIsomorphicLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; +import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; import omit from 'rc-util/lib/omit'; import { composeRef } from 'rc-util/lib/ref'; @@ -19,13 +19,13 @@ import Editable from '../Editable'; import useCopyClick from '../hooks/useCopyClick'; import useMergedConfig from '../hooks/useMergedConfig'; import usePrevious from '../hooks/usePrevious'; -import useUpdatedEffect from '../hooks/useUpdatedEffect'; +import useTooltipProps from '../hooks/useTooltipProps'; import type { TypographyProps } from '../Typography'; import Typography from '../Typography'; import CopyBtn from './CopyBtn'; import Ellipsis from './Ellipsis'; import EllipsisTooltip from './EllipsisTooltip'; -import { isEleEllipsis } from './util'; +import { isEleEllipsis, isValidText } from './util'; export type BaseType = 'secondary' | 'success' | 'warning' | 'danger'; @@ -162,7 +162,7 @@ const Base = React.forwardRef((props, ref) => { // Focus edit icon when back const prevEditing = usePrevious(editing); - useUpdatedEffect(() => { + useLayoutEffect(() => { if (!editing && prevEditing) { editIconRef.current?.focus(); } @@ -223,7 +223,7 @@ const Base = React.forwardRef((props, ref) => { [mergedEnableEllipsis, ellipsisConfig, enableEdit, enableCopy], ); - useIsomorphicLayoutEffect(() => { + useLayoutEffect(() => { if (enableEllipsis && !needMeasureEllipsis) { setIsLineClampSupport(isStyleSupport('webkitLineClamp')); setIsTextOverflowSupport(isStyleSupport('textOverflow')); @@ -246,7 +246,7 @@ const Base = React.forwardRef((props, ref) => { // We use effect to change from css ellipsis to js ellipsis. // To make SSR still can see the ellipsis. - useIsomorphicLayoutEffect(() => { + useLayoutEffect(() => { setCssEllipsis(canUseCssEllipsis && mergedEnableEllipsis); }, [canUseCssEllipsis, mergedEnableEllipsis]); @@ -314,40 +314,13 @@ const Base = React.forwardRef((props, ref) => { }, [cssEllipsis, mergedEnableEllipsis]); // ========================== Tooltip =========================== - let tooltipProps: TooltipProps = {}; - if (ellipsisConfig.tooltip === true) { - tooltipProps = { title: editConfig.text ?? children }; - } else if (React.isValidElement(ellipsisConfig.tooltip)) { - tooltipProps = { title: ellipsisConfig.tooltip }; - } else if (typeof ellipsisConfig.tooltip === 'object') { - tooltipProps = { title: editConfig.text ?? children, ...ellipsisConfig.tooltip }; - } else { - tooltipProps = { title: ellipsisConfig.tooltip }; - } - const topAriaLabel = React.useMemo(() => { - const isValid = (val: any): val is string | number => ['string', 'number'].includes(typeof val); + const tooltipProps = useTooltipProps(ellipsisConfig.tooltip, editConfig.text, children); + const topAriaLabel = React.useMemo(() => { if (!enableEllipsis || cssEllipsis) { return undefined; } - - if (isValid(editConfig.text)) { - return editConfig.text; - } - - if (isValid(children)) { - return children; - } - - if (isValid(title)) { - return title; - } - - if (isValid(tooltipProps.title)) { - return tooltipProps.title; - } - - return undefined; + return [editConfig.text, children, title, tooltipProps.title].find(isValidText); }, [enableEllipsis, cssEllipsis, title, tooltipProps.title, isMergedEllipsis]); // =========================== Render =========================== diff --git a/components/typography/Base/util.ts b/components/typography/Base/util.ts index 5efdec424d..2fa13e719c 100644 --- a/components/typography/Base/util.ts +++ b/components/typography/Base/util.ts @@ -35,16 +35,15 @@ export function isEleEllipsis(ele: HTMLElement): boolean { ele.removeChild(childDiv); // Range checker - if ( - // Horizontal in range - rect.left <= childRect.left && - childRect.right <= rect.right && - // Vertical in range - rect.top <= childRect.top && - childRect.bottom <= rect.bottom - ) { - return false; - } - - return true; + return ( + // Horizontal out of range + rect.left > childRect.left || + childRect.right > rect.right || + // Vertical out of range + rect.top > childRect.top || + childRect.bottom > rect.bottom + ); } + +export const isValidText = (val: any): val is string | number => + ['string', 'number'].includes(typeof val); diff --git a/components/typography/Editable.tsx b/components/typography/Editable.tsx index 10df72794c..608c93e76e 100644 --- a/components/typography/Editable.tsx +++ b/components/typography/Editable.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import EnterOutlined from '@ant-design/icons/EnterOutlined'; import classNames from 'classnames'; -import type { AutoSizeType } from 'rc-textarea'; +import type { TextAreaProps } from 'rc-textarea'; import KeyCode from 'rc-util/lib/KeyCode'; import { cloneElement } from '../_util/reactNode'; @@ -21,7 +21,7 @@ interface EditableProps { style?: React.CSSProperties; direction?: DirectionType; maxLength?: number; - autoSize?: boolean | AutoSizeType; + autoSize?: TextAreaProps['autoSize']; enterIcon?: React.ReactNode; component?: string; } @@ -94,19 +94,20 @@ const Editable: React.FC = (props) => { }) => { // Check if it's a real key if ( - lastKeyCode.current === keyCode && - !inComposition.current && - !ctrlKey && - !altKey && - !metaKey && - !shiftKey + lastKeyCode.current !== keyCode || + inComposition.current || + ctrlKey || + altKey || + metaKey || + shiftKey ) { - if (keyCode === KeyCode.ENTER) { - confirmChange(); - onEnd?.(); - } else if (keyCode === KeyCode.ESC) { - onCancel(); - } + return; + } + if (keyCode === KeyCode.ENTER) { + confirmChange(); + onEnd?.(); + } else if (keyCode === KeyCode.ESC) { + onCancel(); } }; @@ -114,8 +115,6 @@ const Editable: React.FC = (props) => { confirmChange(); }; - const textClassName = component ? `${prefixCls}-${component}` : ''; - const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); const textAreaClassName = classNames( @@ -123,9 +122,9 @@ const Editable: React.FC = (props) => { `${prefixCls}-edit-content`, { [`${prefixCls}-rtl`]: direction === 'rtl', + [`${prefixCls}-${component}`]: !!component, }, className, - textClassName, hashId, cssVarCls, ); diff --git a/components/typography/Text.tsx b/components/typography/Text.tsx index eb1bf07521..a20aa9ab0f 100644 --- a/components/typography/Text.tsx +++ b/components/typography/Text.tsx @@ -19,7 +19,6 @@ const Text: React.ForwardRefRenderFunction = ( if (ellipsis && typeof ellipsis === 'object') { return omit(ellipsis as EllipsisConfig, ['expandable', 'rows']); } - return ellipsis; }, [ellipsis]); diff --git a/components/typography/Title.tsx b/components/typography/Title.tsx index 6eb8cdd068..ce59447d8a 100644 --- a/components/typography/Title.tsx +++ b/components/typography/Title.tsx @@ -17,8 +17,6 @@ export interface TitleProps const Title = React.forwardRef((props, ref) => { const { level = 1, ...restProps } = props; - let component: keyof JSX.IntrinsicElements; - if (process.env.NODE_ENV !== 'production') { const warning = devUseWarning('Typography.Title'); @@ -28,13 +26,9 @@ const Title = React.forwardRef((props, ref) => { 'Title only accept `1 | 2 | 3 | 4 | 5` as `level` value. And `5` need 4.6.0+ version.', ); } - - if (TITLE_ELE_LIST.includes(level)) { - component = `h${level}`; - } else { - component = 'h1'; - } - + const component: keyof JSX.IntrinsicElements = TITLE_ELE_LIST.includes(level) + ? `h${level}` + : `h1`; return ; }); diff --git a/components/typography/Typography.tsx b/components/typography/Typography.tsx index 98bb83694d..cfdea88241 100644 --- a/components/typography/Typography.tsx +++ b/components/typography/Typography.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import classNames from 'classnames'; import { composeRef } from 'rc-util/lib/ref'; - import { devUseWarning } from '../_util/warning'; import type { ConfigConsumerProps, DirectionType } from '../config-provider'; import { ConfigContext } from '../config-provider'; @@ -42,6 +41,7 @@ const Typography = React.forwardRef< style, ...restProps } = props; + const { getPrefixCls, direction: contextDirection, @@ -49,23 +49,16 @@ const Typography = React.forwardRef< } = React.useContext(ConfigContext); const direction = typographyDirection ?? contextDirection; - - let mergedRef = ref; - if (setContentRef) { - mergedRef = composeRef(ref, setContentRef); - } + const mergedRef = setContentRef ? composeRef(ref, setContentRef) : ref; + const prefixCls = getPrefixCls('typography', customizePrefixCls); if (process.env.NODE_ENV !== 'production') { const warning = devUseWarning('Typography'); - warning.deprecated(!setContentRef, 'setContentRef', 'ref'); } - const prefixCls = getPrefixCls('typography', customizePrefixCls); - // Style const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); - const componentClassName = classNames( prefixCls, typography?.className, @@ -92,5 +85,4 @@ if (process.env.NODE_ENV !== 'production') { Typography.displayName = 'Typography'; } -// es default export should use const instead of let export default Typography; diff --git a/components/typography/hooks/useTooltipProps.ts b/components/typography/hooks/useTooltipProps.ts new file mode 100644 index 0000000000..8b83569e90 --- /dev/null +++ b/components/typography/hooks/useTooltipProps.ts @@ -0,0 +1,22 @@ +import { isValidElement, useMemo } from 'react'; +import type { TooltipProps } from '../../tooltip'; + +const useTooltipProps = ( + tooltip: React.ReactNode | TooltipProps, + editConfigText: React.ReactNode, + children: React.ReactNode, +) => + useMemo(() => { + if (tooltip === true) { + return { title: editConfigText ?? children }; + } + if (isValidElement(tooltip)) { + return { title: tooltip }; + } + if (typeof tooltip === 'object') { + return { title: editConfigText ?? children, ...tooltip }; + } + return { title: tooltip }; + }, [typeof tooltip === 'object' ? JSON.stringify(tooltip) : tooltip, editConfigText, children]); + +export default useTooltipProps; diff --git a/components/typography/hooks/useUpdatedEffect.ts b/components/typography/hooks/useUpdatedEffect.ts deleted file mode 100644 index ec85286161..0000000000 --- a/components/typography/hooks/useUpdatedEffect.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; - -/** Similar with `useEffect` but only trigger after mounted */ -const useUpdatedEffect = (callback: () => void, conditions?: React.DependencyList) => { - const mountRef = React.useRef(false); - - React.useEffect(() => { - if (mountRef.current) { - callback(); - } else { - mountRef.current = true; - } - }, conditions); -}; - -export default useUpdatedEffect;