Skip to content
Merged
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
38 changes: 38 additions & 0 deletions packages/core/src/editor/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,20 @@ export class BlockNoteEditor<
* @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting
*/
public mount = (element: HTMLElement) => {
const root = element.getRootNode();
if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot) {
root.appendChild(this.portalElement);
} else {
document.body.appendChild(this.portalElement);
}
this._tiptapEditor.mount({ mount: element });
};

/**
* Unmount the editor from the DOM element it is bound to
*/
public unmount = () => {
this.portalElement?.remove();
this._tiptapEditor.unmount();
};

Expand Down Expand Up @@ -714,6 +721,37 @@ export class BlockNoteEditor<
return this.prosemirrorView?.dom as HTMLDivElement | undefined;
}

private _portalElement: HTMLElement | 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 get portalElement() {
if (typeof document === "undefined") {
throw new Error(
"Portal element accessed, but not available in headless mode",
);
}
if (!this._portalElement) {
this._portalElement = document.createElement("div");
}
return this._portalElement;
}

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

/*
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)
) {
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
Loading
Loading