UNPKG

@ant-design/x

Version:

Craft AI-driven interfaces effortlessly

102 lines (97 loc) 3.7 kB
"use strict"; 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;