or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cloud-integration.mdcontext-management.mdindex.mdmulti-root-editor.mdsingle-root-editor.md
tile.json

multi-root-editor.mddocs/

Multi-Root Editor

The useMultiRootEditor hook provides advanced functionality for managing multi-root CKEditor 5 instances where content is split across multiple editable areas with independent data management and dynamic root manipulation.

Capabilities

useMultiRootEditor Hook

React hook for managing multi-root editor instances with comprehensive state management and lifecycle control.

/**
 * Hook for managing multi-root editor instances
 * Provides state management for multiple editable areas
 */
function useMultiRootEditor(props: MultiRootHookProps): MultiRootHookReturns;

interface MultiRootHookProps {
  /** Component identifier for React reconciliation */
  id?: any;
  
  /** DOM element for semaphore lifecycle management */
  semaphoreElement?: HTMLElement;
  
  /** Flag indicating if layout is ready for editor initialization */
  isLayoutReady?: boolean;
  
  /** Read-only mode toggle */
  disabled?: boolean;
  
  /** Root content data - object with root names as keys and content as values */
  data: Record<string, string>;
  
  /** Root attributes configuration for each root */
  rootsAttributes?: Record<string, Record<string, unknown>>;
  
  /** MultiRootEditor constructor */
  editor: typeof MultiRootEditor;
  
  /** Editor configuration object */
  config?: Record<string, unknown>;
  
  /** Watchdog configuration for error recovery */
  watchdogConfig?: WatchdogConfig;
  
  /** Disable watchdog functionality */
  disableWatchdog?: boolean;
  
  /** Disable automatic two-way data binding */
  disableTwoWayDataBinding?: boolean;
  
  /** Callback fired when editor is ready */
  onReady?: (editor: MultiRootEditor) => void;
  
  /** Callback fired after editor is destroyed */
  onAfterDestroy?: (editor: MultiRootEditor) => void;
  
  /** Error handling callback */
  onError?: (error: Error, details: ErrorDetails) => void;
  
  /** Content change callback */
  onChange?: (event: EventInfo, editor: MultiRootEditor) => void;
  
  /** Focus event callback */
  onFocus?: (event: EventInfo, editor: MultiRootEditor) => void;
  
  /** Blur event callback */
  onBlur?: (event: EventInfo, editor: MultiRootEditor) => void;
}

interface MultiRootHookReturns {
  /** Current editor instance */
  editor: MultiRootEditor | null;
  
  /** Array of React components for editable areas */
  editableElements: Array<JSX.Element>;
  
  /** React component for the editor toolbar */
  toolbarElement: JSX.Element;
  
  /** Current data state for all roots */
  data: Record<string, string>;
  
  /** Function to update data state */
  setData: Dispatch<SetStateAction<Record<string, string>>>;
  
  /** Current attributes state for all roots */
  attributes: Record<string, Record<string, unknown>>;
  
  /** Function to update attributes state */
  setAttributes: Dispatch<SetStateAction<Record<string, Record<string, unknown>>>>;
}

Usage Examples:

import React, { useState } from 'react';
import { useMultiRootEditor } from '@ckeditor/ckeditor5-react';
import { MultiRootEditor } from 'ckeditor5';

// Basic multi-root editor
function BasicMultiRootEditor() {
  const initialData = {
    intro: '<h2>Introduction</h2><p>Welcome to our article.</p>',
    content: '<p>Main content goes here.</p>',
    outro: '<p>Thank you for reading!</p>'
  };

  const { 
    editor, 
    editableElements, 
    toolbarElement, 
    data, 
    setData 
  } = useMultiRootEditor({
    editor: MultiRootEditor,
    data: initialData,
    onReady: (editor) => {
      console.log('Multi-root editor ready:', editor);
    },
    onChange: (event, editor) => {
      console.log('Content changed in multi-root editor');
    }
  });

  return (
    <div>
      <div>
        <h3>Toolbar</h3>
        {toolbarElement}
      </div>
      
      <div>
        <h3>Editable Areas</h3>
        {editableElements}
      </div>
      
      <div>
        <h3>Current Data</h3>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    </div>
  );
}

// Advanced multi-root with attributes and dynamic roots
function AdvancedMultiRootEditor() {
  const [rootData, setRootData] = useState({
    header: '<h1>Article Title</h1>',
    sidebar: '<p>Sidebar content</p>',
    main: '<p>Main article content</p>'
  });

  const [rootAttributes, setRootAttributes] = useState({
    header: { 
      class: 'header-section',
      'data-priority': 'high' 
    },
    sidebar: { 
      class: 'sidebar-section',
      'data-priority': 'medium' 
    },
    main: { 
      class: 'main-section',
      'data-priority': 'high' 
    }
  });

  const { 
    editor, 
    editableElements, 
    toolbarElement,
    data,
    setData,
    attributes,
    setAttributes
  } = useMultiRootEditor({
    editor: MultiRootEditor,
    data: rootData,
    rootsAttributes: rootAttributes,
    config: {
      toolbar: ['bold', 'italic', 'link', 'bulletedList'],
      placeholder: {
        header: 'Enter title...',
        sidebar: 'Sidebar content...',
        main: 'Main content...'
      }
    },
    onReady: (editor) => {
      console.log('Advanced multi-root editor ready');
      console.log('Available roots:', Object.keys(editor.getRootsAttributes()));
    },
    onChange: (event, editor) => {
      const changedRoots = event.source.differ.getChangedRoots();
      console.log('Changed roots:', changedRoots.map(r => r.name));
    }
  });

  const addNewRoot = () => {
    const newRootName = `section_${Date.now()}`;
    setData(prev => ({
      ...prev,
      [newRootName]: '<p>New section content</p>'
    }));
    setAttributes(prev => ({
      ...prev,
      [newRootName]: { class: 'dynamic-section' }
    }));
  };

  const removeRoot = (rootName: string) => {
    setData(prev => {
      const { [rootName]: removed, ...rest } = prev;
      return rest;
    });
    setAttributes(prev => {
      const { [rootName]: removed, ...rest } = prev;
      return rest;
    });
  };

  return (
    <div>
      <div>
        {toolbarElement}
      </div>
      
      <button onClick={addNewRoot}>Add New Root</button>
      
      <div style={{ display: 'grid', gap: '10px', marginTop: '20px' }}>
        {editableElements.map((element, index) => (
          <div key={element.key} style={{ border: '1px solid #ccc', padding: '10px' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
              <strong>Root: {element.props.rootName}</strong>
              <button onClick={() => removeRoot(element.props.rootName)}>
                Remove
              </button>
            </div>
            {element}
          </div>
        ))}
      </div>
    </div>
  );
}

EditorEditable Component

React component for individual editable areas in multi-root editor setup. This component is automatically managed by the useMultiRootEditor hook and is not intended for direct use.

Note: These components are exported with the useMultiRootEditor function and are available as EditorEditable and EditorToolbarWrapper when importing from the main package index. They are primarily for internal use by the hook but can be imported directly if needed:

import { useMultiRootEditor, EditorEditable, EditorToolbarWrapper } from "@ckeditor/ckeditor5-react";
/**
 * Individual editable area component for multi-root editor
 * Automatically managed by useMultiRootEditor hook
 */
const EditorEditable: React.ForwardRefExoticComponent<{
  /** DOM element ID */
  id: string;
  
  /** Root name identifier */
  rootName: string;
  
  /** Lifecycle semaphore for editor management */
  semaphore: LifeCycleSemaphoreSyncRefResult<LifeCycleMountResult>;
} & React.RefAttributes<unknown>>;

EditorToolbarWrapper Component

React component wrapper for the multi-root editor toolbar. This component is automatically managed by the useMultiRootEditor hook and is not intended for direct use.

/**
 * Wrapper component for editor toolbar
 * Automatically managed by useMultiRootEditor hook
 */
const EditorToolbarWrapper: React.ForwardRefExoticComponent<{
  /** Current editor instance */
  editor: MultiRootEditor | null;
} & React.RefAttributes<unknown>>;

Error Handling Types

interface ErrorDetails {
  /** Phase when error occurred */
  phase: 'initialization' | 'runtime';
  
  /** Whether the error will cause editor restart */
  willEditorRestart?: boolean;
}

Advanced Multi-Root Features

Dynamic Root Management:

import React, { useState, useCallback } from 'react';
import { useMultiRootEditor } from '@ckeditor/ckeditor5-react';
import { MultiRootEditor } from 'ckeditor5';

function DynamicRootManagement() {
  const [data, setData] = useState({
    root1: '<p>First root</p>'
  });

  const [attributes, setAttributes] = useState({
    root1: { class: 'first-root' }
  });

  const { editor, editableElements, toolbarElement } = useMultiRootEditor({
    editor: MultiRootEditor,
    data,
    rootsAttributes: attributes,
    onReady: (editor) => {
      // Listen for root addition/removal events
      editor.on('addRoot', (evt, root) => {
        console.log('Root added:', root.rootName);
      });
      
      editor.on('detachRoot', (evt, root) => {
        console.log('Root removed:', root.rootName);
      });
    }
  });

  const addRoot = useCallback(() => {
    const newRootName = `root_${Date.now()}`;
    setData(prev => ({
      ...prev,
      [newRootName]: '<p>New root content</p>'
    }));
    setAttributes(prev => ({
      ...prev,
      [newRootName]: { class: 'dynamic-root' }
    }));
  }, []);

  const removeLastRoot = useCallback(() => {
    const rootNames = Object.keys(data);
    if (rootNames.length > 1) {
      const lastRoot = rootNames[rootNames.length - 1];
      setData(prev => {
        const { [lastRoot]: removed, ...rest } = prev;
        return rest;
      });
      setAttributes(prev => {
        const { [lastRoot]: removed, ...rest } = prev;
        return rest;
      });
    }
  }, [data]);

  return (
    <div>
      {toolbarElement}
      
      <div style={{ margin: '10px 0' }}>
        <button onClick={addRoot}>Add Root</button>
        <button onClick={removeLastRoot}>Remove Last Root</button>
      </div>
      
      <div>
        {editableElements.map((element) => (
          <div key={element.key} style={{ margin: '10px 0', padding: '10px', border: '1px solid #ddd' }}>
            <h4>Root: {element.props.rootName}</h4>
            {element}
          </div>
        ))}
      </div>
    </div>
  );
}

Two-Way Data Binding Control:

import React, { useState } from 'react';
import { useMultiRootEditor } from '@ckeditor/ckeditor5-react';
import { MultiRootEditor } from 'ckeditor5';

function ControlledDataBinding() {
  const [externalData, setExternalData] = useState({
    content: '<p>Controlled content</p>'
  });

  const { 
    editor, 
    editableElements, 
    toolbarElement,
    data,
    setData 
  } = useMultiRootEditor({
    editor: MultiRootEditor,
    data: externalData,
    disableTwoWayDataBinding: true, // Disable automatic sync
    onChange: (event, editor) => {
      // Manual data synchronization
      const editorData = editor.getFullData();
      console.log('Manual data sync:', editorData);
      setExternalData(editorData);
    }
  });

  const updateExternalData = () => {
    const newData = {
      content: `<p>Updated at ${new Date().toLocaleTimeString()}</p>`
    };
    setExternalData(newData);
    setData(newData); // Manually sync to editor
  };

  return (
    <div>
      {toolbarElement}
      
      <button onClick={updateExternalData}>
        Update External Data
      </button>
      
      <div>
        <h4>External Data:</h4>
        <pre>{JSON.stringify(externalData, null, 2)}</pre>
      </div>
      
      <div>
        <h4>Editor Data:</h4>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
      
      {editableElements}
    </div>
  );
}

Integration with Context:

import React from 'react';
import { CKEditorContext, useMultiRootEditor } from '@ckeditor/ckeditor5-react';
import { Context, MultiRootEditor } from 'ckeditor5';

function MultiRootWithContext() {
  return (
    <CKEditorContext
      context={Context}
      contextWatchdog={Context.ContextWatchdog}
      config={{
        plugins: ['Essentials', 'Bold', 'Italic', 'Link']
      }}
    >
      <MultiRootEditorComponent />
    </CKEditorContext>
  );
}

function MultiRootEditorComponent() {
  const { editor, editableElements, toolbarElement } = useMultiRootEditor({
    editor: MultiRootEditor,
    data: {
      header: '<h1>Shared Context Header</h1>',
      body: '<p>Content using shared context resources</p>'
    },
    onReady: (editor) => {
      console.log('Multi-root editor ready within context');
    }
  });

  return (
    <div>
      {toolbarElement}
      {editableElements}
    </div>
  );
}