From 956566eaad0a522d6bc27d44594aa36d6c33f8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20K=C3=BChn?= Date: Thu, 12 Aug 2021 18:03:45 +0200 Subject: [PATCH] fix: fix some react focus issues (#1724), fix #1716, fix #1608, fix #1520 * remove async createNodeViews * focus asynchronously to fix weird bugs in react --- packages/core/src/commands/blur.ts | 10 ++++++---- packages/core/src/commands/focus.ts | 18 +++++++++++++++--- .../src/bubble-menu-plugin.ts | 19 ++++++++++++------- .../src/floating-menu-plugin.ts | 19 ++++++++++++------- packages/react/src/EditorContent.tsx | 7 +------ 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/packages/core/src/commands/blur.ts b/packages/core/src/commands/blur.ts index 2137091bf..4663ffea8 100644 --- a/packages/core/src/commands/blur.ts +++ b/packages/core/src/commands/blur.ts @@ -11,10 +11,12 @@ declare module '@tiptap/core' { } } -export const blur: RawCommands['blur'] = () => ({ view }) => { - const element = view.dom as HTMLElement - - element.blur() +export const blur: RawCommands['blur'] = () => ({ editor, view }) => { + requestAnimationFrame(() => { + if (!editor.isDestroyed) { + (view.dom as HTMLElement).blur() + } + }) return true } diff --git a/packages/core/src/commands/focus.ts b/packages/core/src/commands/focus.ts index 4fb3d13c9..a5e78aeac 100644 --- a/packages/core/src/commands/focus.ts +++ b/packages/core/src/commands/focus.ts @@ -47,13 +47,23 @@ export const focus: RawCommands['focus'] = (position = null) => ({ tr, dispatch, }) => { + const delayedFocus = () => { + // For React we have to focus asynchronously. Otherwise wild things happen. + // see: https://github.com/ueberdosis/tiptap/issues/1520 + requestAnimationFrame(() => { + if (!editor.isDestroyed) { + view.focus() + } + }) + } + if ((view.hasFocus() && position === null) || position === false) { return true } // we don’t try to resolve a NodeSelection or CellSelection if (dispatch && position === null && !isTextSelection(editor.state.selection)) { - view.focus() + delayedFocus() return true } @@ -67,7 +77,9 @@ export const focus: RawCommands['focus'] = (position = null) => ({ const isSameSelection = editor.state.selection.eq(selection) if (dispatch) { - tr.setSelection(selection) + if (!isSameSelection) { + tr.setSelection(selection) + } // `tr.setSelection` resets the stored marks // so we’ll restore them if the selection is the same as before @@ -75,7 +87,7 @@ export const focus: RawCommands['focus'] = (position = null) => ({ tr.setStoredMarks(storedMarks) } - view.focus() + delayedFocus() } return true diff --git a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts index 2ffb4072c..2eb9f1717 100644 --- a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts +++ b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts @@ -36,7 +36,7 @@ export class BubbleMenuView { public preventHide = false - public tippy!: Instance + public tippy: Instance | undefined public shouldShow: Exclude = ({ state, from, to }) => { const { doc, selection } = state @@ -74,8 +74,13 @@ export class BubbleMenuView { this.view.dom.addEventListener('dragstart', this.dragstartHandler) this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) - this.createTooltip(tippyOptions) this.element.style.visibility = 'visible' + + // We create tippy asynchronously to make sure that `editor.options.element` + // has already been moved to the right position in the DOM + requestAnimationFrame(() => { + this.createTooltip(tippyOptions) + }) } mousedownHandler = () => { @@ -109,7 +114,7 @@ export class BubbleMenuView { } createTooltip(options: Partial = {}) { - this.tippy = tippy(this.view.dom, { + this.tippy = tippy(this.editor.options.element, { duration: 0, getReferenceClientRect: null, content: this.element, @@ -150,7 +155,7 @@ export class BubbleMenuView { return } - this.tippy.setProps({ + this.tippy?.setProps({ getReferenceClientRect: () => { if (isNodeSelection(state.selection)) { const node = view.nodeDOM(from) as HTMLElement @@ -168,15 +173,15 @@ export class BubbleMenuView { } show() { - this.tippy.show() + this.tippy?.show() } hide() { - this.tippy.hide() + this.tippy?.hide() } destroy() { - this.tippy.destroy() + this.tippy?.destroy() this.element.removeEventListener('mousedown', this.mousedownHandler) this.view.dom.removeEventListener('dragstart', this.dragstartHandler) this.editor.off('focus', this.focusHandler) diff --git a/packages/extension-floating-menu/src/floating-menu-plugin.ts b/packages/extension-floating-menu/src/floating-menu-plugin.ts index 4916c0e32..56c200e6a 100644 --- a/packages/extension-floating-menu/src/floating-menu-plugin.ts +++ b/packages/extension-floating-menu/src/floating-menu-plugin.ts @@ -29,7 +29,7 @@ export class FloatingMenuView { public preventHide = false - public tippy!: Instance + public tippy: Instance | undefined public shouldShow: Exclude = ({ state }) => { const { selection } = state @@ -64,8 +64,13 @@ export class FloatingMenuView { this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true }) this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) - this.createTooltip(tippyOptions) this.element.style.visibility = 'visible' + + // We create tippy asynchronously to make sure that `editor.options.element` + // has already been moved to the right position in the DOM + requestAnimationFrame(() => { + this.createTooltip(tippyOptions) + }) } mousedownHandler = () => { @@ -95,7 +100,7 @@ export class FloatingMenuView { } createTooltip(options: Partial = {}) { - this.tippy = tippy(this.view.dom, { + this.tippy = tippy(this.editor.options.element, { duration: 0, getReferenceClientRect: null, content: this.element, @@ -130,7 +135,7 @@ export class FloatingMenuView { return } - this.tippy.setProps({ + this.tippy?.setProps({ getReferenceClientRect: () => posToDOMRect(view, from, to), }) @@ -138,15 +143,15 @@ export class FloatingMenuView { } show() { - this.tippy.show() + this.tippy?.show() } hide() { - this.tippy.hide() + this.tippy?.hide() } destroy() { - this.tippy.destroy() + this.tippy?.destroy() this.element.removeEventListener('mousedown', this.mousedownHandler) this.editor.off('focus', this.focusHandler) this.editor.off('blur', this.blurHandler) diff --git a/packages/react/src/EditorContent.tsx b/packages/react/src/EditorContent.tsx index f37454c31..712c3f29b 100644 --- a/packages/react/src/EditorContent.tsx +++ b/packages/react/src/EditorContent.tsx @@ -63,12 +63,7 @@ export class PureEditorContent extends React.Component { - if (!editor.isDestroyed) { - editor.createNodeViews() - } - }, 0) + editor.createNodeViews() } }