Skip to content

Commit

Permalink
feat: Ellipsis2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
eternallycyf committed Jul 8, 2023
1 parent caf6191 commit b6d5cfd
Show file tree
Hide file tree
Showing 13 changed files with 544 additions and 273 deletions.
9 changes: 5 additions & 4 deletions src/components/CommonEditTable/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export const formatEditTableColumns = (defaultOptions: ICommonEditTableColumnsTy
format: 'YYYY-MM-DD',
ellipsis: false,
ellipsisType: 'line',
lines: 1,
number: 100,
rows: 1,
maxLength: 100,
...defaultOptions,
};

Expand Down Expand Up @@ -70,12 +70,13 @@ export const formatEditTableColumns = (defaultOptions: ICommonEditTableColumnsTy
options.ellipsis = {
showTitle: false,
};

return options.ellipsisType === 'line' ? (
<Ellipsis tooltip={true} lines={options.lines}>
<Ellipsis tooltip={true} lines={options.rows}>
{text}
</Ellipsis>
) : (
<Ellipsis tooltip={true} length={options.number}>
<Ellipsis tooltip={true} length={options.maxLength}>
{text}
</Ellipsis>
);
Expand Down
94 changes: 94 additions & 0 deletions src/core/base/Ellipsis/Ellipsis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from 'react';
import classNames from 'classnames';
import { TooltipProps } from 'antd';
import EllipsisText from './EllipsisText';
import EllipsisWidth from './EllipsisWidth';
import EllipsisLine from './EllipsisLine';
import EllipsisLineClamp from './EllipsisLineClamp';

// @ts-ignore
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;

export interface EllipsisProps {
tooltip?: boolean;
tooltipProps?: TooltipProps;
length?: number;
lines?: number;
fullWidthRecognition?: boolean;
className?: string;
width?: number | string;
style?: React.CSSProperties;
prefix?: string;
children: React.ReactNode;
}

const Ellipsis = (props: EllipsisProps) => {
const {
children,
lines,
length,
width,
className,
tooltip = true,
style,
fullWidthRecognition = false,
prefix = 'plus-ellipsis',
tooltipProps = {},
...restProps
} = props;

const cls = classNames(`${prefix}-ellipsis`, className, {
[`${prefix}-width-mode`]: width,
[`${prefix}-line`]: lines && !isSupportLineClamp,
[`${prefix}-lineClamp`]: lines && isSupportLineClamp,
});

// 一种限制都没有返回原值
if (!lines && !length && !width) {
return (
<span className={cls} {...restProps}>
{children}
</span>
);
}

if (width) {
return (
<EllipsisWidth className={cls} prefix={prefix} style={style} tooltip={tooltip} tooltipProps={tooltipProps} width={width} {...restProps}>
{children}
</EllipsisWidth>
);
}

// 字数限制
if (length) {
return (
<EllipsisText
className={cls}
prefix={prefix}
tooltipProps={tooltipProps}
length={length}
text={children || ''}
tooltip={tooltip}
fullWidthRecognition={fullWidthRecognition}
{...restProps}
/>
);
}

if (isSupportLineClamp) {
return (
<EllipsisLineClamp className={cls} prefix={prefix} tooltip={tooltip} tooltipProps={tooltipProps} lines={lines} {...restProps}>
{children}
</EllipsisLineClamp>
);
}

return (
<EllipsisLine className={cls} prefix={prefix} tooltip={tooltip} tooltipProps={tooltipProps} lines={lines} {...restProps}>
{children}
</EllipsisLine>
);
};

export default Ellipsis;
150 changes: 150 additions & 0 deletions src/core/base/Ellipsis/EllipsisLine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import * as React from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import { Tooltip } from 'antd';
import { EllipsisProps } from './Ellipsis';

const bisection = (th: number, m: number, b: number, e: any, text: string, shadowNode: { innerHTML: string; offsetHeight: any }): number => {
const suffix = '...';
let mid = m;
let end = e;
let begin = b;
shadowNode.innerHTML = text.substring(0, mid) + suffix;
let sh = shadowNode.offsetHeight;

if (sh <= th) {
shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
sh = shadowNode.offsetHeight;
if (sh > th || mid === begin) {
return mid;
}
begin = mid;
if (end - begin === 1) {
mid = 1 + begin;
} else {
mid = Math.floor((end - begin) / 2) + begin;
}
return bisection(th, mid, begin, end, text, shadowNode);
}
if (mid - 1 < 0) {
return mid;
}
shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
sh = shadowNode.offsetHeight;
if (sh <= th) {
return mid - 1;
}
end = mid;
mid = Math.floor((end - begin) / 2) + begin;
return bisection(th, mid, begin, end, text, shadowNode);
};

type EllipsisLineProps = Omit<EllipsisProps, 'length' | 'width' | 'fullWidthRecognition'>;

const EllipsisLine = (props: EllipsisLineProps) => {
const { className, prefix, lines = 2, tooltip, tooltipProps, children, ...restProps } = props;

const [tooltipVisible, setTooltipVisible] = React.useState<boolean>(false);
const [targetCount, setTargetCount] = React.useState<number>(0);
const [text, setText] = React.useState<string>('');

const rootRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
const nodeRef = React.useRef<HTMLSpanElement>(null);
const shadowRef = React.useRef<HTMLDivElement>(null);
const shadowChildrenRef = React.useRef<HTMLDivElement>(null);

const handleTooltipVisibleChange = (visible: boolean) => {
const node = nodeRef.current;
if (!node) {
return;
}

const shadowChildren = shadowChildrenRef.current;
const text = (shadowChildren!.innerText || shadowChildren!.textContent || '') as string;
const nextVisible = visible && targetCount < (text!.length || 0);

setTooltipVisible(nextVisible);
};

const computeLine = () => {
const root = rootRef.current!;
const content = contentRef.current!;
const shadow = shadowRef.current!;
const shadowChildren = shadowChildrenRef.current!;

const text = shadowChildren.innerText || shadowChildren.textContent;
const lineHeight = parseInt(getComputedStyle(root).lineHeight, 10);
const targetHeight = lines * lineHeight;
content.style.height = `${targetHeight}px`;
const totalHeight = shadowChildren.offsetHeight;
const shadowNode = shadow.firstChild! as HTMLDivElement;

if (totalHeight <= targetHeight) {
setText(text!);
setTargetCount(text!.length);
return;
}

// bisection
const len = text!.length;
const mid = Math.ceil(len / 2);

const count = bisection(targetHeight, mid, 0, len, text!, shadowNode);

setText(text!);
setTargetCount(count);
};

React.useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
if (entry.target === contentRef.current) {
computeLine();
}
});
});
resizeObserver.observe(contentRef.current!);
return () => {
resizeObserver.disconnect();
};
}, []);

React.useEffect(() => {
computeLine();
}, [lines, children]);

const childNode = (
<span ref={nodeRef}>
{targetCount > 0 && text.substring(0, targetCount)}
{targetCount > 0 && targetCount < text.length && '...'}
</span>
);

return (
<div {...restProps} ref={rootRef} className={className}>
<div ref={contentRef}>
{tooltip ? (
<Tooltip
{...tooltipProps}
overlayClassName={`${prefix}-tooltip`}
title={text}
open={tooltipVisible}
onOpenChange={handleTooltipVisibleChange}
>
{childNode}
</Tooltip>
) : (
childNode
)}
<div className={`${prefix}-shadow`} ref={shadowChildrenRef}>
{children}
</div>
<div className={`${prefix}-shadow`} ref={shadowRef}>
<span>{text}</span>
</div>
</div>
</div>
);
};

export default EllipsisLine;
52 changes: 52 additions & 0 deletions src/core/base/Ellipsis/EllipsisLineClamp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react';
import { Tooltip } from 'antd';
import { EllipsisProps } from './Ellipsis';

type EllipsisLineClampProps = Omit<EllipsisProps, 'length' | 'width' | 'fullWidthRecognition'>;

const EllipsisLineClamp = (props: EllipsisLineClampProps) => {
const { className, prefix, lines, tooltip, tooltipProps, children, ...restProps } = props;

const [tooltipVisible, setTooltipVisible] = React.useState<boolean>(false);
const lineClampNodeRef = React.useRef<HTMLDivElement>(null);

const handleTooltipVisibleChange = (visible: boolean) => {
const node = lineClampNodeRef.current;
if (!node) {
return;
}

const nextVisible = visible && (node.offsetHeight < node.scrollHeight || node.offsetWidth < node.scrollWidth);

setTooltipVisible(nextVisible);
};

//行数限制

// support document.body.style.webkitLineClamp
const id = `plus-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`;
const style = `#${id}{-webkit-line-clamp:${lines};-webkit-box-orient: vertical;}`;

const node = (
<div ref={lineClampNodeRef} id={id} className={className} {...restProps}>
<style>{style}</style>
{children}
</div>
);

return tooltip ? (
<Tooltip
{...tooltipProps}
overlayClassName={`${prefix}-tooltip`}
title={children}
open={tooltipVisible}
onOpenChange={handleTooltipVisibleChange}
>
{node}
</Tooltip>
) : (
node
);
};

export default EllipsisLineClamp;
Loading

0 comments on commit b6d5cfd

Please sign in to comment.