antd-mobile
Version:
<div align="center">
151 lines • 6.19 kB
JavaScript
import React, { useMemo, useRef, useState } from 'react';
import runes from 'runes2';
import { mergeProps } from '../../utils/with-default-props';
import { withNativeProps } from '../../utils/native-props';
import { useResizeEffect } from '../../utils/use-resize-effect';
import { useIsomorphicLayoutEffect } from 'ahooks';
import { withStopPropagation } from '../../utils/with-stop-propagation';
const classPrefix = `adm-ellipsis`;
const defaultProps = {
direction: 'end',
rows: 1,
expandText: '',
content: '',
collapseText: '',
stopPropagationForActionButtons: [],
onContentClick: () => {},
defaultExpanded: false
};
export const Ellipsis = p => {
const props = mergeProps(defaultProps, p);
const rootRef = useRef(null);
const expandElRef = useRef(null);
const collapseElRef = useRef(null);
const [ellipsised, setEllipsised] = useState({});
const [expanded, setExpanded] = useState(props.defaultExpanded);
const [exceeded, setExceeded] = useState(false);
const chars = useMemo(() => runes(props.content), [props.content]);
function getSubString(start, end) {
return chars.slice(start, end).join('');
}
function calcEllipsised() {
var _a, _b;
const root = rootRef.current;
if (!root) return;
const originDisplay = root.style.display;
root.style.display = 'block';
const originStyle = window.getComputedStyle(root);
const container = document.createElement('div');
const styleNames = Array.prototype.slice.apply(originStyle);
styleNames.forEach(name => {
container.style.setProperty(name, originStyle.getPropertyValue(name));
});
root.style.display = originDisplay;
container.style.height = 'auto';
container.style.minHeight = 'auto';
container.style.maxHeight = 'auto';
container.style.textOverflow = 'clip';
container.style.webkitLineClamp = 'unset';
container.style.display = 'block';
const lineHeight = pxToNumber(originStyle.lineHeight);
const maxHeight = Math.floor(lineHeight * (props.rows + 0.5) + pxToNumber(originStyle.paddingTop) + pxToNumber(originStyle.paddingBottom));
container.innerText = props.content;
document.body.appendChild(container);
if (container.offsetHeight <= maxHeight) {
setExceeded(false);
} else {
setExceeded(true);
const end = props.content.length;
const collapseEl = typeof props.collapseText === 'string' ? props.collapseText : (_a = collapseElRef.current) === null || _a === void 0 ? void 0 : _a.innerHTML;
const expandEl = typeof props.expandText === 'string' ? props.expandText : (_b = expandElRef.current) === null || _b === void 0 ? void 0 : _b.innerHTML;
const actionText = expanded ? collapseEl : expandEl;
function check(left, right) {
if (right - left <= 1) {
if (props.direction === 'end') {
return {
leading: getSubString(0, left) + '...'
};
} else {
return {
tailing: '...' + getSubString(right, end)
};
}
}
const middle = Math.round((left + right) / 2);
if (props.direction === 'end') {
container.innerHTML = getSubString(0, middle) + '...' + actionText;
} else {
container.innerHTML = actionText + '...' + getSubString(middle, end);
}
if (container.offsetHeight <= maxHeight) {
if (props.direction === 'end') {
return check(middle, right);
} else {
return check(left, middle);
}
} else {
if (props.direction === 'end') {
return check(left, middle);
} else {
return check(middle, right);
}
}
}
function checkMiddle(leftPart, rightPart) {
if (leftPart[1] - leftPart[0] <= 1 && rightPart[1] - rightPart[0] <= 1) {
return {
leading: getSubString(0, leftPart[0]) + '...',
tailing: '...' + getSubString(rightPart[1], end)
};
}
const leftPartMiddle = Math.floor((leftPart[0] + leftPart[1]) / 2);
const rightPartMiddle = Math.ceil((rightPart[0] + rightPart[1]) / 2);
container.innerHTML = getSubString(0, leftPartMiddle) + '...' + actionText + '...' + getSubString(rightPartMiddle, end);
if (container.offsetHeight <= maxHeight) {
return checkMiddle([leftPartMiddle, leftPart[1]], [rightPart[0], rightPartMiddle]);
} else {
return checkMiddle([leftPart[0], leftPartMiddle], [rightPartMiddle, rightPart[1]]);
}
}
const middle = Math.floor((0 + end) / 2);
const ellipsised = props.direction === 'middle' ? checkMiddle([0, middle], [middle, end]) : check(0, end);
setEllipsised(ellipsised);
}
document.body.removeChild(container);
}
useResizeEffect(calcEllipsised, rootRef);
useIsomorphicLayoutEffect(() => {
calcEllipsised();
}, [props.content, props.direction, props.rows, props.expandText, props.collapseText]);
const expandActionElement = !!props.expandText && withStopPropagation(props.stopPropagationForActionButtons, React.createElement("a", {
ref: expandElRef,
onClick: () => {
setExpanded(true);
}
}, props.expandText));
const collapseActionElement = !!props.collapseText && withStopPropagation(props.stopPropagationForActionButtons, React.createElement("a", {
ref: collapseElRef,
onClick: () => {
setExpanded(false);
}
}, props.collapseText));
const renderContent = () => {
if (!exceeded) return props.content;
if (expanded) return React.createElement(React.Fragment, null, props.content, collapseActionElement);
return React.createElement(React.Fragment, null, ellipsised.leading, expandActionElement, ellipsised.tailing);
};
return withNativeProps(props, React.createElement("div", {
ref: rootRef,
className: classPrefix,
onClick: e => {
if (e.target === e.currentTarget) {
props.onContentClick(e);
}
}
}, renderContent()));
};
function pxToNumber(value) {
if (!value) return 0;
const match = value.match(/^\d*(\.\d*)?/);
return match ? Number(match[0]) : 0;
}