@ckeditor/ckeditor5-engine 35.0.1 → 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 (124) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/package.json +30 -24
  3. package/src/controller/datacontroller.js +467 -561
  4. package/src/controller/editingcontroller.js +168 -204
  5. package/src/conversion/conversion.js +541 -565
  6. package/src/conversion/conversionhelpers.js +24 -28
  7. package/src/conversion/downcastdispatcher.js +457 -686
  8. package/src/conversion/downcasthelpers.js +1583 -1965
  9. package/src/conversion/mapper.js +518 -707
  10. package/src/conversion/modelconsumable.js +240 -283
  11. package/src/conversion/upcastdispatcher.js +372 -718
  12. package/src/conversion/upcasthelpers.js +707 -818
  13. package/src/conversion/viewconsumable.js +524 -581
  14. package/src/dataprocessor/basichtmlwriter.js +12 -16
  15. package/src/dataprocessor/dataprocessor.js +5 -0
  16. package/src/dataprocessor/htmldataprocessor.js +100 -116
  17. package/src/dataprocessor/htmlwriter.js +1 -18
  18. package/src/dataprocessor/xmldataprocessor.js +116 -137
  19. package/src/dev-utils/model.js +260 -352
  20. package/src/dev-utils/operationreplayer.js +106 -126
  21. package/src/dev-utils/utils.js +34 -51
  22. package/src/dev-utils/view.js +632 -753
  23. package/src/index.js +0 -11
  24. package/src/model/batch.js +111 -127
  25. package/src/model/differ.js +988 -1233
  26. package/src/model/document.js +340 -449
  27. package/src/model/documentfragment.js +327 -364
  28. package/src/model/documentselection.js +996 -1189
  29. package/src/model/element.js +306 -410
  30. package/src/model/history.js +224 -262
  31. package/src/model/item.js +5 -0
  32. package/src/model/liveposition.js +84 -145
  33. package/src/model/liverange.js +108 -185
  34. package/src/model/markercollection.js +379 -480
  35. package/src/model/model.js +883 -1034
  36. package/src/model/node.js +419 -463
  37. package/src/model/nodelist.js +175 -201
  38. package/src/model/operation/attributeoperation.js +153 -182
  39. package/src/model/operation/detachoperation.js +64 -83
  40. package/src/model/operation/insertoperation.js +135 -166
  41. package/src/model/operation/markeroperation.js +114 -140
  42. package/src/model/operation/mergeoperation.js +163 -191
  43. package/src/model/operation/moveoperation.js +157 -187
  44. package/src/model/operation/nooperation.js +28 -38
  45. package/src/model/operation/operation.js +106 -125
  46. package/src/model/operation/operationfactory.js +30 -34
  47. package/src/model/operation/renameoperation.js +109 -135
  48. package/src/model/operation/rootattributeoperation.js +155 -188
  49. package/src/model/operation/splitoperation.js +196 -232
  50. package/src/model/operation/transform.js +1833 -2204
  51. package/src/model/operation/utils.js +140 -204
  52. package/src/model/position.js +899 -1053
  53. package/src/model/range.js +910 -1028
  54. package/src/model/rootelement.js +77 -97
  55. package/src/model/schema.js +1189 -1835
  56. package/src/model/selection.js +745 -862
  57. package/src/model/text.js +90 -114
  58. package/src/model/textproxy.js +204 -240
  59. package/src/model/treewalker.js +316 -397
  60. package/src/model/typecheckable.js +16 -0
  61. package/src/model/utils/autoparagraphing.js +32 -44
  62. package/src/model/utils/deletecontent.js +334 -418
  63. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  64. package/src/model/utils/getselectedcontent.js +96 -118
  65. package/src/model/utils/insertcontent.js +654 -773
  66. package/src/model/utils/insertobject.js +96 -119
  67. package/src/model/utils/modifyselection.js +120 -158
  68. package/src/model/utils/selection-post-fixer.js +153 -201
  69. package/src/model/writer.js +1305 -1474
  70. package/src/view/attributeelement.js +189 -225
  71. package/src/view/containerelement.js +75 -85
  72. package/src/view/document.js +172 -215
  73. package/src/view/documentfragment.js +200 -249
  74. package/src/view/documentselection.js +338 -367
  75. package/src/view/domconverter.js +1370 -1617
  76. package/src/view/downcastwriter.js +1747 -2076
  77. package/src/view/editableelement.js +81 -97
  78. package/src/view/element.js +739 -890
  79. package/src/view/elementdefinition.js +5 -0
  80. package/src/view/emptyelement.js +82 -92
  81. package/src/view/filler.js +35 -50
  82. package/src/view/item.js +5 -0
  83. package/src/view/matcher.js +260 -559
  84. package/src/view/node.js +274 -360
  85. package/src/view/observer/arrowkeysobserver.js +19 -28
  86. package/src/view/observer/bubblingemittermixin.js +120 -263
  87. package/src/view/observer/bubblingeventinfo.js +47 -55
  88. package/src/view/observer/clickobserver.js +7 -13
  89. package/src/view/observer/compositionobserver.js +14 -24
  90. package/src/view/observer/domeventdata.js +57 -67
  91. package/src/view/observer/domeventobserver.js +40 -64
  92. package/src/view/observer/fakeselectionobserver.js +81 -96
  93. package/src/view/observer/focusobserver.js +45 -61
  94. package/src/view/observer/inputobserver.js +7 -13
  95. package/src/view/observer/keyobserver.js +17 -27
  96. package/src/view/observer/mouseobserver.js +7 -14
  97. package/src/view/observer/mutationobserver.js +220 -315
  98. package/src/view/observer/observer.js +81 -102
  99. package/src/view/observer/selectionobserver.js +191 -246
  100. package/src/view/observer/tabobserver.js +23 -36
  101. package/src/view/placeholder.js +128 -173
  102. package/src/view/position.js +350 -401
  103. package/src/view/range.js +453 -513
  104. package/src/view/rawelement.js +85 -112
  105. package/src/view/renderer.js +874 -1018
  106. package/src/view/rooteditableelement.js +80 -90
  107. package/src/view/selection.js +608 -689
  108. package/src/view/styles/background.js +43 -44
  109. package/src/view/styles/border.js +220 -276
  110. package/src/view/styles/margin.js +8 -17
  111. package/src/view/styles/padding.js +8 -16
  112. package/src/view/styles/utils.js +127 -160
  113. package/src/view/stylesmap.js +728 -905
  114. package/src/view/text.js +102 -126
  115. package/src/view/textproxy.js +144 -170
  116. package/src/view/treewalker.js +383 -479
  117. package/src/view/typecheckable.js +19 -0
  118. package/src/view/uielement.js +166 -187
  119. package/src/view/upcastwriter.js +395 -449
  120. package/src/view/view.js +569 -664
  121. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  122. package/src/model/item.jsdoc +0 -14
  123. package/src/view/elementdefinition.jsdoc +0 -59
  124. 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
+ };