Skip to content

Commit 570883e

Browse files
committed
fix: extract useTextResize hook
1 parent 7a40fef commit 570883e

File tree

3 files changed

+194
-180
lines changed

3 files changed

+194
-180
lines changed

src/ellipsisText/index.tsx

Lines changed: 6 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
1-
import React, {
2-
CSSProperties,
3-
ReactNode,
4-
useCallback,
5-
useLayoutEffect,
6-
useRef,
7-
useState,
8-
} from 'react';
1+
import React, { ReactNode, useCallback } from 'react';
92
import { Tooltip } from 'antd';
103
import { AbstractTooltipProps, RenderFunction } from 'antd/lib/tooltip';
114
import classNames from 'classnames';
125

136
import Resize from '../resize';
7+
import useTextResize from './useTextResize';
148
import './style.scss';
159

1610
export interface IEllipsisTextProps extends AbstractTooltipProps {
1711
/**
1812
* 文本内容
1913
*/
20-
value: string | number | ReactNode | RenderFunction;
14+
value: ReactNode | RenderFunction;
2115
/**
2216
* 提示内容
2317
* @default value
@@ -41,12 +35,6 @@ export interface IEllipsisTextProps extends AbstractTooltipProps {
4135
[propName: string]: any;
4236
}
4337

44-
export interface NewHTMLElement extends HTMLElement {
45-
currentStyle?: CSSStyleDeclaration;
46-
}
47-
48-
const DEFAULT_MAX_WIDTH = 120;
49-
5038
const EllipsisText = (props: IEllipsisTextProps) => {
5139
const {
5240
value,
@@ -56,176 +44,14 @@ const EllipsisText = (props: IEllipsisTextProps) => {
5644
watchParentSizeChange = false,
5745
...otherProps
5846
} = props;
47+
const { isOverflow, style, ref: ellipsisRef, onResize } = useTextResize(value, maxWidth);
5948

60-
const ellipsisRef = useRef<HTMLSpanElement>(null);
6149
const observerEle =
6250
watchParentSizeChange && ellipsisRef.current?.parentElement
6351
? ellipsisRef.current?.parentElement
6452
: null;
6553

66-
const [visible, setVisible] = useState(false);
67-
const [width, setWidth] = useState<number | string>(DEFAULT_MAX_WIDTH);
68-
const [cursor, setCursor] = useState('default');
69-
70-
useLayoutEffect(() => {
71-
onResize();
72-
}, [value, maxWidth]);
73-
74-
/**
75-
* @description: 根据属性名,获取dom的属性值
76-
* @param {NewHTMLElement} dom
77-
* @param {string} attr
78-
* @return {*}
79-
*/
80-
const getStyle = (dom: NewHTMLElement, attr: string) => {
81-
// Compatible width IE8
82-
// @ts-ignore
83-
return window.getComputedStyle(dom)[attr] || dom.currentStyle[attr];
84-
};
85-
86-
/**
87-
* @description: 根据属性名,获取dom的属性值为number的属性。如: height、width。。。
88-
* @param {NewHTMLElement} dom
89-
* @param {string} attr
90-
* @return {*}
91-
*/
92-
const getNumTypeStyleValue = (dom: NewHTMLElement, attr: string) => {
93-
return parseInt(getStyle(dom, attr));
94-
};
95-
96-
/**
97-
* @description: 10 -> 10,
98-
* @description: 10px -> 10,
99-
* @description: 90% -> ele.width * 0.9
100-
* @description: calc(100% - 32px) -> ele.width - 32
101-
* @param {*} ele
102-
* @param {string & number} maxWidth
103-
* @return {*}
104-
*/
105-
const transitionWidth = (ele: HTMLElement, maxWidth: string | number) => {
106-
const eleWidth = getActualWidth(ele);
107-
108-
if (typeof maxWidth === 'number') {
109-
return maxWidth > eleWidth ? eleWidth : maxWidth; // 如果父元素的宽度小于传入的最大宽度,返回父元素的宽度
110-
}
111-
112-
const numMatch = maxWidth.match(/^(\d+)(px)?$/);
113-
if (numMatch) {
114-
return +numMatch[1] > eleWidth ? eleWidth : +numMatch[1]; // 如果父元素的宽度小于传入的最大宽度,返回父元素的宽度
115-
}
116-
117-
const percentMatch = maxWidth.match(/^(\d+)%$/);
118-
if (percentMatch) {
119-
return eleWidth * (parseInt(percentMatch[1]) / 100);
120-
}
121-
122-
const relativeMatch = maxWidth.match(/^calc\(100% - (\d+)px\)$/);
123-
if (relativeMatch) {
124-
return eleWidth - parseInt(relativeMatch[1]);
125-
}
126-
127-
return eleWidth;
128-
};
129-
130-
const hideEleContent = (node: HTMLElement) => {
131-
node.style.display = 'none';
132-
};
133-
134-
const showEleContent = (node: HTMLElement) => {
135-
node.style.display = 'inline-block';
136-
};
137-
138-
/**
139-
* @description: 获取能够得到宽度的最近父元素宽度。行内元素无法获得宽度,需向上查找父元素
140-
* @param {HTMLElement} ele
141-
* @return {*}
142-
*/
143-
const getContainerWidth = (ele: HTMLElement): number | string => {
144-
if (!ele) return DEFAULT_MAX_WIDTH;
145-
146-
const { scrollWidth, parentElement } = ele;
147-
148-
// 如果是行内元素,获取不到宽度,则向上寻找父元素
149-
if (scrollWidth === 0) {
150-
return getContainerWidth(parentElement!);
151-
}
152-
// 如果设置了最大宽度,则直接返回宽度
153-
if (maxWidth) {
154-
return transitionWidth(ele, maxWidth);
155-
}
156-
157-
hideEleContent(ellipsisRef.current!);
158-
159-
const availableWidth = getAvailableWidth(ele);
160-
161-
return availableWidth < 0 ? 0 : availableWidth;
162-
};
163-
164-
/**
165-
* @description: 获取dom元素的内容宽度
166-
* @param {HTMLElement} ele
167-
* @return {*}
168-
*/
169-
const getRangeWidth = (ele: HTMLElement): any => {
170-
const range = document.createRange();
171-
range.selectNodeContents(ele);
172-
const rangeWidth = range.getBoundingClientRect().width;
173-
174-
return rangeWidth;
175-
};
176-
177-
/**
178-
* @description: 获取元素不包括 padding 的宽度
179-
* @param {HTMLElement} ele
180-
* @return {*}
181-
*/
182-
const getActualWidth = (ele: HTMLElement) => {
183-
const width = ele.getBoundingClientRect().width;
184-
const paddingLeft = getNumTypeStyleValue(ele, 'paddingLeft');
185-
const paddingRight = getNumTypeStyleValue(ele, 'paddingRight');
186-
return width - paddingLeft - paddingRight;
187-
};
188-
189-
/**
190-
* @description: 获取dom的可用宽度
191-
* @param {HTMLElement} ele
192-
* @return {*}
193-
*/
194-
const getAvailableWidth = (ele: HTMLElement) => {
195-
const width = getActualWidth(ele);
196-
const contentWidth = getRangeWidth(ele);
197-
const ellipsisWidth = width - contentWidth;
198-
return ellipsisWidth;
199-
};
200-
201-
/**
202-
* @description: 计算父元素的宽度是否满足内容的大小
203-
* @return {*}
204-
*/
205-
const onResize = () => {
206-
const ellipsisNode = ellipsisRef.current!;
207-
const parentElement = ellipsisNode.parentElement!;
208-
const rangeWidth = getRangeWidth(ellipsisNode);
209-
const containerWidth = getContainerWidth(parentElement);
210-
const visible = rangeWidth > containerWidth;
211-
setVisible(visible);
212-
setWidth(containerWidth);
213-
const parentCursor = getStyle(parentElement, 'cursor');
214-
if (parentCursor !== 'default') {
215-
// 继承父元素的 hover 手势
216-
setCursor(parentCursor);
217-
} else {
218-
// 截取文本时,则改变 hover 手势为 pointer
219-
visible && setCursor('pointer');
220-
}
221-
showEleContent(ellipsisNode);
222-
};
223-
22454
const renderText = useCallback(() => {
225-
const style: CSSProperties = {
226-
maxWidth: width,
227-
cursor,
228-
};
22955
return (
23056
<span
23157
ref={ellipsisRef}
@@ -235,11 +61,11 @@ const EllipsisText = (props: IEllipsisTextProps) => {
23561
{typeof value === 'function' ? value() : value}
23662
</span>
23763
);
238-
}, [width, cursor, value]);
64+
}, [style, value]);
23965

24066
return (
24167
<Resize onResize={onResize} observerEle={observerEle}>
242-
{visible ? (
68+
{isOverflow ? (
24369
<Tooltip title={title} mouseEnterDelay={0} mouseLeaveDelay={0} {...otherProps}>
24470
{renderText()}
24571
</Tooltip>

src/ellipsisText/useTextResize.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useLayoutEffect, useRef, useState } from 'react';
2+
3+
import {
4+
getAvailableWidth,
5+
getRangeWidth,
6+
getStyle,
7+
hideEleContent,
8+
showEleContent,
9+
transitionWidth,
10+
} from './utils';
11+
12+
const DEFAULT_MAX_WIDTH = 120;
13+
export default function useTextResize<T>(value: T, maxWidth?: string | number) {
14+
const [isOverflow, setIsOverflow] = useState(false);
15+
const [width, setWidth] = useState<number | string>(DEFAULT_MAX_WIDTH);
16+
const [cursor, setCursor] = useState('default');
17+
18+
const ref = useRef<HTMLSpanElement>(null);
19+
20+
/**
21+
* @description: 获取能够得到宽度的最近父元素宽度。行内元素无法获得宽度,需向上查找父元素
22+
* @param {HTMLElement} ele
23+
* @return {*}
24+
*/
25+
const getContainerWidth = (ele: HTMLElement): number | string => {
26+
if (!ele) return DEFAULT_MAX_WIDTH;
27+
28+
const { scrollWidth, parentElement } = ele;
29+
30+
// 如果是行内元素,获取不到宽度,则向上寻找父元素
31+
if (scrollWidth === 0) {
32+
return getContainerWidth(parentElement!);
33+
}
34+
// 如果设置了最大宽度,则直接返回宽度
35+
if (maxWidth) {
36+
return transitionWidth(ele, maxWidth);
37+
}
38+
39+
hideEleContent(ref.current!);
40+
41+
const availableWidth = getAvailableWidth(ele);
42+
43+
return availableWidth < 0 ? 0 : availableWidth;
44+
};
45+
46+
/**
47+
* @description: 计算父元素的宽度是否满足内容的大小
48+
* @return {*}
49+
*/
50+
const onResize = () => {
51+
const textNode = ref.current!;
52+
const parentElement = textNode.parentElement!;
53+
const rangeWidth = getRangeWidth(textNode);
54+
const containerWidth = getContainerWidth(parentElement);
55+
const visible = rangeWidth > containerWidth;
56+
const parentCursor = getStyle(parentElement, 'cursor');
57+
58+
setIsOverflow(visible);
59+
setWidth(containerWidth);
60+
if (parentCursor !== 'default') {
61+
// 继承父元素的 hover 手势
62+
setCursor(parentCursor);
63+
} else {
64+
// 截取文本时,则改变 hover 手势为 pointer
65+
visible && setCursor('pointer');
66+
}
67+
showEleContent(textNode);
68+
};
69+
70+
useLayoutEffect(() => {
71+
onResize();
72+
}, [value, maxWidth]);
73+
74+
return {
75+
ref,
76+
isOverflow,
77+
style: {
78+
maxWidth: width,
79+
cursor,
80+
},
81+
onResize,
82+
};
83+
}

0 commit comments

Comments
 (0)