diff --git a/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx b/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx index 1510730b..58c8833b 100644 --- a/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx +++ b/packages/client/src/tiptap/core/bubble-menu/bubble-menu-plugin.tsx @@ -27,6 +27,8 @@ export type BubbleMenuViewProps = BubbleMenuPluginProps & { view: EditorView; }; +const ACTIVE_BUBBLE_MENUS: Instance[] = []; + export class BubbleMenuView { public editor: Editor; @@ -178,7 +180,11 @@ export class BubbleMenuView { const cursorAt = selection.$anchor.pos; const from = Math.min(...ranges.map((range) => range.$from.pos)); const to = Math.max(...ranges.map((range) => range.$to.pos)); - const placement = Math.abs(cursorAt - to) <= Math.abs(cursorAt - from) ? 'bottom' : 'top'; + const placement = isNodeSelection(selection) + ? 'top' + : Math.abs(cursorAt - to) <= Math.abs(cursorAt - from) + ? 'bottom-start' + : 'top-start'; const domAtPos = view.domAtPos(from).node as HTMLElement; const nodeDOM = view.nodeDOM(from) as HTMLElement; const node = nodeDOM || domAtPos; @@ -200,7 +206,28 @@ export class BubbleMenuView { return; } + const otherBubbleMenus = ACTIVE_BUBBLE_MENUS.filter( + (instance) => instance.id !== this.tippy?.id && instance.popperInstance && instance.popperInstance.state + ); + const offsetX = this.tippyOptions?.offset?.[0] ?? 0; + const offsetY = otherBubbleMenus.length + ? otherBubbleMenus.reduce((prev, instance, currentIndex, array) => { + const prevY = array[currentIndex - 1] + ? array[currentIndex - 1]?.popperInstance?.state?.modifiersData?.popperOffsets?.y ?? 0 + : 0; + const currentY = instance?.popperInstance?.state?.modifiersData?.popperOffsets?.y ?? 0; + const currentHeight = instance?.popperInstance?.state?.rects?.popper?.height ?? 40; + + if (Math.abs(prevY - currentY) <= currentHeight) { + prev += currentHeight; + } + + return prev; + }, 0) + : this.tippyOptions?.offset?.[1] ?? 10; + this.tippy?.setProps({ + offset: [offsetX, offsetY], placement, getReferenceClientRect: () => { let toMountNode = null; @@ -230,15 +257,31 @@ export class BubbleMenuView { this.show(); } + addActiveBubbleMenu = () => { + const idx = ACTIVE_BUBBLE_MENUS.findIndex((instance) => instance?.id === this.tippy?.id); + if (idx < 0) { + ACTIVE_BUBBLE_MENUS.push(this.tippy); + } + }; + + removeActiveBubbleMenu = () => { + const idx = ACTIVE_BUBBLE_MENUS.findIndex((instance) => instance?.id === this.tippy?.id); + if (idx > -1) { + ACTIVE_BUBBLE_MENUS.splice(idx, 1); + } + }; show() { + this.addActiveBubbleMenu(); this.tippy?.show(); } hide() { + this.removeActiveBubbleMenu(); this.tippy?.hide(); } destroy() { + this.removeActiveBubbleMenu(); this.tippy?.destroy(); this.element.removeEventListener('mousedown', this.mousedownHandler, { capture: true,