From 3b9c72d09e56700258f70434efdde488a07bac72 Mon Sep 17 00:00:00 2001 From: Gavin Date: Sat, 8 Jul 2023 17:03:37 +0800 Subject: [PATCH] feat(TextEllipsis): add position prop (#12058) * feat(TextEllipsis): add position prop * docs: update docs * chore: optimize code --- packages/vant/src/text-ellipsis/README.md | 61 ++++++++-- .../vant/src/text-ellipsis/README.zh-CN.md | 63 ++++++++-- .../vant/src/text-ellipsis/TextEllipsis.tsx | 115 +++++++++++++++--- .../vant/src/text-ellipsis/demo/index.vue | 28 +++++ .../test/__snapshots__/demo-ssr.spec.ts.snap | 13 ++ .../test/__snapshots__/demo.spec.ts.snap | 24 +++- 6 files changed, 268 insertions(+), 36 deletions(-) diff --git a/packages/vant/src/text-ellipsis/README.md b/packages/vant/src/text-ellipsis/README.md index 5f1592914..aa0bd6aeb 100644 --- a/packages/vant/src/text-ellipsis/README.md +++ b/packages/vant/src/text-ellipsis/README.md @@ -81,17 +81,64 @@ export default { }; ``` +### Custom Collapse Position + +- Collapse the beginning part of the content: + +```html + +``` + +```js +export default { + setup() { + const text = + "That day, I turned twenty-one. In the golden age of my life, I was full of dreams. I wanted to love, to eat, and to instantly transform into one of these clouds, part alight, part darkened. It was only later that I understood life is but a slow, drawn-out process of getting your balls crushed. Day by day, you get older. Day by day, your dreams fade. In the end you are no different from a crushed ox. But I hadn't foreseen any of it on my twenty-first birthday. I thought I would be vigorous forever, and that nothing could ever crush me."; + return { text }; + }, +}; +``` + +- Collapse the middle part of the content: + +```html + +``` + +```js +export default { + setup() { + const text = + "That day, I turned twenty-one. In the golden age of my life, I was full of dreams. I wanted to love, to eat, and to instantly transform into one of these clouds, part alight, part darkened. It was only later that I understood life is but a slow, drawn-out process of getting your balls crushed. Day by day, you get older. Day by day, your dreams fade. In the end you are no different from a crushed ox. But I hadn't foreseen any of it on my twenty-first birthday. I thought I would be vigorous forever, and that nothing could ever crush me."; + return { text }; + }, +}; +``` + ## API ### Props -| Attribute | Description | Type | Default | -| ------------- | ------------------------ | ------------------ | ------- | -| rows | Number of rows displayed | _number \| string_ | `1` | -| content | The text displayed | _string_ | - | -| expand-text | Expand operation text | _string_ | - | -| collapse-text | Collapse operation text | _string_ | - | -| dots `v4.2.0` | Text content of ellipsis | _string_ | `'...'` | +| Attribute | Description | Type | Default | +| --- | --- | --- | --- | +| rows | Number of rows displayed | _number \| string_ | `1` | +| content | The text displayed | _string_ | - | +| expand-text | Expand operation text | _string_ | - | +| collapse-text | Collapse operation text | _string_ | - | +| dots `v4.2.0` | Text content of ellipsis | _string_ | `'...'` | +| position `v4.6.2` | Can be set to `start` `middle` | _string_ | `'end'` | ### Events diff --git a/packages/vant/src/text-ellipsis/README.zh-CN.md b/packages/vant/src/text-ellipsis/README.zh-CN.md index 9bfc2f038..44b91b2dc 100644 --- a/packages/vant/src/text-ellipsis/README.zh-CN.md +++ b/packages/vant/src/text-ellipsis/README.zh-CN.md @@ -76,17 +76,66 @@ export default { }; ``` +### 自定义省略位置 + +通过设置 `position` 控制省略位置。 + +- 头部省略: + +```html + +``` + +```js +export default { + setup() { + const text = + '那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。'; + return { text }; + }, +}; +``` + +- 中部省略: + +```html + +``` + +```js +export default { + setup() { + const text = + '那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。'; + return { text }; + }, +}; +``` + ## API ### Props -| 参数 | 说明 | 类型 | 默认值 | -| ------------- | ---------------- | ------------------ | ------- | -| rows | 展示的行数 | _number \| string_ | `1` | -| content | 需要展示的文本 | _string_ | - | -| expand-text | 展开操作的文案 | _string_ | - | -| collapse-text | 收起操作的文案 | _string_ | - | -| dots `v4.2.0` | 省略号的文本内容 | _string_ | `'...'` | +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| rows | 展示的行数 | _number \| string_ | `1` | +| content | 需要展示的文本 | _string_ | - | +| expand-text | 展开操作的文案 | _string_ | - | +| collapse-text | 收起操作的文案 | _string_ | - | +| dots `v4.2.0` | 省略号的文本内容 | _string_ | `'...'` | +| position `v4.6.2` | 省略位置,可选值为 `start` `middle` | _string_ | `'end'` | ### Events diff --git a/packages/vant/src/text-ellipsis/TextEllipsis.tsx b/packages/vant/src/text-ellipsis/TextEllipsis.tsx index efdd56a0d..82f09a977 100644 --- a/packages/vant/src/text-ellipsis/TextEllipsis.tsx +++ b/packages/vant/src/text-ellipsis/TextEllipsis.tsx @@ -1,6 +1,7 @@ import { ref, watch, + computed, onMounted, defineComponent, type ExtractPropTypes, @@ -20,6 +21,7 @@ export const textEllipsisProps = { content: makeStringProp(''), expandText: makeStringProp(''), collapseText: makeStringProp(''), + position: makeStringProp('end'), }; export type TextEllipsisProps = ExtractPropTypes; @@ -37,6 +39,10 @@ export default defineComponent({ const hasAction = ref(false); const root = ref(); + const actionText = computed(() => + expanded.value ? props.expandText : props.collapseText + ); + const pxToNum = (value: string | null) => { if (!value) return 0; const match = value.match(/^\d*(\.\d*)?/); @@ -70,33 +76,104 @@ export default defineComponent({ container: HTMLDivElement, maxHeight: number ) => { - const { dots, content, expandText } = props; + const { content, position, dots } = props; + const end = content.length; - let left = 0; - let right = content.length; - let res = -1; + const calcEllipse = () => { + // calculate the former or later content + const tail = (left: number, right: number): string => { + if (right - left <= 1) { + if (position === 'end') { + return content.slice(0, left) + dots; + } + return dots + content.slice(right, end); + } - while (left <= right) { - const mid = Math.floor((left + right) / 2); - container.innerText = content.slice(0, mid) + dots + expandText; - if (container.offsetHeight <= maxHeight) { - left = mid + 1; - res = mid; - } else { - right = mid - 1; + const middle = Math.round((left + right) >> 1); + + // Set the interception location + if (position === 'end') { + container.innerText = + content.slice(0, middle) + dots + actionText.value; + } else { + container.innerText = + dots + content.slice(middle, end) + actionText.value; + } + + // The height after interception still does not match the rquired height + if (container.offsetHeight > maxHeight) { + if (position === 'end') { + return tail(left, middle); + } + return tail(middle, right); + } + + if (position === 'end') { + return tail(middle, right); + } + + return tail(left, middle); + }; + + container.innerText = tail(0, end); + }; + + const middleTail = ( + leftPart: [number, number], + rightPart: [number, number] + ): string => { + if ( + leftPart[1] - leftPart[0] <= 1 && + rightPart[1] - rightPart[0] <= 1 + ) { + return ( + content.slice(0, leftPart[1]) + + dots + + dots + + content.slice(rightPart[1], end) + ); } - } - return content.slice(0, res) + dots; + + const leftMiddle = Math.floor((leftPart[0] + leftPart[1]) >> 1); + const rightMiddle = Math.ceil((rightPart[0] + rightPart[1]) >> 1); + + container.innerText = + props.content.slice(0, leftMiddle) + + props.dots + + actionText.value + + props.dots + + props.content.slice(rightMiddle, end); + + if (container.offsetHeight >= maxHeight) { + return middleTail( + [leftPart[0], leftMiddle], + [rightMiddle, rightPart[1]] + ); + } + + return middleTail( + [leftMiddle, leftPart[1]], + [rightPart[0], rightMiddle] + ); + }; + + const middle = (0 + end) >> 1; + props.position === 'middle' + ? (container.innerText = middleTail([0, middle], [middle, end])) + : calcEllipse(); + return container.innerText; }; + // Calculate the interceptional text const container = cloneContainer(); if (!container) return; - const { paddingBottom, paddingTop, lineHeight } = container.style; - const maxHeight = + const maxHeight = Math.ceil( (Number(props.rows) + 0.5) * pxToNum(lineHeight) + - pxToNum(paddingTop) + - pxToNum(paddingBottom); + pxToNum(paddingTop) + + pxToNum(paddingBottom) + ); + if (maxHeight < container.offsetHeight) { hasAction.value = true; text.value = calcEllipsisText(container, maxHeight); @@ -121,7 +198,7 @@ export default defineComponent({ onMounted(calcEllipsised); - watch(() => [props.content, props.rows], calcEllipsised); + watch(() => [props.content, props.rows, props.position], calcEllipsised); useEventListener('resize', calcEllipsised); diff --git a/packages/vant/src/text-ellipsis/demo/index.vue b/packages/vant/src/text-ellipsis/demo/index.vue index 9cd4a1873..12fb62b0b 100644 --- a/packages/vant/src/text-ellipsis/demo/index.vue +++ b/packages/vant/src/text-ellipsis/demo/index.vue @@ -13,6 +13,9 @@ const t = useTranslate({ collapseText: '收起', expandCollapse: '展开/收起', customRows: '自定义展示行数', + collapsePosition: '自定义省略位置', + collapseStart: '头部省略', + collapseMiddle: '中部省略', }, 'en-US': { text1: @@ -25,6 +28,9 @@ const t = useTranslate({ collapseText: 'collapse', expandCollapse: 'Expand/Collapse', customRows: 'Customize Rows', + collapsePosition: 'Custom Collapse Position', + collapseStart: 'Head Area Collapse Position', + collapseMiddle: 'Middle Area Collapse Position', }, }); @@ -50,6 +56,28 @@ const t = useTranslate({ :collapse-text="t('collapseText')" /> + + + + + + + + + +