@ckeditor/ckeditor5-engine 42.0.2-alpha.2 → 43.0.0-alpha.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/CHANGELOG.md +1 -820
- package/dist/dev-utils/model.d.ts +2 -0
- package/dist/dev-utils/view.d.ts +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +466 -271
- package/dist/index.js.map +1 -1
- package/dist/model/schema.d.ts +149 -51
- package/dist/view/observer/focusobserver.d.ts +12 -0
- package/dist/view/observer/mutationobserver.d.ts +34 -5
- package/dist/view/observer/selectionobserver.d.ts +1 -2
- package/dist/view/renderer.d.ts +12 -0
- package/dist/view/view.d.ts +1 -4
- package/package.json +2 -2
- package/src/conversion/upcasthelpers.js +0 -7
- package/src/dev-utils/model.d.ts +2 -0
- package/src/dev-utils/model.js +4 -2
- package/src/dev-utils/utils.js +7 -0
- package/src/dev-utils/view.d.ts +1 -0
- package/src/dev-utils/view.js +3 -0
- package/src/index.d.ts +3 -1
- package/src/index.js +2 -0
- package/src/model/model.js +1 -5
- package/src/model/schema.d.ts +149 -51
- package/src/model/schema.js +200 -70
- package/src/model/utils/insertcontent.js +21 -65
- package/src/view/domconverter.js +13 -9
- package/src/view/observer/compositionobserver.js +2 -0
- package/src/view/observer/focusobserver.d.ts +12 -0
- package/src/view/observer/focusobserver.js +55 -25
- package/src/view/observer/inputobserver.js +7 -5
- package/src/view/observer/mutationobserver.d.ts +34 -5
- package/src/view/observer/mutationobserver.js +8 -11
- package/src/view/observer/selectionobserver.d.ts +1 -2
- package/src/view/observer/selectionobserver.js +27 -9
- package/src/view/renderer.d.ts +12 -0
- package/src/view/renderer.js +111 -63
- package/src/view/view.d.ts +1 -4
- package/src/view/view.js +9 -0
package/src/view/renderer.js
CHANGED
|
@@ -60,6 +60,7 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
60
60
|
this.selection = selection;
|
|
61
61
|
this.set('isFocused', false);
|
|
62
62
|
this.set('isSelecting', false);
|
|
63
|
+
this.set('isComposing', false);
|
|
63
64
|
// Rendering the selection and inline filler manipulation should be postponed in (non-Android) Blink until the user finishes
|
|
64
65
|
// creating the selection in DOM to avoid accidental selection collapsing
|
|
65
66
|
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
|
|
@@ -71,12 +72,6 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
71
72
|
}
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
|
-
this.set('isComposing', false);
|
|
75
|
-
this.on('change:isComposing', () => {
|
|
76
|
-
if (!this.isComposing) {
|
|
77
|
-
this.render();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
75
|
}
|
|
81
76
|
/**
|
|
82
77
|
* Marks a view node to be updated in the DOM by {@link #render `render()`}.
|
|
@@ -138,15 +133,15 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
138
133
|
// and we should not do it because the difference between view and DOM could lead to position mapping problems.
|
|
139
134
|
if (this.isComposing && !env.isAndroid) {
|
|
140
135
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
141
|
-
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Rendering aborted while isComposing',
|
|
142
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', ''
|
|
136
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Rendering aborted while isComposing.',
|
|
137
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic'
|
|
143
138
|
// @if CK_DEBUG_TYPING // );
|
|
144
139
|
// @if CK_DEBUG_TYPING // }
|
|
145
140
|
return;
|
|
146
141
|
}
|
|
147
142
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
148
143
|
// @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Rendering',
|
|
149
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', ''
|
|
144
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: bold'
|
|
150
145
|
// @if CK_DEBUG_TYPING // );
|
|
151
146
|
// @if CK_DEBUG_TYPING // }
|
|
152
147
|
let inlineFillerPosition = null;
|
|
@@ -432,10 +427,10 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
432
427
|
}
|
|
433
428
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
434
429
|
// @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Update text',
|
|
435
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', ''
|
|
430
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: normal'
|
|
436
431
|
// @if CK_DEBUG_TYPING // );
|
|
437
432
|
// @if CK_DEBUG_TYPING // }
|
|
438
|
-
|
|
433
|
+
this._updateTextNode(domText, expectedText);
|
|
439
434
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
440
435
|
// @if CK_DEBUG_TYPING // console.groupEnd();
|
|
441
436
|
// @if CK_DEBUG_TYPING // }
|
|
@@ -486,7 +481,7 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
486
481
|
}
|
|
487
482
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
488
483
|
// @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Update children',
|
|
489
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', ''
|
|
484
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: normal'
|
|
490
485
|
// @if CK_DEBUG_TYPING // );
|
|
491
486
|
// @if CK_DEBUG_TYPING // }
|
|
492
487
|
// IME on Android inserts a new text node while typing after a link
|
|
@@ -516,6 +511,11 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
516
511
|
// We need to make sure that we update the existing text node and not replace it with another one.
|
|
517
512
|
// The composition and different "language" browser extensions are fragile to text node being completely replaced.
|
|
518
513
|
const actions = this._findUpdateActions(diff, actualDomChildren, expectedDomChildren, areTextNodes);
|
|
514
|
+
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping && actions.every( a => a == 'equal' ) ) {
|
|
515
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Nothing to update.',
|
|
516
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic'
|
|
517
|
+
// @if CK_DEBUG_TYPING // );
|
|
518
|
+
// @if CK_DEBUG_TYPING // }
|
|
519
519
|
let i = 0;
|
|
520
520
|
const nodesToUnbind = new Set();
|
|
521
521
|
// Handle deletions first.
|
|
@@ -527,9 +527,22 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
527
527
|
for (const action of actions) {
|
|
528
528
|
if (action === 'delete') {
|
|
529
529
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
530
|
-
// @if CK_DEBUG_TYPING //
|
|
531
|
-
// @if CK_DEBUG_TYPING //
|
|
532
|
-
// @if CK_DEBUG_TYPING //
|
|
530
|
+
// @if CK_DEBUG_TYPING // const node = actualDomChildren[ i ];
|
|
531
|
+
// @if CK_DEBUG_TYPING // if ( isText( node ) ) {
|
|
532
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Remove text node' +
|
|
533
|
+
// @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: ` +
|
|
534
|
+
// @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( node.data ) }%c (${ node.data.length })`,
|
|
535
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold',
|
|
536
|
+
// @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '', 'color: blue', ''
|
|
537
|
+
// @if CK_DEBUG_TYPING // );
|
|
538
|
+
// @if CK_DEBUG_TYPING // } else {
|
|
539
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Remove element' +
|
|
540
|
+
// @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: `,
|
|
541
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold',
|
|
542
|
+
// @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '',
|
|
543
|
+
// @if CK_DEBUG_TYPING // node
|
|
544
|
+
// @if CK_DEBUG_TYPING // );
|
|
545
|
+
// @if CK_DEBUG_TYPING // }
|
|
533
546
|
// @if CK_DEBUG_TYPING // }
|
|
534
547
|
nodesToUnbind.add(actualDomChildren[i]);
|
|
535
548
|
remove(actualDomChildren[i]);
|
|
@@ -542,25 +555,29 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
542
555
|
for (const action of actions) {
|
|
543
556
|
if (action === 'insert') {
|
|
544
557
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
545
|
-
// @if CK_DEBUG_TYPING //
|
|
546
|
-
// @if CK_DEBUG_TYPING //
|
|
547
|
-
// @if CK_DEBUG_TYPING //
|
|
558
|
+
// @if CK_DEBUG_TYPING // const node = expectedDomChildren[ i ];
|
|
559
|
+
// @if CK_DEBUG_TYPING // if ( isText( node ) ) {
|
|
560
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Insert text node' +
|
|
561
|
+
// @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: ` +
|
|
562
|
+
// @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( node.data ) }%c (${ node.data.length })`,
|
|
563
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold',
|
|
564
|
+
// @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '',
|
|
565
|
+
// @if CK_DEBUG_TYPING // 'color: blue', ''
|
|
566
|
+
// @if CK_DEBUG_TYPING // );
|
|
567
|
+
// @if CK_DEBUG_TYPING // } else {
|
|
568
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Insert element:',
|
|
569
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: normal',
|
|
570
|
+
// @if CK_DEBUG_TYPING // node
|
|
571
|
+
// @if CK_DEBUG_TYPING // );
|
|
572
|
+
// @if CK_DEBUG_TYPING // }
|
|
548
573
|
// @if CK_DEBUG_TYPING // }
|
|
549
574
|
insertAt(domElement, i, expectedDomChildren[i]);
|
|
550
575
|
i++;
|
|
551
576
|
}
|
|
552
|
-
// Update the existing text node data.
|
|
577
|
+
// Update the existing text node data.
|
|
553
578
|
else if (action === 'update') {
|
|
554
|
-
|
|
555
|
-
// @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Update text node',
|
|
556
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', ''
|
|
557
|
-
// @if CK_DEBUG_TYPING // );
|
|
558
|
-
// @if CK_DEBUG_TYPING // }
|
|
559
|
-
updateTextNode(actualDomChildren[i], expectedDomChildren[i].data);
|
|
579
|
+
this._updateTextNode(actualDomChildren[i], expectedDomChildren[i].data);
|
|
560
580
|
i++;
|
|
561
|
-
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
562
|
-
// @if CK_DEBUG_TYPING // console.groupEnd();
|
|
563
|
-
// @if CK_DEBUG_TYPING // }
|
|
564
581
|
}
|
|
565
582
|
else if (action === 'equal') {
|
|
566
583
|
// Force updating text nodes inside elements which did not change and do not need to be re-rendered (#1125).
|
|
@@ -639,6 +656,63 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
639
656
|
return newActions.concat(diff(actualSlice, expectedSlice, comparator)
|
|
640
657
|
.map(action => action === 'equal' ? 'update' : action));
|
|
641
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Checks if text needs to be updated and possibly updates it by removing and inserting only parts
|
|
661
|
+
* of the data from the existing text node to reduce impact on the IME composition.
|
|
662
|
+
*
|
|
663
|
+
* @param domText DOM text node to update.
|
|
664
|
+
* @param expectedText The expected data of a text node.
|
|
665
|
+
*/
|
|
666
|
+
_updateTextNode(domText, expectedText) {
|
|
667
|
+
const actualText = domText.data;
|
|
668
|
+
if (actualText == expectedText) {
|
|
669
|
+
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
670
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Text node does not need update:%c ' +
|
|
671
|
+
// @if CK_DEBUG_TYPING // `${ _escapeTextNodeData( actualText ) }%c (${ actualText.length })`,
|
|
672
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic', 'color: blue', ''
|
|
673
|
+
// @if CK_DEBUG_TYPING // );
|
|
674
|
+
// @if CK_DEBUG_TYPING // }
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
// Our approach to interleaving space character with NBSP might differ with the one implemented by the browser.
|
|
678
|
+
// Avoid modifying the text node in the DOM if only NBSPs and spaces are interchanged.
|
|
679
|
+
// We should avoid DOM modifications while composing to avoid breakage of composition.
|
|
680
|
+
// See: https://github.com/ckeditor/ckeditor5/issues/13994.
|
|
681
|
+
if (env.isAndroid && this.isComposing && actualText.replace(/\u00A0/g, ' ') == expectedText.replace(/\u00A0/g, ' ')) {
|
|
682
|
+
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
683
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Text node ignore NBSP changes while composing: ' +
|
|
684
|
+
// @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( actualText ) }%c (${ actualText.length }) ->` +
|
|
685
|
+
// @if CK_DEBUG_TYPING // ` %c${ _escapeTextNodeData( expectedText ) }%c (${ expectedText.length })`,
|
|
686
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic', 'color: blue', '', 'color: blue', ''
|
|
687
|
+
// @if CK_DEBUG_TYPING // );
|
|
688
|
+
// @if CK_DEBUG_TYPING // }
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
692
|
+
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Update text node' +
|
|
693
|
+
// @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: ` +
|
|
694
|
+
// @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( actualText ) }%c (${ actualText.length }) ->` +
|
|
695
|
+
// @if CK_DEBUG_TYPING // ` %c${ _escapeTextNodeData( expectedText ) }%c (${ expectedText.length })`,
|
|
696
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', this.isComposing ? 'color: red; font-weight: bold' : '',
|
|
697
|
+
// @if CK_DEBUG_TYPING // 'color: blue', '', 'color: blue', ''
|
|
698
|
+
// @if CK_DEBUG_TYPING // );
|
|
699
|
+
// @if CK_DEBUG_TYPING // }
|
|
700
|
+
this._updateTextNodeInternal(domText, expectedText);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Part of the `_updateTextNode` method extracted for easier testing.
|
|
704
|
+
*/
|
|
705
|
+
_updateTextNodeInternal(domText, expectedText) {
|
|
706
|
+
const actions = fastDiff(domText.data, expectedText);
|
|
707
|
+
for (const action of actions) {
|
|
708
|
+
if (action.type === 'insert') {
|
|
709
|
+
domText.insertData(action.index, action.values.join(''));
|
|
710
|
+
}
|
|
711
|
+
else { // 'delete'
|
|
712
|
+
domText.deleteData(action.index, action.howMany);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
642
716
|
/**
|
|
643
717
|
* Marks text nodes to be synchronized.
|
|
644
718
|
*
|
|
@@ -745,7 +819,7 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
|
|
|
745
819
|
const focus = this.domConverter.viewPositionToDom(this.selection.focus);
|
|
746
820
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
747
821
|
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Update DOM selection:',
|
|
748
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', '', anchor, focus
|
|
822
|
+
// @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', '', anchor, focus
|
|
749
823
|
// @if CK_DEBUG_TYPING // );
|
|
750
824
|
// @if CK_DEBUG_TYPING // }
|
|
751
825
|
domSelection.setBaseAndExtent(anchor.parent, anchor.offset, focus.parent, focus.offset);
|
|
@@ -969,37 +1043,11 @@ function createFakeSelectionContainer(domDocument) {
|
|
|
969
1043
|
container.textContent = '\u00A0';
|
|
970
1044
|
return container;
|
|
971
1045
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const actualText = domText.data;
|
|
981
|
-
if (actualText == expectedText) {
|
|
982
|
-
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
983
|
-
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Text node does not need update:',
|
|
984
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', '',
|
|
985
|
-
// @if CK_DEBUG_TYPING // `"${ domText.data }" (${ domText.data.length })`
|
|
986
|
-
// @if CK_DEBUG_TYPING // );
|
|
987
|
-
// @if CK_DEBUG_TYPING // }
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
991
|
-
// @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Update text node:',
|
|
992
|
-
// @if CK_DEBUG_TYPING // 'color: green;font-weight: bold', '',
|
|
993
|
-
// @if CK_DEBUG_TYPING // `"${ domText.data }" (${ domText.data.length }) -> "${ expectedText }" (${ expectedText.length })`
|
|
994
|
-
// @if CK_DEBUG_TYPING // );
|
|
995
|
-
// @if CK_DEBUG_TYPING // }
|
|
996
|
-
const actions = fastDiff(actualText, expectedText);
|
|
997
|
-
for (const action of actions) {
|
|
998
|
-
if (action.type === 'insert') {
|
|
999
|
-
domText.insertData(action.index, action.values.join(''));
|
|
1000
|
-
}
|
|
1001
|
-
else { // 'delete'
|
|
1002
|
-
domText.deleteData(action.index, action.howMany);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1046
|
+
// @if CK_DEBUG_TYPING // function _escapeTextNodeData( text ) {
|
|
1047
|
+
// @if CK_DEBUG_TYPING // const escapedText = text
|
|
1048
|
+
// @if CK_DEBUG_TYPING // .replace( /&/g, '&' )
|
|
1049
|
+
// @if CK_DEBUG_TYPING // .replace( /\u00A0/g, ' ' )
|
|
1050
|
+
// @if CK_DEBUG_TYPING // .replace( /\u2060/g, '⁠' );
|
|
1051
|
+
// @if CK_DEBUG_TYPING //
|
|
1052
|
+
// @if CK_DEBUG_TYPING // return `"${ escapedText }"`;
|
|
1053
|
+
// @if CK_DEBUG_TYPING // }
|
package/src/view/view.d.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Document from './document.js';
|
|
9
9
|
import DowncastWriter from './downcastwriter.js';
|
|
10
|
-
import Renderer from './renderer.js';
|
|
11
10
|
import DomConverter from './domconverter.js';
|
|
12
11
|
import Position, { type PositionOffset } from './position.js';
|
|
13
12
|
import Range from './range.js';
|
|
@@ -95,10 +94,8 @@ export default class View extends /* #__PURE__ */ View_base {
|
|
|
95
94
|
hasDomSelection: boolean;
|
|
96
95
|
/**
|
|
97
96
|
* Instance of the {@link module:engine/view/renderer~Renderer renderer}.
|
|
98
|
-
*
|
|
99
|
-
* @internal
|
|
100
97
|
*/
|
|
101
|
-
readonly _renderer
|
|
98
|
+
private readonly _renderer;
|
|
102
99
|
/**
|
|
103
100
|
* A DOM root attributes cache. It saves the initial values of DOM root attributes before the DOM element
|
|
104
101
|
* is {@link module:engine/view/view~View#attachDomRoot attached} to the view so later on, when
|
package/src/view/view.js
CHANGED
|
@@ -144,6 +144,15 @@ export default class View extends /* #__PURE__ */ ObservableMixin() {
|
|
|
144
144
|
}
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
+
// Listen to external content mutations (directly in the DOM) and mark them to get verified by the renderer.
|
|
148
|
+
this.listenTo(this.document, 'mutations', (evt, { mutations }) => {
|
|
149
|
+
mutations.forEach(mutation => this._renderer.markToSync(mutation.type, mutation.node));
|
|
150
|
+
}, { priority: 'low' });
|
|
151
|
+
// After all mutated nodes were marked to sync we can trigger view to DOM synchronization
|
|
152
|
+
// to make sure the DOM structure matches the view.
|
|
153
|
+
this.listenTo(this.document, 'mutations', () => {
|
|
154
|
+
this.forceRender();
|
|
155
|
+
}, { priority: 'lowest' });
|
|
147
156
|
}
|
|
148
157
|
/**
|
|
149
158
|
* Attaches a DOM root element to the view element and enable all observers on that element.
|