From ae65ae5f717c877eee0e3f839b76fc18d8b44999 Mon Sep 17 00:00:00 2001 From: Jake Donham Date: Mon, 11 Oct 2021 05:15:04 -0700 Subject: [PATCH] revert #4455 / #4512; fix triple-click by unhanging range with void (#4588) * revert #4455 / #4512; fix triple-click by unhanging range with void * added changeset --- .changeset/curly-jobs-reflect.md | 5 ++ .../slate-react/src/plugin/react-editor.ts | 77 ++++--------------- site/examples/images.tsx | 2 +- site/examples/richtext.tsx | 4 + 4 files changed, 25 insertions(+), 63 deletions(-) create mode 100644 .changeset/curly-jobs-reflect.md diff --git a/.changeset/curly-jobs-reflect.md b/.changeset/curly-jobs-reflect.md new file mode 100644 index 0000000000..edf8c4710d --- /dev/null +++ b/.changeset/curly-jobs-reflect.md @@ -0,0 +1,5 @@ +--- +'slate-react': patch +--- + +revert #4455 / #4512; fix triple-click by unhanging range with void diff --git a/packages/slate-react/src/plugin/react-editor.ts b/packages/slate-react/src/plugin/react-editor.ts index 8fcdd6c2cc..09f618bdb2 100644 --- a/packages/slate-react/src/plugin/react-editor.ts +++ b/packages/slate-react/src/plugin/react-editor.ts @@ -565,65 +565,6 @@ export const ReactEditor = { anchorOffset = domRange.anchorOffset focusNode = domRange.focusNode focusOffset = domRange.focusOffset - // When triple clicking a block, Chrome will return a selection object whose - // focus node is the next element sibling and focusOffset is 0. - // This will highlight the corresponding toolbar button for the sibling - // block even though users just want to target the previous block. - // (2021/08/24) - // Signs of a triple click in Chrome - // - anchor node will be a text node but focus node won't - // - both anchorOffset and focusOffset are 0 - // - focusNode value will be null since Chrome tries to extend to just the - // beginning of the next block - if ( - IS_CHROME && - anchorNode && - focusNode && - anchorNode.nodeType !== focusNode.nodeType && - domRange.anchorOffset === 0 && - domRange.focusOffset === 0 && - focusNode.nodeValue == null - ) { - // If an anchorNode is an element node when triple clicked, then the focusNode - // should also be the same as anchorNode when triple clicked. - // Otherwise, anchorNode is a text node and we need to - // - climb up the DOM tree to get the farthest element node that receives - // triple click. It should have atribute 'data-slate-node' = "element" - // - get the last child of that element node - // - climb down the DOM tree to get the text node of the last child - // - this is also the end of the selection aka the focusNode - const anchorElement = anchorNode.parentNode as HTMLElement - const selectedBlock = anchorElement.closest( - '[data-slate-node="element"]' - ) - if (selectedBlock) { - // The Slate Text nodes are leaf-level and contains document's text. - // However, when represented in the DOM, they are actually Element nodes - // and different from the DOM's Text nodes - const { childElementCount: slateTextNodeCount } = selectedBlock - if (slateTextNodeCount === 1) { - focusNode = anchorNode as Text - focusOffset = focusNode.length - } else if (slateTextNodeCount > 1) { - // A element with attribute data-slate-node="element" can have multiple - // children with attribute data-slate-node="text". But these children only have - // one child at each level. - // - // - // - // - // - const focusElement = selectedBlock.lastElementChild as HTMLElement - const nodeIterator = document.createNodeIterator( - focusElement, - NodeFilter.SHOW_TEXT - ) - focusNode = nodeIterator.nextNode() as Text - focusOffset = focusNode.length - } - } - } - // COMPAT: There's a bug in chrome that always returns `true` for // `isCollapsed` for a Selection that comes from a ShadowRoot. // (2020/08/08) @@ -671,9 +612,21 @@ export const ReactEditor = { return null as T extends true ? Range | null : Range } - return ({ anchor, focus } as unknown) as T extends true - ? Range | null - : Range + let range: Range = { anchor: anchor as Point, focus: focus as Point } + // if the selection is a hanging range that ends in a void + // and the DOM focus is an Element + // (meaning that the selection ends before the element) + // unhang the range to avoid mistakenly including the void + if ( + Range.isExpanded(range) && + Range.isForward(range) && + isDOMElement(focusNode) && + Editor.void(editor, { at: range.focus, mode: 'highest' }) + ) { + range = Editor.unhangRange(editor, range, { voids: true }) + } + + return (range as unknown) as T extends true ? Range | null : Range }, hasRange(editor: ReactEditor, range: Range): boolean { diff --git a/site/examples/images.tsx b/site/examples/images.tsx index 595377c96e..30fb3964a6 100644 --- a/site/examples/images.tsx +++ b/site/examples/images.tsx @@ -93,6 +93,7 @@ const Image = ({ attributes, children, element }) => { const focused = useFocused() return (
+ {children}
{ `} />
- {children}
) } diff --git a/site/examples/richtext.tsx b/site/examples/richtext.tsx index 92ef201551..29bce988c2 100644 --- a/site/examples/richtext.tsx +++ b/site/examples/richtext.tsx @@ -93,7 +93,11 @@ const toggleMark = (editor, format) => { } const isBlockActive = (editor, format) => { + const { selection } = editor + if (!selection) return false + const [match] = Editor.nodes(editor, { + at: Editor.unhangRange(editor, selection), match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === format, })