Skip to content
Draft
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
33 changes: 20 additions & 13 deletions docs/content/docs/features/custom-schemas/container-blocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ You can create custom blocks that contain other blocks, such as panels, callouts

## Creating a Container Block

Use the `createReactBlockSpec` function to create a container block, just like a [Custom Block](/docs/features/custom-schemas/custom-blocks). For the panel below, we set `content` to `"none"` and add the `children` option to let it contain other blocks:
Use the `createReactBlockSpec` function to create a container block, just like a [Custom Block](/docs/features/custom-schemas/custom-blocks). For the panel below, we set `content` to `"none"` and `container` to `true`, so the panel's child blocks go inside it:

```tsx
import { createReactBlockSpec } from "@blocknote/react";
Expand All @@ -21,7 +21,7 @@ export const createPanel = createReactBlockSpec(
type: "panel",
propSchema: {},
content: "none",
children: { allow: "blocks" },
container: true,
},
{
render: (props) => (
Expand All @@ -35,15 +35,15 @@ export const createPanel = createReactBlockSpec(

The block config defines the content and child blocks your container can hold:

`content:` Works the same as for [Custom Blocks](/docs/features/custom-schemas/custom-blocks#block-config-customblockconfig). When using the `children` option, choose `"none"`, `"inline"`, or `"plain"`.
`content:` Must be `"none"` for a container: its node holds nothing but its child blocks. For a block with its own text and child blocks, see [Combining Content and Child Blocks](#combining-content-and-child-blocks).

`children.allow:` Set to `"blocks"` to accept the editor's block types. You can also restrict a container to specific container types, as explained in [Restricting Children](#restricting-children).
`container:` Set to `true` to put the block's child blocks inside it. Without it, a block's child blocks are indented below it. A container accepts any block by default. You can also restrict it to specific container types, as explained in [Restricting Children](#restricting-children).

`propSchema:` Defines the container's props, just like for other custom blocks. Use these to customize its appearance or behavior.

### Block Implementation

`render:` Your React component defines how the block should look. With `content: "none"`, attach `contentRef` where the child blocks should appear. With `content: "inline"` or `"plain"`, attach it to the block's own editable text. You can add icons, buttons, or other elements around it:
`render:` Your React component defines how the block should look. For a container, attach `contentRef` where the child blocks should appear. With `content: "inline"` or `"plain"`, attach it to the block's own editable text. You can add icons, buttons, or other elements around it:

```tsx
render: (props) => (
Expand Down Expand Up @@ -109,7 +109,7 @@ A block can have both its own text and child blocks. Use this for a question fol

<Example name="custom-schema/callout-block" />

Set `content` to `"inline"` for rich text or `"plain"` for unstyled text, and add `children: { allow: "blocks" }`. Use `render` for the block's own text and `renderFrame` to style that content and its child blocks together:
Set `content` to `"inline"` for rich text or `"plain"` for unstyled text. Every block with content can have child blocks, so there's nothing to declare for them. Use `render` for the block's own text, `renderFrame` to style that content and its child blocks together, and `keyboard` to keep the child blocks inside the block:

```tsx
import { createReactBlockSpec } from "@blocknote/react";
Expand All @@ -119,9 +119,13 @@ export const createCallout = createReactBlockSpec(
type: "callout",
propSchema: {},
content: "inline",
children: { allow: "blocks" },
},
{
keyboard: {
enter: "into-children",
childrenCanOutdent: false,
emptyChildEnter: "exit-at-end",
},
render: (props) => (
<div className="callout-title" ref={props.contentRef} />
),
Expand All @@ -146,9 +150,11 @@ The child blocks can still contain rich text, images, and other block types.

`renderFrame:` An optional React component for styling the block and its children together, such as giving the callout a shared border or background. It receives `block`, `editor`, and `contentRef`, just like `render`. Attach `contentRef` where the block's content and children should appear. You can use the block's props to customize the frame, add interactive controls, or return `null` to show the block without a frame.

You can also use `renderFrame` without the `children` option to style a block and its indented children together. For a container with `content: "none"`, like the panel above, add the surrounding styling directly in `render`.
`keyboard:` Without it, child blocks behave like any indented blocks. Here, `enter: "into-children"` makes Enter in the title add a first child block, `childrenCanOutdent: false` keeps Shift-Tab from moving blocks out of the callout, and `emptyChildEnter: "exit-at-end"` makes Enter in an empty last block leave the callout. See [Custom Blocks](/docs/features/custom-schemas/custom-blocks) for all keyboard settings.

For a container with `content: "none"`, like the panel above, add the surrounding styling directly in `render`.

Add `callout: createCallout()` to your schema, then use `content` for the title and `children` for the body:
Add `callout: createCallout()` to your schema, then use `content` for the title and `children` for the blocks inside it:

```typescript
{
Expand All @@ -160,7 +166,7 @@ Add `callout: createCallout()` to your schema, then use `content` for the title
}
```

Pressing Enter at the end of the title starts a paragraph in the body. Moving the callout moves its title and body together.
Pressing Enter at the end of the title adds a paragraph inside the callout, and Enter in an empty last paragraph leaves the callout. Moving the callout moves its title and child blocks together.

To add blocks to an existing container, see [Inserting Blocks](/docs/reference/editor/manipulating-content#inserting-blocks).

Expand All @@ -172,23 +178,24 @@ Use an array of container type names for `children.allow`, and `min` to set the

```typescript
// Column layout config:
container: true,
children: { allow: ["column"], min: 2 },
```

On the column itself, set `placeable` to `"namedOnly"` so it can only be used inside a container that explicitly allows it:

```typescript
// Column config:
children: { allow: "blocks" },
container: true,
placeable: "namedOnly",
```

`children.allow:` Accepts `"blocks"` or an array of container type names. You cannot list regular block types such as `"paragraph"` individually.
`children.allow:` Accepts `"blocks"` (the default) or an array of container type names. You cannot list regular block types such as `"paragraph"` individually.

`children.min:` The minimum number of children. Defaults to `1`.

`placeable:` Set to `"namedOnly"` to restrict a container to parents that name it in `children.allow`. Defaults to `"anywhere"`.

These options apply to containers with `content: "none"`. Blocks with `content: "inline"` or `"plain"` use `children: { allow: "blocks" }` and can have no child blocks.
These options need `container: true`. Other blocks can always have child blocks of any type, so for them `children` can only be the default, `{ allow: "blocks" }`.

For built-in column blocks, see [Multi-Column Layouts](/docs/foundations/document-structure#column-blocks).
42 changes: 34 additions & 8 deletions docs/content/docs/features/custom-schemas/custom-blocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ type BlockConfig = {
type: string;
content: "inline" | "plain" | "none";
readonly propSchema: PropSchema;
container?: true; // only with content: "none"
children?: {
allow: "blocks" | string[];
allow?: "blocks" | string[];
min?: number;
};
placeable?: "anywhere" | "namedOnly";
Expand All @@ -78,12 +79,12 @@ type BlockConfig = {
</Callout>

<Callout type="info">
_Custom blocks can also contain child blocks by declaring the `children`
option, with or without editable content of their own. See [Container
Blocks](/docs/features/custom-schemas/container-blocks)._
_Every block can have child blocks, indented below it. A block without
content can also hold them inside itself with `container: true`. See
[Container Blocks](/docs/features/custom-schemas/container-blocks)._
</Callout>

`children?:` Defines which child blocks the block can contain. `placeable?:` Controls where a container block can be used. See [Container Blocks](/docs/features/custom-schemas/container-blocks) for the supported configurations.
`container?:` Puts the block's child blocks inside it. `children?:` Restricts which child blocks a container can hold. `placeable?:` Controls where a container block can be used. See [Container Blocks](/docs/features/custom-schemas/container-blocks) for the supported configurations.

`propSchema:` The `PropSchema` specifies the props that the block supports. Block props (properties) are data stored with your Block in the document, and can be used to customize its appearance or behavior.

Expand Down Expand Up @@ -146,8 +147,17 @@ type ReactCustomBlockImplementation = {
schema: Schema;
}) => Fragment | undefined;
runsBefore?: string[];
keyboard?: KeyboardSettings | ((block: Block) => KeyboardSettings);
// KeyboardSettings: {
// enter?: "split" | "into-children" | "line-break";
// shiftEnter?: "line-break" | "same-as-enter";
// splitKeepsType?: boolean;
// resetsTo?: { type: string; props?: Record<string, unknown> };
// emptyEnterResets?: boolean;
// emptyChildEnter?: "outdent" | "exit-at-end" | "stay";
// childrenCanOutdent?: boolean;
// }
meta?: {
hardBreakShortcut?: "shift+enter" | "enter" | "none";
selectable?: boolean;
fileBlockAccept?: string[];
code?: boolean;
Expand Down Expand Up @@ -184,9 +194,25 @@ type ReactCustomBlockImplementation = {

`runsBefore?:` If this block has parsing or extensions that need to be given priority over any other blocks, you can pass their `type`s in an array here.

`meta?:` An object for setting various generic properties of the block.
`keyboard?:` How the keyboard treats the block and its children. Give only the settings that differ from the defaults. To make settings depend on the block's props, give a function that gets the block and returns them instead.

- `enter?:` What Enter does in the block's content. `"split"` (default) splits the block, moving the text after the caret into a new block below. `"into-children"` moves it into a new first child instead. `"line-break"` inserts a line break, and makes Shift-Enter do the same. For `content: "plain"` blocks (which can't hold hard break nodes), a line break is a literal newline (`"\n"`).

- `shiftEnter?:` What Shift-Enter does in the block's content: `"line-break"` (default) or `"same-as-enter"`.

- `splitKeepsType?:` Whether the block created by splitting this one with Enter has the same type, as in lists. Defaults to `false`.

- `resetsTo?:` What the block turns into when it's reset, keeping its content and children. Backspace at the start of the block always resets it. A `type`, and `props` to merge into the block's props. Defaults to `{ type: "paragraph" }`.

- `hardBreakShortcut?:` Defines which keyboard shortcut should be used to insert a hard break into the block's inline content. Defaults to `"shift+enter"`. For `content: "plain"` blocks (which can't hold hard break nodes), the shortcut inserts a literal newline (`"\n"`) instead.
- `emptyEnterResets?:` Whether Enter in the empty block resets it too, as when an empty list item turns into a paragraph. Defaults to `false`.

- `emptyChildEnter?:` What Enter does in an empty child of this block. `"outdent"` (default) outdents any empty child. `"exit-at-end"` (default for container blocks) moves an empty last child out to after the block, and adds a new child after any other empty child. `"stay"` always adds a new child after it.

- `childrenCanOutdent?:` Whether the block's children can be outdented out of it with Shift-Tab, or by Enter or Backspace in an empty or nested child. Defaults to `true`, or `false` for container blocks, whose children can never be outdented.

When settings meet, Enter at the start of non-empty content always inserts an empty block above it, and resetting an empty block comes before `enter: "into-children"`.

`meta?:` An object for setting various generic properties of the block.

- `selectable?:` Can be set to false in order to make the block non-selectable, both using the mouse and keyboard. This also helps with being able to select non-editable content within the block. Should only be set to false when `content` is `none` and defaults to true.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@ return (

A few more props customize the states: `errorPreview` for the compact error state shown in place of the preview, `emptySourcePlaceholder` for when the source is empty (a string customizes the default placeholder's text, an element — e.g. the exported `PreviewPlaceholder` with your own icon — replaces it entirely), and `sourcePlaceholder` for the popup input's placeholder. See the `SourceWithPreviewProps` type for the full list.

**3. The spec's `meta`**, opting into the popup:
**3. The spec's `meta`**, opting into the popup, and its `keyboard`:

```tsx
const createMyBlockSpec = createReactBlockSpec(createMyBlockConfig, {
meta: {
code: true,
// Marks the block as rendering a preview with an editable source popup.
hasPreview: true,
// What Enter does while the popup is open: "enter" inserts a newline
// (multiline sources, like diagrams), "shift+enter" closes the popup
// (single-line sources, like math).
hardBreakShortcut: "enter",
},
// What Enter does while the popup is open: "line-break" inserts a newline
// (multiline sources, like diagrams). Without it, Enter closes the popup
// (single-line sources, like math).
keyboard: {
enter: "line-break",
},
render: MyBlockPreview,
});
Expand Down
22 changes: 13 additions & 9 deletions docs/content/docs/features/export/typst.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,22 @@ For a block with inline content, render it the way the default mappings do:
`exporter.transformInlineContent(block.content).join("")` (inline results are
markup strings, so plain concatenation composes them).

### Container blocks
### Blocks that place their children

A [container block](/docs/features/custom-schemas/container-blocks) holds
child blocks, and its mapping decides where they go: the exporter renders the
children first and passes them in as the mapping's last argument, rather than
appending them after the container's own output. A container without a
mapping is an error rather than a silent omission, since dropping it would
drop its children too.
By default, a mapping renders only its block, and the exporter places the
block's children after it, indented. A block whose children are part of it -
a [container block](/docs/features/custom-schemas/container-blocks), or a
callout with a body - uses a `{ withChildren }` mapping instead: the exporter renders
the children first and passes them in as its last argument, and the mapping
decides where they go. Container blocks must use a `{ withChildren }` mapping, and a
container without one is an error rather than a silent omission, since
dropping it would drop its children too.

```typescript
myContainer: (block, exporter, nestingLevel, numberedListIndex, children) =>
`#rect(width: 100%)[${children.join("\n\n")}]`,
myContainer: {
withChildren: (block, exporter, nestingLevel, numberedListIndex, children) =>
`#rect(width: 100%)[${children.join("\n\n")}]`,
},
```

Separate the children with a blank line, as above, if each should stay its own
Expand Down

This file was deleted.

9 changes: 0 additions & 9 deletions examples/06-custom-schema/06-toggleable-blocks/README.md

This file was deleted.

17 changes: 0 additions & 17 deletions examples/06-custom-schema/06-toggleable-blocks/index.html

This file was deleted.

11 changes: 0 additions & 11 deletions examples/06-custom-schema/06-toggleable-blocks/main.tsx

This file was deleted.

30 changes: 0 additions & 30 deletions examples/06-custom-schema/06-toggleable-blocks/package.json

This file was deleted.

55 changes: 0 additions & 55 deletions examples/06-custom-schema/06-toggleable-blocks/src/App.tsx

This file was deleted.

Loading
Loading