Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .claude/worktrees/emoji-fixes
Submodule emoji-fixes added at c9234d
1 change: 1 addition & 0 deletions .claude/worktrees/fix-pr-2553-port
Submodule fix-pr-2553-port added at da22c7
1 change: 1 addition & 0 deletions .claude/worktrees/toggle-block-bugs-blo-1018
Submodule toggle-block-bugs-blo-1018 added at 7b7762
2 changes: 1 addition & 1 deletion docs/content/docs/react/styling-theming/themes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Here are each of the theme CSS variables you can set, with values from the defau
--bn-border-radius: 6px;
```

Setting these variables on the `.bn-container[data-color-scheme]` selector will overwrite them for both default light & dark themes. To overwrite variables separately for light & dark themes, use the `.bn-container[data-color-scheme="light"]` and `.bn-container[data-color-scheme="dark"]` selectors.
Setting these variables on the `.bn-root[data-color-scheme]` selector will overwrite them for both default light & dark themes. To overwrite variables separately for light & dark themes, use the `.bn-root[data-color-scheme="light"]` and `.bn-root[data-color-scheme="dark"]` selectors.

## Programmatic Configuration

Expand Down
11 changes: 9 additions & 2 deletions examples/01-basic/12-multi-editor/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";

// Component that creates & renders a BlockNote editor.
function Editor(props: { initialContent?: PartialBlock[] }) {
function Editor(props: {
initialContent?: PartialBlock[];
theme: "dark" | "light";
}) {
// Creates a new editor instance.
const editor = useCreateBlockNote({
initialContent: props.initialContent,
});

// Renders the editor instance using a React component.
return <BlockNoteView editor={editor} style={{ flex: 1 }} />;
return (
<BlockNoteView theme={props.theme} editor={editor} style={{ flex: 1 }} />
);
}

export default function App() {
// Creates & renders two editors side by side.
return (
<div style={{ display: "flex" }}>
<Editor
theme="dark"
initialContent={[
{
type: "paragraph",
Expand All @@ -35,6 +41,7 @@ export default function App() {
]}
/>
<Editor
theme="light"
initialContent={[
{
type: "paragraph",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function App() {
// additional class names/attributes depend on the UI library you're using,
// whether you want to show light or dark more, etc. It's easiest to just
// check the rendered editor HTML to see what you need to add.
<div className="bn-container bn-mantine">
<div className="bn-root bn-container bn-mantine">
<div
className="ProseMirror bn-editor bn-default-styles"
dangerouslySetInnerHTML={{ __html: html }}
Expand Down
2 changes: 1 addition & 1 deletion examples/04-theming/02-changing-font/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.bn-container[data-changing-font-demo] .bn-editor * {
.bn-root[data-changing-font-demo] .bn-editor * {
font-family: "Comic Sans MS", sans-serif;
}
7 changes: 3 additions & 4 deletions examples/04-theming/03-theming-css/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* Adds border and shadow to editor */
.bn-container[data-theming-css-demo] .bn-editor * {
.bn-root[data-theming-css-demo] .bn-editor * {
color: blue;
}

/* Makes slash menu hovered items blue */
.bn-container[data-theming-css-demo]
.bn-suggestion-menu-item[aria-selected="true"],
.bn-container[data-theming-css-demo] .bn-suggestion-menu-item:hover {
.bn-root[data-theming-css-demo] .bn-suggestion-menu-item[aria-selected="true"],
.bn-root[data-theming-css-demo] .bn-suggestion-menu-item:hover {
background-color: blue;
}
4 changes: 2 additions & 2 deletions examples/04-theming/04-theming-css-variables/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Base theme */
.bn-container[data-theming-css-variables-demo][data-color-scheme] {
.bn-root[data-theming-css-variables-demo][data-color-scheme] {
--bn-colors-editor-text: #222222;
--bn-colors-editor-background: #ffeeee;
--bn-colors-menu-text: #ffffff;
Expand All @@ -21,7 +21,7 @@
}

/* Changes for dark mode */
.bn-container[data-theming-css-variables-demo][data-color-scheme="dark"] {
.bn-root[data-theming-css-variables-demo][data-color-scheme="dark"] {
--bn-colors-editor-text: #ffffff;
--bn-colors-editor-background: #9b0000;
--bn-colors-side-menu: #ffffff;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export default function App() {
etc. It's easiest to just copy the class names and HTML attributes
from an actual BlockNote editor. */}
<div
className="bn-container bn-mantine"
className="bn-root bn-container bn-mantine"
data-color-scheme={theme}
data-mantine-color-scheme={theme}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import "@blocknote/core/fonts/inter.css";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote, usePrefersColorScheme } from "@blocknote/react";
import { useRef, useEffect } from "react";

export default function App() {
// Creates a new editor instance.
Expand Down Expand Up @@ -158,7 +157,7 @@ export default function App() {
// Renders the exported static HTML from the editor.
return (
<div
className="bn-container bn-mantine"
className="bn-root bn-container bn-mantine"
data-color-scheme={theme}
data-mantine-color-scheme={theme}
>
Expand Down
5 changes: 3 additions & 2 deletions packages/ariakit/src/comments/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const Comment = forwardRef<
actions,
children,
edited,
emojiPickerOpen, // Unused
emojiPickerOpen,
...rest
} = props;

Expand All @@ -72,7 +72,8 @@ export const Comment = forwardRef<
(showActions === true ||
showActions === undefined ||
(showActions === "hover" && hovered) ||
focused);
focused ||
emojiPickerOpen);

return (
<AriakitGroup
Expand Down
15 changes: 12 additions & 3 deletions packages/ariakit/src/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {

import { assertEmpty, mergeCSSClasses } from "@blocknote/core";
import { ComponentProps } from "@blocknote/react";
import { forwardRef } from "react";
import { createContext, forwardRef, useContext } from "react";

const PortalRootContext = createContext<HTMLElement | null | undefined>(
undefined,
);

export const PopoverTrigger = forwardRef<
HTMLButtonElement,
Expand All @@ -27,13 +31,16 @@ export const PopoverContent = forwardRef<

assertEmpty(rest);

const portalRoot = useContext(PortalRootContext);

return (
<AriakitPopover
className={mergeCSSClasses(
"bn-ak-popover",
className || "",
variant === "panel-popover" ? "bn-ak-panel-popover" : "",
)}
portalElement={portalRoot ?? undefined}
ref={ref}
>
{children}
Expand All @@ -44,7 +51,7 @@ export const PopoverContent = forwardRef<
export const Popover = (
props: ComponentProps["Generic"]["Popover"]["Root"],
) => {
const { children, open, onOpenChange, position, ...rest } = props;
const { children, open, onOpenChange, position, portalRoot, ...rest } = props;

assertEmpty(rest);

Expand All @@ -54,7 +61,9 @@ export const Popover = (
setOpen={onOpenChange}
placement={position}
>
{children}
<PortalRootContext.Provider value={portalRoot}>
{children}
</PortalRootContext.Provider>
</AriakitPopoverProvider>
);
};
19 changes: 19 additions & 0 deletions packages/core/src/editor/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,25 @@ export class BlockNoteEditor<
return this.prosemirrorView?.dom as HTMLDivElement | undefined;
}

/**
* The portal container element at `document.body` used by floating UI
* elements (menus, toolbars) to escape overflow:hidden ancestors.
* Set by BlockNoteView; undefined in headless mode.
*/
public portalElement: HTMLElement | undefined;

/**
* Checks whether a DOM element belongs to this editor — either inside the
* editor's DOM tree or inside its portal container (used for floating UI
* elements like menus and toolbars).
*/
public isWithinEditor = (element: Element): boolean => {
return !!(
this.domElement?.parentElement?.contains(element) ||
this.portalElement?.contains(element)
);
};

public isFocused() {
if (this.headless) {
return false;
Expand Down
20 changes: 0 additions & 20 deletions packages/core/src/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,6 @@
padding: 0;
}

/*
bn-root should be applied to all top-level elements

This includes the Prosemirror editor, but also <div> element such as
Tippy popups that are appended to document.body directly
*/
.bn-root {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.bn-root *,
.bn-root *::before,
.bn-root *::after {
-webkit-box-sizing: inherit;
-moz-box-sizing: inherit;
box-sizing: inherit;
}

/* reset styles, they will be set on blockContent */
.bn-default-styles p,
.bn-default-styles h1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export function getDefaultTiptapExtensions(
// everything from bnBlock group (nodes that represent a BlockNote block should have an id)
types: ["blockContainer", "columnList", "column"],
setIdAttribute: options.setIdAttribute,
isWithinEditor: editor.isWithinEditor,
}),
HardBreak,
Text,
Expand Down
10 changes: 2 additions & 8 deletions packages/core/src/extensions/SideMenu/SideMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,9 +612,6 @@ export class SideMenuView<
this.mousePos.y > editorOuterBoundingBox.top &&
this.mousePos.y < editorOuterBoundingBox.bottom;

// TODO: remove parentElement, but then we need to remove padding from boundingbox or find a different solution
const editorWrapper = this.pmView.dom!.parentElement!;

// Doesn't update if the mouse hovers an element that's over the editor but
// isn't a part of it or the side menu.
if (
Expand All @@ -623,11 +620,8 @@ export class SideMenuView<
// An element is hovered
event &&
event.target &&
// Element is outside the editor
!(
editorWrapper === event.target ||
editorWrapper.contains(event.target as HTMLElement)
)
// Element is outside this editor and its portaled UI
!this.editor.isWithinEditor(event.target as HTMLElement)
) {
if (this.state?.show) {
this.state.show = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ const UniqueID = Extension.create({
attributeName: "id",
types: [],
setIdAttribute: false,
isWithinEditor: undefined as
| ((element: Element) => boolean)
| undefined,
generateID: () => {
// Use mock ID if tests are running.
if (typeof window !== "undefined" && (window as any).__TEST_OPTIONS) {
Expand Down Expand Up @@ -128,6 +131,7 @@ const UniqueID = Extension.create({
// view.dispatch(tr);
// },
addProseMirrorPlugins() {
const { isWithinEditor } = this.options;
let dragSourceElement: any = null;
let transformPasted = false;
return [
Expand Down Expand Up @@ -228,14 +232,11 @@ const UniqueID = Extension.create({
// we register a global drag handler to track the current drag source element
view(view) {
const handleDragstart = (event: any) => {
let _a;
dragSourceElement = (
(_a = view.dom.parentElement) === null || _a === void 0
? void 0
: _a.contains(event.target)
)
? view.dom.parentElement
: null;
const editorParent = view.dom.parentElement;
const isFromEditor =
editorParent?.contains(event.target) ||
isWithinEditor?.(event.target);
dragSourceElement = isFromEditor ? editorParent : null;
};
window.addEventListener("dragstart", handleDragstart);
return {
Expand Down
7 changes: 4 additions & 3 deletions packages/mantine/src/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ import { forwardRef } from "react";
export const Popover = (
props: ComponentProps["Generic"]["Popover"]["Root"],
) => {
const { open, onOpenChange, position, children, ...rest } = props;
const { open, onOpenChange, position, portalRoot, children, ...rest } = props;

assertEmpty(rest);

return (
<MantinePopover
middlewares={{ size: { padding: 20 } }}
withinPortal={false}
withinPortal={!!portalRoot}
portalProps={portalRoot ? { target: portalRoot } : undefined}
opened={open}
onChange={onOpenChange}
position={position}
zIndex={10000}
zIndex={portalRoot ? undefined : 10000}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify z-index handling for Mantine popovers and bn root variable wiring.

# 1) Confirm changed Mantine popover behavior in this file.
rg -nP -C3 'withinPortal=|portalProps=|zIndex=' packages/mantine/src/popover/Popover.tsx

# 2) Check whether bn-ui-base-z-index is consumed anywhere in styling/code.
rg -nP -C2 'bn-ui-base-z-index|--bn-ui-base-z-index|z-index' packages/react packages/mantine

# 3) Inspect portal root creation context for explicit stacking styles.
rg -nP -C8 'createPortal\\(|className=|data-color-scheme|bn-root' packages/react/src/editor/BlockNoteView.tsx

Repository: TypeCellOS/BlockNote

Length of output: 2993


🏁 Script executed:

# Find the portal root creation and its styling context
rg -nA15 -B5 'createPortal|portalRoot.*=' packages/react/src/editor/BlockNoteView.tsx | head -100

Repository: TypeCellOS/BlockNote

Length of output: 2391


🏁 Script executed:

# Check if there's z-index styling on the portal root or bn-root specifically
rg -nP 'bn-root|portal.*root' packages/react/src/editor/styles.css packages/react/src/editor/BlockNoteView.tsx -A3 -B3

Repository: TypeCellOS/BlockNote

Length of output: 4550


🏁 Script executed:

# Check Mantine Popover implementation or types to see how zIndex behaves
fd -t f -e tsx -e ts packages/mantine | xargs grep -l "zIndex" | head -5

Repository: TypeCellOS/BlockNote

Length of output: 433


🏁 Script executed:

# Find Mantine Popover files
fd -t f --full-path 'packages/mantine' -e tsx -e ts | head -20

Repository: TypeCellOS/BlockNote

Length of output: 856


🏁 Script executed:

# Check if there's any handling of zIndex prop for Popover in Mantine code
rg -n 'zIndex|z-index' packages/mantine/src/popover/ -B3 -A3

Repository: TypeCellOS/BlockNote

Length of output: 538


🏁 Script executed:

# Verify what Mantine's default z-index behavior is for portaled popovers
rg -n 'const.*Popover|export.*Popover' packages/mantine/src/popover/ -A20 | head -80

Repository: TypeCellOS/BlockNote

Length of output: 3408


Portaled popovers lose stacking control with Mantine's default z-index.

At line 26, zIndex is set to undefined when portalRoot exists. The portal root (created in packages/react/src/editor/BlockNoteView.tsx:217-226) has no explicit z-index styling and inherits --bn-ui-base-z-index: 0 from the .bn-root CSS rule. This causes Mantine to apply its default z-index (300 via CSS variables), which can fall behind fixed/sticky app chrome without explicit stacking control.

The fix aligns with the established pattern used in GenericPopover.tsx and other UI components, which leverage the --bn-ui-base-z-index CSS variable for consistent z-index management:

Proposed fix
-      zIndex={portalRoot ? undefined : 10000}
+      zIndex={portalRoot ? "var(--bn-ui-base-z-index, 10000)" : 10000}

This ensures portaled popovers respect the base z-index system while maintaining fallback to 10000 when the variable is not defined.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zIndex={portalRoot ? undefined : 10000}
zIndex={portalRoot ? "var(--bn-ui-base-z-index, 10000)" : 10000}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mantine/src/popover/Popover.tsx` at line 26, Popover.tsx currently
clears zIndex when portalRoot exists which lets Mantine's default z-index win;
change the zIndex logic to use the CSS variable fallback pattern from
GenericPopover.tsx by assigning zIndex to the CSS variable --bn-ui-base-z-index
with a 10000 fallback when portalRoot is truthy (and keep 10000 when not
portaled) so portaled popovers respect the app's base z-index system; update the
zIndex prop usage in the Popover component (the zIndex prop set at the
repository diff line) accordingly to use the var(--bn-ui-base-z-index, 10000)
approach.

>
{children}
</MantinePopover>
Expand Down
Loading
Loading