From 4eb4f6c97e2fcc6353705018c1d2aeb1547875c0 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 4 Mar 2026 15:29:41 +0100 Subject: [PATCH 1/2] docs: update typescript types to be more convenient to use --- .../04-pdf-file-block/src/PDF.tsx | 9 +- packages/core/src/schema/blocks/createSpec.ts | 53 +++------- packages/core/src/schema/blocks/types.ts | 94 ++++++++++++++++++ packages/react/src/blocks/Audio/block.tsx | 18 +--- .../File/helpers/render/AddFileButton.tsx | 9 +- .../File/helpers/render/FileBlockWrapper.tsx | 10 +- .../File/helpers/render/FileNameWithIcon.tsx | 6 +- .../render/ResizableFileBlockWrapper.tsx | 18 ++-- packages/react/src/blocks/Image/block.tsx | 18 +--- .../blocks/ToggleWrapper/ToggleWrapper.tsx | 6 +- packages/react/src/blocks/Video/block.tsx | 18 +--- packages/react/src/schema/ReactBlockSpec.tsx | 99 +++++++++---------- 12 files changed, 189 insertions(+), 169 deletions(-) diff --git a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx index f17619d8ba..8ce605ea70 100644 --- a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx @@ -10,14 +10,7 @@ import { RiFilePdfFill } from "react-icons/ri"; import "./styles.css"; export const PDFPreview = ( - props: Omit< - ReactCustomBlockRenderProps< - FileBlockConfig["type"], - FileBlockConfig["propSchema"], - FileBlockConfig["content"] - >, - "contentRef" - >, + props: Omit, "contentRef">, ) => { return ( > | undefined = undefined, >( blockConfigOrCreator: BlockConfig, - blockImplementationOrCreator: - | BlockImplementation - | (TOptions extends undefined - ? () => BlockImplementation - : ( - options: Partial, - ) => BlockImplementation), + blockImplementationOrCreator: BlockImplementationOrCreator< + BlockConfig, + TOptions + >, extensionsOrCreator?: | ExtensionFactoryInstance[] | (TOptions extends undefined @@ -318,25 +317,10 @@ export function createBlockSpec< const TOptions extends Partial>, >( blockCreator: (options: Partial) => BlockConf, - blockImplementationOrCreator: - | BlockImplementation< - BlockConf["type"], - BlockConf["propSchema"], - BlockConf["content"] - > - | (TOptions extends undefined - ? () => BlockImplementation< - BlockConf["type"], - BlockConf["propSchema"], - BlockConf["content"] - > - : ( - options: Partial, - ) => BlockImplementation< - BlockConf["type"], - BlockConf["propSchema"], - BlockConf["content"] - >), + blockImplementationOrCreator: BlockImplementationOrCreator< + BlockConf, + TOptions + >, extensionsOrCreator?: | ExtensionFactoryInstance[] | (TOptions extends undefined @@ -355,18 +339,11 @@ export function createBlockSpec< const TContent extends "inline" | "none", const TOptions extends Partial> | undefined = undefined, >( - blockConfigOrCreator: - | BlockConfig - | (TOptions extends undefined - ? () => BlockConfig - : (options: Partial) => BlockConfig), - blockImplementationOrCreator: - | BlockImplementation - | (TOptions extends undefined - ? () => BlockImplementation - : ( - options: Partial, - ) => BlockImplementation), + blockConfigOrCreator: BlockConfigOrCreator, + blockImplementationOrCreator: BlockImplementationOrCreator< + BlockConfig, + TOptions + >, extensionsOrCreator?: | ExtensionFactoryInstance[] | (TOptions extends undefined diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 06270c6d82..086cac370f 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -86,6 +86,34 @@ export interface BlockConfig< // e.g. tables, alerts (with title & content) } +/** + * BlockConfigOrCreator is a union type of BlockConfig and a function that returns a BlockConfig. + * This is used to create block configs that can be passed to the createBlockSpec function. + */ +export type BlockConfigOrCreator< + TName extends string = string, + TProps extends PropSchema = PropSchema, + TContent extends "inline" | "none" = "inline" | "none", + TOptions extends Record | undefined = + | Record + | undefined, +> = + | BlockConfig + | (TOptions extends undefined + ? () => BlockConfig + : (options: Partial) => BlockConfig); + +/** + * ExtractBlockConfigFromConfigOrCreator is a helper type that extracts the BlockConfig type from a BlockConfigOrCreator. + */ +export type ExtractBlockConfigFromConfigOrCreator< + ConfigOrCreator extends + | BlockConfig + | ((...args: any[]) => BlockConfig), +> = ConfigOrCreator extends (...args: any[]) => infer Config + ? Config + : ConfigOrCreator; + // restrict content to "inline" and "none" only export type CustomBlockConfig< T extends string = string, @@ -104,6 +132,32 @@ export type BlockSpec< extensions?: (Extension | ExtensionFactoryInstance)[]; }; +/** + * BlockSpecOrCreator is a union type of BlockSpec and a function that returns a BlockSpec. + * This is used to create block specs that can be passed to the createBlockSpec function. + */ +export type BlockSpecOrCreator< + T extends string = string, + PS extends PropSchema = PropSchema, + C extends "inline" | "none" | "table" = "inline" | "none" | "table", + TOptions extends Record | undefined = + | Record + | undefined, +> = + | BlockSpec + | (TOptions extends undefined + ? () => BlockSpec + : (options: Partial) => BlockSpec); + +/** + * ExtractBlockSpecFromSpecOrCreator is a helper type that extracts the BlockSpec type from a BlockSpecOrCreator. + */ +export type ExtractBlockSpecFromSpecOrCreator< + SpecOrCreator extends + | BlockSpec + | ((...args: any[]) => BlockSpec), +> = SpecOrCreator extends (...args: any[]) => infer Spec ? Spec : SpecOrCreator; + /** * This allows de-coupling the types that we display to users versus the types we expose internally. * @@ -497,6 +551,46 @@ export type BlockImplementation< parseContent?: (options: { el: HTMLElement; schema: Schema }) => Fragment; }; +/** + * BlockImplementationOrCreator is a union type of BlockImplementation and a function that returns a BlockImplementation. + * This is used to create block implementations that can be passed to the createBlockSpec function. + */ +export type BlockImplementationOrCreator< + ConfigOrCreator extends BlockConfigOrCreator = BlockConfigOrCreator, + TOptions extends Record | undefined = + | Record + | undefined, + Config extends + ExtractBlockConfigFromConfigOrCreator = ExtractBlockConfigFromConfigOrCreator, +> = + | BlockImplementation + | (TOptions extends undefined + ? () => BlockImplementation< + Config["type"], + Config["propSchema"], + Config["content"] + > + : ( + options: Partial, + ) => BlockImplementation< + Config["type"], + Config["propSchema"], + Config["content"] + >); + +/** + * ExtractBlockImplementationFromImplementationOrCreator is a helper type that extracts the BlockImplementation type from a BlockImplementationOrCreator. + */ +export type ExtractBlockImplementationFromImplementationOrCreator< + ImplementationOrCreator extends + | BlockImplementation + | (( + ...args: any[] + ) => BlockImplementation), +> = ImplementationOrCreator extends (...args: any[]) => infer Implementation + ? Implementation + : ImplementationOrCreator; + // restrict content to "inline" and "none" only export type CustomBlockImplementation< T extends string = string, diff --git a/packages/react/src/blocks/Audio/block.tsx b/packages/react/src/blocks/Audio/block.tsx index e6eeb07e9e..d7efb9857e 100644 --- a/packages/react/src/blocks/Audio/block.tsx +++ b/packages/react/src/blocks/Audio/block.tsx @@ -13,11 +13,7 @@ import { LinkWithCaption } from "../File/helpers/toExternalHTML/LinkWithCaption. export const AudioPreview = ( props: Omit< - ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + ReactCustomBlockRenderProps, "contentRef" >, ) => { @@ -40,11 +36,7 @@ export const AudioPreview = ( export const AudioToExternalHTML = ( props: Omit< - ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + ReactCustomBlockRenderProps, "contentRef" >, ) => { @@ -76,11 +68,7 @@ export const AudioToExternalHTML = ( }; export const AudioBlock = ( - props: ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + props: ReactCustomBlockRenderProps, ) => { return ( , - "contentRef" - > & { + props: Omit, "contentRef"> & { buttonIcon?: ReactNode; }, ) => { diff --git a/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx b/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx index 0f852a5f49..6499b40ca0 100644 --- a/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx +++ b/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx @@ -1,4 +1,4 @@ -import { FileBlockConfig } from "@blocknote/core"; +import { BlockConfig, FileBlockConfig } from "@blocknote/core"; import { CSSProperties, ReactNode } from "react"; import { useUploadLoading } from "../../../../hooks/useUploadLoading.js"; @@ -9,9 +9,11 @@ import { FileNameWithIcon } from "./FileNameWithIcon.js"; export const FileBlockWrapper = ( props: Omit< ReactCustomBlockRenderProps< - FileBlockConfig["type"], - FileBlockConfig["propSchema"] & { showPreview?: { default: true } }, - FileBlockConfig["content"] + BlockConfig< + FileBlockConfig["type"], + FileBlockConfig["propSchema"] & { showPreview?: { default: true } }, + FileBlockConfig["content"] + > >, "contentRef" > & { diff --git a/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx b/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx index 3951b1f6dd..880b5ced96 100644 --- a/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx +++ b/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx @@ -5,11 +5,7 @@ import { ReactCustomBlockRenderProps } from "../../../../schema/ReactBlockSpec.j export const FileNameWithIcon = ( props: Omit< - ReactCustomBlockRenderProps< - FileBlockConfig["type"], - FileBlockConfig["propSchema"], - FileBlockConfig["content"] - >, + ReactCustomBlockRenderProps, "editor" | "contentRef" >, ) => ( diff --git a/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx b/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx index c1b37eac68..96d361b1a6 100644 --- a/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx +++ b/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx @@ -1,4 +1,4 @@ -import { FileBlockConfig } from "@blocknote/core"; +import { BlockConfig, FileBlockConfig } from "@blocknote/core"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; import { useUploadLoading } from "../../../../hooks/useUploadLoading.js"; @@ -8,13 +8,15 @@ import { FileBlockWrapper } from "./FileBlockWrapper.js"; export const ResizableFileBlockWrapper = ( props: Omit< ReactCustomBlockRenderProps< - FileBlockConfig["type"], - FileBlockConfig["propSchema"] & { - showPreview?: { default: true }; - previewWidth?: { default: number }; - textAlignment?: { default: "left" }; - }, - FileBlockConfig["content"] + BlockConfig< + FileBlockConfig["type"], + FileBlockConfig["propSchema"] & { + showPreview?: { default: true }; + previewWidth?: { default: number }; + textAlignment?: { default: "left" }; + }, + FileBlockConfig["content"] + > >, "contentRef" > & { diff --git a/packages/react/src/blocks/Image/block.tsx b/packages/react/src/blocks/Image/block.tsx index 880208ee87..974adc50b5 100644 --- a/packages/react/src/blocks/Image/block.tsx +++ b/packages/react/src/blocks/Image/block.tsx @@ -12,11 +12,7 @@ import { LinkWithCaption } from "../File/helpers/toExternalHTML/LinkWithCaption. export const ImagePreview = ( props: Omit< - ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + ReactCustomBlockRenderProps, "contentRef" >, ) => { @@ -39,11 +35,7 @@ export const ImagePreview = ( export const ImageToExternalHTML = ( props: Omit< - ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + ReactCustomBlockRenderProps, "contentRef" >, ) => { @@ -81,11 +73,7 @@ export const ImageToExternalHTML = ( }; export const ImageBlock = ( - props: ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + props: ReactCustomBlockRenderProps, ) => { return ( , "contentRef"> & { + props: Omit< + ReactCustomBlockRenderProps>, + "contentRef" + > & { children: ReactNode; toggledState?: { set: (block: Block, isToggled: boolean) => void; diff --git a/packages/react/src/blocks/Video/block.tsx b/packages/react/src/blocks/Video/block.tsx index 74870a1974..56a8b88fd1 100644 --- a/packages/react/src/blocks/Video/block.tsx +++ b/packages/react/src/blocks/Video/block.tsx @@ -12,11 +12,7 @@ import { LinkWithCaption } from "../File/helpers/toExternalHTML/LinkWithCaption. export const VideoPreview = ( props: Omit< - ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + ReactCustomBlockRenderProps, "contentRef" >, ) => { @@ -39,11 +35,7 @@ export const VideoPreview = ( export const VideoToExternalHTML = ( props: Omit< - ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + ReactCustomBlockRenderProps, "contentRef" >, ) => { @@ -75,11 +67,7 @@ export const VideoToExternalHTML = ( }; export const VideoBlock = ( - props: ReactCustomBlockRenderProps< - ReturnType["type"], - ReturnType["propSchema"], - ReturnType["content"] - >, + props: ReactCustomBlockRenderProps, ) => { return ( = ExtractBlockConfigFromConfigOrCreator, > = { - block: BlockNoDefaults< - Record>, - any, - any - >; - editor: BlockNoteEditor< - Record>, - any, - any - >; - contentRef: (node: HTMLElement | null) => void; -}; + block: BlockNoDefaults, any, any>; + editor: BlockNoteEditor, any, any>; +} & (Config["content"] extends "inline" + ? { + contentRef: (node: HTMLElement | null) => void; + } + : object); // extend BlockConfig but use a React render function export type ReactCustomBlockImplementation< - TName extends string = string, - TProps extends PropSchema = PropSchema, - TContent extends "inline" | "none" = "inline" | "none", + B extends BlockConfigOrCreator = BlockConfigOrCreator, + Config extends + ExtractBlockConfigFromConfigOrCreator = ExtractBlockConfigFromConfigOrCreator, > = Omit< - CustomBlockImplementation, + CustomBlockImplementation< + Config["type"], + Config["propSchema"], + Config["content"] + >, "render" | "toExternalHTML" > & { - render: FC>; + render: FC>; toExternalHTML?: FC< - ReactCustomBlockRenderProps & { + ReactCustomBlockRenderProps & { context: { nestingLevel: number; }; @@ -61,12 +62,14 @@ export type ReactCustomBlockImplementation< }; export type ReactCustomBlockSpec< - T extends string = string, - PS extends PropSchema = PropSchema, - C extends "inline" | "none" = "inline" | "none", + B extends BlockConfig = BlockConfig< + string, + PropSchema, + "inline" | "none" + >, > = { - config: BlockConfig; - implementation: ReactCustomBlockImplementation; + config: B; + implementation: ReactCustomBlockImplementation; extensions?: Extension[]; }; @@ -134,12 +137,16 @@ export function createReactBlockSpec< >( blockConfigOrCreator: BlockConfig, blockImplementationOrCreator: - | ReactCustomBlockImplementation + | ReactCustomBlockImplementation> | (TOptions extends undefined - ? () => ReactCustomBlockImplementation + ? () => ReactCustomBlockImplementation< + BlockConfig + > : ( options: Partial, - ) => ReactCustomBlockImplementation), + ) => ReactCustomBlockImplementation< + BlockConfig + >), extensionsOrCreator?: | Extension[] | (TOptions extends undefined @@ -155,24 +162,12 @@ export function createReactBlockSpec< >( blockCreator: (options: Partial) => BlockConf, blockImplementationOrCreator: - | ReactCustomBlockImplementation< - BlockConf["type"], - BlockConf["propSchema"], - BlockConf["content"] - > + | ReactCustomBlockImplementation | (TOptions extends undefined - ? () => ReactCustomBlockImplementation< - BlockConf["type"], - BlockConf["propSchema"], - BlockConf["content"] - > + ? () => ReactCustomBlockImplementation : ( options: Partial, - ) => ReactCustomBlockImplementation< - BlockConf["type"], - BlockConf["propSchema"], - BlockConf["content"] - >), + ) => ReactCustomBlockImplementation), extensionsOrCreator?: | Extension[] | (TOptions extends undefined @@ -191,18 +186,18 @@ export function createReactBlockSpec< const TContent extends "inline" | "none", const TOptions extends Record | undefined = undefined, >( - blockConfigOrCreator: - | BlockConfig - | (TOptions extends undefined - ? () => BlockConfig - : (options: Partial) => BlockConfig), + blockConfigOrCreator: BlockConfigOrCreator, blockImplementationOrCreator: - | ReactCustomBlockImplementation + | ReactCustomBlockImplementation> | (TOptions extends undefined - ? () => ReactCustomBlockImplementation + ? () => ReactCustomBlockImplementation< + BlockConfig + > : ( options: Partial, - ) => ReactCustomBlockImplementation), + ) => ReactCustomBlockImplementation< + BlockConfig + >), extensionsOrCreator?: | Extension[] | (TOptions extends undefined From 6a103e7f54010394299f5dbce21d02be590c8992 Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Wed, 4 Mar 2026 16:11:03 +0100 Subject: [PATCH 2/2] fix: widen the createBlockSpec and createReactBlockSpec types for extensions --- packages/core/src/schema/blocks/createSpec.ts | 24 +++++++++++------- packages/react/src/schema/ReactBlockSpec.tsx | 25 ++++++++++++------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/core/src/schema/blocks/createSpec.ts b/packages/core/src/schema/blocks/createSpec.ts index 606103b351..9b1e9e4bc6 100644 --- a/packages/core/src/schema/blocks/createSpec.ts +++ b/packages/core/src/schema/blocks/createSpec.ts @@ -304,10 +304,12 @@ export function createBlockSpec< TOptions >, extensionsOrCreator?: - | ExtensionFactoryInstance[] + | (ExtensionFactoryInstance | Extension)[] | (TOptions extends undefined - ? () => ExtensionFactoryInstance[] - : (options: Partial) => ExtensionFactoryInstance[]), + ? () => (ExtensionFactoryInstance | Extension)[] + : ( + options: Partial, + ) => (ExtensionFactoryInstance | Extension)[]), ): (options?: Partial) => BlockSpec; export function createBlockSpec< const TName extends string, @@ -322,10 +324,12 @@ export function createBlockSpec< TOptions >, extensionsOrCreator?: - | ExtensionFactoryInstance[] + | (ExtensionFactoryInstance | Extension)[] | (TOptions extends undefined - ? () => ExtensionFactoryInstance[] - : (options: Partial) => ExtensionFactoryInstance[]), + ? () => (ExtensionFactoryInstance | Extension)[] + : ( + options: Partial, + ) => (ExtensionFactoryInstance | Extension)[]), ): ( options?: Partial, ) => BlockSpec< @@ -345,10 +349,12 @@ export function createBlockSpec< TOptions >, extensionsOrCreator?: - | ExtensionFactoryInstance[] + | (ExtensionFactoryInstance | Extension)[] | (TOptions extends undefined - ? () => ExtensionFactoryInstance[] - : (options: Partial) => ExtensionFactoryInstance[]), + ? () => (ExtensionFactoryInstance | Extension)[] + : ( + options: Partial, + ) => (ExtensionFactoryInstance | Extension)[]), ): (options?: Partial) => BlockSpec { return (options = {} as TOptions) => { const blockConfig = diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index 1aeee7b190..952f0a06ed 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -8,6 +8,7 @@ import { camelToDataKebab, CustomBlockImplementation, Extension, + ExtensionFactoryInstance, ExtractBlockConfigFromConfigOrCreator, getBlockFromPos, mergeCSSClasses, @@ -148,10 +149,12 @@ export function createReactBlockSpec< BlockConfig >), extensionsOrCreator?: - | Extension[] + | (ExtensionFactoryInstance | Extension)[] | (TOptions extends undefined - ? () => Extension[] - : (options: Partial) => Extension[]), + ? () => (ExtensionFactoryInstance | Extension)[] + : ( + options: Partial, + ) => (ExtensionFactoryInstance | Extension)[]), ): (options?: Partial) => BlockSpec; export function createReactBlockSpec< const TName extends string, @@ -169,10 +172,12 @@ export function createReactBlockSpec< options: Partial, ) => ReactCustomBlockImplementation), extensionsOrCreator?: - | Extension[] + | (ExtensionFactoryInstance | Extension)[] | (TOptions extends undefined - ? () => Extension[] - : (options: Partial) => Extension[]), + ? () => (ExtensionFactoryInstance | Extension)[] + : ( + options: Partial, + ) => (ExtensionFactoryInstance | Extension)[]), ): ( options?: Partial, ) => BlockSpec< @@ -199,10 +204,12 @@ export function createReactBlockSpec< BlockConfig >), extensionsOrCreator?: - | Extension[] + | (ExtensionFactoryInstance | Extension)[] | (TOptions extends undefined - ? () => Extension[] - : (options: Partial) => Extension[]), + ? () => (ExtensionFactoryInstance | Extension)[] + : ( + options: Partial, + ) => (ExtensionFactoryInstance | Extension)[]), ): (options?: Partial) => BlockSpec { return (options = {} as TOptions) => { const blockConfig =