@ckeditor/ckeditor5-watchdog 47.6.1 → 48.0.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.
package/LICENSE.md CHANGED
@@ -18,7 +18,7 @@ Where not otherwise indicated, all CKEditor 5 content is authored by CKSour
18
18
 
19
19
  The following libraries are included in CKEditor 5 under the [MIT license](https://opensource.org/licenses/MIT):
20
20
 
21
- * es-toolkit - Copyright (c) 2024 Viva Republica, Inc.
21
+ * es-toolkit - Copyright (c) 2024 Viva Republica, Inc and Copyright OpenJS Foundation and other contributors.
22
22
 
23
23
  Trademarks
24
24
  ----------
@@ -5,7 +5,7 @@
5
5
  "className": "EditorWatchdog",
6
6
  "description": "A utility that detects if an editor crashed, destroys it, and automatically creates a new instance of that editor with the content of the previous editor.",
7
7
  "docs": "features/watchdog.html",
8
- "path": "src/editorwatchdog.js"
8
+ "path": "src/editorwatchdog.ts"
9
9
  }
10
10
  ]
11
11
  }
@@ -152,12 +152,12 @@ export declare class ContextWatchdog<TContext extends Context = Context> extends
152
152
  * await watchdog.add( [ {
153
153
  * id: 'editor1',
154
154
  * type: 'editor',
155
- * sourceElementOrData: document.querySelector( '#editor' ),
156
155
  * config: {
156
+ * attachTo: document.querySelector( '#editor' ),
157
157
  * plugins: [ Essentials, Paragraph, Bold, Italic ],
158
158
  * toolbar: [ 'bold', 'italic', 'alignment' ]
159
159
  * },
160
- * creator: ( element, config ) => ClassicEditor.create( element, config )
160
+ * creator: config => ClassicEditor.create( config )
161
161
  * } ] );
162
162
  * ```
163
163
  *
@@ -167,13 +167,13 @@ export declare class ContextWatchdog<TContext extends Context = Context> extends
167
167
  * await watchdog.add( {
168
168
  * id: 'editor1',
169
169
  * type: 'editor',
170
- * sourceElementOrData: document.querySelector( '#editor' ),
171
170
  * config: {
171
+ * attachTo: document.querySelector( '#editor' ),
172
172
  * plugins: [ Essentials, Paragraph, Bold, Italic ],
173
173
  * toolbar: [ 'bold', 'italic', 'alignment' ]
174
174
  * },
175
- * creator: ( element, config ) => ClassicEditor.create( element, config )
176
- * ] );
175
+ * creator: config => ClassicEditor.create( config )
176
+ * } );
177
177
  * ```
178
178
  *
179
179
  * Then an instance can be retrieved using the {@link #getItem} method:
@@ -312,8 +312,8 @@ export interface ContextWatchdogItemConfiguration {
312
312
  */
313
313
  type: 'editor';
314
314
  /**
315
- * A function that initializes the item (the editor). The function takes editor initialization arguments
316
- * and should return a promise. For example: `( el, config ) => ClassicEditor.create( el, config )`.
315
+ * A function that initializes the item (the editor). The function takes the editor configuration
316
+ * and should return a promise. For example: `config => ClassicEditor.create( config )`.
317
317
  */
318
318
  creator: EditorWatchdogCreatorFunction;
319
319
  /**
@@ -324,8 +324,13 @@ export interface ContextWatchdogItemConfiguration {
324
324
  /**
325
325
  * The source element or data that will be passed
326
326
  * as the first argument to the `Editor.create()` method.
327
+ *
328
+ * When not provided, the watchdog will use config-based creator mode, passing only the {@link #config}
329
+ * to the editor creator.
330
+ *
331
+ * @deprecated
327
332
  */
328
- sourceElementOrData: string | HTMLElement;
333
+ sourceElementOrData?: string | HTMLElement;
329
334
  /**
330
335
  * An editor configuration.
331
336
  */
@@ -41,9 +41,19 @@ export declare class EditorWatchdog<TEditor extends Editor = Editor> extends Wat
41
41
  */
42
42
  private _elementOrData?;
43
43
  /**
44
- * Specifies whether the editor was initialized using document data (`true`) or HTML elements (`false`).
44
+ * Stores the original DOM element for single-root editors.
45
45
  */
46
- private _initUsingData;
46
+ private _editorAttachTo;
47
+ /**
48
+ * Specifies whether the editor is a single-root editor (e.g. ClassicEditor) or a multi-root editor (e.g. MultiRootEditor).
49
+ */
50
+ private _isSingleRootEditor;
51
+ /**
52
+ * Specifies whether the editor was created using config-based creator mode (without a source element or data as the first argument).
53
+ *
54
+ * @internal
55
+ */
56
+ _isUsingConfigBasedCreator: boolean;
47
57
  /**
48
58
  * The latest record of the editor editable elements. Used to restart the editor.
49
59
  */
@@ -84,6 +94,14 @@ export declare class EditorWatchdog<TEditor extends Editor = Editor> extends Wat
84
94
  * Sets the function that is responsible for the editor creation.
85
95
  * It expects a function that should return a promise.
86
96
  *
97
+ * For config-based editor creation:
98
+ *
99
+ * ```ts
100
+ * watchdog.setCreator( config => ClassicEditor.create( config ) );
101
+ * ```
102
+ *
103
+ * For legacy editor creation (with element or data as the first argument):
104
+ *
87
105
  * ```ts
88
106
  * watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) );
89
107
  * ```
@@ -117,11 +135,21 @@ export declare class EditorWatchdog<TEditor extends Editor = Editor> extends Wat
117
135
  /**
118
136
  * Creates the editor instance and keeps it running, using the defined creator and destructor.
119
137
  *
138
+ * @param config The editor configuration.
139
+ * @param context A context for the editor.
140
+ */
141
+ create(config: EditorConfig, context?: Context): Promise<unknown>;
142
+ /**
143
+ * Creates the editor instance and keeps it running, using the defined creator and destructor.
144
+ *
145
+ * **Note**: This method signature is deprecated and will be removed in the future release.
146
+ *
147
+ * @deprecated
120
148
  * @param elementOrData The editor source element or the editor data.
121
149
  * @param config The editor configuration.
122
150
  * @param context A context for the editor.
123
151
  */
124
- create(elementOrData?: HTMLElement | string | Record<string, string> | Record<string, HTMLElement>, config?: EditorConfig, context?: Context): Promise<unknown>;
152
+ create(elementOrData: HTMLElement | string | Record<string, string> | Record<string, HTMLElement>, config: EditorConfig, context?: Context): Promise<unknown>;
125
153
  /**
126
154
  * Destroys the watchdog and the current editor instance. It fires the callback
127
155
  * registered in {@link #setDestructor `setDestructor()`} and uses it to destroy the editor instance.
@@ -153,6 +181,11 @@ export declare class EditorWatchdog<TEditor extends Editor = Editor> extends Wat
153
181
  * @internal
154
182
  */
155
183
  _isErrorComingFromThisItem(error: CKEditorError): boolean;
184
+ /**
185
+ * Detects whether the `create()` call was made in config-based creator mode
186
+ * (i.e., the first argument is a config object rather than a source element or data).
187
+ */
188
+ private _detectConfigBasedCreator;
156
189
  /**
157
190
  * Clones the editor configuration.
158
191
  */
@@ -188,4 +221,4 @@ export type EditorWatchdogRestartEvent = {
188
221
  args: [];
189
222
  return: undefined;
190
223
  };
191
- export type EditorWatchdogCreatorFunction<TEditor = Editor> = (elementOrData: HTMLElement | string | Record<string, string> | Record<string, HTMLElement>, config: EditorConfig) => Promise<TEditor>;
224
+ export type EditorWatchdogCreatorFunction<TEditor = Editor> = ((config: EditorConfig) => Promise<TEditor>) | ((elementOrData: HTMLElement | string | Record<string, string> | Record<string, HTMLElement> | undefined, config: EditorConfig) => Promise<TEditor>);
package/dist/index.css CHANGED
@@ -2,3 +2,6 @@
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
+
6
+
7
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["index.css"],"names":[],"mappings":";;;;;;AAEA,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC","file":"index.css.map","sourcesContent":["\n\n/*# sourceMappingURL=index.css.map */"]}
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 { throttle, isElement, cloneDeepWith, isPlainObject } from 'es-toolkit/compat';
5
+ import { isElement as isElement$2, throttle, cloneDeepWith, isPlainObject as isPlainObject$1 } from 'es-toolkit/compat';
6
6
 
7
7
  /**
8
8
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
@@ -274,6 +274,82 @@ function isObject(structure) {
274
274
  return typeof structure === 'object' && structure !== null;
275
275
  }
276
276
 
277
+ // Note: This file is a copy of core/editor/utils/normalizerootsconfig with some adjustments.
278
+ // The main difference is that it does not use the `Config` class from `@ckeditor/ckeditor5-utils` and instead works
279
+ // with a plain `EditorConfig` object. It also does not throw `CKEditorError` but a generic `Error` with a specific message.
280
+ // It resolves root configuration for the editor, but does not care about real data in initialData as the watchdog does not need it.
281
+ // It just needs to ensure that initialData is defined for all roots, so the editor features can work with it without additional checks.
282
+ // Note that this helper modifies the provided config object.
283
+ /**
284
+ * Normalizes the editor roots configuration. It ensures that all root configurations are defined in `config.roots`
285
+ * and that they have `initialData` defined.
286
+ *
287
+ * It normalizes a single-root configuration (where `config.root` is used) to a multi-root configuration
288
+ * (where all roots are defined in `config.roots`). This is considered a standard configuration format,
289
+ * so the editor features can always expect roots to be defined in `config.roots`.
290
+ *
291
+ * It also handles legacy configuration options, such as `config.initialData`, `config.placeholder`, and `config.label`.
292
+ *
293
+ * @internal
294
+ */ function normalizeRootsConfig(sourceElementsOrData, config, defaultRootName) {
295
+ const mainRootConfig = config.root;
296
+ const rootsConfig = config.roots || Object.create(null);
297
+ // Move `config.root` to `config.roots.main`.
298
+ // This makes access to root configuration more consistent as all roots will be defined in `config.roots`.
299
+ if (defaultRootName && !rootsConfig[defaultRootName]) {
300
+ rootsConfig[defaultRootName] = mainRootConfig || Object.create(null);
301
+ }
302
+ const sourceElementIsPlainObject = isPlainObject(sourceElementsOrData);
303
+ // Collect legacy configuration values for `initialData`, `placeholder`, and `label` from the config.
304
+ const legacyInitialData = getLegacyInitialData(config, sourceElementIsPlainObject, defaultRootName);
305
+ // Collect root names. This includes root names from the source element (if it's an object),
306
+ // from `config.roots`, and from legacy config. This ensures that all roots are processed in the next step.
307
+ const rootNames = Array.from(new Set([
308
+ ...sourceElementIsPlainObject ? Object.keys(sourceElementsOrData) : [],
309
+ ...Object.keys(rootsConfig),
310
+ ...Object.keys(legacyInitialData)
311
+ ]));
312
+ // Ensure that all roots have `initialData` defined. If not, try to get it from the source element or data.
313
+ for (const rootName of rootNames){
314
+ const rootConfig = rootsConfig[rootName] || Object.create(null);
315
+ // Watchdog does not need HTML, it uses dedicated plugin for restoring the model.
316
+ // As watchdog uses only DOM element or map of DOM elements for sourceElementsOrData,
317
+ // it can not use string as initial data, so it can be safely set to an empty string.
318
+ rootConfig.initialData = '';
319
+ // Handle legacy `config.placeholder` and `config.label` for the root.
320
+ rootConfig.placeholder ??= getLegacyPlainConfigValue(config, 'placeholder', rootName);
321
+ rootConfig.label ??= getLegacyPlainConfigValue(config, 'label', rootName);
322
+ rootsConfig[rootName] = rootConfig;
323
+ }
324
+ config.roots = rootsConfig;
325
+ }
326
+ /**
327
+ * Type guard to check if the provided value is a plain object.
328
+ */ function isPlainObject(sourceElementsOrData) {
329
+ return !!sourceElementsOrData && typeof sourceElementsOrData == 'object' && !Array.isArray(sourceElementsOrData) && !isElement$1(sourceElementsOrData);
330
+ }
331
+ /**
332
+ * Retrieve legacy configuration value for `initialData` from the config.
333
+ * Normalize single-root config so returned value is always an object with root names as keys.
334
+ */ function getLegacyInitialData(config, sourceElementIsObject, defaultRootName) {
335
+ return sourceElementIsObject || !defaultRootName ? config.initialData || Object.create(null) : {
336
+ [defaultRootName]: config.initialData
337
+ };
338
+ }
339
+ /**
340
+ * Retrieve legacy configuration value for `placeholder` or `label` from the config for a specific root.
341
+ */ function getLegacyPlainConfigValue(config, key, rootName) {
342
+ const legacyValue = config[key];
343
+ if (legacyValue) {
344
+ return typeof legacyValue == 'string' ? legacyValue : legacyValue[rootName];
345
+ }
346
+ }
347
+ /**
348
+ * An alias for `isElement` from `es-toolkit/compat` with additional type guard.
349
+ */ function isElement$1(value) {
350
+ return isElement$2(value);
351
+ }
352
+
277
353
  /**
278
354
  * A watchdog for CKEditor 5 editors.
279
355
  *
@@ -303,8 +379,16 @@ function isObject(structure) {
303
379
  * The editor source element or data.
304
380
  */ _elementOrData;
305
381
  /**
306
- * Specifies whether the editor was initialized using document data (`true`) or HTML elements (`false`).
307
- */ _initUsingData = true;
382
+ * Stores the original DOM element for single-root editors.
383
+ */ _editorAttachTo = null;
384
+ /**
385
+ * Specifies whether the editor is a single-root editor (e.g. ClassicEditor) or a multi-root editor (e.g. MultiRootEditor).
386
+ */ _isSingleRootEditor = true;
387
+ /**
388
+ * Specifies whether the editor was created using config-based creator mode (without a source element or data as the first argument).
389
+ *
390
+ * @internal
391
+ */ _isUsingConfigBasedCreator = false;
308
392
  /**
309
393
  * The latest record of the editor editable elements. Used to restart the editor.
310
394
  */ _editables = {};
@@ -321,7 +405,14 @@ function isObject(structure) {
321
405
  this._throttledSave = throttle(this._save.bind(this), typeof watchdogConfig.saveInterval === 'number' ? watchdogConfig.saveInterval : 5000);
322
406
  // Set default creator and destructor functions:
323
407
  if (Editor) {
324
- this._creator = (elementOrData, config)=>Editor.create(elementOrData, config);
408
+ this._creator = (elementOrDataOrConfig, config)=>{
409
+ if (config === undefined) {
410
+ // Config-based mode: first argument is the config.
411
+ return Editor.create(elementOrDataOrConfig);
412
+ }
413
+ // Legacy mode: first argument is element/data, second is config.
414
+ return Editor.create(elementOrDataOrConfig, config);
415
+ };
325
416
  }
326
417
  this._destructor = (editor)=>editor.destroy();
327
418
  }
@@ -339,6 +430,14 @@ function isObject(structure) {
339
430
  * Sets the function that is responsible for the editor creation.
340
431
  * It expects a function that should return a promise.
341
432
  *
433
+ * For config-based editor creation:
434
+ *
435
+ * ```ts
436
+ * watchdog.setCreator( config => ClassicEditor.create( config ) );
437
+ * ```
438
+ *
439
+ * For legacy editor creation (with element or data as the first argument):
440
+ *
342
441
  * ```ts
343
442
  * watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) );
344
443
  * ```
@@ -379,80 +478,98 @@ function isObject(structure) {
379
478
  }).then(()=>{
380
479
  // Pre-process some data from the original editor config.
381
480
  // Our goal here is to make sure that the restarted editor will be reinitialized with correct set of roots.
382
- // We are not interested in any data set in config or in `.create()` first parameter. It will be replaced anyway.
481
+ // We are not interested in any data set in config. It will be replaced anyway.
383
482
  // But we need to set them correctly to make sure that proper roots are created.
384
483
  //
385
- // Since a different set of roots will be created, `lazyRoots` and `rootsAttributes` properties must be managed too.
386
- // Keys are root names, values are ''. Used when the editor was initialized by setting the first parameter to document data.
387
- const existingRoots = {};
388
- // Keeps lazy roots. They may be different when compared to initial config if some of the roots were loaded.
389
- const lazyRoots = [];
390
- // Roots attributes from the old config. Will be referred when setting new attributes.
391
- const oldRootsAttributes = this._config.rootsAttributes || {};
392
- // New attributes to be set. Is filled only for roots that still exist in the document.
393
- const rootsAttributes = {};
394
- // Traverse through the roots saved when the editor crashed and set up the discussed values.
395
- for (const [rootName, rootData] of Object.entries(this._data.roots)){
396
- if (rootData.isLoaded) {
397
- existingRoots[rootName] = '';
398
- rootsAttributes[rootName] = oldRootsAttributes[rootName] || {};
399
- } else {
400
- lazyRoots.push(rootName);
401
- }
484
+ // Since a different set of roots will be created, lazy-roots and roots-attributes must be managed too.
485
+ if (this._isUsingConfigBasedCreator) {
486
+ // In config-based creator mode, normalize using an empty source to ensure `config.root` is moved
487
+ // to `config.roots.main` and other legacy config properties are handled.
488
+ normalizeRootsConfig(this._isSingleRootEditor ? '' : {}, this._config, this._isSingleRootEditor ? 'main' : false);
489
+ } else {
490
+ // Normalize the roots configuration based on the editor source element or data and the editor configuration.
491
+ normalizeRootsConfig(this._isSingleRootEditor ? this._editorAttachTo || '' : this._editables, this._config, this._isSingleRootEditor ? 'main' : false);
402
492
  }
403
493
  const updatedConfig = {
404
494
  ...this._config,
405
495
  extraPlugins: this._config.extraPlugins || [],
406
- lazyRoots,
407
- rootsAttributes,
408
496
  _watchdogInitialData: this._data
409
497
  };
410
- // Delete `initialData` as it is not needed. Data will be set by the watchdog based on `_watchdogInitialData`.
411
- // First parameter of the editor `.create()` will be used to set up initial roots.
412
- delete updatedConfig.initialData;
498
+ // Add content loading plugin to the editor configuration.
499
+ // This plugin will be responsible for loading the editor data into the editor after it is restarted.
413
500
  updatedConfig.extraPlugins.push(EditorWatchdogInitPlugin);
414
- if (this._initUsingData) {
415
- return this.create(existingRoots, updatedConfig, updatedConfig.context);
416
- } else {
417
- // Set correct editables to make sure that proper roots are created and linked with DOM elements.
418
- // No need to set initial data, as it would be discarded anyway.
419
- //
420
- // If one element was initially set in `elementOrData`, then use that original element to restart the editor.
421
- // This is for compatibility purposes with single-root editor types.
422
- if (isElement(this._elementOrData)) {
423
- return this.create(this._elementOrData, updatedConfig, updatedConfig.context);
501
+ // Collect existing roots configuration and update it. This will ensure that the same set of roots
502
+ // will be created after the restart, and they will have correct lazy loading and attributes configuration.
503
+ const updatedRootsConfig = {};
504
+ for (const [rootName, rootData] of Object.entries(this._data.roots)){
505
+ const rootConfig = updatedConfig.roots[rootName] || Object.create(null);
506
+ // Delete `initialData` as it is not needed. Data will be set by the watchdog based on `_watchdogInitialData`.
507
+ rootConfig.initialData = '';
508
+ if (rootData.isLoaded) {
509
+ rootConfig.lazyLoad = false;
424
510
  } else {
425
- return this.create(this._editables, updatedConfig, updatedConfig.context);
511
+ delete rootConfig.modelAttributes;
426
512
  }
513
+ updatedRootsConfig[rootName] = rootConfig;
427
514
  }
515
+ updatedConfig.roots = updatedRootsConfig;
516
+ // Delete `initialData` as it is not needed. Data will be set by the watchdog based on `_watchdogInitialData`.
517
+ delete updatedConfig.initialData;
518
+ // Also alias for main root should not provide initial data.
519
+ // In fact, it should not provide any data as it is only an alias for the root defined in `config.roots`
520
+ // and it is the `config.roots` that should be used to set up the initial data for the main root.
521
+ // This would cause a crash while normalizing conflict when left as is.
522
+ delete updatedConfig.root;
523
+ if (this._isUsingConfigBasedCreator) {
524
+ return this.create(updatedConfig, updatedConfig.context);
525
+ }
526
+ const elementOrData = this._isSingleRootEditor ? this._editorAttachTo || '' : this._editables;
527
+ return this.create(elementOrData, updatedConfig, updatedConfig.context);
428
528
  }).then(()=>{
429
529
  this._fire('restart');
430
530
  });
431
531
  }
432
- /**
433
- * Creates the editor instance and keeps it running, using the defined creator and destructor.
434
- *
435
- * @param elementOrData The editor source element or the editor data.
436
- * @param config The editor configuration.
437
- * @param context A context for the editor.
438
- */ create(elementOrData = this._elementOrData, config = this._config, context) {
532
+ create(elementOrDataOrConfig = this._isUsingConfigBasedCreator ? this._config : this._elementOrData, configOrContext = this._isUsingConfigBasedCreator ? undefined : this._config, context) {
533
+ // Detect config-based creator mode: first argument is a config object (not an element, string, or record of strings/elements).
534
+ // The detection is skipped during restart (when `_elementOrData` or `_config` is already set).
535
+ const isUsingConfigBasedCreator = this._detectConfigBasedCreator(elementOrDataOrConfig, configOrContext);
536
+ const elementOrData = isUsingConfigBasedCreator ? undefined : elementOrDataOrConfig;
537
+ const config = isUsingConfigBasedCreator ? elementOrDataOrConfig : configOrContext;
538
+ const resolvedContext = isUsingConfigBasedCreator ? configOrContext : context;
439
539
  this._lifecyclePromise = Promise.resolve(this._lifecyclePromise).then(()=>{
440
540
  super._startErrorHandling();
541
+ this._isUsingConfigBasedCreator = isUsingConfigBasedCreator;
441
542
  this._elementOrData = elementOrData;
442
- // Use document data in the first parameter of the editor `.create()` call only if it was used like this originally.
443
- // Use document data if a string or object with strings was passed.
444
- this._initUsingData = typeof elementOrData == 'string' || Object.keys(elementOrData).length > 0 && typeof Object.values(elementOrData)[0] == 'string';
445
543
  // Clone configuration because it might be shared within multiple watchdog instances. Otherwise,
446
544
  // when an error occurs in one of these editors, the watchdog will restart all of them.
447
- this._config = this._cloneEditorConfiguration(config) || {};
448
- this._config.context = context;
545
+ this._config = this._cloneEditorConfiguration(config || {});
546
+ this._config.context = resolvedContext;
547
+ // Store the original DOM element for single-root editors. We can't use editable elements as ClassicEditor
548
+ // expects the attachment element.
549
+ if (isUsingConfigBasedCreator) {
550
+ // In config-based creator mode, element references are already in the config
551
+ // (`config.attachTo` or `config.roots.*.element`), so there's no need to store them separately.
552
+ this._editorAttachTo = null;
553
+ // Detect single-root vs multi-root from config. The config might use `config.root` (alias),
554
+ // `config.roots`, `config.attachTo`, or legacy `config.initialData`.
555
+ const rootsCount = this._config.roots ? Object.keys(this._config.roots).length : 0;
556
+ const legacyInitialData = this._config.initialData;
557
+ const isMultiRootFromLegacy = legacyInitialData && typeof legacyInitialData === 'object';
558
+ this._isSingleRootEditor = !isMultiRootFromLegacy && rootsCount <= 1;
559
+ } else {
560
+ this._editorAttachTo = isElement(elementOrData) ? elementOrData : null;
561
+ this._isSingleRootEditor = isElement(elementOrData) || typeof elementOrData == 'string';
562
+ }
563
+ if (isUsingConfigBasedCreator) {
564
+ return this._creator(this._config);
565
+ }
449
566
  return this._creator(elementOrData, this._config);
450
567
  }).then((editor)=>{
451
568
  this._editor = editor;
452
569
  editor.model.document.on('change:data', this._throttledSave);
453
570
  this._lastDocumentVersion = editor.model.document.version;
454
571
  this._data = this._getData();
455
- if (!this._initUsingData) {
572
+ if (!this._editorAttachTo) {
456
573
  this._editables = this._getEditables();
457
574
  }
458
575
  this.state = 'ready';
@@ -497,7 +614,7 @@ function isObject(structure) {
497
614
  const version = this._editor.model.document.version;
498
615
  try {
499
616
  this._data = this._getData();
500
- if (!this._initUsingData) {
617
+ if (!this._editorAttachTo) {
501
618
  this._editables = this._getEditables();
502
619
  }
503
620
  this._lastDocumentVersion = version;
@@ -576,6 +693,28 @@ function isObject(structure) {
576
693
  */ _isErrorComingFromThisItem(error) {
577
694
  return areConnectedThroughProperties(this._editor, error.context, this._excludedProps);
578
695
  }
696
+ /**
697
+ * Detects whether the `create()` call was made in config-based creator mode
698
+ * (i.e., the first argument is a config object rather than a source element or data).
699
+ */ _detectConfigBasedCreator(elementOrDataOrConfig, configOrContext) {
700
+ // A string or DOM element is clearly the legacy signature.
701
+ if (typeof elementOrDataOrConfig === 'string' || isElement(elementOrDataOrConfig)) {
702
+ return false;
703
+ }
704
+ // If the second argument is a plain object with keys, it's a config → legacy signature.
705
+ if (configOrContext && typeof configOrContext === 'object' && !('destroy' in configOrContext) && Object.keys(configOrContext).length > 0) {
706
+ return false;
707
+ }
708
+ // If the first argument is an object where all values are strings or elements, it's multi-root legacy.
709
+ if (elementOrDataOrConfig && typeof elementOrDataOrConfig === 'object') {
710
+ const values = Object.values(elementOrDataOrConfig);
711
+ if (values.length > 0 && values.every((v)=>typeof v === 'string' || isElement(v))) {
712
+ return false;
713
+ }
714
+ }
715
+ // Otherwise, it's config-based.
716
+ return true;
717
+ }
579
718
  /**
580
719
  * Clones the editor configuration.
581
720
  */ _cloneEditorConfiguration(config) {
@@ -705,6 +844,11 @@ function isObject(structure) {
705
844
  }
706
845
  }
707
846
  }
847
+ /**
848
+ * An alias for `isElement` from `es-toolkit/compat` with additional type guard.
849
+ */ function isElement(value) {
850
+ return isElement$2(value);
851
+ }
708
852
 
709
853
  const mainQueueId = Symbol('MainQueueId');
710
854
  /**
@@ -850,12 +994,12 @@ const mainQueueId = Symbol('MainQueueId');
850
994
  * await watchdog.add( [ {
851
995
  * id: 'editor1',
852
996
  * type: 'editor',
853
- * sourceElementOrData: document.querySelector( '#editor' ),
854
997
  * config: {
998
+ * attachTo: document.querySelector( '#editor' ),
855
999
  * plugins: [ Essentials, Paragraph, Bold, Italic ],
856
1000
  * toolbar: [ 'bold', 'italic', 'alignment' ]
857
1001
  * },
858
- * creator: ( element, config ) => ClassicEditor.create( element, config )
1002
+ * creator: config => ClassicEditor.create( config )
859
1003
  * } ] );
860
1004
  * ```
861
1005
  *
@@ -865,13 +1009,13 @@ const mainQueueId = Symbol('MainQueueId');
865
1009
  * await watchdog.add( {
866
1010
  * id: 'editor1',
867
1011
  * type: 'editor',
868
- * sourceElementOrData: document.querySelector( '#editor' ),
869
1012
  * config: {
1013
+ * attachTo: document.querySelector( '#editor' ),
870
1014
  * plugins: [ Essentials, Paragraph, Bold, Italic ],
871
1015
  * toolbar: [ 'bold', 'italic', 'alignment' ]
872
1016
  * },
873
- * creator: ( element, config ) => ClassicEditor.create( element, config )
874
- * ] );
1017
+ * creator: config => ClassicEditor.create( config )
1018
+ * } );
875
1019
  * ```
876
1020
  *
877
1021
  * Then an instance can be retrieved using the {@link #getItem} method:
@@ -928,7 +1072,10 @@ const mainQueueId = Symbol('MainQueueId');
928
1072
  watchdog.on('restart', rethrowRestartEventOnce);
929
1073
  }));
930
1074
  });
931
- return watchdog.create(item.sourceElementOrData, item.config, this._context);
1075
+ if (item.sourceElementOrData !== undefined) {
1076
+ return watchdog.create(item.sourceElementOrData, item.config, this._context);
1077
+ }
1078
+ return watchdog.create(item.config, this._context);
932
1079
  } else {
933
1080
  throw new Error(`Not supported item type: '${item.type}'.`);
934
1081
  }
@@ -996,6 +1143,9 @@ const mainQueueId = Symbol('MainQueueId');
996
1143
  this._contextProps = getSubNodes(this._context);
997
1144
  return Promise.all(Array.from(this._watchdogs.values()).map((watchdog)=>{
998
1145
  watchdog._setExcludedProperties(this._contextProps);
1146
+ if (watchdog._isUsingConfigBasedCreator) {
1147
+ return watchdog.create(undefined, this._context);
1148
+ }
999
1149
  return watchdog.create(undefined, undefined, this._context);
1000
1150
  }));
1001
1151
  });
@@ -1552,7 +1702,7 @@ const mainQueueId = Symbol('MainQueueId');
1552
1702
  if (typeof value.toJSON == 'function') {
1553
1703
  const jsonData = value.toJSON();
1554
1704
  // Make sure that toJSON returns plain object, otherwise it could be just a clone with circular references.
1555
- if (isPlainObject(jsonData) || Array.isArray(jsonData) || [
1705
+ if (isPlainObject$1(jsonData) || Array.isArray(jsonData) || [
1556
1706
  'string',
1557
1707
  'number',
1558
1708
  'boolean'