index.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/libs/classNames'
  3. import styleToCssString from '../helpers/libs/styleToCssString'
  4. import runes2 from '../helpers/libs/runes2'
  5. import { pxToNumber } from '../helpers/shared/pxToNumber'
  6. import { useRef, useComputedStyle } from '../helpers/hooks/useDOM'
  7. function getSubString(chars, start, end) {
  8. return chars.slice(start, end).join('')
  9. }
  10. baseComponent({
  11. properties: {
  12. prefixCls: {
  13. type: String,
  14. value: 'wux-ellipsis',
  15. },
  16. content: {
  17. type: String,
  18. value: '',
  19. },
  20. direction: {
  21. type: String,
  22. value: 'end',
  23. },
  24. defaultExpanded: {
  25. type: Boolean,
  26. value: false,
  27. },
  28. expandText: {
  29. type: String,
  30. value: '',
  31. },
  32. collapseText: {
  33. type: String,
  34. value: '',
  35. },
  36. rows: {
  37. type: Number,
  38. value: 1,
  39. },
  40. },
  41. data: {
  42. ellipsised: {
  43. leading: '',
  44. tailing: '',
  45. },
  46. expanded: false,
  47. exceeded: false,
  48. innerText: '',
  49. end: -1,
  50. containerStyle: '',
  51. },
  52. observers: {
  53. ['prefixCls, content, direction, rows, expandText, collapseText'](...args) {
  54. const [
  55. prefixCls,
  56. content,
  57. direction,
  58. rows,
  59. expandText,
  60. collapseText,
  61. ] = args
  62. this.calcEllipsised({
  63. prefixCls,
  64. content,
  65. direction,
  66. rows,
  67. expandText,
  68. collapseText,
  69. })
  70. },
  71. },
  72. computed: {
  73. classes: ['prefixCls', function(prefixCls) {
  74. const wrap = classNames(prefixCls)
  75. const container = classNames(prefixCls, [`${prefixCls}--container`])
  76. const expanded = `${prefixCls}__expanded`
  77. const collapsed = `${prefixCls}__collapsed`
  78. return {
  79. wrap,
  80. container,
  81. expanded,
  82. collapsed,
  83. }
  84. }],
  85. },
  86. methods: {
  87. /**
  88. * 点击事件
  89. */
  90. onTap() {
  91. this.triggerEvent('click')
  92. },
  93. /**
  94. * 展开事件
  95. */
  96. setExpanded(e) {
  97. const { expanded } = e.target.dataset
  98. this.setDataPromise({
  99. expanded: expanded === '1',
  100. })
  101. },
  102. /**
  103. * 计算省略值
  104. */
  105. calcEllipsised(props) {
  106. const chars = runes2(props.content)
  107. const end = props.content.length
  108. const middle = Math.floor((0 + end) / 2)
  109. const defaultState = {
  110. innerText: props.content,
  111. chars,
  112. end,
  113. middle,
  114. containerStyle: '',
  115. }
  116. const setExceeded = (exceeded) => {
  117. if (this.data.exceeded !== exceeded) {
  118. this.setDataPromise({
  119. exceeded,
  120. })
  121. }
  122. }
  123. const setEllipsised = (ellipsised) => {
  124. if (this.data.ellipsised !== ellipsised) {
  125. this.setDataPromise({
  126. ellipsised,
  127. })
  128. }
  129. }
  130. this.getRootRef()
  131. .then((root) => (
  132. this.setDataPromise({
  133. ...defaultState,
  134. containerStyle: styleToCssString({
  135. width: root.width,
  136. wordBreak: root.wordBreak,
  137. }),
  138. removeContainer: false,
  139. }).then(() => (
  140. Promise.all([
  141. Promise.resolve(root),
  142. this.getContainerRef(),
  143. ])
  144. ))
  145. ))
  146. .then(([root, container]) => {
  147. if (container.clientHeight <= root.maxHeight) {
  148. setExceeded(false)
  149. } else {
  150. setExceeded(true)
  151. if (props.direction === 'middle') {
  152. this.checkMiddle([0, middle], [middle, end], this.data).then(setEllipsised)
  153. } else {
  154. this.check(0, end, this.data).then(setEllipsised)
  155. }
  156. }
  157. })
  158. },
  159. check(left, right, props) {
  160. const chars = props.chars
  161. const end = props.content.length
  162. const actionText = props.expanded ? props.collapseText : props.expandText
  163. if (right - left <= 1) {
  164. if (props.direction === 'end') {
  165. return Promise.resolve({
  166. leading: getSubString(chars, 0, left) + '...',
  167. })
  168. } else {
  169. return Promise.resolve({
  170. tailing: '...' + getSubString(chars, right, end),
  171. })
  172. }
  173. }
  174. const middle = Math.round((left + right) / 2)
  175. const innerText = props.direction === 'end'
  176. ? getSubString(chars, 0, middle) + '...' + actionText
  177. : actionText + '...' + getSubString(chars, middle, end)
  178. return this.setDataPromise({ innerText })
  179. .then(() => (
  180. Promise.all([
  181. this.getRootRef(),
  182. this.getContainerRef(),
  183. ])
  184. ))
  185. .then(([root, container]) => {
  186. if (container.clientHeight <= root.maxHeight) {
  187. if (props.direction === 'end') {
  188. return this.check(middle, right, props)
  189. } else {
  190. return this.check(left, middle, props)
  191. }
  192. } else {
  193. if (props.direction === 'end') {
  194. return this.check(left, middle, props)
  195. } else {
  196. return this.check(middle, right, props)
  197. }
  198. }
  199. })
  200. },
  201. checkMiddle(
  202. leftPart,
  203. rightPart,
  204. props
  205. ) {
  206. const chars = props.chars
  207. const end = props.content.length
  208. const actionText = props.expanded ? props.collapseText : props.expandText
  209. if (
  210. leftPart[1] - leftPart[0] <= 1 &&
  211. rightPart[1] - rightPart[0] <= 1
  212. ) {
  213. return Promise.resolve({
  214. leading: getSubString(chars, 0, leftPart[0]) + '...',
  215. tailing: '...' + getSubString(chars, rightPart[1], end),
  216. })
  217. }
  218. const leftPartMiddle = Math.floor((leftPart[0] + leftPart[1]) / 2)
  219. const rightPartMiddle = Math.ceil((rightPart[0] + rightPart[1]) / 2)
  220. const innerText =
  221. getSubString(chars, 0, leftPartMiddle) +
  222. '...' +
  223. actionText +
  224. '...' +
  225. getSubString(chars, rightPartMiddle, end)
  226. return this.setDataPromise({ innerText })
  227. .then(() => (
  228. Promise.all([
  229. this.getRootRef(),
  230. this.getContainerRef(),
  231. ])
  232. ))
  233. .then(([root, container]) => {
  234. if (container.clientHeight <= root.maxHeight) {
  235. return this.checkMiddle(
  236. [leftPartMiddle, leftPart[1]],
  237. [rightPart[0], rightPartMiddle],
  238. props,
  239. )
  240. } else {
  241. return this.checkMiddle(
  242. [leftPart[0], leftPartMiddle],
  243. [rightPartMiddle, rightPart[1]],
  244. props,
  245. )
  246. }
  247. })
  248. },
  249. setDataPromise(state) {
  250. return new Promise((resolve) => {
  251. this.setData(state, resolve)
  252. })
  253. },
  254. getContainerRef() {
  255. const { prefixCls } = this.data
  256. return useRef(`.${prefixCls}--container`, this)
  257. },
  258. getRootRef() {
  259. const { prefixCls, rows } = this.data
  260. const computedStyle = [
  261. 'width',
  262. 'wordBreak',
  263. 'lineHeight',
  264. 'paddingTop',
  265. 'paddingBottom',
  266. ]
  267. return useComputedStyle(`.${prefixCls}`, computedStyle, this)
  268. .then((originStyle) => {
  269. const width = pxToNumber(originStyle.width)
  270. const lineHeight = pxToNumber(originStyle.lineHeight)
  271. const maxHeight = Math.floor(
  272. lineHeight * (rows + 0.5) +
  273. pxToNumber(originStyle.paddingTop) +
  274. pxToNumber(originStyle.paddingBottom)
  275. )
  276. return {
  277. width,
  278. wordBreak: originStyle.wordBreak,
  279. maxHeight,
  280. }
  281. })
  282. },
  283. },
  284. attached() {
  285. const props = this.data
  286. const expanded = props.defaultExpanded
  287. this.setDataPromise({ expanded })
  288. this.calcEllipsised({ ...props, expanded })
  289. },
  290. })