From 5afe33eb2a89a0dc7170119e150cc789644e9560 Mon Sep 17 00:00:00 2001 From: Matheus Richard Date: Thu, 2 Jul 2026 14:56:44 -0300 Subject: [PATCH 1/2] Guard focus() against an uninitialized editor `focus()` reads `this.editor`, which is only assigned in connectedCallback. When `focus()` runs before the editor is initialized, the `#isContentFocused` guard evaluates to false and execution falls through to `this.editor.focus()`, throwing "undefined is not an object (evaluating 'this.editor.focus')". This can happen when using Lexxy alongside Turbo and autofocus. Turbo can call `focus()` on the element before the editor is initialized, leading to an error. This commit makes Lexxy return early when `this.editor` is not set. Autofocus still works through the element's own `#handleAutofocus`, which runs after the editor is created. --- src/elements/editor.js | 2 +- test/browser/tests/editor/focus.test.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/elements/editor.js b/src/elements/editor.js index dbe10de0d..089329781 100644 --- a/src/elements/editor.js +++ b/src/elements/editor.js @@ -317,7 +317,7 @@ export class LexicalEditorElement extends HTMLElement { // Skip if the contenteditable already owns focus — the update would be a // no-op but still triggers a full style/layout pass on pages with large // DOMs. - if (this.#isContentFocused) return + if (!this.editor || this.#isContentFocused) return this.editor.focus(() => this.#onFocus()) } diff --git a/test/browser/tests/editor/focus.test.js b/test/browser/tests/editor/focus.test.js index d47a29747..6caf9df1a 100644 --- a/test/browser/tests/editor/focus.test.js +++ b/test/browser/tests/editor/focus.test.js @@ -26,4 +26,21 @@ test.describe("Focus", () => { await expect(editor.content).toBeFocused() }) + + test("focus() on an uninitialized editor is a safe no-op", async ({ page }) => { + const thrown = await page.evaluate(async () => { + await customElements.whenDefined("lexxy-editor") + + const editor = document.createElement("lexxy-editor") + + try { + editor.focus() + return null + } catch (error) { + return error.message + } + }) + + expect(thrown).toBeNull() + }) }) From 9a89c1f606aeb41a91353f4e872589b94c068832 Mon Sep 17 00:00:00 2001 From: Matheus Richard Date: Thu, 2 Jul 2026 15:56:35 -0300 Subject: [PATCH 2/2] Follow style guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Samuel Péchèr --- src/elements/editor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/elements/editor.js b/src/elements/editor.js index 089329781..4a7e58251 100644 --- a/src/elements/editor.js +++ b/src/elements/editor.js @@ -317,9 +317,9 @@ export class LexicalEditorElement extends HTMLElement { // Skip if the contenteditable already owns focus — the update would be a // no-op but still triggers a full style/layout pass on pages with large // DOMs. - if (!this.editor || this.#isContentFocused) return - - this.editor.focus(() => this.#onFocus()) + if (this.editor && !this.#isContentFocused) { + this.editor.focus(() => this.#onFocus()) + } } get #isContentFocused() {