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:
@@ -38,6 +38,8 @@ export class BubbleMenuView {
|
||||
|
||||
public tippy: Instance | undefined
|
||||
|
||||
public tippyOptions?: Partial<Props>
|
||||
|
||||
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({ 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,8 +112,16 @@ export class BubbleMenuView {
|
||||
this.hide()
|
||||
}
|
||||
|
||||
createTooltip(options: Partial<Props> = {}) {
|
||||
this.tippy = tippy(this.editor.options.element, {
|
||||
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,
|
||||
@@ -122,9 +129,10 @@ export class BubbleMenuView {
|
||||
trigger: 'manual',
|
||||
placement: 'top',
|
||||
hideOnClick: 'toggle',
|
||||
...options,
|
||||
...this.tippyOptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
update(view: EditorView, oldState?: EditorState) {
|
||||
const { state, composing } = view
|
||||
@@ -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))
|
||||
|
||||
@@ -31,6 +31,8 @@ export class FloatingMenuView {
|
||||
|
||||
public tippy: Instance | undefined
|
||||
|
||||
public tippyOptions?: Partial<Props>
|
||||
|
||||
public shouldShow: Exclude<FloatingMenuPluginProps['shouldShow'], null> = ({ 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,8 +98,16 @@ export class FloatingMenuView {
|
||||
this.hide()
|
||||
}
|
||||
|
||||
createTooltip(options: Partial<Props> = {}) {
|
||||
this.tippy = tippy(this.editor.options.element, {
|
||||
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,
|
||||
@@ -108,9 +115,10 @@ export class FloatingMenuView {
|
||||
trigger: 'manual',
|
||||
placement: 'right',
|
||||
hideOnClick: 'toggle',
|
||||
...options,
|
||||
...this.tippyOptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
update(view: EditorView, oldState?: EditorState) {
|
||||
const { state, composing } = view
|
||||
@@ -122,6 +130,8 @@ export class FloatingMenuView {
|
||||
return
|
||||
}
|
||||
|
||||
this.createTooltip()
|
||||
|
||||
const shouldShow = this.shouldShow?.({
|
||||
editor: this.editor,
|
||||
view,
|
||||
|
||||
Reference in New Issue
Block a user