UNPKG

vxe-pc-ui

Version:
238 lines (207 loc) 7.53 kB
import { defineComponent, ref, h, PropType, watch, computed, reactive, onMounted, onBeforeUnmount } from 'vue' import XEUtils from 'xe-utils' import { getConfig, createEvent, useSize } from '../../ui' import type { TextEllipsisReactData, VxeTextEllipsisEmits, VxeTextEllipsisPropTypes, TextEllipsisMethods, TextEllipsisPrivateMethods, ValueOf, TextEllipsisPrivateRef, VxeTextEllipsisPrivateComputed, VxeTextEllipsisConstructor, VxeTextEllipsisPrivateMethods } from '../../../types' export default defineComponent({ name: 'VxeTextEllipsis', props: { content: [String, Number] as PropType<VxeTextEllipsisPropTypes.Content>, lineClamp: [String, Number] as PropType<VxeTextEllipsisPropTypes.LineClamp>, status: String as PropType<VxeTextEllipsisPropTypes.Status>, title: [String, Number] as PropType<VxeTextEllipsisPropTypes.Title>, loading: Boolean as PropType<VxeTextEllipsisPropTypes.Loading>, offsetLength: [String, Number] as PropType<VxeTextEllipsisPropTypes.OffsetLength>, size: { type: String as PropType<VxeTextEllipsisPropTypes.Size>, default: () => getConfig().textEllipsis.size || getConfig().size } }, emits: [ 'click' ] as VxeTextEllipsisEmits, setup (props, context) { const { emit } = context const xID = XEUtils.uniqueId() const { computeSize } = useSize(props) const refElem = ref<HTMLDivElement>() const realityElem = ref<HTMLDivElement>() const reactData = reactive<TextEllipsisReactData>({ resizeObserver: null, visibleLen: 0 }) const refMaps: TextEllipsisPrivateRef = { refElem } const computeTextLineClamp = computed(() => { return XEUtils.toNumber(props.lineClamp) }) const computeTextContent = computed(() => { return XEUtils.toValueString(props.content) }) const computeTextOffsetLength = computed(() => { return props.offsetLength ? XEUtils.toNumber(props.offsetLength) : 0 }) const computeVisibleContent = computed(() => { const { visibleLen } = reactData const textLineClamp = computeTextLineClamp.value const textContent = computeTextContent.value const textOffsetLength = computeTextOffsetLength.value if (textLineClamp > 1) { if (textContent.length > visibleLen) { return `${textContent.slice(0, Math.max(1, visibleLen - 3 + textOffsetLength))}...` } return textContent } return textContent }) const computeMaps: VxeTextEllipsisPrivateComputed = { } const $xeTextEllipsis = { xID, props, context, reactData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps } as unknown as VxeTextEllipsisConstructor & VxeTextEllipsisPrivateMethods const dispatchEvent = (type: ValueOf<VxeTextEllipsisEmits>, params: Record<string, any>, evnt: Event | null) => { emit(type, createEvent(evnt, { $textEllipsis: $xeTextEllipsis }, params)) } const calculateFont = (targetWidth: number) => { const el = refElem.value const ryEl = realityElem.value if (el && ryEl) { let fontSize = 12 try { fontSize = Math.max(10, XEUtils.toNumber(getComputedStyle(ryEl).fontSize)) } catch (e) {} const textContent = computeTextContent.value let currIndex = Math.floor((targetWidth) / fontSize) let currStr = textContent.slice(0, currIndex) ryEl.textContent = currStr reactData.visibleLen = currStr.length let maxCount = 0 while (targetWidth > ryEl.clientWidth && maxCount < 30) { maxCount++ const offsetIndex = Math.floor((targetWidth - ryEl.clientWidth) / fontSize) if (offsetIndex) { currIndex += offsetIndex currStr = textContent.slice(0, currIndex) ryEl.textContent = currStr reactData.visibleLen = currStr.length } else { break } } ryEl.textContent = '' ryEl.style.display = '' ryEl.style.position = '' ryEl.style.top = '' ryEl.style.left = '' } } const updateStyle = () => { const el = refElem.value const ryEl = realityElem.value const textContent = computeTextContent.value const textLineClamp = computeTextLineClamp.value if (el && ryEl) { const cWidth = el.clientWidth ryEl.style.display = 'block' ryEl.style.position = 'absolute' ryEl.style.top = '-3000px' ryEl.style.left = '-3000px' ryEl.textContent = textContent const sWidth = ryEl.offsetWidth const targetWidth = Math.floor(cWidth * textLineClamp) if (targetWidth > sWidth) { reactData.visibleLen = textContent.length } else { calculateFont(targetWidth) } } else { reactData.visibleLen = textContent.length } } const textEllipsisMethods: TextEllipsisMethods = { dispatchEvent } const clickEvent = () => { emit('click', {}) } const initObserver = () => { const { resizeObserver } = reactData const textLineClamp = computeTextLineClamp.value if (!resizeObserver) { const el = refElem.value if (el && textLineClamp > 1) { if (window.ResizeObserver) { const observerObj = new window.ResizeObserver(XEUtils.throttle(() => { updateStyle() }, 300, { leading: true, trailing: true })) observerObj.observe(el) reactData.resizeObserver = observerObj } } } } const textEllipsisPrivateMethods: TextEllipsisPrivateMethods = { } Object.assign($xeTextEllipsis, textEllipsisMethods, textEllipsisPrivateMethods) const renderVN = () => { const { loading, status, title } = props const vSize = computeSize.value const visibleContent = computeVisibleContent.value const textLineClamp = computeTextLineClamp.value return h('div', { ref: refElem, class: ['vxe-text-ellipsis', textLineClamp > 1 ? 'is--multi' : 'is--single', { [`size--${vSize}`]: vSize, [`theme--${status}`]: status, 'is--loading': loading }], title, onClick: clickEvent }, [ h('span', { ref: realityElem, class: 'vxe-text-ellipsis-reality' }), h('span', { class: 'vxe-text-ellipsis-content' }, visibleContent) ]) } watch(() => props.content, () => { updateStyle() }) watch(() => props.lineClamp, () => { initObserver() updateStyle() }) onMounted(() => { initObserver() updateStyle() }) onBeforeUnmount(() => { const { resizeObserver } = reactData const el = refElem.value const ryEl = realityElem.value if (ryEl) { ryEl.textContent = '' } if (resizeObserver) { if (el) { resizeObserver.unobserve(el) } resizeObserver.disconnect() reactData.resizeObserver = null } }) $xeTextEllipsis.renderVN = renderVN return $xeTextEllipsis }, render () { return this.renderVN() } })