You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 16:24:57