@blocknote/core 0.31.3 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/blocknote.cjs +9 -9
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +2075 -1884
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/en-CsgPjHa4.cjs +2 -0
  6. package/dist/en-CsgPjHa4.cjs.map +1 -0
  7. package/dist/{en-CdzlzPhp.js → en-Dx9fwHD4.js} +44 -1
  8. package/dist/en-Dx9fwHD4.js.map +1 -0
  9. package/dist/locales.cjs +1 -1
  10. package/dist/locales.cjs.map +1 -1
  11. package/dist/locales.js +1392 -26
  12. package/dist/locales.js.map +1 -1
  13. package/dist/style.css +1 -1
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/dist/webpack-stats.json +1 -1
  16. package/package.json +1 -1
  17. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +18 -0
  18. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +10 -0
  19. package/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +40 -0
  20. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +23 -0
  21. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +13 -0
  22. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +50 -0
  23. package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +72 -74
  24. package/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +1 -0
  25. package/src/blocks/ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.ts +104 -0
  26. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +182 -0
  27. package/src/blocks/defaultBlockTypeGuards.ts +6 -2
  28. package/src/blocks/defaultBlocks.ts +2 -0
  29. package/src/editor/Block.css +98 -6
  30. package/src/editor/BlockNoteEditor.test.ts +1 -0
  31. package/src/editor/BlockNoteEditor.ts +18 -0
  32. package/src/editor/BlockNoteTipTapEditor.ts +1 -0
  33. package/src/editor/editor.css +3 -0
  34. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +4 -1
  35. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +64 -0
  36. package/src/i18n/locales/ar.ts +43 -0
  37. package/src/i18n/locales/de.ts +62 -0
  38. package/src/i18n/locales/en.ts +43 -0
  39. package/src/i18n/locales/es.ts +43 -0
  40. package/src/i18n/locales/fr.ts +65 -0
  41. package/src/i18n/locales/he.ts +402 -0
  42. package/src/i18n/locales/hr.ts +43 -0
  43. package/src/i18n/locales/index.ts +1 -0
  44. package/src/i18n/locales/is.ts +64 -0
  45. package/src/i18n/locales/it.ts +64 -0
  46. package/src/i18n/locales/ja.ts +63 -0
  47. package/src/i18n/locales/ko.ts +43 -0
  48. package/src/i18n/locales/nl.ts +44 -0
  49. package/src/i18n/locales/no.ts +61 -0
  50. package/src/i18n/locales/pl.ts +49 -0
  51. package/src/i18n/locales/pt.ts +43 -0
  52. package/src/i18n/locales/ru.ts +59 -0
  53. package/src/i18n/locales/sk.ts +43 -0
  54. package/src/i18n/locales/uk.ts +43 -0
  55. package/src/i18n/locales/vi.ts +43 -0
  56. package/src/i18n/locales/zh-tw.ts +51 -0
  57. package/src/i18n/locales/zh.ts +51 -0
  58. package/src/index.ts +1 -0
  59. package/src/schema/blocks/createSpec.ts +3 -1
  60. package/types/src/api/blockManipulation/setupTestEnv.d.ts +42 -2
  61. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +12 -3
  62. package/types/src/blocks/ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.d.ts +46 -0
  63. package/types/src/blocks/ToggleWrapper/createToggleWrapper.d.ts +15 -0
  64. package/types/src/blocks/defaultBlockTypeGuards.d.ts +2 -2
  65. package/types/src/blocks/defaultBlocks.d.ts +84 -4
  66. package/types/src/editor/BlockNoteEditor.d.ts +14 -0
  67. package/types/src/i18n/locales/en.d.ts +42 -0
  68. package/types/src/i18n/locales/he.d.ts +2 -0
  69. package/types/src/i18n/locales/index.d.ts +1 -0
  70. package/types/src/i18n/locales/sk.d.ts +42 -0
  71. package/types/src/index.d.ts +1 -0
  72. package/types/src/schema/blocks/createSpec.d.ts +2 -1
  73. package/dist/en-C0Chm3Nc.cjs +0 -2
  74. package/dist/en-C0Chm3Nc.cjs.map +0 -1
  75. package/dist/en-CdzlzPhp.js.map +0 -1
@@ -5,14 +5,20 @@ import {
5
5
  PropSchema,
6
6
  createBlockSpecFromStronglyTypedTiptapNode,
7
7
  createStronglyTypedTiptapNode,
8
+ getBlockFromPos,
8
9
  propsToAttributes,
9
10
  } from "../../schema/index.js";
10
11
  import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
11
12
  import { defaultProps } from "../defaultProps.js";
13
+ import { createToggleWrapper } from "../ToggleWrapper/createToggleWrapper.js";
14
+ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
15
+
16
+ const HEADING_LEVELS = [1, 2, 3, 4, 5, 6] as const;
12
17
 
13
18
  export const headingPropSchema = {
14
19
  ...defaultProps,
15
- level: { default: 1, values: [1, 2, 3] as const },
20
+ level: { default: 1, values: HEADING_LEVELS },
21
+ isToggleable: { default: false },
16
22
  } satisfies PropSchema;
17
23
 
18
24
  const HeadingBlockContent = createStronglyTypedTiptapNode({
@@ -25,8 +31,9 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
25
31
  },
26
32
 
27
33
  addInputRules() {
34
+ const editor = this.options.editor as BlockNoteEditor<any, any, any>;
28
35
  return [
29
- ...[1, 2, 3].map((level) => {
36
+ ...editor.settings.heading.levels.map((level) => {
30
37
  // Creates a heading of appropriate level when starting with "#", "##", or "###".
31
38
  return new InputRule({
32
39
  find: new RegExp(`^(#{${level}})\\s$`),
@@ -58,87 +65,46 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
58
65
  },
59
66
 
60
67
  addKeyboardShortcuts() {
61
- return {
62
- "Mod-Alt-1": () => {
63
- const blockInfo = getBlockInfoFromSelection(this.editor.state);
64
- if (
65
- !blockInfo.isBlockContainer ||
66
- blockInfo.blockContent.node.type.spec.content !== "inline*"
67
- ) {
68
- return true;
69
- }
70
-
71
- // call updateBlockCommand
72
- return this.editor.commands.command(
73
- updateBlockCommand(blockInfo.bnBlock.beforePos, {
74
- type: "heading",
75
- props: {
76
- level: 1 as any,
77
- },
78
- }),
79
- );
80
- },
81
- "Mod-Alt-2": () => {
82
- const blockInfo = getBlockInfoFromSelection(this.editor.state);
83
- if (
84
- !blockInfo.isBlockContainer ||
85
- blockInfo.blockContent.node.type.spec.content !== "inline*"
86
- ) {
87
- return true;
88
- }
89
-
90
- return this.editor.commands.command(
91
- updateBlockCommand(blockInfo.bnBlock.beforePos, {
92
- type: "heading",
93
- props: {
94
- level: 2 as any,
95
- },
96
- }),
97
- );
98
- },
99
- "Mod-Alt-3": () => {
100
- const blockInfo = getBlockInfoFromSelection(this.editor.state);
101
- if (
102
- !blockInfo.isBlockContainer ||
103
- blockInfo.blockContent.node.type.spec.content !== "inline*"
104
- ) {
105
- return true;
106
- }
107
-
108
- return this.editor.commands.command(
109
- updateBlockCommand(blockInfo.bnBlock.beforePos, {
110
- type: "heading",
111
- props: {
112
- level: 3 as any,
113
- },
114
- }),
115
- );
116
- },
117
- };
68
+ const editor = this.options.editor as BlockNoteEditor<any, any, any>;
69
+
70
+ return Object.fromEntries(
71
+ editor.settings.heading.levels.map((level) => [
72
+ `Mod-Alt-${level}`,
73
+ () => {
74
+ const blockInfo = getBlockInfoFromSelection(this.editor.state);
75
+ if (
76
+ !blockInfo.isBlockContainer ||
77
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
78
+ ) {
79
+ return true;
80
+ }
81
+
82
+ return this.editor.commands.command(
83
+ updateBlockCommand(blockInfo.bnBlock.beforePos, {
84
+ type: "heading",
85
+ props: {
86
+ level: level as any,
87
+ },
88
+ }),
89
+ );
90
+ },
91
+ ]),
92
+ );
118
93
  },
119
94
  parseHTML() {
95
+ const editor = this.options.editor as BlockNoteEditor<any, any, any>;
96
+
120
97
  return [
121
98
  // Parse from internal HTML.
122
99
  {
123
100
  tag: "div[data-content-type=" + this.name + "]",
124
101
  contentElement: ".bn-inline-content",
125
102
  },
126
- // Parse from external HTML.
127
- {
128
- tag: "h1",
129
- attrs: { level: 1 },
103
+ ...editor.settings.heading.levels.map((level) => ({
104
+ tag: `h${level}`,
105
+ attrs: { level },
130
106
  node: "heading",
131
- },
132
- {
133
- tag: "h2",
134
- attrs: { level: 2 },
135
- node: "heading",
136
- },
137
- {
138
- tag: "h3",
139
- attrs: { level: 3 },
140
- node: "heading",
141
- },
107
+ })),
142
108
  ];
143
109
  },
144
110
 
@@ -153,6 +119,38 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
153
119
  this.options.domAttributes?.inlineContent || {},
154
120
  );
155
121
  },
122
+
123
+ addNodeView() {
124
+ return ({ node, HTMLAttributes, getPos }) => {
125
+ const { dom, contentDOM } = createDefaultBlockDOMOutputSpec(
126
+ this.name,
127
+ `h${node.attrs.level}`,
128
+ {
129
+ ...(this.options.domAttributes?.blockContent || {}),
130
+ ...HTMLAttributes,
131
+ },
132
+ this.options.domAttributes?.inlineContent || {},
133
+ );
134
+ dom.removeChild(contentDOM);
135
+
136
+ const editor = this.options.editor;
137
+ const block = getBlockFromPos(getPos, editor, this.editor, this.name);
138
+
139
+ const toggleWrapper = createToggleWrapper(
140
+ block as any,
141
+ editor,
142
+ contentDOM,
143
+ );
144
+ dom.appendChild(toggleWrapper.dom);
145
+
146
+ return {
147
+ dom,
148
+ contentDOM,
149
+ ignoreMutation: toggleWrapper.ignoreMutation,
150
+ destroy: toggleWrapper.destroy,
151
+ };
152
+ };
153
+ },
156
154
  });
157
155
 
158
156
  export const Heading = createBlockSpecFromStronglyTypedTiptapNode(
@@ -18,6 +18,7 @@ export const handleEnter = (editor: BlockNoteEditor<any, any, any>) => {
18
18
 
19
19
  if (
20
20
  !(
21
+ blockContent.node.type.name === "toggleListItem" ||
21
22
  blockContent.node.type.name === "bulletListItem" ||
22
23
  blockContent.node.type.name === "numberedListItem" ||
23
24
  blockContent.node.type.name === "checkListItem"
@@ -0,0 +1,104 @@
1
+ import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
2
+ import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js";
3
+ import {
4
+ PropSchema,
5
+ createBlockSpecFromStronglyTypedTiptapNode,
6
+ createStronglyTypedTiptapNode,
7
+ getBlockFromPos,
8
+ } from "../../../schema/index.js";
9
+ import { createDefaultBlockDOMOutputSpec } from "../../defaultBlockHelpers.js";
10
+ import { defaultProps } from "../../defaultProps.js";
11
+ import { createToggleWrapper } from "../../ToggleWrapper/createToggleWrapper.js";
12
+ import { handleEnter } from "../ListItemKeyboardShortcuts.js";
13
+
14
+ export const toggleListItemPropSchema = {
15
+ ...defaultProps,
16
+ } satisfies PropSchema;
17
+
18
+ const ToggleListItemBlockContent = createStronglyTypedTiptapNode({
19
+ name: "toggleListItem",
20
+ content: "inline*",
21
+ group: "blockContent",
22
+ // This is to make sure that the list item Enter keyboard handler takes
23
+ // priority over the default one.
24
+ priority: 90,
25
+ addKeyboardShortcuts() {
26
+ return {
27
+ Enter: () => handleEnter(this.options.editor),
28
+ "Mod-Shift-6": () => {
29
+ const blockInfo = getBlockInfoFromSelection(this.editor.state);
30
+ if (
31
+ !blockInfo.isBlockContainer ||
32
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
33
+ ) {
34
+ return true;
35
+ }
36
+
37
+ return this.editor.commands.command(
38
+ updateBlockCommand(blockInfo.bnBlock.beforePos, {
39
+ type: "toggleListItem",
40
+ props: {},
41
+ }),
42
+ );
43
+ },
44
+ };
45
+ },
46
+
47
+ parseHTML() {
48
+ return [
49
+ // Parse from internal HTML.
50
+ {
51
+ tag: "div[data-content-type=" + this.name + "]",
52
+ contentElement: ".bn-inline-content",
53
+ },
54
+ ];
55
+ },
56
+
57
+ renderHTML({ HTMLAttributes }) {
58
+ return createDefaultBlockDOMOutputSpec(
59
+ this.name,
60
+ "p",
61
+ {
62
+ ...(this.options.domAttributes?.blockContent || {}),
63
+ ...HTMLAttributes,
64
+ },
65
+ this.options.domAttributes?.inlineContent || {},
66
+ );
67
+ },
68
+
69
+ addNodeView() {
70
+ return ({ HTMLAttributes, getPos }) => {
71
+ const { dom, contentDOM } = createDefaultBlockDOMOutputSpec(
72
+ this.name,
73
+ "p",
74
+ {
75
+ ...(this.options.domAttributes?.blockContent || {}),
76
+ ...HTMLAttributes,
77
+ },
78
+ this.options.domAttributes?.inlineContent || {},
79
+ );
80
+
81
+ const editor = this.options.editor;
82
+ const block = getBlockFromPos(getPos, editor, this.editor, this.name);
83
+
84
+ const toggleWrapper = createToggleWrapper(
85
+ block as any,
86
+ editor,
87
+ contentDOM,
88
+ );
89
+ dom.appendChild(toggleWrapper.dom);
90
+
91
+ return {
92
+ dom,
93
+ contentDOM,
94
+ ignoreMutation: toggleWrapper.ignoreMutation,
95
+ destroy: toggleWrapper.destroy,
96
+ };
97
+ };
98
+ },
99
+ });
100
+
101
+ export const ToggleListItem = createBlockSpecFromStronglyTypedTiptapNode(
102
+ ToggleListItemBlockContent,
103
+ toggleListItemPropSchema,
104
+ );
@@ -0,0 +1,182 @@
1
+ import { ViewMutationRecord } from "@tiptap/pm/view";
2
+
3
+ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
4
+ import { Block } from "../defaultBlocks.js";
5
+
6
+ type ToggledState = {
7
+ set: (block: Block<any, any, any>, isToggled: boolean) => void;
8
+ get: (block: Block<any, any, any>) => boolean;
9
+ };
10
+
11
+ export const defaultToggledState: ToggledState = {
12
+ set: (block, isToggled: boolean) =>
13
+ window.localStorage.setItem(
14
+ `toggle-${block.id}`,
15
+ isToggled ? "true" : "false",
16
+ ),
17
+ get: (block) => window.localStorage.getItem(`toggle-${block.id}`) === "true",
18
+ };
19
+
20
+ export const createToggleWrapper = (
21
+ block: Block<any, any, any>,
22
+ editor: BlockNoteEditor<any, any, any>,
23
+ renderedElement: HTMLElement,
24
+ toggledState: ToggledState = defaultToggledState,
25
+ ): {
26
+ dom: HTMLElement;
27
+ contentDOM?: HTMLElement;
28
+ ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
29
+ destroy?: () => void;
30
+ } => {
31
+ if ("isToggleable" in block.props && !block.props.isToggleable) {
32
+ return {
33
+ dom: renderedElement,
34
+ };
35
+ }
36
+
37
+ const dom = document.createElement("div");
38
+
39
+ const toggleWrapper = document.createElement("div");
40
+ toggleWrapper.className = "bn-toggle-wrapper";
41
+
42
+ const toggleButton = document.createElement("button");
43
+ toggleButton.className = "bn-toggle-button";
44
+ toggleButton.innerHTML =
45
+ // https://fonts.google.com/icons?selected=Material+Symbols+Rounded:chevron_right:FILL@0;wght@700;GRAD@0;opsz@24&icon.query=chevron&icon.style=Rounded&icon.size=24&icon.color=%23e8eaed
46
+ '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="CURRENTCOLOR"><path d="M320-200v-560l440 280-440 280Z"/></svg>';
47
+ const toggleButtonMouseDown = (event: MouseEvent) => event.preventDefault();
48
+ toggleButton.addEventListener("mousedown", toggleButtonMouseDown);
49
+ const toggleButtonOnClick = () => {
50
+ // Toggles visibility of child blocks. Also adds/removes the "add block"
51
+ // button if there are no child blocks.
52
+ if (toggleWrapper.getAttribute("data-show-children") === "true") {
53
+ toggleWrapper.setAttribute("data-show-children", "false");
54
+ toggledState.set(editor.getBlock(block)!, false);
55
+
56
+ if (dom.contains(toggleAddBlockButton)) {
57
+ dom.removeChild(toggleAddBlockButton);
58
+ }
59
+ } else {
60
+ toggleWrapper.setAttribute("data-show-children", "true");
61
+ toggledState.set(editor.getBlock(block)!, true);
62
+
63
+ if (
64
+ editor.getBlock(block)?.children.length === 0 &&
65
+ !dom.contains(toggleAddBlockButton)
66
+ ) {
67
+ dom.appendChild(toggleAddBlockButton);
68
+ }
69
+ }
70
+ };
71
+ toggleButton.addEventListener("click", toggleButtonOnClick);
72
+
73
+ toggleWrapper.appendChild(toggleButton);
74
+ toggleWrapper.appendChild(renderedElement);
75
+
76
+ const toggleAddBlockButton = document.createElement("button");
77
+ toggleAddBlockButton.className = "bn-toggle-add-block-button";
78
+ toggleAddBlockButton.textContent = "Empty toggle. Click to add a block.";
79
+ const toggleAddBlockButtonMouseDown = (event: MouseEvent) =>
80
+ event.preventDefault();
81
+ toggleAddBlockButton.addEventListener(
82
+ "mousedown",
83
+ toggleAddBlockButtonMouseDown,
84
+ );
85
+ const toggleAddBlockButtonOnClick = () => {
86
+ // Adds a single empty child block.
87
+ editor.transact(() => {
88
+ // dom.removeChild(toggleAddBlockButton);
89
+
90
+ const updatedBlock = editor.updateBlock(block, {
91
+ // Single empty block with default type.
92
+ children: [{}],
93
+ });
94
+ editor.setTextCursorPosition(updatedBlock.children[0].id, "end");
95
+ editor.focus();
96
+ });
97
+ };
98
+ toggleAddBlockButton.addEventListener("click", toggleAddBlockButtonOnClick);
99
+
100
+ dom.appendChild(toggleWrapper);
101
+
102
+ let childCount = block.children.length;
103
+ const onEditorChange = editor.onChange(() => {
104
+ const newChildCount = editor.getBlock(block)?.children.length ?? 0;
105
+
106
+ if (newChildCount > childCount) {
107
+ // If a child block is added while children are hidden, show children.
108
+ if (toggleWrapper.getAttribute("data-show-children") === "false") {
109
+ toggleWrapper.setAttribute("data-show-children", "true");
110
+ toggledState.set(editor.getBlock(block)!, true);
111
+ }
112
+
113
+ // Remove the "add block" button as we want to show child blocks and
114
+ // there is at least one child block.
115
+ if (dom.contains(toggleAddBlockButton)) {
116
+ dom.removeChild(toggleAddBlockButton);
117
+ }
118
+ } else if (newChildCount === 0 && newChildCount < childCount) {
119
+ // If the last child block is removed while children are shown, hide
120
+ // children.
121
+ if (toggleWrapper.getAttribute("data-show-children") === "true") {
122
+ toggleWrapper.setAttribute("data-show-children", "false");
123
+ toggledState.set(editor.getBlock(block)!, false);
124
+ }
125
+
126
+ // Remove the "add block" button as we want to hide child blocks,
127
+ // regardless of whether there are child blocks or not.
128
+ if (dom.contains(toggleAddBlockButton)) {
129
+ dom.removeChild(toggleAddBlockButton);
130
+ }
131
+ }
132
+
133
+ childCount = newChildCount;
134
+ });
135
+
136
+ if (toggledState.get(block)) {
137
+ toggleWrapper.setAttribute("data-show-children", "true");
138
+
139
+ if (block.children.length === 0) {
140
+ // If the toggle is set to show children, but there are no children,
141
+ // we add the "add block" button.
142
+ dom.appendChild(toggleAddBlockButton);
143
+ }
144
+ } else {
145
+ toggleWrapper.setAttribute("data-show-children", "false");
146
+ }
147
+
148
+ return {
149
+ dom,
150
+ // Prevents re-renders when the toggle button is clicked.
151
+ ignoreMutation: (mutation) => {
152
+ if (
153
+ mutation instanceof MutationRecord &&
154
+ // We want to prevent re-renders when the view changes, so we ignore
155
+ // all mutations where the `data-show-children` attribute is changed
156
+ // or the "add block" button is added/removed.
157
+ ((mutation.type === "attributes" &&
158
+ mutation.target === toggleWrapper &&
159
+ mutation.attributeName === "data-show-children") ||
160
+ (mutation.type === "childList" &&
161
+ (mutation.addedNodes[0] === toggleAddBlockButton ||
162
+ mutation.removedNodes[0] === toggleAddBlockButton)))
163
+ ) {
164
+ return true;
165
+ }
166
+ return false;
167
+ },
168
+ destroy: () => {
169
+ toggleButton.removeEventListener("mousedown", toggleButtonMouseDown);
170
+ toggleButton.removeEventListener("click", toggleButtonOnClick);
171
+ toggleAddBlockButton.removeEventListener(
172
+ "mousedown",
173
+ toggleAddBlockButtonMouseDown,
174
+ );
175
+ toggleAddBlockButton.removeEventListener(
176
+ "click",
177
+ toggleAddBlockButtonOnClick,
178
+ );
179
+ onEditorChange?.();
180
+ },
181
+ };
182
+ };
@@ -24,7 +24,11 @@ export function checkDefaultBlockTypeInSchema<
24
24
  >(
25
25
  blockType: BlockType,
26
26
  editor: BlockNoteEditor<any, I, S>,
27
- ): editor is BlockNoteEditor<{ Type: DefaultBlockSchema[BlockType] }, I, S> {
27
+ ): editor is BlockNoteEditor<
28
+ { [K in BlockType]: DefaultBlockSchema[BlockType] },
29
+ I,
30
+ S
31
+ > {
28
32
  return (
29
33
  blockType in editor.schema.blockSchema &&
30
34
  editor.schema.blockSchema[blockType] === defaultBlockSchema[blockType]
@@ -40,7 +44,7 @@ export function checkDefaultInlineContentTypeInSchema<
40
44
  editor: BlockNoteEditor<B, any, S>,
41
45
  ): editor is BlockNoteEditor<
42
46
  B,
43
- { Type: DefaultInlineContentSchema[InlineContentType] },
47
+ { [K in InlineContentType]: DefaultInlineContentSchema[InlineContentType] },
44
48
  S
45
49
  > {
46
50
  return (
@@ -25,6 +25,7 @@ import { CodeBlock } from "./CodeBlockContent/CodeBlockContent.js";
25
25
  import { FileBlock } from "./FileBlockContent/FileBlockContent.js";
26
26
  import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js";
27
27
  import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js";
28
+ import { ToggleListItem } from "./ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.js";
28
29
  import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js";
29
30
  import { CheckListItem } from "./ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js";
30
31
  import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js";
@@ -38,6 +39,7 @@ export const defaultBlockSpecs = {
38
39
  heading: Heading,
39
40
  quote: Quote,
40
41
  codeBlock: CodeBlock,
42
+ toggleListItem: ToggleListItem,
41
43
  bulletListItem: BulletListItem,
42
44
  numberedListItem: NumberedListItem,
43
45
  checkListItem: CheckListItem,
@@ -49,7 +49,7 @@ NESTED BLOCKS
49
49
  */
50
50
 
51
51
  .bn-block-group .bn-block-group {
52
- margin-left: 1.5em;
52
+ margin-left: 24px;
53
53
  }
54
54
 
55
55
  .bn-block-group .bn-block-group > .bn-block-outer {
@@ -130,6 +130,15 @@ NESTED BLOCKS
130
130
  [data-content-type="heading"][data-level="3"] {
131
131
  --level: 1.3em;
132
132
  }
133
+ [data-content-type="heading"][data-level="4"] {
134
+ --level: 1em;
135
+ }
136
+ [data-content-type="heading"][data-level="5"] {
137
+ --level: 0.9em;
138
+ }
139
+ [data-content-type="heading"][data-level="6"] {
140
+ --level: 0.8em;
141
+ }
133
142
 
134
143
  [data-prev-level="1"] {
135
144
  --prev-level: 3em;
@@ -140,6 +149,15 @@ NESTED BLOCKS
140
149
  [data-prev-level="3"] {
141
150
  --prev-level: 1.3em;
142
151
  }
152
+ [data-prev-level="4"] {
153
+ --prev-level: 1em;
154
+ }
155
+ [data-prev-level="5"] {
156
+ --prev-level: 0.9em;
157
+ }
158
+ [data-prev-level="6"] {
159
+ --prev-level: 0.8em;
160
+ }
143
161
 
144
162
  .bn-block-outer[data-prev-type="heading"] > .bn-block > .bn-block-content {
145
163
  font-size: var(--prev-level);
@@ -174,9 +192,11 @@ NESTED BLOCKS
174
192
  }
175
193
 
176
194
  /* Ordered */
177
- .bn-block-content[data-content-type="numberedListItem"] {
195
+ .bn-block-content[data-content-type="numberedListItem"]::before {
178
196
  display: flex;
179
- gap: 0.5em;
197
+ justify-content: center;
198
+ min-width: 24px;
199
+ padding-right: 4px;
180
200
  }
181
201
 
182
202
  [data-content-type="numberedListItem"] {
@@ -204,9 +224,11 @@ NESTED BLOCKS
204
224
  }
205
225
 
206
226
  /* Unordered */
207
- .bn-block-content[data-content-type="bulletListItem"] {
227
+ .bn-block-content[data-content-type="bulletListItem"]::before {
208
228
  display: flex;
209
- gap: 0.5em;
229
+ justify-content: center;
230
+ min-width: 24px;
231
+ padding-right: 4px;
210
232
  }
211
233
 
212
234
  /* Checked */
@@ -215,9 +237,15 @@ NESTED BLOCKS
215
237
  width: 100%;
216
238
  }
217
239
 
240
+ .bn-block-content[data-content-type="checkListItem"] > div > div {
241
+ display: flex;
242
+ justify-content: center;
243
+ min-width: 24px;
244
+ padding-right: 4px;
245
+ }
246
+
218
247
  .bn-block-content[data-content-type="checkListItem"] > div > div > input {
219
248
  margin: 0;
220
- margin-inline-end: 0.5em;
221
249
  cursor: pointer;
222
250
  }
223
251
 
@@ -234,6 +262,70 @@ NESTED BLOCKS
234
262
  justify-content: flex-end;
235
263
  }
236
264
 
265
+ /* Toggle */
266
+ .bn-block-content:has(.bn-toggle-wrapper) > div {
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: 4px;
270
+ }
271
+
272
+ .bn-block:has(
273
+ > .bn-block-content > div > .bn-toggle-wrapper[data-show-children="false"]
274
+ )
275
+ > .bn-block-group,
276
+ .bn-block:has(
277
+ > .react-renderer
278
+ > .bn-block-content
279
+ > div
280
+ > .bn-toggle-wrapper[data-show-children="false"]
281
+ )
282
+ > .bn-block-group {
283
+ display: none;
284
+ }
285
+
286
+ .bn-toggle-wrapper {
287
+ display: flex;
288
+ align-items: center;
289
+ }
290
+
291
+ .bn-toggle-button {
292
+ color: var(--bn-colors-editor-text);
293
+ padding: 3px;
294
+ }
295
+
296
+ .bn-toggle-button > svg {
297
+ width: 18px;
298
+ height: 18px;
299
+ }
300
+
301
+ .bn-toggle-wrapper[data-show-children="true"] .bn-toggle-button {
302
+ transform: rotate(90deg);
303
+ }
304
+
305
+ .bn-toggle-add-block-button {
306
+ font-size: 16px;
307
+ color: var(--bn-colors-side-menu);
308
+ font-weight: normal;
309
+ margin-left: 22px;
310
+ padding-inline: 2px;
311
+ width: fit-content;
312
+ }
313
+
314
+ .bn-toggle-button,
315
+ .bn-toggle-add-block-button {
316
+ background: none;
317
+ border: none;
318
+ border-radius: var(--bn-border-radius-small);
319
+ cursor: pointer;
320
+ display: flex;
321
+ user-select: none;
322
+ }
323
+
324
+ .bn-toggle-button:hover,
325
+ .bn-toggle-add-block-button:hover {
326
+ background-color: var(--bn-colors-hovered-background);
327
+ }
328
+
237
329
  /* No list nesting */
238
330
  .bn-block-outer[data-prev-type="bulletListItem"]
239
331
  > .bn-block
@@ -52,6 +52,7 @@ it("immediately replaces doc", async () => {
52
52
  "id": "2",
53
53
  "props": {
54
54
  "backgroundColor": "default",
55
+ "isToggleable": false,
55
56
  "level": 1,
56
57
  "textAlignment": "left",
57
58
  "textColor": "default",