@ant-design/x
Version:
Craft AI-driven interfaces effortlessly
102 lines (97 loc) • 3.7 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EditableContent = void 0;
var _util = require("@rc-component/util");
var _antd = require("antd");
var _react = _interopRequireDefault(require("react"));
var _locale = require("../locale");
var _en_US = _interopRequireDefault(require("../locale/en_US"));
/**
* 判断块级元素(跨浏览器)
* div.contentEditable 在换行时会注入块级元素以达成换行效果
* 编辑后提取格式化纯文本,需要识别出这些块级元素并替换为 \n
* */
function isBlock(el) {
const d = getComputedStyle(el).display;
return d === 'block' || d === 'flex' || d === 'list-item' || d === 'table';
}
function getPlainTextWithFormat(dom) {
const lines = [''];
const walker = document.createTreeWalker(dom, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
while (walker.nextNode()) {
const node = walker.currentNode;
if (node.nodeType === Node.TEXT_NODE) {
// textContent 拒绝直接 xss
lines[lines.length - 1] += node.textContent;
continue;
}
// 单纯空行结构 <div><br></div>(chrome/edge/safari/firefox),仅保留一个换行
if (node.tagName === 'BR' && node.parentNode?.childElementCount === 1) {
continue;
}
// 换行
if (node.tagName === 'BR' || isBlock(node)) {
lines.push('');
}
}
return lines.join('\n');
}
const EditableContent = ({
content,
prefixCls,
okText,
cancelText,
onEditConfirm,
onEditCancel
}) => {
const mockInputRef = _react.default.useRef(null);
const [contextLocale] = (0, _locale.useLocale)('Bubble', _en_US.default.Bubble);
const onConfirm = (0, _util.useEvent)(() => {
// 但 onEditing 端应该对入参做 xss 防护
onEditConfirm?.(getPlainTextWithFormat(mockInputRef.current));
});
const onCancel = (0, _util.useEvent)(() => onEditCancel?.());
_react.default.useEffect(() => {
mockInputRef.current.textContent = content;
mockInputRef.current.focus();
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(mockInputRef.current);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}, []);
// 拒绝非 string content,保证 div 渲染纯文本(Text Node)而不是 HTML
if (typeof content !== 'string') throw new Error('Content of editable Bubble should be string');
// 避免组件更新,影响光标位置
// 初始化文本使用 content,后续由编辑内容确定
const memoedMockInput = _react.default.useMemo(() =>
/*#__PURE__*/
/**
* 为什么使用 div
* input、textarea 是固定行为、固定宽高的元素,无法对内容自适应,体验差
* div.contentEditable 提供了编辑 innerHTML 的能力,同时具备内容自适应能力,体验好
*/
_react.default.createElement("div", {
ref: mockInputRef,
contentEditable: true
}), []);
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, memoedMockInput, /*#__PURE__*/_react.default.createElement(_antd.Flex, {
rootClassName: `${prefixCls}-editing-opts`,
gap: 8
}, /*#__PURE__*/_react.default.createElement(_antd.Button, {
type: "primary",
shape: "round",
size: "small",
onClick: onConfirm
}, okText || contextLocale.editableOk), /*#__PURE__*/_react.default.createElement(_antd.Button, {
type: "text",
shape: "round",
size: "small",
onClick: onCancel
}, cancelText || contextLocale.editableCancel)));
};
exports.EditableContent = EditableContent;