TinyMCE React中Shift+F3大小写切换后重选文本报错解决
问题
在TinyMCE React编辑器中实现类似Microsoft Word的Shift + F3大小写切换功能时,遇到文本修改后无法保持选中的问题:未选中节点最后一个字符时功能正常,但选中行尾字符会抛出错误:Uncaught DOMException: Index or size is negative or greater than the allowed amount
当前实现代码:
const handleCaseChange = (ed: Editor) => { ed.on("keydown", (event: KeyboardEvent) => { if (event.shiftKey && event.key === "F3") { event.preventDefault(); const selection = ed.selection.getSel(); const selectedText = selection?.toString(); const startOffset = selection?.getRangeAt(0).startOffset; const endOffset = selection?.getRangeAt(0).endOffset; if (selectedText !== undefined && selectedText.length > 0) { let transformedText; if (selectedText === selectedText.toUpperCase()) { transformedText = selectedText.toLowerCase(); } else if (selectedText === selectedText.toLowerCase()) { transformedText = capitalizeEachWord(selectedText); } else { transformedText = selectedText.toUpperCase(); } ed.selection.setContent(transformedText); const range = ed.getDoc().createRange(); // 此处抛出错误 range.setStart(selection.anchorNode, startOffset); if (endOffset === selection?.anchorNode?.textContent?.length) { range.setEndAfter(selection.anchorNode); } else { range.setEnd(selection.anchorNode, endOffset); } selection.removeAllRanges(); selection.addRange(range); } } } } const capitalizeEachWord = (str: string) => str.replace(/\b\w/g, (char: string) => char.toUpperCase());
解决方案
问题根源是调用ed.selection.setContent(transformedText)后,原有的selection.anchorNode已被替换为新的文本节点,继续使用旧节点和旧偏移量设置范围会导致不匹配,尤其选中行尾时更容易触发边界错误。
修改后的代码:
const handleCaseChange = (ed: Editor) => { ed.on("keydown", (event: KeyboardEvent) => { if (event.shiftKey && event.key === "F3") { event.preventDefault(); const selection = ed.selection.getSel(); if (!selection || selection.rangeCount === 0) return; const originRange = selection.getRangeAt(0); const selectedText = originRange.toString(); if (!selectedText || selectedText.length === 0) return; // 替换前记录关键状态:起始/结束节点、偏移量、是否选中到节点末尾 const startNode = originRange.startContainer; const startOffset = originRange.startOffset; const endNode = originRange.endContainer; const endOffset = originRange.endOffset; const isEndOfOriginNode = endOffset === endNode.textContent?.length; // 文本大小写转换逻辑 let transformedText; if (selectedText === selectedText.toUpperCase()) { transformedText = selectedText.toLowerCase(); } else if (selectedText === selectedText.toLowerCase()) { transformedText = capitalizeEachWord(selectedText); } else { transformedText = selectedText.toUpperCase(); } // 替换选中内容 ed.selection.setContent(transformedText); // 重新构建选中范围 const newRange = ed.getDoc().createRange(); // 替换后,原选中区域的父节点下会生成新的文本节点 const newTextNode = startNode.parentNode?.firstChild as Text; if (!newTextNode) return; // 设置起始偏移:与原起始位置一致 newRange.setStart(newTextNode, startOffset); // 设置结束偏移:若原先是选中到节点末尾,则用转换后文本的长度;否则沿用原结束偏移 const newEndOffset = isEndOfOriginNode ? transformedText.length : endOffset; newRange.setEnd(newTextNode, newEndOffset); // 应用新的选中范围 selection.removeAllRanges(); selection.addRange(newRange); } }); } const capitalizeEachWord = (str: string) => str.replace(/\b\w/g, (char: string) => char.toUpperCase());
核心修改点:
- 替换文本前提前记录原始选中的节点、偏移量和边界状态
- 替换后通过原节点的父节点定位新生成的文本节点
- 根据转换后的文本长度动态计算结束偏移,避免超出节点范围
内容的提问来源于stack exchange,提问作者Jesser
相关产品推荐
相关产品推荐

