-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
RFC: paragraph-like editor configuration #19921
Description
Related RFCs
This RFC builds on top of:
- Roots Configuration Aggregation RFC - proposes aggregating root-related config options under a single
root/rootskey - Passing DOM Element in Config RFC - proposes allowing DOM elements to be passed inside the configuration object
Context
Currently all editor types hardcode assumption that root elements are container-like - they accept multiple blocks or container elements. On the model side there is only one $root element registered in the Schema. On the view side, editor uses provided DOM element (ignoring whether it is <div> or any other element) or creates a new <div> for every root.
To implement paragraph-like or span-like editor we should use a separate root element name (not root name but root element name - those are separate and only root element name is schema related). Using a separate root element name registered in schema, integrator could use multi-root editor with mixed container-like and paragraph-like roots in a single editor without handcrafted checks or workarounds. The schema is verified correctly in cases of paragraph-like elements for example in the image and table captions so we know this works correctly as long as element allows only inline content and is marked as a limit element.
PoC
- Initial PoC of inline roots in editor. #19467
- https://github.com/ckeditor/ckeditor5-commercial/pull/8982
- Add paragraph like editing support to multiroot hook ckeditor5-react#651
Decision Drivers
- API should be easy to explain.
- API should be backward compatible.
- API should be consistent with other APIs.
- API should not require massive changes in features using it.
Problem Statement
This RFC focuses on how to specify the model root element type in the editor configuration. This determines whether the root accepts:
- Block content (
$root) - multiple paragraphs, headings, lists, tables, etc. - Inline content (
$inlineRoot) - only text and inline elements (bold, italic, links, etc.)
Config Option Naming for Model Root Element
Option name candidates
modelRootElementName: '$inlineRoot'- explicit and descriptivemodelElement: '$inlineRoot'- shorter versionschemaRoot: '$inlineRoot'- references schema conceptschemaRoot: 'container' | 'inline'- abstracted values instead of internal namescontentType: 'block' | 'inline'- describes content type allowedrootType: 'block' | 'inline'- describes root typeallowBlocks: false- boolean flag approachinlineOnly: true- boolean flag for inline-only content
Considerations
Using internal schema names ($root, $inlineRoot):
- Pros: Direct mapping to schema, flexible for custom root elements
- Cons: Exposes internal naming, requires knowledge of schema concepts
Using abstracted values ('block' | 'inline'):
- Pros: Easier to understand, hides implementation details
- Cons: Less flexible, may need extension for custom root types
Using boolean flags:
- Pros: Simple API for common use case
- Cons: Limited to two options, doesn't scale
Examples
Assuming the roots config aggregation and DOM element in config RFCs are accepted, here are examples of paragraph-like editor configuration:
Single-root editor (Balloon, Inline, Decoupled)
// Paragraph-like editor using existing DOM element
BalloonEditor.create( {
root: {
element: document.querySelector( '#title' ),
modelElement: '$inlineRoot',
initialData: 'Document title',
placeholder: 'Enter title...'
}
} )
// Paragraph-like editor with element tag name
BalloonEditor.create( {
root: {
element: 'h1',
modelElement: '$inlineRoot',
initialData: 'Document title',
placeholder: 'Enter title...'
}
} )Classic Editor
// Classic editor with paragraph-like editable root
ClassicEditor.create( {
attachTo: document.querySelector( '#editor-placeholder' ),
root: {
element: 'h1',
modelElement: '$inlineRoot',
initialData: 'Document title',
placeholder: 'Enter title...'
}
} )Multi-root editor with mixed root types
// Multi-root editor with paragraph-like title and block-based content
MultiRootEditor.create( {
roots: {
title: {
element: document.querySelector( '#editor-title' ),
modelElement: '$inlineRoot',
initialData: 'Document title',
placeholder: 'Title'
},
content: {
element: document.querySelector( '#editor-content' ),
modelElement: '$root', // Optional, this is the default
initialData: '<p>Editor content</p>',
placeholder: 'Type the content here!'
}
}
} )Dynamic root creation
const editor = await MultiRootEditor.create( {
roots: {
content: {
element: document.querySelector( '#editor-content' ),
initialData: '<p>Initial content</p>'
}
}
} );
// Add paragraph-like root dynamically
editor.addRoot( 'title', {
element: {
name: 'h1',
classes: [ 'document-title' ]
},
modelElement: '$inlineRoot',
initialData: 'Document title',
placeholder: 'Enter title...'
} );Decision
To be completed after discussion.
Consequences
To be completed after decision is made.