Skip to content

RFC: paragraph-like editor configuration #19921

@niegowski

Description

@niegowski

Related RFCs

This RFC builds on top of:

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

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

  1. modelRootElementName: '$inlineRoot' - explicit and descriptive
  2. modelElement: '$inlineRoot' - shorter version
  3. schemaRoot: '$inlineRoot' - references schema concept
  4. schemaRoot: 'container' | 'inline' - abstracted values instead of internal names
  5. contentType: 'block' | 'inline' - describes content type allowed
  6. rootType: 'block' | 'inline' - describes root type
  7. allowBlocks: false - boolean flag approach
  8. inlineOnly: 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:featureThis issue reports a feature request (an idea for a new functionality or a missing option).

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions