Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Comment thread
YousefED marked this conversation as resolved.
Outdated
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
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fyi, this was not implemented in ariakit / shadcn, so decided to fix

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;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@nperez0111 not sure about setting this on the editor. We need it for the UniqueID and SideMenu extensions though. wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nope, I would not put this on the editor, much less public. Need to find a workaround here


/**
* 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)
);
};
Comment thread
YousefED marked this conversation as resolved.

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;
}

/*
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this was unused, lingering code that I removed in this PR

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)
Comment thread
YousefED marked this conversation as resolved.
) {
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;
Comment thread
YousefED marked this conversation as resolved.
};
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