fix: don’t initialize tippy on requestAnimationFrame to avoid race conditions (#1820)

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 <ealcantara@gitlab.com>
This commit is contained in:
Enrique
2021-09-07 14:46:45 -04:00
committed by GitHub
parent 67ce72a9ee
commit ca3763d3c2
2 changed files with 56 additions and 36 deletions

View File

@@ -38,6 +38,8 @@ export class BubbleMenuView {
public tippy: Instance | undefined public tippy: Instance | undefined
public tippyOptions?: Partial<Props>
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({ state, from, to }) => { public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({ state, from, to }) => {
const { doc, selection } = state const { doc, selection } = state
const { empty } = selection const { empty } = selection
@@ -59,7 +61,7 @@ export class BubbleMenuView {
editor, editor,
element, element,
view, view,
tippyOptions, tippyOptions = {},
shouldShow, shouldShow,
}: BubbleMenuViewProps) { }: BubbleMenuViewProps) {
this.editor = editor this.editor = editor
@@ -74,13 +76,10 @@ export class BubbleMenuView {
this.view.dom.addEventListener('dragstart', this.dragstartHandler) this.view.dom.addEventListener('dragstart', this.dragstartHandler)
this.editor.on('focus', this.focusHandler) this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler) this.editor.on('blur', this.blurHandler)
this.tippyOptions = tippyOptions
// Detaches menu content from its current parent
this.element.remove()
this.element.style.visibility = 'visible' 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 = () => { mousedownHandler = () => {
@@ -113,17 +112,26 @@ export class BubbleMenuView {
this.hide() this.hide()
} }
createTooltip(options: Partial<Props> = {}) { createTooltip() {
this.tippy = tippy(this.editor.options.element, { if (this.tippy) {
duration: 0, return
getReferenceClientRect: null, }
content: this.element,
interactive: true, const { element: editorElement } = this.editor.options
trigger: 'manual',
placement: 'top', // Wait until editor element is attached to the document
hideOnClick: 'toggle', if (editorElement.parentElement) {
...options, 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) { update(view: EditorView, oldState?: EditorState) {
@@ -135,6 +143,8 @@ export class BubbleMenuView {
return return
} }
this.createTooltip()
// support for CellSelections // support for CellSelections
const { ranges } = selection const { ranges } = selection
const from = Math.min(...ranges.map(range => range.$from.pos)) const from = Math.min(...ranges.map(range => range.$from.pos))

View File

@@ -31,6 +31,8 @@ export class FloatingMenuView {
public tippy: Instance | undefined public tippy: Instance | undefined
public tippyOptions?: Partial<Props>
public shouldShow: Exclude<FloatingMenuPluginProps['shouldShow'], null> = ({ state }) => { public shouldShow: Exclude<FloatingMenuPluginProps['shouldShow'], null> = ({ state }) => {
const { selection } = state const { selection } = state
const { $anchor, empty } = selection const { $anchor, empty } = selection
@@ -50,7 +52,7 @@ export class FloatingMenuView {
editor, editor,
element, element,
view, view,
tippyOptions, tippyOptions = {},
shouldShow, shouldShow,
}: FloatingMenuViewProps) { }: FloatingMenuViewProps) {
this.editor = editor this.editor = editor
@@ -64,13 +66,10 @@ export class FloatingMenuView {
this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true }) this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
this.editor.on('focus', this.focusHandler) this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler) this.editor.on('blur', this.blurHandler)
this.tippyOptions = tippyOptions
// Detaches menu content from its current parent
this.element.remove()
this.element.style.visibility = 'visible' 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 = () => { mousedownHandler = () => {
@@ -99,17 +98,26 @@ export class FloatingMenuView {
this.hide() this.hide()
} }
createTooltip(options: Partial<Props> = {}) { createTooltip() {
this.tippy = tippy(this.editor.options.element, { if (this.tippy) {
duration: 0, return
getReferenceClientRect: null, }
content: this.element,
interactive: true, const { element: editorElement } = this.editor.options
trigger: 'manual',
placement: 'right', // Wait until editor element is attached to the document
hideOnClick: 'toggle', if (editorElement.parentElement) {
...options, 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) { update(view: EditorView, oldState?: EditorState) {
@@ -122,6 +130,8 @@ export class FloatingMenuView {
return return
} }
this.createTooltip()
const shouldShow = this.shouldShow?.({ const shouldShow = this.shouldShow?.({
editor: this.editor, editor: this.editor,
view, view,