diff --git a/.github/workflows/doc-site.yml b/.github/workflows/doc-site.yml index b91fb02193..772ede7fec 100644 --- a/.github/workflows/doc-site.yml +++ b/.github/workflows/doc-site.yml @@ -44,13 +44,14 @@ jobs: run: | pnpm run build-doc - - name: Deploy to Surge - uses: dswistowski/surge-sh-action@v1 - with: - domain: 'antd-mobile.surge.sh' - project: './dist' - login: ${{ secrets.SURGE_LOGIN }} - token: ${{ secrets.SURGE_TOKEN }} + # Note: We miss the url permission for surge.sh, so we can't use it now. + # - name: Deploy to Surge + # uses: dswistowski/surge-sh-action@v1 + # with: + # domain: 'antd-mobile.surge.sh' + # project: './dist' + # login: ${{ secrets.SURGE_LOGIN }} + # token: ${{ secrets.SURGE_TOKEN }} - name: Deploy to Surge (with SHA) uses: dswistowski/surge-sh-action@v1 diff --git a/docs/components/components/Main/MainSection/index.tsx b/docs/components/components/Main/MainSection/index.tsx index 1cdc5d283f..b45b7993de 100644 --- a/docs/components/components/Main/MainSection/index.tsx +++ b/docs/components/components/Main/MainSection/index.tsx @@ -2,6 +2,7 @@ import { Button } from 'antd-mobile' import React, { useEffect, useState } from 'react' import Lottie from 'react-lottie' import { useTrans } from '../../../../hooks/useTrans' +import { openUrl } from '../../../../utils' import styles from './index.local.less' export default (props: { isWidthScreen: boolean }) => { @@ -39,7 +40,11 @@ export default (props: { isWidthScreen: boolean }) => { color='primary' shape='rounded' className={styles.buttonLeft} - href={trans('/guide/quick-start', '/zh/guide/quick-start')} + onClick={() => + openUrl({ + href: trans('/guide/quick-start', '/zh/guide/quick-start'), + }) + } > {trans('Get Start', '开始使用')} @@ -47,7 +52,11 @@ export default (props: { isWidthScreen: boolean }) => { color='primary' shape='rounded' className={styles.buttonRight} - href={trans('/components', '/zh/components')} + onClick={() => + openUrl({ + href: trans('/components', '/zh/components'), + }) + } > {trans('Preview Online', '在线体验')} diff --git a/docs/components/components/Main/index.tsx b/docs/components/components/Main/index.tsx index 72858027d7..9187d6974e 100644 --- a/docs/components/components/Main/index.tsx +++ b/docs/components/components/Main/index.tsx @@ -4,6 +4,7 @@ import { Button, Card } from 'antd-mobile' import React, { useEffect, useRef, useState } from 'react' import Lottie from 'react-lottie' import { useTrans } from '../../../hooks/useTrans' +import { openUrl } from '../../../utils' import MainSection from './MainSection' import { getGuides, @@ -99,10 +100,12 @@ export default () => { diff --git a/docs/utils/index.ts b/docs/utils/index.ts new file mode 100644 index 0000000000..475892cdda --- /dev/null +++ b/docs/utils/index.ts @@ -0,0 +1,15 @@ +export const openUrl = ({ + href, + target, +}: { + href: string + target?: string +}) => { + switch (target) { + case '_blank': + window.open(href, target) + break + default: + window.location.href = href + } +} diff --git a/package.json b/package.json index 285a806a52..737d913bde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "antd-mobile", - "version": "5.38.0", + "version": "5.38.1", "homepage": "https://github.com/ant-design/ant-design-mobile#readme", "bugs": { "url": "https://github.com/ant-design/ant-design-mobile/issues" diff --git a/src/components/calendar-picker-view/calendar-picker-view.en.md b/src/components/calendar-picker-view/calendar-picker-view.en.md index 101399cd95..606192c3e5 100644 --- a/src/components/calendar-picker-view/calendar-picker-view.en.md +++ b/src/components/calendar-picker-view/calendar-picker-view.en.md @@ -21,8 +21,8 @@ Only the simplest content area is shown here, and other more usages can be consu | max | Maximum value of a selectable range. | `Date` | - | | min | Minimum value of a selectable range. | `Date` | - | - | | onChange | Trigger when selected date changes. | `(val: Date \| null) => void` when selection mode is "single". `(val: [Date, Date] \| null) => void` when selection mode is "range". | - | -| renderTop | The top information of date render function. | `(date: Date) => ReactNode \| null \| undefined` | - | -| renderBottom | The bottom information of date render function. | `(date: Date) => ReactNode \| null \| undefined` | - | +| renderTop | The top information of date render function. | `((date: Date) => ReactNode \| null \| undefined) \| false` | - | `false`: 5.38.0 | +| renderBottom | The bottom information of date render function. | `((date: Date) => ReactNode \| null \| undefined) \| false` | - | `false`: 5.38.0 | | selectionMode | The selection mode. Disable selection when this prop is not set. | `'single' \| 'range'` | - | | shouldDisableDate | Set whether the date is disable selection. The min and max Settings are ignored | `(date: Date) => boolean` | - | | title | The title of calendar | `React.ReactNode` | `Date selection` | diff --git a/src/components/calendar-picker-view/calendar-picker-view.tsx b/src/components/calendar-picker-view/calendar-picker-view.tsx index f08621e975..e3eda2bc82 100644 --- a/src/components/calendar-picker-view/calendar-picker-view.tsx +++ b/src/components/calendar-picker-view/calendar-picker-view.tsx @@ -41,13 +41,15 @@ export type CalendarPickerViewRef = { getDateRange: () => DateRange } +export type CalendarPickerViewColumRender = (date: Date) => ReactNode + export type CalendarPickerViewProps = { title?: React.ReactNode | false confirmText?: string weekStartsOn?: 'Monday' | 'Sunday' - renderTop?: (date: Date) => React.ReactNode - renderDate?: (date: Date) => React.ReactNode - renderBottom?: (date: Date) => React.ReactNode + renderTop?: CalendarPickerViewColumRender | false + renderDate?: CalendarPickerViewColumRender + renderBottom?: CalendarPickerViewColumRender | false allowClear?: boolean max?: Date min?: Date @@ -269,26 +271,45 @@ export const CalendarPickerView = forwardRef< (minDay && d.isBefore(minDay, 'day')) const renderTop = () => { + if (props.renderTop === false) return null + + const contentWrapper = (content: ReactNode) => ( +
{content}
+ ) + const top = props.renderTop?.(d.toDate()) if (top) { - return top + return contentWrapper(top) } if (props.selectionMode === 'range') { if (isBegin) { - return locale.Calendar.start + return contentWrapper(locale.Calendar.start) } if (isEnd) { - return locale.Calendar.end + return contentWrapper(locale.Calendar.end) } } if (d.isSame(today, 'day') && !isSelect) { - return locale.Calendar.today + return contentWrapper(locale.Calendar.today) } + + return contentWrapper(null) } + + const renderBottom = () => { + if (props.renderBottom === false) return null + + return ( +
+ {props.renderBottom?.(d.toDate())} +
+ ) + } + return (
-
- {renderTop()} -
+ {renderTop()}
{props.renderDate ? props.renderDate(d.toDate()) : d.date()}
-
- {props.renderBottom?.(d.toDate())} -
+ {renderBottom()}
) })} diff --git a/src/components/calendar-picker-view/calendar-picker-view.zh.md b/src/components/calendar-picker-view/calendar-picker-view.zh.md index 69a5d06446..b9bd925d51 100644 --- a/src/components/calendar-picker-view/calendar-picker-view.zh.md +++ b/src/components/calendar-picker-view/calendar-picker-view.zh.md @@ -21,8 +21,8 @@ CalendarPickerView 是 [CalendarPicker](/zh/components/calendar-picker) 的内 | max | 可选择范围的最大值 | `Date` | - | | min | 可选择范围的最小值 | `Date` | - | | onChange | 选择日期变化时触发 | 单选模式下为 `(val: Date \| null) => void`,多选模式下为 `(val: [Date, Date] \| null) => void` | - | -| renderTop | 日期顶部信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | -| renderBottom | 日期底部信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | +| renderTop | 日期顶部信息的渲染函数 | `((date: Date) => ReactNode \| null \| undefined) \| false` | - | `false`: 5.38.0 | +| renderBottom | 日期底部信息的渲染函数 | `((date: Date) => ReactNode \| null \| undefined) \| false` | - | `false`: 5.38.0 | | selectionMode | 选择模式,不设置的话表示不支持选择 | `'single' \| 'range'` | - | | shouldDisableDate | 判断日期是否可选,使用后会忽略 min 和 max 设置 | `(date: Date) => boolean` | - | | title | 日期选择器的标题 | `React.ReactNode` | `日期选择` | diff --git a/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx b/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx index cf4ae4b88d..8d442e641f 100644 --- a/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx +++ b/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx @@ -172,6 +172,18 @@ describe('Calendar', () => { expect(document.querySelector(`.${classPrefix}-header`)).toBeNull() }) + test('renderTop hidden', () => { + render() + + expect(document.querySelector(`.${classPrefix}-cell-top`)).toBeNull() + }) + + test('renderBottom hidden', () => { + render() + + expect(document.querySelector(`.${classPrefix}-cell-bottom`)).toBeNull() + }) + test('not fill empty cells if unnecessary', () => { const { container } = render( = p => { const props = mergeProps(defaultProps, p) - const rootRef = useRef(null) - const expandElRef = useRef(null) - const collapseElRef = useRef(null) - - const [ellipsised, setEllipsised] = useState({}) - const [expanded, setExpanded] = useState(props.defaultExpanded) - const [exceeded, setExceeded] = useState(false) - - const chars = useMemo(() => runes(props.content), [props.content]) - function getSubString(start: number, end: number) { - return chars.slice(start, end).join('') - } - - function calcEllipsised() { - const root = rootRef.current - if (!root) return - - const originDisplay = root.style.display - root.style.display = 'block' - - const originStyle = window.getComputedStyle(root) - const container = document.createElement('div') - - const styleNames: string[] = Array.prototype.slice.apply(originStyle) - styleNames.forEach(name => { - container.style.setProperty(name, originStyle.getPropertyValue(name)) - }) - - root.style.display = originDisplay - - container.style.height = 'auto' - container.style.minHeight = 'auto' - container.style.maxHeight = 'auto' - container.style.textOverflow = 'clip' - container.style.webkitLineClamp = 'unset' - container.style.display = 'block' - - const lineHeight = pxToNumber(originStyle.lineHeight) - const maxHeight = Math.floor( - lineHeight * (props.rows + 0.5) + - pxToNumber(originStyle.paddingTop) + - pxToNumber(originStyle.paddingBottom) - ) - - container.innerText = props.content - document.body.appendChild(container) - - if (container.offsetHeight <= maxHeight) { - setExceeded(false) - } else { - setExceeded(true) - const end = props.content.length - - const collapseEl = - typeof props.collapseText === 'string' - ? props.collapseText - : collapseElRef.current?.innerHTML - const expandEl = - typeof props.expandText === 'string' - ? props.expandText - : expandElRef.current?.innerHTML - const actionText = expanded ? collapseEl : expandEl - - function check(left: number, right: number): EllipsisedValue { - if (right - left <= 1) { - if (props.direction === 'end') { - return { - leading: getSubString(0, left) + '...', - } - } else { - return { - tailing: '...' + getSubString(right, end), - } - } - } - const middle = Math.round((left + right) / 2) - if (props.direction === 'end') { - container.innerHTML = getSubString(0, middle) + '...' + actionText - } else { - container.innerHTML = actionText + '...' + getSubString(middle, end) - } - - if (container.offsetHeight <= maxHeight) { - if (props.direction === 'end') { - return check(middle, right) - } else { - return check(left, middle) - } - } else { - if (props.direction === 'end') { - return check(left, middle) - } else { - return check(middle, right) - } - } - } - - function checkMiddle( - leftPart: [number, number], - rightPart: [number, number] - ): EllipsisedValue { - if ( - leftPart[1] - leftPart[0] <= 1 && - rightPart[1] - rightPart[0] <= 1 - ) { - return { - leading: getSubString(0, leftPart[0]) + '...', - tailing: '...' + getSubString(rightPart[1], end), - } - } - const leftPartMiddle = Math.floor((leftPart[0] + leftPart[1]) / 2) - const rightPartMiddle = Math.ceil((rightPart[0] + rightPart[1]) / 2) - container.innerHTML = - getSubString(0, leftPartMiddle) + - '...' + - actionText + - '...' + - getSubString(rightPartMiddle, end) - if (container.offsetHeight <= maxHeight) { - return checkMiddle( - [leftPartMiddle, leftPart[1]], - [rightPart[0], rightPartMiddle] - ) - } else { - return checkMiddle( - [leftPart[0], leftPartMiddle], - [rightPartMiddle, rightPart[1]] - ) - } - } - - const middle = Math.floor((0 + end) / 2) - const ellipsised = - props.direction === 'middle' - ? checkMiddle([0, middle], [middle, end]) - : check(0, end) - setEllipsised(ellipsised) - } - document.body.removeChild(container) - } - - useResizeEffect(calcEllipsised, rootRef) - useIsomorphicLayoutEffect(() => { - calcEllipsised() - }, [ - props.content, - props.direction, - props.rows, - props.expandText, - props.collapseText, - ]) - const expandActionElement = - !!props.expandText && - withStopPropagation( - props.stopPropagationForActionButtons, - { - setExpanded(true) - }} - > - {props.expandText} - - ) - - const collapseActionElement = - !!props.collapseText && - withStopPropagation( - props.stopPropagationForActionButtons, - { - setExpanded(false) - }} - > - {props.collapseText} - - ) - - const renderContent = () => { - if (!exceeded) return props.content - - if (expanded) - return ( - <> - {props.content} - {collapseActionElement} - + const { + content, + direction, + rows, + expandText, + collapseText, + stopPropagationForActionButtons, + onContentClick, + defaultExpanded, + } = props + + // ============================ Refs ============================ + const rootRef = React.useRef(null) + + // ========================== Expanded ========================== + const [expanded, setExpanded] = React.useState(defaultExpanded) + + const expandNode = expandText + ? withStopPropagation( + stopPropagationForActionButtons, + { + setExpanded(true) + }} + > + {expandText} + + ) + : null + + const collapseNode = collapseText + ? withStopPropagation( + stopPropagationForActionButtons, + { + setExpanded(false) + }} + > + {collapseText} + ) - return ( - <> - {ellipsised.leading} - {expandActionElement} - {ellipsised.tailing} - - ) - } + : null + + // ========================== Ellipsis ========================== + const [measureNodes, forceResize] = useMeasure( + rootRef, + content, + rows, + direction, + expanded, + expandNode, + collapseNode + ) + + useResizeEffect(forceResize, rootRef) + // =========================== Render =========================== return withNativeProps( props,
= p => { className={classPrefix} onClick={e => { if (e.target === e.currentTarget) { - props.onContentClick(e) + onContentClick(e) } }} > - {renderContent()} + {measureNodes}
) } - -function pxToNumber(value: string | null): number { - if (!value) return 0 - const match = value.match(/^\d*(\.\d*)?/) - return match ? Number(match[0]) : 0 -} diff --git a/src/components/ellipsis/tests/__snapshots__/ellipsis.test.tsx.snap b/src/components/ellipsis/tests/__snapshots__/ellipsis.test.tsx.snap index 1621445c10..78ded1e000 100644 --- a/src/components/ellipsis/tests/__snapshots__/ellipsis.test.tsx.snap +++ b/src/components/ellipsis/tests/__snapshots__/ellipsis.test.tsx.snap @@ -4,9 +4,9 @@ exports[`Ellipsis direction end 1`] = `
- 蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅... + 蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量 + ...
`; @@ -14,10 +14,11 @@ exports[`Ellipsis direction middle 1`] = `
- 蚂蚁的企业级产品是一个庞... - ...些稳定且高复用性的内容。 + 蚂蚁的企业级产品是一个庞 + ... + ... + 些稳定且高复用性的内容。
`; @@ -25,9 +26,9 @@ exports[`Ellipsis direction start 1`] = `
- ...以及组件,可以通过抽象得到一些稳定且高复用性的内容。 + ... + 面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
`; @@ -35,7 +36,6 @@ exports[`Ellipsis expand and collapse 1`] = `
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。 @@ -48,9 +48,9 @@ exports[`Ellipsis expand and collapse 2`] = `
- 蚂蚁的企业级产品是一个庞大且复杂的体系。... + 蚂蚁的企业级产品是一个庞大且复杂的体系。这 + ... expand @@ -61,8 +61,8 @@ exports[`Ellipsis multi line 1`] = `
- 蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的... + 蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件, + ...
`; diff --git a/src/components/ellipsis/tests/ellipsis.test.tsx b/src/components/ellipsis/tests/ellipsis.test.tsx index fdb89ed8bd..8cb1738abb 100644 --- a/src/components/ellipsis/tests/ellipsis.test.tsx +++ b/src/components/ellipsis/tests/ellipsis.test.tsx @@ -1,5 +1,6 @@ +import { spyElementPrototypes } from 'rc-util/lib/test/domHook' import React from 'react' -import { render, testA11y, fireEvent } from 'testing' +import { fireEvent, render, testA11y } from 'testing' import Ellipsis from '..' const classPrefix = `adm-ellipsis` @@ -9,36 +10,19 @@ const content = const lineHeight = 19.5 describe('Ellipsis', () => { - const originGetComputedStyle = window.getComputedStyle - beforeAll(() => { - window.getComputedStyle = el => { - const style = originGetComputedStyle(el) - style.lineHeight = `${lineHeight}px` - return style - } - }) - - beforeEach(() => { - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - get() { - if (this.innerHTML.includes('...')) { - const row = Math.ceil( - // the width of '...' is equal to a Chinese char - (this.innerHTML.replace(/\.\.\./g, '中').length / content.length) * - 4 - ) - return lineHeight * row - } - return lineHeight * 4 + spyElementPrototypes(HTMLElement, { + offsetHeight: { + get() { + const that = this as HTMLElement + const charLen = (that.textContent || '').length || 1 + const rows = Math.ceil(charLen / 30) + return rows * lineHeight + }, }, }) }) - afterAll(() => { - window.getComputedStyle = originGetComputedStyle - }) - test('a11y', async () => { await testA11y() }) @@ -119,9 +103,6 @@ describe('Ellipsis', () => { }) test('content not exceeded', () => { - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - value: lineHeight, - }) const { getByTestId } = render( ) @@ -151,22 +132,4 @@ describe('Ellipsis', () => { fireEvent.click(getByText('expand')) expect(getByText('collapse')).toBeInTheDocument() }) - - test('the `whiteSpace` style of the calc container should be the same as Ellipsis', () => { - let container: Element | null = null - Object.defineProperty(HTMLElement.prototype, 'removeChild', { - value: (child: Element) => { - container = child - }, - }) - render( - - ) - expect(container).toHaveStyle('white-space: pre-wrap') - }) }) diff --git a/src/components/ellipsis/useMeasure.tsx b/src/components/ellipsis/useMeasure.tsx new file mode 100644 index 0000000000..a23411a737 --- /dev/null +++ b/src/components/ellipsis/useMeasure.tsx @@ -0,0 +1,195 @@ +import { useEvent } from 'rc-util' +import React from 'react' +import runes from 'runes2' + +const enum MEASURE_STATUS { + PREPARE = 1, + MEASURE_WALKING = 2, + STABLE_ELLIPSIS = 99, + STABLE_NO_ELLIPSIS = 100, +} + +const ELLIPSIS_TEXT = '...' + +const measureStyle: React.CSSProperties = { + visibility: 'hidden', + whiteSpace: 'inherit', + lineHeight: 'inherit', + fontSize: 'inherit', +} + +export default function useMeasure( + containerRef: React.RefObject, + content: string, + rows: number, + direction: 'start' | 'end' | 'middle', + expanded: boolean, + expandNode: React.ReactElement | null, + collapseNode: React.ReactElement | null +) { + const contentChars = React.useMemo(() => runes(content), [content]) + + const [maxHeight, setMaxHeight] = React.useState(0) + + const [walkingIndexes, setWalkingIndexes] = React.useState< + [startOffset: number, endOffset: number] + >([0, 0]) + const midIndex = Math.ceil((walkingIndexes[0] + walkingIndexes[1]) / 2) + + const [status, setStatus] = React.useState( + MEASURE_STATUS.STABLE_NO_ELLIPSIS + ) + + // ============================ Refs ============================ + const singleRowMeasureRef = React.useRef(null) + const fullMeasureRef = React.useRef(null) + const midMeasureRef = React.useRef(null) + + const startMeasure = useEvent(() => { + setStatus(MEASURE_STATUS.PREPARE) + setWalkingIndexes([ + 0, + direction === 'middle' + ? Math.ceil(contentChars.length / 2) + : contentChars.length, + ]) + }) + + // Initialize + React.useLayoutEffect(() => { + startMeasure() + }, [contentChars, rows]) + + // Measure element height + React.useLayoutEffect(() => { + if (status === MEASURE_STATUS.PREPARE) { + const fullMeasureHeight = fullMeasureRef.current?.offsetHeight || 0 + const singleRowMeasureHeight = + singleRowMeasureRef.current?.offsetHeight || 0 + const rowMeasureHeight = singleRowMeasureHeight * rows + + if (fullMeasureHeight <= rowMeasureHeight) { + setStatus(MEASURE_STATUS.STABLE_NO_ELLIPSIS) + } else { + setMaxHeight(rowMeasureHeight) + setStatus(MEASURE_STATUS.MEASURE_WALKING) + } + } + }, [status]) + + // Walking measure + React.useLayoutEffect(() => { + if (status === MEASURE_STATUS.MEASURE_WALKING) { + const diff = walkingIndexes[1] - walkingIndexes[0] + const midHeight = midMeasureRef.current?.offsetHeight || 0 + + if (diff > 1) { + if (midHeight > maxHeight) { + setWalkingIndexes([walkingIndexes[0], midIndex]) + } else { + setWalkingIndexes([midIndex, walkingIndexes[1]]) + } + } else { + if (midHeight > maxHeight) { + setWalkingIndexes([walkingIndexes[0], walkingIndexes[0]]) + } else { + setWalkingIndexes([walkingIndexes[1], walkingIndexes[1]]) + } + setStatus(MEASURE_STATUS.STABLE_ELLIPSIS) + } + } + }, [status, walkingIndexes]) + + // =========================== Render =========================== + /** Render by cut index */ + const renderContent = (index: number) => { + const prefixContent = contentChars.slice(0, index) + const suffixContent = contentChars.slice(contentChars.length - index) + + return ( + <> + {direction === 'start' && ( + <> + {expandNode} + {ELLIPSIS_TEXT} + + )} + {direction !== 'start' && prefixContent.join('')} + {direction === 'middle' && ( + <> + {ELLIPSIS_TEXT} + {expandNode} + {ELLIPSIS_TEXT} + + )} + {direction !== 'end' && suffixContent.join('')} + {direction === 'end' && ( + <> + {ELLIPSIS_TEXT} + {expandNode} + + )} + + ) + } + + const finalContent = React.useMemo(() => { + if (expanded || status === MEASURE_STATUS.STABLE_NO_ELLIPSIS) { + return ( + + {content} + {status === MEASURE_STATUS.STABLE_ELLIPSIS && collapseNode} + + ) + } + + if (status === MEASURE_STATUS.STABLE_ELLIPSIS) { + return renderContent(midIndex) + } + + return null + }, [expanded, status, content, collapseNode, midIndex]) + + const allNodes = ( + <> + {/******************* Measure Prepare *******************/} + {/* Origin full content */} + {status === MEASURE_STATUS.PREPARE && ( +
+ {content} + {expandNode} +
+ )} + + {/* Row measure */} + {status === MEASURE_STATUS.PREPARE && ( +
+ {'\u00A0'} +
+ )} + + {/******************* Measure Walking *******************/} + {status === MEASURE_STATUS.MEASURE_WALKING && ( +
+ {renderContent(midIndex)} +
+ )} + + {/*********************** Display ***********************/} + {/* Display content */} + {finalContent} + + ) + + return [allNodes, startMeasure] as const +}