diff --git a/examples/06-custom-schema/08-custom-table-props/.bnexample.json b/examples/06-custom-schema/08-custom-table-props/.bnexample.json new file mode 100644 index 0000000000..79b7f88d51 --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/.bnexample.json @@ -0,0 +1,11 @@ +{ + "playground": true, + "docs": true, + "author": "Richard Misiak", + "tags": [ + "Intermediate", + "UI Components", + "Tables", + "Appearance & Styling" + ] +} \ No newline at end of file diff --git a/examples/06-custom-schema/08-custom-table-props/README.md b/examples/06-custom-schema/08-custom-table-props/README.md new file mode 100644 index 0000000000..5121e71db2 --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/README.md @@ -0,0 +1,8 @@ +# Custom table props + +This example demonstrates overriding the schema for tables to allow for custom props to be used. + +**Relevant Docs:** + +- [Tables](/docs/features/custom-schemas) +- [Custom Schemas](/docs/features/custom-schemas) diff --git a/examples/06-custom-schema/08-custom-table-props/index.html b/examples/06-custom-schema/08-custom-table-props/index.html new file mode 100644 index 0000000000..19e0c72013 --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/index.html @@ -0,0 +1,14 @@ + + + + + Custom table props + + + +
+ + + diff --git a/examples/06-custom-schema/08-custom-table-props/main.tsx b/examples/06-custom-schema/08-custom-table-props/main.tsx new file mode 100644 index 0000000000..677c7f7eed --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/06-custom-schema/08-custom-table-props/package.json b/examples/06-custom-schema/08-custom-table-props/package.json new file mode 100644 index 0000000000..7098076d26 --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-custom-schema-custom-table-props", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^8.3.11", + "@mantine/hooks": "^8.3.11", + "@mantine/utils": "^6.0.22", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^4.7.0", + "vite": "^5.4.20" + } +} \ No newline at end of file diff --git a/examples/06-custom-schema/08-custom-table-props/src/App.tsx b/examples/06-custom-schema/08-custom-table-props/src/App.tsx new file mode 100644 index 0000000000..06c28ac5d8 --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/src/App.tsx @@ -0,0 +1,147 @@ +import "@blocknote/core/fonts/inter.css"; +import "@blocknote/mantine/style.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import { useCreateBlockNote } from "@blocknote/react"; +import { + BlockNoteSchema, + defaultBlockSpecs, + createTableBlockSpec, +} from "@blocknote/core"; +import { useState } from "react"; + +const customTable = createTableBlockSpec({ + customProp: { + default: "", + }, +}); + +const schema = BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + table: customTable, + }, +}); + +export default function App() { + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "table", + props: { + textColor: "default", + customProp: "custom", + }, + content: { + type: "tableContent", + + headerRows: 1, + rows: [ + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Foo", + styles: {}, + }, + ], + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Bar", + styles: {}, + }, + ], + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "value", + styles: {}, + }, + ], + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "value", + styles: {}, + }, + ], + }, + ], + }, + ], + }, + children: [], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "This is a table that has a custom prop `customProp` defined. Click on the button to change it to a random value. The JSON dump of the document is shown.", + }, + ], + }, + ], + }); + + const [doc, setDoc] = useState(editor.document); + + editor.onChange((editor) => { + setDoc(editor.document); // re-render on changes + }); + + return ( +
+
+ + +
+
+

Document:

+
+          {JSON.stringify(editor.document, null, 2)}
+        
+
+
+ ); +} diff --git a/examples/06-custom-schema/08-custom-table-props/tsconfig.json b/examples/06-custom-schema/08-custom-table-props/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/06-custom-schema/08-custom-table-props/vite.config.ts b/examples/06-custom-schema/08-custom-table-props/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/06-custom-schema/08-custom-table-props/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 5048f91a2b..8d04ab95d1 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -425,11 +425,25 @@ export function nodeToBlock< throw Error("Block is of an unrecognized type: " + blockInfo.blockNoteType); } + const blockConfig = blockSchema[blockInfo.blockNoteType]; + const props: any = {}; - for (const [attr, value] of Object.entries({ + + // Collect attributes from node, blockContent, and table content node (for tables) + const attrsToCheck = { ...node.attrs, ...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}), - })) { + }; + + // For table blocks, also check the table content node for custom props + if (blockConfig.content === "table" && blockInfo.isBlockContainer) { + const tableContentNode = blockInfo.blockContent.node; + if (tableContentNode && tableContentNode.attrs) { + Object.assign(attrsToCheck, tableContentNode.attrs); + } + } + + for (const [attr, value] of Object.entries(attrsToCheck)) { const propSchema = blockSpec.propSchema; if ( @@ -440,8 +454,6 @@ export function nodeToBlock< } } - const blockConfig = blockSchema[blockInfo.blockNoteType]; - const children: Block[] = []; blockInfo.childContainer?.node.forEach((child) => { children.push( diff --git a/packages/core/src/blocks/Table/block.ts b/packages/core/src/blocks/Table/block.ts index 9a23d227aa..29afabd60f 100644 --- a/packages/core/src/blocks/Table/block.ts +++ b/packages/core/src/blocks/Table/block.ts @@ -9,6 +9,8 @@ import { createBlockSpecFromTiptapNode, TableContent, } from "../../schema/index.js"; +import { propsToAttributes } from "../../schema/blocks/internal.js"; +import { PropSchema } from "../../schema/propTypes.js"; import { mergeCSSClasses } from "../../util/browser.js"; import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js"; import { defaultProps } from "../defaultProps.js"; @@ -381,10 +383,23 @@ export type TableBlockConfig = BlockConfig< "table" >; -export const createTableBlockSpec = () => - createBlockSpecFromTiptapNode( - { node: TiptapTableNode, type: "table", content: "table" }, - tablePropSchema, +export const createTableBlockSpec = (additionalPropSchema?: PropSchema) => { + // Merge the base table prop schema with any additional props + const mergedPropSchema = { + ...tablePropSchema, + ...(additionalPropSchema || {}), + }; + + // Extend the TiptapTableNode to add attributes based on the merged prop schema + const extendedTableNode = TiptapTableNode.extend({ + addAttributes() { + return propsToAttributes(mergedPropSchema); + }, + }); + + return createBlockSpecFromTiptapNode( + { node: extendedTableNode, type: "table", content: "table" }, + mergedPropSchema, [ createExtension({ key: "table-extensions", @@ -452,6 +467,7 @@ export const createTableBlockSpec = () => }), ], ); +}; // We need to declare this here because we aren't using the table extensions from tiptap, so the types are not automatically inferred. declare module "@tiptap/core" { diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index d73534652c..310d6f788c 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1373,6 +1373,28 @@ }, "readme": "This example shows how you can configure the editor's default blocks. Specifically, heading blocks are made to only support levels 1-3, and cannot be toggleable.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Default Schema](/docs/foundations/schemas)\n- [Custom Schemas](/docs/features/custom-schemas)" }, + { + "projectSlug": "custom-table-props", + "fullSlug": "custom-schema/custom-table-props", + "pathFromRoot": "examples/06-custom-schema/08-custom-table-props", + "config": { + "playground": true, + "docs": true, + "author": "Richard Misiak", + "tags": [ + "Intermediate", + "UI Components", + "Tables", + "Appearance & Styling" + ] + }, + "title": "Custom table props", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "This example demonstrates overriding the schema for tables to allow for custom props to be used.\n\n**Relevant Docs:**\n\n- [Tables](/docs/features/custom-schemas)\n- [Custom Schemas](/docs/features/custom-schemas)" + }, { "projectSlug": "draggable-inline-content", "fullSlug": "custom-schema/draggable-inline-content", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 461388700a..f8fc676ff5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3304,6 +3304,52 @@ importers: specifier: ^5.4.20 version: 5.4.20(@types/node@25.2.1)(lightningcss@1.30.2)(terser@5.46.0) + examples/06-custom-schema/08-custom-table-props: + dependencies: + '@blocknote/ariakit': + specifier: latest + version: link:../../../packages/ariakit + '@blocknote/core': + specifier: latest + version: link:../../../packages/core + '@blocknote/mantine': + specifier: latest + version: link:../../../packages/mantine + '@blocknote/react': + specifier: latest + version: link:../../../packages/react + '@blocknote/shadcn': + specifier: latest + version: link:../../../packages/shadcn + '@mantine/core': + specifier: ^8.3.11 + version: 8.3.11(@mantine/hooks@8.3.11(react@19.2.3))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mantine/hooks': + specifier: ^8.3.11 + version: 8.3.11(react@19.2.3) + '@mantine/utils': + specifier: ^6.0.22 + version: 6.0.22(react@19.2.3) + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + devDependencies: + '@types/react': + specifier: ^19.2.3 + version: 19.2.8 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.8) + '@vitejs/plugin-react': + specifier: ^4.7.0 + version: 4.7.0(vite@5.4.20(@types/node@25.2.1)(lightningcss@1.30.2)(terser@5.46.0)) + vite: + specifier: ^5.4.20 + version: 5.4.20(@types/node@25.2.1)(lightningcss@1.30.2)(terser@5.46.0) + examples/06-custom-schema/draggable-inline-content: dependencies: '@blocknote/ariakit':