@ckeditor/ckeditor5-editor-multi-root 48.0.1-alpha.1 → 48.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,88 +2,9 @@
2
2
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
- import { type RootAttributes } from './multirooteditor.js';
5
+ import '@ckeditor/ckeditor5-core';
6
6
  declare module '@ckeditor/ckeditor5-core' {
7
7
  interface RootConfig {
8
- /**
9
- * Initial root attributes for a root.
10
- *
11
- * **Note: This configuration option is supported only by the
12
- * {@link module:editor-multi-root/multirooteditor~MultiRootEditor multi-root} editor type.**
13
- *
14
- * **Note: You must provide full set of attributes for each root. If an attribute is not set on a root, set the value to `null`.
15
- * Only provided attribute keys will be returned by
16
- * {@link module:editor-multi-root/multirooteditor~MultiRootEditor#getRootsAttributes}.**
17
- *
18
- * Roots attributes hold additional data related to the document roots, in addition to the regular document data (which usually is
19
- * HTML). In roots attributes, for each root, you can store arbitrary key-value pairs with attributes connected with that root.
20
- * Use it to store any custom data that is specific to your integration or custom features.
21
- *
22
- * Currently, any official plugins do not use root attributes. This is a mechanism that is prepared for custom features
23
- * and non-standard integrations. If you do not provide any custom feature that would use root attributes, you do not need to
24
- * handle (save and load) this property.
25
- *
26
- * ```ts
27
- * MultiRootEditor.create(
28
- * // Roots for the editor:
29
- * {
30
- * uid1: document.querySelector( '#uid1' ),
31
- * uid2: document.querySelector( '#uid2' ),
32
- * uid3: document.querySelector( '#uid3' ),
33
- * uid4: document.querySelector( '#uid4' )
34
- * },
35
- * // Config:
36
- * {
37
- * roots: {
38
- * uid1: {
39
- * modelAttributes: { order: 20, isLocked: false } // Third, unlocked.
40
- * },
41
- * uid2: {
42
- * modelAttributes: { order: 10, isLocked: true } // Second, locked.
43
- * },
44
- * uid3: {
45
- * modelAttributes: { order: 30, isLocked: true } // Fourth, locked.
46
- * },
47
- * uid4: {
48
- * modelAttributes: { order: 0, isLocked: false } // First, unlocked.
49
- * }
50
- * }
51
- * }
52
- * )
53
- * .then( ... )
54
- * .catch( ... );
55
- * ```
56
- *
57
- * Note, that the above code snippet is only an example. You need to implement your own features that will use these attributes.
58
- *
59
- * Roots attributes can be changed the same way as attributes set on other model nodes:
60
- *
61
- * ```ts
62
- * editor.model.change( writer => {
63
- * const root = editor.model.getRoot( 'uid3' );
64
- *
65
- * writer.setAttribute( 'order', 40, root );
66
- * } );
67
- * ```
68
- *
69
- * You can react to root attributes changes by listening to
70
- * {@link module:engine/model/document~ModelDocument#event:change:data document `change:data` event}:
71
- *
72
- * ```ts
73
- * editor.model.document.on( 'change:data', () => {
74
- * const changedRoots = editor.model.document.differ.getChangedRoots();
75
- *
76
- * for ( const change of changedRoots ) {
77
- * if ( change.attributes ) {
78
- * const root = editor.model.getRoot( change.name );
79
- *
80
- * // ...
81
- * }
82
- * }
83
- * } );
84
- * ```
85
- */
86
- modelAttributes?: RootAttributes;
87
8
  /**
88
9
  * Flag for the root that exist in the document but is not initially loaded by the editor.
89
10
  *
package/dist/index.d.ts CHANGED
@@ -8,5 +8,6 @@
8
8
  export { MultiRootEditor } from './multirooteditor.js';
9
9
  export { MultiRootEditorUI } from './multirooteditorui.js';
10
10
  export { MultiRootEditorUIView } from './multirooteditoruiview.js';
11
- export type { RootAttributes, AddRootEvent, DetachRootEvent, LoadRootEvent, AddRootOptions, LoadRootOptions, AddRootRootConfig, RootEditableOptions } from './multirooteditor.js';
11
+ export type { AddRootEvent, DetachRootEvent, LoadRootEvent, AddRootOptions, LoadRootOptions, AddRootRootConfig, RootEditableOptions } from './multirooteditor.js';
12
+ export type { EditorRootAttributes as RootAttributes } from '@ckeditor/ckeditor5-core';
12
13
  import './augmentation.js';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
- import { Editor, normalizeMultiRootEditorConstructorParams, normalizeRootsConfig, secureSourceElement } from '@ckeditor/ckeditor5-core/dist/index.js';
5
+ import { Editor, normalizeMultiRootEditorConstructorParams, normalizeRootsConfig, secureSourceElement, registerAndInitializeRootConfigAttributes, verifyRootElements } from '@ckeditor/ckeditor5-core/dist/index.js';
6
6
  import { CKEditorError, logWarning, decodeLicenseKey, isFeatureBlockedByLicenseKey, setDataInElement } from '@ckeditor/ckeditor5-utils/dist/index.js';
7
7
  import { EditorUI, EditorUIView, ToolbarView, MenuBarView, InlineEditableUIView } from '@ckeditor/ckeditor5-ui/dist/index.js';
8
8
  import { enableViewPlaceholder } from '@ckeditor/ckeditor5-engine/dist/index.js';
@@ -310,11 +310,6 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
310
310
  /**
311
311
  * The elements on which the editor has been initialized.
312
312
  */ sourceElements;
313
- /**
314
- * Holds attributes keys that were passed in
315
- * {@link module:core/editor/editorconfig~EditorConfig#roots `config.roots.<rootName>.modelAttributes`}
316
- * and should be returned by {@link #getRootsAttributes}.
317
- */ _registeredRootsAttributesKeys = new Set();
318
313
  /**
319
314
  * A set of lock IDs for enabling or disabling particular root.
320
315
  */ _readOnlyRootLocks = new Map();
@@ -366,17 +361,12 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
366
361
  });
367
362
  for (const [rootName, rootConfig] of rootsConfig){
368
363
  // Create root and `UIView` element for each editable container.
369
- const root = this.model.document.createRoot('$root', rootName);
364
+ const root = this.model.document.createRoot(rootConfig.modelElement, rootName);
370
365
  if (rootConfig.lazyLoad) {
371
366
  root._isLoaded = false;
372
367
  }
373
- const attributes = rootConfig.modelAttributes;
374
- if (attributes) {
375
- for (const key of Object.keys(attributes)){
376
- this.registerRootAttribute(key);
377
- }
378
- }
379
368
  }
369
+ registerAndInitializeRootConfigAttributes(this);
380
370
  // Registering `$rootEditableOptions` attribute to make it available in the editor model.
381
371
  // This allows to store editable options for each root in the model, and make them available on other RTC clients.
382
372
  // We do not use `registerRootAttribute()` method here, as this attribute is used internally
@@ -390,11 +380,6 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
390
380
  }, (writer)=>{
391
381
  for (const [rootName, rootConfig] of rootsConfig){
392
382
  const root = this.model.document.getRoot(rootName);
393
- for (const [key, value] of Object.entries(rootConfig.modelAttributes || {})){
394
- if (value !== null) {
395
- writer.setAttribute(key, value, root);
396
- }
397
- }
398
383
  // Set editable config for consistency with `addRoot()` method. This will allow features
399
384
  // to use the same configuration for both initially loaded and dynamically added roots.
400
385
  const rootEditableOptions = {
@@ -517,7 +502,7 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
517
502
  * console.log( 'Editor was destroyed' );
518
503
  * } );
519
504
  * ```
520
- */ destroy() {
505
+ */ async destroy() {
521
506
  const shouldUpdateSourceElement = this.config.get('updateSourceElementOnDestroy');
522
507
  // Cache the data and editable DOM elements, then destroy.
523
508
  // It's safe to assume that the model->view conversion will not work after `super.destroy()`,
@@ -529,16 +514,32 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
529
514
  }) : '';
530
515
  }
531
516
  this.ui.destroy();
532
- return super.destroy().then(()=>{
533
- for (const rootName of Object.keys(this.sourceElements)){
534
- setDataInElement(this.sourceElements[rootName], data[rootName]);
535
- }
536
- });
517
+ await super.destroy();
518
+ for (const rootName of Object.keys(this.sourceElements)){
519
+ setDataInElement(this.sourceElements[rootName], data[rootName]);
520
+ }
521
+ // To satisfy the return type and to keep it backward compatible.
522
+ // eslint-disable-next-line no-useless-return
523
+ return;
537
524
  }
538
525
  addRoot(rootName, options = {}) {
539
526
  const initialData = options.initialData || options.data || '';
540
527
  const modelAttributes = options.modelAttributes || options.attributes || {};
541
- const modelElement = options.elementName || '$root';
528
+ const modelElement = options.modelElement || options.elementName || '$root';
529
+ if (!this.model.schema.isLimit(modelElement)) {
530
+ /**
531
+ * The model root element must be a {@link module:engine/model/schema~ModelSchemaItemDefinition#isLimit limit element}.
532
+ * The element name specified in {@link ~MultiRootEditor#addRoot `addRoot()`} options must be registered in the schema
533
+ * with `isLimit` set to `true`.
534
+ *
535
+ * @error multi-root-editor-add-root-element-is-not-limit
536
+ * @param rootName The name of the root that uses a non-limit element.
537
+ * @param elementName The name of the model element used for the root.
538
+ */ throw new CKEditorError('multi-root-editor-add-root-element-is-not-limit', this, {
539
+ rootName,
540
+ elementName: modelElement
541
+ });
542
+ }
542
543
  if (isElement(options.element)) {
543
544
  /**
544
545
  * The `element` option is not supported in {@link #addRoot `addRoot()`} method, and will be ignored.
@@ -751,38 +752,6 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
751
752
  }
752
753
  return rootsAttributes;
753
754
  }
754
- /**
755
- * Returns attributes for the specified root.
756
- *
757
- * Note: all and only {@link ~MultiRootEditor#registerRootAttribute registered} roots attributes will be returned.
758
- * If a registered root attribute is not set for a given root, `null` will be returned.
759
- */ getRootAttributes(rootName) {
760
- const rootAttributes = {};
761
- const root = this.model.document.getRoot(rootName);
762
- for (const key of this._registeredRootsAttributesKeys){
763
- rootAttributes[key] = root.hasAttribute(key) ? root.getAttribute(key) : null;
764
- }
765
- return rootAttributes;
766
- }
767
- /**
768
- * Registers given string as a root attribute key. Registered root attributes are added to
769
- * {@link module:engine/model/schema~ModelSchema schema}, and also returned by
770
- * {@link ~MultiRootEditor#getRootAttributes `getRootAttributes()`} and
771
- * {@link ~MultiRootEditor#getRootsAttributes `getRootsAttributes()`}.
772
- *
773
- * Note: attributes passed in
774
- * {@link module:core/editor/editorconfig~EditorConfig#roots `config.roots.<rootName>.modelAttributes`}
775
- * are automatically registered as the editor is initialized. However, registering the same attribute twice does not have any
776
- * negative impact, so it is recommended to use this method in any feature that uses roots attributes.
777
- */ registerRootAttribute(key) {
778
- if (this._registeredRootsAttributesKeys.has(key)) {
779
- return;
780
- }
781
- this._registeredRootsAttributesKeys.add(key);
782
- this.editing.model.schema.extend('$root', {
783
- allowAttributes: key
784
- });
785
- }
786
755
  /**
787
756
  * Switches given editor root to the read-only mode.
788
757
  *
@@ -860,17 +829,21 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
860
829
  locksForGivenRoot.delete(lockId);
861
830
  }
862
831
  }
863
- static create(sourceElementsOrDataOrConfig, config = {}) {
864
- return new Promise((resolve)=>{
865
- const editor = new this(sourceElementsOrDataOrConfig, config);
866
- resolve(editor.initPlugins().then(()=>editor.ui.init()).then(()=>{
867
- const initialData = extractRootsConfigField(editor.config.get('roots'), 'initialData');
868
- // This is checked directly before setting the initial data,
869
- // as plugins may change `EditorConfig#initialData` value.
870
- editor._verifyRootsWithInitialData(initialData);
871
- return editor.data.init(initialData);
872
- }).then(()=>editor.fire('ready')).then(()=>editor));
873
- });
832
+ static async create(sourceElementsOrDataOrConfig, config = {}) {
833
+ const editor = new this(sourceElementsOrDataOrConfig, config);
834
+ await editor.initPlugins();
835
+ // Roots are created in the editor constructor (before plugins are loaded), but the schema is only fully
836
+ // built after plugins register their items during init(). Custom root element names (e.g. registered by a
837
+ // plugin) may not exist in the schema at construction time, so we defer this check until here.
838
+ verifyRootElements(editor);
839
+ await editor.ui.init();
840
+ const initialData = extractRootsConfigField(editor.config.get('roots'), 'initialData');
841
+ // This is checked directly before setting the initial data,
842
+ // as plugins may change `EditorConfig#initialData` value.
843
+ editor._verifyRootsWithInitialData(initialData);
844
+ await editor.data.init(initialData);
845
+ editor.fire('ready');
846
+ return editor;
874
847
  }
875
848
  /**
876
849
  * @internal