index.js 4.62 KB
Newer Older
1
import React, { Component } from 'react';
niko's avatar
niko committed
2 3 4 5 6 7 8
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 */

9 10
const isSupportLineClamp = (document.body.style.webkitLineClamp !== undefined);

niko's avatar
niko committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
const EllipsisText = ({ text, length, tooltip, ...other }) => {
  if (typeof text !== 'string') {
    throw new Error('Ellipsis children must be string.');
  }
  if (text.length <= length || length < 0) {
    return <span {...other}>{text}</span>;
  }
  const tail = '...';
  let displayText;
  if (length - tail.length <= 0) {
    displayText = '';
  } else {
    displayText = text.slice(0, (length - tail.length));
  }

  if (tooltip) {
27
    return <Tooltip title={text}><span>{displayText}{tail}</span></Tooltip>;
niko's avatar
niko committed
28 29 30 31 32 33 34 35 36
  }

  return (
    <span {...other}>
      {displayText}{tail}
    </span>
  );
};

37
export default class Ellipsis extends Component {
niko's avatar
niko committed
38 39 40 41 42 43 44 45 46 47 48 49
  state = {
    text: '',
    targetCount: 0,
  }

  componentDidMount() {
    if (this.node) {
      this.computeLine();
    }
  }

  componentWillReceiveProps(nextProps) {
50
    if (this.props.lines !== nextProps.lines) {
niko's avatar
niko committed
51 52 53 54 55
      this.computeLine();
    }
  }

  computeLine = () => {
56 57
    const { lines } = this.props;
    if (lines && !isSupportLineClamp) {
niko's avatar
niko committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
      const fontSize = parseInt(window.getComputedStyle(this.node).fontSize, 10) || 14;
      const text = this.shadowChildren.innerText;
      const targetWidth = (this.node.offsetWidth || this.node.parentNode.offsetWidth) * lines;
      const shadowNode = this.shadow.firstChild;

      // bisection
      const tw = (targetWidth - (lines * (fontSize / 2)) - fontSize);
      const len = text.length;
      const mid = Math.floor(len / 2);

      const count = this.bisection(tw, mid, 0, len, text, shadowNode);

      this.setState({
        text,
        targetCount: count,
      });
    }
  }

  bisection = (tw, m, b, e, text, shadowNode) => {
    let mid = m;
    let end = e;
    let begin = b;
    shadowNode.innerHTML = text.substring(0, mid);
    let sw = shadowNode.offsetWidth;

    if (sw < tw) {
      shadowNode.innerHTML = text.substring(0, mid + 1);
      sw = shadowNode.offsetWidth;
      if (sw >= tw) {
        return mid;
      } else {
        begin = mid;
        mid = Math.floor((end - begin) / 2) + begin;
        return this.bisection(tw, mid, begin, end, text, shadowNode);
      }
    } else {
      if (mid - 1 < 0) {
        return mid;
      }
      shadowNode.innerHTML = text.substring(0, mid - 1);
      sw = shadowNode.offsetWidth;
      if (sw <= tw) {
        return mid;
      } else {
        end = mid;
        mid = Math.floor((end - begin) / 2) + begin;
        return this.bisection(tw, mid, begin, end, text, shadowNode);
      }
    }
  }

  handleRef = (n) => {
    this.node = n;
  }

  handleShadow = (n) => {
    this.shadow = n;
  }

  handleShadowChildren = (n) => {
    this.shadowChildren = n;
  }

  render() {
123
    const { text, targetCount } = this.state;
niko's avatar
niko committed
124 125 126 127 128 129 130 131 132
    const {
      children,
      lines,
      length,
      className,
      tooltip,
      ...restProps
    } = this.props;

133

niko's avatar
niko committed
134
    const cls = classNames(styles.ellipsis, className, {
135 136
      [styles.lines]: (lines && !isSupportLineClamp),
      [styles.lineClamp]: (lines && isSupportLineClamp),
niko's avatar
niko committed
137 138 139 140 141 142 143 144 145 146 147
    });

    if (!lines && !length) {
      return (<span className={cls} {...restProps}>{children}</span>);
    }

    // length
    if (!lines) {
      return (<EllipsisText className={cls} length={length} text={children || ''} tooltip={tooltip} {...restProps} />);
    }

148 149 150 151 152
    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};}`;
niko's avatar
niko committed
153
      return (
154
        <div id={id} className={cls} {...restProps}>
niko's avatar
niko committed
155
          <style>{style}</style>
156 157 158 159
          {
            tooltip ? (<Tooltip title={text}>{children}</Tooltip>) : children
          }
        </div>);
niko's avatar
niko committed
160 161
    }

162 163 164 165 166 167 168 169 170 171
    const childNode = (
      <span>
        {
          (targetCount > 0) && text.substring(0, targetCount)
        }
        {
          (targetCount > 0) && (targetCount < text.length) && '...'
        }
      </span>
    );
niko's avatar
niko committed
172 173 174 175 176 177 178 179

    return (
      <div
        {...restProps}
        ref={this.handleRef}
        className={cls}
      >
        {
180 181 182
          tooltip ? (
            <Tooltip title={text}>{childNode}</Tooltip>
          ) : childNode
niko's avatar
niko committed
183 184 185 186 187 188 189
        }
        <div className={styles.shadow} ref={this.handleShadowChildren}>{children}</div>
        <div className={styles.shadow} ref={this.handleShadow}><span>{text}</span></div>
      </div>
    );
  }
}