1- import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
1+ import { useCallback , useEffect , useRef , useState } from 'react'
22import { posToDOMRect } from '@tiptap/core'
33import { PluginKey } from '@tiptap/pm/state'
44import type { Editor } from '@tiptap/react'
@@ -32,12 +32,10 @@ function hasFormattableSelection(editor: Editor, from: number, to: number): bool
3232 return editor . state . doc . textBetween ( from , to , ' ' ) . trim ( ) . length > 0
3333}
3434
35- // Pin the toolbar to the viewport (fixed) and never attach a scroll listener, so once it's placed for
36- // a selection it stays put while the document scrolls instead of tracking the text — matching Linear.
35+ /** Pins the toolbar to the viewport so it stays put while the document scrolls instead of tracking the text. */
3736const FLOATING_OPTIONS = { strategy : 'fixed' } as const
3837
39- // Render into the body so a transformed/clipping ancestor (e.g. the mothership panels) can't reparent
40- // the fixed-positioned toolbar and shift it off the selection.
38+ /** Renders into the body so a transformed/clipping ancestor can't reparent the fixed toolbar and shift it. */
4139const APPEND_TO_BODY = ( ) => document . body
4240
4341interface EditorBubbleMenuProps {
@@ -58,8 +56,7 @@ export function EditorBubbleMenu({ editor, scrollContainerRef }: EditorBubbleMen
5856 const linkRangeRef = useRef < { from : number ; to : number } | null > ( null )
5957 const isEditingLink = linkValue !== null
6058
61- // Explicit key so `setMeta` can target this menu to reveal it after a drag-select.
62- const bubbleMenuKey = useMemo ( ( ) => new PluginKey ( 'markdownBubbleMenu' ) , [ ] )
59+ const [ bubbleMenuKey ] = useState ( ( ) => new PluginKey ( 'markdownBubbleMenu' ) )
6360 const isPointerDownRef = useRef ( false )
6461
6562 const active = useEditorState ( {
@@ -94,8 +91,6 @@ export function EditorBubbleMenu({ editor, scrollContainerRef }: EditorBubbleMen
9491 }
9592 } , [ editor ] )
9693
97- // Reveal the toolbar only once a drag-select finishes (Linear-style); `shouldShow` keeps it hidden
98- // while the pointer is down. Keyboard selection has no pointer, so it still shows live.
9994 useEffect ( ( ) => {
10095 const dom = editor . view . dom
10196 const onPointerDown = ( ) => {
@@ -106,13 +101,10 @@ export function EditorBubbleMenu({ editor, scrollContainerRef }: EditorBubbleMen
106101 isPointerDownRef . current = false
107102 const { from, to } = editor . state . selection
108103 if ( hasFormattableSelection ( editor , from , to ) ) {
109- // `show` alone leaves the bar visible-but-unpositioned (its updatePosition no-ops until shown),
110- // so a second `updatePosition` anchors it. Both are step-free, so the doc isn't marked dirty.
111104 editor . commands . setMeta ( bubbleMenuKey , 'show' )
112105 editor . commands . setMeta ( bubbleMenuKey , 'updatePosition' )
113106 }
114107 }
115- // A release outside the window delivers no mouseup; clear the flag on blur so it can't stay wedged.
116108 const onWindowBlur = ( ) => {
117109 isPointerDownRef . current = false
118110 }
@@ -175,10 +167,6 @@ export function EditorBubbleMenu({ editor, scrollContainerRef }: EditorBubbleMen
175167 setLinkValue ( null )
176168 }
177169
178- // Freeze the anchor per selection: the rect is computed once (in viewport coordinates) and reused on
179- // every scroll/resize reposition, so the toolbar stays where it first appeared instead of tracking
180- // the moving text — matching Linear. A new selection recomputes it. A selection taller than the
181- // viewport (e.g. select-all) is clamped into the visible area so the bar isn't placed off-screen.
182170 const anchorCacheRef = useRef < { key : string ; rect : DOMRect } | null > ( null )
183171 const resolveAnchor = useCallback ( ( ) => {
184172 const { view, state } = editor
@@ -218,7 +206,6 @@ export function EditorBubbleMenu({ editor, scrollContainerRef }: EditorBubbleMen
218206 // can't be applied to a doc that must not mutate.
219207 if ( ! e . isEditable ) return false
220208 if ( isEditingLink ) return true
221- // Suppressed mid-drag; the pointer-release handler forces it back open once the selection sticks.
222209 if ( isPointerDownRef . current ) return false
223210 return hasFormattableSelection ( e , from , to )
224211 } }
0 commit comments