mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-27 14:59:27 +08:00
refactor(extension/bubble-menu): add debounce to bubble menu updates (#3385)
* refactor(extension/bubble-menu): add debounce to bubble menu updates * fix: change default duration in react bubble menu demo
This commit is contained in:
parent
1f549c0773
commit
cd5fd606d1
@ -4,7 +4,11 @@
|
||||
<input type="checkbox" :checked="isEditable" @change="() => isEditable = !isEditable">
|
||||
Editable
|
||||
</div>
|
||||
<bubble-menu :editor="editor" :tippy-options="{ duration: 100 }" v-if="editor">
|
||||
<bubble-menu
|
||||
:editor="editor"
|
||||
:tippy-options="{ duration: 100 }"
|
||||
v-if="editor"
|
||||
>
|
||||
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
|
||||
bold
|
||||
</button>
|
||||
|
@ -25,6 +25,14 @@ Type: `HTMLElement`
|
||||
|
||||
Default: `null`
|
||||
|
||||
### delay
|
||||
The `BubbleMenu` debounces the `update` method to allow the bubble menu to not be updated on every selection update. This can be controlled in milliseconds.
|
||||
The BubbleMenuPlugin will come with a default delay of 250ms. This can be deactivated, by setting the delay to `0` which deactivates the debounce.
|
||||
|
||||
Type: `Number`
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
### tippyOptions
|
||||
Under the hood, the `BubbleMenu` uses [tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/). You can directly pass options to it.
|
||||
|
||||
|
25
package-lock.json
generated
25
package-lock.json
generated
@ -6790,6 +6790,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.187",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.187.tgz",
|
||||
"integrity": "sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"dev": true,
|
||||
@ -13603,8 +13609,9 @@
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
@ -19356,6 +19363,10 @@
|
||||
"prosemirror-view": "^1.28.2",
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.187",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
@ -24872,6 +24883,8 @@
|
||||
"@tiptap/extension-bubble-menu": {
|
||||
"version": "file:packages/extension-bubble-menu",
|
||||
"requires": {
|
||||
"@types/lodash": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"prosemirror-state": "^1.4.1",
|
||||
"prosemirror-view": "^1.28.2",
|
||||
"tippy.js": "^6.3.7"
|
||||
@ -25207,6 +25220,12 @@
|
||||
"version": "0.0.29",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.187",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.187.tgz",
|
||||
"integrity": "sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"dev": true
|
||||
@ -29770,6 +29789,8 @@
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.debounce": {
|
||||
|
@ -33,5 +33,9 @@
|
||||
"url": "https://github.com/ueberdosis/tiptap",
|
||||
"directory": "packages/extension-bubble-menu"
|
||||
},
|
||||
"sideEffects": false
|
||||
"sideEffects": false,
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.187",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
isTextSelection,
|
||||
posToDOMRect,
|
||||
} from '@tiptap/core'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
import tippy, { Instance, Props } from 'tippy.js'
|
||||
@ -13,6 +14,7 @@ export interface BubbleMenuPluginProps {
|
||||
editor: Editor,
|
||||
element: HTMLElement,
|
||||
tippyOptions?: Partial<Props>,
|
||||
delay?: number,
|
||||
shouldShow?: ((props: {
|
||||
editor: Editor,
|
||||
view: EditorView,
|
||||
@ -40,6 +42,8 @@ export class BubbleMenuView {
|
||||
|
||||
public tippyOptions?: Partial<Props>
|
||||
|
||||
public delay: number
|
||||
|
||||
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({
|
||||
view,
|
||||
state,
|
||||
@ -79,11 +83,13 @@ export class BubbleMenuView {
|
||||
element,
|
||||
view,
|
||||
tippyOptions = {},
|
||||
delay = 250,
|
||||
shouldShow,
|
||||
}: BubbleMenuViewProps) {
|
||||
this.editor = editor
|
||||
this.element = element
|
||||
this.view = view
|
||||
this.delay = delay
|
||||
|
||||
if (shouldShow) {
|
||||
this.shouldShow = shouldShow
|
||||
@ -157,6 +163,21 @@ export class BubbleMenuView {
|
||||
}
|
||||
|
||||
update(view: EditorView, oldState?: EditorState) {
|
||||
const { state } = view
|
||||
const hasValidSelection = state.selection.$from.pos !== state.selection.$to.pos
|
||||
|
||||
if (hasValidSelection) {
|
||||
if (this.delay > 0) {
|
||||
debounce(this.updateHandler, this.delay)(view, oldState)
|
||||
} else {
|
||||
this.updateHandler(view, oldState)
|
||||
}
|
||||
} else {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
updateHandler = (view: EditorView, oldState?: EditorState) => {
|
||||
const { state, composing } = view
|
||||
const { doc, selection } = state
|
||||
const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection)
|
||||
|
@ -14,6 +14,7 @@ export const BubbleMenu = Extension.create<BubbleMenuOptions>({
|
||||
element: null,
|
||||
tippyOptions: {},
|
||||
pluginKey: 'bubbleMenu',
|
||||
delay: undefined,
|
||||
shouldShow: null,
|
||||
}
|
||||
},
|
||||
@ -29,6 +30,7 @@ export const BubbleMenu = Extension.create<BubbleMenuOptions>({
|
||||
editor: this.editor,
|
||||
element: this.options.element,
|
||||
tippyOptions: this.options.tippyOptions,
|
||||
delay: this.options.delay,
|
||||
shouldShow: this.options.shouldShow,
|
||||
}),
|
||||
]
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { BubbleMenuPlugin, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
|
||||
import React, {
|
||||
useEffect, useState,
|
||||
} from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
|
||||
export type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'> & {
|
||||
className?: string,
|
||||
children: React.ReactNode
|
||||
}
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
export const BubbleMenu = (props: BubbleMenuProps) => {
|
||||
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
||||
@ -23,26 +22,21 @@ export const BubbleMenu = (props: BubbleMenuProps) => {
|
||||
}
|
||||
|
||||
const {
|
||||
pluginKey = 'bubbleMenu',
|
||||
editor,
|
||||
tippyOptions = {},
|
||||
shouldShow = null,
|
||||
pluginKey = 'bubbleMenu', editor, tippyOptions = {}, delay, shouldShow = null,
|
||||
} = props
|
||||
|
||||
const plugin = BubbleMenuPlugin({
|
||||
pluginKey,
|
||||
delay,
|
||||
editor,
|
||||
element,
|
||||
tippyOptions,
|
||||
pluginKey,
|
||||
shouldShow,
|
||||
tippyOptions,
|
||||
})
|
||||
|
||||
editor.registerPlugin(plugin)
|
||||
return () => editor.unregisterPlugin(pluginKey)
|
||||
}, [
|
||||
props.editor,
|
||||
element,
|
||||
])
|
||||
}, [props.editor, element])
|
||||
|
||||
return (
|
||||
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
||||
|
@ -5,6 +5,7 @@ export interface BubbleMenuInterface extends Vue {
|
||||
pluginKey: BubbleMenuPluginProps['pluginKey'],
|
||||
editor: BubbleMenuPluginProps['editor'],
|
||||
tippyOptions: BubbleMenuPluginProps['tippyOptions'],
|
||||
delay: BubbleMenuPluginProps['delay'],
|
||||
shouldShow: BubbleMenuPluginProps['shouldShow'],
|
||||
}
|
||||
|
||||
@ -22,6 +23,10 @@ export const BubbleMenu: Component = {
|
||||
required: true,
|
||||
},
|
||||
|
||||
delay: {
|
||||
type: Number as PropType<BubbleMenuPluginProps['delay']>,
|
||||
},
|
||||
|
||||
tippyOptions: {
|
||||
type: Object as PropType<BubbleMenuPluginProps['tippyOptions']>,
|
||||
default: () => ({}),
|
||||
@ -43,11 +48,12 @@ export const BubbleMenu: Component = {
|
||||
|
||||
this.$nextTick(() => {
|
||||
editor.registerPlugin(BubbleMenuPlugin({
|
||||
pluginKey: this.pluginKey,
|
||||
delay: this.delay,
|
||||
editor,
|
||||
element: this.$el as HTMLElement,
|
||||
tippyOptions: this.tippyOptions,
|
||||
pluginKey: this.pluginKey,
|
||||
shouldShow: this.shouldShow,
|
||||
tippyOptions: this.tippyOptions,
|
||||
}))
|
||||
})
|
||||
},
|
||||
|
@ -24,6 +24,11 @@ export const BubbleMenu = defineComponent({
|
||||
required: true,
|
||||
},
|
||||
|
||||
delay: {
|
||||
type: Number as PropType<BubbleMenuPluginProps['delay']>,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
tippyOptions: {
|
||||
type: Object as PropType<BubbleMenuPluginProps['tippyOptions']>,
|
||||
default: () => ({}),
|
||||
@ -40,18 +45,20 @@ export const BubbleMenu = defineComponent({
|
||||
|
||||
onMounted(() => {
|
||||
const {
|
||||
pluginKey,
|
||||
delay,
|
||||
editor,
|
||||
tippyOptions,
|
||||
pluginKey,
|
||||
shouldShow,
|
||||
tippyOptions,
|
||||
} = props
|
||||
|
||||
editor.registerPlugin(BubbleMenuPlugin({
|
||||
pluginKey,
|
||||
delay,
|
||||
editor,
|
||||
element: root.value as HTMLElement,
|
||||
tippyOptions,
|
||||
pluginKey,
|
||||
shouldShow,
|
||||
tippyOptions,
|
||||
}))
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user