如何强制设置 @lexical/react 纯文本编辑器的值,同时保留 `Selection`?

Mic*_*urz 8 reactjs lexicaljs

我需要完成什么

我想制作一个简单的受控 词汇明文编辑器 - 一个由父字符串字段控制的编辑器。

但我真的很难让我的编辑同时:

  1. 每当父状态发生变化时就采用它
  2. Selection收养母国后保留
  3. 不会仅仅因为外部值发生变化(并被采用)而自动聚焦

到目前为止我得到了什么

我尝试了一些方法,但这是我得到的最接近的 -沙盒

export const useAdoptPlaintextValue = (value: string) => {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    editor.update(() => {
      const initialSelection = $getSelection()?.clone() ?? null;
      $getRoot().clear();
      $getRoot().select(); // for some reason this is not even necessary
      $getSelection()?.insertText(value);
      $setSelection(initialSelection);
    });
  }, [value, editor]);
};
Run Code Online (Sandbox Code Playgroud)

这种方法在写入输入本身时效果很好,但强制采用value 仅在首次选择输入之前有效。在选择它之后(即使再次取消选择),编辑器仅采用一个“渲染帧”的值,然后立即使用旧值重新渲染。我显然在选择上做错了,因为:

  1. 删除setSelection(initialSelection)也可以解决这个问题 - 但更新之间不会保持选择,这也是不可接受的。
  2. 每次击键时我都会收到此错误:

updateEditor:选择已丢失,因为先前选择的节点已被删除并且选择未移动到另一个节点。确保删除/替换选定节点后选择发生更改。

...在我看来,initialSelection保留了对被删除的节点的引用$getRoot().clear(),但我尝试绕过它,但一无所获。

我很高兴获得有关我的代码或实现我的目标的任何建议/帮助。

谢谢

zur*_*fyx 8

关于 Lexical 如何工作的一些背景信息(可以跳到下一点)

Lexical 利用 EditorState 作为编辑器内容更改和选择的事实来源。当您执行 操作时editor.update,Lexical 会创建一个全新的 EditorState(前一个 EditorState 的克隆)并相应地对其进行修改。

在稍后的时间点(同步或异步),这些更改将反映到 DOM(除非它们直接来自 DOM;然后我们立即更新 EditorState)。

当 DOM 更改或操作节点时,Lexical 会自动重新计算选择。这是有充分理由的,选择是很难做到正确的:

  • 选定的节点已删除,但有兄弟节点 -> 移动到兄弟节点
  • 选定的节点已删除,但没有兄弟节点 -> 查找最近的父节点
  • 文本节点内容变化->了解当前选择是否适合
  • DOM 选择因组合或 beforeinput -> 复制选择而发生变化
  • ETC。

此选择重新计算最初也是对 EditorState 进行的(除非它直接来自 DOM),然后返回到 DOM。

焦点恢复

默认情况下,选择协调将恢复 DOM 选择,以确保它与事实来源相匹配:EditorState。因此,无论您将选择移动到何处(即使它是上述自动选择恢复的一部分),都会将焦点移动到可编辑内容。

此规则有 3 个例外:

  1. $setSelection(null);-> 清除选择
  2. readonly editor.setReadOnly-> 你不应该与只读编辑器交互
  3. 协作editor.update(() => ..., {tag: 'collaboration'})-> 我们为此创建了一个例外

我绝不会推荐 1. 为此目的,因为编辑会忘记这个位置。

当编辑器真正是只读时,第二个是有意义的。

第三个可以作为临时补丁为您工作,但最终您想要一个比这更好的解决方案。

针对您的用例的另一个临时补丁是document.selection在 Lexical contenteditable 取得控制权后立即存储和恢复它。

也就是说,有时能够以编程方式跳过 DOM 选择协调似乎是一个合理的用例。我创建了这个提案(https://github.com/facebook/lexical/pull/2134)。

关于您的错误的旁注

您的选择可能位于段落或某些文本节点上。当你清除根部时,你就摧毁了元素。在大多数情况下,我们尝试恢复上面列出的选择,但选择恢复仅限于有效选择。在您的情况下,您正在将选择移动到未附加的节点(已作为 的一部分删除root.clear())。