util.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _vue = require("vue");
  7. var _css = require("../vc-util/Dom/css");
  8. // We only handle element & text node.
  9. const TEXT_NODE = 3;
  10. const COMMENT_NODE = 8;
  11. let ellipsisContainer;
  12. const wrapperStyle = {
  13. padding: 0,
  14. margin: 0,
  15. display: 'inline',
  16. lineHeight: 'inherit'
  17. };
  18. function resetDomStyles(target, origin) {
  19. target.setAttribute('aria-hidden', 'true');
  20. const originStyle = window.getComputedStyle(origin);
  21. const originCSS = (0, _css.styleToString)(originStyle);
  22. // Set shadow
  23. target.setAttribute('style', originCSS);
  24. target.style.position = 'fixed';
  25. target.style.left = '0';
  26. target.style.height = 'auto';
  27. target.style.minHeight = 'auto';
  28. target.style.maxHeight = 'auto';
  29. target.style.paddingTop = '0';
  30. target.style.paddingBottom = '0';
  31. target.style.borderTopWidth = '0';
  32. target.style.borderBottomWidth = '0';
  33. target.style.top = '-999999px';
  34. target.style.zIndex = '-1000';
  35. // clean up css overflow
  36. target.style.textOverflow = 'clip';
  37. target.style.whiteSpace = 'normal';
  38. target.style.webkitLineClamp = 'none';
  39. }
  40. function getRealLineHeight(originElement) {
  41. const heightContainer = document.createElement('div');
  42. resetDomStyles(heightContainer, originElement);
  43. heightContainer.appendChild(document.createTextNode('text'));
  44. document.body.appendChild(heightContainer);
  45. // The element real height is always less than multiple of line-height
  46. // Use getBoundingClientRect to get actual single row height of the element
  47. const realHeight = heightContainer.getBoundingClientRect().height;
  48. document.body.removeChild(heightContainer);
  49. return realHeight;
  50. }
  51. var _default = (originElement, option, content, fixedContent, ellipsisStr) => {
  52. if (!ellipsisContainer) {
  53. ellipsisContainer = document.createElement('div');
  54. ellipsisContainer.setAttribute('aria-hidden', 'true');
  55. document.body.appendChild(ellipsisContainer);
  56. }
  57. const {
  58. rows,
  59. suffix = ''
  60. } = option;
  61. const lineHeight = getRealLineHeight(originElement);
  62. const maxHeight = Math.round(lineHeight * rows * 100) / 100;
  63. resetDomStyles(ellipsisContainer, originElement);
  64. // Render in the fake container
  65. const vm = (0, _vue.createApp)({
  66. render() {
  67. return (0, _vue.createVNode)("div", {
  68. "style": wrapperStyle
  69. }, [(0, _vue.createVNode)("span", {
  70. "style": wrapperStyle
  71. }, [content, suffix]), (0, _vue.createVNode)("span", {
  72. "style": wrapperStyle
  73. }, [fixedContent])]);
  74. }
  75. });
  76. vm.mount(ellipsisContainer);
  77. // Check if ellipsis in measure div is height enough for content
  78. function inRange() {
  79. const currentHeight = Math.round(ellipsisContainer.getBoundingClientRect().height * 100) / 100;
  80. return currentHeight - 0.1 <= maxHeight; // -.1 for firefox
  81. }
  82. // Skip ellipsis if already match
  83. if (inRange()) {
  84. vm.unmount();
  85. return {
  86. content,
  87. text: ellipsisContainer.innerHTML,
  88. ellipsis: false
  89. };
  90. }
  91. const childNodes = Array.prototype.slice.apply(ellipsisContainer.childNodes[0].childNodes[0].cloneNode(true).childNodes).filter(_ref => {
  92. let {
  93. nodeType,
  94. data
  95. } = _ref;
  96. return nodeType !== COMMENT_NODE && data !== '';
  97. });
  98. const fixedNodes = Array.prototype.slice.apply(ellipsisContainer.childNodes[0].childNodes[1].cloneNode(true).childNodes);
  99. vm.unmount();
  100. // ========================= Find match ellipsis content =========================
  101. const ellipsisChildren = [];
  102. ellipsisContainer.innerHTML = '';
  103. // Create origin content holder
  104. const ellipsisContentHolder = document.createElement('span');
  105. ellipsisContainer.appendChild(ellipsisContentHolder);
  106. const ellipsisTextNode = document.createTextNode(ellipsisStr + suffix);
  107. ellipsisContentHolder.appendChild(ellipsisTextNode);
  108. fixedNodes.forEach(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. let startLoc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  118. let endLoc = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : fullText.length;
  119. let lastSuccessLoc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
  120. const midLoc = Math.floor((startLoc + endLoc) / 2);
  121. const currentText = fullText.slice(0, midLoc);
  122. textNode.textContent = currentText;
  123. if (startLoc >= endLoc - 1) {
  124. // Loop when step is small
  125. for (let step = endLoc; step >= startLoc; step -= 1) {
  126. const 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. const 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. const fullText = childNode.textContent || '';
  165. const 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(childNode => {
  176. const {
  177. finished,
  178. vNode
  179. } = measureNode(childNode);
  180. if (vNode) {
  181. ellipsisChildren.push(vNode);
  182. }
  183. return finished;
  184. });
  185. return {
  186. content: ellipsisChildren,
  187. text: ellipsisContainer.innerHTML,
  188. ellipsis: true
  189. };
  190. };
  191. exports.default = _default;