@ckeditor/ckeditor5-engine 34.2.0 → 35.1.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 (125) hide show
  1. package/CHANGELOG.md +823 -0
  2. package/LICENSE.md +4 -0
  3. package/package.json +32 -25
  4. package/src/controller/datacontroller.js +467 -561
  5. package/src/controller/editingcontroller.js +168 -204
  6. package/src/conversion/conversion.js +541 -565
  7. package/src/conversion/conversionhelpers.js +24 -28
  8. package/src/conversion/downcastdispatcher.js +457 -686
  9. package/src/conversion/downcasthelpers.js +1583 -1965
  10. package/src/conversion/mapper.js +518 -707
  11. package/src/conversion/modelconsumable.js +240 -283
  12. package/src/conversion/upcastdispatcher.js +372 -718
  13. package/src/conversion/upcasthelpers.js +707 -818
  14. package/src/conversion/viewconsumable.js +524 -581
  15. package/src/dataprocessor/basichtmlwriter.js +12 -16
  16. package/src/dataprocessor/dataprocessor.js +5 -0
  17. package/src/dataprocessor/htmldataprocessor.js +101 -117
  18. package/src/dataprocessor/htmlwriter.js +1 -18
  19. package/src/dataprocessor/xmldataprocessor.js +117 -138
  20. package/src/dev-utils/model.js +260 -352
  21. package/src/dev-utils/operationreplayer.js +106 -126
  22. package/src/dev-utils/utils.js +34 -51
  23. package/src/dev-utils/view.js +632 -753
  24. package/src/index.js +0 -11
  25. package/src/model/batch.js +111 -127
  26. package/src/model/differ.js +988 -1233
  27. package/src/model/document.js +340 -449
  28. package/src/model/documentfragment.js +327 -364
  29. package/src/model/documentselection.js +996 -1189
  30. package/src/model/element.js +306 -410
  31. package/src/model/history.js +224 -262
  32. package/src/model/item.js +5 -0
  33. package/src/model/liveposition.js +84 -145
  34. package/src/model/liverange.js +108 -185
  35. package/src/model/markercollection.js +379 -480
  36. package/src/model/model.js +883 -1034
  37. package/src/model/node.js +419 -463
  38. package/src/model/nodelist.js +175 -201
  39. package/src/model/operation/attributeoperation.js +153 -182
  40. package/src/model/operation/detachoperation.js +64 -83
  41. package/src/model/operation/insertoperation.js +135 -166
  42. package/src/model/operation/markeroperation.js +114 -140
  43. package/src/model/operation/mergeoperation.js +163 -191
  44. package/src/model/operation/moveoperation.js +157 -187
  45. package/src/model/operation/nooperation.js +28 -38
  46. package/src/model/operation/operation.js +106 -125
  47. package/src/model/operation/operationfactory.js +30 -34
  48. package/src/model/operation/renameoperation.js +109 -135
  49. package/src/model/operation/rootattributeoperation.js +155 -188
  50. package/src/model/operation/splitoperation.js +196 -232
  51. package/src/model/operation/transform.js +1833 -2204
  52. package/src/model/operation/utils.js +140 -204
  53. package/src/model/position.js +899 -1053
  54. package/src/model/range.js +910 -1028
  55. package/src/model/rootelement.js +77 -97
  56. package/src/model/schema.js +1189 -1835
  57. package/src/model/selection.js +745 -862
  58. package/src/model/text.js +90 -114
  59. package/src/model/textproxy.js +204 -240
  60. package/src/model/treewalker.js +316 -397
  61. package/src/model/typecheckable.js +16 -0
  62. package/src/model/utils/autoparagraphing.js +32 -44
  63. package/src/model/utils/deletecontent.js +334 -418
  64. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  65. package/src/model/utils/getselectedcontent.js +96 -118
  66. package/src/model/utils/insertcontent.js +654 -773
  67. package/src/model/utils/insertobject.js +96 -119
  68. package/src/model/utils/modifyselection.js +120 -158
  69. package/src/model/utils/selection-post-fixer.js +153 -201
  70. package/src/model/writer.js +1305 -1474
  71. package/src/view/attributeelement.js +189 -225
  72. package/src/view/containerelement.js +75 -85
  73. package/src/view/document.js +172 -215
  74. package/src/view/documentfragment.js +200 -249
  75. package/src/view/documentselection.js +338 -367
  76. package/src/view/domconverter.js +1371 -1613
  77. package/src/view/downcastwriter.js +1747 -2076
  78. package/src/view/editableelement.js +81 -97
  79. package/src/view/element.js +739 -890
  80. package/src/view/elementdefinition.js +5 -0
  81. package/src/view/emptyelement.js +82 -92
  82. package/src/view/filler.js +35 -50
  83. package/src/view/item.js +5 -0
  84. package/src/view/matcher.js +260 -559
  85. package/src/view/node.js +274 -360
  86. package/src/view/observer/arrowkeysobserver.js +19 -28
  87. package/src/view/observer/bubblingemittermixin.js +120 -263
  88. package/src/view/observer/bubblingeventinfo.js +47 -55
  89. package/src/view/observer/clickobserver.js +7 -13
  90. package/src/view/observer/compositionobserver.js +14 -24
  91. package/src/view/observer/domeventdata.js +57 -67
  92. package/src/view/observer/domeventobserver.js +40 -64
  93. package/src/view/observer/fakeselectionobserver.js +81 -96
  94. package/src/view/observer/focusobserver.js +45 -61
  95. package/src/view/observer/inputobserver.js +7 -13
  96. package/src/view/observer/keyobserver.js +17 -27
  97. package/src/view/observer/mouseobserver.js +7 -14
  98. package/src/view/observer/mutationobserver.js +220 -315
  99. package/src/view/observer/observer.js +81 -102
  100. package/src/view/observer/selectionobserver.js +191 -246
  101. package/src/view/observer/tabobserver.js +23 -36
  102. package/src/view/placeholder.js +128 -173
  103. package/src/view/position.js +350 -401
  104. package/src/view/range.js +453 -513
  105. package/src/view/rawelement.js +85 -112
  106. package/src/view/renderer.js +874 -1014
  107. package/src/view/rooteditableelement.js +80 -90
  108. package/src/view/selection.js +608 -689
  109. package/src/view/styles/background.js +43 -44
  110. package/src/view/styles/border.js +220 -276
  111. package/src/view/styles/margin.js +8 -17
  112. package/src/view/styles/padding.js +8 -16
  113. package/src/view/styles/utils.js +127 -160
  114. package/src/view/stylesmap.js +728 -905
  115. package/src/view/text.js +102 -126
  116. package/src/view/textproxy.js +144 -170
  117. package/src/view/treewalker.js +383 -479
  118. package/src/view/typecheckable.js +19 -0
  119. package/src/view/uielement.js +166 -187
  120. package/src/view/upcastwriter.js +395 -449
  121. package/src/view/view.js +569 -664
  122. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  123. package/src/model/item.jsdoc +0 -14
  124. package/src/view/elementdefinition.jsdoc +0 -59
  125. package/src/view/item.jsdoc +0 -14
package/src/view/node.js CHANGED
@@ -2,20 +2,17 @@
2
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
5
+ /* eslint-disable new-cap */
6
6
  /**
7
7
  * @module engine/view/node
8
8
  */
9
-
9
+ import TypeCheckable from './typecheckable';
10
10
  import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
11
11
  import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
12
- import mix from '@ckeditor/ckeditor5-utils/src/mix';
13
12
  import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
14
13
  import { clone } from 'lodash-es';
15
-
16
14
  // To check if component is loaded more than once.
17
15
  import '@ckeditor/ckeditor5-utils/src/version';
18
-
19
16
  /**
20
17
  * Abstract view node class.
21
18
  *
@@ -25,367 +22,284 @@ import '@ckeditor/ckeditor5-utils/src/version';
25
22
  *
26
23
  * @abstract
27
24
  */
28
- export default class Node {
29
- /**
30
- * Creates a tree view node.
31
- *
32
- * @protected
33
- * @param {module:engine/view/document~Document} document The document instance to which this node belongs.
34
- */
35
- constructor( document ) {
36
- /**
37
- * The document instance to which this node belongs.
38
- *
39
- * @readonly
40
- * @member {module:engine/view/document~Document}
41
- */
42
- this.document = document;
43
-
44
- /**
45
- * Parent element. Null by default. Set by {@link module:engine/view/element~Element#_insertChild}.
46
- *
47
- * @readonly
48
- * @member {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
49
- */
50
- this.parent = null;
51
- }
52
-
53
- /**
54
- * Index of the node in the parent element or null if the node has no parent.
55
- *
56
- * Accessing this property throws an error if this node's parent element does not contain it.
57
- * This means that view tree got broken.
58
- *
59
- * @readonly
60
- * @type {Number|null}
61
- */
62
- get index() {
63
- let pos;
64
-
65
- if ( !this.parent ) {
66
- return null;
67
- }
68
-
69
- // No parent or child doesn't exist in parent's children.
70
- if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
71
- /**
72
- * The node's parent does not contain this node. It means that the document tree is corrupted.
73
- *
74
- * @error view-node-not-found-in-parent
75
- */
76
- throw new CKEditorError( 'view-node-not-found-in-parent', this );
77
- }
78
-
79
- return pos;
80
- }
81
-
82
- /**
83
- * Node's next sibling, or `null` if it is the last child.
84
- *
85
- * @readonly
86
- * @type {module:engine/view/node~Node|null}
87
- */
88
- get nextSibling() {
89
- const index = this.index;
90
-
91
- return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
92
- }
93
-
94
- /**
95
- * Node's previous sibling, or `null` if it is the first child.
96
- *
97
- * @readonly
98
- * @type {module:engine/view/node~Node|null}
99
- */
100
- get previousSibling() {
101
- const index = this.index;
102
-
103
- return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
104
- }
105
-
106
- /**
107
- * Top-most ancestor of the node. If the node has no parent it is the root itself.
108
- *
109
- * @readonly
110
- * @type {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
111
- */
112
- get root() {
113
- let root = this; // eslint-disable-line consistent-this
114
-
115
- while ( root.parent ) {
116
- root = root.parent;
117
- }
118
-
119
- return root;
120
- }
121
-
122
- /**
123
- * Returns true if the node is in a tree rooted in the document (is a descendant of one of its roots).
124
- *
125
- * @returns {Boolean}
126
- */
127
- isAttached() {
128
- return this.root.is( 'rootElement' );
129
- }
130
-
131
- /**
132
- * Gets a path to the node. The path is an array containing indices of consecutive ancestors of this node,
133
- * beginning from {@link module:engine/view/node~Node#root root}, down to this node's index.
134
- *
135
- * const abc = downcastWriter.createText( 'abc' );
136
- * const foo = downcastWriter.createText( 'foo' );
137
- * const h1 = downcastWriter.createElement( 'h1', null, downcastWriter.createText( 'header' ) );
138
- * const p = downcastWriter.createElement( 'p', null, [ abc, foo ] );
139
- * const div = downcastWriter.createElement( 'div', null, [ h1, p ] );
140
- * foo.getPath(); // Returns [ 1, 3 ]. `foo` is in `p` which is in `div`. `p` starts at offset 1, while `foo` at 3.
141
- * h1.getPath(); // Returns [ 0 ].
142
- * div.getPath(); // Returns [].
143
- *
144
- * @returns {Array.<Number>} The path.
145
- */
146
- getPath() {
147
- const path = [];
148
- let node = this; // eslint-disable-line consistent-this
149
-
150
- while ( node.parent ) {
151
- path.unshift( node.index );
152
- node = node.parent;
153
- }
154
-
155
- return path;
156
- }
157
-
158
- /**
159
- * Returns ancestors array of this node.
160
- *
161
- * @param {Object} options Options object.
162
- * @param {Boolean} [options.includeSelf=false] When set to `true` this node will be also included in parent's array.
163
- * @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from node's parent to root element,
164
- * otherwise root element will be the first item in the array.
165
- * @returns {Array} Array with ancestors.
166
- */
167
- getAncestors( options = { includeSelf: false, parentFirst: false } ) {
168
- const ancestors = [];
169
- let parent = options.includeSelf ? this : this.parent;
170
-
171
- while ( parent ) {
172
- ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
173
- parent = parent.parent;
174
- }
175
-
176
- return ancestors;
177
- }
178
-
179
- /**
180
- * Returns a {@link module:engine/view/element~Element} or {@link module:engine/view/documentfragment~DocumentFragment}
181
- * which is a common ancestor of both nodes.
182
- *
183
- * @param {module:engine/view/node~Node} node The second node.
184
- * @param {Object} options Options object.
185
- * @param {Boolean} [options.includeSelf=false] When set to `true` both nodes will be considered "ancestors" too.
186
- * Which means that if e.g. node A is inside B, then their common ancestor will be B.
187
- * @returns {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
188
- */
189
- getCommonAncestor( node, options = {} ) {
190
- const ancestorsA = this.getAncestors( options );
191
- const ancestorsB = node.getAncestors( options );
192
-
193
- let i = 0;
194
-
195
- while ( ancestorsA[ i ] == ancestorsB[ i ] && ancestorsA[ i ] ) {
196
- i++;
197
- }
198
-
199
- return i === 0 ? null : ancestorsA[ i - 1 ];
200
- }
201
-
202
- /**
203
- * Returns whether this node is before given node. `false` is returned if nodes are in different trees (for example,
204
- * in different {@link module:engine/view/documentfragment~DocumentFragment}s).
205
- *
206
- * @param {module:engine/view/node~Node} node Node to compare with.
207
- * @returns {Boolean}
208
- */
209
- isBefore( node ) {
210
- // Given node is not before this node if they are same.
211
- if ( this == node ) {
212
- return false;
213
- }
214
-
215
- // Return `false` if it is impossible to compare nodes.
216
- if ( this.root !== node.root ) {
217
- return false;
218
- }
219
-
220
- const thisPath = this.getPath();
221
- const nodePath = node.getPath();
222
-
223
- const result = compareArrays( thisPath, nodePath );
224
-
225
- switch ( result ) {
226
- case 'prefix':
227
- return true;
228
-
229
- case 'extension':
230
- return false;
231
-
232
- default:
233
- return thisPath[ result ] < nodePath[ result ];
234
- }
235
- }
236
-
237
- /**
238
- * Returns whether this node is after given node. `false` is returned if nodes are in different trees (for example,
239
- * in different {@link module:engine/view/documentfragment~DocumentFragment}s).
240
- *
241
- * @param {module:engine/view/node~Node} node Node to compare with.
242
- * @returns {Boolean}
243
- */
244
- isAfter( node ) {
245
- // Given node is not before this node if they are same.
246
- if ( this == node ) {
247
- return false;
248
- }
249
-
250
- // Return `false` if it is impossible to compare nodes.
251
- if ( this.root !== node.root ) {
252
- return false;
253
- }
254
-
255
- // In other cases, just check if the `node` is before, and return the opposite.
256
- return !this.isBefore( node );
257
- }
258
-
259
- /**
260
- * Removes node from parent.
261
- *
262
- * @protected
263
- */
264
- _remove() {
265
- this.parent._removeChildren( this.index );
266
- }
267
-
268
- /**
269
- * @protected
270
- * @param {module:engine/view/document~ChangeType} type Type of the change.
271
- * @param {module:engine/view/node~Node} node Changed node.
272
- * @fires change
273
- */
274
- _fireChange( type, node ) {
275
- this.fire( 'change:' + type, node );
276
-
277
- if ( this.parent ) {
278
- this.parent._fireChange( type, node );
279
- }
280
- }
281
-
282
- /**
283
- * Custom toJSON method to solve child-parent circular dependencies.
284
- *
285
- * @returns {Object} Clone of this object with the parent property removed.
286
- */
287
- toJSON() {
288
- const json = clone( this );
289
-
290
- // Due to circular references we need to remove parent reference.
291
- delete json.parent;
292
-
293
- return json;
294
- }
295
-
296
- /**
297
- * Checks whether this object is of the given type.
298
- *
299
- * This method is useful when processing view objects that are of unknown type. For example, a function
300
- * may return a {@link module:engine/view/documentfragment~DocumentFragment} or a {@link module:engine/view/node~Node}
301
- * that can be either a text node or an element. This method can be used to check what kind of object is returned.
302
- *
303
- * someObject.is( 'element' ); // -> true if this is an element
304
- * someObject.is( 'node' ); // -> true if this is a node (a text node or an element)
305
- * someObject.is( 'documentFragment' ); // -> true if this is a document fragment
306
- *
307
- * Since this method is also available on a range of model objects, you can prefix the type of the object with
308
- * `model:` or `view:` to check, for example, if this is the model's or view's element:
309
- *
310
- * viewElement.is( 'view:element' ); // -> true
311
- * viewElement.is( 'model:element' ); // -> false
312
- *
313
- * By using this method it is also possible to check a name of an element:
314
- *
315
- * imgElement.is( 'element', 'img' ); // -> true
316
- * imgElement.is( 'view:element', 'img' ); // -> same as above, but more precise
317
- *
318
- * The list of view objects which implement the `is()` method:
319
- *
320
- * * {@link module:engine/view/attributeelement~AttributeElement#is `AttributeElement#is()`}
321
- * * {@link module:engine/view/containerelement~ContainerElement#is `ContainerElement#is()`}
322
- * * {@link module:engine/view/documentfragment~DocumentFragment#is `DocumentFragment#is()`}
323
- * * {@link module:engine/view/documentselection~DocumentSelection#is `DocumentSelection#is()`}
324
- * * {@link module:engine/view/editableelement~EditableElement#is `EditableElement#is()`}
325
- * * {@link module:engine/view/element~Element#is `Element#is()`}
326
- * * {@link module:engine/view/emptyelement~EmptyElement#is `EmptyElement#is()`}
327
- * * {@link module:engine/view/node~Node#is `Node#is()`}
328
- * * {@link module:engine/view/position~Position#is `Position#is()`}
329
- * * {@link module:engine/view/range~Range#is `Range#is()`}
330
- * * {@link module:engine/view/rooteditableelement~RootEditableElement#is `RootEditableElement#is()`}
331
- * * {@link module:engine/view/selection~Selection#is `Selection#is()`}
332
- * * {@link module:engine/view/text~Text#is `Text#is()`}
333
- * * {@link module:engine/view/textproxy~TextProxy#is `TextProxy#is()`}
334
- * * {@link module:engine/view/uielement~UIElement#is `UIElement#is()`}
335
- *
336
- * @method #is
337
- * @param {String} type Type to check.
338
- * @returns {Boolean}
339
- */
340
- is( type ) {
341
- return type === 'node' || type === 'view:node';
342
- }
343
-
344
- /**
345
- * Clones this node.
346
- *
347
- * @protected
348
- * @method #_clone
349
- * @returns {module:engine/view/node~Node} Clone of this node.
350
- */
351
-
352
- /**
353
- * Checks if provided node is similar to this node.
354
- *
355
- * @method #isSimilar
356
- * @returns {Boolean} True if nodes are similar.
357
- */
25
+ export default class Node extends EmitterMixin(TypeCheckable) {
26
+ /**
27
+ * Creates a tree view node.
28
+ *
29
+ * @protected
30
+ * @param {module:engine/view/document~Document} document The document instance to which this node belongs.
31
+ */
32
+ constructor(document) {
33
+ super();
34
+ /**
35
+ * The document instance to which this node belongs.
36
+ *
37
+ * @readonly
38
+ * @member {module:engine/view/document~Document}
39
+ */
40
+ this.document = document;
41
+ /**
42
+ * Parent element. Null by default. Set by {@link module:engine/view/element~Element#_insertChild}.
43
+ *
44
+ * @readonly
45
+ * @member {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
46
+ */
47
+ this.parent = null;
48
+ }
49
+ /**
50
+ * Index of the node in the parent element or null if the node has no parent.
51
+ *
52
+ * Accessing this property throws an error if this node's parent element does not contain it.
53
+ * This means that view tree got broken.
54
+ *
55
+ * @readonly
56
+ * @type {Number|null}
57
+ */
58
+ get index() {
59
+ let pos;
60
+ if (!this.parent) {
61
+ return null;
62
+ }
63
+ // No parent or child doesn't exist in parent's children.
64
+ if ((pos = this.parent.getChildIndex(this)) == -1) {
65
+ /**
66
+ * The node's parent does not contain this node. It means that the document tree is corrupted.
67
+ *
68
+ * @error view-node-not-found-in-parent
69
+ */
70
+ throw new CKEditorError('view-node-not-found-in-parent', this);
71
+ }
72
+ return pos;
73
+ }
74
+ /**
75
+ * Node's next sibling, or `null` if it is the last child.
76
+ *
77
+ * @readonly
78
+ * @type {module:engine/view/node~Node|null}
79
+ */
80
+ get nextSibling() {
81
+ const index = this.index;
82
+ return (index !== null && this.parent.getChild(index + 1)) || null;
83
+ }
84
+ /**
85
+ * Node's previous sibling, or `null` if it is the first child.
86
+ *
87
+ * @readonly
88
+ * @type {module:engine/view/node~Node|null}
89
+ */
90
+ get previousSibling() {
91
+ const index = this.index;
92
+ return (index !== null && this.parent.getChild(index - 1)) || null;
93
+ }
94
+ /**
95
+ * Top-most ancestor of the node. If the node has no parent it is the root itself.
96
+ *
97
+ * @readonly
98
+ * @type {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
99
+ */
100
+ get root() {
101
+ // eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
102
+ let root = this;
103
+ while (root.parent) {
104
+ root = root.parent;
105
+ }
106
+ return root;
107
+ }
108
+ /**
109
+ * Returns true if the node is in a tree rooted in the document (is a descendant of one of its roots).
110
+ *
111
+ * @returns {Boolean}
112
+ */
113
+ isAttached() {
114
+ return this.root.is('rootElement');
115
+ }
116
+ /**
117
+ * Gets a path to the node. The path is an array containing indices of consecutive ancestors of this node,
118
+ * beginning from {@link module:engine/view/node~Node#root root}, down to this node's index.
119
+ *
120
+ * const abc = downcastWriter.createText( 'abc' );
121
+ * const foo = downcastWriter.createText( 'foo' );
122
+ * const h1 = downcastWriter.createElement( 'h1', null, downcastWriter.createText( 'header' ) );
123
+ * const p = downcastWriter.createElement( 'p', null, [ abc, foo ] );
124
+ * const div = downcastWriter.createElement( 'div', null, [ h1, p ] );
125
+ * foo.getPath(); // Returns [ 1, 3 ]. `foo` is in `p` which is in `div`. `p` starts at offset 1, while `foo` at 3.
126
+ * h1.getPath(); // Returns [ 0 ].
127
+ * div.getPath(); // Returns [].
128
+ *
129
+ * @returns {Array.<Number>} The path.
130
+ */
131
+ getPath() {
132
+ const path = [];
133
+ // eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
134
+ let node = this;
135
+ while (node.parent) {
136
+ path.unshift(node.index);
137
+ node = node.parent;
138
+ }
139
+ return path;
140
+ }
141
+ /**
142
+ * Returns ancestors array of this node.
143
+ *
144
+ * @param {Object} options Options object.
145
+ * @param {Boolean} [options.includeSelf=false] When set to `true` this node will be also included in parent's array.
146
+ * @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from node's parent to root element,
147
+ * otherwise root element will be the first item in the array.
148
+ * @returns {Array} Array with ancestors.
149
+ */
150
+ getAncestors(options = {}) {
151
+ const ancestors = [];
152
+ let parent = options.includeSelf ? this : this.parent;
153
+ while (parent) {
154
+ ancestors[options.parentFirst ? 'push' : 'unshift'](parent);
155
+ parent = parent.parent;
156
+ }
157
+ return ancestors;
158
+ }
159
+ /**
160
+ * Returns a {@link module:engine/view/element~Element} or {@link module:engine/view/documentfragment~DocumentFragment}
161
+ * which is a common ancestor of both nodes.
162
+ *
163
+ * @param {module:engine/view/node~Node} node The second node.
164
+ * @param {Object} options Options object.
165
+ * @param {Boolean} [options.includeSelf=false] When set to `true` both nodes will be considered "ancestors" too.
166
+ * Which means that if e.g. node A is inside B, then their common ancestor will be B.
167
+ * @returns {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
168
+ */
169
+ getCommonAncestor(node, options = {}) {
170
+ const ancestorsA = this.getAncestors(options);
171
+ const ancestorsB = node.getAncestors(options);
172
+ let i = 0;
173
+ while (ancestorsA[i] == ancestorsB[i] && ancestorsA[i]) {
174
+ i++;
175
+ }
176
+ return i === 0 ? null : ancestorsA[i - 1];
177
+ }
178
+ /**
179
+ * Returns whether this node is before given node. `false` is returned if nodes are in different trees (for example,
180
+ * in different {@link module:engine/view/documentfragment~DocumentFragment}s).
181
+ *
182
+ * @param {module:engine/view/node~Node} node Node to compare with.
183
+ * @returns {Boolean}
184
+ */
185
+ isBefore(node) {
186
+ // Given node is not before this node if they are same.
187
+ if (this == node) {
188
+ return false;
189
+ }
190
+ // Return `false` if it is impossible to compare nodes.
191
+ if (this.root !== node.root) {
192
+ return false;
193
+ }
194
+ const thisPath = this.getPath();
195
+ const nodePath = node.getPath();
196
+ const result = compareArrays(thisPath, nodePath);
197
+ switch (result) {
198
+ case 'prefix':
199
+ return true;
200
+ case 'extension':
201
+ return false;
202
+ default:
203
+ return thisPath[result] < nodePath[result];
204
+ }
205
+ }
206
+ /**
207
+ * Returns whether this node is after given node. `false` is returned if nodes are in different trees (for example,
208
+ * in different {@link module:engine/view/documentfragment~DocumentFragment}s).
209
+ *
210
+ * @param {module:engine/view/node~Node} node Node to compare with.
211
+ * @returns {Boolean}
212
+ */
213
+ isAfter(node) {
214
+ // Given node is not before this node if they are same.
215
+ if (this == node) {
216
+ return false;
217
+ }
218
+ // Return `false` if it is impossible to compare nodes.
219
+ if (this.root !== node.root) {
220
+ return false;
221
+ }
222
+ // In other cases, just check if the `node` is before, and return the opposite.
223
+ return !this.isBefore(node);
224
+ }
225
+ /**
226
+ * Removes node from parent.
227
+ *
228
+ * @internal
229
+ * @protected
230
+ */
231
+ _remove() {
232
+ this.parent._removeChildren(this.index);
233
+ }
234
+ /**
235
+ * @internal
236
+ * @protected
237
+ * @param {module:engine/view/document~ChangeType} type Type of the change.
238
+ * @param {module:engine/view/node~Node} node Changed node.
239
+ * @fires change
240
+ */
241
+ _fireChange(type, node) {
242
+ this.fire(`change:${type}`, node);
243
+ if (this.parent) {
244
+ this.parent._fireChange(type, node);
245
+ }
246
+ }
247
+ /**
248
+ * Custom toJSON method to solve child-parent circular dependencies.
249
+ *
250
+ * @returns {Object} Clone of this object with the parent property removed.
251
+ */
252
+ toJSON() {
253
+ const json = clone(this);
254
+ // Due to circular references we need to remove parent reference.
255
+ delete json.parent;
256
+ return json;
257
+ }
358
258
  }
359
-
360
259
  /**
361
- * Fired when list of {@link module:engine/view/element~Element elements} children changes.
260
+ * Checks whether this object is of the given type.
362
261
  *
363
- * Change event is bubbled it is fired on all ancestors.
262
+ * This method is useful when processing view objects that are of unknown type. For example, a function
263
+ * may return a {@link module:engine/view/documentfragment~DocumentFragment} or a {@link module:engine/view/node~Node}
264
+ * that can be either a text node or an element. This method can be used to check what kind of object is returned.
364
265
  *
365
- * @event change:children
366
- * @param {module:engine/view/node~Node} changedNode
367
- */
368
-
369
- /**
370
- * Fired when list of {@link module:engine/view/element~Element elements} attributes changes.
266
+ * someObject.is( 'element' ); // -> true if this is an element
267
+ * someObject.is( 'node' ); // -> true if this is a node (a text node or an element)
268
+ * someObject.is( 'documentFragment' ); // -> true if this is a document fragment
371
269
  *
372
- * Change event is bubbled it is fired on all ancestors.
270
+ * Since this method is also available on a range of model objects, you can prefix the type of the object with
271
+ * `model:` or `view:` to check, for example, if this is the model's or view's element:
373
272
  *
374
- * @event change:attributes
375
- * @param {module:engine/view/node~Node} changedNode
376
- */
377
-
378
- /**
379
- * Fired when {@link module:engine/view/text~Text text nodes} data changes.
273
+ * viewElement.is( 'view:element' ); // -> true
274
+ * viewElement.is( 'model:element' ); // -> false
380
275
  *
381
- * Change event is bubbled it is fired on all ancestors.
276
+ * By using this method it is also possible to check a name of an element:
382
277
  *
383
- * @event change:text
384
- * @param {module:engine/view/node~Node} changedNode
385
- */
386
-
387
- /**
388
- * @event change
278
+ * imgElement.is( 'element', 'img' ); // -> true
279
+ * imgElement.is( 'view:element', 'img' ); // -> same as above, but more precise
280
+ *
281
+ * The list of view objects which implement the `is()` method:
282
+ *
283
+ * * {@link module:engine/view/attributeelement~AttributeElement#is `AttributeElement#is()`}
284
+ * * {@link module:engine/view/containerelement~ContainerElement#is `ContainerElement#is()`}
285
+ * * {@link module:engine/view/documentfragment~DocumentFragment#is `DocumentFragment#is()`}
286
+ * * {@link module:engine/view/documentselection~DocumentSelection#is `DocumentSelection#is()`}
287
+ * * {@link module:engine/view/editableelement~EditableElement#is `EditableElement#is()`}
288
+ * * {@link module:engine/view/element~Element#is `Element#is()`}
289
+ * * {@link module:engine/view/emptyelement~EmptyElement#is `EmptyElement#is()`}
290
+ * * {@link module:engine/view/node~Node#is `Node#is()`}
291
+ * * {@link module:engine/view/position~Position#is `Position#is()`}
292
+ * * {@link module:engine/view/range~Range#is `Range#is()`}
293
+ * * {@link module:engine/view/rooteditableelement~RootEditableElement#is `RootEditableElement#is()`}
294
+ * * {@link module:engine/view/selection~Selection#is `Selection#is()`}
295
+ * * {@link module:engine/view/text~Text#is `Text#is()`}
296
+ * * {@link module:engine/view/textproxy~TextProxy#is `TextProxy#is()`}
297
+ * * {@link module:engine/view/uielement~UIElement#is `UIElement#is()`}
298
+ *
299
+ * @method #is
300
+ * @param {String} type Type to check.
301
+ * @returns {Boolean}
389
302
  */
390
-
391
- mix( Node, EmitterMixin );
303
+ Node.prototype.is = function (type) {
304
+ return type === 'node' || type === 'view:node';
305
+ };