Skip to content

Commit 59a2b69

Browse files
fix: Tab handling (indentation and toolbar focus) (#1285)
* Added editor option to disable tabbing into toolbars * Made formatting & link toolbars stay hidden after indenting/unindenting using tab/shift+tab * Removed log * Implemented PR feedback
1 parent a7bf806 commit 59a2b69

File tree

5 files changed

+42
-12
lines changed

5 files changed

+42
-12
lines changed

packages/core/src/editor/BlockNoteEditor.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,21 @@ export type BlockNoteEditorOptions<
219219
setIdAttribute?: boolean;
220220

221221
dropCursor?: (opts: any) => Plugin;
222+
223+
/**
224+
Select desired behavior when pressing `Tab` (or `Shift-Tab`). Specifically,
225+
what should happen when a user has selected multiple blocks while a toolbar
226+
is open:
227+
- `"prefer-navigate-ui"`: Change focus to the toolbar. The user needs to
228+
first press `Escape` to close the toolbar, and can then indent multiple
229+
blocks. Better for keyboard accessibility.
230+
- `"prefer-indent"`: Regardless of whether toolbars are open, indent the
231+
selection of blocks. In this case, it's not possible to navigate toolbars
232+
with the keyboard.
233+
234+
@default "prefer-navigate-ui"
235+
*/
236+
tabBehavior: "prefer-navigate-ui" | "prefer-indent";
222237
};
223238

224239
const blockNoteTipTapOptions = {
@@ -395,6 +410,7 @@ export class BlockNoteEditor<
395410
tableHandles: checkDefaultBlockTypeInSchema("table", this),
396411
dropCursor: this.options.dropCursor ?? dropCursor,
397412
placeholders: newOptions.placeholders,
413+
tabBehavior: newOptions.tabBehavior,
398414
});
399415

400416
// add extensions from _tiptapOptions

packages/core/src/editor/BlockNoteExtensions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type ExtensionOptions<
6767
tableHandles: boolean;
6868
dropCursor: (opts: any) => Plugin;
6969
placeholders: Record<string | "default", string>;
70+
tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
7071
};
7172

7273
/**
@@ -200,6 +201,7 @@ const getTipTapExtensions = <
200201
}),
201202
KeyboardShortcutsExtension.configure({
202203
editor: opts.editor,
204+
tabBehavior: opts.tabBehavior,
203205
}),
204206
BlockGroup.configure({
205207
domAttributes: opts.domAttributes,

packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ export class FormattingToolbarView implements PluginView {
139139
// Wrapping in a setTimeout gives enough time to wait for the blur event to
140140
// occur before updating the toolbar.
141141
const { state, composing } = view;
142-
const { doc, selection } = state;
142+
const { selection } = state;
143143
const isSame =
144-
oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
144+
oldState &&
145+
oldState.selection.from === state.selection.from &&
146+
oldState.selection.to === state.selection.to;
145147

146148
if (composing || isSame) {
147149
return;

packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
1616

1717
export const KeyboardShortcutsExtension = Extension.create<{
1818
editor: BlockNoteEditor<any, any, any>;
19+
tabBehavior: "prefer-navigate-ui" | "prefer-indent";
1920
}>({
2021
priority: 50,
2122

@@ -479,9 +480,10 @@ export const KeyboardShortcutsExtension = Extension.create<{
479480
// editor since the browser will try to use tab for keyboard navigation.
480481
Tab: () => {
481482
if (
482-
this.options.editor.formattingToolbar?.shown ||
483-
this.options.editor.linkToolbar?.shown ||
484-
this.options.editor.filePanel?.shown
483+
this.options.tabBehavior !== "prefer-indent" &&
484+
(this.options.editor.formattingToolbar?.shown ||
485+
this.options.editor.linkToolbar?.shown ||
486+
this.options.editor.filePanel?.shown)
485487
) {
486488
// don't handle tabs if a toolbar is shown, so we can tab into / out of it
487489
return false;
@@ -491,9 +493,10 @@ export const KeyboardShortcutsExtension = Extension.create<{
491493
},
492494
"Shift-Tab": () => {
493495
if (
494-
this.options.editor.formattingToolbar?.shown ||
495-
this.options.editor.linkToolbar?.shown ||
496-
this.options.editor.filePanel?.shown
496+
this.options.tabBehavior !== "prefer-indent" &&
497+
(this.options.editor.formattingToolbar?.shown ||
498+
this.options.editor.linkToolbar?.shown ||
499+
this.options.editor.filePanel?.shown)
497500
) {
498501
// don't handle tabs if a toolbar is shown, so we can tab into / out of it
499502
return false;

packages/core/src/extensions/LinkToolbar/LinkToolbarPlugin.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getMarkRange, posToDOMRect, Range } from "@tiptap/core";
22

33
import { EditorView } from "@tiptap/pm/view";
44
import { Mark } from "prosemirror-model";
5-
import { Plugin, PluginKey, PluginView } from "prosemirror-state";
5+
import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
66

77
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
88
import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
@@ -52,7 +52,7 @@ class LinkToolbarView implements PluginView {
5252

5353
this.startMenuUpdateTimer = () => {
5454
this.menuUpdateTimer = setTimeout(() => {
55-
this.update();
55+
this.update(this.pmView);
5656
}, 250);
5757
};
5858

@@ -190,8 +190,15 @@ class LinkToolbarView implements PluginView {
190190
}
191191
}
192192

193-
update() {
194-
if (!this.pmView.hasFocus()) {
193+
update(view: EditorView, oldState?: EditorState) {
194+
const { state } = view;
195+
196+
const isSame =
197+
oldState &&
198+
oldState.selection.from === state.selection.from &&
199+
oldState.selection.to === state.selection.to;
200+
201+
if (isSame || !this.pmView.hasFocus()) {
195202
return;
196203
}
197204

0 commit comments

Comments
 (0)