ckeditor5-blazor 1.11.1 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ckeditor5-blazor",
3
- "version": "1.11.1",
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
 
@@ -32,19 +32,20 @@ export function assignEditorRootsToConfig<C extends EditorConfig>(
32
32
  ...config.roots?.[rootKey],
33
33
  ...rootKey === 'main' ? config.root : {},
34
34
 
35
- /* v8 ignore next 11 */
35
+ /* v8 ignore next 12 */
36
36
  ...rootKey in editables
37
37
  ? {
38
38
  ...editables[rootKey]!.content !== null && {
39
39
  initialData: editables[rootKey]!.content,
40
40
  },
41
+ modelElement: editables[rootKey]!.modelElement || '$root',
41
42
  ...!isClassicEditor && editables[rootKey]!.element !== null && {
42
43
  element: editables[rootKey]!.element,
43
44
  },
44
45
  }
45
46
  : {},
46
47
  },
47
- }), Object.create(config.roots || {}));
48
+ }), { ...config.roots || {} });
48
49
 
49
50
  const mappedConfig: C = {
50
51
  ...config,
@@ -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
  }
@@ -17,14 +17,16 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
17
17
  .from(document.querySelectorAll<HTMLElement>(`cke5-editable[data-cke-editor-id="${editorId}"]`))
18
18
  .reduce<Record<string, EditableItem>>((acc, element) => {
19
19
  const rootName = element.getAttribute('data-cke-root-name')!;
20
+ const modelElement = element.getAttribute('data-cke-root-model-element-name') || null;
20
21
 
21
22
  acc[rootName] = {
22
23
  element: element.querySelector<HTMLElement>('[data-cke-editable-content]'),
23
24
  content: element.getAttribute('data-cke-content'),
25
+ modelElement,
24
26
  };
25
27
 
26
28
  return acc;
27
- }, Object.create({}));
29
+ }, Object.create(null));
28
30
 
29
31
  const rootEditorElement = document.querySelector<HTMLElement>(`cke5-editor[data-cke-editor-id="${editorId}"]`);
30
32
 
@@ -36,11 +38,13 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
36
38
  // v8 ignore next -- @preserve
37
39
  const editorContent: Record<string, string> = JSON.parse(rootEditorElement.getAttribute('data-cke-content')!) ?? {};
38
40
  const classicMainElement = document.querySelector<HTMLElement>(`#${editorId}_editor`);
41
+ const rootEditorModelElement = rootEditorElement.getAttribute('data-cke-root-model-element-name');
39
42
 
40
43
  if (classicMainElement && !acc['main']) {
41
44
  acc['main'] = {
42
45
  element: classicMainElement,
43
46
  content: editorContent['main'] || '',
47
+ modelElement: rootEditorModelElement,
44
48
  };
45
49
  }
46
50
 
@@ -50,6 +54,7 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
50
54
  acc[rootName] = {
51
55
  ...acc[rootName],
52
56
  content: acc[rootName].content ?? rootContent,
57
+ modelElement: acc[rootName].modelElement ?? rootEditorModelElement,
53
58
  };
54
59
  }
55
60
  else {
@@ -57,6 +62,7 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
57
62
  acc[rootName] = {
58
63
  element: null,
59
64
  content: rootContent,
65
+ modelElement: rootEditorModelElement,
60
66
  };
61
67
  }
62
68
  }
@@ -71,4 +77,5 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
71
77
  export type EditableItem = {
72
78
  element: HTMLElement | null;
73
79
  content: string | null;
80
+ modelElement: string | null;
74
81
  };
@@ -2,7 +2,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import {
4
4
  createEditorPreset,
5
- createUIPartSnapshot,
6
5
  renderTestEditor,
7
6
  renderTestUIPart,
8
7
  waitForDestroyAllEditors,
@@ -34,7 +33,7 @@ describe('ui-part component', () => {
34
33
  const editor = await waitForTestEditor();
35
34
  const toolbarElement = editor.ui.view.toolbar?.element;
36
35
 
37
- const el = renderTestUIPart(createUIPartSnapshot('toolbar'));
36
+ const el = renderTestUIPart();
38
37
 
39
38
  await vi.waitFor(() => {
40
39
  expect(el.contains(toolbarElement!)).toBe(true);
@@ -49,7 +48,7 @@ describe('ui-part component', () => {
49
48
  const editor = await waitForTestEditor();
50
49
  const menubarElement = (editor.ui.view as any).menuBarView.element;
51
50
 
52
- const el = renderTestUIPart(createUIPartSnapshot('menubar'));
51
+ const el = renderTestUIPart({ name: 'menubar' });
53
52
 
54
53
  await vi.waitFor(() => {
55
54
  expect(el.children.length).toBeGreaterThan(0);
@@ -59,7 +58,7 @@ describe('ui-part component', () => {
59
58
  });
60
59
 
61
60
  it('should mount UI part before editor is created', async () => {
62
- const el = renderTestUIPart(createUIPartSnapshot('toolbar'));
61
+ const el = renderTestUIPart();
63
62
 
64
63
  appendMultirootEditor();
65
64
 
@@ -80,7 +79,7 @@ describe('ui-part component', () => {
80
79
  const toolbarElement = editor.ui.view.toolbar?.element;
81
80
 
82
81
  // Render UI part without editorId
83
- const el = renderTestUIPart({ editorId: undefined } as any);
82
+ const el = renderTestUIPart({ editorId: null });
84
83
 
85
84
  await vi.waitFor(() => {
86
85
  expect(el.contains(toolbarElement!)).toBe(true);
@@ -90,7 +89,7 @@ describe('ui-part component', () => {
90
89
  });
91
90
 
92
91
  it('should not mount UI part if element is disconnected before editor is ready', async () => {
93
- const el = renderTestUIPart(createUIPartSnapshot('toolbar'));
92
+ const el = renderTestUIPart();
94
93
  el.remove();
95
94
 
96
95
  appendMultirootEditor();
@@ -109,7 +108,7 @@ describe('ui-part component', () => {
109
108
  });
110
109
 
111
110
  it('should clear UI part element on destruction', async () => {
112
- const el = renderTestUIPart(createUIPartSnapshot('toolbar'));
111
+ const el = renderTestUIPart();
113
112
 
114
113
  await vi.waitFor(() => {
115
114
  expect(el.children.length).toBeGreaterThan(0);
@@ -125,7 +124,7 @@ describe('ui-part component', () => {
125
124
  });
126
125
 
127
126
  it('should hide element during destruction', async () => {
128
- const el = renderTestUIPart(createUIPartSnapshot('toolbar'));
127
+ const el = renderTestUIPart();
129
128
 
130
129
  // If we remove immediately, disconnectedCallback should hide it.
131
130
  el.remove();
@@ -138,7 +137,7 @@ describe('ui-part component', () => {
138
137
  document.body.innerHTML = '';
139
138
  await EditorsRegistry.the.reset();
140
139
 
141
- const el = renderTestUIPart(createUIPartSnapshot('toolbar'));
140
+ const el = renderTestUIPart();
142
141
 
143
142
  el.remove();
144
143
 
@@ -28,7 +28,7 @@ describe('createEditableBlazorInterop', () => {
28
28
  dotnetInterop = createDotNetInteropMock();
29
29
  globalThis.DotNet = createDotnet();
30
30
 
31
- element = renderTestEditable({}, {
31
+ element = renderTestEditable({
32
32
  interactive: false,
33
33
  });
34
34
 
@@ -120,7 +120,10 @@ describe('createEditableBlazorInterop', () => {
120
120
  });
121
121
 
122
122
  it('should ignore events for an unknown root name', async () => {
123
- const custom = renderTestEditable({ rootName: 'other' }, { interactive: false });
123
+ const custom = renderTestEditable({
124
+ rootName: 'other',
125
+ interactive: false,
126
+ });
124
127
 
125
128
  const interop = createEditableBlazorInterop(custom, dotnetInterop);
126
129
  const editor = await waitForTestEditor();