Overview
Yoopta Editor is built with TypeScript and provides comprehensive type definitions for all APIs, plugins, and custom extensions. This guide covers type-safe usage patterns and advanced TypeScript features.Installation
All Yoopta packages include TypeScript definitions out of the box:npm install @yoopta/editor @yoopta/paragraph
@types packages are needed.
Core Types
Editor Instance
The main editor type isYooEditor:
import type { YooEditor } from '@yoopta/editor';
import { createYooptaEditor } from '@yoopta/editor';
const editor: YooEditor = createYooptaEditor({
plugins: PLUGINS,
marks: MARKS,
});
type YooEditor = {
// Identification
id: string;
readOnly: boolean;
// Content
children: YooptaContentValue;
getEditorValue: () => YooptaContentValue;
setEditorValue: (value: YooptaContentValue) => void;
isEmpty: () => boolean;
// Block operations
insertBlock: (options: InsertBlockOptions) => string | undefined;
updateBlock: (blockId: string, options: UpdateBlockOptions) => void;
deleteBlock: (blockId: string) => void;
duplicateBlock: (blockId: string) => void;
toggleBlock: (blockId: string, options: ToggleBlockOptions) => void;
moveBlock: (options: MoveBlockOptions) => void;
focusBlock: (blockId: string, options?: FocusBlockOptions) => void;
getBlock: (options: GetBlockOptions) => YooptaBlockData | null;
// Element operations
insertElement: (blockId: string, options: InsertElementOptions) => void;
updateElement: (blockId: string, options: UpdateElementOptions) => void;
deleteElement: (blockId: string, options: DeleteElementOptions) => void;
getElement: (blockId: string, options: GetElementOptions) => SlateElement | null;
// History
undo: (options?: UndoRedoOptions) => void;
redo: (options?: UndoRedoOptions) => void;
// Events
on: <K extends keyof YooptaEventsMap>(
event: K,
fn: (payload: YooptaEventsMap[K]) => void
) => void;
emit: <K extends keyof YooptaEventsMap>(
event: K,
payload: YooptaEventsMap[K]
) => void;
// ... and more
};
Content Types
YooptaContentValue - The editor’s content structure:type YooptaContentValue = Record<string, YooptaBlockData>;
type YooptaBlockData<T = Descendant | SlateElement> = {
id: string;
value: T[];
type: string; // PascalCase: "Paragraph", "HeadingOne"
meta: YooptaBlockBaseMeta;
};
type YooptaBlockBaseMeta = {
order: number;
depth: number;
align?: 'left' | 'center' | 'right';
};
type SlateElement<K extends string = string, T = any> = {
id: string;
type: K; // kebab-case: "paragraph", "heading-one"
children: Descendant[];
props?: PluginElementProps<T>;
};
type SlateElementTextNode = {
text: string;
bold?: boolean;
italic?: boolean;
underline?: boolean;
code?: boolean;
strike?: boolean;
highlight?: any;
};
Plugin Types
Plugin - Type-safe plugin definition:import type { Plugin, PluginElement, SlateElement } from '@yoopta/editor';
// Define your element types
type ParagraphElement = SlateElement<'paragraph', { nodeType: 'block' }>;
type ElementMap = {
paragraph: ParagraphElement;
};
const Paragraph: Plugin<ElementMap> = {
type: 'Paragraph',
elements: {
paragraph: {
render: (props) => <p {...props.attributes}>{props.children}</p>,
props: { nodeType: 'block' },
},
},
options: {
display: {
title: 'Paragraph',
description: 'Basic text block',
},
},
};
type PluginElementRenderProps = {
element: SlateElement;
attributes: Record<string, any>;
children: React.ReactNode;
blockId: string;
HTMLAttributes?: HTMLAttributes<HTMLElement>;
};
Type-Safe Plugin Development
Creating a Custom Plugin
import type { Plugin, SlateElement, YooEditor } from '@yoopta/editor';
import type { RenderElementProps } from 'slate-react';
// 1. Define element types
type CalloutType = 'info' | 'warning' | 'error' | 'success';
type CalloutElement = SlateElement<
'callout',
{
nodeType: 'block';
theme: CalloutType;
}
>;
type ElementMap = {
callout: CalloutElement;
};
// 2. Create typed plugin
const Callout: Plugin<ElementMap, { defaultTheme?: CalloutType }> = {
type: 'Callout',
elements: {
callout: {
render: (props) => {
const { element, attributes, children } = props;
const theme = element.props?.theme || 'info';
return (
<div {...attributes} data-theme={theme}>
{children}
</div>
);
},
props: {
nodeType: 'block',
theme: 'info',
},
},
},
options: {
defaultTheme: 'info',
},
};
export default Callout;
Type-Safe Commands
Add custom commands with full type safety:type CalloutCommands = {
setTheme: (editor: YooEditor, blockId: string, theme: CalloutType) => void;
getTheme: (editor: YooEditor, blockId: string) => CalloutType | null;
};
const Callout: Plugin<ElementMap, {}, CalloutCommands> = {
type: 'Callout',
elements: { /* ... */ },
commands: {
setTheme: (editor, blockId, theme) => {
editor.updateElement(blockId, {
type: 'callout',
props: { theme },
});
},
getTheme: (editor, blockId) => {
const element = editor.getElement(blockId, { type: 'callout' });
return element?.props?.theme || null;
},
},
};
// Usage
const theme = Callout.commands.getTheme(editor, blockId);
Callout.commands.setTheme(editor, blockId, 'warning');
Type-Safe Event Handling
Events Map
type YooptaEventsMap = {
change: YooptaEventChangePayload;
focus: boolean;
blur: boolean;
'block:copy': YooptaBlockData;
'path-change': YooptaPath;
'decorations:change': undefined;
};
type YooptaEventChangePayload = {
operations: YooptaOperation[];
value: YooptaContentValue;
};
Type-Safe Event Listeners
import type { YooptaEventsMap } from '@yoopta/editor';
// Type-safe change handler
editor.on('change', (payload) => {
// payload is automatically typed as YooptaEventChangePayload
const { value, operations } = payload;
console.log('Content changed:', value);
console.log('Operations:', operations);
});
// Type-safe path change handler
editor.on('path-change', (path) => {
// path is automatically typed as YooptaPath
console.log('Current block:', path.current);
console.log('Selected blocks:', path.selected);
});
Namespace API Types
Blocks Namespace
import { Blocks } from '@yoopta/editor';
import type { GetBlockOptions } from '@yoopta/editor';
// Get block with type-safe options
const options: GetBlockOptions = {
blockId: 'block-id',
// or
at: [0],
// or
focus: true,
};
const block = Blocks.getBlock(editor, options);
if (block) {
// block is typed as YooptaBlockData
console.log(block.type, block.meta.order);
}
Elements Namespace
import { Elements } from '@yoopta/editor';
import type { UpdateElementOptions } from '@yoopta/editor';
// Update element with type-safe options
const options: UpdateElementOptions = {
type: 'paragraph',
props: {
theme: 'accent',
},
children: [
{ text: 'Updated content', bold: true },
],
};
Elements.updateElement(editor, blockId, options);
Marks Namespace
import { Marks } from '@yoopta/editor';
// Type-safe mark updates
Marks.update(editor, {
type: 'highlight',
value: {
color: '#ff0000',
backgroundColor: '#ffff00',
},
at: [0, 1, 2], // block indices
});
Generics and Type Parameters
Custom Element Props
type CustomProps = {
alignment: 'left' | 'center' | 'right';
color: string;
};
type CustomElement = SlateElement<'custom', CustomProps>;
type ElementMap = {
custom: CustomElement;
};
const CustomPlugin: Plugin<ElementMap> = {
type: 'Custom',
elements: {
custom: {
render: (props) => {
// props.element.props is typed as CustomProps
const { alignment, color } = props.element.props || {};
return (
<div style={{ textAlign: alignment, color }}>
{props.children}
</div>
);
},
props: {
nodeType: 'block',
alignment: 'left',
color: '#000000',
},
},
},
};
Plugin Options Type
type MyPluginOptions = {
maxLength?: number;
allowedFormats?: string[];
onValidate?: (value: string) => boolean;
};
const MyPlugin: Plugin<ElementMap, MyPluginOptions> = {
type: 'MyPlugin',
elements: { /* ... */ },
options: {
display: {
title: 'My Plugin',
},
maxLength: 500,
allowedFormats: ['bold', 'italic'],
onValidate: (value) => value.length <= 500,
},
};
Utility Types
WithoutFirstArg
Used to omit the editor parameter from methods:import type { WithoutFirstArg } from '@yoopta/editor';
// Original function
function insertBlock(editor: YooEditor, options: InsertBlockOptions): string {
// ...
}
// Type without first argument
type InsertBlockMethod = WithoutFirstArg<typeof insertBlock>;
// Equivalent to: (options: InsertBlockOptions) => string
Type Guards
Element Type Checking
import type { SlateElement } from '@yoopta/editor';
function isParagraph(element: SlateElement): element is SlateElement<'paragraph'> {
return element.type === 'paragraph';
}
function isHeading(element: SlateElement): element is SlateElement<'heading-one' | 'heading-two' | 'heading-three'> {
return ['heading-one', 'heading-two', 'heading-three'].includes(element.type);
}
// Usage
const element = editor.getElement(blockId, { at: [0] });
if (element && isParagraph(element)) {
// element is typed as SlateElement<'paragraph'>
console.log('This is a paragraph');
}
Node Type Checking
import { Element, Text } from 'slate';
import type { Descendant } from 'slate';
function processNode(node: Descendant) {
if (Text.isText(node)) {
// node is typed as Text
console.log('Text node:', node.text);
} else if (Element.isElement(node)) {
// node is typed as Element
console.log('Element node:', node.type);
}
}
React Component Types
Typed YooptaEditor Component
import type { YooEditor } from '@yoopta/editor';
import { YooptaEditor } from '@yoopta/editor';
import type { CSSProperties } from 'react';
type EditorProps = {
initialValue?: YooptaContentValue;
onChange?: (value: YooptaContentValue) => void;
className?: string;
style?: CSSProperties;
};
function Editor({ initialValue, onChange, className, style }: EditorProps) {
const editor: YooEditor = useMemo(
() => createYooptaEditor({
plugins: PLUGINS,
marks: MARKS,
value: initialValue,
}),
[]
);
return (
<YooptaEditor
editor={editor}
onChange={onChange}
className={className}
style={style}
>
{/* ... */}
</YooptaEditor>
);
}
Best Practices
Use strict TypeScript config
Use strict TypeScript config
Enable strict mode in your
tsconfig.json:{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
Import types explicitly
Import types explicitly
Use
import type for type-only imports:import type { YooEditor, Plugin } from '@yoopta/editor';
import { createYooptaEditor } from '@yoopta/editor';
Define element types upfront
Define element types upfront
Create a dedicated types file for your custom plugins:
// types.ts
export type MyElementMap = {
custom: CustomElement;
another: AnotherElement;
};
Leverage type inference
Leverage type inference
Let TypeScript infer types when possible:
// Type is inferred from function
editor.on('change', (payload) => {
// payload type is automatically inferred
});
Use type guards for runtime checks
Use type guards for runtime checks
Always check types at runtime when dealing with dynamic content:
const block = editor.getBlock({ blockId });
if (block && block.type === 'Paragraph') {
// Safe to access Paragraph-specific properties
}
Common Patterns
Extending Plugin Types
import Paragraph from '@yoopta/paragraph';
import type { Plugin } from '@yoopta/editor';
type ExtendedParagraphProps = {
indent: number;
spacing: 'tight' | 'normal' | 'loose';
};
const ExtendedParagraph = Paragraph.extend({
elements: {
paragraph: {
props: {
nodeType: 'block',
indent: 0,
spacing: 'normal',
} as ExtendedParagraphProps & { nodeType: 'block' },
},
},
});
Type-Safe Serialization
import type { YooptaContentValue } from '@yoopta/editor';
function serializeToJSON(value: YooptaContentValue): string {
return JSON.stringify(value);
}
function deserializeFromJSON(json: string): YooptaContentValue {
const parsed = JSON.parse(json);
// Add runtime validation here
return parsed as YooptaContentValue;
}
Related
Custom Plugins
Create type-safe custom plugins
API Reference
Complete API type definitions