ckeditor5 1.31.8 → 1.32.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -4
  3. data/lib/ckeditor5/rails/assets/webcomponent_bundle.rb +2 -21
  4. data/lib/ckeditor5/rails/version.rb +1 -1
  5. data/npm_package/dist/index.cjs +2 -0
  6. data/npm_package/dist/index.cjs.map +1 -0
  7. data/npm_package/dist/index.d.ts +1 -0
  8. data/npm_package/dist/index.mjs +723 -0
  9. data/npm_package/dist/index.mjs.map +1 -0
  10. data/npm_package/dist/src/components/context.d.ts +24 -0
  11. data/npm_package/dist/src/components/context.d.ts.map +1 -0
  12. data/npm_package/dist/src/components/editable.d.ts +34 -0
  13. data/npm_package/dist/src/components/editable.d.ts.map +1 -0
  14. data/npm_package/dist/src/components/editor/editor.d.ts +79 -0
  15. data/npm_package/dist/src/components/editor/editor.d.ts.map +1 -0
  16. data/npm_package/dist/src/components/editor/index.d.ts +3 -0
  17. data/npm_package/dist/src/components/editor/index.d.ts.map +1 -0
  18. data/npm_package/dist/src/components/editor/multiroot-editables-tracker.d.ts +36 -0
  19. data/npm_package/dist/src/components/editor/multiroot-editables-tracker.d.ts.map +1 -0
  20. data/npm_package/dist/src/components/index.d.ts +5 -0
  21. data/npm_package/dist/src/components/index.d.ts.map +1 -0
  22. data/npm_package/dist/src/components/ui-part.d.ts +2 -0
  23. data/npm_package/dist/src/components/ui-part.d.ts.map +1 -0
  24. data/npm_package/dist/src/helpers/exec-if-dom-ready.d.ts +7 -0
  25. data/npm_package/dist/src/helpers/exec-if-dom-ready.d.ts.map +1 -0
  26. data/npm_package/dist/src/helpers/index.d.ts +8 -0
  27. data/npm_package/dist/src/helpers/index.d.ts.map +1 -0
  28. data/npm_package/dist/src/helpers/inject-script.d.ts +8 -0
  29. data/npm_package/dist/src/helpers/inject-script.d.ts.map +1 -0
  30. data/npm_package/dist/src/helpers/is-safe-key.d.ts +8 -0
  31. data/npm_package/dist/src/helpers/is-safe-key.d.ts.map +1 -0
  32. data/npm_package/dist/src/helpers/load-async-css.d.ts +9 -0
  33. data/npm_package/dist/src/helpers/load-async-css.d.ts.map +1 -0
  34. data/npm_package/dist/src/helpers/load-async-imports.d.ts +25 -0
  35. data/npm_package/dist/src/helpers/load-async-imports.d.ts.map +1 -0
  36. data/npm_package/dist/src/helpers/resolve-config-element-references.d.ts +9 -0
  37. data/npm_package/dist/src/helpers/resolve-config-element-references.d.ts.map +1 -0
  38. data/npm_package/dist/src/helpers/uid.d.ts +7 -0
  39. data/npm_package/dist/src/helpers/uid.d.ts.map +1 -0
  40. data/npm_package/dist/src/index.d.ts +1 -0
  41. data/npm_package/dist/src/index.d.ts.map +1 -0
  42. data/npm_package/dist/vite.config.d.ts +3 -0
  43. data/npm_package/dist/vite.config.d.ts.map +1 -0
  44. data/npm_package/package.json +37 -0
  45. metadata +41 -6
  46. data/lib/ckeditor5/rails/assets/webcomponents/components/context.mjs +0 -123
  47. data/lib/ckeditor5/rails/assets/webcomponents/components/editable.mjs +0 -113
  48. data/lib/ckeditor5/rails/assets/webcomponents/components/editor.mjs +0 -778
  49. data/lib/ckeditor5/rails/assets/webcomponents/components/ui-part.mjs +0 -26
  50. data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +0 -235
@@ -1,778 +0,0 @@
1
- class CKEditorComponent extends HTMLElement {
2
- /**
3
- * List of attributes that trigger updates when changed.
4
- *
5
- * @static
6
- * @returns {string[]} Array of attribute names to observe
7
- */
8
- static get observedAttributes() {
9
- return ['config', 'plugins', 'translations', 'type'];
10
- }
11
-
12
- /**
13
- * List of input attributes that trigger updates when changed.
14
- *
15
- * @static
16
- * @returns {string[]} Array of input attribute names to observe
17
- */
18
- static get inputAttributes() {
19
- return ['name', 'required', 'value'];
20
- }
21
-
22
- /** @type {Promise<import('ckeditor5').Editor>|null} Promise to initialize editor instance */
23
- instancePromise = Promise.withResolvers();
24
-
25
- /** @type {import('ckeditor5').Watchdog|null} Editor watchdog */
26
- watchdog = null;
27
-
28
- /** @type {import('ckeditor5').Editor|null} Current editor instance */
29
- instance = null;
30
-
31
- /** @type {Record<string, HTMLElement>} Map of editable elements by name */
32
- editables = {};
33
-
34
- /** @type {String} Initial HTML passed to component */
35
- #initialHTML = '';
36
-
37
- /** @type {CKEditorContextComponent|null} */
38
- #context = null;
39
-
40
- /** @type {String} ID of editor within context */
41
- #contextEditorId = null;
42
-
43
- /** @type {Object} Description of ckeditor bundle */
44
- #bundle = null;
45
-
46
- /** @type {(event: CustomEvent) => void} Event handler for editor change */
47
- get oneditorchange() {
48
- return this.#getEventHandler('editorchange');
49
- }
50
-
51
- set oneditorchange(handler) {
52
- this.#setEventHandler('editorchange', handler);
53
- }
54
-
55
- /** @type {(event: CustomEvent) => void} Event handler for editor ready */
56
- get oneditorready() {
57
- return this.#getEventHandler('editorready');
58
- }
59
-
60
- set oneditorready(handler) {
61
- this.#setEventHandler('editorready', handler);
62
- }
63
-
64
- /** @type {(event: CustomEvent) => void} Event handler for editor error */
65
- get oneditorerror() {
66
- return this.#getEventHandler('editorerror');
67
- }
68
-
69
- set oneditorerror(handler) {
70
- this.#setEventHandler('editorerror', handler);
71
- }
72
-
73
- /**
74
- * Gets event handler function from attribute or property
75
- *
76
- * @private
77
- * @param {string} name - Event name without 'on' prefix
78
- * @returns {Function|null} Event handler or null
79
- */
80
- #getEventHandler(name) {
81
- if (this.hasAttribute(`on${name}`)) {
82
- const handler = this.getAttribute(`on${name}`);
83
-
84
- if (!isSafeKey(handler)) {
85
- throw new Error(`Unsafe event handler attribute value: ${handler}`);
86
- }
87
-
88
- return window[handler] || new Function('event', handler);
89
- }
90
- return this[`#${name}Handler`];
91
- }
92
-
93
- /**
94
- * Sets event handler function
95
- *
96
- * @private
97
- * @param {string} name - Event name without 'on' prefix
98
- * @param {Function|string|null} handler - Event handler
99
- */
100
- #setEventHandler(name, handler) {
101
- if (typeof handler === 'string') {
102
- this.setAttribute(`on${name}`, handler);
103
- } else {
104
- this.removeAttribute(`on${name}`);
105
- this[`#${name}Handler`] = handler;
106
- }
107
- }
108
-
109
- /**
110
- * Lifecycle callback when element is connected to DOM
111
- * Initializes the editor when DOM is ready
112
- * @protected
113
- */
114
- connectedCallback() {
115
- this.#context = this.closest('ckeditor-context-component');
116
- this.#initialHTML = this.innerHTML;
117
-
118
- try {
119
- execIfDOMReady(async () => {
120
- if (this.#context) {
121
- await this.#context.instancePromise.promise;
122
- this.#context.registerEditor(this);
123
- }
124
-
125
- await this.reinitializeEditor();
126
- });
127
- } catch (error) {
128
- console.error('Failed to initialize editor:', error);
129
-
130
- const event = new CustomEvent('editor-error', { detail: error });
131
-
132
- this.dispatchEvent(event);
133
- this.oneditorerror?.(event);
134
- }
135
- }
136
-
137
- /**
138
- * Handles attribute changes and reinitializes editor if needed
139
- * @protected
140
- * @param {string} name - Name of changed attribute
141
- * @param {string|null} oldValue - Previous attribute value
142
- * @param {string|null} newValue - New attribute value
143
- */
144
- async attributeChangedCallback(name, oldValue, newValue) {
145
- if (oldValue !== null &&
146
- oldValue !== newValue &&
147
- CKEditorComponent.observedAttributes.includes(name) && this.isConnected) {
148
- await this.reinitializeEditor();
149
- }
150
- }
151
-
152
- /**
153
- * Lifecycle callback when element is removed from DOM
154
- * Destroys the editor instance
155
- * @protected
156
- */
157
- async disconnectedCallback() {
158
- if (this.#context) {
159
- this.#context.unregisterEditor(this);
160
- }
161
-
162
- try {
163
- await this.#destroy();
164
- } catch (error) {
165
- console.error('Failed to destroy editor:', error);
166
- }
167
- }
168
-
169
- /**
170
- * Runs a callback after the editor is ready. It waits for editor
171
- * initialization if needed.
172
- *
173
- * @param {(editor: import('ckeditor5').Editor) => void} callback - Callback to run
174
- * @returns {Promise<void>}
175
- */
176
- runAfterEditorReady(callback) {
177
- if (this.instance) {
178
- return Promise.resolve(callback(this.instance));
179
- }
180
-
181
- return this.instancePromise.promise.then(callback);
182
- }
183
-
184
- /**
185
- * Determines appropriate editor element tag based on editor type
186
- *
187
- * @private
188
- * @returns {string} HTML tag name to use
189
- */
190
- get #editorElementTag() {
191
- switch (this.getAttribute('type')) {
192
- case 'ClassicEditor':
193
- return 'textarea';
194
-
195
- default:
196
- return 'div';
197
- }
198
- }
199
-
200
- /**
201
- * Gets the CKEditor context instance if available.
202
- *
203
- * @private
204
- * @returns {import('ckeditor5').ContextWatchdog|null}
205
- */
206
- get #contextWatchdog() {
207
- return this.#context?.instance;
208
- }
209
-
210
- /**
211
- * Destroys the editor instance and watchdog if available
212
- */
213
- async #destroy() {
214
- if (this.#contextEditorId) {
215
- await this.#contextWatchdog.remove(this.#contextEditorId);
216
- }
217
-
218
- await this.instance?.destroy();
219
- await this.watchdog?.destroy();
220
- }
221
-
222
- /**
223
- * Gets editor configuration with resolved element references
224
- *
225
- * @private
226
- * @returns {EditorConfig}
227
- */
228
- #getConfig() {
229
- const config = JSON.parse(this.getAttribute('config') || '{}');
230
-
231
- return resolveElementReferences(config);
232
- }
233
-
234
- /**
235
- * Creates a new CKEditor instance
236
- *
237
- * @private
238
- * @param {Record<string, HTMLElement>|CKEditorMultiRootEditablesTracker} editablesOrContent - Editable or content
239
- * @returns {Promise<{ editor: import('ckeditor5').Editor, watchdog: editor: import('ckeditor5').EditorWatchdog }>} Initialized editor instance
240
- * @throws {Error} When initialization fails
241
- */
242
- async #initializeEditor(editablesOrContent) {
243
- await Promise.all([
244
- this.#ensureStylesheetsInjected(),
245
- this.#ensureWindowScriptsInjected(),
246
- ]);
247
-
248
- // Depending on the type of the editor the content supplied on the first
249
- // argument is different. For ClassicEditor it's a element or string, for MultiRootEditor
250
- // it's an object with editables, for DecoupledEditor it's string.
251
- let content = editablesOrContent;
252
-
253
- if (editablesOrContent instanceof CKEditorMultiRootEditablesTracker) {
254
- content = editablesOrContent.getAll();
255
- } else if (typeof editablesOrContent !== 'string') {
256
- content = editablesOrContent.main;
257
- }
258
-
259
- // Broadcast editor initialization event. It's good time to load add inline window plugins.
260
- const beforeInitEventDetails = {
261
- ...content instanceof HTMLElement && { element: content },
262
- ...content instanceof String && { data: content },
263
- ...content instanceof Object && { editables: content }
264
- };
265
-
266
- window.dispatchEvent(
267
- new CustomEvent('ckeditor:attach:before', { detail: beforeInitEventDetails})
268
- );
269
-
270
- // Start fetching constructor.
271
- const Editor = await this.#getEditorConstructor();
272
- const [plugins, translations] = await Promise.all([
273
- this.#getPlugins(),
274
- this.#getTranslations()
275
- ]);
276
-
277
- const config = {
278
- ...this.#getConfig(),
279
- ...translations.length && {
280
- translations
281
- },
282
- plugins,
283
- };
284
-
285
- // Broadcast editor mounting event. It's good time to map configuration.
286
- window.dispatchEvent(
287
- new CustomEvent('ckeditor:attach', { detail: { config, ...beforeInitEventDetails } })
288
- );
289
-
290
- console.warn('Initializing CKEditor with:', { config, watchdog: this.hasWatchdog(), context: this.#context });
291
-
292
- // Initialize watchdog if needed
293
- let watchdog = null;
294
- let instance = null;
295
- let contextId = null;
296
-
297
- if (this.#context) {
298
- contextId = uid();
299
-
300
- await this.#contextWatchdog.add( {
301
- creator: (_element, _config) => Editor.create(_element, _config),
302
- id: contextId,
303
- sourceElementOrData: content,
304
- type: 'editor',
305
- config,
306
- } );
307
-
308
- instance = this.#contextWatchdog.getItem(contextId);
309
- } else if (this.hasWatchdog()) {
310
- // Let's create use with plain watchdog.
311
- const { EditorWatchdog } = await import('ckeditor5');
312
- const watchdog = new EditorWatchdog(Editor);
313
-
314
- await watchdog.create(content, config);
315
-
316
- instance = watchdog.editor;
317
- } else {
318
- // Let's create the editor without watchdog.
319
- instance = await Editor.create(content, config);
320
- }
321
-
322
- console.warn('CKEditor initialized:', {
323
- instance,
324
- watchdog, config: instance.config._config,
325
- });
326
-
327
- return {
328
- contextId,
329
- instance,
330
- watchdog,
331
- };
332
- }
333
-
334
- /**
335
- * Re-initializes the editor by destroying existing instance and creating new one
336
- *
337
- * @private
338
- * @returns {Promise<void>}
339
- */
340
- async reinitializeEditor() {
341
- if (this.instance) {
342
- this.instancePromise = Promise.withResolvers();
343
-
344
- await this.#destroy();
345
-
346
- this.instance = null;
347
- }
348
-
349
- this.style.display = 'block';
350
-
351
- if (!this.isMultiroot() && !this.isDecoupled()) {
352
- this.innerHTML = `<${this.#editorElementTag}>${this.#initialHTML}</${this.#editorElementTag}>`;
353
- this.#assignInputAttributes();
354
- }
355
-
356
- // Let's track changes in editables if it's a multiroot editor.
357
- if(this.isMultiroot()) {
358
- this.editables = new CKEditorMultiRootEditablesTracker(this, this.#queryEditables());
359
- } else if (this.isDecoupled()) {
360
- this.editables = null;
361
- } else {
362
- this.editables = this.#queryEditables();
363
- }
364
-
365
- try {
366
- const { watchdog, instance, contextId } = await this.#initializeEditor(this.editables || this.#getConfig().initialData || '');
367
-
368
- this.watchdog = watchdog;
369
- this.instance = instance;
370
- this.#contextEditorId = contextId;
371
-
372
- this.#setupContentSync();
373
- this.#setupEditableHeight();
374
- this.#setupDataChangeListener();
375
-
376
- this.instancePromise.resolve(this.instance);
377
-
378
- // Broadcast editor ready event
379
- const event = new CustomEvent('editor-ready', { detail: this.instance });
380
-
381
- this.dispatchEvent(event);
382
- this.oneditorready?.(event);
383
- } catch (err) {
384
- this.instancePromise.reject(err);
385
- throw err;
386
- }
387
- }
388
-
389
- /**
390
- * Sets up data change listener that broadcasts content changes
391
- *
392
- * @private
393
- */
394
- #setupDataChangeListener() {
395
- const getRootContent = rootName => this.instance.getData({ rootName });
396
- const getAllRoots = () =>
397
- this.instance.model.document
398
- .getRootNames()
399
- .reduce((acc, rootName) => ({
400
- ...acc,
401
- [rootName]: getRootContent(rootName)
402
- }), {});
403
-
404
- this.instance?.model.document.on('change:data', () => {
405
- const event = new CustomEvent('editor-change', {
406
- detail: {
407
- editor: this.instance,
408
- data: getAllRoots(),
409
- },
410
- bubbles: true
411
- });
412
-
413
- this.dispatchEvent(event);
414
- this.oneditorchange?.(event);
415
- });
416
- }
417
-
418
- /**
419
- * Checks if current editor is classic type
420
- *
421
- * @returns {boolean}
422
- */
423
- isClassic() {
424
- return this.getAttribute('type') === 'ClassicEditor';
425
- }
426
-
427
- /**
428
- * Checks if current editor is balloon type
429
- *
430
- * @returns {boolean}
431
- */
432
- isBallon() {
433
- return this.getAttribute('type') === 'BalloonEditor';
434
- }
435
-
436
- /**
437
- * Checks if current editor is multiroot type
438
- *
439
- * @returns {boolean}
440
- */
441
- isMultiroot() {
442
- return this.getAttribute('type') === 'MultiRootEditor';
443
- }
444
-
445
- /**
446
- * Checks if current editor is decoupled type
447
- *
448
- * @returns {boolean}
449
- */
450
- isDecoupled() {
451
- return this.getAttribute('type') === 'DecoupledEditor';
452
- }
453
-
454
- /**
455
- * Checks if current editor has watchdog enabled
456
- *
457
- * @returns {boolean}
458
- */
459
- hasWatchdog() {
460
- return this.getAttribute('watchdog') === 'true';
461
- }
462
-
463
- /**
464
- * Queries and validates editable elements
465
- *
466
- * @private
467
- * @returns {Record<string, HTMLElement>}
468
- * @throws {Error} When required editables are missing
469
- */
470
- #queryEditables() {
471
- if (this.isDecoupled()) {
472
- return {};
473
- }
474
-
475
- if (this.isMultiroot()) {
476
- const editables = [...this.querySelectorAll('ckeditor-editable-component')];
477
-
478
- return editables.reduce((acc, element) => {
479
- if (!element.name) {
480
- throw new Error('Editable component missing required "name" attribute');
481
- }
482
- acc[element.name] = element;
483
- return acc;
484
- }, Object.create(null));
485
- }
486
-
487
- const mainEditable = this.querySelector(this.#editorElementTag);
488
-
489
- if (!mainEditable) {
490
- throw new Error(`No ${this.#editorElementTag} element found`);
491
- }
492
-
493
- return { main: mainEditable };
494
- }
495
-
496
- /**
497
- * Copies input-related attributes from component to the main editable element
498
- *
499
- * @private
500
- */
501
- #assignInputAttributes() {
502
- const textarea = this.querySelector('textarea');
503
-
504
- if (!textarea) {
505
- return;
506
- }
507
-
508
- for (const attr of CKEditorComponent.inputAttributes) {
509
- if (this.hasAttribute(attr)) {
510
- textarea.setAttribute(attr, this.getAttribute(attr));
511
- }
512
- }
513
- }
514
-
515
- /**
516
- * Sets up content sync between editor and textarea element.
517
- *
518
- * @private
519
- */
520
- #setupContentSync() {
521
- if (!this.instance) {
522
- return;
523
- }
524
-
525
- const textarea = this.querySelector('textarea');
526
-
527
- if (!textarea) {
528
- return;
529
- }
530
-
531
- // Initial sync
532
- const syncInput = () => {
533
- this.style.position = 'relative';
534
-
535
- textarea.innerHTML = '';
536
- textarea.value = this.instance.getData();
537
- textarea.tabIndex = -1;
538
-
539
- Object.assign(textarea.style, {
540
- display: 'flex',
541
- position: 'absolute',
542
- bottom: '0',
543
- left: '50%',
544
- width: '1px',
545
- height: '1px',
546
- opacity: '0',
547
- pointerEvents: 'none',
548
- margin: '0',
549
- padding: '0',
550
- border: 'none'
551
- });
552
- };
553
-
554
- syncInput();
555
-
556
- // Listen for changes
557
- this.instance.model.document.on('change:data', () => {
558
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
559
- textarea.dispatchEvent(new Event('change', { bubbles: true }));
560
-
561
- syncInput();
562
- });
563
- }
564
-
565
- /**
566
- * Sets up editable height for ClassicEditor
567
- *
568
- * @private
569
- */
570
- #setupEditableHeight() {
571
- if (!this.isClassic() && !this.isBallon()) {
572
- return;
573
- }
574
-
575
- const { instance } = this;
576
- const height = Number.parseInt(this.getAttribute('editable-height'), 10);
577
-
578
- if (Number.isNaN(height)) {
579
- return;
580
- }
581
-
582
- instance.editing.view.change((writer) => {
583
- writer.setStyle('height', `${height}px`, instance.editing.view.document.getRoot());
584
- });
585
- }
586
-
587
- /**
588
- * Gets bundle JSON description from translations attribute
589
- */
590
- #getBundle() {
591
- return this.#bundle ||= JSON.parse(this.getAttribute('bundle'));
592
- }
593
-
594
-
595
- /**
596
- * Checks if all required stylesheets are injected. If not, inject.
597
- */
598
- async #ensureStylesheetsInjected() {
599
- await loadAsyncCSS(this.#getBundle().stylesheets || []);
600
- }
601
-
602
- /**
603
- * Checks if all required scripts are injected. If not, inject.
604
- */
605
- async #ensureWindowScriptsInjected() {
606
- const windowScripts = (this.#getBundle().scripts || []).filter(script => !!script.window_name);
607
-
608
- await loadAsyncImports(windowScripts);
609
- }
610
-
611
- /**
612
- * Loads translation modules
613
- *
614
- * @private
615
- * @returns {Promise<Array<any>>}
616
- */
617
- async #getTranslations() {
618
- const translations = this.#getBundle().scripts.filter(script => script.translation);
619
-
620
- return loadAsyncImports(translations);
621
- }
622
-
623
- /**
624
- * Loads plugin modules
625
- *
626
- * @private
627
- * @returns {Promise<Array<any>>}
628
- */
629
- async #getPlugins() {
630
- const raw = this.getAttribute('plugins');
631
- const items = raw ? JSON.parse(raw) : [];
632
- const mappedItems = items.map(item =>
633
- typeof item === 'string'
634
- ? { import_name: 'ckeditor5', import_as: item }
635
- : item
636
- );
637
-
638
- return loadAsyncImports(mappedItems);
639
- }
640
-
641
- /**
642
- * Gets editor constructor based on type attribute
643
- *
644
- * @private
645
- * @returns {Promise<typeof import('ckeditor5').Editor>}
646
- * @throws {Error} When editor type is invalid
647
- */
648
- async #getEditorConstructor() {
649
- const CKEditor = await import('ckeditor5');
650
- const editorType = this.getAttribute('type');
651
-
652
- if (!editorType || !Object.prototype.hasOwnProperty.call(CKEditor, editorType)) {
653
- throw new Error(`Invalid editor type: ${editorType}`);
654
- }
655
-
656
- return CKEditor[editorType];
657
- }
658
- }
659
-
660
- class CKEditorMultiRootEditablesTracker {
661
- #editorElement;
662
- #editables;
663
-
664
- /**
665
- * Creates new tracker instance wrapped in a Proxy for dynamic property access
666
- *
667
- * @param {CKEditorComponent} editorElement - Parent editor component reference
668
- * @param {Record<string, HTMLElement>} initialEditables - Initial editable elements
669
- * @returns {Proxy<CKEditorMultiRootEditablesTracker>} Proxy wrapping the tracker
670
- */
671
- constructor(editorElement, initialEditables = {}) {
672
- this.#editorElement = editorElement;
673
- this.#editables = initialEditables;
674
-
675
- return new Proxy(this, {
676
- /**
677
- * Handles property access, returns class methods or editable elements
678
- *
679
- * @param {CKEditorMultiRootEditablesTracker} target - The tracker instance
680
- * @param {string|symbol} name - Property name being accessed
681
- */
682
- get(target, name) {
683
- if (typeof target[name] === 'function') {
684
- return target[name].bind(target);
685
- }
686
-
687
- return target.#editables[name];
688
- },
689
-
690
- /**
691
- * Handles setting new editable elements, triggers root attachment
692
- *
693
- * @param {CKEditorMultiRootEditablesTracker} target - The tracker instance
694
- * @param {string} name - Name of the editable root
695
- * @param {HTMLElement} element - Element to attach as editable
696
- */
697
- set(target, name, element) {
698
- if (target.#editables[name] !== element) {
699
- target.attachRoot(name, element);
700
- target.#editables[name] = element;
701
- }
702
- return true;
703
- },
704
-
705
- /**
706
- * Handles removing editable elements, triggers root detachment
707
- *
708
- * @param {CKEditorMultiRootEditablesTracker} target - The tracker instance
709
- * @param {string} name - Name of the root to remove
710
- */
711
- deleteProperty(target, name) {
712
- target.detachRoot(name);
713
- delete target.#editables[name];
714
- return true;
715
- }
716
- });
717
- }
718
-
719
- /**
720
- * Attaches a new editable root to the editor.
721
- * Creates new editor root and binds UI elements.
722
- *
723
- * @param {string} name - Name of the editable root
724
- * @param {HTMLElement} element - DOM element to use as editable
725
- * @returns {Promise<void>} Resolves when root is attached
726
- */
727
- async attachRoot(name, element) {
728
- await this.detachRoot(name);
729
-
730
- return this.#editorElement.runAfterEditorReady((editor) => {
731
- const { ui, editing, model } = editor;
732
-
733
- editor.addRoot(name, {
734
- isUndoable: false,
735
- data: element.innerHTML
736
- });
737
-
738
- const root = model.document.getRoot(name);
739
-
740
- if (ui.getEditableElement(name)) {
741
- editor.detachEditable(root);
742
- }
743
-
744
- const editable = ui.view.createEditable(name, element);
745
- ui.addEditable(editable);
746
- editing.view.forceRender();
747
- });
748
- }
749
-
750
- /**
751
- * Detaches an editable root from the editor.
752
- * Removes editor root and cleans up UI bindings.
753
- *
754
- * @param {string} name - Name of root to detach
755
- * @returns {Promise<void>} Resolves when root is detached
756
- */
757
- async detachRoot(name) {
758
- return this.#editorElement.runAfterEditorReady(editor => {
759
- const root = editor.model.document.getRoot(name);
760
-
761
- if (root) {
762
- editor.detachEditable(root);
763
- editor.detachRoot(name, true);
764
- }
765
- });
766
- }
767
-
768
- /**
769
- * Gets all currently tracked editable elements
770
- *
771
- * @returns {Record<string, HTMLElement>} Map of all editable elements
772
- */
773
- getAll() {
774
- return this.#editables;
775
- }
776
- }
777
-
778
- customElements.define('ckeditor-component', CKEditorComponent);