422 lines
16 KiB
JavaScript
422 lines
16 KiB
JavaScript
"use client";
|
|
|
|
var __rest = this && this.__rest || function (s, e) {
|
|
var t = {};
|
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
|
|
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
|
|
}
|
|
return t;
|
|
};
|
|
import CheckOutlined from "@ant-design/icons/es/icons/CheckOutlined";
|
|
import CopyOutlined from "@ant-design/icons/es/icons/CopyOutlined";
|
|
import EditOutlined from "@ant-design/icons/es/icons/EditOutlined";
|
|
import classNames from 'classnames';
|
|
import copy from 'copy-to-clipboard';
|
|
import ResizeObserver from 'rc-resize-observer';
|
|
import toArray from "rc-util/es/Children/toArray";
|
|
import useIsomorphicLayoutEffect from "rc-util/es/hooks/useLayoutEffect";
|
|
import useMergedState from "rc-util/es/hooks/useMergedState";
|
|
import omit from "rc-util/es/omit";
|
|
import { composeRef } from "rc-util/es/ref";
|
|
import * as React from 'react';
|
|
import { isStyleSupport } from '../../_util/styleChecker';
|
|
import TransButton from '../../_util/transButton';
|
|
import { ConfigContext } from '../../config-provider';
|
|
import useLocale from '../../locale/useLocale';
|
|
import Tooltip from '../../tooltip';
|
|
import Editable from '../Editable';
|
|
import Typography from '../Typography';
|
|
import useMergedConfig from '../hooks/useMergedConfig';
|
|
import useUpdatedEffect from '../hooks/useUpdatedEffect';
|
|
import Ellipsis from './Ellipsis';
|
|
import EllipsisTooltip from './EllipsisTooltip';
|
|
function wrapperDecorations(_ref, content) {
|
|
let {
|
|
mark,
|
|
code,
|
|
underline,
|
|
delete: del,
|
|
strong,
|
|
keyboard,
|
|
italic
|
|
} = _ref;
|
|
let currentContent = content;
|
|
function wrap(tag, needed) {
|
|
if (!needed) {
|
|
return;
|
|
}
|
|
currentContent = /*#__PURE__*/React.createElement(tag, {}, currentContent);
|
|
}
|
|
wrap('strong', strong);
|
|
wrap('u', underline);
|
|
wrap('del', del);
|
|
wrap('code', code);
|
|
wrap('mark', mark);
|
|
wrap('kbd', keyboard);
|
|
wrap('i', italic);
|
|
return currentContent;
|
|
}
|
|
function getNode(dom, defaultNode, needDom) {
|
|
if (dom === true || dom === undefined) {
|
|
return defaultNode;
|
|
}
|
|
return dom || needDom && defaultNode;
|
|
}
|
|
function toList(val) {
|
|
if (val === false) {
|
|
return [false, false];
|
|
}
|
|
return Array.isArray(val) ? val : [val];
|
|
}
|
|
const ELLIPSIS_STR = '...';
|
|
const Base = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
var _a, _b, _c;
|
|
const {
|
|
prefixCls: customizePrefixCls,
|
|
className,
|
|
style,
|
|
type,
|
|
disabled,
|
|
children,
|
|
ellipsis,
|
|
editable,
|
|
copyable,
|
|
component,
|
|
title
|
|
} = props,
|
|
restProps = __rest(props, ["prefixCls", "className", "style", "type", "disabled", "children", "ellipsis", "editable", "copyable", "component", "title"]);
|
|
const {
|
|
getPrefixCls,
|
|
direction
|
|
} = React.useContext(ConfigContext);
|
|
const [textLocale] = useLocale('Text');
|
|
const typographyRef = React.useRef(null);
|
|
const editIconRef = React.useRef(null);
|
|
// ============================ MISC ============================
|
|
const prefixCls = getPrefixCls('typography', customizePrefixCls);
|
|
const textProps = omit(restProps, ['mark', 'code', 'delete', 'underline', 'strong', 'keyboard', 'italic']);
|
|
// ========================== Editable ==========================
|
|
const [enableEdit, editConfig] = useMergedConfig(editable);
|
|
const [editing, setEditing] = useMergedState(false, {
|
|
value: editConfig.editing
|
|
});
|
|
const {
|
|
triggerType = ['icon']
|
|
} = editConfig;
|
|
const triggerEdit = edit => {
|
|
var _a;
|
|
if (edit) {
|
|
(_a = editConfig.onStart) === null || _a === void 0 ? void 0 : _a.call(editConfig);
|
|
}
|
|
setEditing(edit);
|
|
};
|
|
// Focus edit icon when back
|
|
useUpdatedEffect(() => {
|
|
var _a;
|
|
if (!editing) {
|
|
(_a = editIconRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
}
|
|
}, [editing]);
|
|
const onEditClick = e => {
|
|
e === null || e === void 0 ? void 0 : e.preventDefault();
|
|
triggerEdit(true);
|
|
};
|
|
const onEditChange = value => {
|
|
var _a;
|
|
(_a = editConfig.onChange) === null || _a === void 0 ? void 0 : _a.call(editConfig, value);
|
|
triggerEdit(false);
|
|
};
|
|
const onEditCancel = () => {
|
|
var _a;
|
|
(_a = editConfig.onCancel) === null || _a === void 0 ? void 0 : _a.call(editConfig);
|
|
triggerEdit(false);
|
|
};
|
|
// ========================== Copyable ==========================
|
|
const [enableCopy, copyConfig] = useMergedConfig(copyable);
|
|
const [copied, setCopied] = React.useState(false);
|
|
const copyIdRef = React.useRef(null);
|
|
const copyOptions = {};
|
|
if (copyConfig.format) {
|
|
copyOptions.format = copyConfig.format;
|
|
}
|
|
const cleanCopyId = () => {
|
|
if (copyIdRef.current) {
|
|
clearTimeout(copyIdRef.current);
|
|
}
|
|
};
|
|
const onCopyClick = e => {
|
|
var _a;
|
|
e === null || e === void 0 ? void 0 : e.preventDefault();
|
|
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
|
copy(copyConfig.text || String(children) || '', copyOptions);
|
|
setCopied(true);
|
|
// Trigger tips update
|
|
cleanCopyId();
|
|
copyIdRef.current = setTimeout(() => {
|
|
setCopied(false);
|
|
}, 3000);
|
|
(_a = copyConfig.onCopy) === null || _a === void 0 ? void 0 : _a.call(copyConfig, e);
|
|
};
|
|
React.useEffect(() => cleanCopyId, []);
|
|
// ========================== Ellipsis ==========================
|
|
const [isLineClampSupport, setIsLineClampSupport] = React.useState(false);
|
|
const [isTextOverflowSupport, setIsTextOverflowSupport] = React.useState(false);
|
|
const [expanded, setExpanded] = React.useState(false);
|
|
const [isJsEllipsis, setIsJsEllipsis] = React.useState(false);
|
|
const [isNativeEllipsis, setIsNativeEllipsis] = React.useState(false);
|
|
const [isNativeVisible, setIsNativeVisible] = React.useState(true);
|
|
const [enableEllipsis, ellipsisConfig] = useMergedConfig(ellipsis, {
|
|
expandable: false
|
|
});
|
|
const mergedEnableEllipsis = enableEllipsis && !expanded;
|
|
// Shared prop to reduce bundle size
|
|
const {
|
|
rows = 1
|
|
} = ellipsisConfig;
|
|
const needMeasureEllipsis = React.useMemo(() =>
|
|
// Disable ellipsis
|
|
!mergedEnableEllipsis ||
|
|
// Provide suffix
|
|
ellipsisConfig.suffix !== undefined || ellipsisConfig.onEllipsis ||
|
|
// Can't use css ellipsis since we need to provide the place for button
|
|
ellipsisConfig.expandable || enableEdit || enableCopy, [mergedEnableEllipsis, ellipsisConfig, enableEdit, enableCopy]);
|
|
useIsomorphicLayoutEffect(() => {
|
|
if (enableEllipsis && !needMeasureEllipsis) {
|
|
setIsLineClampSupport(isStyleSupport('webkitLineClamp'));
|
|
setIsTextOverflowSupport(isStyleSupport('textOverflow'));
|
|
}
|
|
}, [needMeasureEllipsis, enableEllipsis]);
|
|
const cssEllipsis = React.useMemo(() => {
|
|
if (needMeasureEllipsis) {
|
|
return false;
|
|
}
|
|
if (rows === 1) {
|
|
return isTextOverflowSupport;
|
|
}
|
|
return isLineClampSupport;
|
|
}, [needMeasureEllipsis, isTextOverflowSupport, isLineClampSupport]);
|
|
const isMergedEllipsis = mergedEnableEllipsis && (cssEllipsis ? isNativeEllipsis : isJsEllipsis);
|
|
const cssTextOverflow = mergedEnableEllipsis && rows === 1 && cssEllipsis;
|
|
const cssLineClamp = mergedEnableEllipsis && rows > 1 && cssEllipsis;
|
|
// >>>>> Expand
|
|
const onExpandClick = e => {
|
|
var _a;
|
|
setExpanded(true);
|
|
(_a = ellipsisConfig.onExpand) === null || _a === void 0 ? void 0 : _a.call(ellipsisConfig, e);
|
|
};
|
|
const [ellipsisWidth, setEllipsisWidth] = React.useState(0);
|
|
const [ellipsisFontSize, setEllipsisFontSize] = React.useState(0);
|
|
const onResize = (_ref2, element) => {
|
|
let {
|
|
offsetWidth
|
|
} = _ref2;
|
|
var _a;
|
|
setEllipsisWidth(offsetWidth);
|
|
setEllipsisFontSize(parseInt((_a = window.getComputedStyle) === null || _a === void 0 ? void 0 : _a.call(window, element).fontSize, 10) || 0);
|
|
};
|
|
// >>>>> JS Ellipsis
|
|
const onJsEllipsis = jsEllipsis => {
|
|
var _a;
|
|
setIsJsEllipsis(jsEllipsis);
|
|
// Trigger if changed
|
|
if (isJsEllipsis !== jsEllipsis) {
|
|
(_a = ellipsisConfig.onEllipsis) === null || _a === void 0 ? void 0 : _a.call(ellipsisConfig, jsEllipsis);
|
|
}
|
|
};
|
|
// >>>>> Native ellipsis
|
|
React.useEffect(() => {
|
|
const textEle = typographyRef.current;
|
|
if (enableEllipsis && cssEllipsis && textEle) {
|
|
const currentEllipsis = cssLineClamp ? textEle.offsetHeight < textEle.scrollHeight : textEle.offsetWidth < textEle.scrollWidth;
|
|
if (isNativeEllipsis !== currentEllipsis) {
|
|
setIsNativeEllipsis(currentEllipsis);
|
|
}
|
|
}
|
|
}, [enableEllipsis, cssEllipsis, children, cssLineClamp, isNativeVisible]);
|
|
// https://github.com/ant-design/ant-design/issues/36786
|
|
// Use IntersectionObserver to check if element is invisible
|
|
React.useEffect(() => {
|
|
const textEle = typographyRef.current;
|
|
if (typeof IntersectionObserver === 'undefined' || !textEle || !cssEllipsis || !mergedEnableEllipsis) {
|
|
return;
|
|
}
|
|
/* eslint-disable-next-line compat/compat */
|
|
const observer = new IntersectionObserver(() => {
|
|
setIsNativeVisible(!!textEle.offsetParent);
|
|
});
|
|
observer.observe(textEle);
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
}, [cssEllipsis, mergedEnableEllipsis]);
|
|
// ========================== Tooltip ===========================
|
|
let tooltipProps = {};
|
|
if (ellipsisConfig.tooltip === true) {
|
|
tooltipProps = {
|
|
title: (_a = editConfig.text) !== null && _a !== void 0 ? _a : children
|
|
};
|
|
} else if ( /*#__PURE__*/React.isValidElement(ellipsisConfig.tooltip)) {
|
|
tooltipProps = {
|
|
title: ellipsisConfig.tooltip
|
|
};
|
|
} else if (typeof ellipsisConfig.tooltip === 'object') {
|
|
tooltipProps = Object.assign({
|
|
title: (_b = editConfig.text) !== null && _b !== void 0 ? _b : children
|
|
}, ellipsisConfig.tooltip);
|
|
} else {
|
|
tooltipProps = {
|
|
title: ellipsisConfig.tooltip
|
|
};
|
|
}
|
|
const topAriaLabel = React.useMemo(() => {
|
|
const isValid = val => ['string', 'number'].includes(typeof val);
|
|
if (!enableEllipsis || cssEllipsis) {
|
|
return undefined;
|
|
}
|
|
if (isValid(editConfig.text)) {
|
|
return editConfig.text;
|
|
}
|
|
if (isValid(children)) {
|
|
return children;
|
|
}
|
|
if (isValid(title)) {
|
|
return title;
|
|
}
|
|
if (isValid(tooltipProps.title)) {
|
|
return tooltipProps.title;
|
|
}
|
|
return undefined;
|
|
}, [enableEllipsis, cssEllipsis, title, tooltipProps.title, isMergedEllipsis]);
|
|
// =========================== Render ===========================
|
|
// >>>>>>>>>>> Editing input
|
|
if (editing) {
|
|
return /*#__PURE__*/React.createElement(Editable, {
|
|
value: (_c = editConfig.text) !== null && _c !== void 0 ? _c : typeof children === 'string' ? children : '',
|
|
onSave: onEditChange,
|
|
onCancel: onEditCancel,
|
|
onEnd: editConfig.onEnd,
|
|
prefixCls: prefixCls,
|
|
className: className,
|
|
style: style,
|
|
direction: direction,
|
|
component: component,
|
|
maxLength: editConfig.maxLength,
|
|
autoSize: editConfig.autoSize,
|
|
enterIcon: editConfig.enterIcon
|
|
});
|
|
}
|
|
// >>>>>>>>>>> Typography
|
|
// Expand
|
|
const renderExpand = () => {
|
|
const {
|
|
expandable,
|
|
symbol
|
|
} = ellipsisConfig;
|
|
if (!expandable) return null;
|
|
let expandContent;
|
|
if (symbol) {
|
|
expandContent = symbol;
|
|
} else {
|
|
expandContent = textLocale === null || textLocale === void 0 ? void 0 : textLocale.expand;
|
|
}
|
|
return /*#__PURE__*/React.createElement("a", {
|
|
key: "expand",
|
|
className: `${prefixCls}-expand`,
|
|
onClick: onExpandClick,
|
|
"aria-label": textLocale === null || textLocale === void 0 ? void 0 : textLocale.expand
|
|
}, expandContent);
|
|
};
|
|
// Edit
|
|
const renderEdit = () => {
|
|
if (!enableEdit) return;
|
|
const {
|
|
icon,
|
|
tooltip
|
|
} = editConfig;
|
|
const editTitle = toArray(tooltip)[0] || (textLocale === null || textLocale === void 0 ? void 0 : textLocale.edit);
|
|
const ariaLabel = typeof editTitle === 'string' ? editTitle : '';
|
|
return triggerType.includes('icon') ? /*#__PURE__*/React.createElement(Tooltip, {
|
|
key: "edit",
|
|
title: tooltip === false ? '' : editTitle
|
|
}, /*#__PURE__*/React.createElement(TransButton, {
|
|
ref: editIconRef,
|
|
className: `${prefixCls}-edit`,
|
|
onClick: onEditClick,
|
|
"aria-label": ariaLabel
|
|
}, icon || /*#__PURE__*/React.createElement(EditOutlined, {
|
|
role: "button"
|
|
}))) : null;
|
|
};
|
|
// Copy
|
|
const renderCopy = () => {
|
|
if (!enableCopy) return;
|
|
const {
|
|
tooltips,
|
|
icon
|
|
} = copyConfig;
|
|
const tooltipNodes = toList(tooltips);
|
|
const iconNodes = toList(icon);
|
|
const copyTitle = copied ? getNode(tooltipNodes[1], textLocale === null || textLocale === void 0 ? void 0 : textLocale.copied) : getNode(tooltipNodes[0], textLocale === null || textLocale === void 0 ? void 0 : textLocale.copy);
|
|
const systemStr = copied ? textLocale === null || textLocale === void 0 ? void 0 : textLocale.copied : textLocale === null || textLocale === void 0 ? void 0 : textLocale.copy;
|
|
const ariaLabel = typeof copyTitle === 'string' ? copyTitle : systemStr;
|
|
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
key: "copy",
|
|
title: copyTitle
|
|
}, /*#__PURE__*/React.createElement(TransButton, {
|
|
className: classNames(`${prefixCls}-copy`, copied && `${prefixCls}-copy-success`),
|
|
onClick: onCopyClick,
|
|
"aria-label": ariaLabel
|
|
}, copied ? getNode(iconNodes[1], /*#__PURE__*/React.createElement(CheckOutlined, null), true) : getNode(iconNodes[0], /*#__PURE__*/React.createElement(CopyOutlined, null), true)));
|
|
};
|
|
const renderOperations = renderExpanded => [renderExpanded && renderExpand(), renderEdit(), renderCopy()];
|
|
const renderEllipsis = needEllipsis => [needEllipsis && /*#__PURE__*/React.createElement("span", {
|
|
"aria-hidden": true,
|
|
key: "ellipsis"
|
|
}, ELLIPSIS_STR), ellipsisConfig.suffix, renderOperations(needEllipsis)];
|
|
return /*#__PURE__*/React.createElement(ResizeObserver, {
|
|
onResize: onResize,
|
|
disabled: !mergedEnableEllipsis || cssEllipsis
|
|
}, resizeRef => /*#__PURE__*/React.createElement(EllipsisTooltip, {
|
|
tooltipProps: tooltipProps,
|
|
enabledEllipsis: mergedEnableEllipsis,
|
|
isEllipsis: isMergedEllipsis
|
|
}, /*#__PURE__*/React.createElement(Typography, Object.assign({
|
|
className: classNames({
|
|
[`${prefixCls}-${type}`]: type,
|
|
[`${prefixCls}-disabled`]: disabled,
|
|
[`${prefixCls}-ellipsis`]: enableEllipsis,
|
|
[`${prefixCls}-single-line`]: mergedEnableEllipsis && rows === 1,
|
|
[`${prefixCls}-ellipsis-single-line`]: cssTextOverflow,
|
|
[`${prefixCls}-ellipsis-multiple-line`]: cssLineClamp
|
|
}, className),
|
|
prefixCls: customizePrefixCls,
|
|
style: Object.assign(Object.assign({}, style), {
|
|
WebkitLineClamp: cssLineClamp ? rows : undefined
|
|
}),
|
|
component: component,
|
|
ref: composeRef(resizeRef, typographyRef, ref),
|
|
direction: direction,
|
|
onClick: triggerType.includes('text') ? onEditClick : undefined,
|
|
"aria-label": topAriaLabel === null || topAriaLabel === void 0 ? void 0 : topAriaLabel.toString(),
|
|
title: title
|
|
}, textProps), /*#__PURE__*/React.createElement(Ellipsis, {
|
|
enabledMeasure: mergedEnableEllipsis && !cssEllipsis,
|
|
text: children,
|
|
rows: rows,
|
|
width: ellipsisWidth,
|
|
fontSize: ellipsisFontSize,
|
|
onEllipsis: onJsEllipsis
|
|
}, (node, needEllipsis) => {
|
|
let renderNode = node;
|
|
if (node.length && needEllipsis && topAriaLabel) {
|
|
renderNode = /*#__PURE__*/React.createElement("span", {
|
|
key: "show-content",
|
|
"aria-hidden": true
|
|
}, renderNode);
|
|
}
|
|
const wrappedContext = wrapperDecorations(props, /*#__PURE__*/React.createElement(React.Fragment, null, renderNode, renderEllipsis(needEllipsis)));
|
|
return wrappedContext;
|
|
}))));
|
|
});
|
|
export default Base; |