Skip to content

Commit 9ce0e2d

Browse files
committed
fix: add correct position for ui elements with floating ui
1 parent 9c665d4 commit 9ce0e2d

File tree

9 files changed

+145
-40
lines changed

9 files changed

+145
-40
lines changed

docs/src/shared/layout/example-tabs.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
const tabBtn =
1717
'inline-flex items-center justify-center whitespace-nowrap py-1 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none';
1818
const tabContent =
19-
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative';
19+
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2';
2020
@Component({
2121
selector: 'bna-example-tabs',
2222
standalone: true,

libs/block-note-angular/src/lib/block-note-editor/bna-editor.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="block h-full overflow-auto relative py-3 bn-container">
1+
<div class="block h-full py-3 bn-container">
22
@if(isInitialized){
33
<bna-formatting-toolbar [editor]="editor">
44
<div class="bn-toolbar">
@@ -24,7 +24,7 @@
2424
<bna-drag-handle-menu-btn [editor]="editor" />
2525
</bna-side-menu>
2626

27-
<bna-view [editor]="editor" class="block ProseMirror bn-editor bn-default-styles" />
27+
<bna-view [editor]="editor" class="block ProseMirror bn-editor relative bn-default-styles" />
2828
<bna-suggestions-menu [editor]="editor" class="z-index-3">
2929
<hlm-menu class="shadow-3xl">
3030
<hlm-menu-label>Basic blocks</hlm-menu-label>

libs/block-note-angular/src/lib/components/bna-formatting-toolbar/bna-formatting-toolbar.directive.ts

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ import {
66
Renderer2,
77
} from '@angular/core';
88
import { BlockNoteEditor } from '@blocknote/core';
9+
import { autoUpdate, computePosition, flip } from '@floating-ui/dom';
10+
import { getVirtualElement } from '../../util/get-virtual-element.util';
11+
12+
const shiftTopBy20px = {
13+
name: 'shiftTopBy20Px',
14+
fn({ x, y }: { x: number; y: number }) {
15+
return {
16+
x: x,
17+
y: y - 100,
18+
};
19+
},
20+
};
921

1022
@Directive({
1123
selector: 'bna-formatting-toolbar[editor]',
@@ -24,22 +36,41 @@ export class BnaFormattingToolbarDirective implements OnChanges {
2436
}
2537

2638
adjustVisibilityAndPosition() {
27-
const position = this.elRef.nativeElement.getBoundingClientRect();
2839
this.toggleVisibility(false);
29-
this.renderer2.addClass(this.elRef.nativeElement, 'z-30');
40+
let cleanup: () => void = () => {
41+
return;
42+
};
43+
this.renderer2.addClass(this.elRef.nativeElement, 'z-40');
3044
this.renderer2.addClass(this.elRef.nativeElement, 'absolute');
3145
if (this.editor()) {
32-
this.editor().formattingToolbar.onUpdate((formattingToolbar) => {
33-
if (formattingToolbar.show) {
34-
this.renderer2.setStyle(
35-
this.elRef.nativeElement,
36-
'top',
37-
`${formattingToolbar.referencePos.top - position.top - 50}px`
38-
);
39-
this.renderer2.setStyle(
46+
this.editor().formattingToolbar.onUpdate(async (formattingToolbar) => {
47+
if (!formattingToolbar.show) {
48+
cleanup();
49+
} else {
50+
const updatePosition = async () => {
51+
const result = await computePosition(
52+
getVirtualElement(formattingToolbar.referencePos),
53+
this.elRef.nativeElement,
54+
{
55+
placement: 'top',
56+
middleware: [flip()],
57+
}
58+
);
59+
this.renderer2.setStyle(
60+
this.elRef.nativeElement,
61+
'top',
62+
`${result.y}px`
63+
);
64+
this.renderer2.setStyle(
65+
this.elRef.nativeElement,
66+
'left',
67+
`${result.x}px`
68+
);
69+
};
70+
cleanup = autoUpdate(
71+
getVirtualElement(formattingToolbar.referencePos),
4072
this.elRef.nativeElement,
41-
'left',
42-
`${formattingToolbar.referencePos.left - position.left}px`
73+
updatePosition
4374
);
4475
}
4576
this.toggleVisibility(formattingToolbar.show);

libs/block-note-angular/src/lib/components/bna-side-menu/bna-side-menu.directive.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
Renderer2,
77
} from '@angular/core';
88
import { BlockNoteEditor } from '@blocknote/core';
9+
import { autoUpdate, computePosition, flip } from '@floating-ui/dom';
10+
import { getVirtualElement } from '../../util/get-virtual-element.util';
911

1012
@Directive({
1113
selector: 'bna-side-menu[editor]',
@@ -24,21 +26,39 @@ export class BnaSideMenuDirective implements OnChanges {
2426
}
2527

2628
private adjustVisibilityAndPosition() {
27-
const position = this.elRef.nativeElement.getBoundingClientRect();
29+
let cleanup: () => void = () => {
30+
return;
31+
};
2832
const editorSnapshot = this.editor();
2933
this.toggleVisibility(true);
3034
this.renderer2.addClass(this.elRef.nativeElement, 'z-30');
3135
this.renderer2.addClass(this.elRef.nativeElement, 'absolute');
32-
editorSnapshot.sideMenu.onUpdate((sideMenuState) => {
33-
if (sideMenuState.show) {
34-
this.renderer2.setStyle(
36+
editorSnapshot.sideMenu.onUpdate(async (sideMenuState) => {
37+
if (!sideMenuState.show) {
38+
cleanup();
39+
} else {
40+
const updatePosition = async () => {
41+
const result = await computePosition(
42+
getVirtualElement(sideMenuState.referencePos),
43+
this.elRef.nativeElement,
44+
{
45+
placement: 'left',
46+
middleware: [flip()],
47+
}
48+
);
49+
this.renderer2.setStyle(
50+
this.elRef.nativeElement,
51+
'top',
52+
`${result.y}px`
53+
);
54+
};
55+
cleanup = autoUpdate(
56+
getVirtualElement(sideMenuState.referencePos),
3557
this.elRef.nativeElement,
36-
'top',
37-
//TODO: change to relative position to view
38-
sideMenuState.referencePos.top - position.top + 35 + 'px'
58+
updatePosition
3959
);
4060
}
41-
// this.toggleVisibility(sideMenuState.show);
61+
this.toggleVisibility(sideMenuState.show);
4262
});
4363
}
4464

libs/block-note-angular/src/lib/components/bna-suggestions-menu/bna-suggestions-menu.directive.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
Renderer2,
77
} from '@angular/core';
88
import { BlockNoteEditor } from '@blocknote/core';
9+
import { autoUpdate, computePosition, flip } from '@floating-ui/dom';
10+
import { getVirtualElement } from '../../util/get-virtual-element.util';
911

1012
@Directive({
1113
selector: 'bna-suggestions-menu[editor]',
@@ -24,27 +26,40 @@ export class BnaSuggestionsMenuDirective implements OnChanges {
2426
}
2527

2628
private adjustVisibilityAndPosition() {
27-
const position = this.elRef.nativeElement.getBoundingClientRect();
2829
this.toggleVisibility(false);
30+
let cleanup: () => void = () => {
31+
return;
32+
};
2933
this.renderer2.addClass(this.elRef.nativeElement, 'z-30');
3034
this.renderer2.addClass(this.elRef.nativeElement, 'absolute');
31-
this.editor().suggestionMenus.onUpdate('/', (suggestionMenuState) => {
32-
if (suggestionMenuState.show) {
33-
this.renderer2.setStyle(
35+
this.editor().suggestionMenus.onUpdate('/', async (suggestionMenuState) => {
36+
if (!suggestionMenuState.show) {
37+
cleanup();
38+
} else {
39+
const updatePosition = async () => {
40+
const result = await computePosition(
41+
getVirtualElement(suggestionMenuState.referencePos),
42+
this.elRef.nativeElement,
43+
{
44+
placement: 'bottom-start',
45+
middleware: [flip()],
46+
}
47+
);
48+
this.renderer2.setStyle(
49+
this.elRef.nativeElement,
50+
'top',
51+
`${result.y}px`
52+
);
53+
this.renderer2.setStyle(
54+
this.elRef.nativeElement,
55+
'left',
56+
`${result.x}px`
57+
);
58+
};
59+
cleanup = autoUpdate(
60+
getVirtualElement(suggestionMenuState.referencePos),
3461
this.elRef.nativeElement,
35-
'top',
36-
`${
37-
suggestionMenuState.referencePos.top -
38-
position.top +
39-
//TODO: change to relative position to view
40-
20 +
41-
suggestionMenuState.referencePos.height
42-
}px`
43-
);
44-
this.renderer2.setStyle(
45-
this.elRef.nativeElement,
46-
'left',
47-
`${suggestionMenuState.referencePos.left - position.left}px`
62+
updatePosition
4863
);
4964
}
5065
this.toggleVisibility(suggestionMenuState.show);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { getVirtualElement } from './get-virtual-element.util';
2+
3+
describe('getVirtualElement', () => {
4+
it('should return object with getBoundingClientRect function to act as a virtual element for floating ui ', () => {
5+
const domRect: DOMRect = { x: 0, y: 0 } as DOMRect;
6+
expect(getVirtualElement(domRect).getBoundingClientRect()).toEqual(domRect);
7+
});
8+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const getVirtualElement = (referencePos: DOMRect) => {
2+
return {
3+
getBoundingClientRect() {
4+
return referencePos;
5+
},
6+
};
7+
};

package-lock.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@angular/platform-browser-dynamic": "~18.0.0",
2020
"@angular/router": "~18.0.0",
2121
"@blocknote/core": "^0.14.5",
22+
"@floating-ui/dom": "^1.6.8",
2223
"@ng-icons/core": "^25.1.0",
2324
"@ng-icons/lucide": "^26.3.0",
2425
"@nx/angular": "19.4.2",

0 commit comments

Comments
 (0)