Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a667938
feat(core,react): public focus API with editor-UI-aware tracking
YousefED Aug 31, 2026
366979f
fix(core): make the focus unsubscribe idempotent
YousefED Aug 31, 2026
fb26579
fix(react): keep passing the editor to useEditorSelectionChange callb…
YousefED Aug 31, 2026
c230245
test(react): colocate the focus-hook tests as a browser unit test
YousefED Aug 31, 2026
dd80e8c
fix(react): update latest-ref callbacks at layout-effect timing
YousefED Aug 31, 2026
c4ebbfe
fix(react): re-read the focus snapshot when its inputs change
YousefED Aug 31, 2026
dd5b4ab
docs(react): state the latest-callback contract in the hook jsdoc
YousefED Aug 31, 2026
b78f144
refactor(core): drop the focus tracker's reference counting
YousefED Sep 1, 2026
3a4b8bc
refactor(react): drop useEditorFocusChange
YousefED Sep 1, 2026
c9c09bd
refactor: cleanup EventManager
nperez0111 Sep 2, 2026
c178945
feat: add support for the `focus` event type to useEditorState
nperez0111 Sep 2, 2026
a0a19ee
refactor: base useEditorFocus on useEditorState instead
nperez0111 Sep 2, 2026
e1500d1
fix(react): keep per-option focus semantics under useEditorState
YousefED Sep 2, 2026
1506e07
refactor: put back some changes
nperez0111 Sep 2, 2026
119c5ed
Merge branch 'portals-cleanup-v2' into mobile/focus-api
YousefED Sep 5, 2026
4b901ce
test(react): port the useEditorFocus test off `editor.portalElement`
YousefED Sep 5, 2026
e1ca677
Merge branch 'portals-cleanup-v2' into mobile/focus-api
YousefED Sep 5, 2026
f78b104
Merge branch 'portals-cleanup-v2' into mobile/focus-api
YousefED Sep 5, 2026
cfe0afa
Merge branch 'portals-cleanup-v2' into mobile/focus-api
YousefED Sep 5, 2026
a71fd45
Merge branch 'portals-cleanup-v2' into mobile/focus-api
YousefED Sep 6, 2026
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
40 changes: 38 additions & 2 deletions packages/core/src/editor/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
import type { TextCursorPosition } from "./cursorPositionTypes.js";
import {
BlockManager,
EditorFocusOptions,
EventManager,
ExportManager,
ExtensionManager,
Expand Down Expand Up @@ -805,6 +806,14 @@ export class BlockNoteEditor<
* DOM tree, or inside any portal element registered via
* {@link registerPortalElement} (used for floating UI elements like menus and
* toolbars, which may portal outside the editor's DOM tree).
*
* The DOM-tree check starts at the content area's *parent*, so that UI the
* host app renders as `BlockNoteView` children counts too — React places
* those beside the content (see the "Static Formatting Toolbar" example).
* The boundary is whatever the element passed to `editor.mount()` has as
* its parent: `BlockNoteView` always provides a wrapper; mounting bare into
* `<body>`, as the vanilla-JS docs do, makes the whole page count as within
* the editor.
*/
public isWithinEditor = (element: Element): boolean => {
if (this.domElement?.parentElement?.contains(element)) {
Expand All @@ -820,11 +829,17 @@ export class BlockNoteEditor<
return false;
};

public isFocused() {
public isFocused(options?: EditorFocusOptions) {
if (this.headless) {
return false;
}
return this.prosemirrorView?.hasFocus() || false;
const contentFocused = this.prosemirrorView?.hasFocus() || false;
if (!options?.includeEditorUI) {
return contentFocused;
}
const active =
typeof document !== "undefined" ? document.activeElement : null;
return contentFocused || (!!active && this.isWithinEditor(active));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

public headless = true;
Expand Down Expand Up @@ -1380,6 +1395,27 @@ export class BlockNoteEditor<
);
}

/**
* A callback function that runs whenever the editor's content area gains or
* loses DOM focus.
*
* Note that `focused: false` only means the content area itself blurred —
* focus may have moved into the editor's own UI (e.g. a toolbar
* popover's input).
*
* @param callback The callback to execute.
* @returns A function to remove the callback.
*/
public onFocusChange(
callback: (
editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
context: { focused: boolean; event: FocusEvent },
) => void,
options?: EditorFocusOptions,
) {
return this._eventManager.onFocusChange(callback, options);
}

/**
* A callback function that runs when the editor has been mounted.
*
Expand Down
Loading
Loading