@ckeditor/ckeditor5-utils 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 (61) hide show
  1. package/CHANGELOG.md +324 -0
  2. package/LICENSE.md +1 -1
  3. package/package.json +19 -8
  4. package/src/areconnectedthroughproperties.js +54 -71
  5. package/src/ckeditorerror.js +92 -114
  6. package/src/collection.js +594 -762
  7. package/src/comparearrays.js +22 -28
  8. package/src/config.js +193 -223
  9. package/src/count.js +8 -12
  10. package/src/diff.js +85 -110
  11. package/src/difftochanges.js +47 -57
  12. package/src/dom/createelement.js +17 -25
  13. package/src/dom/emittermixin.js +202 -263
  14. package/src/dom/getancestors.js +9 -13
  15. package/src/dom/getborderwidths.js +10 -13
  16. package/src/dom/getcommonancestor.js +9 -15
  17. package/src/dom/getdatafromelement.js +5 -9
  18. package/src/dom/getpositionedancestor.js +9 -14
  19. package/src/dom/global.js +15 -4
  20. package/src/dom/indexof.js +7 -11
  21. package/src/dom/insertat.js +2 -4
  22. package/src/dom/iscomment.js +2 -5
  23. package/src/dom/isnode.js +10 -12
  24. package/src/dom/isrange.js +2 -4
  25. package/src/dom/istext.js +2 -4
  26. package/src/dom/isvisible.js +2 -4
  27. package/src/dom/iswindow.js +11 -16
  28. package/src/dom/position.js +220 -410
  29. package/src/dom/rect.js +335 -414
  30. package/src/dom/remove.js +5 -8
  31. package/src/dom/resizeobserver.js +109 -342
  32. package/src/dom/scroll.js +151 -183
  33. package/src/dom/setdatainelement.js +5 -9
  34. package/src/dom/tounit.js +10 -12
  35. package/src/elementreplacer.js +30 -44
  36. package/src/emittermixin.js +368 -634
  37. package/src/env.js +109 -116
  38. package/src/eventinfo.js +12 -65
  39. package/src/fastdiff.js +96 -128
  40. package/src/first.js +8 -12
  41. package/src/focustracker.js +77 -133
  42. package/src/index.js +0 -9
  43. package/src/inserttopriorityarray.js +9 -30
  44. package/src/isiterable.js +2 -4
  45. package/src/keyboard.js +117 -196
  46. package/src/keystrokehandler.js +72 -88
  47. package/src/language.js +9 -15
  48. package/src/locale.js +61 -158
  49. package/src/mapsequal.js +12 -17
  50. package/src/mix.js +17 -16
  51. package/src/nth.js +8 -11
  52. package/src/objecttomap.js +7 -11
  53. package/src/observablemixin.js +474 -778
  54. package/src/priorities.js +20 -32
  55. package/src/spy.js +3 -6
  56. package/src/toarray.js +2 -13
  57. package/src/tomap.js +8 -10
  58. package/src/translation-service.js +57 -93
  59. package/src/uid.js +34 -38
  60. package/src/unicode.js +28 -43
  61. package/src/version.js +134 -143
package/src/dom/rect.js CHANGED
@@ -2,442 +2,363 @@
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
-
6
5
  /**
7
6
  * @module utils/dom/rect
8
7
  */
9
-
10
8
  import isRange from './isrange';
11
9
  import isWindow from './iswindow';
12
10
  import getBorderWidths from './getborderwidths';
13
11
  import isText from './istext';
14
12
  import { isElement } from 'lodash-es';
15
-
16
- const rectProperties = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ];
17
-
13
+ const rectProperties = ['top', 'right', 'bottom', 'left', 'width', 'height'];
18
14
  /**
19
15
  * A helper class representing a `ClientRect` object, e.g. value returned by
20
16
  * the native `object.getBoundingClientRect()` method. Provides a set of methods
21
17
  * to manipulate the rect and compare it against other rect instances.
22
18
  */
23
19
  export default class Rect {
24
- /**
25
- * Creates an instance of rect.
26
- *
27
- * // Rect of an HTMLElement.
28
- * const rectA = new Rect( document.body );
29
- *
30
- * // Rect of a DOM Range.
31
- * const rectB = new Rect( document.getSelection().getRangeAt( 0 ) );
32
- *
33
- * // Rect of a window (web browser viewport).
34
- * const rectC = new Rect( window );
35
- *
36
- * // Rect out of an object.
37
- * const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
38
- *
39
- * // Rect out of another Rect instance.
40
- * const rectE = new Rect( rectD );
41
- *
42
- * // Rect out of a ClientRect.
43
- * const rectF = new Rect( document.body.getClientRects().item( 0 ) );
44
- *
45
- * **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any)
46
- * ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders}
47
- * to get the inner part of the rect.
48
- *
49
- * @param {HTMLElement|Range|Window|ClientRect|DOMRect|module:utils/dom/rect~Rect|Object} source A source object to create the rect.
50
- */
51
- constructor( source ) {
52
- const isSourceRange = isRange( source );
53
-
54
- /**
55
- * The object this rect is for.
56
- *
57
- * @protected
58
- * @readonly
59
- * @member {HTMLElement|Range|Window|ClientRect|DOMRect|module:utils/dom/rect~Rect|Object} #_source
60
- */
61
- Object.defineProperty( this, '_source', {
62
- // If the source is a Rect instance, copy it's #_source.
63
- value: source._source || source,
64
- writable: true,
65
- enumerable: false
66
- } );
67
-
68
- if ( isElement( source ) || isSourceRange ) {
69
- // The `Rect` class depends on `getBoundingClientRect` and `getClientRects` DOM methods. If the source
70
- // of a rect in an HTML element or a DOM range but it does not belong to any rendered DOM tree, these methods
71
- // will fail to obtain the geometry and the rect instance makes little sense to the features using it.
72
- // To get rid of this warning make sure the source passed to the constructor is a descendant of `window.document.body`.
73
- // @if CK_DEBUG // const sourceNode = isSourceRange ? source.startContainer : source;
74
- // @if CK_DEBUG // if ( !sourceNode.ownerDocument || !sourceNode.ownerDocument.body.contains( sourceNode ) ) {
75
- // @if CK_DEBUG // console.warn(
76
- // @if CK_DEBUG // 'rect-source-not-in-dom: The source of this rect does not belong to any rendered DOM tree.',
77
- // @if CK_DEBUG // { source } );
78
- // @if CK_DEBUG // }
79
-
80
- if ( isSourceRange ) {
81
- const rangeRects = Rect.getDomRangeRects( source );
82
- copyRectProperties( this, Rect.getBoundingRect( rangeRects ) );
83
- } else {
84
- copyRectProperties( this, source.getBoundingClientRect() );
85
- }
86
- } else if ( isWindow( source ) ) {
87
- const { innerWidth, innerHeight } = source;
88
-
89
- copyRectProperties( this, {
90
- top: 0,
91
- right: innerWidth,
92
- bottom: innerHeight,
93
- left: 0,
94
- width: innerWidth,
95
- height: innerHeight
96
- } );
97
- } else {
98
- copyRectProperties( this, source );
99
- }
100
-
101
- /**
102
- * The "top" value of the rect.
103
- *
104
- * @readonly
105
- * @member {Number} #top
106
- */
107
-
108
- /**
109
- * The "right" value of the rect.
110
- *
111
- * @readonly
112
- * @member {Number} #right
113
- */
114
-
115
- /**
116
- * The "bottom" value of the rect.
117
- *
118
- * @readonly
119
- * @member {Number} #bottom
120
- */
121
-
122
- /**
123
- * The "left" value of the rect.
124
- *
125
- * @readonly
126
- * @member {Number} #left
127
- */
128
-
129
- /**
130
- * The "width" value of the rect.
131
- *
132
- * @readonly
133
- * @member {Number} #width
134
- */
135
-
136
- /**
137
- * The "height" value of the rect.
138
- *
139
- * @readonly
140
- * @member {Number} #height
141
- */
142
- }
143
-
144
- /**
145
- * Returns a clone of the rect.
146
- *
147
- * @returns {module:utils/dom/rect~Rect} A cloned rect.
148
- */
149
- clone() {
150
- return new Rect( this );
151
- }
152
-
153
- /**
154
- * Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location.
155
- *
156
- * @param {Number} x Desired horizontal location.
157
- * @param {Number} y Desired vertical location.
158
- * @returns {module:utils/dom/rect~Rect} A rect which has been moved.
159
- */
160
- moveTo( x, y ) {
161
- this.top = y;
162
- this.right = x + this.width;
163
- this.bottom = y + this.height;
164
- this.left = x;
165
-
166
- return this;
167
- }
168
-
169
- /**
170
- * Moves the rect in–place by a dedicated offset.
171
- *
172
- * @param {Number} x A horizontal offset.
173
- * @param {Number} y A vertical offset
174
- * @returns {module:utils/dom/rect~Rect} A rect which has been moved.
175
- */
176
- moveBy( x, y ) {
177
- this.top += y;
178
- this.right += x;
179
- this.left += x;
180
- this.bottom += y;
181
-
182
- return this;
183
- }
184
-
185
- /**
186
- * Returns a new rect a a result of intersection with another rect.
187
- *
188
- * @param {module:utils/dom/rect~Rect} anotherRect
189
- * @returns {module:utils/dom/rect~Rect}
190
- */
191
- getIntersection( anotherRect ) {
192
- const rect = {
193
- top: Math.max( this.top, anotherRect.top ),
194
- right: Math.min( this.right, anotherRect.right ),
195
- bottom: Math.min( this.bottom, anotherRect.bottom ),
196
- left: Math.max( this.left, anotherRect.left )
197
- };
198
-
199
- rect.width = rect.right - rect.left;
200
- rect.height = rect.bottom - rect.top;
201
-
202
- if ( rect.width < 0 || rect.height < 0 ) {
203
- return null;
204
- } else {
205
- return new Rect( rect );
206
- }
207
- }
208
-
209
- /**
210
- * Returns the area of intersection with another rect.
211
- *
212
- * @param {module:utils/dom/rect~Rect} anotherRect [description]
213
- * @returns {Number} Area of intersection.
214
- */
215
- getIntersectionArea( anotherRect ) {
216
- const rect = this.getIntersection( anotherRect );
217
-
218
- if ( rect ) {
219
- return rect.getArea();
220
- } else {
221
- return 0;
222
- }
223
- }
224
-
225
- /**
226
- * Returns the area of the rect.
227
- *
228
- * @returns {Number}
229
- */
230
- getArea() {
231
- return this.width * this.height;
232
- }
233
-
234
- /**
235
- * Returns a new rect, a part of the original rect, which is actually visible to the user,
236
- * e.g. an original rect cropped by parent element rects which have `overflow` set in CSS
237
- * other than `"visible"`.
238
- *
239
- * If there's no such visible rect, which is when the rect is limited by one or many of
240
- * the ancestors, `null` is returned.
241
- *
242
- * @returns {module:utils/dom/rect~Rect|null} A visible rect instance or `null`, if there's none.
243
- */
244
- getVisible() {
245
- const source = this._source;
246
- let visibleRect = this.clone();
247
-
248
- // There's no ancestor to crop <body> with the overflow.
249
- if ( !isBody( source ) ) {
250
- let parent = source.parentNode || source.commonAncestorContainer;
251
-
252
- // Check the ancestors all the way up to the <body>.
253
- while ( parent && !isBody( parent ) ) {
254
- const parentRect = new Rect( parent );
255
- const intersectionRect = visibleRect.getIntersection( parentRect );
256
-
257
- if ( intersectionRect ) {
258
- if ( intersectionRect.getArea() < visibleRect.getArea() ) {
259
- // Reduce the visible rect to the intersection.
260
- visibleRect = intersectionRect;
261
- }
262
- } else {
263
- // There's no intersection, the rect is completely invisible.
264
- return null;
265
- }
266
-
267
- parent = parent.parentNode;
268
- }
269
- }
270
-
271
- return visibleRect;
272
- }
273
-
274
- /**
275
- * Checks if all property values ({@link #top}, {@link #left}, {@link #right},
276
- * {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect
277
- * instances.
278
- *
279
- * @param {module:utils/dom/rect~Rect} rect A rect instance to compare with.
280
- * @returns {Boolean} `true` when Rects are equal. `false` otherwise.
281
- */
282
- isEqual( anotherRect ) {
283
- for ( const prop of rectProperties ) {
284
- if ( this[ prop ] !== anotherRect[ prop ] ) {
285
- return false;
286
- }
287
- }
288
-
289
- return true;
290
- }
291
-
292
- /**
293
- * Checks whether a rect fully contains another rect instance.
294
- *
295
- * @param {module:utils/dom/rect~Rect} anotherRect
296
- * @returns {Boolean} `true` if contains, `false` otherwise.
297
- */
298
- contains( anotherRect ) {
299
- const intersectRect = this.getIntersection( anotherRect );
300
-
301
- return !!( intersectRect && intersectRect.isEqual( anotherRect ) );
302
- }
303
-
304
- /**
305
- * Excludes scrollbars and CSS borders from the rect.
306
- *
307
- * * Borders are removed when {@link #_source} is an HTML element.
308
- * * Scrollbars are excluded from HTML elements and the `window`.
309
- *
310
- * @returns {module:utils/dom/rect~Rect} A rect which has been updated.
311
- */
312
- excludeScrollbarsAndBorders() {
313
- const source = this._source;
314
- let scrollBarWidth, scrollBarHeight, direction;
315
-
316
- if ( isWindow( source ) ) {
317
- scrollBarWidth = source.innerWidth - source.document.documentElement.clientWidth;
318
- scrollBarHeight = source.innerHeight - source.document.documentElement.clientHeight;
319
- direction = source.getComputedStyle( source.document.documentElement ).direction;
320
- } else {
321
- const borderWidths = getBorderWidths( this._source );
322
-
323
- scrollBarWidth = source.offsetWidth - source.clientWidth - borderWidths.left - borderWidths.right;
324
- scrollBarHeight = source.offsetHeight - source.clientHeight - borderWidths.top - borderWidths.bottom;
325
- direction = source.ownerDocument.defaultView.getComputedStyle( source ).direction;
326
-
327
- this.left += borderWidths.left;
328
- this.top += borderWidths.top;
329
- this.right -= borderWidths.right;
330
- this.bottom -= borderWidths.bottom;
331
- this.width = this.right - this.left;
332
- this.height = this.bottom - this.top;
333
- }
334
-
335
- this.width -= scrollBarWidth;
336
-
337
- if ( direction === 'ltr' ) {
338
- this.right -= scrollBarWidth;
339
- } else {
340
- this.left += scrollBarWidth;
341
- }
342
-
343
- this.height -= scrollBarHeight;
344
- this.bottom -= scrollBarHeight;
345
-
346
- return this;
347
- }
348
-
349
- /**
350
- * Returns an array of rects of the given native DOM Range.
351
- *
352
- * @param {Range} range A native DOM range.
353
- * @returns {Array.<module:utils/dom/rect~Rect>} DOM Range rects.
354
- */
355
- static getDomRangeRects( range ) {
356
- const rects = [];
357
- // Safari does not iterate over ClientRectList using for...of loop.
358
- const clientRects = Array.from( range.getClientRects() );
359
-
360
- if ( clientRects.length ) {
361
- for ( const rect of clientRects ) {
362
- rects.push( new Rect( rect ) );
363
- }
364
- }
365
- // If there's no client rects for the Range, use parent container's bounding rect
366
- // instead and adjust rect's width to simulate the actual geometry of such range.
367
- // https://github.com/ckeditor/ckeditor5-utils/issues/153
368
- // https://github.com/ckeditor/ckeditor5-ui/issues/317
369
- else {
370
- let startContainer = range.startContainer;
371
-
372
- if ( isText( startContainer ) ) {
373
- startContainer = startContainer.parentNode;
374
- }
375
-
376
- const rect = new Rect( startContainer.getBoundingClientRect() );
377
- rect.right = rect.left;
378
- rect.width = 0;
379
-
380
- rects.push( rect );
381
- }
382
-
383
- return rects;
384
- }
385
-
386
- /**
387
- * Returns a bounding rectangle that contains all the given `rects`.
388
- *
389
- * @param {Iterable.<module:utils/dom/rect~Rect>} rects A list of rectangles that should be contained in the result rectangle.
390
- * @returns {module:utils/dom/rect~Rect|null} Bounding rectangle or `null` if no `rects` were given.
391
- */
392
- static getBoundingRect( rects ) {
393
- const boundingRectData = {
394
- left: Number.POSITIVE_INFINITY,
395
- top: Number.POSITIVE_INFINITY,
396
- right: Number.NEGATIVE_INFINITY,
397
- bottom: Number.NEGATIVE_INFINITY
398
- };
399
- let rectangleCount = 0;
400
-
401
- for ( const rect of rects ) {
402
- rectangleCount++;
403
-
404
- boundingRectData.left = Math.min( boundingRectData.left, rect.left );
405
- boundingRectData.top = Math.min( boundingRectData.top, rect.top );
406
- boundingRectData.right = Math.max( boundingRectData.right, rect.right );
407
- boundingRectData.bottom = Math.max( boundingRectData.bottom, rect.bottom );
408
- }
409
-
410
- if ( rectangleCount == 0 ) {
411
- return null;
412
- }
413
-
414
- boundingRectData.width = boundingRectData.right - boundingRectData.left;
415
- boundingRectData.height = boundingRectData.bottom - boundingRectData.top;
416
-
417
- return new Rect( boundingRectData );
418
- }
20
+ /**
21
+ * Creates an instance of rect.
22
+ *
23
+ * // Rect of an HTMLElement.
24
+ * const rectA = new Rect( document.body );
25
+ *
26
+ * // Rect of a DOM Range.
27
+ * const rectB = new Rect( document.getSelection().getRangeAt( 0 ) );
28
+ *
29
+ * // Rect of a window (web browser viewport).
30
+ * const rectC = new Rect( window );
31
+ *
32
+ * // Rect out of an object.
33
+ * const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
34
+ *
35
+ * // Rect out of another Rect instance.
36
+ * const rectE = new Rect( rectD );
37
+ *
38
+ * // Rect out of a ClientRect.
39
+ * const rectF = new Rect( document.body.getClientRects().item( 0 ) );
40
+ *
41
+ * **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any)
42
+ * ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders}
43
+ * to get the inner part of the rect.
44
+ *
45
+ * @param {module:utils/dom/rect~RectSource} source A source object to create the rect.
46
+ */
47
+ constructor(source) {
48
+ const isSourceRange = isRange(source);
49
+ Object.defineProperty(this, '_source', {
50
+ // If the source is a Rect instance, copy it's #_source.
51
+ value: source._source || source,
52
+ writable: true,
53
+ enumerable: false
54
+ });
55
+ if (isDomElement(source) || isSourceRange) {
56
+ // The `Rect` class depends on `getBoundingClientRect` and `getClientRects` DOM methods. If the source
57
+ // of a rect in an HTML element or a DOM range but it does not belong to any rendered DOM tree, these methods
58
+ // will fail to obtain the geometry and the rect instance makes little sense to the features using it.
59
+ // To get rid of this warning make sure the source passed to the constructor is a descendant of `window.document.body`.
60
+ // @if CK_DEBUG // const sourceNode = isSourceRange ? source.startContainer : source;
61
+ // @if CK_DEBUG // if ( !sourceNode.ownerDocument || !sourceNode.ownerDocument.body.contains( sourceNode ) ) {
62
+ // @if CK_DEBUG // console.warn(
63
+ // @if CK_DEBUG // 'rect-source-not-in-dom: The source of this rect does not belong to any rendered DOM tree.',
64
+ // @if CK_DEBUG // { source } );
65
+ // @if CK_DEBUG // }
66
+ if (isSourceRange) {
67
+ const rangeRects = Rect.getDomRangeRects(source);
68
+ copyRectProperties(this, Rect.getBoundingRect(rangeRects));
69
+ }
70
+ else {
71
+ copyRectProperties(this, source.getBoundingClientRect());
72
+ }
73
+ }
74
+ else if (isWindow(source)) {
75
+ const { innerWidth, innerHeight } = source;
76
+ copyRectProperties(this, {
77
+ top: 0,
78
+ right: innerWidth,
79
+ bottom: innerHeight,
80
+ left: 0,
81
+ width: innerWidth,
82
+ height: innerHeight
83
+ });
84
+ }
85
+ else {
86
+ copyRectProperties(this, source);
87
+ }
88
+ }
89
+ /**
90
+ * Returns a clone of the rect.
91
+ *
92
+ * @returns {module:utils/dom/rect~Rect} A cloned rect.
93
+ */
94
+ clone() {
95
+ return new Rect(this);
96
+ }
97
+ /**
98
+ * Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location.
99
+ *
100
+ * @param {Number} x Desired horizontal location.
101
+ * @param {Number} y Desired vertical location.
102
+ * @returns {module:utils/dom/rect~Rect} A rect which has been moved.
103
+ */
104
+ moveTo(x, y) {
105
+ this.top = y;
106
+ this.right = x + this.width;
107
+ this.bottom = y + this.height;
108
+ this.left = x;
109
+ return this;
110
+ }
111
+ /**
112
+ * Moves the rect in–place by a dedicated offset.
113
+ *
114
+ * @param {Number} x A horizontal offset.
115
+ * @param {Number} y A vertical offset
116
+ * @returns {module:utils/dom/rect~Rect} A rect which has been moved.
117
+ */
118
+ moveBy(x, y) {
119
+ this.top += y;
120
+ this.right += x;
121
+ this.left += x;
122
+ this.bottom += y;
123
+ return this;
124
+ }
125
+ /**
126
+ * Returns a new rect a a result of intersection with another rect.
127
+ *
128
+ * @param {module:utils/dom/rect~Rect} anotherRect
129
+ * @returns {module:utils/dom/rect~Rect|null}
130
+ */
131
+ getIntersection(anotherRect) {
132
+ const rect = {
133
+ top: Math.max(this.top, anotherRect.top),
134
+ right: Math.min(this.right, anotherRect.right),
135
+ bottom: Math.min(this.bottom, anotherRect.bottom),
136
+ left: Math.max(this.left, anotherRect.left),
137
+ width: 0,
138
+ height: 0
139
+ };
140
+ rect.width = rect.right - rect.left;
141
+ rect.height = rect.bottom - rect.top;
142
+ if (rect.width < 0 || rect.height < 0) {
143
+ return null;
144
+ }
145
+ else {
146
+ return new Rect(rect);
147
+ }
148
+ }
149
+ /**
150
+ * Returns the area of intersection with another rect.
151
+ *
152
+ * @param {module:utils/dom/rect~Rect} anotherRect
153
+ * @returns {Number} Area of intersection.
154
+ */
155
+ getIntersectionArea(anotherRect) {
156
+ const rect = this.getIntersection(anotherRect);
157
+ if (rect) {
158
+ return rect.getArea();
159
+ }
160
+ else {
161
+ return 0;
162
+ }
163
+ }
164
+ /**
165
+ * Returns the area of the rect.
166
+ *
167
+ * @returns {Number}
168
+ */
169
+ getArea() {
170
+ return this.width * this.height;
171
+ }
172
+ /**
173
+ * Returns a new rect, a part of the original rect, which is actually visible to the user,
174
+ * e.g. an original rect cropped by parent element rects which have `overflow` set in CSS
175
+ * other than `"visible"`.
176
+ *
177
+ * If there's no such visible rect, which is when the rect is limited by one or many of
178
+ * the ancestors, `null` is returned.
179
+ *
180
+ * @returns {module:utils/dom/rect~Rect|null} A visible rect instance or `null`, if there's none.
181
+ */
182
+ getVisible() {
183
+ const source = this._source;
184
+ let visibleRect = this.clone();
185
+ // There's no ancestor to crop <body> with the overflow.
186
+ if (!isBody(source)) {
187
+ let parent = source.parentNode || source.commonAncestorContainer;
188
+ // Check the ancestors all the way up to the <body>.
189
+ while (parent && !isBody(parent)) {
190
+ const parentRect = new Rect(parent);
191
+ const intersectionRect = visibleRect.getIntersection(parentRect);
192
+ if (intersectionRect) {
193
+ if (intersectionRect.getArea() < visibleRect.getArea()) {
194
+ // Reduce the visible rect to the intersection.
195
+ visibleRect = intersectionRect;
196
+ }
197
+ }
198
+ else {
199
+ // There's no intersection, the rect is completely invisible.
200
+ return null;
201
+ }
202
+ parent = parent.parentNode;
203
+ }
204
+ }
205
+ return visibleRect;
206
+ }
207
+ /**
208
+ * Checks if all property values ({@link #top}, {@link #left}, {@link #right},
209
+ * {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect
210
+ * instances.
211
+ *
212
+ * @param {module:utils/dom/rect~Rect} anotherRect A rect instance to compare with.
213
+ * @returns {Boolean} `true` when Rects are equal. `false` otherwise.
214
+ */
215
+ isEqual(anotherRect) {
216
+ for (const prop of rectProperties) {
217
+ if (this[prop] !== anotherRect[prop]) {
218
+ return false;
219
+ }
220
+ }
221
+ return true;
222
+ }
223
+ /**
224
+ * Checks whether a rect fully contains another rect instance.
225
+ *
226
+ * @param {module:utils/dom/rect~Rect} anotherRect
227
+ * @returns {Boolean} `true` if contains, `false` otherwise.
228
+ */
229
+ contains(anotherRect) {
230
+ const intersectRect = this.getIntersection(anotherRect);
231
+ return !!(intersectRect && intersectRect.isEqual(anotherRect));
232
+ }
233
+ /**
234
+ * Excludes scrollbars and CSS borders from the rect.
235
+ *
236
+ * * Borders are removed when {@link #_source} is an HTML element.
237
+ * * Scrollbars are excluded from HTML elements and the `window`.
238
+ *
239
+ * @returns {module:utils/dom/rect~Rect} A rect which has been updated.
240
+ */
241
+ excludeScrollbarsAndBorders() {
242
+ const source = this._source;
243
+ let scrollBarWidth, scrollBarHeight, direction;
244
+ if (isWindow(source)) {
245
+ scrollBarWidth = source.innerWidth - source.document.documentElement.clientWidth;
246
+ scrollBarHeight = source.innerHeight - source.document.documentElement.clientHeight;
247
+ direction = source.getComputedStyle(source.document.documentElement).direction;
248
+ }
249
+ else {
250
+ const borderWidths = getBorderWidths(source);
251
+ scrollBarWidth = source.offsetWidth - source.clientWidth - borderWidths.left - borderWidths.right;
252
+ scrollBarHeight = source.offsetHeight - source.clientHeight - borderWidths.top - borderWidths.bottom;
253
+ direction = source.ownerDocument.defaultView.getComputedStyle(source).direction;
254
+ this.left += borderWidths.left;
255
+ this.top += borderWidths.top;
256
+ this.right -= borderWidths.right;
257
+ this.bottom -= borderWidths.bottom;
258
+ this.width = this.right - this.left;
259
+ this.height = this.bottom - this.top;
260
+ }
261
+ this.width -= scrollBarWidth;
262
+ if (direction === 'ltr') {
263
+ this.right -= scrollBarWidth;
264
+ }
265
+ else {
266
+ this.left += scrollBarWidth;
267
+ }
268
+ this.height -= scrollBarHeight;
269
+ this.bottom -= scrollBarHeight;
270
+ return this;
271
+ }
272
+ /**
273
+ * Returns an array of rects of the given native DOM Range.
274
+ *
275
+ * @param {Range} range A native DOM range.
276
+ * @returns {Array.<module:utils/dom/rect~Rect>} DOM Range rects.
277
+ */
278
+ static getDomRangeRects(range) {
279
+ const rects = [];
280
+ // Safari does not iterate over ClientRectList using for...of loop.
281
+ const clientRects = Array.from(range.getClientRects());
282
+ if (clientRects.length) {
283
+ for (const rect of clientRects) {
284
+ rects.push(new Rect(rect));
285
+ }
286
+ }
287
+ // If there's no client rects for the Range, use parent container's bounding rect
288
+ // instead and adjust rect's width to simulate the actual geometry of such range.
289
+ // https://github.com/ckeditor/ckeditor5-utils/issues/153
290
+ // https://github.com/ckeditor/ckeditor5-ui/issues/317
291
+ else {
292
+ let startContainer = range.startContainer;
293
+ if (isText(startContainer)) {
294
+ startContainer = startContainer.parentNode;
295
+ }
296
+ const rect = new Rect(startContainer.getBoundingClientRect());
297
+ rect.right = rect.left;
298
+ rect.width = 0;
299
+ rects.push(rect);
300
+ }
301
+ return rects;
302
+ }
303
+ /**
304
+ * Returns a bounding rectangle that contains all the given `rects`.
305
+ *
306
+ * @param {Iterable.<module:utils/dom/rect~Rect>} rects A list of rectangles that should be contained in the result rectangle.
307
+ * @returns {module:utils/dom/rect~Rect|null} Bounding rectangle or `null` if no `rects` were given.
308
+ */
309
+ static getBoundingRect(rects) {
310
+ const boundingRectData = {
311
+ left: Number.POSITIVE_INFINITY,
312
+ top: Number.POSITIVE_INFINITY,
313
+ right: Number.NEGATIVE_INFINITY,
314
+ bottom: Number.NEGATIVE_INFINITY,
315
+ width: 0,
316
+ height: 0
317
+ };
318
+ let rectangleCount = 0;
319
+ for (const rect of rects) {
320
+ rectangleCount++;
321
+ boundingRectData.left = Math.min(boundingRectData.left, rect.left);
322
+ boundingRectData.top = Math.min(boundingRectData.top, rect.top);
323
+ boundingRectData.right = Math.max(boundingRectData.right, rect.right);
324
+ boundingRectData.bottom = Math.max(boundingRectData.bottom, rect.bottom);
325
+ }
326
+ if (rectangleCount == 0) {
327
+ return null;
328
+ }
329
+ boundingRectData.width = boundingRectData.right - boundingRectData.left;
330
+ boundingRectData.height = boundingRectData.bottom - boundingRectData.top;
331
+ return new Rect(boundingRectData);
332
+ }
419
333
  }
420
-
421
334
  // Acquires all the rect properties from the passed source.
422
335
  //
423
336
  // @private
424
337
  // @param {module:utils/dom/rect~Rect} rect
425
- // @param {ClientRect|module:utils/dom/rect~Rect|Object} source
426
- function copyRectProperties( rect, source ) {
427
- for ( const p of rectProperties ) {
428
- rect[ p ] = source[ p ];
429
- }
338
+ // @param {module:utils/dom/rect~RectLike} source
339
+ function copyRectProperties(rect, source) {
340
+ for (const p of rectProperties) {
341
+ rect[p] = source[p];
342
+ }
430
343
  }
431
-
432
344
  // Checks if provided object is a <body> HTML element.
433
345
  //
434
346
  // @private
435
- // @param {HTMLElement|Range} elementOrRange
347
+ // @param {*} value
436
348
  // @returns {Boolean}
437
- function isBody( elementOrRange ) {
438
- if ( !isElement( elementOrRange ) ) {
439
- return false;
440
- }
441
-
442
- return elementOrRange === elementOrRange.ownerDocument.body;
349
+ function isBody(value) {
350
+ if (!isDomElement(value)) {
351
+ return false;
352
+ }
353
+ return value === value.ownerDocument.body;
354
+ }
355
+ // Checks if provided object is a DOM Element.
356
+ //
357
+ // It's just a wrapper for lodash `isElement` but with type guard.
358
+ //
359
+ // @private
360
+ // @param {*} value
361
+ // @returns {Boolean}
362
+ function isDomElement(value) {
363
+ return isElement(value);
443
364
  }