You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
7.1 KiB
224 lines
7.1 KiB
"use strict";
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports["default"] = void 0;
|
|
|
|
var _reactDom = require("react-dom");
|
|
|
|
var React = _interopRequireWildcard(require("react"));
|
|
|
|
var _toArray = _interopRequireDefault(require("rc-util/lib/Children/toArray"));
|
|
|
|
// We only handle element & text node.
|
|
var ELEMENT_NODE = 1;
|
|
var TEXT_NODE = 3;
|
|
var COMMENT_NODE = 8;
|
|
var ellipsisContainer;
|
|
var wrapperStyle = {
|
|
padding: 0,
|
|
margin: 0,
|
|
display: 'inline',
|
|
lineHeight: 'inherit'
|
|
};
|
|
|
|
function pxToNumber(value) {
|
|
if (!value) return 0;
|
|
var match = value.match(/^\d*(\.\d*)?/);
|
|
return match ? Number(match[0]) : 0;
|
|
}
|
|
|
|
function styleToString(style) {
|
|
// There are some different behavior between Firefox & Chrome.
|
|
// We have to handle this ourself.
|
|
var styleNames = Array.prototype.slice.apply(style);
|
|
return styleNames.map(function (name) {
|
|
return "".concat(name, ": ").concat(style.getPropertyValue(name), ";");
|
|
}).join('');
|
|
}
|
|
|
|
function mergeChildren(children) {
|
|
var childList = [];
|
|
children.forEach(function (child) {
|
|
var prevChild = childList[childList.length - 1];
|
|
|
|
if (typeof child === 'string' && typeof prevChild === 'string') {
|
|
childList[childList.length - 1] += child;
|
|
} else {
|
|
childList.push(child);
|
|
}
|
|
});
|
|
return childList;
|
|
}
|
|
|
|
var _default = function _default(originEle, option, content, fixedContent, ellipsisStr) {
|
|
if (!ellipsisContainer) {
|
|
ellipsisContainer = document.createElement('div');
|
|
ellipsisContainer.setAttribute('aria-hidden', 'true');
|
|
document.body.appendChild(ellipsisContainer);
|
|
}
|
|
|
|
var rows = option.rows,
|
|
_option$suffix = option.suffix,
|
|
suffix = _option$suffix === void 0 ? '' : _option$suffix; // Get origin style
|
|
|
|
var originStyle = window.getComputedStyle(originEle);
|
|
var originCSS = styleToString(originStyle);
|
|
var lineHeight = pxToNumber(originStyle.lineHeight);
|
|
var maxHeight = Math.round(lineHeight * (rows + 1) + pxToNumber(originStyle.paddingTop) + pxToNumber(originStyle.paddingBottom)); // Set shadow
|
|
|
|
ellipsisContainer.setAttribute('style', originCSS);
|
|
ellipsisContainer.style.position = 'fixed';
|
|
ellipsisContainer.style.left = '0';
|
|
ellipsisContainer.style.height = 'auto';
|
|
ellipsisContainer.style.minHeight = 'auto';
|
|
ellipsisContainer.style.maxHeight = 'auto';
|
|
ellipsisContainer.style.top = '-999999px';
|
|
ellipsisContainer.style.zIndex = '-1000'; // clean up css overflow
|
|
|
|
ellipsisContainer.style.textOverflow = 'clip';
|
|
ellipsisContainer.style.whiteSpace = 'normal';
|
|
ellipsisContainer.style.webkitLineClamp = 'none'; // Render in the fake container
|
|
|
|
var contentList = mergeChildren((0, _toArray["default"])(content));
|
|
(0, _reactDom.render)( /*#__PURE__*/React.createElement("div", {
|
|
style: wrapperStyle
|
|
}, /*#__PURE__*/React.createElement("span", {
|
|
style: wrapperStyle
|
|
}, contentList, suffix), /*#__PURE__*/React.createElement("span", {
|
|
style: wrapperStyle
|
|
}, fixedContent)), ellipsisContainer); // wrap in an div for old version react
|
|
// Check if ellipsis in measure div is height enough for content
|
|
|
|
function inRange() {
|
|
return ellipsisContainer.offsetHeight < maxHeight;
|
|
} // Skip ellipsis if already match
|
|
|
|
|
|
if (inRange()) {
|
|
(0, _reactDom.unmountComponentAtNode)(ellipsisContainer);
|
|
return {
|
|
content: content,
|
|
text: ellipsisContainer.innerHTML,
|
|
ellipsis: false
|
|
};
|
|
} // We should clone the childNode since they're controlled by React and we can't reuse it without warning
|
|
|
|
|
|
var childNodes = Array.prototype.slice.apply(ellipsisContainer.childNodes[0].childNodes[0].cloneNode(true).childNodes).filter(function (_ref) {
|
|
var nodeType = _ref.nodeType;
|
|
return nodeType !== COMMENT_NODE;
|
|
});
|
|
var fixedNodes = Array.prototype.slice.apply(ellipsisContainer.childNodes[0].childNodes[1].cloneNode(true).childNodes);
|
|
(0, _reactDom.unmountComponentAtNode)(ellipsisContainer); // ========================= Find match ellipsis content =========================
|
|
|
|
var ellipsisChildren = [];
|
|
ellipsisContainer.innerHTML = ''; // Create origin content holder
|
|
|
|
var ellipsisContentHolder = document.createElement('span');
|
|
ellipsisContainer.appendChild(ellipsisContentHolder);
|
|
var ellipsisTextNode = document.createTextNode(ellipsisStr + suffix);
|
|
ellipsisContentHolder.appendChild(ellipsisTextNode);
|
|
fixedNodes.forEach(function (childNode) {
|
|
ellipsisContainer.appendChild(childNode);
|
|
}); // Append before fixed nodes
|
|
|
|
function appendChildNode(node) {
|
|
ellipsisContentHolder.insertBefore(node, ellipsisTextNode);
|
|
} // Get maximum text
|
|
|
|
|
|
function measureText(textNode, fullText) {
|
|
var startLoc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
var endLoc = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : fullText.length;
|
|
var lastSuccessLoc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
|
|
var midLoc = Math.floor((startLoc + endLoc) / 2);
|
|
var currentText = fullText.slice(0, midLoc);
|
|
textNode.textContent = currentText;
|
|
|
|
if (startLoc >= endLoc - 1) {
|
|
// Loop when step is small
|
|
for (var step = endLoc; step >= startLoc; step -= 1) {
|
|
var currentStepText = fullText.slice(0, step);
|
|
textNode.textContent = currentStepText;
|
|
|
|
if (inRange() || !currentStepText) {
|
|
return step === fullText.length ? {
|
|
finished: false,
|
|
reactNode: fullText
|
|
} : {
|
|
finished: true,
|
|
reactNode: currentStepText
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (inRange()) {
|
|
return measureText(textNode, fullText, midLoc, endLoc, midLoc);
|
|
}
|
|
|
|
return measureText(textNode, fullText, startLoc, midLoc, lastSuccessLoc);
|
|
}
|
|
|
|
function measureNode(childNode, index) {
|
|
var type = childNode.nodeType;
|
|
|
|
if (type === ELEMENT_NODE) {
|
|
// We don't split element, it will keep if whole element can be displayed.
|
|
appendChildNode(childNode);
|
|
|
|
if (inRange()) {
|
|
return {
|
|
finished: false,
|
|
reactNode: contentList[index]
|
|
};
|
|
} // Clean up if can not pull in
|
|
|
|
|
|
ellipsisContentHolder.removeChild(childNode);
|
|
return {
|
|
finished: true,
|
|
reactNode: null
|
|
};
|
|
}
|
|
|
|
if (type === TEXT_NODE) {
|
|
var fullText = childNode.textContent || '';
|
|
var textNode = document.createTextNode(fullText);
|
|
appendChildNode(textNode);
|
|
return measureText(textNode, fullText);
|
|
} // Not handle other type of content
|
|
// PS: This code should not be attached after react 16
|
|
|
|
|
|
return {
|
|
finished: false,
|
|
reactNode: null
|
|
};
|
|
}
|
|
|
|
childNodes.some(function (childNode, index) {
|
|
var _measureNode = measureNode(childNode, index),
|
|
finished = _measureNode.finished,
|
|
reactNode = _measureNode.reactNode;
|
|
|
|
if (reactNode) {
|
|
ellipsisChildren.push(reactNode);
|
|
}
|
|
|
|
return finished;
|
|
});
|
|
return {
|
|
content: ellipsisChildren,
|
|
text: ellipsisContainer.innerHTML,
|
|
ellipsis: true
|
|
};
|
|
};
|
|
|
|
exports["default"] = _default; |