@ckeditor/ckeditor5-utils 29.2.0 → 32.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +2 -2
- package/package.json +6 -8
- package/src/areconnectedthroughproperties.js +1 -1
- package/src/ckeditorerror.js +40 -6
- package/src/collection.js +2 -2
- package/src/comparearrays.js +1 -1
- package/src/config.js +1 -1
- package/src/count.js +1 -1
- package/src/diff.js +1 -1
- package/src/difftochanges.js +1 -1
- package/src/dom/createelement.js +1 -1
- package/src/dom/emittermixin.js +118 -52
- package/src/dom/getancestors.js +1 -1
- package/src/dom/getborderwidths.js +1 -1
- package/src/dom/getcommonancestor.js +1 -1
- package/src/dom/getdatafromelement.js +1 -1
- package/src/dom/getpositionedancestor.js +1 -1
- package/src/dom/global.js +1 -1
- package/src/dom/indexof.js +1 -1
- package/src/dom/insertat.js +1 -1
- package/src/dom/iscomment.js +20 -0
- package/src/dom/isnode.js +1 -1
- package/src/dom/isrange.js +1 -1
- package/src/dom/istext.js +1 -1
- package/src/dom/isvisible.js +25 -0
- package/src/dom/iswindow.js +1 -1
- package/src/dom/position.js +293 -190
- package/src/dom/rect.js +1 -1
- package/src/dom/remove.js +1 -1
- package/src/dom/resizeobserver.js +1 -1
- package/src/dom/scroll.js +1 -1
- package/src/dom/setdatainelement.js +1 -1
- package/src/dom/tounit.js +1 -1
- package/src/elementreplacer.js +1 -1
- package/src/emittermixin.js +1 -1
- package/src/env.js +38 -1
- package/src/eventinfo.js +1 -1
- package/src/fastdiff.js +1 -1
- package/src/first.js +1 -1
- package/src/focustracker.js +1 -1
- package/src/index.js +2 -1
- package/src/isiterable.js +1 -1
- package/src/keyboard.js +7 -1
- package/src/keystrokehandler.js +1 -1
- package/src/language.js +1 -1
- package/src/locale.js +1 -1
- package/src/mapsequal.js +1 -1
- package/src/mix.js +1 -1
- package/src/nth.js +1 -1
- package/src/objecttomap.js +1 -1
- package/src/observablemixin.js +1 -1
- package/src/priorities.js +1 -1
- package/src/spy.js +1 -1
- package/src/toarray.js +1 -1
- package/src/tomap.js +1 -1
- package/src/translation-service.js +2 -2
- package/src/uid.js +1 -1
- package/src/unicode.js +1 -1
- package/src/version.js +2 -2
- package/CHANGELOG.md +0 -324
package/src/dom/position.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
|
@@ -13,6 +13,8 @@ import getPositionedAncestor from './getpositionedancestor';
|
|
|
13
13
|
import getBorderWidths from './getborderwidths';
|
|
14
14
|
import { isFunction } from 'lodash-es';
|
|
15
15
|
|
|
16
|
+
// @if CK_DEBUG_POSITION // import { RectDrawer } from '@ckeditor/ckeditor5-minimap/src/utils';
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* Calculates the `position: absolute` coordinates of a given element so it can be positioned with respect to the
|
|
18
20
|
* target in the visually most efficient way, taking various restrictions like viewport or limiter geometry
|
|
@@ -74,10 +76,10 @@ import { isFunction } from 'lodash-es';
|
|
|
74
76
|
* element.style.top = top;
|
|
75
77
|
* element.style.left = left;
|
|
76
78
|
*
|
|
77
|
-
* @param {module:utils/dom/position~Options} options
|
|
79
|
+
* @param {module:utils/dom/position~Options} options The input data and configuration of the helper.
|
|
78
80
|
* @returns {module:utils/dom/position~Position}
|
|
79
81
|
*/
|
|
80
|
-
export function getOptimalPosition( { element, target, positions, limiter, fitInViewport } ) {
|
|
82
|
+
export function getOptimalPosition( { element, target, positions, limiter, fitInViewport, viewportOffsetConfig } ) {
|
|
81
83
|
// If the {@link module:utils/dom/position~Options#target} is a function, use what it returns.
|
|
82
84
|
// https://github.com/ckeditor/ckeditor5-utils/issues/157
|
|
83
85
|
if ( isFunction( target ) ) {
|
|
@@ -94,52 +96,54 @@ export function getOptimalPosition( { element, target, positions, limiter, fitIn
|
|
|
94
96
|
const elementRect = new Rect( element );
|
|
95
97
|
const targetRect = new Rect( target );
|
|
96
98
|
|
|
97
|
-
let
|
|
98
|
-
|
|
99
|
+
let bestPosition;
|
|
100
|
+
|
|
101
|
+
// @if CK_DEBUG_POSITION // RectDrawer.clear();
|
|
102
|
+
// @if CK_DEBUG_POSITION // RectDrawer.draw( targetRect, { outlineWidth: '5px' }, 'Target' );
|
|
103
|
+
|
|
104
|
+
const positionOptions = { targetRect, elementRect, positionedElementAncestor };
|
|
99
105
|
|
|
100
106
|
// If there are no limits, just grab the very first position and be done with that drama.
|
|
101
107
|
if ( !limiter && !fitInViewport ) {
|
|
102
|
-
|
|
108
|
+
bestPosition = new Position( positions[ 0 ], positionOptions );
|
|
103
109
|
} else {
|
|
104
110
|
const limiterRect = limiter && new Rect( limiter ).getVisible();
|
|
105
|
-
const viewportRect = fitInViewport &&
|
|
106
|
-
const bestPosition = getBestPositionNameAndRect( positions, { targetRect, elementRect, limiterRect, viewportRect } );
|
|
111
|
+
const viewportRect = fitInViewport && getConstrainedViewportRect( viewportOffsetConfig );
|
|
107
112
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
}
|
|
113
|
+
// @if CK_DEBUG_POSITION // if ( viewportRect ) {
|
|
114
|
+
// @if CK_DEBUG_POSITION // RectDrawer.draw( viewportRect, { outlineWidth: '5px' }, 'Viewport' );
|
|
115
|
+
// @if CK_DEBUG_POSITION // }
|
|
112
116
|
|
|
113
|
-
|
|
117
|
+
// @if CK_DEBUG_POSITION // if ( limiter ) {
|
|
118
|
+
// @if CK_DEBUG_POSITION // RectDrawer.draw( limiterRect, { outlineWidth: '5px', outlineColor: 'green' }, 'Visible limiter' );
|
|
119
|
+
// @if CK_DEBUG_POSITION // }
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
Object.assign( positionOptions, { limiterRect, viewportRect } );
|
|
122
|
+
|
|
123
|
+
// If there's no best position found, i.e. when all intersections have no area because
|
|
124
|
+
// rects have no width or height, then just use the first available position.
|
|
125
|
+
bestPosition = getBestPosition( positions, positionOptions ) || new Position( positions[ 0 ], positionOptions );
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
return
|
|
120
|
-
left: absoluteRectCoordinates.left,
|
|
121
|
-
top: absoluteRectCoordinates.top,
|
|
122
|
-
name: bestPositionName
|
|
123
|
-
};
|
|
128
|
+
return bestPosition;
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
//
|
|
131
|
+
// Returns a viewport `Rect` shrunk by the viewport offset config from all sides.
|
|
127
132
|
//
|
|
128
133
|
// @private
|
|
129
|
-
// @param {
|
|
130
|
-
// @
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
function getPositionNameAndRect( position, targetRect, elementRect ) {
|
|
134
|
-
const positionData = position( targetRect, elementRect );
|
|
135
|
-
|
|
136
|
-
if ( !positionData ) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
134
|
+
// @param {Object} An object containing viewportOffset config.
|
|
135
|
+
// @returns {utils/dom/rect~Rect} A shrunken rect of the viewport.
|
|
136
|
+
function getConstrainedViewportRect( viewportOffsetConfig ) {
|
|
137
|
+
viewportOffsetConfig = Object.assign( { top: 0, bottom: 0, left: 0, right: 0 }, viewportOffsetConfig );
|
|
139
138
|
|
|
140
|
-
const
|
|
139
|
+
const viewportRect = new Rect( global.window );
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
viewportRect.top += viewportOffsetConfig.top;
|
|
142
|
+
viewportRect.height -= viewportOffsetConfig.top;
|
|
143
|
+
viewportRect.bottom -= viewportOffsetConfig.bottom;
|
|
144
|
+
viewportRect.height -= viewportOffsetConfig.bottom;
|
|
145
|
+
|
|
146
|
+
return viewportRect;
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
// For a given array of positioning functions, returns such that provides the best
|
|
@@ -148,157 +152,50 @@ function getPositionNameAndRect( position, targetRect, elementRect ) {
|
|
|
148
152
|
// @private
|
|
149
153
|
//
|
|
150
154
|
// @param {Object} options
|
|
151
|
-
// @param {module:utils/dom/position~Options#positions} positions Functions returning
|
|
152
|
-
// to be checked, in the order of preference.
|
|
155
|
+
// @param {module:utils/dom/position~Options#positions} positions Functions returning
|
|
156
|
+
// {@link module:utils/dom/position~Position}to be checked, in the order of preference.
|
|
153
157
|
// @param {Object} options
|
|
154
158
|
// @param {utils/dom/rect~Rect} options.targetRect A rect of the {@link module:utils/dom/position~Options#target}.
|
|
155
|
-
// @param {utils/dom/rect~Rect} options.elementRect A rect of positioned
|
|
159
|
+
// @param {utils/dom/rect~Rect} options.elementRect A rect of positioned
|
|
160
|
+
// {@link module:utils/dom/position~Options#element}.
|
|
156
161
|
// @param {utils/dom/rect~Rect} options.limiterRect A rect of the {@link module:utils/dom/position~Options#limiter}.
|
|
157
|
-
// @param {utils/dom/rect~Rect} options.viewportRect A rect of the viewport.
|
|
162
|
+
// @param {utils/dom/rect~Rect} options.viewportRect A rect of the {@link module:utils/dom/position~Options#viewport}.
|
|
158
163
|
//
|
|
159
164
|
// @returns {Array} An array containing the name of the position and it's rect.
|
|
160
|
-
function
|
|
161
|
-
const { elementRect
|
|
162
|
-
|
|
163
|
-
// This is when element is fully visible.
|
|
164
|
-
const elementRectArea = elementRect.getArea();
|
|
165
|
-
|
|
166
|
-
// Let's calculate intersection areas for positions. It will end early if best match is found.
|
|
167
|
-
const processedPositions = processPositionsToAreas( positions, options );
|
|
168
|
-
|
|
169
|
-
// First let's check all positions that fully fit in the viewport.
|
|
170
|
-
if ( viewportRect ) {
|
|
171
|
-
const processedPositionsInViewport = processedPositions.filter( ( { viewportIntersectArea } ) => {
|
|
172
|
-
return viewportIntersectArea === elementRectArea;
|
|
173
|
-
} );
|
|
174
|
-
|
|
175
|
-
// Try to find best position from those which fit completely in viewport.
|
|
176
|
-
const bestPositionData = getBestOfProcessedPositions( processedPositionsInViewport, elementRectArea );
|
|
177
|
-
|
|
178
|
-
if ( bestPositionData ) {
|
|
179
|
-
return bestPositionData;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Either there is no viewportRect or there is no position that fits completely in the viewport.
|
|
184
|
-
return getBestOfProcessedPositions( processedPositions, elementRectArea );
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// For a given array of positioning functions, calculates intersection areas for them.
|
|
188
|
-
//
|
|
189
|
-
// Note: If some position fully fits into the `limiterRect`, it will be returned early, without further consideration
|
|
190
|
-
// of other positions.
|
|
191
|
-
//
|
|
192
|
-
// @private
|
|
193
|
-
//
|
|
194
|
-
// @param {module:utils/dom/position~Options#positions} positions Functions returning {@link module:utils/dom/position~Position}
|
|
195
|
-
// to be checked, in the order of preference.
|
|
196
|
-
// @param {Object} options
|
|
197
|
-
// @param {utils/dom/rect~Rect} options.targetRect A rect of the {@link module:utils/dom/position~Options#target}.
|
|
198
|
-
// @param {utils/dom/rect~Rect} options.elementRect A rect of positioned {@link module:utils/dom/position~Options#element}.
|
|
199
|
-
// @param {utils/dom/rect~Rect} options.limiterRect A rect of the {@link module:utils/dom/position~Options#limiter}.
|
|
200
|
-
// @param {utils/dom/rect~Rect} options.viewportRect A rect of the viewport.
|
|
201
|
-
//
|
|
202
|
-
// @returns {Array.<Object>} Array of positions with calculated intersection areas. Each item is an object containing:
|
|
203
|
-
// * {String} positionName Name of position.
|
|
204
|
-
// * {utils/dom/rect~Rect} positionRect Rect of position.
|
|
205
|
-
// * {Number} limiterIntersectArea Area of intersection of the position with limiter part that is in the viewport.
|
|
206
|
-
// * {Number} viewportIntersectArea Area of intersection of the position with viewport.
|
|
207
|
-
function processPositionsToAreas( positions, { targetRect, elementRect, limiterRect, viewportRect } ) {
|
|
208
|
-
const processedPositions = [];
|
|
165
|
+
function getBestPosition( positions, options ) {
|
|
166
|
+
const { elementRect } = options;
|
|
209
167
|
|
|
210
168
|
// This is when element is fully visible.
|
|
211
169
|
const elementRectArea = elementRect.getArea();
|
|
212
170
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const [ positionName, positionRect ] = positionData;
|
|
221
|
-
let limiterIntersectArea = 0;
|
|
222
|
-
let viewportIntersectArea = 0;
|
|
223
|
-
|
|
224
|
-
if ( limiterRect ) {
|
|
225
|
-
if ( viewportRect ) {
|
|
226
|
-
// Consider only the part of the limiter which is visible in the viewport. So the limiter is getting limited.
|
|
227
|
-
const limiterViewportIntersectRect = limiterRect.getIntersection( viewportRect );
|
|
228
|
-
|
|
229
|
-
if ( limiterViewportIntersectRect ) {
|
|
230
|
-
// If the limiter is within the viewport, then check the intersection between that part of the
|
|
231
|
-
// limiter and actual position.
|
|
232
|
-
limiterIntersectArea = limiterViewportIntersectRect.getIntersectionArea( positionRect );
|
|
233
|
-
}
|
|
234
|
-
} else {
|
|
235
|
-
limiterIntersectArea = limiterRect.getIntersectionArea( positionRect );
|
|
236
|
-
}
|
|
237
|
-
}
|
|
171
|
+
const positionInstances = positions
|
|
172
|
+
.map( positioningFunction => new Position( positioningFunction, options ) )
|
|
173
|
+
// Some positioning functions may return `null` if they don't want to participate.
|
|
174
|
+
.filter( position => !!position.name );
|
|
238
175
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
176
|
+
let maxFitFactor = 0;
|
|
177
|
+
let bestPosition = null;
|
|
242
178
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
positionRect,
|
|
246
|
-
limiterIntersectArea,
|
|
247
|
-
viewportIntersectArea
|
|
248
|
-
};
|
|
179
|
+
for ( const position of positionInstances ) {
|
|
180
|
+
const { _limiterIntersectionArea, _viewportIntersectionArea } = position;
|
|
249
181
|
|
|
250
182
|
// If a such position is found that element is fully contained by the limiter then, obviously,
|
|
251
183
|
// there will be no better one, so finishing.
|
|
252
|
-
if (
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
processedPositions.push( processedPosition );
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return processedPositions;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// For a given array of processed position data (with calculated Rects for positions and intersection areas)
|
|
263
|
-
// returns such that provides the best fit of the `elementRect` into the `limiterRect` and `viewportRect` at the same time.
|
|
264
|
-
//
|
|
265
|
-
// **Note**: It will return early if some position fully fits into the `limiterRect`.
|
|
266
|
-
//
|
|
267
|
-
// @private
|
|
268
|
-
// @param {Array.<Object>} Array of positions with calculated intersection areas (in order of preference).
|
|
269
|
-
// Each item is an object containing:
|
|
270
|
-
//
|
|
271
|
-
// * {String} positionName Name of position.
|
|
272
|
-
// * {utils/dom/rect~Rect} positionRect Rect of position.
|
|
273
|
-
// * {Number} limiterIntersectArea Area of intersection of the position with limiter part that is in the viewport.
|
|
274
|
-
// * {Number} viewportIntersectArea Area of intersection of the position with viewport.
|
|
275
|
-
//
|
|
276
|
-
// @param {Number} elementRectArea Area of positioned {@link module:utils/dom/position~Options#element}.
|
|
277
|
-
// @returns {Array|null} An array containing the name of the position and it's rect, or null if not found.
|
|
278
|
-
function getBestOfProcessedPositions( processedPositions, elementRectArea ) {
|
|
279
|
-
let maxFitFactor = 0;
|
|
280
|
-
let bestPositionRect;
|
|
281
|
-
let bestPositionName;
|
|
282
|
-
|
|
283
|
-
for ( const { positionName, positionRect, limiterIntersectArea, viewportIntersectArea } of processedPositions ) {
|
|
284
|
-
// If a such position is found that element is fully container by the limiter then, obviously,
|
|
285
|
-
// there will be no better one, so finishing.
|
|
286
|
-
if ( limiterIntersectArea === elementRectArea ) {
|
|
287
|
-
return [ positionName, positionRect ];
|
|
184
|
+
if ( _limiterIntersectionArea === elementRectArea ) {
|
|
185
|
+
return position;
|
|
288
186
|
}
|
|
289
187
|
|
|
290
|
-
// To maximize both viewport and limiter intersection areas we use distance on
|
|
291
|
-
// and
|
|
292
|
-
const fitFactor =
|
|
188
|
+
// To maximize both viewport and limiter intersection areas we use distance on _viewportIntersectionArea
|
|
189
|
+
// and _limiterIntersectionArea plane (without sqrt because we are looking for max value).
|
|
190
|
+
const fitFactor = _viewportIntersectionArea ** 2 + _limiterIntersectionArea ** 2;
|
|
293
191
|
|
|
294
192
|
if ( fitFactor > maxFitFactor ) {
|
|
295
193
|
maxFitFactor = fitFactor;
|
|
296
|
-
|
|
297
|
-
bestPositionName = positionName;
|
|
194
|
+
bestPosition = position;
|
|
298
195
|
}
|
|
299
196
|
}
|
|
300
197
|
|
|
301
|
-
return
|
|
198
|
+
return bestPosition;
|
|
302
199
|
}
|
|
303
200
|
|
|
304
201
|
// For a given absolute Rect coordinates object and a positioned element ancestor, it returns an object with
|
|
@@ -310,41 +207,44 @@ function getBestOfProcessedPositions( processedPositions, elementRectArea ) {
|
|
|
310
207
|
//
|
|
311
208
|
// @private
|
|
312
209
|
//
|
|
313
|
-
// @param {
|
|
314
|
-
// @param {
|
|
315
|
-
// @param {
|
|
210
|
+
// @param {utils/dom/rect~Rect} rect A rect with absolute rect coordinates.
|
|
211
|
+
// @param {Number} rect.top
|
|
212
|
+
// @param {Number} rect.left
|
|
316
213
|
// @param {HTMLElement} positionedElementAncestor An ancestor element that should be considered.
|
|
317
214
|
//
|
|
318
|
-
// @returns {
|
|
215
|
+
// @returns {utils/dom/rect~Rect} A rect corresponding to `absoluteRect` input but with values shifted
|
|
319
216
|
// to make up for the positioned element ancestor.
|
|
320
|
-
function
|
|
321
|
-
const ancestorPosition =
|
|
217
|
+
function shiftRectToCompensatePositionedAncestor( rect, positionedElementAncestor ) {
|
|
218
|
+
const ancestorPosition = getRectForAbsolutePositioning( new Rect( positionedElementAncestor ) );
|
|
322
219
|
const ancestorBorderWidths = getBorderWidths( positionedElementAncestor );
|
|
323
220
|
|
|
221
|
+
let moveX = 0;
|
|
222
|
+
let moveY = 0;
|
|
223
|
+
|
|
324
224
|
// (https://github.com/ckeditor/ckeditor5-ui-default/issues/126)
|
|
325
225
|
// If there's some positioned ancestor of the panel, then its `Rect` must be taken into
|
|
326
226
|
// consideration. `Rect` is always relative to the viewport while `position: absolute` works
|
|
327
227
|
// with respect to that positioned ancestor.
|
|
328
|
-
|
|
329
|
-
|
|
228
|
+
moveX -= ancestorPosition.left;
|
|
229
|
+
moveY -= ancestorPosition.top;
|
|
330
230
|
|
|
331
231
|
// (https://github.com/ckeditor/ckeditor5-utils/issues/139)
|
|
332
232
|
// If there's some positioned ancestor of the panel, not only its position must be taken into
|
|
333
233
|
// consideration (see above) but also its internal scrolls. Scroll have an impact here because `Rect`
|
|
334
234
|
// is relative to the viewport (it doesn't care about scrolling), while `position: absolute`
|
|
335
235
|
// must compensate that scrolling.
|
|
336
|
-
|
|
337
|
-
|
|
236
|
+
moveX += positionedElementAncestor.scrollLeft;
|
|
237
|
+
moveY += positionedElementAncestor.scrollTop;
|
|
338
238
|
|
|
339
239
|
// (https://github.com/ckeditor/ckeditor5-utils/issues/139)
|
|
340
240
|
// If there's some positioned ancestor of the panel, then its `Rect` includes its CSS `borderWidth`
|
|
341
241
|
// while `position: absolute` positioning does not consider it.
|
|
342
242
|
// E.g. `{ position: absolute, top: 0, left: 0 }` means upper left corner of the element,
|
|
343
243
|
// not upper-left corner of its border.
|
|
344
|
-
|
|
345
|
-
|
|
244
|
+
moveX -= ancestorBorderWidths.left;
|
|
245
|
+
moveY -= ancestorBorderWidths.top;
|
|
346
246
|
|
|
347
|
-
|
|
247
|
+
rect.moveBy( moveX, moveY );
|
|
348
248
|
}
|
|
349
249
|
|
|
350
250
|
// DOMRect (also Rect) works in a scroll–independent geometry but `position: absolute` doesn't.
|
|
@@ -353,13 +253,172 @@ function shiftRectCoordinatesDueToPositionedAncestor( { left, top }, positionedE
|
|
|
353
253
|
// @private
|
|
354
254
|
// @param {utils/dom/rect~Rect} rect A rect to be converted.
|
|
355
255
|
// @returns {Object} Object containing `left` and `top` properties, in absolute coordinates.
|
|
356
|
-
function
|
|
256
|
+
function getRectForAbsolutePositioning( rect ) {
|
|
357
257
|
const { scrollX, scrollY } = global.window;
|
|
358
258
|
|
|
359
|
-
return
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
259
|
+
return rect.clone().moveBy( scrollX, scrollY );
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* A position class which instances are created and used by the {@link module:utils/dom/position~getOptimalPosition} helper.
|
|
264
|
+
*
|
|
265
|
+
* {@link module:utils/dom/position~Position#top} and {@link module:utils/dom/position~Position#left} properties of the position instance
|
|
266
|
+
* translate directly to the `top` and `left` properties in CSS "`position: absolute` coordinate system". If set on the positioned element
|
|
267
|
+
* in DOM, they will make it display it in the right place in the viewport.
|
|
268
|
+
*/
|
|
269
|
+
export class Position {
|
|
270
|
+
/**
|
|
271
|
+
* Creates an instance of the {@link module:utils/dom/position~Position} class.
|
|
272
|
+
*
|
|
273
|
+
* @param {module:utils/dom/position~positioningFunction} [positioningFunction] function The function that defines the expected
|
|
274
|
+
* coordinates the positioned element should move to.
|
|
275
|
+
* @param {Object} [options] options object.
|
|
276
|
+
* @param {module:utils/dom/rect~Rect} options.elementRect The positioned element rect.
|
|
277
|
+
* @param {module:utils/dom/rect~Rect} options.targetRect The target element rect.
|
|
278
|
+
* @param {module:utils/dom/rect~Rect} options.viewportRect The viewport rect.
|
|
279
|
+
* @param {HTMLElement|null} [options.positionedElementAncestor] Nearest element ancestor element which CSS position is not "static".
|
|
280
|
+
*/
|
|
281
|
+
constructor( positioningFunction, options ) {
|
|
282
|
+
const positioningFunctionOutput = positioningFunction( options.targetRect, options.elementRect, options.viewportRect );
|
|
283
|
+
|
|
284
|
+
// Nameless position for a function that didn't participate.
|
|
285
|
+
if ( !positioningFunctionOutput ) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const { left, top, name, config } = positioningFunctionOutput;
|
|
290
|
+
|
|
291
|
+
Object.assign( this, { name, config } );
|
|
292
|
+
|
|
293
|
+
this._positioningFunctionCorrdinates = { left, top };
|
|
294
|
+
this._options = options;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Position name.
|
|
298
|
+
*
|
|
299
|
+
* @readonly
|
|
300
|
+
* @member {String} #name
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Additional position configuration, as passed from the {@link module:utils/dom/position~positioningFunction positioning function}.
|
|
305
|
+
*
|
|
306
|
+
* This object can be use, for instance, to pass through presentation options used by the consumer of the
|
|
307
|
+
* {@link module:utils/dom/position~getOptimalPosition} helper.
|
|
308
|
+
*
|
|
309
|
+
* @readonly
|
|
310
|
+
* @member {Object} #config
|
|
311
|
+
*/
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The left value in pixels in the CSS `position: absolute` coordinate system.
|
|
316
|
+
* Set it on the positioned element in DOM to move it to the position.
|
|
317
|
+
*
|
|
318
|
+
* @readonly
|
|
319
|
+
* @type {Number}
|
|
320
|
+
*/
|
|
321
|
+
get left() {
|
|
322
|
+
return this._absoluteRect.left;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The top value in pixels in the CSS `position: absolute` coordinate system.
|
|
327
|
+
* Set it on the positioned element in DOM to move it to the position.
|
|
328
|
+
*
|
|
329
|
+
* @readonly
|
|
330
|
+
* @type {Number}
|
|
331
|
+
*/
|
|
332
|
+
get top() {
|
|
333
|
+
return this._absoluteRect.top;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* An intersection area between positioned element and limiter within viewport constraints.
|
|
338
|
+
*
|
|
339
|
+
* @readonly
|
|
340
|
+
* @private
|
|
341
|
+
* @type {Number}
|
|
342
|
+
*/
|
|
343
|
+
get _limiterIntersectionArea() {
|
|
344
|
+
const limiterRect = this._options.limiterRect;
|
|
345
|
+
|
|
346
|
+
if ( limiterRect ) {
|
|
347
|
+
const viewportRect = this._options.viewportRect;
|
|
348
|
+
|
|
349
|
+
if ( viewportRect ) {
|
|
350
|
+
// Consider only the part of the limiter which is visible in the viewport. So the limiter is getting limited.
|
|
351
|
+
const limiterViewportIntersectRect = limiterRect.getIntersection( viewportRect );
|
|
352
|
+
|
|
353
|
+
if ( limiterViewportIntersectRect ) {
|
|
354
|
+
// If the limiter is within the viewport, then check the intersection between that part of the
|
|
355
|
+
// limiter and actual position.
|
|
356
|
+
return limiterViewportIntersectRect.getIntersectionArea( this._rect );
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
return limiterRect.getIntersectionArea( this._rect );
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* An intersection area between positioned element and viewport.
|
|
368
|
+
*
|
|
369
|
+
* @readonly
|
|
370
|
+
* @private
|
|
371
|
+
* @type {Number}
|
|
372
|
+
*/
|
|
373
|
+
get _viewportIntersectionArea() {
|
|
374
|
+
const viewportRect = this._options.viewportRect;
|
|
375
|
+
|
|
376
|
+
if ( viewportRect ) {
|
|
377
|
+
return viewportRect.getIntersectionArea( this._rect );
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* An already positioned element rect. A clone of the element rect passed to the constructor
|
|
385
|
+
* but placed in the viewport according to the positioning function.
|
|
386
|
+
*
|
|
387
|
+
* @private
|
|
388
|
+
* @type {module:utils/dom/rect~Rect}
|
|
389
|
+
*/
|
|
390
|
+
get _rect() {
|
|
391
|
+
if ( this._cachedRect ) {
|
|
392
|
+
return this._cachedRect;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
this._cachedRect = this._options.elementRect.clone().moveTo(
|
|
396
|
+
this._positioningFunctionCorrdinates.left,
|
|
397
|
+
this._positioningFunctionCorrdinates.top
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
return this._cachedRect;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* An already absolutely positioned element rect. See ({@link #_rect}).
|
|
405
|
+
*
|
|
406
|
+
* @private
|
|
407
|
+
* @type {module:utils/dom/rect~Rect}
|
|
408
|
+
*/
|
|
409
|
+
get _absoluteRect() {
|
|
410
|
+
if ( this._cachedAbsoluteRect ) {
|
|
411
|
+
return this._cachedAbsoluteRect;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this._cachedAbsoluteRect = getRectForAbsolutePositioning( this._rect );
|
|
415
|
+
|
|
416
|
+
if ( this._options.positionedElementAncestor ) {
|
|
417
|
+
shiftRectToCompensatePositionedAncestor( this._cachedAbsoluteRect, this._options.positionedElementAncestor );
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return this._cachedAbsoluteRect;
|
|
421
|
+
}
|
|
363
422
|
}
|
|
364
423
|
|
|
365
424
|
/**
|
|
@@ -381,12 +440,15 @@ function getAbsoluteRectCoordinates( { left, top } ) {
|
|
|
381
440
|
*/
|
|
382
441
|
|
|
383
442
|
/**
|
|
384
|
-
* An array of functions
|
|
385
|
-
*
|
|
443
|
+
* An array of positioning functions.
|
|
444
|
+
*
|
|
445
|
+
* **Note**: Positioning functions are processed in the order of preference. The first function that works
|
|
446
|
+
* in the current environment (e.g. offers the complete fit in the viewport geometry) will be picked by
|
|
447
|
+
* `getOptimalPosition()`.
|
|
386
448
|
*
|
|
387
|
-
* **Note**:
|
|
449
|
+
* **Note**: Any positioning function returning `null` is ignored.
|
|
388
450
|
*
|
|
389
|
-
* @member {Array.<
|
|
451
|
+
* @member {Array.<module:utils/dom/position~positioningFunction>} #positions
|
|
390
452
|
*/
|
|
391
453
|
|
|
392
454
|
/**
|
|
@@ -404,12 +466,53 @@ function getAbsoluteRectCoordinates( { left, top } ) {
|
|
|
404
466
|
*/
|
|
405
467
|
|
|
406
468
|
/**
|
|
407
|
-
*
|
|
408
|
-
* system, along with position name.
|
|
469
|
+
* Viewport offset config object. It restricts the visible viewport available to the `getOptimalPosition()` from each side.
|
|
409
470
|
*
|
|
410
|
-
*
|
|
471
|
+
* {
|
|
472
|
+
* top: 50,
|
|
473
|
+
* right: 50,
|
|
474
|
+
* bottom: 50,
|
|
475
|
+
* left: 50
|
|
476
|
+
* }
|
|
477
|
+
*
|
|
478
|
+
* @member {Object} #viewportOffsetConfig
|
|
479
|
+
*/
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* A positioning function which, based on positioned element and target {@link module:utils/dom/rect~Rect Rects}, returns rect coordinates
|
|
483
|
+
* representing the geometrical relation between them. Used by the {@link module:utils/dom/position~getOptimalPosition} helper.
|
|
484
|
+
*
|
|
485
|
+
* // This simple position will place the element directly under the target, in the middle:
|
|
486
|
+
* //
|
|
487
|
+
* // [ Target ]
|
|
488
|
+
* // +-----------------+
|
|
489
|
+
* // | Element |
|
|
490
|
+
* // +-----------------+
|
|
491
|
+
* //
|
|
492
|
+
* const position = ( targetRect, elementRect, [ viewportRect ] ) => ( {
|
|
493
|
+
* top: targetRect.bottom,
|
|
494
|
+
* left: targetRect.left + targetRect.width / 2 - elementRect.width / 2,
|
|
495
|
+
* name: 'bottomMiddle',
|
|
496
|
+
*
|
|
497
|
+
* // Note: The config is optional.
|
|
498
|
+
* config: {
|
|
499
|
+
* zIndex: '999'
|
|
500
|
+
* }
|
|
501
|
+
* } );
|
|
411
502
|
*
|
|
412
|
-
* @
|
|
413
|
-
* @
|
|
414
|
-
* @
|
|
503
|
+
* @callback module:utils/dom/position~positioningFunction
|
|
504
|
+
* @param {module:utils/dom/rect~Rect} elementRect The rect of the element to be positioned.
|
|
505
|
+
* @param {module:utils/dom/rect~Rect} targetRect The rect of the target the element (its rect) is relatively positioned to.
|
|
506
|
+
* @param {module:utils/dom/rect~Rect} viewportRect The rect of the visual browser viewport.
|
|
507
|
+
* @returns {Object|null} return When the function returns `null`, it will not be considered by
|
|
508
|
+
* {@link module:utils/dom/position~getOptimalPosition}.
|
|
509
|
+
* @returns {Number} return.top The `top` value of the element rect that would represent the position.
|
|
510
|
+
* @returns {Number} return.left The `left` value of the element rect that would represent the position.
|
|
511
|
+
* @returns {Number} return.name The name of the position. It helps the user of the {@link module:utils/dom/position~getOptimalPosition}
|
|
512
|
+
* helper to recognize different positioning function results. It will pass through to the {@link module:utils/dom/position~Position}
|
|
513
|
+
* returned by the helper.
|
|
514
|
+
* @returns {Number} [return.config] An optional configuration that will pass-through the
|
|
515
|
+
* {@link module:utils/dom/position~getOptimalPosition} helper to the {@link module:utils/dom/position~Position} returned by this helper.
|
|
516
|
+
* This configuration may, for instance, let the user of {@link module:utils/dom/position~getOptimalPosition} know that this particular
|
|
517
|
+
* position comes with a certain presentation.
|
|
415
518
|
*/
|
package/src/dom/rect.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
package/src/dom/remove.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
package/src/dom/scroll.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
package/src/dom/tounit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|
package/src/elementreplacer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
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
|
|