Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/elements/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,15 @@ export class LexicalToolbarElement extends HTMLElement {
}
}

#dispatchButtonCommand(event, { dataset: { command, payload } }) {
#dispatchButtonCommand(event, button) {
const { command, payload } = button.dataset
const isKeyboard = event instanceof PointerEvent && event.pointerId === -1

this.editor.update(() => {
this.editor.dispatchCommand(command, payload)
}, { tag: isKeyboard ? SKIP_DOM_SELECTION_TAG : undefined })

if (button.closest(".lexxy-editor__toolbar-dropdown")) this.closeDropdowns()
if (!isKeyboard) this.editor.focus()
}

Expand Down Expand Up @@ -200,14 +202,23 @@ export class LexicalToolbarElement extends HTMLElement {
}

#monitorSelectionChanges() {
this.#listeners.track(this.editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
this.#updateButtonStates()
this.closeDropdowns()
})
this.#listeners.track(this.editor.registerUpdateListener(({ editorState, prevEditorState }) => {
editorState.read(() => this.#updateButtonStates())
if (this.#selectionChanged(editorState, prevEditorState)) this.closeDropdowns()
}))
}

#selectionChanged(editorState, prevEditorState) {
const selection = editorState.read($getSelection)
const previousSelection = prevEditorState.read($getSelection)

if (selection === null || previousSelection === null) {
return selection !== previousSelection
} else {
return !selection.is(previousSelection)
}
}

#monitorHistoryChanges() {
this.#listeners.track(
this.editor.registerCommand(CAN_UNDO_COMMAND, (enabled) => { this.#setButtonDisabled("undo", !enabled) }, COMMAND_PRIORITY_LOW),
Expand Down
39 changes: 39 additions & 0 deletions test/browser/tests/formatting/toolbar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,45 @@ test.describe("Toolbar", () => {
expect(accept).toBeNull()
})

test("dropdown stays open during editor updates that do not change the selection", async ({ page, editor }) => {
await editor.setValue(HELLO_EVERYONE)

const formatPanel = page.locator("lexxy-toolbar-dropdown.lexxy-editor__toolbar-dropdown [data-dropdown-panel]").first()

await page.locator("lexxy-toolbar button[name='format']").click()
await expect(formatPanel).toBeVisible()

// Simulate an upload-driven update: content mutates but the selection is untouched,
// the same way upload progress / placeholder mutations fire while a dropdown is open.
await page.evaluate(async () => {
const element = document.querySelector("lexxy-editor")
await new Promise((resolve) => {
element.editor.update(() => {
const editorState = element.editor._pendingEditorState
const root = editorState._nodeMap.get("root")
const paragraph = editorState._nodeMap.get(root.__first)
const textNode = editorState._nodeMap.get(paragraph.__first)
textNode.setTextContent("Hello everyone uploading…")
}, { onUpdate: resolve })
})
})

await expect(formatPanel).toBeVisible()
})

test("dropdown closes when the selection changes", async ({ page, editor }) => {
await editor.setValue(HELLO_EVERYONE)

const formatPanel = page.locator("lexxy-toolbar-dropdown.lexxy-editor__toolbar-dropdown [data-dropdown-panel]").first()

await page.locator("lexxy-toolbar button[name='format']").click()
await expect(formatPanel).toBeVisible()

await editor.select("everyone")

await expect(formatPanel).toBeHidden()
})

test("external toolbar", async ({ page }) => {
await page.goto("/toolbar-external.html")
await expect(
Expand Down
Loading