diff --git a/packages/core/package.json b/packages/core/package.json
index c409f97fc5..afea1daadb 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -112,7 +112,8 @@
"uuid": "^8.3.2",
"y-prosemirror": "^1.3.4",
"y-protocols": "^1.0.6",
- "yjs": "^13.6.15"
+ "yjs": "^13.6.15",
+ "zod": "^3.25.30"
},
"devDependencies": {
"@types/emoji-mart": "^3.0.14",
diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
index f74757c8d7..a279f6ffb8 100644
--- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
+++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
@@ -1,5 +1,6 @@
import { DOMSerializer, Fragment } from "prosemirror-model";
+import * as z from "zod/v4/core";
import { PartialBlock } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
import {
@@ -90,10 +91,13 @@ function serializeBlock<
if (!block.props) {
props = {};
for (const [name, spec] of Object.entries(
- editor.schema.blockSchema[block.type as any].propSchema,
+ (editor.schema.blockSchema[block.type as any].propSchema as z.$ZodObject) // TODO
+ ._zod.def.shape,
)) {
- if (spec.default !== undefined) {
- (props as any)[name] = spec.default;
+ const def =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
+ if (def !== undefined) {
+ (props as any)[name] = def;
}
}
}
diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
index 0bd7722172..b94661460e 100644
--- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
+++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
@@ -1,5 +1,6 @@
import { DOMSerializer, Fragment } from "prosemirror-model";
+import * as z from "zod/v4/core";
import { PartialBlock } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
import {
@@ -65,10 +66,13 @@ function serializeBlock<
if (!block.props) {
props = {};
for (const [name, spec] of Object.entries(
- editor.schema.blockSchema[block.type as any].propSchema,
+ (editor.schema.blockSchema[block.type as any].propSchema as z.$ZodObject) // TODO
+ ._zod.def.shape,
)) {
- if (spec.default !== undefined) {
- (props as any)[name] = spec.default;
+ const def =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
+ if (def !== undefined) {
+ (props as any)[name] = def;
}
}
}
diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts
index 1f5d2c75d4..f60d9fedee 100644
--- a/packages/core/src/api/nodeConversions/nodeToBlock.ts
+++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts
@@ -1,4 +1,5 @@
import { Mark, Node, Schema, Slice } from "@tiptap/pm/model";
+import * as z from "zod/v4/core";
import type { Block } from "../../blocks/defaultBlocks.js";
import UniqueID from "../../extensions/UniqueID/UniqueID.js";
import type {
@@ -429,12 +430,22 @@ export function nodeToBlock<
...node.attrs,
...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}),
})) {
- const propSchema = blockSpec.propSchema;
+ const propSchema =
+ blockSpec.propSchema._zod.def.shape[
+ attr as keyof typeof blockSpec.propSchema._zod.def.shape
+ ];
- if (
- attr in propSchema &&
- !(propSchema[attr].default === undefined && value === undefined)
- ) {
+ if (!propSchema) {
+ continue;
+ }
+
+ const def =
+ propSchema instanceof z.$ZodDefault
+ ? propSchema._zod.def.defaultValue
+ : undefined;
+
+ // TODO: is this if statement correct?
+ if (!(def === undefined && value === undefined)) {
props[attr] = value;
}
}
diff --git a/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts b/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts
index 7a3e0101fe..488e6f5f73 100644
--- a/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts
+++ b/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts
@@ -1,13 +1,16 @@
+import z from "zod/v4";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
createBlockSpec,
FileBlockConfig,
- Props,
- PropSchema,
} from "../../schema/index.js";
import { defaultProps } from "../defaultProps.js";
+import {
+ baseFilePropSchema,
+ optionalFilePropFields,
+} from "../FileBlockContent/FileBlockContent.js";
import { parseFigureElement } from "../FileBlockContent/helpers/parse/parseFigureElement.js";
import { createFileBlockWrapper } from "../FileBlockContent/helpers/render/createFileBlockWrapper.js";
import { createFigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js";
@@ -17,30 +20,22 @@ import { parseAudioElement } from "./parseAudioElement.js";
export const FILE_AUDIO_ICON_SVG =
'';
-export const audioPropSchema = {
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
-
- showPreview: {
- default: true,
- },
-} satisfies PropSchema;
+export const audioPropSchema = z.object({
+ ...defaultProps.pick({
+ backgroundColor: true,
+ }).shape,
+ ...baseFilePropSchema.shape,
+ ...optionalFilePropFields.pick({
+ url: true,
+ showPreview: true,
+ previewWidth: true,
+ }).shape,
+});
export const audioBlockConfig = {
type: "audio" as const,
propSchema: audioPropSchema,
- content: "none",
+ content: "none" as const,
isFileBlock: true,
fileBlockAccept: ["audio/*"],
} satisfies FileBlockConfig;
@@ -76,7 +71,7 @@ export const audioRender = (
export const audioParse = (
element: HTMLElement,
-): Partial> | undefined => {
+): Partial> | undefined => {
if (element.tagName === "AUDIO") {
// Ignore if parent figure has already been parsed.
if (element.closest("figure")) {
diff --git a/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts b/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts
index e322a83be9..90f0e70aea 100644
--- a/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts
+++ b/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts
@@ -3,14 +3,13 @@ import { InputRule, isTextSelection } from "@tiptap/core";
import { TextSelection } from "@tiptap/pm/state";
import { Parser, createHighlightPlugin } from "prosemirror-highlight";
import { createParser } from "prosemirror-highlight/shiki";
+import { z } from "zod/v4";
import { BlockNoteEditor } from "../../index.js";
import {
- PropSchema,
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
} from "../../schema/index.js";
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
-
export type CodeBlockOptions = {
/**
* Whether to indent lines with a tab when the user presses `Tab` in a code block.
@@ -66,11 +65,9 @@ export const shikiParserSymbol = Symbol.for("blocknote.shikiParser");
export const shikiHighlighterPromiseSymbol = Symbol.for(
"blocknote.shikiHighlighterPromise",
);
-export const defaultCodeBlockPropSchema = {
- language: {
- default: "text",
- },
-} satisfies PropSchema;
+export const defaultCodeBlockPropSchema = z.object({
+ language: z.string().default("text"),
+});
const CodeBlockContent = createStronglyTypedTiptapNode({
name: "codeBlock",
diff --git a/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts b/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts
index 433487d8e0..c3f85e1c85 100644
--- a/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts
+++ b/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts
@@ -1,9 +1,9 @@
+import * as z from "zod/v4";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
- FileBlockConfig,
- PropSchema,
createBlockSpec,
+ FileBlockConfig,
} from "../../schema/index.js";
import { defaultProps } from "../defaultProps.js";
import { parseEmbedElement } from "./helpers/parse/parseEmbedElement.js";
@@ -11,21 +11,29 @@ import { parseFigureElement } from "./helpers/parse/parseFigureElement.js";
import { createFileBlockWrapper } from "./helpers/render/createFileBlockWrapper.js";
import { createLinkWithCaption } from "./helpers/toExternalHTML/createLinkWithCaption.js";
-export const filePropSchema = {
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
-} satisfies PropSchema;
+export const baseFilePropSchema = z.object({
+ caption: z.string().default(""),
+ name: z.string().default(""),
+});
+
+export const optionalFilePropFields = z.object({
+ // URL is optional, as we also want to accept files with no URL, but for example ids
+ // (ids can be used for files that are resolved on the backend)
+ url: z.string().default(""),
+ // Whether to show the file preview or the name only.
+ // This is useful for some file blocks, but not all
+ // (e.g.: not relevant for default "file" block which doesn;'t show previews)
+ showPreview: z.boolean().default(true),
+ // File preview width in px.
+ previewWidth: z.number(),
+});
+
+export const filePropSchema = defaultProps
+ .pick({
+ backgroundColor: true,
+ })
+ .extend(baseFilePropSchema.shape)
+ .extend(optionalFilePropFields.pick({ url: true }).shape);
export const fileBlockConfig = {
type: "file" as const,
diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts
index 8299892a03..8014e54b8c 100644
--- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts
+++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts
@@ -1,8 +1,8 @@
import { InputRule } from "@tiptap/core";
+import * as z from "zod/v4";
import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js";
import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js";
import {
- PropSchema,
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
propsToAttributes,
@@ -10,10 +10,9 @@ import {
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
-export const headingPropSchema = {
- ...defaultProps,
- level: { default: 1, values: [1, 2, 3] as const },
-} satisfies PropSchema;
+export const headingPropSchema = defaultProps.extend({
+ level: z.number().int().min(1).max(3).default(1),
+});
const HeadingBlockContent = createStronglyTypedTiptapNode({
name: "heading",
diff --git a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
index 32b7338640..9a4ec0e318 100644
--- a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
+++ b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
@@ -1,46 +1,37 @@
+import z from "zod/v4";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
createBlockSpec,
FileBlockConfig,
- Props,
- PropSchema,
} from "../../schema/index.js";
import { defaultProps } from "../defaultProps.js";
import { parseFigureElement } from "../FileBlockContent/helpers/parse/parseFigureElement.js";
+import { createResizableFileBlockWrapper } from "../FileBlockContent/helpers/render/createResizableFileBlockWrapper.js";
import { createFigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js";
import { createLinkWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js";
-import { createResizableFileBlockWrapper } from "../FileBlockContent/helpers/render/createResizableFileBlockWrapper.js";
import { parseImageElement } from "./parseImageElement.js";
export const FILE_IMAGE_ICON_SVG =
'';
-export const imagePropSchema = {
- textAlignment: defaultProps.textAlignment,
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
-
- showPreview: {
- default: true,
- },
- // File preview width in px.
- previewWidth: {
- default: undefined,
- type: "number",
- },
-} satisfies PropSchema;
+export const imagePropSchema = defaultProps
+ .pick({
+ textAlignment: true,
+ backgroundColor: true,
+ })
+ .extend({
+ // File name.
+ name: z.string().default(""),
+ // File url.
+ url: z.string().default(""),
+ // File caption.
+ caption: z.string().default(""),
+ // Show preview.
+ showPreview: z.boolean().default(true),
+ // File preview width in px.
+ previewWidth: z.number().optional(),
+ });
export const imageBlockConfig = {
type: "image" as const,
@@ -87,7 +78,7 @@ export const imageRender = (
export const imageParse = (
element: HTMLElement,
-): Partial> | undefined => {
+): Partial> | undefined => {
if (element.tagName === "IMG") {
// Ignore if parent figure has already been parsed.
if (element.closest("figure")) {
diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts
index e6412633c4..3b23092949 100644
--- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts
+++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts
@@ -2,7 +2,6 @@ import { InputRule } from "@tiptap/core";
import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js";
import {
- PropSchema,
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
} from "../../../schema/index.js";
@@ -11,9 +10,7 @@ import { defaultProps } from "../../defaultProps.js";
import { getListItemContent } from "../getListItemContent.js";
import { handleEnter } from "../ListItemKeyboardShortcuts.js";
-export const bulletListItemPropSchema = {
- ...defaultProps,
-} satisfies PropSchema;
+export const bulletListItemPropSchema = defaultProps;
const BulletListItemBlockContent = createStronglyTypedTiptapNode({
name: "bulletListItem",
diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
index 8ebf62aa63..9f2650cc56 100644
--- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
+++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts
@@ -1,11 +1,11 @@
import { InputRule } from "@tiptap/core";
+import * as z from "zod/v4";
import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
import {
getBlockInfoFromSelection,
getNearestBlockPos,
} from "../../../api/getBlockInfoFromPos.js";
import {
- PropSchema,
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
propsToAttributes,
@@ -15,12 +15,9 @@ import { defaultProps } from "../../defaultProps.js";
import { getListItemContent } from "../getListItemContent.js";
import { handleEnter } from "../ListItemKeyboardShortcuts.js";
-export const checkListItemPropSchema = {
- ...defaultProps,
- checked: {
- default: false,
- },
-} satisfies PropSchema;
+export const checkListItemPropSchema = defaultProps.extend({
+ checked: z.boolean().default(false),
+});
const checkListItemBlockContent = createStronglyTypedTiptapNode({
name: "checkListItem",
diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts
index 4e271bae14..598791d04b 100644
--- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts
+++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts
@@ -1,8 +1,8 @@
import { InputRule } from "@tiptap/core";
+import { z } from "zod/v4";
import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js";
import {
- PropSchema,
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
propsToAttributes,
@@ -13,10 +13,9 @@ import { getListItemContent } from "../getListItemContent.js";
import { handleEnter } from "../ListItemKeyboardShortcuts.js";
import { NumberedListIndexingPlugin } from "./NumberedListIndexingPlugin.js";
-export const numberedListItemPropSchema = {
- ...defaultProps,
- start: { default: undefined, type: "number" },
-} satisfies PropSchema;
+export const numberedListItemPropSchema = defaultProps.extend({
+ start: z.number().optional(),
+});
const NumberedListItemBlockContent = createStronglyTypedTiptapNode({
name: "numberedListItem",
diff --git a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts
index c8343a30f3..178bfab5a2 100644
--- a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts
+++ b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts
@@ -1,12 +1,9 @@
-import {
- createBlockSpec,
- CustomBlockConfig,
- Props,
-} from "../../schema/index.js";
+import z from "zod/v4";
+import { createBlockSpec, CustomBlockConfig } from "../../schema/index.js";
export const pageBreakConfig = {
type: "pageBreak" as const,
- propSchema: {},
+ propSchema: z.object(),
content: "none",
isFileBlock: false,
isSelectable: false,
@@ -23,7 +20,7 @@ export const pageBreakRender = () => {
};
export const pageBreakParse = (
element: HTMLElement,
-): Partial> | undefined => {
+): Partial> | undefined => {
if (element.tagName === "DIV" && element.hasAttribute("data-page-break")) {
return {
type: "pageBreak",
diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts
index 0c35c117a7..ae3c6b401c 100644
--- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts
+++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts
@@ -7,9 +7,7 @@ import {
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
-export const paragraphPropSchema = {
- ...defaultProps,
-};
+export const paragraphPropSchema = defaultProps;
export const ParagraphBlockContent = createStronglyTypedTiptapNode({
name: "paragraph",
diff --git a/packages/core/src/blocks/QuoteBlockContent/QuoteBlockContent.ts b/packages/core/src/blocks/QuoteBlockContent/QuoteBlockContent.ts
index 3c13c56c2d..5e4dc501bd 100644
--- a/packages/core/src/blocks/QuoteBlockContent/QuoteBlockContent.ts
+++ b/packages/core/src/blocks/QuoteBlockContent/QuoteBlockContent.ts
@@ -1,16 +1,14 @@
+import { InputRule } from "@tiptap/core";
+import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js";
+import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js";
import {
createBlockSpecFromStronglyTypedTiptapNode,
createStronglyTypedTiptapNode,
} from "../../schema/index.js";
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
-import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js";
-import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js";
-import { InputRule } from "@tiptap/core";
-export const quotePropSchema = {
- ...defaultProps,
-};
+export const quotePropSchema = defaultProps;
export const QuoteBlockContent = createStronglyTypedTiptapNode({
name: "quote",
diff --git a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts
index 6d26b8ec54..06544f3964 100644
--- a/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts
+++ b/packages/core/src/blocks/TableBlockContent/TableBlockContent.ts
@@ -13,9 +13,9 @@ import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
import { EMPTY_CELL_WIDTH, TableExtension } from "./TableExtension.js";
-export const tablePropSchema = {
- textColor: defaultProps.textColor,
-};
+export const tablePropSchema = defaultProps.pick({
+ textColor: true,
+});
export const TableBlockContent = createStronglyTypedTiptapNode({
name: "table",
diff --git a/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts b/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts
index af65e3d0df..45245fafaa 100644
--- a/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts
+++ b/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts
@@ -1,46 +1,37 @@
+import z from "zod/v4";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
createBlockSpec,
FileBlockConfig,
- Props,
- PropSchema,
} from "../../schema/index.js";
import { defaultProps } from "../defaultProps.js";
import { parseFigureElement } from "../FileBlockContent/helpers/parse/parseFigureElement.js";
+import { createResizableFileBlockWrapper } from "../FileBlockContent/helpers/render/createResizableFileBlockWrapper.js";
import { createFigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js";
import { createLinkWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js";
-import { createResizableFileBlockWrapper } from "../FileBlockContent/helpers/render/createResizableFileBlockWrapper.js";
import { parseVideoElement } from "./parseVideoElement.js";
export const FILE_VIDEO_ICON_SVG =
'';
-export const videoPropSchema = {
- textAlignment: defaultProps.textAlignment,
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
-
- showPreview: {
- default: true,
- },
- // File preview width in px.
- previewWidth: {
- default: undefined,
- type: "number",
- },
-} satisfies PropSchema;
+export const videoPropSchema = defaultProps
+ .pick({
+ textAlignment: true,
+ backgroundColor: true,
+ })
+ .extend({
+ // File name.
+ name: z.string().default(""),
+ // File url.
+ url: z.string().default(""),
+ // File caption.
+ caption: z.string().default(""),
+ // Show preview.
+ showPreview: z.boolean().default(true),
+ // File preview width in px.
+ previewWidth: z.number().optional(),
+ });
export const videoBlockConfig = {
type: "video" as const,
@@ -72,7 +63,9 @@ export const videoRender = (
video.controls = true;
video.contentEditable = "false";
video.draggable = false;
- video.width = block.props.previewWidth;
+ if (block.props.previewWidth) {
+ video.width = block.props.previewWidth;
+ }
videoWrapper.appendChild(video);
return createResizableFileBlockWrapper(
@@ -87,7 +80,7 @@ export const videoRender = (
export const videoParse = (
element: HTMLElement,
-): Partial> | undefined => {
+): Partial> | undefined => {
if (element.tagName === "VIDEO") {
// Ignore if parent figure has already been parsed.
if (element.closest("figure")) {
diff --git a/packages/core/src/blocks/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts
index 5db988da9a..f0db7f38a0 100644
--- a/packages/core/src/blocks/defaultBlockTypeGuards.ts
+++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts
@@ -1,4 +1,6 @@
+import { Selection } from "prosemirror-state";
import { CellSelection } from "prosemirror-tables";
+import * as z from "zod/v4/core";
import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
@@ -15,8 +17,6 @@ import {
defaultInlineContentSchema,
} from "./defaultBlocks.js";
import { defaultProps } from "./defaultProps.js";
-import { Selection } from "prosemirror-state";
-
export function checkDefaultBlockTypeInSchema<
BlockType extends keyof DefaultBlockSchema,
I extends InlineContentSchema,
@@ -89,9 +89,10 @@ export function checkBlockIsFileBlockWithPreview<
block: Block,
editor: BlockNoteEditor,
): block is BlockFromConfig<
- FileBlockConfig & {
- propSchema: Required;
- },
+ // FileBlockConfig & {
+ // propSchema: Required;
+ // },
+ any, // TODO
I,
S
> {
@@ -113,7 +114,7 @@ export function checkBlockIsFileBlockWithPlaceholder<
}
export function checkBlockTypeHasDefaultProp<
- Prop extends keyof typeof defaultProps,
+ Prop extends keyof typeof defaultProps.def.shape,
I extends InlineContentSchema,
S extends StyleSchema,
>(
@@ -124,9 +125,9 @@ export function checkBlockTypeHasDefaultProp<
{
[BT in string]: {
type: BT;
- propSchema: {
- [P in Prop]: (typeof defaultProps)[P];
- };
+ propSchema: z.$ZodObject<{
+ [P in Prop]: (typeof defaultProps.def.shape)[P];
+ }>;
content: "table" | "inline" | "none";
};
},
@@ -136,12 +137,13 @@ export function checkBlockTypeHasDefaultProp<
return (
blockType in editor.schema.blockSchema &&
prop in editor.schema.blockSchema[blockType].propSchema &&
- editor.schema.blockSchema[blockType].propSchema[prop] === defaultProps[prop]
+ editor.schema.blockSchema[blockType].propSchema[prop] ===
+ defaultProps.def.shape[prop]
);
}
export function checkBlockHasDefaultProp<
- Prop extends keyof typeof defaultProps,
+ Prop extends keyof typeof defaultProps.def.shape,
I extends InlineContentSchema,
S extends StyleSchema,
>(
@@ -149,13 +151,14 @@ export function checkBlockHasDefaultProp<
block: Block,
editor: BlockNoteEditor,
): block is BlockFromConfig<
- {
- type: string;
- propSchema: {
- [P in Prop]: (typeof defaultProps)[P];
- };
- content: "table" | "inline" | "none";
- },
+ // {
+ // type: string;
+ // propSchema: {
+ // [P in Prop]: (typeof defaultProps)[P];
+ // };
+ // content: "table" | "inline" | "none";
+ // },
+ any, // TODO
I,
S
> {
diff --git a/packages/core/src/blocks/defaultProps.ts b/packages/core/src/blocks/defaultProps.ts
index 4fb0b838c8..5b807c4597 100644
--- a/packages/core/src/blocks/defaultProps.ts
+++ b/packages/core/src/blocks/defaultProps.ts
@@ -1,22 +1,14 @@
-import type { Props, PropSchema } from "../schema/index.js";
-
+import * as z from "zod/v4";
// TODO: this system should probably be moved / refactored.
// The dependency from schema on this file doesn't make sense
-export const defaultProps = {
- backgroundColor: {
- default: "default" as const,
- },
- textColor: {
- default: "default" as const,
- },
- textAlignment: {
- default: "left" as const,
- values: ["left", "center", "right", "justify"] as const,
- },
-} satisfies PropSchema;
+export const defaultProps = z.object({
+ backgroundColor: z.string().default("default"),
+ textColor: z.string().default("default"),
+ textAlignment: z.enum(["left", "center", "right", "justify"]).default("left"),
+});
-export type DefaultProps = Props;
+export type DefaultProps = z.infer;
// Default props which are set on `blockContainer` nodes rather than
// `blockContent` nodes. Ensures that they are not redundantly added to
diff --git a/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts b/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts
index fca4922a1a..95e5137553 100644
--- a/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts
+++ b/packages/core/src/extensions/BackgroundColor/BackgroundColorExtension.ts
@@ -10,15 +10,15 @@ export const BackgroundColorExtension = Extension.create({
types: ["blockContainer", "tableCell", "tableHeader"],
attributes: {
backgroundColor: {
- default: defaultProps.backgroundColor.default,
+ default: defaultProps.shape.backgroundColor._zod.def.defaultValue,
parseHTML: (element) =>
element.hasAttribute("data-background-color")
? element.getAttribute("data-background-color")
- : defaultProps.backgroundColor.default,
+ : defaultProps.shape.backgroundColor._zod.def.defaultValue,
renderHTML: (attributes) => {
if (
attributes.backgroundColor ===
- defaultProps.backgroundColor.default
+ defaultProps.shape.backgroundColor._zod.def.defaultValue
) {
return {};
}
diff --git a/packages/core/src/extensions/TextColor/TextColorExtension.ts b/packages/core/src/extensions/TextColor/TextColorExtension.ts
index 4060fea6d6..d3a1934e89 100644
--- a/packages/core/src/extensions/TextColor/TextColorExtension.ts
+++ b/packages/core/src/extensions/TextColor/TextColorExtension.ts
@@ -10,13 +10,16 @@ export const TextColorExtension = Extension.create({
types: ["blockContainer", "tableCell", "tableHeader"],
attributes: {
textColor: {
- default: defaultProps.textColor.default,
+ default: defaultProps.shape.textColor._zod.def.defaultValue,
parseHTML: (element) =>
element.hasAttribute("data-text-color")
? element.getAttribute("data-text-color")
- : defaultProps.textColor.default,
+ : defaultProps.shape.textColor._zod.def.defaultValue,
renderHTML: (attributes) => {
- if (attributes.textColor === defaultProps.textColor.default) {
+ if (
+ attributes.textColor ===
+ defaultProps.shape.textColor._zod.def.defaultValue
+ ) {
return {};
}
return {
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 312e7dd537..8c35514363 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -78,5 +78,6 @@ export * from "./api/parsers/html/parseHTML.js";
export * from "./api/parsers/markdown/parseMarkdown.js";
// TODO: for ai, remove?
+export type * from "zod/v4";
export * from "./api/blockManipulation/getBlock/getBlock.js";
export * from "./api/positionMapping.js";
diff --git a/packages/core/src/schema/blocks/internal.ts b/packages/core/src/schema/blocks/internal.ts
index d3749ab53a..b807b65dd0 100644
--- a/packages/core/src/schema/blocks/internal.ts
+++ b/packages/core/src/schema/blocks/internal.ts
@@ -6,13 +6,13 @@ import {
Node,
NodeConfig,
} from "@tiptap/core";
+import * as z from "zod/v4/core";
import { defaultBlockToHTML } from "../../blocks/defaultBlockHelpers.js";
import { inheritedProps } from "../../blocks/defaultProps.js";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import { mergeCSSClasses } from "../../util/browser.js";
import { camelToDataKebab } from "../../util/string.js";
import { InlineContentSchema } from "../inlineContent/types.js";
-import { PropSchema, Props } from "../propTypes.js";
import { StyleSchema } from "../styles/types.js";
import {
BlockConfig,
@@ -26,15 +26,18 @@ import {
// Function that uses the 'propSchema' of a blockConfig to create a TipTap
// node's `addAttributes` property.
-// TODO: extract function
-export function propsToAttributes(propSchema: PropSchema): Attributes {
+// TODO: extract function0
+export function propsToAttributes(propSchema: z.$ZodObject): Attributes {
const tiptapAttributes: Record = {};
- Object.entries(propSchema)
+ Object.entries(propSchema._zod.def.shape)
.filter(([name, _spec]) => !inheritedProps.includes(name))
.forEach(([name, spec]) => {
+ const def =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
+
tiptapAttributes[name] = {
- default: spec.default,
+ default: def,
keepOnSplit: true,
// Props are displayed in kebab-case as HTML attributes. If a prop's
// value is the same as its default, we don't display an HTML
@@ -46,41 +49,19 @@ export function propsToAttributes(propSchema: PropSchema): Attributes {
return null;
}
- if (
- (spec.default === undefined && spec.type === "boolean") ||
- (spec.default !== undefined && typeof spec.default === "boolean")
- ) {
- if (value === "true") {
- return true;
- }
-
- if (value === "false") {
- return false;
- }
-
- return null;
+ // TBD: this might not be fault proof, but it's also ugly to store prop=""..."" for strings
+ try {
+ const jsonValue = JSON.parse(value);
+ // it was a number / boolean / json object stored as attribute
+ return z.parse(spec, jsonValue);
+ } catch (e) {
+ // it might have been a string directly stored as attribute
+ return z.parse(spec, value);
}
-
- if (
- (spec.default === undefined && spec.type === "number") ||
- (spec.default !== undefined && typeof spec.default === "number")
- ) {
- const asNumber = parseFloat(value);
- const isNumeric =
- !Number.isNaN(asNumber) && Number.isFinite(asNumber);
-
- if (isNumeric) {
- return asNumber;
- }
-
- return null;
- }
-
- return value;
},
renderHTML: (attributes) => {
// don't render to html if the value is the same as the default
- return attributes[name] !== spec.default
+ return attributes[name] !== def
? {
[camelToDataKebab(name)]: attributes[name],
}
@@ -142,7 +123,7 @@ export function getBlockFromPos<
// an `inlineContent` class to it.
export function wrapInBlockStructure<
BType extends string,
- PSchema extends PropSchema,
+ PSchema extends z.$ZodObject,
>(
element: {
dom: HTMLElement;
@@ -150,7 +131,7 @@ export function wrapInBlockStructure<
destroy?: () => void;
},
blockType: BType,
- blockProps: Props,
+ blockProps: z.output,
propSchema: PSchema,
isFileBlock = false,
domAttributes?: Record,
@@ -181,10 +162,18 @@ export function wrapInBlockStructure<
// which are already added as HTML attributes to the parent `blockContent`
// element (inheritedProps) and props set to their default values.
for (const [prop, value] of Object.entries(blockProps)) {
- const spec = propSchema[prop];
- const defaultValue = spec.default;
+ const spec = propSchema._zod.def.shape[prop];
+ const defaultValue =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
if (!inheritedProps.includes(prop) && value !== defaultValue) {
- blockContent.setAttribute(camelToDataKebab(prop), value);
+ if (typeof value === "string") {
+ blockContent.setAttribute(camelToDataKebab(prop), value);
+ } else {
+ blockContent.setAttribute(
+ camelToDataKebab(prop),
+ JSON.stringify(value),
+ );
+ }
}
}
// Adds file block attribute
@@ -249,7 +238,7 @@ export function createInternalBlockSpec(
export function createBlockSpecFromStronglyTypedTiptapNode<
T extends Node,
- P extends PropSchema,
+ P extends z.$ZodObject,
>(node: T, propSchema: P, requiredExtensions?: Array) {
return createInternalBlockSpec(
{
diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts
index 0f97205638..1889c1afee 100644
--- a/packages/core/src/schema/blocks/types.ts
+++ b/packages/core/src/schema/blocks/types.ts
@@ -1,13 +1,14 @@
/** Define the main block types **/
import type { Extension, Node } from "@tiptap/core";
-
+import * as z from "zod/v4";
+import * as zCore from "zod/v4/core";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import type {
InlineContent,
InlineContentSchema,
PartialInlineContent,
} from "../inlineContent/types.js";
-import type { PropSchema, Props } from "../propTypes.js";
+
import type { StyleSchema } from "../styles/types.js";
export type BlockNoteDOMElement =
@@ -21,34 +22,23 @@ export type BlockNoteDOMAttributes = Partial<{
[DOMElement in BlockNoteDOMElement]: Record;
}>;
+const filePropSchema = z.looseObject({
+ caption: z.string().default(""),
+ name: z.string().default(""),
+ // URL is optional, as we also want to accept files with no URL, but for example ids
+ // (ids can be used for files that are resolved on the backend)
+ // url: z.string().default("").optional(),
+ // // Whether to show the file preview or the name only.
+ // // This is useful for some file blocks, but not all
+ // // (e.g.: not relevant for default "file" block which doesn;'t show previews)
+ // showPreview: z.boolean().default(true).optional(),
+ // // File preview width in px.
+ // previewWidth: z.number().optional(),
+});
+
export type FileBlockConfig = {
type: string;
- readonly propSchema: PropSchema & {
- caption: {
- default: "";
- };
- name: {
- default: "";
- };
-
- // URL is optional, as we also want to accept files with no URL, but for example ids
- // (ids can be used for files that are resolved on the backend)
- url?: {
- default: "";
- };
-
- // Whether to show the file preview or the name only.
- // This is useful for some file blocks, but not all
- // (e.g.: not relevant for default "file" block which doesn;'t show previews)
- showPreview?: {
- default: boolean;
- };
- // File preview width in px.
- previewWidth?: {
- default: undefined;
- type: "number";
- };
- };
+ readonly propSchema: typeof filePropSchema;
content: "none";
isSelectable?: boolean;
isFileBlock: true;
@@ -60,7 +50,7 @@ export type FileBlockConfig = {
export type BlockConfig =
| {
type: string;
- readonly propSchema: PropSchema;
+ readonly propSchema: zCore.$ZodObject;
content: "inline" | "none" | "table";
isSelectable?: boolean;
isFileBlock?: false;
@@ -186,7 +176,7 @@ export type BlockFromConfigNoChildren<
> = {
id: string;
type: B["type"];
- props: Props;
+ props: z.output;
content: B["content"] extends "inline"
? InlineContent[]
: B["content"] extends "table"
@@ -270,7 +260,7 @@ type PartialBlockFromConfigNoChildren<
> = {
id?: string;
type?: B["type"];
- props?: Partial>;
+ props?: Partial>;
content?: B["content"] extends "inline"
? PartialInlineContent
: B["content"] extends "table"
diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts
index 7f7cafc561..cf987a86e0 100644
--- a/packages/core/src/schema/index.ts
+++ b/packages/core/src/schema/index.ts
@@ -4,7 +4,6 @@ export * from "./blocks/types.js";
export * from "./inlineContent/createSpec.js";
export * from "./inlineContent/internal.js";
export * from "./inlineContent/types.js";
-export * from "./propTypes.js";
export * from "./styles/createSpec.js";
export * from "./styles/internal.js";
export * from "./styles/types.js";
diff --git a/packages/core/src/schema/inlineContent/createSpec.ts b/packages/core/src/schema/inlineContent/createSpec.ts
index 9168c1207b..4bd7d248f2 100644
--- a/packages/core/src/schema/inlineContent/createSpec.ts
+++ b/packages/core/src/schema/inlineContent/createSpec.ts
@@ -5,7 +5,8 @@ import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js";
import { nodeToCustomInlineContent } from "../../api/nodeConversions/nodeToBlock.js";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import { propsToAttributes } from "../blocks/internal.js";
-import { Props } from "../propTypes.js";
+
+import * as z from "zod/v4/core";
import { StyleSchema } from "../styles/types.js";
import {
addInlineContentAttributes,
@@ -18,7 +19,6 @@ import {
InlineContentSpec,
PartialCustomInlineContentFromConfig,
} from "./types.js";
-
// TODO: support serialization
export type CustomInlineContentImplementation<
@@ -116,7 +116,7 @@ export function createInlineContentSpec<
return addInlineContentAttributes(
output,
inlineContentConfig.type,
- node.attrs as Props,
+ node.attrs as z.infer,
inlineContentConfig.propSchema,
);
},
@@ -148,7 +148,7 @@ export function createInlineContentSpec<
return addInlineContentAttributes(
output,
inlineContentConfig.type,
- node.attrs as Props,
+ node.attrs as z.infer,
inlineContentConfig.propSchema,
);
};
diff --git a/packages/core/src/schema/inlineContent/internal.ts b/packages/core/src/schema/inlineContent/internal.ts
index 3e438d7cfb..1039c6f7f0 100644
--- a/packages/core/src/schema/inlineContent/internal.ts
+++ b/packages/core/src/schema/inlineContent/internal.ts
@@ -1,7 +1,8 @@
import { KeyboardShortcutCommand, Node } from "@tiptap/core";
import { camelToDataKebab } from "../../util/string.js";
-import { PropSchema, Props } from "../propTypes.js";
+
+import * as z from "zod/v4/core";
import {
CustomInlineContentConfig,
InlineContentConfig,
@@ -10,21 +11,20 @@ import {
InlineContentSpec,
InlineContentSpecs,
} from "./types.js";
-
// Function that adds necessary classes and attributes to the `dom` element
// returned from a custom inline content's 'render' function, to ensure no data
// is lost on internal copy & paste.
export function addInlineContentAttributes<
IType extends string,
- PSchema extends PropSchema,
+ PSchema extends z.$ZodObject,
>(
element: {
dom: HTMLElement;
contentDOM?: HTMLElement;
},
inlineContentType: IType,
- inlineContentProps: Props,
- propSchema: PSchema,
+ inlineContentProps: z.infer,
+ propSchema: z.$ZodObject,
): {
dom: HTMLElement;
contentDOM?: HTMLElement;
@@ -35,13 +35,17 @@ export function addInlineContentAttributes<
// set to their default values.
Object.entries(inlineContentProps)
.filter(([prop, value]) => {
- const spec = propSchema[prop];
- return value !== spec.default;
+ const spec = propSchema._zod.def.shape[prop];
+ const defaultValue =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
+ return value !== defaultValue;
})
.map(([prop, value]) => {
- return [camelToDataKebab(prop), value];
+ return [camelToDataKebab(prop), value] satisfies [string, unknown];
})
- .forEach(([prop, value]) => element.dom.setAttribute(prop, value));
+ .forEach(([prop, value]) =>
+ element.dom.setAttribute(prop, JSON.stringify(value)),
+ );
if (element.contentDOM !== undefined) {
element.contentDOM.setAttribute("data-editable", "");
@@ -85,7 +89,7 @@ export function createInternalInlineContentSpec(
export function createInlineContentSpecFromTipTapNode<
T extends Node,
- P extends PropSchema,
+ P extends z.$ZodObject,
>(node: T, propSchema: P) {
return createInternalInlineContentSpec(
{
diff --git a/packages/core/src/schema/inlineContent/types.ts b/packages/core/src/schema/inlineContent/types.ts
index 6ec87055d4..a975923899 100644
--- a/packages/core/src/schema/inlineContent/types.ts
+++ b/packages/core/src/schema/inlineContent/types.ts
@@ -1,11 +1,11 @@
import { Node } from "@tiptap/core";
-import { PropSchema, Props } from "../propTypes.js";
-import { StyleSchema, Styles } from "../styles/types.js";
+import * as z from "zod/v4/core";
+import { StyleSchema, Styles } from "../styles/types.js";
export type CustomInlineContentConfig = {
type: string;
content: "styled" | "none"; // | "plain"
- readonly propSchema: PropSchema;
+ readonly propSchema: z.$ZodObject;
// content: "inline" | "none" | "table";
};
// InlineContentConfig contains the "schema" info about an InlineContent type
@@ -47,7 +47,7 @@ export type CustomInlineContentFromConfig<
S extends StyleSchema,
> = {
type: I["type"];
- props: Props;
+ props: z.infer;
content: I["content"] extends "styled"
? StyledText[]
: I["content"] extends "plain"
@@ -73,7 +73,7 @@ export type PartialCustomInlineContentFromConfig<
S extends StyleSchema,
> = {
type: I["type"];
- props?: Props;
+ props?: Partial>;
content?: I["content"] extends "styled"
? StyledText[] | string
: I["content"] extends "plain"
diff --git a/packages/core/src/schema/propTypes.ts b/packages/core/src/schema/propTypes.ts
deleted file mode 100644
index 76a8df2769..0000000000
--- a/packages/core/src/schema/propTypes.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// The PropSpec specifies the type of a prop and possibly a default value.
-// Note that props are always optional when used as "input"
-// (i.e., when creating a PartialBlock, for example by calling `insertBlocks({...})`)
-//
-// However, internally they're always set to `default`, unless a prop is marked optional
-//
-// At some point we should migrate this to zod or effect-schema
-export type PropSpec =
- | {
- // We infer the type of the prop from the default value
- default: PType;
- // a list of possible values, for example for a string prop (this will then be used as a string union type)
- values?: readonly PType[];
- }
- | {
- default: undefined;
- // Because there is no default value (for an optional prop, the default value is undefined),
- // we need to specify the type of the prop manually (we can't infer it from the default value)
- type: "string" | "number" | "boolean";
- values?: readonly PType[];
- };
-
-// Defines multiple block prop specs. The key of each prop is the name of the
-// prop, while the value is a corresponding prop spec. This should be included
-// in a block config or schema. From a prop schema, we can derive both the props'
-// internal implementation (as TipTap node attributes) and the type information
-// for the external API.
-export type PropSchema = Record>;
-
-// Defines Props objects for use in Block objects in the external API. Converts
-// each prop spec into a union type of its possible values, or a string if no
-// values are specified.
-export type Props = {
- // for required props, get type from type of "default" value,
- // and if values are specified, get type from values
- [PName in keyof PSchema]: (
- PSchema[PName] extends { default: boolean } | { type: "boolean" }
- ? PSchema[PName]["values"] extends readonly boolean[]
- ? PSchema[PName]["values"][number]
- : boolean
- : PSchema[PName] extends { default: number } | { type: "number" }
- ? PSchema[PName]["values"] extends readonly number[]
- ? PSchema[PName]["values"][number]
- : number
- : PSchema[PName] extends { default: string } | { type: "string" }
- ? PSchema[PName]["values"] extends readonly string[]
- ? PSchema[PName]["values"][number]
- : string
- : never
- ) extends infer T
- ? PSchema[PName] extends { optional: true }
- ? T | undefined
- : T
- : never;
-};
diff --git a/packages/react/package.json b/packages/react/package.json
index 68c50eec8f..e9dcad935f 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -66,7 +66,8 @@
"@tiptap/react": "^2.12.0",
"emoji-mart": "^5.6.0",
"lodash.merge": "^4.6.2",
- "react-icons": "^5.2.1"
+ "react-icons": "^5.2.1",
+ "zod": "^3.25.36"
},
"devDependencies": {
"@types/emoji-mart": "^3.0.14",
diff --git a/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx b/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx
index b602aaa971..46914e61e2 100644
--- a/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx
+++ b/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx
@@ -1,4 +1,4 @@
-import { FileBlockConfig, audioBlockConfig, audioParse } from "@blocknote/core";
+import { audioBlockConfig, audioParse } from "@blocknote/core";
import { RiVolumeUpFill } from "react-icons/ri";
@@ -6,18 +6,18 @@ import {
ReactCustomBlockRenderProps,
createReactBlockSpec,
} from "../../schema/ReactBlockSpec.js";
-import { useResolveUrl } from "../FileBlockContent/useResolveUrl.js";
-import { FigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/FigureWithCaption.js";
import { FileBlockWrapper } from "../FileBlockContent/helpers/render/FileBlockWrapper.js";
+import { FigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/FigureWithCaption.js";
import { LinkWithCaption } from "../FileBlockContent/helpers/toExternalHTML/LinkWithCaption.js";
+import { useResolveUrl } from "../FileBlockContent/useResolveUrl.js";
export const AudioPreview = (
props: Omit<
- ReactCustomBlockRenderProps,
+ ReactCustomBlockRenderProps,
"contentRef"
>,
) => {
- const resolved = useResolveUrl(props.block.props.url!);
+ const resolved = useResolveUrl(props.block.props.url);
return (