util.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { createVNode as _createVNode } from "vue";
  2. import { createApp } from 'vue';
  3. // We only handle element & text node.
  4. var TEXT_NODE = 3;
  5. var COMMENT_NODE = 8;
  6. var ellipsisContainer;
  7. var wrapperStyle = {
  8. padding: 0,
  9. margin: 0,
  10. display: 'inline',
  11. lineHeight: 'inherit'
  12. };
  13. function styleToString(style) {
  14. // There are some different behavior between Firefox & Chrome.
  15. // We have to handle this ourself.
  16. var styleNames = Array.prototype.slice.apply(style);
  17. return styleNames.map(function (name) {
  18. return "".concat(name, ": ").concat(style.getPropertyValue(name), ";");
  19. }).join('');
  20. }
  21. function resetDomStyles(target, origin) {
  22. target.setAttribute('aria-hidden', 'true');
  23. var originStyle = window.getComputedStyle(origin);
  24. var originCSS = styleToString(originStyle);
  25. // Set shadow
  26. target.setAttribute('style', originCSS);
  27. target.style.position = 'fixed';
  28. target.style.left = '0';
  29. target.style.height = 'auto';
  30. target.style.minHeight = 'auto';
  31. target.style.maxHeight = 'auto';
  32. target.style.paddingTop = '0';
  33. target.style.paddingBottom = '0';
  34. target.style.borderTopWidth = '0';
  35. target.style.borderBottomWidth = '0';
  36. target.style.top = '-999999px';
  37. target.style.zIndex = '-1000';
  38. // clean up css overflow
  39. target.style.textOverflow = 'clip';
  40. target.style.whiteSpace = 'normal';
  41. target.style.webkitLineClamp = 'none';
  42. }
  43. function getRealLineHeight(originElement) {
  44. var heightContainer = document.createElement('div');
  45. resetDomStyles(heightContainer, originElement);
  46. heightContainer.appendChild(document.createTextNode('text'));
  47. document.body.appendChild(heightContainer);
  48. // The element real height is always less than multiple of line-height
  49. // Use getBoundingClientRect to get actual single row height of the element
  50. var realHeight = heightContainer.getBoundingClientRect().height;
  51. document.body.removeChild(heightContainer);
  52. return realHeight;
  53. }
  54. export default (function (originElement, option, content, fixedContent, ellipsisStr) {
  55. if (!ellipsisContainer) {
  56. ellipsisContainer = document.createElement('div');
  57. ellipsisContainer.setAttribute('aria-hidden', 'true');
  58. document.body.appendChild(ellipsisContainer);
  59. }
  60. var rows = option.rows,
  61. _option$suffix = option.suffix,
  62. suffix = _option$suffix === void 0 ? '' : _option$suffix;
  63. var lineHeight = getRealLineHeight(originElement);
  64. var maxHeight = Math.round(lineHeight * rows * 100) / 100;
  65. resetDomStyles(ellipsisContainer, originElement);
  66. // Render in the fake container
  67. var vm = createApp({
  68. render: function render() {
  69. return _createVNode("div", {
  70. "style": wrapperStyle
  71. }, [_createVNode("span", {
  72. "style": wrapperStyle
  73. }, [content, suffix]), _createVNode("span", {
  74. "style": wrapperStyle
  75. }, [fixedContent])]);
  76. }
  77. });
  78. vm.mount(ellipsisContainer);
  79. // Check if ellipsis in measure div is height enough for content
  80. function inRange() {
  81. var currentHeight = Math.round(ellipsisContainer.getBoundingClientRect().height * 100) / 100;
  82. return currentHeight - 0.1 <= maxHeight; // -.1 for firefox
  83. }
  84. // Skip ellipsis if already match
  85. if (inRange()) {
  86. vm.unmount();
  87. return {
  88. content: content,
  89. text: ellipsisContainer.innerHTML,
  90. ellipsis: false
  91. };
  92. }
  93. var childNodes = Array.prototype.slice.apply(ellipsisContainer.childNodes[0].childNodes[0].cloneNode(true).childNodes).filter(function (_ref) {
  94. var nodeType = _ref.nodeType,
  95. data = _ref.data;
  96. return nodeType !== COMMENT_NODE && data !== '';
  97. });
  98. var fixedNodes = Array.prototype.slice.apply(ellipsisContainer.childNodes[0].childNodes[1].cloneNode(true).childNodes);
  99. vm.unmount();
  100. // ========================= Find match ellipsis content =========================
  101. var ellipsisChildren = [];
  102. ellipsisContainer.innerHTML = '';
  103. // Create origin content holder
  104. var ellipsisContentHolder = document.createElement('span');
  105. ellipsisContainer.appendChild(ellipsisContentHolder);
  106. var ellipsisTextNode = document.createTextNode(ellipsisStr + suffix);
  107. ellipsisContentHolder.appendChild(ellipsisTextNode);
  108. fixedNodes.forEach(function (childNode) {
  109. ellipsisContainer.appendChild(childNode);
  110. });
  111. // Append before fixed nodes
  112. function appendChildNode(node) {
  113. ellipsisContentHolder.insertBefore(node, ellipsisTextNode);
  114. }
  115. // Get maximum text
  116. function measureText(textNode, fullText) {
  117. var startLoc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  118. var endLoc = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : fullText.length;
  119. var lastSuccessLoc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
  120. var midLoc = Math.floor((startLoc + endLoc) / 2);
  121. var currentText = fullText.slice(0, midLoc);
  122. textNode.textContent = currentText;
  123. if (startLoc >= endLoc - 1) {
  124. // Loop when step is small
  125. for (var step = endLoc; step >= startLoc; step -= 1) {
  126. var currentStepText = fullText.slice(0, step);
  127. textNode.textContent = currentStepText;
  128. if (inRange() || !currentStepText) {
  129. return step === fullText.length ? {
  130. finished: false,
  131. vNode: fullText
  132. } : {
  133. finished: true,
  134. vNode: currentStepText
  135. };
  136. }
  137. }
  138. }
  139. if (inRange()) {
  140. return measureText(textNode, fullText, midLoc, endLoc, midLoc);
  141. }
  142. return measureText(textNode, fullText, startLoc, midLoc, lastSuccessLoc);
  143. }
  144. function measureNode(childNode) {
  145. var type = childNode.nodeType;
  146. // console.log('type', type);
  147. // if (type === ELEMENT_NODE) {
  148. // // We don't split element, it will keep if whole element can be displayed.
  149. // appendChildNode(childNode);
  150. // if (inRange()) {
  151. // return {
  152. // finished: false,
  153. // vNode: contentList[index],
  154. // };
  155. // }
  156. // // Clean up if can not pull in
  157. // ellipsisContentHolder.removeChild(childNode);
  158. // return {
  159. // finished: true,
  160. // vNode: null,
  161. // };
  162. // }
  163. if (type === TEXT_NODE) {
  164. var fullText = childNode.textContent || '';
  165. var textNode = document.createTextNode(fullText);
  166. appendChildNode(textNode);
  167. return measureText(textNode, fullText);
  168. }
  169. // Not handle other type of content
  170. return {
  171. finished: false,
  172. vNode: null
  173. };
  174. }
  175. childNodes.some(function (childNode) {
  176. var _measureNode = measureNode(childNode),
  177. finished = _measureNode.finished,
  178. vNode = _measureNode.vNode;
  179. if (vNode) {
  180. ellipsisChildren.push(vNode);
  181. }
  182. return finished;
  183. });
  184. return {
  185. content: ellipsisChildren,
  186. text: ellipsisContainer.innerHTML,
  187. ellipsis: true
  188. };
  189. });