diff --git a/.changeset/calm-books-applaud.md b/.changeset/calm-books-applaud.md new file mode 100644 index 000000000..1e25cacf4 --- /dev/null +++ b/.changeset/calm-books-applaud.md @@ -0,0 +1,5 @@ +--- +'slate-react': patch +--- + +[slate-react]: fix selection bugs when multiple editors share value diff --git a/packages/slate-react/src/components/element.tsx b/packages/slate-react/src/components/element.tsx index 591655fef..20a50f612 100644 --- a/packages/slate-react/src/components/element.tsx +++ b/packages/slate-react/src/components/element.tsx @@ -11,7 +11,7 @@ import { ELEMENT_TO_NODE, NODE_TO_PARENT, NODE_TO_INDEX, - KEY_TO_ELEMENT, + EDITOR_TO_KEY_TO_ELEMENT, } from '../utils/weak-maps' import { isDecoratorRangeListEqual } from '../utils/range-list' import { @@ -120,12 +120,13 @@ const Element = (props: { // Update element-related weak maps with the DOM element ref. useIsomorphicLayoutEffect(() => { + const KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor) if (ref.current) { - KEY_TO_ELEMENT.set(key, ref.current) + KEY_TO_ELEMENT?.set(key, ref.current) NODE_TO_ELEMENT.set(element, ref.current) ELEMENT_TO_NODE.set(ref.current, element) } else { - KEY_TO_ELEMENT.delete(key) + KEY_TO_ELEMENT?.delete(key) NODE_TO_ELEMENT.delete(element) } }) diff --git a/packages/slate-react/src/components/text.tsx b/packages/slate-react/src/components/text.tsx index 4b41e7112..6d7fe8d1b 100644 --- a/packages/slate-react/src/components/text.tsx +++ b/packages/slate-react/src/components/text.tsx @@ -6,9 +6,9 @@ import { ReactEditor, useSlateStatic } from '..' import { RenderLeafProps, RenderPlaceholderProps } from './editable' import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect' import { - KEY_TO_ELEMENT, NODE_TO_ELEMENT, ELEMENT_TO_NODE, + EDITOR_TO_KEY_TO_ELEMENT, } from '../utils/weak-maps' import { isDecoratorRangeListEqual } from '../utils/range-list' @@ -56,12 +56,13 @@ const Text = (props: { // Update element-related weak maps with the DOM element ref. useIsomorphicLayoutEffect(() => { + const KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor) if (ref.current) { - KEY_TO_ELEMENT.set(key, ref.current) + KEY_TO_ELEMENT?.set(key, ref.current) NODE_TO_ELEMENT.set(text, ref.current) ELEMENT_TO_NODE.set(ref.current, text) } else { - KEY_TO_ELEMENT.delete(key) + KEY_TO_ELEMENT?.delete(key) NODE_TO_ELEMENT.delete(text) } }) diff --git a/packages/slate-react/src/plugin/react-editor.ts b/packages/slate-react/src/plugin/react-editor.ts index b4b3fd383..6d79be585 100644 --- a/packages/slate-react/src/plugin/react-editor.ts +++ b/packages/slate-react/src/plugin/react-editor.ts @@ -6,11 +6,11 @@ import { ELEMENT_TO_NODE, IS_FOCUSED, IS_READ_ONLY, - KEY_TO_ELEMENT, NODE_TO_INDEX, NODE_TO_KEY, NODE_TO_PARENT, EDITOR_TO_WINDOW, + EDITOR_TO_KEY_TO_ELEMENT, } from '../utils/weak-maps' import { DOMElement, @@ -241,9 +241,10 @@ export const ReactEditor = { */ toDOMNode(editor: ReactEditor, node: Node): HTMLElement { + const KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor) const domNode = Editor.isEditor(node) ? EDITOR_TO_ELEMENT.get(editor) - : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node)) + : KEY_TO_ELEMENT?.get(ReactEditor.findKey(editor, node)) if (!domNode) { throw new Error( diff --git a/packages/slate-react/src/plugin/with-react.ts b/packages/slate-react/src/plugin/with-react.ts index ebc40643b..d73609731 100644 --- a/packages/slate-react/src/plugin/with-react.ts +++ b/packages/slate-react/src/plugin/with-react.ts @@ -3,7 +3,11 @@ import { Editor, Node, Path, Operation, Transforms, Range } from 'slate' import { ReactEditor } from './react-editor' import { Key } from '../utils/key' -import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps' +import { + EDITOR_TO_KEY_TO_ELEMENT, + EDITOR_TO_ON_CHANGE, + NODE_TO_KEY, +} from '../utils/weak-maps' import { AS_NATIVE, NATIVE_OPERATIONS, @@ -29,6 +33,10 @@ export const withReact = (editor: T) => { const e = editor as T & ReactEditor const { apply, onChange, deleteBackward } = e + // The WeakMap which maps a key to a specific HTMLElement must be scoped to the editor instance to + // avoid collisions between editors in the DOM that share the same value. + EDITOR_TO_KEY_TO_ELEMENT.set(e, new WeakMap()) + e.deleteBackward = unit => { if (unit !== 'line') { return deleteBackward(unit) diff --git a/packages/slate-react/src/utils/weak-maps.ts b/packages/slate-react/src/utils/weak-maps.ts index b5c294664..955dfb2d7 100644 --- a/packages/slate-react/src/utils/weak-maps.ts +++ b/packages/slate-react/src/utils/weak-maps.ts @@ -18,9 +18,12 @@ export const EDITOR_TO_WINDOW: WeakMap = new WeakMap() export const EDITOR_TO_ELEMENT: WeakMap = new WeakMap() export const EDITOR_TO_PLACEHOLDER: WeakMap = new WeakMap() export const ELEMENT_TO_NODE: WeakMap = new WeakMap() -export const KEY_TO_ELEMENT: WeakMap = new WeakMap() export const NODE_TO_ELEMENT: WeakMap = new WeakMap() export const NODE_TO_KEY: WeakMap = new WeakMap() +export const EDITOR_TO_KEY_TO_ELEMENT: WeakMap< + Editor, + WeakMap +> = new WeakMap() /** * Weak maps for storing editor-related state.