import React, { Component } from 'react'; import { Tooltip } from 'antd'; import classNames from 'classnames'; import styles from './index.less'; /* eslint react/no-did-mount-set-state: 0 */ /* eslint no-param-reassign: 0 */ const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined; const TooltipOverlayStyle = { overflowWrap: 'break-word', wordWrap: 'break-word', }; export const getStrFullLength = (str = '') => str.split('').reduce((pre, cur) => { const charCode = cur.charCodeAt(0); if (charCode >= 0 && charCode <= 128) { return pre + 1; } return pre + 2; }, 0); export const cutStrByFullLength = (str = '', maxLength) => { let showLength = 0; return str.split('').reduce((pre, cur) => { const charCode = cur.charCodeAt(0); if (charCode >= 0 && charCode <= 128) { showLength += 1; } else { showLength += 2; } if (showLength <= maxLength) { return pre + cur; } return pre; }, ''); }; const getTooltip = ({ tooltip, overlayStyle, title, children }) => { if(tooltip) { const props = tooltip === true ? { overlayStyle, title } : { ...tooltip, overlayStyle, title }; return ( {children} ) } return children; } const EllipsisText = ({ text, length, tooltip, fullWidthRecognition, ...other }) => { if (typeof text !== 'string') { throw new Error('Ellipsis children must be string.'); } const textLength = fullWidthRecognition ? getStrFullLength(text) : text.length; if (textLength <= length || length < 0) { return {text}; } const tail = '...'; let displayText; if (length - tail.length <= 0) { displayText = ''; } else { displayText = fullWidthRecognition ? cutStrByFullLength(text, length) : text.slice(0, length); } const spanAttrs = tooltip ? {} : { ...other }; return getTooltip( { tooltip, overlayStyle:TooltipOverlayStyle, title: text, children: ( {displayText} {tail} ) }); }; export default class Ellipsis extends Component { state = { text: '', targetCount: 0, }; componentDidMount() { if (this.node) { this.computeLine(); } } componentDidUpdate(perProps) { const { lines } = this.props; if (lines !== perProps.lines) { this.computeLine(); } } computeLine = () => { const { lines } = this.props; if (lines && !isSupportLineClamp) { const text = this.shadowChildren.innerText || this.shadowChildren.textContent; const lineHeight = parseInt(getComputedStyle(this.root).lineHeight, 10); const targetHeight = lines * lineHeight; this.content.style.height = `${targetHeight}px`; const totalHeight = this.shadowChildren.offsetHeight; const shadowNode = this.shadow.firstChild; if (totalHeight <= targetHeight) { this.setState({ text, targetCount: text.length, }); return; } // bisection const len = text.length; const mid = Math.ceil(len / 2); const count = this.bisection(targetHeight, mid, 0, len, text, shadowNode); this.setState({ text, targetCount: count, }); } }; bisection = (th, m, b, e, text, shadowNode) => { const suffix = '...'; let mid = m; let end = e; let begin = b; shadowNode.innerHTML = text.substring(0, mid) + suffix; let sh = shadowNode.offsetHeight; if (sh <= th) { shadowNode.innerHTML = text.substring(0, mid + 1) + suffix; sh = shadowNode.offsetHeight; if (sh > th || mid === begin) { return mid; } begin = mid; if (end - begin === 1) { mid = 1 + begin; } else { mid = Math.floor((end - begin) / 2) + begin; } return this.bisection(th, mid, begin, end, text, shadowNode); } if (mid - 1 < 0) { return mid; } shadowNode.innerHTML = text.substring(0, mid - 1) + suffix; sh = shadowNode.offsetHeight; if (sh <= th) { return mid - 1; } end = mid; mid = Math.floor((end - begin) / 2) + begin; return this.bisection(th, mid, begin, end, text, shadowNode); }; handleRoot = n => { this.root = n; }; handleContent = n => { this.content = n; }; handleNode = n => { this.node = n; }; handleShadow = n => { this.shadow = n; }; handleShadowChildren = n => { this.shadowChildren = n; }; render() { const { text, targetCount } = this.state; const { children, lines, length, className, tooltip, fullWidthRecognition, ...restProps } = this.props; const cls = classNames(styles.ellipsis, className, { [styles.lines]: lines && !isSupportLineClamp, [styles.lineClamp]: lines && isSupportLineClamp, }); if (!lines && !length) { return ( {children} ); } // length if (!lines) { return ( ); } const id = `antd-pro-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`; // support document.body.style.webkitLineClamp if (isSupportLineClamp) { const style = `#${id}{-webkit-line-clamp:${lines};-webkit-box-orient: vertical;}`; const node = (
{children}
); return getTooltip({ tooltip, overlayStyle:TooltipOverlayStyle, title: children, children: node }); } const childNode = ( {targetCount > 0 && text.substring(0, targetCount)} {targetCount > 0 && targetCount < text.length && '...'} ); return (
{ getTooltip({ tooltip, overlayStyle:TooltipOverlayStyle, title: text, children: childNode }) }
{children}
{text}
); } }