Skip to content

Commit 535a1b5

Browse files
fix: Scrolling responsiveness & UI element overflow (#250)
* Fixed scroll overflow issue and improved responsiveness * Fixed typing * Big cleanup - moved fix from factory component to menu/toolbar plugins * Removed `referenceRect` from all dynamic params * Implemented PR feedback
1 parent eeda50b commit 535a1b5

16 files changed

+125
-115
lines changed

examples/vanilla/src/ui/blockSideMenuFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ export const blockSideMenuFactory: BlockSideMenuFactory<DefaultBlockSchema> = (
3737
container.style.display = "block";
3838
}
3939

40-
container.style.top = params.referenceRect.y + "px";
40+
container.style.top = staticParams.getReferenceRect()!.y + "px";
4141
container.style.left =
42-
params.referenceRect.x - container.offsetWidth + "px";
42+
staticParams.getReferenceRect()!.x - container.offsetWidth + "px";
4343
},
4444
hide: () => {
4545
container.style.display = "none";

examples/vanilla/src/ui/formattingToolbarFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export const formattingToolbarFactory: FormattingToolbarFactory<
3838
"bold" in staticParams.editor.getActiveStyles()
3939
? "unset bold"
4040
: "set bold";
41-
container.style.top = params.referenceRect.y + "px";
42-
container.style.left = params.referenceRect.x + "px";
41+
container.style.top = staticParams.getReferenceRect()!.y + "px";
42+
container.style.left = staticParams.getReferenceRect()!.x + "px";
4343
},
4444
hide: () => {
4545
container.style.display = "none";

examples/vanilla/src/ui/hyperlinkToolbarFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ export const hyperlinkToolbarFactory: HyperlinkToolbarFactory = (
4343
container.style.display = "block";
4444
}
4545

46-
container.style.top = params.referenceRect.y + "px";
47-
container.style.left = params.referenceRect.x + "px";
46+
container.style.top = staticParams.getReferenceRect()!.y + "px";
47+
container.style.left = staticParams.getReferenceRect()!.x + "px";
4848
},
4949
hide: () => {
5050
container.style.display = "none";

examples/vanilla/src/ui/slashMenuFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ export const slashMenuFactory: SuggestionsMenuFactory<
5353
container.style.display = "block";
5454
}
5555

56-
container.style.top = params.referenceRect.y + "px";
57-
container.style.left = params.referenceRect.x + "px";
56+
container.style.top = staticParams.getReferenceRect()!.y + "px";
57+
container.style.left = staticParams.getReferenceRect()!.x + "px";
5858
},
5959
hide: () => {
6060
container.style.display = "none";

packages/core/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ export type BlockSideMenuStaticParams<BSchema extends BlockSchema> = {
1212

1313
freezeMenu: () => void;
1414
unfreezeMenu: () => void;
15+
16+
getReferenceRect: () => DOMRect;
1517
};
1618

1719
export type BlockSideMenuDynamicParams<BSchema extends BlockSchema> = {
1820
block: Block<BSchema>;
19-
20-
referenceRect: DOMRect;
2121
};
2222

2323
export type BlockSideMenu<BSchema extends BlockSchema> = EditorElement<

packages/core/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ export class BlockMenuView<BSchema extends BlockSchema> {
250250
menuOpen = false;
251251
menuFrozen = false;
252252

253+
private lastPosition: DOMRect | undefined;
254+
253255
constructor({
254256
tiptapEditor,
255257
editor,
@@ -272,9 +274,6 @@ export class BlockMenuView<BSchema extends BlockSchema> {
272274
// Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
273275
document.body.addEventListener("mousemove", this.onMouseMove, true);
274276

275-
// Makes menu scroll with the page.
276-
document.addEventListener("scroll", this.onScroll);
277-
278277
// Hides and unfreezes the menu whenever the user selects the editor with the mouse or presses a key.
279278
// TODO: Better integration with suggestions menu and only editor scope?
280279
document.body.addEventListener("mousedown", this.onMouseDown, true);
@@ -463,20 +462,6 @@ export class BlockMenuView<BSchema extends BlockSchema> {
463462
}
464463
};
465464

466-
onScroll = () => {
467-
// Editor itself may have padding or other styling which affects size/position, so we get the boundingRect of
468-
// the first child (i.e. the blockGroup that wraps all blocks in the editor) for a more accurate bounding box.
469-
const editorBoundingBox = (
470-
this.ttEditor.view.dom.firstChild! as HTMLElement
471-
).getBoundingClientRect();
472-
473-
this.horizontalPosAnchor = editorBoundingBox.x;
474-
475-
if (this.menuOpen) {
476-
this.blockMenu.render(this.getDynamicParams(), false);
477-
}
478-
};
479-
480465
destroy() {
481466
if (this.menuOpen) {
482467
this.menuOpen = false;
@@ -487,7 +472,6 @@ export class BlockMenuView<BSchema extends BlockSchema> {
487472
this.ttEditor.view.dom.removeEventListener("dragstart", this.onDragStart);
488473
document.body.removeEventListener("drop", this.onDrop);
489474
document.body.removeEventListener("mousedown", this.onMouseDown);
490-
document.removeEventListener("scroll", this.onScroll);
491475
document.body.removeEventListener("keydown", this.onKeyDown);
492476
}
493477

@@ -556,23 +540,32 @@ export class BlockMenuView<BSchema extends BlockSchema> {
556540
unfreezeMenu: () => {
557541
this.menuFrozen = false;
558542
},
543+
getReferenceRect: () => {
544+
if (!this.menuOpen) {
545+
if (this.lastPosition === undefined) {
546+
throw new Error(
547+
"Attempted to access block reference rect before rendering block side menu."
548+
);
549+
}
550+
551+
return this.lastPosition;
552+
}
553+
554+
const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
555+
const blockContentBoundingBox = blockContent.getBoundingClientRect();
556+
if (this.horizontalPosAnchoredAtRoot) {
557+
blockContentBoundingBox.x = this.horizontalPosAnchor;
558+
}
559+
this.lastPosition = blockContentBoundingBox;
560+
561+
return blockContentBoundingBox;
562+
},
559563
};
560564
}
561565

562566
getDynamicParams(): BlockSideMenuDynamicParams<BSchema> {
563-
const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
564-
const blockContentBoundingBox = blockContent.getBoundingClientRect();
565-
566567
return {
567568
block: this.editor.getBlock(this.hoveredBlock!.getAttribute("data-id")!)!,
568-
referenceRect: new DOMRect(
569-
this.horizontalPosAnchoredAtRoot
570-
? this.horizontalPosAnchor
571-
: blockContentBoundingBox.x,
572-
blockContentBoundingBox.y,
573-
blockContentBoundingBox.width,
574-
blockContentBoundingBox.height
575-
),
576569
};
577570
}
578571
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import { BlockSchema } from "../Blocks/api/blockTypes";
44

55
export type FormattingToolbarStaticParams<BSchema extends BlockSchema> = {
66
editor: BlockNoteEditor<BSchema>;
7-
};
87

9-
export type FormattingToolbarDynamicParams = {
10-
referenceRect: DOMRect;
8+
getReferenceRect: () => DOMRect;
119
};
1210

13-
export type FormattingToolbar = EditorElement<
14-
FormattingToolbarDynamicParams
15-
>;
11+
export type FormattingToolbarDynamicParams = {};
12+
13+
export type FormattingToolbar = EditorElement<FormattingToolbarDynamicParams>;
1614
export type FormattingToolbarFactory<BSchema extends BlockSchema> =
1715
ElementFactory<
1816
FormattingToolbarStaticParams<BSchema>,

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

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { EditorView } from "prosemirror-view";
99
import { BlockNoteEditor, BlockSchema } from "../..";
1010
import {
1111
FormattingToolbar,
12-
FormattingToolbarDynamicParams,
1312
FormattingToolbarFactory,
1413
FormattingToolbarStaticParams,
1514
} from "./FormattingToolbarFactoryTypes";
@@ -44,6 +43,8 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
4443

4544
public prevWasEditable: boolean | null = null;
4645

46+
private lastPosition: DOMRect | undefined;
47+
4748
public shouldShow: (props: {
4849
view: EditorView;
4950
state: EditorState;
@@ -80,8 +81,6 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
8081

8182
this.ttEditor.on("focus", this.focusHandler);
8283
this.ttEditor.on("blur", this.blurHandler);
83-
84-
document.addEventListener("scroll", this.scrollHandler);
8584
}
8685

8786
viewMousedownHandler = () => {
@@ -129,12 +128,6 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
129128
}
130129
};
131130

132-
scrollHandler = () => {
133-
if (this.toolbarIsOpen) {
134-
this.formattingToolbar.render(this.getDynamicParams(), false);
135-
}
136-
};
137-
138131
update(view: EditorView, oldState?: EditorState) {
139132
const { state, composing } = view;
140133
const { doc, selection } = state;
@@ -170,7 +163,7 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
170163
!this.preventShow &&
171164
(shouldShow || this.preventHide)
172165
) {
173-
this.formattingToolbar.render(this.getDynamicParams(), true);
166+
this.formattingToolbar.render({}, true);
174167
this.toolbarIsOpen = true;
175168

176169
return;
@@ -182,7 +175,7 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
182175
!this.preventShow &&
183176
(shouldShow || this.preventHide)
184177
) {
185-
this.formattingToolbar.render(this.getDynamicParams(), false);
178+
this.formattingToolbar.render({}, false);
186179
return;
187180
}
188181

@@ -206,8 +199,6 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
206199

207200
this.ttEditor.off("focus", this.focusHandler);
208201
this.ttEditor.off("blur", this.blurHandler);
209-
210-
document.removeEventListener("scroll", this.scrollHandler);
211202
}
212203

213204
getSelectionBoundingBox() {
@@ -233,12 +224,22 @@ export class FormattingToolbarView<BSchema extends BlockSchema> {
233224
getStaticParams(): FormattingToolbarStaticParams<BSchema> {
234225
return {
235226
editor: this.editor,
236-
};
237-
}
238-
239-
getDynamicParams(): FormattingToolbarDynamicParams {
240-
return {
241-
referenceRect: this.getSelectionBoundingBox(),
227+
getReferenceRect: () => {
228+
if (!this.toolbarIsOpen) {
229+
if (this.lastPosition === undefined) {
230+
throw new Error(
231+
"Attempted to access selection reference rect before rendering formatting toolbar."
232+
);
233+
}
234+
235+
return this.lastPosition;
236+
}
237+
238+
const selectionBoundingBox = this.getSelectionBoundingBox();
239+
this.lastPosition = selectionBoundingBox;
240+
241+
return selectionBoundingBox;
242+
},
242243
};
243244
}
244245
}

packages/core/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { EditorElement, ElementFactory } from "../../shared/EditorElement";
33
export type HyperlinkToolbarStaticParams = {
44
editHyperlink: (url: string, text: string) => void;
55
deleteHyperlink: () => void;
6+
7+
getReferenceRect: () => DOMRect;
68
};
79

810
export type HyperlinkToolbarDynamicParams = {
911
url: string;
1012
text: string;
11-
12-
referenceRect: DOMRect;
1313
};
1414

1515
export type HyperlinkToolbar = EditorElement<HyperlinkToolbarDynamicParams>;

packages/core/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class HyperlinkToolbarView {
3636
hyperlinkMark: Mark | undefined;
3737
hyperlinkMarkRange: Range | undefined;
3838

39+
private lastPosition: DOMRect | undefined;
40+
3941
constructor({ editor, hyperlinkToolbarFactory }: HyperlinkToolbarViewProps) {
4042
this.editor = editor;
4143

@@ -58,7 +60,6 @@ class HyperlinkToolbarView {
5860

5961
this.editor.view.dom.addEventListener("mouseover", this.mouseOverHandler);
6062
document.addEventListener("click", this.clickHandler, true);
61-
document.addEventListener("scroll", this.scrollHandler);
6263
}
6364

6465
mouseOverHandler = (event: MouseEvent) => {
@@ -120,12 +121,6 @@ class HyperlinkToolbarView {
120121
}
121122
};
122123

123-
scrollHandler = () => {
124-
if (this.hyperlinkMark !== undefined) {
125-
this.hyperlinkToolbar.render(this.getDynamicParams(), false);
126-
}
127-
};
128-
129124
update() {
130125
if (!this.editor.view.hasFocus()) {
131126
return;
@@ -220,7 +215,6 @@ class HyperlinkToolbarView {
220215
"mouseover",
221216
this.mouseOverHandler
222217
);
223-
document.removeEventListener("scroll", this.scrollHandler);
224218
}
225219

226220
getStaticParams(): HyperlinkToolbarStaticParams {
@@ -255,6 +249,26 @@ class HyperlinkToolbarView {
255249

256250
this.hyperlinkToolbar.hide();
257251
},
252+
getReferenceRect: () => {
253+
if (!this.hyperlinkMark) {
254+
if (this.lastPosition === undefined) {
255+
throw new Error(
256+
"Attempted to access hyperlink reference rect before rendering hyperlink toolbar."
257+
);
258+
}
259+
260+
return this.lastPosition;
261+
}
262+
263+
const hyperlinkBoundingBox = posToDOMRect(
264+
this.editor.view,
265+
this.hyperlinkMarkRange!.from,
266+
this.hyperlinkMarkRange!.to
267+
);
268+
this.lastPosition = hyperlinkBoundingBox;
269+
270+
return hyperlinkBoundingBox;
271+
},
258272
};
259273
}
260274

@@ -265,11 +279,6 @@ class HyperlinkToolbarView {
265279
this.hyperlinkMarkRange!.from,
266280
this.hyperlinkMarkRange!.to
267281
),
268-
referenceRect: posToDOMRect(
269-
this.editor.view,
270-
this.hyperlinkMarkRange!.from,
271-
this.hyperlinkMarkRange!.to
272-
),
273282
};
274283
}
275284
}

0 commit comments

Comments
 (0)