ckeditor5-blazor 1.10.3 → 1.11.1

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 (36) hide show
  1. package/dist/elements/context/context.d.ts.map +1 -1
  2. package/dist/elements/editor/editor.d.ts.map +1 -1
  3. package/dist/elements/editor/types/editor-relaxed-constructor.type.d.ts +6 -0
  4. package/dist/elements/editor/types/editor-relaxed-constructor.type.d.ts.map +1 -0
  5. package/dist/elements/editor/types/index.d.ts +2 -0
  6. package/dist/elements/editor/types/index.d.ts.map +1 -0
  7. package/dist/elements/editor/utils/assign-editor-roots-to-config.d.ts +17 -0
  8. package/dist/elements/editor/utils/assign-editor-roots-to-config.d.ts.map +1 -0
  9. package/dist/elements/editor/utils/create-editor-in-context.d.ts +1 -3
  10. package/dist/elements/editor/utils/create-editor-in-context.d.ts.map +1 -1
  11. package/dist/elements/editor/utils/index.d.ts +3 -1
  12. package/dist/elements/editor/utils/index.d.ts.map +1 -1
  13. package/dist/elements/editor/utils/normalize-editor-language.d.ts +9 -0
  14. package/dist/elements/editor/utils/normalize-editor-language.d.ts.map +1 -0
  15. package/dist/elements/editor/utils/query-all-editor-editables.d.ts +23 -0
  16. package/dist/elements/editor/utils/query-all-editor-editables.d.ts.map +1 -0
  17. package/dist/index.cjs +2 -2
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.mjs +396 -387
  20. package/dist/index.mjs.map +1 -1
  21. package/package.json +3 -3
  22. package/src/elements/context/context.test.ts +107 -66
  23. package/src/elements/context/context.ts +9 -3
  24. package/src/elements/editable.ts +2 -2
  25. package/src/elements/editor/editor.ts +55 -76
  26. package/src/elements/editor/types/editor-relaxed-constructor.type.ts +6 -0
  27. package/src/elements/editor/types/index.ts +1 -0
  28. package/src/elements/editor/utils/assign-editor-roots-to-config.ts +60 -0
  29. package/src/elements/editor/utils/create-editor-in-context.ts +2 -5
  30. package/src/elements/editor/utils/index.ts +3 -1
  31. package/src/elements/editor/utils/normalize-editor-language.test.ts +50 -0
  32. package/src/elements/editor/utils/normalize-editor-language.ts +25 -0
  33. package/src/elements/editor/utils/query-all-editor-editables.ts +74 -0
  34. package/dist/elements/editor/utils/query-editor-editables.d.ts +0 -25
  35. package/dist/elements/editor/utils/query-editor-editables.d.ts.map +0 -1
  36. 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.10.3",
3
+ "version": "1.11.1",
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": "^47.6.0",
32
- "ckeditor5-premium-features": "^47.6.0",
31
+ "ckeditor5": "^48.1.0",
32
+ "ckeditor5-premium-features": "^48.1.0",
33
33
  "happy-dom": "^20.8.9",
34
34
  "typescript": "^5.9.3",
35
35
  "vite": "^7.3.2",
@@ -84,102 +84,143 @@ describe('context component', () => {
84
84
  expect(context?.plugins.get('CustomPlugin')).toBeInstanceOf(CustomPlugin);
85
85
  });
86
86
 
87
- it('registered plugins should support custom translations', async () => {
88
- class CustomPlugin extends ContextPlugin {
89
- static get pluginName() {
90
- return 'CustomPlugin';
87
+ describe('language param', () => {
88
+ it('should support passing null language for context translations', async () => {
89
+ class CustomPlugin extends ContextPlugin {
90
+ static get pluginName() {
91
+ return 'CustomPlugin';
92
+ }
93
+
94
+ getHelloTitle() {
95
+ return this.context.t('HELLO');
96
+ }
91
97
  }
92
98
 
93
- getHelloTitle() {
94
- return this.context.t('HELLO');
95
- }
96
- }
99
+ CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
100
+
101
+ renderTestContext(createContextSnapshot(
102
+ DEFAULT_TEST_CONTEXT_ID,
103
+ {
104
+ customTranslations: {
105
+ en: {
106
+ HELLO: 'Hello from CustomPlugin',
107
+ },
108
+ pl: {
109
+ HELLO: 'Witaj z CustomPlugin',
110
+ },
111
+ },
112
+ config: {
113
+ plugins: ['CustomPlugin'],
114
+ },
115
+ },
116
+ null,
117
+ ));
97
118
 
98
- CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
119
+ const { context } = await waitForTestContext();
120
+ const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
99
121
 
100
- renderTestContext(createContextSnapshot(DEFAULT_TEST_CONTEXT_ID, {
101
- customTranslations: {
102
- en: {
103
- HELLO: 'Hello from CustomPlugin',
122
+ expect(plugin.getHelloTitle()).toBe('Hello from CustomPlugin');
123
+ });
124
+
125
+ it('should support custom language for context translations', async () => {
126
+ class CustomPlugin extends ContextPlugin {
127
+ static get pluginName() {
128
+ return 'CustomPlugin';
129
+ }
130
+
131
+ getHelloTitle() {
132
+ return this.context.t('HELLO');
133
+ }
134
+ }
135
+
136
+ CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
137
+
138
+ renderTestContext(createContextSnapshot(
139
+ DEFAULT_TEST_CONTEXT_ID,
140
+ {
141
+ customTranslations: {
142
+ en: {
143
+ HELLO: 'Hello from CustomPlugin',
144
+ },
145
+ pl: {
146
+ HELLO: 'Witaj z CustomPlugin',
147
+ },
148
+ },
149
+ config: {
150
+ plugins: ['CustomPlugin'],
151
+ },
104
152
  },
105
- },
106
- config: {
107
- plugins: ['CustomPlugin'],
108
- },
109
- }));
153
+ {
154
+ ui: 'pl',
155
+ content: 'pl',
156
+ },
157
+ ));
110
158
 
111
- const { context } = await waitForTestContext();
112
- const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
159
+ const { context } = await waitForTestContext();
160
+ const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
113
161
 
114
- expect(plugin.getHelloTitle()).toBe('Hello from CustomPlugin');
162
+ expect(plugin.getHelloTitle()).toBe('Witaj z CustomPlugin');
163
+ });
115
164
  });
116
165
 
117
- it('should support custom language for context translations', async () => {
118
- class CustomPlugin extends ContextPlugin {
119
- static get pluginName() {
120
- return 'CustomPlugin';
121
- }
166
+ describe('translations', () => {
167
+ it('registered plugins should support custom translations', async () => {
168
+ class CustomPlugin extends ContextPlugin {
169
+ static get pluginName() {
170
+ return 'CustomPlugin';
171
+ }
122
172
 
123
- getHelloTitle() {
124
- return this.context.t('HELLO');
173
+ getHelloTitle() {
174
+ return this.context.t('HELLO');
175
+ }
125
176
  }
126
- }
127
177
 
128
- CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
178
+ CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
129
179
 
130
- renderTestContext(createContextSnapshot(
131
- DEFAULT_TEST_CONTEXT_ID,
132
- {
180
+ renderTestContext(createContextSnapshot(DEFAULT_TEST_CONTEXT_ID, {
133
181
  customTranslations: {
134
182
  en: {
135
183
  HELLO: 'Hello from CustomPlugin',
136
184
  },
137
- pl: {
138
- HELLO: 'Witaj z CustomPlugin',
139
- },
140
185
  },
141
186
  config: {
142
187
  plugins: ['CustomPlugin'],
143
188
  },
144
- },
145
- {
146
- ui: 'pl',
147
- content: 'pl',
148
- },
149
- ));
189
+ }));
150
190
 
151
- const { context } = await waitForTestContext();
152
- const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
191
+ const { context } = await waitForTestContext();
192
+ const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
153
193
 
154
- expect(plugin.getHelloTitle()).toBe('Witaj z CustomPlugin');
155
- });
194
+ expect(plugin.getHelloTitle()).toBe('Hello from CustomPlugin');
195
+ });
156
196
 
157
- it('should support translations references in config', async () => {
158
- renderTestContext(createContextSnapshot(
159
- DEFAULT_TEST_CONTEXT_ID,
160
- {
161
- customTranslations: {
162
- en: {
163
- HELLO: 'Hello from CustomPlugin',
197
+ it('should support translations references in config', async () => {
198
+ renderTestContext(createContextSnapshot(
199
+ DEFAULT_TEST_CONTEXT_ID,
200
+ {
201
+ customTranslations: {
202
+ en: {
203
+ HELLO: 'Hello from CustomPlugin',
204
+ },
205
+ pl: {
206
+ HELLO: 'Witaj z CustomPlugin',
207
+ },
164
208
  },
165
- pl: {
166
- HELLO: 'Witaj z CustomPlugin',
209
+ config: {
210
+ customConfig: { $translation: 'HELLO' },
167
211
  },
168
212
  },
169
- config: {
170
- customConfig: { $translation: 'HELLO' },
213
+ {
214
+ ui: 'pl',
215
+ content: 'pl',
171
216
  },
172
- },
173
- {
174
- ui: 'pl',
175
- content: 'pl',
176
- },
177
- ));
217
+ ));
178
218
 
179
- const { context } = await waitForTestContext();
180
- const translation = context?.config.get('customConfig') as string;
219
+ const { context } = await waitForTestContext();
220
+ const translation = context?.config.get('customConfig') as string;
181
221
 
182
- expect(translation).toBe('Witaj z CustomPlugin');
222
+ expect(translation).toBe('Witaj z CustomPlugin');
223
+ });
183
224
  });
184
225
  });
185
226
 
@@ -8,6 +8,7 @@ import {
8
8
  loadAllEditorTranslations,
9
9
  loadEditorPlugins,
10
10
  normalizeCustomTranslations,
11
+ normalizeEditorLanguage,
11
12
  resolveEditorConfigElementReferences,
12
13
  resolveEditorConfigTranslations,
13
14
  } from '../editor/utils';
@@ -46,11 +47,16 @@ export class ContextComponentElement extends HTMLElement {
46
47
  * Initializes the context component.
47
48
  */
48
49
  private async initializeContext(): Promise<void> {
49
- const contextId = this.getAttribute('data-cke-context-id')!;
50
- const language = JSON.parse(this.getAttribute('data-cke-language')!) as EditorLanguage;
51
50
  const contextConfig = JSON.parse(this.getAttribute('data-cke-context')!) as ContextConfig;
52
-
53
51
  const { customTranslations, watchdogConfig, config: { plugins, ...config } } = contextConfig;
52
+
53
+ const contextId = this.getAttribute('data-cke-context-id')!;
54
+ const language = (
55
+ this.getAttribute('data-cke-language')
56
+ ? JSON.parse(this.getAttribute('data-cke-language')!)
57
+ : normalizeEditorLanguage(config['language'])
58
+ ) as EditorLanguage;
59
+
54
60
  const { loadedPlugins, hasPremium } = await loadEditorPlugins(plugins ?? []);
55
61
 
56
62
  // Mix custom translations with loaded translations.
@@ -88,9 +88,9 @@ export class EditableComponentElement extends HTMLElement {
88
88
 
89
89
  editor.addRoot(rootName, {
90
90
  isUndoable: false,
91
- attributes: { ...rootAttributes },
91
+ modelAttributes: { ...rootAttributes },
92
92
  ...content !== null && {
93
- data: content,
93
+ initialData: content,
94
94
  },
95
95
  });
96
96
 
@@ -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,6 +15,7 @@ import {
14
15
  createSyncEditorWithInputPlugin,
15
16
  } from './plugins';
16
17
  import {
18
+ assignEditorRootsToConfig,
17
19
  cleanupOrphanEditorElements,
18
20
  createEditorInContext,
19
21
  isSingleRootEditor,
@@ -21,8 +23,8 @@ import {
21
23
  loadEditorConstructor,
22
24
  loadEditorPlugins,
23
25
  normalizeCustomTranslations,
24
- queryEditablesElements,
25
- queryEditablesSnapshotContent,
26
+ normalizeEditorLanguage,
27
+ queryAllEditorEditables,
26
28
  resolveEditorConfigElementReferences,
27
29
  resolveEditorConfigTranslations,
28
30
  setEditorEditableHeight,
@@ -140,16 +142,7 @@ export class EditorComponentElement extends HTMLElement {
140
142
  * Creates the CKEditor instance.
141
143
  */
142
144
  private async createEditor() {
143
- const editorId = this.getAttribute('data-cke-editor-id')!;
144
145
  const preset = JSON.parse(this.getAttribute('data-cke-preset')!) as EditorPreset;
145
- const contextId = this.getAttribute('data-cke-context-id');
146
- const rootAttributes = JSON.parse(this.getAttribute('data-cke-root-attributes') || '{}');
147
- const editableHeight = this.getAttribute('data-cke-editable-height') ? Number.parseInt(this.getAttribute('data-cke-editable-height')!, 10) : null;
148
- const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
149
- const language = JSON.parse(this.getAttribute('data-cke-language')!) as EditorLanguage;
150
- const useWatchdog = this.hasAttribute('data-cke-watchdog');
151
- const content = JSON.parse(this.getAttribute('data-cke-content')!) as Record<string, string>;
152
-
153
146
  const {
154
147
  customTranslations,
155
148
  editorType,
@@ -158,6 +151,18 @@ export class EditorComponentElement extends HTMLElement {
158
151
  config: { plugins, ...config },
159
152
  } = preset;
160
153
 
154
+ const editorId = this.getAttribute('data-cke-editor-id')!;
155
+ const contextId = this.getAttribute('data-cke-context-id');
156
+ const rootAttributes = JSON.parse(this.getAttribute('data-cke-root-attributes') || '{}');
157
+ const editableHeight = this.getAttribute('data-cke-editable-height') ? Number.parseInt(this.getAttribute('data-cke-editable-height')!, 10) : null;
158
+ const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
159
+ const useWatchdog = this.hasAttribute('data-cke-watchdog');
160
+ const language = (
161
+ this.getAttribute('data-cke-language')
162
+ ? JSON.parse(this.getAttribute('data-cke-language')!)
163
+ : normalizeEditorLanguage(preset.config['language'])
164
+ ) as EditorLanguage;
165
+
161
166
  const Constructor = await loadEditorConstructor(editorType);
162
167
  const context = await (
163
168
  contextId
@@ -193,70 +198,44 @@ export class EditorComponentElement extends HTMLElement {
193
198
  ]
194
199
  .filter(translations => !isEmptyObject(translations));
195
200
 
196
- // Let's query all elements, and create basic configuration.
197
- let initialData: string | Record<string, string> = {
198
- ...content,
199
- ...queryEditablesSnapshotContent(editorId),
200
- };
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);
201
206
 
202
207
  if (isSingleRootEditor(editorType)) {
203
- initialData = initialData['main'] || '';
208
+ requiredRoots.push('main');
204
209
  }
205
210
 
206
- // Depending of the editor type, and parent lookup for nearest context or initialize it without it.
207
- const editor = await (async () => {
208
- let sourceElementOrData: HTMLElement | Record<string, HTMLElement> = queryEditablesElements(editorId);
209
-
210
- // Handle special case when user specified `initialData` of several root elements, but editable components
211
- // are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
212
- if (!sourceElementOrData['main']) {
213
- const requiredRoots = (
214
- isSingleRootEditor(editorType)
215
- ? ['main']
216
- : Object.keys(initialData as Record<string, string>)
217
- );
218
-
219
- if (!checkIfAllRootsArePresent(sourceElementOrData, requiredRoots)) {
220
- sourceElementOrData = await waitForAllRootsToBePresent(editorId, requiredRoots);
221
- initialData = {
222
- ...content,
223
- ...queryEditablesSnapshotContent(editorId),
224
- };
225
- }
226
- }
211
+ if (!checkIfAllRootsArePresent(editables, requiredRoots)) {
212
+ editables = await waitForAllRootsToBePresent(editorId, requiredRoots);
213
+ }
227
214
 
228
- // If single root editor, unwrap the element from the object.
229
- if (isSingleRootEditor(editorType) && 'main' in sourceElementOrData) {
230
- sourceElementOrData = sourceElementOrData['main'];
231
- }
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
+ };
232
225
 
233
- // Construct parsed config. First resolve DOM element references in the provided configuration.
234
- let resolvedConfig = resolveEditorConfigElementReferences(config);
235
-
236
- // Then resolve translation references in the provided configuration, using the mixed translations.
237
- resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
238
-
239
- // Construct parsed config.
240
- const parsedConfig = {
241
- ...resolvedConfig,
242
- initialData,
243
- licenseKey,
244
- plugins: loadedPlugins,
245
- language,
246
- ...mixedTranslations.length && {
247
- translations: mixedTranslations,
248
- },
249
- };
226
+ resolvedConfig = resolveEditorConfigElementReferences(resolvedConfig);
227
+ resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
228
+ resolvedConfig = assignEditorRootsToConfig(Constructor, editables, resolvedConfig);
250
229
 
251
- if (!context || !(sourceElementOrData instanceof HTMLElement)) {
252
- return Constructor.create(sourceElementOrData as any, parsedConfig);
230
+ const editor = await (async () => {
231
+ if (!context) {
232
+ return Constructor.create(resolvedConfig);
253
233
  }
254
234
 
255
235
  const result = await createEditorInContext({
256
236
  context,
257
- element: sourceElementOrData,
258
237
  creator: Constructor,
259
- config: parsedConfig,
238
+ config: resolvedConfig,
260
239
  });
261
240
 
262
241
  return result.editor;
@@ -309,42 +288,42 @@ export class EditorComponentElement extends HTMLElement {
309
288
  }
310
289
 
311
290
  /**
312
- * 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.
313
292
  *
314
- * @param elements The elements object mapping root IDs to HTMLElements.
315
- * @param requiredRoots The list of required root IDs.
316
- * @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.
317
296
  */
318
- function checkIfAllRootsArePresent(elements: Record<string, HTMLElement>, requiredRoots: string[]): boolean {
319
- return requiredRoots.every(rootId => elements[rootId]);
297
+ function checkIfAllRootsArePresent(editables: Record<string, EditableItem>, requiredRoots: string[]): boolean {
298
+ return requiredRoots.every(rootId => editables[rootId]?.element);
320
299
  }
321
300
 
322
301
  /**
323
302
  * Waits for all required root elements to be present in the DOM.
324
303
  *
325
304
  * @param editorId The editor's ID.
326
- * @param requiredRoots The list of required root IDs.
327
- * @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.
328
307
  */
329
308
  async function waitForAllRootsToBePresent(
330
309
  editorId: EditorId,
331
310
  requiredRoots: string[],
332
- ): Promise<Record<string, HTMLElement>> {
311
+ ): Promise<Record<string, EditableItem>> {
333
312
  return waitFor(
334
313
  () => {
335
- const elements = queryEditablesElements(editorId) as unknown as Record<string, HTMLElement>;
314
+ const editables = queryAllEditorEditables(editorId);
336
315
 
337
- if (!checkIfAllRootsArePresent(elements, requiredRoots)) {
316
+ if (!checkIfAllRootsArePresent(editables, requiredRoots)) {
338
317
  throw new Error(
339
318
  'It looks like not all required root elements are present yet.\n'
340
319
  + '* If you want to wait for them, ensure they are registered before editor initialization.\n'
341
320
  + '* If you want lazy initialize roots, consider removing root values from the `initialData` config '
342
321
  + 'and assign initial data in editable components.\n'
343
- + `Missing roots: ${requiredRoots.filter(rootId => !elements[rootId]).join(', ')}.`,
322
+ + `Missing roots: ${requiredRoots.filter(rootId => !editables[rootId]?.element).join(', ')}.`,
344
323
  );
345
324
  }
346
325
 
347
- return elements;
326
+ return editables;
348
327
  },
349
328
  { timeOutAfter: 2000, retryAfter: 100 },
350
329
  );
@@ -0,0 +1,6 @@
1
+ import type { Editor } from 'ckeditor5';
2
+
3
+ export type EditorRelaxedConstructor<TEditor extends Editor = Editor> = {
4
+ create: (...args: any) => Promise<TEditor>;
5
+ editorName?: string;
6
+ };
@@ -0,0 +1 @@
1
+ export * from './editor-relaxed-constructor.type';
@@ -0,0 +1,60 @@
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 11 */
36
+ ...rootKey in editables
37
+ ? {
38
+ ...editables[rootKey]!.content !== null && {
39
+ initialData: editables[rootKey]!.content,
40
+ },
41
+ ...!isClassicEditor && editables[rootKey]!.element !== null && {
42
+ element: editables[rootKey]!.element,
43
+ },
44
+ }
45
+ : {},
46
+ },
47
+ }), Object.create(config.roots || {}));
48
+
49
+ const mappedConfig: C = {
50
+ ...config,
51
+ roots: rootsConfig,
52
+ ...isClassicEditor && editables['main']?.element && {
53
+ attachTo: editables['main'].element,
54
+ },
55
+ };
56
+
57
+ delete mappedConfig.root;
58
+
59
+ return mappedConfig;
60
+ }
@@ -12,19 +12,17 @@ const CONTEXT_EDITOR_WATCHDOG_SYMBOL = Symbol.for('context-editor-watchdog');
12
12
  * Creates a CKEditor 5 editor instance within a given context watchdog.
13
13
  *
14
14
  * @param params Parameters for editor creation.
15
- * @param params.element The DOM element or data for the editor.
16
15
  * @param params.context The context watchdog instance.
17
16
  * @param params.creator The editor creator utility.
18
17
  * @param params.config The editor configuration object.
19
18
  * @returns The created editor instance.
20
19
  */
21
- export async function createEditorInContext({ element, context, creator, config }: Attrs) {
20
+ export async function createEditorInContext({ context, creator, config }: Attrs) {
22
21
  const editorContextId = uid();
23
22
 
24
23
  await context.add({
25
- creator: (_element, _config) => creator.create(_element, _config),
24
+ creator: creator.create.bind(creator),
26
25
  id: editorContextId,
27
- sourceElementOrData: element,
28
26
  type: 'editor',
29
27
  config,
30
28
  });
@@ -74,7 +72,6 @@ export function unwrapEditorContext(editor: Editor): EditorContextDescriptor | n
74
72
  type Attrs = {
75
73
  context: ContextWatchdog<Context>;
76
74
  creator: EditorCreator;
77
- element: HTMLElement;
78
75
  config: EditorConfig;
79
76
  };
80
77
 
@@ -1,3 +1,4 @@
1
+ export * from './assign-editor-roots-to-config';
1
2
  export * from './cleanup-orphan-editor-elements';
2
3
  export * from './create-editor-in-context';
3
4
  export * from './get-editor-roots-values';
@@ -6,8 +7,9 @@ export * from './load-editor-constructor';
6
7
  export * from './load-editor-plugins';
7
8
  export * from './load-editor-translations';
8
9
  export * from './normalize-custom-translations';
10
+ export * from './normalize-editor-language';
11
+ export * from './query-all-editor-editables';
9
12
  export * from './query-all-editor-ids';
10
- export * from './query-editor-editables';
11
13
  export * from './resolve-editor-config-elements-references';
12
14
  export * from './resolve-editor-config-translations';
13
15
  export * from './set-editor-editable-height';
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { normalizeEditorLanguage } from './normalize-editor-language';
4
+
5
+ describe('normalizeEditorLanguage', () => {
6
+ describe('when lang is falsy', () => {
7
+ it('returns default object with "en" for empty string', () => {
8
+ expect(normalizeEditorLanguage('')).toEqual({ ui: 'en', content: 'en' });
9
+ });
10
+
11
+ it('returns default object with "en" for null', () => {
12
+ expect(normalizeEditorLanguage(null as any)).toEqual({ ui: 'en', content: 'en' });
13
+ });
14
+
15
+ it('returns default object with "en" for undefined', () => {
16
+ expect(normalizeEditorLanguage(undefined as any)).toEqual({ ui: 'en', content: 'en' });
17
+ });
18
+ });
19
+
20
+ describe('when lang is a string', () => {
21
+ it('returns object with ui and content set to the given language', () => {
22
+ expect(normalizeEditorLanguage('pl')).toEqual({ ui: 'pl', content: 'pl' });
23
+ });
24
+
25
+ it('handles "en" language code correctly', () => {
26
+ expect(normalizeEditorLanguage('en')).toEqual({ ui: 'en', content: 'en' });
27
+ });
28
+
29
+ it('handles "de" language code correctly', () => {
30
+ expect(normalizeEditorLanguage('de')).toEqual({ ui: 'de', content: 'de' });
31
+ });
32
+ });
33
+
34
+ describe('when lang is an object', () => {
35
+ it('returns the object unchanged when ui and content are the same', () => {
36
+ const lang = { ui: 'pl', content: 'pl' };
37
+ expect(normalizeEditorLanguage(lang)).toEqual(lang);
38
+ });
39
+
40
+ it('returns the object unchanged when ui and content differ', () => {
41
+ const lang = { ui: 'pl', content: 'en' };
42
+ expect(normalizeEditorLanguage(lang)).toEqual(lang);
43
+ });
44
+
45
+ it('returns the exact same object reference', () => {
46
+ const lang = { ui: 'fr', content: 'en' };
47
+ expect(normalizeEditorLanguage(lang)).toBe(lang);
48
+ });
49
+ });
50
+ });