@ckeditor/ckeditor5-engine 44.3.0 → 45.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.
Files changed (93) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/index.js +304 -128
  3. package/dist/index.js.map +1 -1
  4. package/package.json +3 -3
  5. package/src/controller/datacontroller.js +40 -0
  6. package/src/controller/editingcontroller.js +16 -0
  7. package/src/conversion/conversion.js +6 -4
  8. package/src/conversion/conversionhelpers.js +1 -0
  9. package/src/conversion/downcastdispatcher.js +10 -0
  10. package/src/conversion/downcasthelpers.js +1 -1
  11. package/src/conversion/mapper.js +92 -95
  12. package/src/conversion/modelconsumable.js +13 -15
  13. package/src/conversion/upcastdispatcher.js +28 -24
  14. package/src/conversion/upcasthelpers.js +1 -1
  15. package/src/conversion/viewconsumable.js +19 -20
  16. package/src/dataprocessor/htmldataprocessor.js +13 -1
  17. package/src/dataprocessor/xmldataprocessor.js +21 -1
  18. package/src/dev-utils/model.js +1 -1
  19. package/src/dev-utils/operationreplayer.js +3 -0
  20. package/src/dev-utils/utils.js +35 -1
  21. package/src/dev-utils/view.js +13 -0
  22. package/src/model/batch.js +20 -0
  23. package/src/model/differ.js +92 -88
  24. package/src/model/document.d.ts +1 -3
  25. package/src/model/document.js +38 -1
  26. package/src/model/documentfragment.js +10 -10
  27. package/src/model/documentselection.js +44 -32
  28. package/src/model/element.js +8 -4
  29. package/src/model/history.js +31 -33
  30. package/src/model/markercollection.js +25 -7
  31. package/src/model/model.js +21 -0
  32. package/src/model/node.js +22 -18
  33. package/src/model/nodelist.js +12 -12
  34. package/src/model/operation/attributeoperation.js +25 -1
  35. package/src/model/operation/detachoperation.js +8 -0
  36. package/src/model/operation/insertoperation.js +18 -0
  37. package/src/model/operation/markeroperation.js +29 -0
  38. package/src/model/operation/mergeoperation.js +16 -0
  39. package/src/model/operation/moveoperation.js +12 -0
  40. package/src/model/operation/operation.js +19 -0
  41. package/src/model/operation/renameoperation.js +12 -0
  42. package/src/model/operation/rootattributeoperation.js +20 -0
  43. package/src/model/operation/rootoperation.js +16 -0
  44. package/src/model/operation/splitoperation.js +19 -0
  45. package/src/model/operation/transform.js +5 -0
  46. package/src/model/position.js +40 -0
  47. package/src/model/range.js +8 -0
  48. package/src/model/rootelement.js +18 -10
  49. package/src/model/schema.js +25 -23
  50. package/src/model/selection.js +10 -10
  51. package/src/model/text.js +6 -0
  52. package/src/model/textproxy.js +12 -0
  53. package/src/model/treewalker.js +49 -0
  54. package/src/model/utils/insertcontent.js +60 -25
  55. package/src/model/writer.js +8 -0
  56. package/src/view/attributeelement.js +23 -23
  57. package/src/view/datatransfer.js +8 -0
  58. package/src/view/document.js +21 -4
  59. package/src/view/documentfragment.js +13 -9
  60. package/src/view/documentselection.js +4 -0
  61. package/src/view/domconverter.d.ts +6 -1
  62. package/src/view/domconverter.js +109 -48
  63. package/src/view/downcastwriter.js +14 -10
  64. package/src/view/element.js +29 -17
  65. package/src/view/matcher.js +1 -1
  66. package/src/view/node.js +9 -1
  67. package/src/view/observer/bubblingeventinfo.js +12 -0
  68. package/src/view/observer/clickobserver.js +4 -7
  69. package/src/view/observer/compositionobserver.js +14 -12
  70. package/src/view/observer/domeventdata.js +17 -1
  71. package/src/view/observer/domeventobserver.js +10 -13
  72. package/src/view/observer/fakeselectionobserver.d.ts +1 -1
  73. package/src/view/observer/fakeselectionobserver.js +5 -1
  74. package/src/view/observer/focusobserver.js +65 -14
  75. package/src/view/observer/inputobserver.js +33 -26
  76. package/src/view/observer/keyobserver.js +4 -7
  77. package/src/view/observer/mouseobserver.js +4 -7
  78. package/src/view/observer/mutationobserver.js +23 -6
  79. package/src/view/observer/observer.js +12 -4
  80. package/src/view/observer/selectionobserver.d.ts +6 -1
  81. package/src/view/observer/selectionobserver.js +94 -17
  82. package/src/view/observer/touchobserver.js +4 -7
  83. package/src/view/position.js +8 -0
  84. package/src/view/range.js +8 -0
  85. package/src/view/renderer.js +142 -70
  86. package/src/view/selection.js +16 -0
  87. package/src/view/stylesmap.js +26 -11
  88. package/src/view/text.js +6 -0
  89. package/src/view/textproxy.js +12 -0
  90. package/src/view/tokenlist.js +4 -6
  91. package/src/view/treewalker.js +42 -0
  92. package/src/view/upcastwriter.js +5 -1
  93. package/src/view/view.js +51 -33
@@ -10,7 +10,7 @@ import Observer from './observer.js';
10
10
  import MutationObserver from './mutationobserver.js';
11
11
  import FocusObserver from './focusobserver.js';
12
12
  import { env } from '@ckeditor/ckeditor5-utils';
13
- import { debounce } from 'lodash-es';
13
+ import { debounce } from 'es-toolkit/compat';
14
14
  /**
15
15
  * Selection observer class observes selection changes in the document. If a selection changes on the document this
16
16
  * observer checks if the DOM selection is different from the {@link module:engine/view/document~Document#selection view selection}.
@@ -22,19 +22,81 @@ import { debounce } from 'lodash-es';
22
22
  * Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
23
23
  */
24
24
  export default class SelectionObserver extends Observer {
25
+ /**
26
+ * Instance of the mutation observer. Selection observer calls
27
+ * {@link module:engine/view/observer/mutationobserver~MutationObserver#flush} to ensure that the mutations will be handled
28
+ * before the {@link module:engine/view/document~Document#event:selectionChange} event is fired.
29
+ */
30
+ mutationObserver;
31
+ /**
32
+ * Instance of the focus observer. Focus observer calls
33
+ * {@link module:engine/view/observer/focusobserver~FocusObserver#flush} to mark the latest focus change as complete.
34
+ */
35
+ focusObserver;
36
+ /**
37
+ * Reference to the view {@link module:engine/view/documentselection~DocumentSelection} object used to compare
38
+ * new selection with it.
39
+ */
40
+ selection;
41
+ /**
42
+ * Reference to the {@link module:engine/view/view~View#domConverter}.
43
+ */
44
+ domConverter;
45
+ /**
46
+ * A set of documents which have added `selectionchange` listener to avoid adding a listener twice to the same
47
+ * document.
48
+ */
49
+ _documents = new WeakSet();
50
+ /**
51
+ * Fires debounced event `selectionChangeDone`. It uses `es-toolkit#debounce` method to delay function call.
52
+ */
53
+ _fireSelectionChangeDoneDebounced;
54
+ /**
55
+ * When called, starts clearing the {@link #_loopbackCounter} counter in time intervals. When the number of selection
56
+ * changes exceeds a certain limit within the interval of time, the observer will not fire `selectionChange` but warn about
57
+ * possible infinite selection loop.
58
+ */
59
+ _clearInfiniteLoopInterval;
60
+ /**
61
+ * Unlocks the `isSelecting` state of the view document in case the selection observer did not record this fact
62
+ * correctly (for whatever reason). It is a safeguard (paranoid check), that returns document to the normal state
63
+ * after a certain period of time (debounced, postponed by each selectionchange event).
64
+ */
65
+ _documentIsSelectingInactivityTimeoutDebounced;
66
+ /**
67
+ * Private property to check if the code does not enter infinite loop.
68
+ */
69
+ _loopbackCounter = 0;
70
+ /**
71
+ * A set of DOM documents that have a pending selection change.
72
+ * Pending selection change is recorded while selection change event is detected on non focused editable.
73
+ */
74
+ _pendingSelectionChange = new Set();
25
75
  constructor(view) {
26
76
  super(view);
27
77
  this.mutationObserver = view.getObserver(MutationObserver);
28
78
  this.focusObserver = view.getObserver(FocusObserver);
29
79
  this.selection = this.document.selection;
30
80
  this.domConverter = view.domConverter;
31
- this._documents = new WeakSet();
32
81
  this._fireSelectionChangeDoneDebounced = debounce(data => {
33
82
  this.document.fire('selectionChangeDone', data);
34
83
  }, 200);
35
84
  this._clearInfiniteLoopInterval = setInterval(() => this._clearInfiniteLoop(), 1000);
36
85
  this._documentIsSelectingInactivityTimeoutDebounced = debounce(() => (this.document.isSelecting = false), 5000);
37
- this._loopbackCounter = 0;
86
+ this.view.document.on('change:isFocused', (evt, name, isFocused) => {
87
+ if (isFocused && this._pendingSelectionChange.size) {
88
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
89
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
90
+ // @if CK_DEBUG_TYPING // 'Flush pending selection change'
91
+ // @if CK_DEBUG_TYPING // ) );
92
+ // @if CK_DEBUG_TYPING // }
93
+ // Iterate over a copy of set because it is modified in selection change handler.
94
+ for (const domDocument of Array.from(this._pendingSelectionChange)) {
95
+ this._handleSelectionChange(domDocument);
96
+ }
97
+ this._pendingSelectionChange.clear();
98
+ }
99
+ });
38
100
  }
39
101
  /**
40
102
  * @inheritDoc
@@ -74,20 +136,22 @@ export default class SelectionObserver extends Observer {
74
136
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
75
137
  // @if CK_DEBUG_TYPING // _debouncedLine();
76
138
  // @if CK_DEBUG_TYPING // const domSelection = domDocument.defaultView!.getSelection();
77
- // @if CK_DEBUG_TYPING // console.group( '%c[SelectionObserver]%c selectionchange', 'color: green', ''
78
- // @if CK_DEBUG_TYPING // );
79
- // @if CK_DEBUG_TYPING // console.info( '%c[SelectionObserver]%c DOM Selection:', 'font-weight: bold; color: green', '',
139
+ // @if CK_DEBUG_TYPING // console.group( ..._buildLogMessage( this, 'SelectionObserver',
140
+ // @if CK_DEBUG_TYPING // 'selectionchange'
141
+ // @if CK_DEBUG_TYPING // ) );
142
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
143
+ // @if CK_DEBUG_TYPING // 'DOM Selection:',
80
144
  // @if CK_DEBUG_TYPING // { node: domSelection!.anchorNode, offset: domSelection!.anchorOffset },
81
145
  // @if CK_DEBUG_TYPING // { node: domSelection!.focusNode, offset: domSelection!.focusOffset }
82
- // @if CK_DEBUG_TYPING // );
146
+ // @if CK_DEBUG_TYPING // ) );
83
147
  // @if CK_DEBUG_TYPING // }
84
148
  // The Renderer is disabled while composing on non-android browsers, so we can't update the view selection
85
149
  // because the DOM and view tree drifted apart. Position mapping could fail because of it.
86
150
  if (this.document.isComposing && !env.isAndroid) {
87
151
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
88
- // @if CK_DEBUG_TYPING // console.info( '%c[SelectionObserver]%c Selection change ignored (isComposing)',
89
- // @if CK_DEBUG_TYPING // 'font-weight: bold; color: green', ''
90
- // @if CK_DEBUG_TYPING // );
152
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
153
+ // @if CK_DEBUG_TYPING // 'Selection change ignored (isComposing)'
154
+ // @if CK_DEBUG_TYPING // ) );
91
155
  // @if CK_DEBUG_TYPING // console.groupEnd();
92
156
  // @if CK_DEBUG_TYPING // }
93
157
  return;
@@ -106,12 +170,14 @@ export default class SelectionObserver extends Observer {
106
170
  this.listenTo(this.view.document, 'compositionstart', () => {
107
171
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
108
172
  // @if CK_DEBUG_TYPING // const domSelection = domDocument.defaultView!.getSelection();
109
- // @if CK_DEBUG_TYPING // console.group( '%c[SelectionObserver]%c update selection on compositionstart', 'color: green', ''
110
- // @if CK_DEBUG_TYPING // );
111
- // @if CK_DEBUG_TYPING // console.info( '%c[SelectionObserver]%c DOM Selection:', 'font-weight: bold; color: green', '',
173
+ // @if CK_DEBUG_TYPING // console.group( ..._buildLogMessage( this, 'SelectionObserver',
174
+ // @if CK_DEBUG_TYPING // 'update selection on compositionstart'
175
+ // @if CK_DEBUG_TYPING // ) );
176
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
177
+ // @if CK_DEBUG_TYPING // 'DOM Selection:',
112
178
  // @if CK_DEBUG_TYPING // { node: domSelection!.anchorNode, offset: domSelection!.anchorOffset },
113
179
  // @if CK_DEBUG_TYPING // { node: domSelection!.focusNode, offset: domSelection!.focusOffset }
114
- // @if CK_DEBUG_TYPING // );
180
+ // @if CK_DEBUG_TYPING // ) );
115
181
  // @if CK_DEBUG_TYPING // }
116
182
  this._handleSelectionChange(domDocument);
117
183
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
@@ -171,6 +237,17 @@ export default class SelectionObserver extends Observer {
171
237
  this.view.hasDomSelection = true;
172
238
  // Mark the latest focus change as complete (we got new selection after the focus so the selection is in the focused element).
173
239
  this.focusObserver.flush();
240
+ // Ignore selection change as the editable is not focused.
241
+ if (!this.view.document.isFocused) {
242
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
243
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
244
+ // @if CK_DEBUG_TYPING // 'Ignore selection change while editable is not focused'
245
+ // @if CK_DEBUG_TYPING // ) );
246
+ // @if CK_DEBUG_TYPING // }
247
+ this._pendingSelectionChange.add(domDocument);
248
+ return;
249
+ }
250
+ this._pendingSelectionChange.delete(domDocument);
174
251
  if (this.selection.isEqual(newViewSelection) && this.domConverter.isDomSelectionCorrect(domSelection)) {
175
252
  return;
176
253
  }
@@ -197,10 +274,10 @@ export default class SelectionObserver extends Observer {
197
274
  domSelection
198
275
  };
199
276
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
200
- // @if CK_DEBUG_TYPING // console.info( '%c[SelectionObserver]%c Fire selection change:',
201
- // @if CK_DEBUG_TYPING // 'font-weight: bold; color: green', '',
277
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
278
+ // @if CK_DEBUG_TYPING // 'Fire selection change:',
202
279
  // @if CK_DEBUG_TYPING // newViewSelection.getFirstRange()
203
- // @if CK_DEBUG_TYPING // );
280
+ // @if CK_DEBUG_TYPING // ) );
204
281
  // @if CK_DEBUG_TYPING // }
205
282
  // Prepare data for new selection and fire appropriate events.
206
283
  this.document.fire('selectionChange', data);
@@ -13,13 +13,10 @@ import DomEventObserver from './domeventobserver.js';
13
13
  * {@link module:engine/view/view~View} by {@link module:engine/view/view~View#addObserver} method.
14
14
  */
15
15
  export default class TouchObserver extends DomEventObserver {
16
- constructor() {
17
- super(...arguments);
18
- /**
19
- * @inheritDoc
20
- */
21
- this.domEventType = ['touchstart', 'touchend', 'touchmove'];
22
- }
16
+ /**
17
+ * @inheritDoc
18
+ */
19
+ domEventType = ['touchstart', 'touchend', 'touchmove'];
23
20
  /**
24
21
  * @inheritDoc
25
22
  */
@@ -19,6 +19,14 @@ import { default as TreeWalker } from './treewalker.js';
19
19
  * * {@link module:engine/view/upcastwriter~UpcastWriter}
20
20
  */
21
21
  export default class Position extends TypeCheckable {
22
+ /**
23
+ * Position parent.
24
+ */
25
+ parent;
26
+ /**
27
+ * Position offset.
28
+ */
29
+ offset;
22
30
  /**
23
31
  * Creates a position.
24
32
  *
package/src/view/range.js CHANGED
@@ -18,6 +18,14 @@ import { default as TreeWalker } from './treewalker.js';
18
18
  * * {@link module:engine/view/upcastwriter~UpcastWriter}
19
19
  */
20
20
  export default class Range extends TypeCheckable {
21
+ /**
22
+ * Start position.
23
+ */
24
+ start;
25
+ /**
26
+ * End position.
27
+ */
28
+ end;
21
29
  /**
22
30
  * Creates a range spanning from `start` position to `end` position.
23
31
  *
@@ -9,6 +9,7 @@ import ViewText from './text.js';
9
9
  import ViewPosition from './position.js';
10
10
  import { INLINE_FILLER, INLINE_FILLER_LENGTH, startsWithFiller, isInlineFiller } from './filler.js';
11
11
  import { CKEditorError, ObservableMixin, diff, env, fastDiff, insertAt, isComment, isNode, isText, remove, indexOf } from '@ckeditor/ckeditor5-utils';
12
+ // @if CK_DEBUG_TYPING // const { _buildLogMessage } = require( '../dev-utils/utils.js' );
12
13
  import '../../theme/renderer.css';
13
14
  /**
14
15
  * Renderer is responsible for updating the DOM structure and the DOM selection based on
@@ -24,6 +25,38 @@ import '../../theme/renderer.css';
24
25
  * to and from the DOM.
25
26
  */
26
27
  export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
28
+ /**
29
+ * Set of DOM Documents instances.
30
+ */
31
+ domDocuments = new Set();
32
+ /**
33
+ * Converter instance.
34
+ */
35
+ domConverter;
36
+ /**
37
+ * Set of nodes which attributes changed and may need to be rendered.
38
+ */
39
+ markedAttributes = new Set();
40
+ /**
41
+ * Set of elements which child lists changed and may need to be rendered.
42
+ */
43
+ markedChildren = new Set();
44
+ /**
45
+ * Set of text nodes which text data changed and may need to be rendered.
46
+ */
47
+ markedTexts = new Set();
48
+ /**
49
+ * View selection. Renderer updates DOM selection based on the view selection.
50
+ */
51
+ selection;
52
+ /**
53
+ * The text node in which the inline filler was rendered.
54
+ */
55
+ _inlineFiller = null;
56
+ /**
57
+ * DOM element containing fake selection.
58
+ */
59
+ _fakeSelectionContainer = null;
27
60
  /**
28
61
  * Creates a renderer instance.
29
62
  *
@@ -32,30 +65,6 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
32
65
  */
33
66
  constructor(domConverter, selection) {
34
67
  super();
35
- /**
36
- * Set of DOM Documents instances.
37
- */
38
- this.domDocuments = new Set();
39
- /**
40
- * Set of nodes which attributes changed and may need to be rendered.
41
- */
42
- this.markedAttributes = new Set();
43
- /**
44
- * Set of elements which child lists changed and may need to be rendered.
45
- */
46
- this.markedChildren = new Set();
47
- /**
48
- * Set of text nodes which text data changed and may need to be rendered.
49
- */
50
- this.markedTexts = new Set();
51
- /**
52
- * The text node in which the inline filler was rendered.
53
- */
54
- this._inlineFiller = null;
55
- /**
56
- * DOM element containing fake selection.
57
- */
58
- this._fakeSelectionContainer = null;
59
68
  this.domConverter = domConverter;
60
69
  this.selection = selection;
61
70
  this.set('isFocused', false);
@@ -133,16 +142,18 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
133
142
  // and we should not do it because the difference between view and DOM could lead to position mapping problems.
134
143
  if (this.isComposing && !env.isAndroid) {
135
144
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
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'
138
- // @if CK_DEBUG_TYPING // );
145
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
146
+ // @if CK_DEBUG_TYPING // '%cRendering aborted while isComposing.',
147
+ // @if CK_DEBUG_TYPING // 'font-style: italic'
148
+ // @if CK_DEBUG_TYPING // ) );
139
149
  // @if CK_DEBUG_TYPING // }
140
150
  return;
141
151
  }
142
152
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
143
- // @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Rendering',
144
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: bold'
145
- // @if CK_DEBUG_TYPING // );
153
+ // @if CK_DEBUG_TYPING // console.group( ..._buildLogMessage( this, 'Renderer',
154
+ // @if CK_DEBUG_TYPING // '%cRendering',
155
+ // @if CK_DEBUG_TYPING // 'font-weight: bold'
156
+ // @if CK_DEBUG_TYPING // ) );
146
157
  // @if CK_DEBUG_TYPING // }
147
158
  let inlineFillerPosition = null;
148
159
  const isInlineFillerRenderingPossible = env.isBlink && !env.isAndroid ? !this.isSelecting : true;
@@ -426,9 +437,10 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
426
437
  expectedText = INLINE_FILLER + expectedText;
427
438
  }
428
439
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
429
- // @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Update text',
430
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: normal'
431
- // @if CK_DEBUG_TYPING // );
440
+ // @if CK_DEBUG_TYPING // console.group( ..._buildLogMessage( this, 'Renderer',
441
+ // @if CK_DEBUG_TYPING // '%cUpdate text',
442
+ // @if CK_DEBUG_TYPING // 'font-weight: normal'
443
+ // @if CK_DEBUG_TYPING // ) );
432
444
  // @if CK_DEBUG_TYPING // }
433
445
  this._updateTextNode(domText, expectedText);
434
446
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
@@ -485,9 +497,10 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
485
497
  return;
486
498
  }
487
499
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
488
- // @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Update children',
489
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: normal'
490
- // @if CK_DEBUG_TYPING // );
500
+ // @if CK_DEBUG_TYPING // console.group( ..._buildLogMessage( this, 'Renderer',
501
+ // @if CK_DEBUG_TYPING // '%cUpdate children',
502
+ // @if CK_DEBUG_TYPING // 'font-weight: normal'
503
+ // @if CK_DEBUG_TYPING // ) );
491
504
  // @if CK_DEBUG_TYPING // }
492
505
  // IME on Android inserts a new text node while typing after a link
493
506
  // instead of updating an existing text node that follows the link.
@@ -517,9 +530,10 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
517
530
  // The composition and different "language" browser extensions are fragile to text node being completely replaced.
518
531
  const actions = this._findUpdateActions(diff, actualDomChildren, expectedDomChildren, areTextNodes);
519
532
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping && actions.every( a => a == 'equal' ) ) {
520
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Nothing to update.',
521
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic'
522
- // @if CK_DEBUG_TYPING // );
533
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
534
+ // @if CK_DEBUG_TYPING // '%cNothing to update.',
535
+ // @if CK_DEBUG_TYPING // 'font-style: italic'
536
+ // @if CK_DEBUG_TYPING // ) );
523
537
  // @if CK_DEBUG_TYPING // }
524
538
  let i = 0;
525
539
  const nodesToUnbind = new Set();
@@ -534,19 +548,20 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
534
548
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
535
549
  // @if CK_DEBUG_TYPING // const node = actualDomChildren[ i ];
536
550
  // @if CK_DEBUG_TYPING // if ( isText( node ) ) {
537
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Remove text node' +
551
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
552
+ // @if CK_DEBUG_TYPING // '%cRemove text node' +
538
553
  // @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: ` +
539
554
  // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( node.data ) }%c (${ node.data.length })`,
540
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold',
541
- // @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '', 'color: blue', ''
542
- // @if CK_DEBUG_TYPING // );
555
+ // @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '',
556
+ // @if CK_DEBUG_TYPING // 'color: blue', ''
557
+ // @if CK_DEBUG_TYPING // ) );
543
558
  // @if CK_DEBUG_TYPING // } else {
544
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Remove element' +
559
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
560
+ // @if CK_DEBUG_TYPING // '%cRemove element' +
545
561
  // @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: `,
546
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold',
547
562
  // @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '',
548
563
  // @if CK_DEBUG_TYPING // node
549
- // @if CK_DEBUG_TYPING // );
564
+ // @if CK_DEBUG_TYPING // ) );
550
565
  // @if CK_DEBUG_TYPING // }
551
566
  // @if CK_DEBUG_TYPING // }
552
567
  nodesToUnbind.add(actualDomChildren[i]);
@@ -562,18 +577,20 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
562
577
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
563
578
  // @if CK_DEBUG_TYPING // const node = expectedDomChildren[ i ];
564
579
  // @if CK_DEBUG_TYPING // if ( isText( node ) ) {
565
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Insert text node' +
580
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
581
+ // @if CK_DEBUG_TYPING // '%cInsert text node' +
566
582
  // @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: ` +
567
583
  // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( node.data ) }%c (${ node.data.length })`,
568
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold',
569
584
  // @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '',
570
- // @if CK_DEBUG_TYPING // 'color: blue', ''
571
- // @if CK_DEBUG_TYPING // );
585
+ // @if CK_DEBUG_TYPING // 'color: blue',
586
+ // @if CK_DEBUG_TYPING // ''
587
+ // @if CK_DEBUG_TYPING // ) );
572
588
  // @if CK_DEBUG_TYPING // } else {
573
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Insert element:',
574
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-weight: normal',
589
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
590
+ // @if CK_DEBUG_TYPING // '%cInsert element:',
591
+ // @if CK_DEBUG_TYPING // 'font-weight: normal',
575
592
  // @if CK_DEBUG_TYPING // node
576
- // @if CK_DEBUG_TYPING // );
593
+ // @if CK_DEBUG_TYPING // ) );
577
594
  // @if CK_DEBUG_TYPING // }
578
595
  // @if CK_DEBUG_TYPING // }
579
596
  insertAt(domElement, i, expectedDomChildren[i]);
@@ -672,10 +689,13 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
672
689
  const actualText = domText.data;
673
690
  if (actualText == expectedText) {
674
691
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
675
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Text node does not need update:%c ' +
692
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
693
+ // @if CK_DEBUG_TYPING // '%cText node does not need update:%c ' +
676
694
  // @if CK_DEBUG_TYPING // `${ _escapeTextNodeData( actualText ) }%c (${ actualText.length })`,
677
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic', 'color: blue', ''
678
- // @if CK_DEBUG_TYPING // );
695
+ // @if CK_DEBUG_TYPING // 'font-style: italic',
696
+ // @if CK_DEBUG_TYPING // 'color: blue',
697
+ // @if CK_DEBUG_TYPING // ''
698
+ // @if CK_DEBUG_TYPING // ) );
679
699
  // @if CK_DEBUG_TYPING // }
680
700
  return;
681
701
  }
@@ -685,22 +705,31 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
685
705
  // See: https://github.com/ckeditor/ckeditor5/issues/13994.
686
706
  if (env.isAndroid && this.isComposing && actualText.replace(/\u00A0/g, ' ') == expectedText.replace(/\u00A0/g, ' ')) {
687
707
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
688
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Text node ignore NBSP changes while composing: ' +
689
- // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( actualText ) }%c (${ actualText.length }) ->` +
690
- // @if CK_DEBUG_TYPING // ` %c${ _escapeTextNodeData( expectedText ) }%c (${ expectedText.length })`,
691
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', 'font-style: italic', 'color: blue', '', 'color: blue', ''
692
- // @if CK_DEBUG_TYPING // );
708
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
709
+ // @if CK_DEBUG_TYPING // '%cText node ignore NBSP changes while composing: ' +
710
+ // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( actualText ) }%c (${ actualText.length }) -> ` +
711
+ // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( expectedText ) }%c (${ expectedText.length })`,
712
+ // @if CK_DEBUG_TYPING // 'font-style: italic',
713
+ // @if CK_DEBUG_TYPING // 'color: blue',
714
+ // @if CK_DEBUG_TYPING // '',
715
+ // @if CK_DEBUG_TYPING // 'color: blue',
716
+ // @if CK_DEBUG_TYPING // ''
717
+ // @if CK_DEBUG_TYPING // ) );
693
718
  // @if CK_DEBUG_TYPING // }
694
719
  return;
695
720
  }
696
721
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
697
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Update text node' +
722
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
723
+ // @if CK_DEBUG_TYPING // '%cUpdate text node' +
698
724
  // @if CK_DEBUG_TYPING // `${ this.isComposing ? ' while composing (may break composition)' : '' }: ` +
699
- // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( actualText ) }%c (${ actualText.length }) ->` +
700
- // @if CK_DEBUG_TYPING // ` %c${ _escapeTextNodeData( expectedText ) }%c (${ expectedText.length })`,
701
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', this.isComposing ? 'color: red; font-weight: bold' : '',
702
- // @if CK_DEBUG_TYPING // 'color: blue', '', 'color: blue', ''
703
- // @if CK_DEBUG_TYPING // );
725
+ // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( actualText ) }%c (${ actualText.length }) -> ` +
726
+ // @if CK_DEBUG_TYPING // `%c${ _escapeTextNodeData( expectedText ) }%c (${ expectedText.length })`,
727
+ // @if CK_DEBUG_TYPING // this.isComposing ? 'color: red; font-weight: bold' : '',
728
+ // @if CK_DEBUG_TYPING // 'color: blue',
729
+ // @if CK_DEBUG_TYPING // '',
730
+ // @if CK_DEBUG_TYPING // 'color: blue',
731
+ // @if CK_DEBUG_TYPING // ''
732
+ // @if CK_DEBUG_TYPING // ) );
704
733
  // @if CK_DEBUG_TYPING // }
705
734
  this._updateTextNodeInternal(domText, expectedText);
706
735
  }
@@ -751,6 +780,11 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
751
780
  }
752
781
  // If there is no selection - remove DOM and fake selections.
753
782
  if (this.selection.rangeCount === 0) {
783
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
784
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
785
+ // @if CK_DEBUG_TYPING // 'Update DOM selection: remove all ranges'
786
+ // @if CK_DEBUG_TYPING // ) );
787
+ // @if CK_DEBUG_TYPING // }
754
788
  this._removeDomSelection();
755
789
  this._removeFakeSelection();
756
790
  return;
@@ -758,8 +792,19 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
758
792
  const domRoot = this.domConverter.mapViewToDom(this.selection.editableElement);
759
793
  // Do nothing if there is no focus, or there is no DOM element corresponding to selection's editable element.
760
794
  if (!this.isFocused || !domRoot) {
795
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
796
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
797
+ // @if CK_DEBUG_TYPING // 'Skip updating DOM selection:',
798
+ // @if CK_DEBUG_TYPING // `isFocused: ${ this.isFocused }, hasDomRoot: ${ !!domRoot }`
799
+ // @if CK_DEBUG_TYPING // ) );
800
+ // @if CK_DEBUG_TYPING // }
761
801
  return;
762
802
  }
803
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
804
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
805
+ // @if CK_DEBUG_TYPING // 'Update DOM selection'
806
+ // @if CK_DEBUG_TYPING // ) );
807
+ // @if CK_DEBUG_TYPING // }
763
808
  // Render fake selection - create the fake selection container (if needed) and move DOM selection to it.
764
809
  if (this.selection.isFake) {
765
810
  this._updateFakeSelection(domRoot);
@@ -798,6 +843,11 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
798
843
  domRoot.appendChild(container);
799
844
  }
800
845
  container.textContent = this.selection.fakeSelectionLabel || '\u00A0';
846
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
847
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
848
+ // @if CK_DEBUG_TYPING // 'Set DOM fake selection'
849
+ // @if CK_DEBUG_TYPING // ) );
850
+ // @if CK_DEBUG_TYPING // }
801
851
  const domSelection = domDocument.getSelection();
802
852
  const domRange = domDocument.createRange();
803
853
  domSelection.removeAllRanges();
@@ -813,6 +863,12 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
813
863
  const domSelection = domRoot.ownerDocument.defaultView.getSelection();
814
864
  // Let's check whether DOM selection needs updating at all.
815
865
  if (!this._domSelectionNeedsUpdate(domSelection)) {
866
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
867
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
868
+ // @if CK_DEBUG_TYPING // '%cDOM selection is already correct',
869
+ // @if CK_DEBUG_TYPING // 'font-style: italic;'
870
+ // @if CK_DEBUG_TYPING // ) );
871
+ // @if CK_DEBUG_TYPING // }
816
872
  return;
817
873
  }
818
874
  // Multi-range selection is not available in most browsers, and, at least in Chrome, trying to
@@ -823,9 +879,11 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
823
879
  const anchor = this.domConverter.viewPositionToDom(this.selection.anchor);
824
880
  const focus = this.domConverter.viewPositionToDom(this.selection.focus);
825
881
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
826
- // @if CK_DEBUG_TYPING // console.info( '%c[Renderer]%c Update DOM selection:',
827
- // @if CK_DEBUG_TYPING // 'color: green; font-weight: bold', '', anchor, focus
828
- // @if CK_DEBUG_TYPING // );
882
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
883
+ // @if CK_DEBUG_TYPING // 'Update DOM selection:',
884
+ // @if CK_DEBUG_TYPING // anchor,
885
+ // @if CK_DEBUG_TYPING // focus
886
+ // @if CK_DEBUG_TYPING // ) );
829
887
  // @if CK_DEBUG_TYPING // }
830
888
  domSelection.setBaseAndExtent(anchor.parent, anchor.offset, focus.parent, focus.offset);
831
889
  // Firefox–specific hack (https://github.com/ckeditor/ckeditor5-engine/issues/1439).
@@ -902,12 +960,26 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
902
960
  * Checks if focus needs to be updated and possibly updates it.
903
961
  */
904
962
  _updateFocus() {
963
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
964
+ // @if CK_DEBUG_TYPING // console.group( ..._buildLogMessage( this, 'Renderer',
965
+ // @if CK_DEBUG_TYPING // `update focus: ${ this.isFocused ? 'focused' : 'not focused' }`
966
+ // @if CK_DEBUG_TYPING // ) );
967
+ // @if CK_DEBUG_TYPING // }
905
968
  if (this.isFocused) {
906
969
  const editable = this.selection.editableElement;
970
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
971
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
972
+ // @if CK_DEBUG_TYPING // 'focus editable:',
973
+ // @if CK_DEBUG_TYPING // { editable }
974
+ // @if CK_DEBUG_TYPING // ) );
975
+ // @if CK_DEBUG_TYPING // }
907
976
  if (editable) {
908
977
  this.domConverter.focus(editable);
909
978
  }
910
979
  }
980
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
981
+ // @if CK_DEBUG_TYPING // console.groupEnd();
982
+ // @if CK_DEBUG_TYPING // }
911
983
  }
912
984
  }
913
985
  /**
@@ -24,6 +24,22 @@ import { CKEditorError, EmitterMixin, count, isIterable } from '@ckeditor/ckedit
24
24
  * the {@link module:engine/view/selection~Selection#setTo `Selection#setTo()`} method.
25
25
  */
26
26
  export default class Selection extends /* #__PURE__ */ EmitterMixin(TypeCheckable) {
27
+ /**
28
+ * Stores all ranges that are selected.
29
+ */
30
+ _ranges;
31
+ /**
32
+ * Specifies whether the last added range was added as a backward or forward range.
33
+ */
34
+ _lastRangeBackward;
35
+ /**
36
+ * Specifies whether selection instance is fake.
37
+ */
38
+ _isFake;
39
+ /**
40
+ * Fake selection's label.
41
+ */
42
+ _fakeSelectionLabel;
27
43
  /**
28
44
  * Creates new selection instance.
29
45
  *