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
5 changes: 3 additions & 2 deletions docs/content/docs/react/styling-theming/overriding-css.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ BlockNote uses classes with the `bn-` prefix to style editor elements. Here are

#### Editor Structure

- `.bn-container`: Container for editor and all menus/toolbars.
- `.bn-editor`: Main editor element.
- `.bn-root`: Container class both the floating menus / toolbars and the editor
- `.bn-container`: Container around `.bn-editor`
- `.bn-editor`: Main editor element (the "contenteditable").
- `.bn-block`: Individual block element (including nested).
- `.bn-block-group`: Container for nested blocks.
- `.bn-block-content`: Block content wrapper.
Expand Down
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>
);
};
4 changes: 0 additions & 4 deletions packages/ariakit/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@
inset 0 1px 1px 1px var(--shadow);
}

.bn-ak-popover {
z-index: 10000;
}

.bn-toolbar .bn-ak-popover {
gap: 0.5rem;
}
Expand Down
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)
);
};
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.

This will have to change if portalElement isn't stored here


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 on lines +623 to +624
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.

I'm not sure that this is correct. The TODO comment from before made it seem like it was intentionally using the parent element of the editor

) {
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;
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.

Does dragging from a popover really matter? Why is this check here?

};
window.addEventListener("dragstart", handleDragstart);
return {
Expand Down
31 changes: 21 additions & 10 deletions packages/mantine/src/BlockNoteView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@ export const BlockNoteView = <
const defaultColorScheme =
existingContext?.colorSchemePreference || systemColorScheme;

const ref = useCallback(
const finalTheme =
typeof theme === "string"
? theme
: defaultColorScheme !== "no-preference"
? defaultColorScheme
: "light";

const applyThemeVariables = useCallback(
(node: HTMLDivElement | null) => {
if (!node) {
// todo: clean variables?
return;
}

Expand All @@ -70,14 +76,18 @@ export const BlockNoteView = <
[defaultColorScheme, theme],
);

const mantineContext = useContext(MantineContext);
const portalRef = useCallback(
(node: HTMLDivElement | null) => {
if (!node) {
return;
}
node.setAttribute("data-mantine-color-scheme", finalTheme);
applyThemeVariables(node);
},
[applyThemeVariables, finalTheme],
);

const finalTheme =
typeof theme === "string"
? theme
: defaultColorScheme !== "no-preference"
? defaultColorScheme
: "light";
const mantineContext = useContext(MantineContext);

const view = (
<ComponentsContext.Provider value={components}>
Expand All @@ -86,7 +96,8 @@ export const BlockNoteView = <
className={mergeCSSClasses("bn-mantine", className || "")}
theme={typeof theme === "object" ? undefined : theme}
{...rest}
ref={ref}
ref={applyThemeVariables}
portalRootRef={portalRef}
/>
</ComponentsContext.Provider>
);
Expand Down
Loading
Loading