ckeditor5-blazor 1.8.0 → 1.9.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.
@@ -17,5 +17,10 @@ export declare function createEditableBlazorInterop(element: HTMLElement, intero
17
17
  * If the editor is focused, the update is deferred until blur to avoid interrupting the user.
18
18
  */
19
19
  setValue: (value: string) => Promise<void>;
20
+ /**
21
+ * Updates the root attributes on the editor. This is useful when the Blazor component
22
+ * re-renders with new root attributes.
23
+ */
24
+ setRootAttributes: (rootAttributes?: Record<string, unknown> | null) => Promise<void>;
20
25
  };
21
26
  //# sourceMappingURL=create-editable-blazor-interop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-editable-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editable-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAQ9C;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;IAgDpF;;OAEG;;IAiBH;;;OAGG;sBACqB,MAAM;EAWjC"}
1
+ {"version":3,"file":"create-editable-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editable-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAS9C;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;IAoDpF;;OAEG;;IAkBH;;;OAGG;sBACqB,MAAM;IAW9B;;;OAGG;yCACwC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;EAU5E"}
@@ -11,6 +11,10 @@ export declare function createEditorBlazorInterop(element: HTMLElement, interop:
11
11
  * Updates the editor data from Blazor. If the editor is focused, the update is deferred until blur to avoid interrupting the user.
12
12
  */
13
13
  setValue: (value: Record<string, string>) => Promise<void>;
14
+ /**
15
+ * Updates the root attributes on the editor instance.
16
+ */
17
+ setRootAttributes: (rootAttributes?: Record<string, unknown> | null) => Promise<void>;
14
18
  /**
15
19
  * Cleans up all event listeners when the Blazor component is disposed.
16
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"create-editor-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editor-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;IA+DlF;;OAEG;sBACqB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAS9C;;OAEG;;IAkBH;;;;OAIG;;EAWN"}
1
+ {"version":3,"file":"create-editor-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editor-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAW9C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;IAmElF;;OAEG;sBACqB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAS9C;;OAEG;yCACwC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAUzE;;OAEG;;IAmBH;;;;OAIG;;EAWN"}
@@ -1,2 +1,3 @@
1
1
  export * from './create-editor-value-sync';
2
+ export * from './sync-root-attributes';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/interop/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/interop/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Editor } from 'ckeditor5';
2
+ /**
3
+ * Creates a function that synchronizes root attributes on the given editor root.
4
+ *
5
+ * The returned function tracks which attributes were set by itself and will only
6
+ * remove attributes it previously managed. This avoids interfering with other
7
+ * consumers that may also change attributes on the same root.
8
+ *
9
+ * @param editor The editor instance containing the root to manage.
10
+ * @param rootName The name of the root to manage attributes on.
11
+ * @returns A function that can be called with the desired set of attributes to apply them to the root.
12
+ * Calling the function with `null` or an empty object will clear all attributes previously set by it.
13
+ */
14
+ export declare function createRootAttributesUpdater(editor: Editor, rootName: string): RootAttributesUpdater;
15
+ export type RootAttributesUpdater = (rootAttributes?: Record<string, unknown> | null) => void;
16
+ //# sourceMappingURL=sync-root-attributes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-root-attributes.d.ts","sourceRoot":"","sources":["../../../../src/interop/utils/sync-root-attributes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CA6BnG;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ckeditor5-blazor",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "CKEditor 5 integration for Blazor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -139,6 +139,63 @@ describe('editable component', () => {
139
139
  });
140
140
  });
141
141
 
142
+ it('should apply provided root attributes to the editable root', async () => {
143
+ renderTestEditor({
144
+ preset: createEditorPreset('multiroot'),
145
+ content: {},
146
+ });
147
+
148
+ const editor = await waitForTestEditor<MultiRootEditor>();
149
+
150
+ renderTestEditable({
151
+ rootName: 'foo',
152
+ content: '<p>Initial foo component</p>',
153
+ rootAttributes: {
154
+ 'data-test-attr': 'foo-root',
155
+ },
156
+ });
157
+
158
+ await vi.waitFor(() => {
159
+ expect(editor.model.document.getRoot('foo')!.getAttribute('data-test-attr')).toBe('foo-root');
160
+ });
161
+ });
162
+
163
+ it('should apply provided root attributes when editable is mounted after root already exists', async () => {
164
+ renderTestEditor({
165
+ preset: createEditorPreset('multiroot'),
166
+ content: {},
167
+ });
168
+
169
+ const editor = await waitForTestEditor<MultiRootEditor>();
170
+
171
+ renderTestEditable({
172
+ rootName: 'foo',
173
+ content: '<p>Initial foo component</p>',
174
+ rootAttributes: {
175
+ 'data-test-attr': 'foo-root',
176
+ },
177
+ });
178
+
179
+ editor.model.change((writer) => {
180
+ writer.addRoot('foo-2');
181
+ });
182
+
183
+ renderTestEditable({
184
+ rootName: 'foo-2',
185
+ content: '<p>Initial foo-2 component</p>',
186
+ rootAttributes: {
187
+ 'data-test-attr': 'foo-2-root',
188
+ },
189
+ });
190
+
191
+ await vi.waitFor(() => {
192
+ const { document } = editor.model;
193
+
194
+ expect(document.getRoot('foo')!.getAttribute('data-test-attr')).toBe('foo-root');
195
+ expect(document.getRoot('foo-2')!.getAttribute('data-test-attr')).toBe('foo-2-root');
196
+ });
197
+ });
198
+
142
199
  it('should auto-assign editor ID if not provided', async () => {
143
200
  renderTestEditor({
144
201
  preset: createEditorPreset('multiroot'),
@@ -2,7 +2,7 @@ import type { WaitForInteractiveResult } from '../shared';
2
2
  import type { MultiRootEditor } from 'ckeditor5';
3
3
 
4
4
  import { CKEditor5BlazorError } from '../ckeditor5-blazor-error';
5
- import { debounce, waitForDOMReady, waitForInteractiveAttribute } from '../shared';
5
+ import { debounce, isEmptyObject, waitForDOMReady, waitForInteractiveAttribute } from '../shared';
6
6
  import { EditorsRegistry } from './editor/editors-registry';
7
7
  import { queryAllEditorIds } from './editor/utils';
8
8
 
@@ -53,6 +53,7 @@ export class EditableComponentElement extends HTMLElement {
53
53
 
54
54
  const editorId = this.getAttribute('data-cke-editor-id');
55
55
  const rootName = this.getAttribute('data-cke-root-name');
56
+ const rootAttributes = JSON.parse(this.getAttribute('data-cke-root-attributes') || '{}');
56
57
  const content = this.getAttribute('data-cke-content');
57
58
  const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
58
59
 
@@ -86,11 +87,20 @@ export class EditableComponentElement extends HTMLElement {
86
87
  }
87
88
  }
88
89
 
90
+ // Assign attributes to the root if they are not empty.
91
+ // This allows users to add custom attributes to the root element of the editable.
92
+ if (!isEmptyObject(rootAttributes)) {
93
+ editor.model.change((writer) => {
94
+ writer.setAttributes(rootAttributes, root);
95
+ });
96
+ }
97
+
89
98
  return editor;
90
99
  }
91
100
 
92
101
  editor.addRoot(rootName, {
93
102
  isUndoable: false,
103
+ attributes: { ...rootAttributes },
94
104
  ...content !== null && {
95
105
  data: content,
96
106
  },
@@ -103,6 +103,21 @@ describe('editor component', () => {
103
103
 
104
104
  expect(editor.getData()).toBe('');
105
105
  });
106
+
107
+ it('should apply provided root attributes to the editor root', async () => {
108
+ renderTestEditor({
109
+ rootAttributes: {
110
+ 'data-test-attr': '123',
111
+ 'data-another-attr': 'abc',
112
+ },
113
+ });
114
+
115
+ const editor = await waitForTestEditor();
116
+ const root = editor.model.document.getRoot()!;
117
+
118
+ expect(root.getAttribute('data-test-attr')).toBe('123');
119
+ expect(root.getAttribute('data-another-attr')).toBe('abc');
120
+ });
106
121
  });
107
122
 
108
123
  describe('inline', () => {
@@ -151,6 +151,7 @@ export class EditorComponentElement extends HTMLElement {
151
151
  const editorId = this.getAttribute('data-cke-editor-id')!;
152
152
  const preset = JSON.parse(this.getAttribute('data-cke-preset')!) as EditorPreset;
153
153
  const contextId = this.getAttribute('data-cke-context-id');
154
+ const rootAttributes = JSON.parse(this.getAttribute('data-cke-root-attributes') || '{}');
154
155
  const editableHeight = this.getAttribute('data-cke-editable-height') ? Number.parseInt(this.getAttribute('data-cke-editable-height')!, 10) : null;
155
156
  const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
156
157
  const language = JSON.parse(this.getAttribute('data-cke-language')!) as EditorLanguage;
@@ -279,6 +280,13 @@ export class EditorComponentElement extends HTMLElement {
279
280
  return result.editor;
280
281
  })();
281
282
 
283
+ // Assign root attributes if they are not empty. This is needed to support custom attributes on the root element of the editor.
284
+ if (!isEmptyObject(rootAttributes)) {
285
+ editor.model.change((writer) => {
286
+ writer.setAttributes(rootAttributes, editor.model.document.getRoot()!);
287
+ });
288
+ }
289
+
282
290
  if (isSingleRootEditor(editorType) && editableHeight) {
283
291
  setEditorEditableHeight(editor, editableHeight);
284
292
  }
@@ -149,6 +149,48 @@ describe('createEditableBlazorInterop', () => {
149
149
  expect(editor.getData()).toBe('<p>test</p>');
150
150
  });
151
151
 
152
+ it('should set root attributes on the editor when requested', async () => {
153
+ const { setRootAttributes } = createEditableBlazorInterop(element, dotnetInterop);
154
+
155
+ const editor = await waitForTestEditor();
156
+ const root = editor.model.document.getRoot()!;
157
+
158
+ expect(root.getAttribute('data-test')).toBeUndefined();
159
+
160
+ await setRootAttributes({ 'data-test': 'value' });
161
+
162
+ expect(root.getAttribute('data-test')).toBe('value');
163
+ });
164
+
165
+ it('should only remove attributes that it previously set', async () => {
166
+ const { setRootAttributes } = createEditableBlazorInterop(element, dotnetInterop);
167
+ const editor = await waitForTestEditor();
168
+ const root = editor.model.document.getRoot()!;
169
+
170
+ // Simulate another consumer setting an attribute.
171
+ editor.model.change(writer => writer.setAttribute('data-keep', 'true', root));
172
+ expect(root.getAttribute('data-keep')).toBe('true');
173
+
174
+ await setRootAttributes({ 'data-test': 'value' });
175
+ expect(root.getAttribute('data-test')).toBe('value');
176
+ expect(root.getAttribute('data-keep')).toBe('true');
177
+
178
+ // Updating with the same key should not remove it.
179
+ await setRootAttributes({ 'data-test': 'updated' });
180
+ expect(root.getAttribute('data-test')).toBe('updated');
181
+
182
+ // Clearing with an empty object should remove only the attribute managed by us.
183
+ await setRootAttributes({});
184
+
185
+ expect(root.getAttribute('data-test')).toBeUndefined();
186
+ expect(root.getAttribute('data-keep')).toBe('true');
187
+
188
+ // Clearing with null should still not remove attributes managed by others.
189
+ await setRootAttributes(null);
190
+
191
+ expect(root.getAttribute('data-keep')).toBe('true');
192
+ });
193
+
152
194
  it('should not set data if the interop is unmounted before the editor is ready', async () => {
153
195
  const { setValue, unmount } = createEditableBlazorInterop(element, dotnetInterop);
154
196
 
@@ -161,6 +203,19 @@ describe('createEditableBlazorInterop', () => {
161
203
  expect(editor.getData()).toBe('<p>Initial content</p>');
162
204
  });
163
205
 
206
+ it('should not set root attributes if the interop is unmounted before the editor is ready', async () => {
207
+ const { setRootAttributes, unmount } = createEditableBlazorInterop(element, dotnetInterop);
208
+
209
+ unmount();
210
+
211
+ const editor = await waitForTestEditor();
212
+ const root = editor.model.document.getRoot()!;
213
+
214
+ await setRootAttributes({ 'data-test': 'value' });
215
+
216
+ expect(root.getAttribute('data-test')).toBeUndefined();
217
+ });
218
+
164
219
  it('should delay setting data if the editor is focused', async () => {
165
220
  const { setValue } = createEditableBlazorInterop(element, dotnetInterop);
166
221
  const editor = await waitForTestEditor();
@@ -1,10 +1,11 @@
1
1
  import type { DotNetInterop } from '../types';
2
+ import type { RootAttributesUpdater } from './utils';
2
3
 
3
4
  import { EditorsRegistry } from '../elements/editor/editors-registry';
4
5
  import { CKEditor5ChangeDataEvent } from '../elements/editor/plugins/dispatch-editor-roots-change-event';
5
6
  import { queryAllEditorIds } from '../elements/editor/utils';
6
7
  import { markElementAsInteractive } from '../shared';
7
- import { createEditorValueSync, createNoopSync } from './utils/create-editor-value-sync';
8
+ import { createEditorValueSync, createNoopSync, createRootAttributesUpdater } from './utils';
8
9
 
9
10
  /**
10
11
  * Creates an interop layer to synchronize a single CKEditor 5 editable root with a Blazor component.
@@ -19,9 +20,11 @@ export function createEditableBlazorInterop(element: HTMLElement, interop: DotNe
19
20
  const rootName = element.getAttribute('data-cke-root-name') ?? 'main';
20
21
 
21
22
  let unmounted = false;
22
- let sync = createNoopSync<string>();
23
23
  let editorRef: unknown | null = null;
24
24
 
25
+ let sync = createNoopSync<string>();
26
+ let syncRootAttributes: RootAttributesUpdater | null = null;
27
+
25
28
  /**
26
29
  * Handles data change events dispatched by the CKEditor plugin.
27
30
  * Filters by both editorId and rootName, then notifies Blazor if the root value changed.
@@ -55,6 +58,8 @@ export function createEditableBlazorInterop(element: HTMLElement, interop: DotNe
55
58
  applyValue: value => editor.setData({ [rootName]: value }),
56
59
  isEqual: (a, b) => a === b,
57
60
  });
61
+
62
+ syncRootAttributes = createRootAttributesUpdater(editor, rootName);
58
63
  };
59
64
 
60
65
  void initializeSynchronization();
@@ -78,6 +83,7 @@ export function createEditableBlazorInterop(element: HTMLElement, interop: DotNe
78
83
  editorRef = null;
79
84
  }
80
85
 
86
+ syncRootAttributes = null;
81
87
  unmounted = true;
82
88
  },
83
89
 
@@ -95,5 +101,19 @@ export function createEditableBlazorInterop(element: HTMLElement, interop: DotNe
95
101
  // Ensure sync is initialized before forwarding (waitFor guarantees the editor exists)
96
102
  sync.setValue(value);
97
103
  },
104
+
105
+ /**
106
+ * Updates the root attributes on the editor. This is useful when the Blazor component
107
+ * re-renders with new root attributes.
108
+ */
109
+ setRootAttributes: async (rootAttributes?: Record<string, unknown> | null) => {
110
+ if (unmounted) {
111
+ return;
112
+ }
113
+
114
+ await EditorsRegistry.the.waitFor(editorId);
115
+
116
+ syncRootAttributes?.(rootAttributes);
117
+ },
98
118
  };
99
119
  }
@@ -113,6 +113,54 @@ describe('createEditorBlazorInterop', () => {
113
113
  });
114
114
  });
115
115
 
116
+ describe('setRootAttributes', () => {
117
+ it('should update root attributes on the editor', async () => {
118
+ const { setRootAttributes } = createEditorBlazorInterop(element, dotnetInterop);
119
+
120
+ const editor = await waitForTestEditor();
121
+ const root = editor.model.document.getRoot()!;
122
+
123
+ expect(root.getAttribute('data-test')).toBeUndefined();
124
+
125
+ await setRootAttributes({ 'data-test': 'value' });
126
+ expect(root.getAttribute('data-test')).toBe('value');
127
+ });
128
+
129
+ it('should only remove attributes that it previously set', async () => {
130
+ const { setRootAttributes } = createEditorBlazorInterop(element, dotnetInterop);
131
+
132
+ const editor = await waitForTestEditor();
133
+ const root = editor.model.document.getRoot()!;
134
+
135
+ // Simulate another consumer setting an attribute.
136
+ editor.model.change(writer => writer.setAttribute('data-keep', 'true', root));
137
+ expect(root.getAttribute('data-keep')).toBe('true');
138
+
139
+ await setRootAttributes({ 'data-test': 'value' });
140
+ expect(root.getAttribute('data-test')).toBe('value');
141
+ expect(root.getAttribute('data-keep')).toBe('true');
142
+
143
+ // Clearing should remove only the attribute managed by us.
144
+ await setRootAttributes(null);
145
+
146
+ expect(root.getAttribute('data-test')).toBeUndefined();
147
+ expect(root.getAttribute('data-keep')).toBe('true');
148
+ });
149
+
150
+ it('should not set root attributes if the interop is unmounted', async () => {
151
+ const { setRootAttributes, unmount } = createEditorBlazorInterop(element, dotnetInterop);
152
+
153
+ unmount();
154
+
155
+ const editor = await waitForTestEditor();
156
+ const root = editor.model.document.getRoot()!;
157
+
158
+ await setRootAttributes({ 'data-test': 'value' });
159
+
160
+ expect(root.getAttribute('data-test')).toBeUndefined();
161
+ });
162
+ });
163
+
116
164
  describe('focus tracking', () => {
117
165
  it('should call OnEditorFocus if editor gets focused', async () => {
118
166
  createEditorBlazorInterop(element, dotnetInterop);
@@ -1,4 +1,5 @@
1
1
  import type { DotNetInterop } from '../types';
2
+ import type { RootAttributesUpdater } from './utils';
2
3
  import type { Editor, FileRepository } from 'ckeditor5';
3
4
 
4
5
  import { ensureEditorElementsRegistered } from '../elements';
@@ -6,7 +7,7 @@ import { EditorsRegistry } from '../elements/editor/editors-registry';
6
7
  import { CKEditor5ChangeDataEvent } from '../elements/editor/plugins/dispatch-editor-roots-change-event';
7
8
  import { getEditorRootsValues } from '../elements/editor/utils';
8
9
  import { markElementAsInteractive, shallowEqual } from '../shared';
9
- import { createEditorValueSync, createNoopSync } from './utils/create-editor-value-sync';
10
+ import { createEditorValueSync, createNoopSync, createRootAttributesUpdater } from './utils';
10
11
 
11
12
  /**
12
13
  * Creates an interop layer to synchronize a CKEditor 5 instance with a Blazor component.
@@ -22,6 +23,8 @@ export function createEditorBlazorInterop(element: HTMLElement, interop: DotNetI
22
23
  let unmountCKEditorListeners: VoidFunction | null = null;
23
24
 
24
25
  let sync = createNoopSync<Record<string, string>>();
26
+ let syncRootAttributes: RootAttributesUpdater | null = null;
27
+
25
28
  let editorRef: unknown | null = null;
26
29
 
27
30
  // Handles data change events dispatched by the CKEditor plugin.
@@ -49,6 +52,8 @@ export function createEditorBlazorInterop(element: HTMLElement, interop: DotNetI
49
52
  isEqual: shallowEqual,
50
53
  });
51
54
 
55
+ syncRootAttributes = createRootAttributesUpdater(editor, 'main');
56
+
52
57
  // Notify Blazor of focus changes so it can trigger the appropriate callbacks.
53
58
  const onFocusChange = (_evt: unknown, _name: unknown, isFocused: boolean) => {
54
59
  const method = isFocused ? 'OnEditorFocus' : 'OnEditorBlur';
@@ -90,6 +95,19 @@ export function createEditorBlazorInterop(element: HTMLElement, interop: DotNetI
90
95
  sync.setValue(value);
91
96
  },
92
97
 
98
+ /**
99
+ * Updates the root attributes on the editor instance.
100
+ */
101
+ setRootAttributes: async (rootAttributes?: Record<string, unknown> | null) => {
102
+ if (unmounted) {
103
+ return;
104
+ }
105
+
106
+ await EditorsRegistry.the.waitFor(editorId);
107
+
108
+ syncRootAttributes?.(rootAttributes);
109
+ },
110
+
93
111
  /**
94
112
  * Cleans up all event listeners when the Blazor component is disposed.
95
113
  */
@@ -107,6 +125,7 @@ export function createEditorBlazorInterop(element: HTMLElement, interop: DotNetI
107
125
  editorRef = null;
108
126
  }
109
127
 
128
+ syncRootAttributes = null;
110
129
  unmounted = true;
111
130
  },
112
131
 
@@ -1 +1,2 @@
1
1
  export * from './create-editor-value-sync';
2
+ export * from './sync-root-attributes';
@@ -0,0 +1,46 @@
1
+ import type { Editor } from 'ckeditor5';
2
+
3
+ /**
4
+ * Creates a function that synchronizes root attributes on the given editor root.
5
+ *
6
+ * The returned function tracks which attributes were set by itself and will only
7
+ * remove attributes it previously managed. This avoids interfering with other
8
+ * consumers that may also change attributes on the same root.
9
+ *
10
+ * @param editor The editor instance containing the root to manage.
11
+ * @param rootName The name of the root to manage attributes on.
12
+ * @returns A function that can be called with the desired set of attributes to apply them to the root.
13
+ * Calling the function with `null` or an empty object will clear all attributes previously set by it.
14
+ */
15
+ export function createRootAttributesUpdater(editor: Editor, rootName: string): RootAttributesUpdater {
16
+ const managedAttrs = new Set<string>();
17
+
18
+ return (rootAttributes?: Record<string, unknown> | null) => {
19
+ editor.model.enqueueChange({ isUndoable: false }, (writer) => {
20
+ const root = editor.model.document.getRoot(rootName);
21
+
22
+ /* v8 ignore next if -- @preserve */
23
+ if (!root) {
24
+ return;
25
+ }
26
+
27
+ // Remove previously managed attributes that are no longer requested.
28
+ for (const key of managedAttrs) {
29
+ if (rootAttributes && (key in rootAttributes)) {
30
+ continue;
31
+ }
32
+
33
+ writer.removeAttribute(key, root);
34
+ managedAttrs.delete(key);
35
+ }
36
+
37
+ // Apply or overwrite requested attributes.
38
+ for (const [key, value] of Object.entries(rootAttributes ?? {})) {
39
+ writer.setAttribute(key, value, root);
40
+ managedAttrs.add(key);
41
+ }
42
+ });
43
+ };
44
+ }
45
+
46
+ export type RootAttributesUpdater = (rootAttributes?: Record<string, unknown> | null) => void;