ckeditor5-blazor 1.11.0 → 1.12.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.
Files changed (32) hide show
  1. package/dist/elements/editable.d.ts.map +1 -1
  2. package/dist/elements/editor/editor.d.ts.map +1 -1
  3. package/dist/elements/editor/utils/assign-editor-roots-to-config.d.ts +17 -0
  4. package/dist/elements/editor/utils/assign-editor-roots-to-config.d.ts.map +1 -0
  5. package/dist/elements/editor/utils/index.d.ts +2 -3
  6. package/dist/elements/editor/utils/index.d.ts.map +1 -1
  7. package/dist/elements/editor/utils/query-all-editor-editables.d.ts +24 -0
  8. package/dist/elements/editor/utils/query-all-editor-editables.d.ts.map +1 -0
  9. package/dist/index.cjs +2 -2
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.mjs +395 -437
  12. package/dist/index.mjs.map +1 -1
  13. package/package.json +3 -3
  14. package/src/elements/editable.test.ts +42 -3
  15. package/src/elements/editable.ts +2 -0
  16. package/src/elements/editor/editor.test.ts +97 -13
  17. package/src/elements/editor/editor.ts +39 -64
  18. package/src/elements/editor/utils/assign-editor-roots-to-config.ts +61 -0
  19. package/src/elements/editor/utils/get-editor-roots-values.ts +1 -1
  20. package/src/elements/editor/utils/index.ts +2 -3
  21. package/src/elements/editor/utils/query-all-editor-editables.ts +81 -0
  22. package/src/elements/ui-part.test.ts +8 -9
  23. package/src/interop/create-editable-blazor-interop.test.ts +5 -2
  24. package/dist/elements/editor/utils/assign-initial-data-to-editor-config.d.ts +0 -10
  25. package/dist/elements/editor/utils/assign-initial-data-to-editor-config.d.ts.map +0 -1
  26. package/dist/elements/editor/utils/assign-source-elements-to-editor-config.d.ts +0 -12
  27. package/dist/elements/editor/utils/assign-source-elements-to-editor-config.d.ts.map +0 -1
  28. package/dist/elements/editor/utils/query-editor-editables.d.ts +0 -25
  29. package/dist/elements/editor/utils/query-editor-editables.d.ts.map +0 -1
  30. package/src/elements/editor/utils/assign-initial-data-to-editor-config.ts +0 -47
  31. package/src/elements/editor/utils/assign-source-elements-to-editor-config.ts +0 -59
  32. package/src/elements/editor/utils/query-editor-editables.ts +0 -101
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ckeditor5-blazor",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "CKEditor 5 integration for Blazor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,8 +28,8 @@
28
28
  ],
29
29
  "devDependencies": {
30
30
  "@vitest/coverage-v8": "^4.0.18",
31
- "ckeditor5": "^48.1.0",
32
- "ckeditor5-premium-features": "^48.1.0",
31
+ "ckeditor5": "^48.2.0",
32
+ "ckeditor5-premium-features": "^48.2.0",
33
33
  "happy-dom": "^20.8.9",
34
34
  "typescript": "^5.9.3",
35
35
  "vite": "^7.3.2",
@@ -233,6 +233,42 @@ describe('editable component', () => {
233
233
 
234
234
  expect(editor.model.document.getRoot('foo')).toBe(null);
235
235
  });
236
+
237
+ it('should set proper root element name on initial added root', async () => {
238
+ renderTestEditable({
239
+ content: '<p>Foo</p>',
240
+ rootName: 'foo',
241
+ rootModelElementName: '$inlineRoot',
242
+ });
243
+
244
+ renderTestEditor({
245
+ preset: createEditorPreset('multiroot'),
246
+ content: {},
247
+ });
248
+
249
+ const editor = await waitForTestEditor<MultiRootEditor>();
250
+
251
+ expect(editor.model.document.getRoot('foo')?.name).to.be.equal('$inlineRoot');
252
+ });
253
+
254
+ it('should set proper root element name on lazy added root', async () => {
255
+ renderTestEditor({
256
+ preset: createEditorPreset('multiroot'),
257
+ content: {},
258
+ });
259
+
260
+ const editor = await waitForTestEditor<MultiRootEditor>();
261
+
262
+ renderTestEditable({
263
+ content: '<p>Foo</p>',
264
+ rootName: 'foo',
265
+ rootModelElementName: '$inlineRoot',
266
+ });
267
+
268
+ await vi.waitFor(() => {
269
+ expect(editor.model.document.getRoot('foo')?.name).to.be.equal('$inlineRoot');
270
+ });
271
+ });
236
272
  });
237
273
 
238
274
  describe('input value synchronization', () => {
@@ -252,7 +288,8 @@ describe('editable component', () => {
252
288
  renderTestEditable({
253
289
  rootName: 'foo',
254
290
  content: '<p>Initial foo component</p>',
255
- }, { withInput: false });
291
+ withInput: false,
292
+ });
256
293
 
257
294
  await vi.waitFor(() => {
258
295
  expect(editor.getData({ rootName: 'foo' })).toBe('<p>Initial foo component</p>');
@@ -263,7 +300,8 @@ describe('editable component', () => {
263
300
  const element = renderTestEditable({
264
301
  rootName: 'foo',
265
302
  content: '<p>Initial foo component</p>',
266
- }, { withInput: true });
303
+ withInput: true,
304
+ });
267
305
 
268
306
  const input = element.querySelector('input')!;
269
307
 
@@ -277,7 +315,8 @@ describe('editable component', () => {
277
315
  rootName: 'foo',
278
316
  content: '<p>Initial foo component</p>',
279
317
  saveDebounceMs: 500,
280
- }, { withInput: true });
318
+ withInput: true,
319
+ });
281
320
 
282
321
  const input = element.querySelector('input')!;
283
322
 
@@ -42,6 +42,7 @@ export class EditableComponentElement extends HTMLElement {
42
42
  const editorId = this.getAttribute('data-cke-editor-id');
43
43
  const rootName = this.getAttribute('data-cke-root-name');
44
44
  const rootAttributes = JSON.parse(this.getAttribute('data-cke-root-attributes') || '{}');
45
+ const rootModelElement = this.getAttribute('data-cke-root-model-element-name') || '$root';
45
46
  const content = this.getAttribute('data-cke-content');
46
47
  const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
47
48
 
@@ -89,6 +90,7 @@ export class EditableComponentElement extends HTMLElement {
89
90
  editor.addRoot(rootName, {
90
91
  isUndoable: false,
91
92
  modelAttributes: { ...rootAttributes },
93
+ modelElement: rootModelElement,
92
94
  ...content !== null && {
93
95
  initialData: content,
94
96
  },
@@ -12,7 +12,6 @@ import {
12
12
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13
13
 
14
14
  import {
15
- createEditableSnapshot,
16
15
  createEditorPreset,
17
16
  getTestEditorInput,
18
17
  html,
@@ -118,6 +117,16 @@ describe('editor component', () => {
118
117
  expect(root.getAttribute('data-test-attr')).toBe('123');
119
118
  expect(root.getAttribute('data-another-attr')).toBe('abc');
120
119
  });
120
+
121
+ it('should be possible to specify root element name', async () => {
122
+ renderTestEditor({
123
+ rootModelElementName: '$inlineRoot',
124
+ });
125
+
126
+ const editor = await waitForTestEditor();
127
+
128
+ expect(editor.model.document.getRoot()?.name).toBe('$inlineRoot');
129
+ });
121
130
  });
122
131
 
123
132
  describe('inline', () => {
@@ -134,7 +143,7 @@ describe('editor component', () => {
134
143
  describe('decoupled', () => {
135
144
  it('should create a decoupled editor with `main` editable and default preset', async () => {
136
145
  renderTestEditor({ preset: createEditorPreset('decoupled') });
137
- renderTestEditable(createEditableSnapshot('main', null));
146
+ renderTestEditable();
138
147
 
139
148
  const editor = await waitForTestEditor();
140
149
 
@@ -146,7 +155,7 @@ describe('editor component', () => {
146
155
  const initialEditableContent = '<p>Initial editable content</p>';
147
156
 
148
157
  renderTestEditor({ preset: createEditorPreset('decoupled') });
149
- renderTestEditable(createEditableSnapshot('main', initialEditableContent));
158
+ renderTestEditable({ content: initialEditableContent });
150
159
 
151
160
  const editor = await waitForTestEditor();
152
161
 
@@ -159,13 +168,64 @@ describe('editor component', () => {
159
168
 
160
169
  await timeout(200);
161
170
 
162
- renderTestEditable(createEditableSnapshot('main', null));
171
+ renderTestEditable({});
163
172
 
164
173
  const editor = await waitForTestEditor();
165
174
 
166
175
  expect(editor).to.toBeInstanceOf(DecoupledEditor);
167
176
  expect(isEditorShown()).toBe(true);
168
177
  });
178
+
179
+ it('should be possible to specify root element name using editable config alone', async () => {
180
+ renderTestEditor({
181
+ preset: createEditorPreset('decoupled'),
182
+ content: {},
183
+ });
184
+
185
+ renderTestEditable({
186
+ rootModelElementName: '$inlineRoot',
187
+ });
188
+
189
+ const editor = await waitForTestEditor<DecoupledEditor>();
190
+
191
+ await vi.waitFor(() => {
192
+ expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
193
+ });
194
+ });
195
+
196
+ it('should use editable root element name config if both specified', async () => {
197
+ renderTestEditor({
198
+ preset: createEditorPreset('decoupled'),
199
+ content: {},
200
+ rootModelElementName: '$miamia',
201
+ });
202
+
203
+ renderTestEditable({
204
+ rootModelElementName: '$inlineRoot',
205
+ });
206
+
207
+ const editor = await waitForTestEditor<DecoupledEditor>();
208
+
209
+ await vi.waitFor(() => {
210
+ expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
211
+ });
212
+ });
213
+
214
+ it('should use default `$root` if editable root name is not specified', async () => {
215
+ renderTestEditor({
216
+ preset: createEditorPreset('decoupled'),
217
+ content: {},
218
+ rootModelElementName: '$miamia',
219
+ });
220
+
221
+ renderTestEditable();
222
+
223
+ const editor = await waitForTestEditor<DecoupledEditor>();
224
+
225
+ await vi.waitFor(() => {
226
+ expect(editor.model.document.getRoot()?.name).toEqual('$root');
227
+ });
228
+ });
169
229
  });
170
230
 
171
231
  describe('balloon', () => {
@@ -201,7 +261,7 @@ describe('editor component', () => {
201
261
 
202
262
  await timeout(500); // Simulate some delay before adding the root.
203
263
 
204
- renderTestEditable(createEditableSnapshot('header'));
264
+ renderTestEditable({ rootName: 'header' });
205
265
 
206
266
  const editor = await waitForTestEditor();
207
267
 
@@ -219,7 +279,10 @@ describe('editor component', () => {
219
279
 
220
280
  await timeout(500); // Simulate some delay before adding the root.
221
281
 
222
- renderTestEditable(createEditableSnapshot('header', ''));
282
+ renderTestEditable({
283
+ rootName: 'header',
284
+ content: '',
285
+ });
223
286
 
224
287
  const editor = await waitForTestEditor();
225
288
 
@@ -235,9 +298,10 @@ describe('editor component', () => {
235
298
  },
236
299
  });
237
300
 
238
- renderTestEditable(
239
- createEditableSnapshot('header', '<p>Editable content overrides snapshot content</p>'),
240
- );
301
+ renderTestEditable({
302
+ rootName: 'header',
303
+ content: '<p>Editable content overrides snapshot content</p>',
304
+ });
241
305
 
242
306
  const editor = await waitForTestEditor();
243
307
 
@@ -270,14 +334,34 @@ describe('editor component', () => {
270
334
 
271
335
  const editor = await waitForTestEditor<MultiRootEditor>();
272
336
 
273
- editor.addRoot('existingRoot', { data: '<p>Old content</p>' });
337
+ editor.addRoot('existingRoot', { initialData: '<p>Old content</p>' });
274
338
 
275
- renderTestEditable(createEditableSnapshot('existingRoot', '<p>New content</p>'));
339
+ renderTestEditable({
340
+ rootName: 'existingRoot',
341
+ content: '<p>New content</p>',
342
+ });
276
343
 
277
344
  await vi.waitFor(() => {
278
345
  expect(editor.getData({ rootName: 'existingRoot' })).toBe('<p>New content</p>');
279
346
  });
280
347
  });
348
+
349
+ it('should create a multiroot editor with inline editables', async () => {
350
+ renderTestEditor({
351
+ preset: createEditorPreset('multiroot'),
352
+ content: {},
353
+ });
354
+
355
+ renderTestEditable({ rootName: 'second', rootModelElementName: '$inlineRoot' });
356
+ renderTestEditable({ rootName: 'third', rootModelElementName: '$inlineRoot' });
357
+
358
+ const editor = await waitForTestEditor();
359
+
360
+ await vi.waitFor(() => {
361
+ expect(editor.model.document.getRoot('second')?.name).toEqual('$inlineRoot');
362
+ expect(editor.model.document.getRoot('third')?.name).toEqual('$inlineRoot');
363
+ });
364
+ });
281
365
  });
282
366
  });
283
367
 
@@ -537,8 +621,8 @@ describe('editor component', () => {
537
621
 
538
622
  component.addEventListener('ckeditor5:change:data', changeSpy);
539
623
 
540
- renderTestEditable(createEditableSnapshot('header'));
541
- renderTestEditable(createEditableSnapshot('footer'));
624
+ renderTestEditable({ rootName: 'header' });
625
+ renderTestEditable({ rootName: 'footer' });
542
626
 
543
627
  const editor = await waitForTestEditor<MultiRootEditor>();
544
628
 
@@ -1,5 +1,6 @@
1
1
  import type { WaitForInteractiveResult } from '../../shared';
2
2
  import type { EditorId, EditorLanguage, EditorPreset } from './typings';
3
+ import type { EditableItem } from './utils';
3
4
 
4
5
  import {
5
6
  isEmptyObject,
@@ -14,8 +15,7 @@ import {
14
15
  createSyncEditorWithInputPlugin,
15
16
  } from './plugins';
16
17
  import {
17
- assignInitialDataToEditorConfig,
18
- assignSourceElementsToEditorConfig,
18
+ assignEditorRootsToConfig,
19
19
  cleanupOrphanEditorElements,
20
20
  createEditorInContext,
21
21
  isSingleRootEditor,
@@ -24,8 +24,7 @@ import {
24
24
  loadEditorPlugins,
25
25
  normalizeCustomTranslations,
26
26
  normalizeEditorLanguage,
27
- queryEditablesElements,
28
- queryEditablesSnapshotContent,
27
+ queryAllEditorEditables,
29
28
  resolveEditorConfigElementReferences,
30
29
  resolveEditorConfigTranslations,
31
30
  setEditorEditableHeight,
@@ -158,7 +157,6 @@ export class EditorComponentElement extends HTMLElement {
158
157
  const editableHeight = this.getAttribute('data-cke-editable-height') ? Number.parseInt(this.getAttribute('data-cke-editable-height')!, 10) : null;
159
158
  const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
160
159
  const useWatchdog = this.hasAttribute('data-cke-watchdog');
161
- const content = JSON.parse(this.getAttribute('data-cke-content')!) as Record<string, string>;
162
160
  const language = (
163
161
  this.getAttribute('data-cke-language')
164
162
  ? JSON.parse(this.getAttribute('data-cke-language')!)
@@ -200,59 +198,36 @@ export class EditorComponentElement extends HTMLElement {
200
198
  ]
201
199
  .filter(translations => !isEmptyObject(translations));
202
200
 
203
- // Let's query all elements, and create basic configuration.
204
- let initialData: string | Record<string, string> = {
205
- ...content,
206
- ...queryEditablesSnapshotContent(editorId),
207
- };
201
+ // Query all editable elements along with their content in one pass.
202
+ // Roots present in the editor container's data-cke-content but not yet in the DOM
203
+ // are included with element: null so we know which roots to wait for.
204
+ let editables = queryAllEditorEditables(editorId);
205
+ const requiredRoots = Object.keys(editables);
208
206
 
209
207
  if (isSingleRootEditor(editorType)) {
210
- initialData = initialData['main'] || '';
208
+ requiredRoots.push('main');
211
209
  }
212
210
 
213
- // Depending of the editor type, and parent lookup for nearest context or initialize it without it.
214
- const editor = await (async () => {
215
- let sourceElements: HTMLElement | Record<string, HTMLElement> = queryEditablesElements(editorId);
216
-
217
- // Handle special case when user specified `initialData` of several root elements, but editable components
218
- // are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
219
- if (!sourceElements['main']) {
220
- const requiredRoots = (
221
- isSingleRootEditor(editorType)
222
- ? ['main']
223
- : Object.keys(initialData as Record<string, string>)
224
- );
225
-
226
- if (!checkIfAllRootsArePresent(sourceElements, requiredRoots)) {
227
- sourceElements = await waitForAllRootsToBePresent(editorId, requiredRoots);
228
- initialData = {
229
- ...content,
230
- ...queryEditablesSnapshotContent(editorId),
231
- };
232
- }
233
- }
234
-
235
- // If single root editor, unwrap the element from the object.
236
- if (isSingleRootEditor(editorType) && 'main' in sourceElements) {
237
- sourceElements = sourceElements['main'];
238
- }
211
+ if (!checkIfAllRootsArePresent(editables, requiredRoots)) {
212
+ editables = await waitForAllRootsToBePresent(editorId, requiredRoots);
213
+ }
239
214
 
240
- // Do some postprocessing on received configuration.
241
- let resolvedConfig = {
242
- ...config,
243
- licenseKey,
244
- plugins: loadedPlugins,
245
- language,
246
- ...mixedTranslations.length && {
247
- translations: mixedTranslations,
248
- },
249
- };
215
+ // Do some postprocessing on received configuration.
216
+ let resolvedConfig = {
217
+ ...config,
218
+ licenseKey,
219
+ plugins: loadedPlugins,
220
+ language,
221
+ ...mixedTranslations.length && {
222
+ translations: mixedTranslations,
223
+ },
224
+ };
250
225
 
251
- resolvedConfig = resolveEditorConfigElementReferences(resolvedConfig);
252
- resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
253
- resolvedConfig = assignSourceElementsToEditorConfig(Constructor, sourceElements, resolvedConfig);
254
- resolvedConfig = assignInitialDataToEditorConfig(initialData, resolvedConfig);
226
+ resolvedConfig = resolveEditorConfigElementReferences(resolvedConfig);
227
+ resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
228
+ resolvedConfig = assignEditorRootsToConfig(Constructor, editables, resolvedConfig);
255
229
 
230
+ const editor = await (async () => {
256
231
  if (!context) {
257
232
  return Constructor.create(resolvedConfig);
258
233
  }
@@ -313,42 +288,42 @@ export class EditorComponentElement extends HTMLElement {
313
288
  }
314
289
 
315
290
  /**
316
- * Checks if all required root elements are present in the elements object.
291
+ * Checks if all required root elements are present (i.e. have a non-null element) in the editables map.
317
292
  *
318
- * @param elements The elements object mapping root IDs to HTMLElements.
319
- * @param requiredRoots The list of required root IDs.
320
- * @returns True if all required roots are present, false otherwise.
293
+ * @param editables The editables map keyed by root name.
294
+ * @param requiredRoots The list of required root names.
295
+ * @returns True if all required roots have a DOM element attached, false otherwise.
321
296
  */
322
- function checkIfAllRootsArePresent(elements: Record<string, HTMLElement>, requiredRoots: string[]): boolean {
323
- return requiredRoots.every(rootId => elements[rootId]);
297
+ function checkIfAllRootsArePresent(editables: Record<string, EditableItem>, requiredRoots: string[]): boolean {
298
+ return requiredRoots.every(rootId => editables[rootId]?.element);
324
299
  }
325
300
 
326
301
  /**
327
302
  * Waits for all required root elements to be present in the DOM.
328
303
  *
329
304
  * @param editorId The editor's ID.
330
- * @param requiredRoots The list of required root IDs.
331
- * @returns A promise that resolves to the record of root elements.
305
+ * @param requiredRoots The list of required root names.
306
+ * @returns A promise that resolves to the updated editables map once all elements are attached.
332
307
  */
333
308
  async function waitForAllRootsToBePresent(
334
309
  editorId: EditorId,
335
310
  requiredRoots: string[],
336
- ): Promise<Record<string, HTMLElement>> {
311
+ ): Promise<Record<string, EditableItem>> {
337
312
  return waitFor(
338
313
  () => {
339
- const elements = queryEditablesElements(editorId) as unknown as Record<string, HTMLElement>;
314
+ const editables = queryAllEditorEditables(editorId);
340
315
 
341
- if (!checkIfAllRootsArePresent(elements, requiredRoots)) {
316
+ if (!checkIfAllRootsArePresent(editables, requiredRoots)) {
342
317
  throw new Error(
343
318
  'It looks like not all required root elements are present yet.\n'
344
319
  + '* If you want to wait for them, ensure they are registered before editor initialization.\n'
345
320
  + '* If you want lazy initialize roots, consider removing root values from the `initialData` config '
346
321
  + 'and assign initial data in editable components.\n'
347
- + `Missing roots: ${requiredRoots.filter(rootId => !elements[rootId]).join(', ')}.`,
322
+ + `Missing roots: ${requiredRoots.filter(rootId => !editables[rootId]?.element).join(', ')}.`,
348
323
  );
349
324
  }
350
325
 
351
- return elements;
326
+ return editables;
352
327
  },
353
328
  { timeOutAfter: 2000, retryAfter: 100 },
354
329
  );
@@ -0,0 +1,61 @@
1
+ import type { EditorRelaxedConstructor } from '../types/editor-relaxed-constructor.type';
2
+ import type { EditableItem } from './query-all-editor-editables';
3
+ import type { EditorConfig } from 'ckeditor5';
4
+
5
+ /**
6
+ * Assigns DOM elements and initial data to the editor configuration in a way that is compatible
7
+ * with the specific editor type.
8
+ *
9
+ * Roots with `element: null` (pending roots not yet in the DOM) contribute only `initialData`
10
+ * and are skipped for element assignment.
11
+ *
12
+ * @param Editor Constructor of the editor used to determine the location of element config entry.
13
+ * @param editables Map of editable items (element + content) keyed by root name.
14
+ * @param config Config of the editor.
15
+ * @returns The updated configuration object.
16
+ */
17
+ export function assignEditorRootsToConfig<C extends EditorConfig>(
18
+ Editor: EditorRelaxedConstructor,
19
+ editables: Record<string, EditableItem>,
20
+ config: C,
21
+ ): C {
22
+ const isClassicEditor = !Editor.editorName || Editor.editorName === 'ClassicEditor';
23
+ const allRootsKeys = new Set([
24
+ ...Object.keys(editables),
25
+ ...Object.keys(config.roots ?? {}),
26
+ ]);
27
+
28
+ const rootsConfig = Array.from(allRootsKeys).reduce((acc, rootKey) => ({
29
+ ...acc,
30
+ [rootKey]: {
31
+ /* v8 ignore next 1 */
32
+ ...config.roots?.[rootKey],
33
+ ...rootKey === 'main' ? config.root : {},
34
+
35
+ /* v8 ignore next 12 */
36
+ ...rootKey in editables
37
+ ? {
38
+ ...editables[rootKey]!.content !== null && {
39
+ initialData: editables[rootKey]!.content,
40
+ },
41
+ modelElement: editables[rootKey]!.modelElement || '$root',
42
+ ...!isClassicEditor && editables[rootKey]!.element !== null && {
43
+ element: editables[rootKey]!.element,
44
+ },
45
+ }
46
+ : {},
47
+ },
48
+ }), { ...config.roots || {} });
49
+
50
+ const mappedConfig: C = {
51
+ ...config,
52
+ roots: rootsConfig,
53
+ ...isClassicEditor && editables['main']?.element && {
54
+ attachTo: editables['main'].element,
55
+ },
56
+ };
57
+
58
+ delete mappedConfig.root;
59
+
60
+ return mappedConfig;
61
+ }
@@ -17,5 +17,5 @@ export function getEditorRootsValues(editor: Editor): Record<string, string> {
17
17
  acc[root.rootName] = editor.getData({ rootName: root.rootName });
18
18
 
19
19
  return acc;
20
- }, Object.create({}));
20
+ }, Object.create(null));
21
21
  }
@@ -1,5 +1,4 @@
1
- export * from './assign-initial-data-to-editor-config';
2
- export * from './assign-source-elements-to-editor-config';
1
+ export * from './assign-editor-roots-to-config';
3
2
  export * from './cleanup-orphan-editor-elements';
4
3
  export * from './create-editor-in-context';
5
4
  export * from './get-editor-roots-values';
@@ -9,8 +8,8 @@ export * from './load-editor-plugins';
9
8
  export * from './load-editor-translations';
10
9
  export * from './normalize-custom-translations';
11
10
  export * from './normalize-editor-language';
11
+ export * from './query-all-editor-editables';
12
12
  export * from './query-all-editor-ids';
13
- export * from './query-editor-editables';
14
13
  export * from './resolve-editor-config-elements-references';
15
14
  export * from './resolve-editor-config-translations';
16
15
  export * from './set-editor-editable-height';
@@ -0,0 +1,81 @@
1
+ import type { EditorId } from '../typings';
2
+
3
+ /**
4
+ * Queries all editable elements within a specific editor instance. It picks
5
+ * initial values from actually rendered elements or from the editor container's
6
+ * `data-cke-content` attribute, whichever is available.
7
+ *
8
+ * Roots present in the editor container's content but without a matching
9
+ * `cke5-editable` element yet are included with `element: null` — these are
10
+ * "pending" roots that haven't been attached to the DOM yet.
11
+ *
12
+ * @param editorId The ID of the editor to query.
13
+ * @returns An object mapping root names to their corresponding elements and content.
14
+ */
15
+ export function queryAllEditorEditables(editorId: EditorId): Record<string, EditableItem> {
16
+ const acc = Array
17
+ .from(document.querySelectorAll<HTMLElement>(`cke5-editable[data-cke-editor-id="${editorId}"]`))
18
+ .reduce<Record<string, EditableItem>>((acc, element) => {
19
+ const rootName = element.getAttribute('data-cke-root-name')!;
20
+ const modelElement = element.getAttribute('data-cke-root-model-element-name') || null;
21
+
22
+ acc[rootName] = {
23
+ element: element.querySelector<HTMLElement>('[data-cke-editable-content]'),
24
+ content: element.getAttribute('data-cke-content'),
25
+ modelElement,
26
+ };
27
+
28
+ return acc;
29
+ }, Object.create(null));
30
+
31
+ const rootEditorElement = document.querySelector<HTMLElement>(`cke5-editor[data-cke-editor-id="${editorId}"]`);
32
+
33
+ /* v8 ignore next -- @preserve */
34
+ if (!rootEditorElement) {
35
+ return acc;
36
+ }
37
+
38
+ // v8 ignore next -- @preserve
39
+ const editorContent: Record<string, string> = JSON.parse(rootEditorElement.getAttribute('data-cke-content')!) ?? {};
40
+ const classicMainElement = document.querySelector<HTMLElement>(`#${editorId}_editor`);
41
+ const rootEditorModelElement = rootEditorElement.getAttribute('data-cke-root-model-element-name');
42
+
43
+ if (classicMainElement && !acc['main']) {
44
+ acc['main'] = {
45
+ element: classicMainElement,
46
+ content: editorContent['main'] || '',
47
+ modelElement: rootEditorModelElement,
48
+ };
49
+ }
50
+
51
+ for (const [rootName, rootContent] of Object.entries(editorContent)) {
52
+ if (acc[rootName]) {
53
+ // Editable element is already present — fill content from editor level if the editable didn't provide its own.
54
+ acc[rootName] = {
55
+ ...acc[rootName],
56
+ content: acc[rootName].content ?? rootContent,
57
+ modelElement: acc[rootName].modelElement ?? rootEditorModelElement,
58
+ };
59
+ }
60
+ else {
61
+ // Root has server-provided content but its editable element hasn't been attached to the DOM yet.
62
+ acc[rootName] = {
63
+ element: null,
64
+ content: rootContent,
65
+ modelElement: rootEditorModelElement,
66
+ };
67
+ }
68
+ }
69
+
70
+ return acc;
71
+ }
72
+
73
+ /**
74
+ * Type representing an editable item within an editor.
75
+ * `element` is null when the root's DOM element hasn't appeared yet (pending root).
76
+ */
77
+ export type EditableItem = {
78
+ element: HTMLElement | null;
79
+ content: string | null;
80
+ modelElement: string | null;
81
+ };