fix: preserve image selection while hovering toolbar
This commit is contained in:
@@ -1,9 +1,21 @@
|
||||
import type {
|
||||
BlockNoteEditor,
|
||||
BlockSchema,
|
||||
InlineContentSchema,
|
||||
StyleSchema,
|
||||
} from '@blocknote/core'
|
||||
import { useEffect, useRef, type RefObject } from 'react'
|
||||
|
||||
type RectLike = Pick<DOMRect, 'left' | 'right' | 'top' | 'bottom'>
|
||||
|
||||
const HOVER_BRIDGE_PADDING_X = 8
|
||||
const HOVER_BRIDGE_PADDING_Y = 8
|
||||
const FORMATTING_TOOLBAR_FILE_BLOCK_TYPES = new Set([
|
||||
'audio',
|
||||
'file',
|
||||
'image',
|
||||
'video',
|
||||
])
|
||||
|
||||
function isVisibleRect(rect: RectLike) {
|
||||
return rect.right > rect.left && rect.bottom > rect.top
|
||||
@@ -90,6 +102,32 @@ export function shouldSuppressFormattingToolbarHoverUpdate({
|
||||
)
|
||||
}
|
||||
|
||||
function getActiveFormattingToolbarFileBlockId(
|
||||
editor: BlockNoteEditor<BlockSchema, InlineContentSchema, StyleSchema>,
|
||||
) {
|
||||
const selectedBlock =
|
||||
editor.getSelection()?.blocks[0] ?? editor.getTextCursorPosition().block
|
||||
|
||||
return FORMATTING_TOOLBAR_FILE_BLOCK_TYPES.has(selectedBlock.type)
|
||||
? selectedBlock.id
|
||||
: null
|
||||
}
|
||||
|
||||
function restoreFormattingToolbarFileBlockSelection(
|
||||
editor: BlockNoteEditor<BlockSchema, InlineContentSchema, StyleSchema>,
|
||||
selectedFileBlockIdRef: RefObject<string | null>,
|
||||
) {
|
||||
const selectedFileBlockId = selectedFileBlockIdRef.current
|
||||
if (!selectedFileBlockId) return
|
||||
if (getActiveFormattingToolbarFileBlockId(editor) === selectedFileBlockId) return
|
||||
|
||||
try {
|
||||
editor.setTextCursorPosition(selectedFileBlockId)
|
||||
} catch {
|
||||
// The image block may have been deleted or replaced while the toolbar stayed open.
|
||||
}
|
||||
}
|
||||
|
||||
function useLastSelectedFormattingToolbarFileBlockId(
|
||||
selectedFileBlockId: string | null,
|
||||
isOpen: boolean,
|
||||
@@ -120,10 +158,12 @@ function getFormattingToolbarHoverGuardEnvironment(container: HTMLElement | null
|
||||
}
|
||||
|
||||
function createFormattingToolbarHoverGuardHandler({
|
||||
editor,
|
||||
container,
|
||||
doc,
|
||||
selectedFileBlockIdRef,
|
||||
}: {
|
||||
editor: BlockNoteEditor<BlockSchema, InlineContentSchema, StyleSchema>
|
||||
container: HTMLElement
|
||||
doc: Document
|
||||
selectedFileBlockIdRef: RefObject<string | null>
|
||||
@@ -141,15 +181,18 @@ function createFormattingToolbarHoverGuardHandler({
|
||||
return
|
||||
}
|
||||
|
||||
restoreFormattingToolbarFileBlockSelection(editor, selectedFileBlockIdRef)
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
export function useBlockNoteFormattingToolbarHoverGuard({
|
||||
editor,
|
||||
container,
|
||||
selectedFileBlockId,
|
||||
isOpen,
|
||||
}: {
|
||||
editor: BlockNoteEditor<BlockSchema, InlineContentSchema, StyleSchema>
|
||||
container: HTMLElement | null
|
||||
selectedFileBlockId: string | null
|
||||
isOpen: boolean
|
||||
@@ -166,6 +209,7 @@ export function useBlockNoteFormattingToolbarHoverGuard({
|
||||
if (!environment) return
|
||||
|
||||
const handleMouseMove = createFormattingToolbarHoverGuardHandler({
|
||||
editor,
|
||||
container: environment.container,
|
||||
doc: environment.doc,
|
||||
selectedFileBlockIdRef: lastSelectedFileBlockIdRef,
|
||||
@@ -175,5 +219,5 @@ export function useBlockNoteFormattingToolbarHoverGuard({
|
||||
return () => {
|
||||
environment.view.removeEventListener('mousemove', handleMouseMove, true)
|
||||
}
|
||||
}, [container, isOpen, lastSelectedFileBlockIdRef])
|
||||
}, [container, editor, isOpen, lastSelectedFileBlockIdRef])
|
||||
}
|
||||
|
||||
@@ -476,6 +476,7 @@ export function TolariaFormattingToolbarController(props: {
|
||||
})
|
||||
|
||||
useBlockNoteFormattingToolbarHoverGuard({
|
||||
editor,
|
||||
container:
|
||||
editor.domElement?.closest('.editor__blocknote-container') ??
|
||||
editor.domElement ??
|
||||
|
||||
@@ -91,6 +91,12 @@ async function moveMouseInSteps(
|
||||
}
|
||||
}
|
||||
|
||||
async function expectImageBlockSelected(image: ReturnType<Page['locator']>) {
|
||||
await expect.poll(async () => (
|
||||
image.evaluate((node) => Boolean(node.closest('.ProseMirror-selectednode')))
|
||||
)).toBe(true)
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testInfo.setTimeout(90_000)
|
||||
tempVaultDir = createFixtureVaultCopy()
|
||||
@@ -117,9 +123,12 @@ test('image toolbar stays usable while the pointer crosses onto its controls', a
|
||||
|
||||
await expect(toolbar).toBeVisible({ timeout: 5_000 })
|
||||
await expect(replaceButton).toBeVisible()
|
||||
await expectImageBlockSelected(image)
|
||||
|
||||
const replaceButtonBox = await replaceButton.boundingBox()
|
||||
expect(replaceButtonBox).not.toBeNull()
|
||||
const toolbarBox = await toolbar.boundingBox()
|
||||
expect(toolbarBox).not.toBeNull()
|
||||
|
||||
await moveMouseInSteps(
|
||||
page,
|
||||
@@ -127,6 +136,24 @@ test('image toolbar stays usable while the pointer crosses onto its controls', a
|
||||
x: imageBox!.x + imageBox!.width / 2,
|
||||
y: imageBox!.y + imageBox!.height / 2,
|
||||
},
|
||||
{
|
||||
x: toolbarBox!.x + toolbarBox!.width / 2,
|
||||
y: toolbarBox!.y + toolbarBox!.height + 10,
|
||||
},
|
||||
{ steps: 12, stepDelayMs: 35 },
|
||||
)
|
||||
|
||||
await page.waitForTimeout(180)
|
||||
await expect(toolbar).toBeVisible()
|
||||
await expect(replaceButton).toBeVisible()
|
||||
await expectImageBlockSelected(image)
|
||||
|
||||
await moveMouseInSteps(
|
||||
page,
|
||||
{
|
||||
x: toolbarBox!.x + toolbarBox!.width / 2,
|
||||
y: toolbarBox!.y + toolbarBox!.height + 10,
|
||||
},
|
||||
{
|
||||
x: replaceButtonBox!.x + replaceButtonBox!.width / 2,
|
||||
y: replaceButtonBox!.y + replaceButtonBox!.height / 2,
|
||||
@@ -136,6 +163,7 @@ test('image toolbar stays usable while the pointer crosses onto its controls', a
|
||||
|
||||
await expect(toolbar).toBeVisible()
|
||||
await expect(replaceButton).toBeVisible()
|
||||
await expectImageBlockSelected(image)
|
||||
|
||||
await replaceButton.click()
|
||||
await expect(page.locator('.bn-panel-popover')).toBeVisible()
|
||||
|
||||
Reference in New Issue
Block a user