@wix/design-system
Version:
@wix/design-system
167 lines • 6.9 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import shallowEqual from 'shallowequal';
import { st, classes } from './Ellipsis.st.css.js';
import Tooltip from '../../Tooltip';
import { ZIndex } from '../ZIndex';
import { TooltipCommonProps } from '../PropTypes/TooltipCommon';
import TextComponent from './components/TextComponent';
class Ellipsis extends React.PureComponent {
constructor(props) {
super(props);
/**
* Once text component has rendered,
* Update text content and tooltip active state
* @private
*/
this._onTextRendered = () => {
const { isActive, textContent } = this.state;
const newTextContent = this._getTextContent();
const shouldBeActive = this._checkShouldBeActive();
const newState = {
textRendered: true,
...(newTextContent !== textContent
? { textContent: newTextContent }
: {}),
...(shouldBeActive !== isActive ? { isActive: shouldBeActive } : {}),
};
this.setState(oldState => ({ ...oldState, ...newState }));
};
/**
* An ellipsis is considered active when either the text's scroll width/height is wider than it's container or itself.
* @private
*/
this._updateIsActive = () => {
const { isActive } = this.state;
const shouldBeActive = this._checkShouldBeActive();
if (shouldBeActive !== isActive) {
this.setState({ isActive: shouldBeActive });
}
};
this._getTextContent = () => {
const { current: textElement } = this.ref;
return textElement && textElement.textContent;
};
this._checkShouldBeActive = () => this._isOverflowingHorizontally() || this._isOverflowingVertically();
this._isOverflowingHorizontally = () => {
const { current: textElement } = this.ref;
const { ellipsis } = this.props;
const parentWidth = textElement?.parentElement?.offsetWidth;
return !!(ellipsis &&
textElement &&
((parentWidth && textElement.scrollWidth - parentWidth > 1) ||
textElement.offsetWidth < textElement.scrollWidth));
};
this._isOverflowingVertically = () => {
const { current: textElement } = this.ref;
const { ellipsis, maxLines } = this.props;
const parentHeight = textElement?.parentElement?.offsetHeight;
return !!(maxLines &&
maxLines > 1 &&
ellipsis &&
textElement &&
((parentHeight && textElement.scrollHeight - parentHeight > 1) ||
textElement.offsetHeight < textElement.scrollHeight));
};
/**
* A callback for resizing the window, must be debounced in order to improve performance.
* @private
*/
this._debouncedUpdate = debounce(this._updateIsActive, 100);
this.state = {
isActive: false,
textContent: null,
textRendered: false,
};
this.ref = React.createRef();
this.tooltipRef = React.createRef();
}
componentDidMount() {
window.addEventListener('resize', this._debouncedUpdate);
this.props.onEllipsisStateChange?.(this.state.isActive);
}
_renderText() {
const { render, ellipsis, maxLines } = this.props;
const { textRendered, isActive } = this.state;
return (React.createElement(TextComponent, { render, ellipsis, maxLines, textRendered, onTextRendered: this._onTextRendered, textElementRef: this.ref, isEllipsisActive: isActive, tooltipRef: this.tooltipRef }));
}
render() {
const { appendTo, wrapperClassName, disabled, enterDelay, exitDelay, fixed, flip, maxWidth, moveArrowTo, moveBy, onHide, onShow, placement, showTooltip, textAlign, zIndex, size, interactive, } = this.props;
const { isActive, textContent } = this.state;
return showTooltip && isActive ? (React.createElement(Tooltip, { ref: this.tooltipRef, className: st(classes.tooltip, wrapperClassName), content: textContent, appendTo,
disabled,
enterDelay,
exitDelay,
fixed,
flip,
maxWidth,
moveArrowTo,
moveBy,
onHide,
onShow,
placement,
textAlign,
zIndex,
size,
interactive }, this._renderText())) : (this._renderText());
}
static getDerivedStateFromProps(props, state) {
const { render, ellipsis, maxLines } = props;
const textPropsChanged = state.prevRender !== render ||
state.prevEllipsis !== ellipsis ||
state.prevMaxLines !== maxLines;
if (!textPropsChanged) {
return null;
}
// Text changed, initialize textRendered state
return {
textRendered: false,
prevRender: render,
prevEllipsis: ellipsis,
prevMaxLines: maxLines,
};
}
componentDidUpdate(prevProps, prevState) {
const { textRendered, isActive } = this.state;
if (textRendered && !shallowEqual(prevProps, this.props)) {
this._updateIsActive();
}
if (prevState.isActive !== isActive && this.props.onEllipsisStateChange) {
this.props.onEllipsisStateChange(this.state.isActive);
}
}
componentWillUnmount() {
this._debouncedUpdate.cancel();
window.removeEventListener('resize', this._debouncedUpdate);
}
}
Ellipsis.propTypes = {
/** When true, text that is longer than it's container will be truncated to a single line followed by ellipsis. Otherwise the text will break into several lines. */
ellipsis: PropTypes.bool,
/** True by default, set it to false in order to show ellipsis without a tooltip. */
showTooltip: PropTypes.bool,
/** A className to be applied to the Ellipsis wrapper. */
wrapperClassName: PropTypes.string,
/** The render function, use it to render a text you want to truncate with ellipsis. */
render: PropTypes.func,
/** maxLines truncates text at a specific number of lines. */
maxLines: PropTypes.number,
/** A callback fired when ellipsis state changes. */
onEllipsisStateChange: PropTypes.func,
// Tooltip props
...TooltipCommonProps,
};
Ellipsis.defaultProps = {
ellipsis: false,
appendTo: 'window',
flip: false,
fixed: false,
placement: 'top',
zIndex: ZIndex.tooltip,
enterDelay: 0,
exitDelay: 0,
showTooltip: true,
};
export default Ellipsis;
//# sourceMappingURL=Ellipsis.js.map