From ca3763d3c29cc0715727fe12a0e907ad86d99806 Mon Sep 17 00:00:00 2001 From: Enrique Date: Tue, 7 Sep 2021 14:46:45 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20don=E2=80=99t=20initialize=20tippy=20on?= =?UTF-8?q?=20requestAnimationFrame=20to=20avoid=20race=20conditions=20(#1?= =?UTF-8?q?820)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of initializting tippy when the bubble menu and floating menu plugins are initialized, defer the initialization of tippy to the moment when the the editor should display the floating or bubble menu Co-authored-by: Enrique Alcantara --- .../src/bubble-menu-plugin.ts | 46 +++++++++++-------- .../src/floating-menu-plugin.ts | 46 +++++++++++-------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts index 91635f2c8d4..98254c3ff8b 100644 --- a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts +++ b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts @@ -38,6 +38,8 @@ export class BubbleMenuView { public tippy: Instance | undefined + public tippyOptions?: Partial + public shouldShow: Exclude = ({ state, from, to }) => { const { doc, selection } = state const { empty } = selection @@ -59,7 +61,7 @@ export class BubbleMenuView { editor, element, view, - tippyOptions, + tippyOptions = {}, shouldShow, }: BubbleMenuViewProps) { this.editor = editor @@ -74,13 +76,10 @@ export class BubbleMenuView { this.view.dom.addEventListener('dragstart', this.dragstartHandler) this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) + this.tippyOptions = tippyOptions + // Detaches menu content from its current parent + this.element.remove() 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 = () => { @@ -113,17 +112,26 @@ export class BubbleMenuView { this.hide() } - createTooltip(options: Partial = {}) { - this.tippy = tippy(this.editor.options.element, { - duration: 0, - getReferenceClientRect: null, - content: this.element, - interactive: true, - trigger: 'manual', - placement: 'top', - hideOnClick: 'toggle', - ...options, - }) + createTooltip() { + if (this.tippy) { + return + } + + const { element: editorElement } = this.editor.options + + // Wait until editor element is attached to the document + if (editorElement.parentElement) { + this.tippy = tippy(editorElement, { + duration: 0, + getReferenceClientRect: null, + content: this.element, + interactive: true, + trigger: 'manual', + placement: 'top', + hideOnClick: 'toggle', + ...this.tippyOptions, + }) + } } update(view: EditorView, oldState?: EditorState) { @@ -135,6 +143,8 @@ export class BubbleMenuView { return } + this.createTooltip() + // support for CellSelections const { ranges } = selection const from = Math.min(...ranges.map(range => range.$from.pos)) diff --git a/packages/extension-floating-menu/src/floating-menu-plugin.ts b/packages/extension-floating-menu/src/floating-menu-plugin.ts index c060edb9cd6..0749d5ca110 100644 --- a/packages/extension-floating-menu/src/floating-menu-plugin.ts +++ b/packages/extension-floating-menu/src/floating-menu-plugin.ts @@ -31,6 +31,8 @@ export class FloatingMenuView { public tippy: Instance | undefined + public tippyOptions?: Partial + public shouldShow: Exclude = ({ state }) => { const { selection } = state const { $anchor, empty } = selection @@ -50,7 +52,7 @@ export class FloatingMenuView { editor, element, view, - tippyOptions, + tippyOptions = {}, shouldShow, }: FloatingMenuViewProps) { this.editor = editor @@ -64,13 +66,10 @@ export class FloatingMenuView { this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true }) this.editor.on('focus', this.focusHandler) this.editor.on('blur', this.blurHandler) + this.tippyOptions = tippyOptions + // Detaches menu content from its current parent + this.element.remove() 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 = () => { @@ -99,17 +98,26 @@ export class FloatingMenuView { this.hide() } - createTooltip(options: Partial = {}) { - this.tippy = tippy(this.editor.options.element, { - duration: 0, - getReferenceClientRect: null, - content: this.element, - interactive: true, - trigger: 'manual', - placement: 'right', - hideOnClick: 'toggle', - ...options, - }) + createTooltip() { + if (this.tippy) { + return + } + + const { element: editorElement } = this.editor.options + + // Wait until editor element is attached to the document + if (editorElement.parentElement) { + this.tippy = tippy(editorElement, { + duration: 0, + getReferenceClientRect: null, + content: this.element, + interactive: true, + trigger: 'manual', + placement: 'right', + hideOnClick: 'toggle', + ...this.tippyOptions, + }) + } } update(view: EditorView, oldState?: EditorState) { @@ -122,6 +130,8 @@ export class FloatingMenuView { return } + this.createTooltip() + const shouldShow = this.shouldShow?.({ editor: this.editor, view,