Skip to content

Commit 62bf888

Browse files
committed
fix: deep merge floatingUIOptions to preserve internal settings #2308
Use lodash.merge instead of spread operator to properly merge nested floatingUIOptions properties. This prevents user-provided options like `useFloatingOptions: { strategy: "fixed" }` from overwriting internal settings such as `open`, `onOpenChange`, `placement`, and `middleware`.
1 parent 575b81c commit 62bf888

File tree

9 files changed

+318
-282
lines changed

9 files changed

+318
-282
lines changed

packages/react/src/components/Comments/FloatingComposerController.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "@blocknote/core";
99
import { CommentsExtension } from "@blocknote/core/comments";
1010
import { flip, offset, shift } from "@floating-ui/react";
11+
import merge from "lodash.merge";
1112
import { ComponentProps, FC, useMemo } from "react";
1213

1314
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
@@ -46,28 +47,31 @@ export default function FloatingComposerController<
4647
});
4748

4849
const floatingUIOptions = useMemo<FloatingUIOptions>(
49-
() => ({
50-
useFloatingOptions: {
51-
open: !!pendingComment,
52-
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
53-
// open state.
54-
onOpenChange: (open) => {
55-
if (!open) {
56-
comments.stopPendingComment();
57-
editor.focus();
58-
}
59-
},
60-
placement: "bottom",
61-
middleware: [offset(10), shift(), flip()],
62-
},
63-
elementProps: {
64-
style: {
65-
zIndex: 60,
66-
},
67-
},
68-
...props.floatingUIOptions,
69-
}),
70-
[comments, editor, pendingComment, props.floatingUIOptions],
50+
() =>
51+
merge(
52+
{
53+
useFloatingOptions: {
54+
open: !!pendingComment,
55+
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
56+
// open state.
57+
onOpenChange: (open) => {
58+
if (!open) {
59+
comments.stopPendingComment();
60+
editor.focus();
61+
}
62+
},
63+
placement: "bottom",
64+
middleware: [offset(10), shift(), flip()],
65+
},
66+
elementProps: {
67+
style: {
68+
zIndex: 60,
69+
},
70+
},
71+
} satisfies FloatingUIOptions,
72+
props.floatingUIOptions
73+
),
74+
[comments, editor, pendingComment, props.floatingUIOptions]
7175
);
7276

7377
// nice to have improvements would be:

packages/react/src/components/Comments/FloatingThreadController.tsx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CommentsExtension } from "@blocknote/core/comments";
22
import { flip, offset, shift } from "@floating-ui/react";
3+
import merge from "lodash.merge";
34
import { ComponentProps, FC, useMemo } from "react";
45

56
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
@@ -39,31 +40,34 @@ export default function FloatingThreadController(props: {
3940
);
4041

4142
const floatingUIOptions = useMemo<FloatingUIOptions>(
42-
() => ({
43-
useFloatingOptions: {
44-
open: !!selectedThread,
45-
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
46-
// open state.
47-
onOpenChange: (open, _event, reason) => {
48-
if (reason === "escape-key") {
49-
editor.focus();
50-
}
43+
() =>
44+
merge(
45+
{
46+
useFloatingOptions: {
47+
open: !!selectedThread,
48+
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
49+
// open state.
50+
onOpenChange: (open, _event, reason) => {
51+
if (reason === "escape-key") {
52+
editor.focus();
53+
}
5154

52-
if (!open) {
53-
comments.selectThread(undefined);
54-
}
55-
},
56-
placement: "bottom",
57-
middleware: [offset(10), shift(), flip()],
58-
},
59-
elementProps: {
60-
style: {
61-
zIndex: 30,
62-
},
63-
},
64-
...props.floatingUIOptions,
65-
}),
66-
[comments, editor, props.floatingUIOptions, selectedThread],
55+
if (!open) {
56+
comments.selectThread(undefined);
57+
}
58+
},
59+
placement: "bottom",
60+
middleware: [offset(10), shift(), flip()],
61+
},
62+
elementProps: {
63+
style: {
64+
zIndex: 30,
65+
},
66+
},
67+
} satisfies FloatingUIOptions,
68+
props.floatingUIOptions
69+
),
70+
[comments, editor, props.floatingUIOptions, selectedThread]
6771
);
6872

6973
// nice to have improvements:

packages/react/src/components/FilePanel/FilePanelController.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FilePanelExtension } from "@blocknote/core/extensions";
22
import { flip, offset } from "@floating-ui/react";
3+
import merge from "lodash.merge";
34
import { FC, useMemo } from "react";
45

56
import { FilePanel } from "./FilePanel.js";
@@ -19,30 +20,33 @@ export const FilePanelController = (props: {
1920
const blockId = useExtensionState(FilePanelExtension);
2021

2122
const floatingUIOptions = useMemo<FloatingUIOptions>(
22-
() => ({
23-
useFloatingOptions: {
24-
open: !!blockId,
25-
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
26-
// open state.
27-
onOpenChange: (open, _event, reason) => {
28-
if (!open) {
29-
filePanel.closeMenu();
30-
}
23+
() =>
24+
merge(
25+
{
26+
useFloatingOptions: {
27+
open: !!blockId,
28+
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
29+
// open state.
30+
onOpenChange: (open, _event, reason) => {
31+
if (!open) {
32+
filePanel.closeMenu();
33+
}
3134

32-
if (reason === "escape-key") {
33-
editor.focus();
34-
}
35-
},
36-
middleware: [offset(10), flip()],
37-
},
38-
elementProps: {
39-
style: {
40-
zIndex: 90,
41-
},
42-
},
43-
...props.floatingUIOptions,
44-
}),
45-
[blockId, editor, filePanel, props.floatingUIOptions],
35+
if (reason === "escape-key") {
36+
editor.focus();
37+
}
38+
},
39+
middleware: [offset(10), flip()],
40+
},
41+
elementProps: {
42+
style: {
43+
zIndex: 90,
44+
},
45+
},
46+
} satisfies FloatingUIOptions,
47+
props.floatingUIOptions
48+
),
49+
[blockId, editor, filePanel, props.floatingUIOptions]
4650
);
4751

4852
const Component = props.filePanel || FilePanel;

packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "@blocknote/core";
99
import { FormattingToolbarExtension } from "@blocknote/core/extensions";
1010
import { flip, offset, shift } from "@floating-ui/react";
11+
import merge from "lodash.merge";
1112
import { FC, useMemo } from "react";
1213

1314
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
@@ -78,29 +79,32 @@ export const FormattingToolbarController = (props: {
7879
});
7980

8081
const floatingUIOptions = useMemo<FloatingUIOptions>(
81-
() => ({
82-
useFloatingOptions: {
83-
open: show,
84-
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
85-
// open state.
86-
onOpenChange: (open, _event, reason) => {
87-
formattingToolbar.store.setState(open);
82+
() =>
83+
merge(
84+
{
85+
useFloatingOptions: {
86+
open: show,
87+
// Needed as hooks like `useDismiss` call `onOpenChange` to change the
88+
// open state.
89+
onOpenChange: (open, _event, reason) => {
90+
formattingToolbar.store.setState(open);
8891

89-
if (reason === "escape-key") {
90-
editor.focus();
91-
}
92-
},
93-
placement,
94-
middleware: [offset(10), shift(), flip()],
95-
},
96-
elementProps: {
97-
style: {
98-
zIndex: 40,
99-
},
100-
},
101-
...props.floatingUIOptions,
102-
}),
103-
[show, placement, props.floatingUIOptions, formattingToolbar.store, editor],
92+
if (reason === "escape-key") {
93+
editor.focus();
94+
}
95+
},
96+
placement,
97+
middleware: [offset(10), shift(), flip()],
98+
},
99+
elementProps: {
100+
style: {
101+
zIndex: 40,
102+
},
103+
},
104+
} satisfies FloatingUIOptions,
105+
props.floatingUIOptions
106+
),
107+
[show, placement, props.floatingUIOptions, formattingToolbar.store, editor]
104108
);
105109

106110
const Component = props.formattingToolbar || FormattingToolbar;

packages/react/src/components/LinkToolbar/LinkToolbarController.tsx

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { LinkToolbarExtension } from "@blocknote/core/extensions";
22
import { flip, offset, safePolygon } from "@floating-ui/react";
33
import { Range } from "@tiptap/core";
4+
import merge from "lodash.merge";
45
import { FC, useEffect, useMemo, useState } from "react";
56

67
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
@@ -109,51 +110,54 @@ export const LinkToolbarController = (props: {
109110
}, [editor, linkToolbar, link, toolbarPositionFrozen]);
110111

111112
const floatingUIOptions = useMemo<FloatingUIOptions>(
112-
() => ({
113-
useFloatingOptions: {
114-
open: toolbarOpen,
115-
onOpenChange: (open, _event, reason) => {
116-
if (toolbarPositionFrozen) {
117-
return;
118-
}
119-
120-
// We want to prioritize `selectionLink` over `mouseHoverLink`, so we
121-
// ignore opening/closing from hover events.
122-
if (
123-
link !== undefined &&
124-
link.cursorType === "text" &&
125-
reason === "hover"
126-
) {
127-
return;
128-
}
129-
130-
if (reason === "escape-key") {
131-
editor.focus();
132-
}
133-
134-
setToolbarOpen(open);
135-
},
136-
placement: "top-start",
137-
middleware: [offset(10), flip()],
138-
},
139-
useHoverProps: {
140-
// `useHover` hook only enabled when a link is hovered with the
141-
// mouse.
142-
enabled: link !== undefined && link.cursorType === "mouse",
143-
delay: {
144-
open: 250,
145-
close: 250,
146-
},
147-
handleClose: safePolygon(),
148-
},
149-
elementProps: {
150-
style: {
151-
zIndex: 50,
152-
},
153-
},
154-
...props.floatingUIOptions,
155-
}),
156-
[editor, link, props.floatingUIOptions, toolbarOpen, toolbarPositionFrozen],
113+
() =>
114+
merge(
115+
{
116+
useFloatingOptions: {
117+
open: toolbarOpen,
118+
onOpenChange: (open, _event, reason) => {
119+
if (toolbarPositionFrozen) {
120+
return;
121+
}
122+
123+
// We want to prioritize `selectionLink` over `mouseHoverLink`, so we
124+
// ignore opening/closing from hover events.
125+
if (
126+
link !== undefined &&
127+
link.cursorType === "text" &&
128+
reason === "hover"
129+
) {
130+
return;
131+
}
132+
133+
if (reason === "escape-key") {
134+
editor.focus();
135+
}
136+
137+
setToolbarOpen(open);
138+
},
139+
placement: "top-start",
140+
middleware: [offset(10), flip()],
141+
},
142+
useHoverProps: {
143+
// `useHover` hook only enabled when a link is hovered with the
144+
// mouse.
145+
enabled: link !== undefined && link.cursorType === "mouse",
146+
delay: {
147+
open: 250,
148+
close: 250,
149+
},
150+
handleClose: safePolygon(),
151+
},
152+
elementProps: {
153+
style: {
154+
zIndex: 50,
155+
},
156+
},
157+
} satisfies FloatingUIOptions,
158+
props.floatingUIOptions
159+
),
160+
[editor, link, props.floatingUIOptions, toolbarOpen, toolbarPositionFrozen]
157161
);
158162

159163
const reference = useMemo<GenericPopoverReference | undefined>(

0 commit comments

Comments
 (0)