diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.d.ts new file mode 100644 index 00000000..ee6761c6 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.d.ts @@ -0,0 +1,11 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isnode + */ +/** + * Checks if the object is a native DOM Node. + */ +export default function isNode(obj: any): obj is Node; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js new file mode 100644 index 00000000..bf95d715 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js @@ -0,0 +1,21 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isnode + */ +/** + * Checks if the object is a native DOM Node. + */ +export default function isNode(obj) { + if (obj) { + if (obj.defaultView) { + return obj instanceof obj.defaultView.Document; + } + else if (obj.ownerDocument && obj.ownerDocument.defaultView) { + return obj instanceof obj.ownerDocument.defaultView.Node; + } + } + return false; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.d.ts new file mode 100644 index 00000000..d4d664fd --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.d.ts @@ -0,0 +1,11 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isrange + */ +/** + * Checks if the object is a native DOM Range. + */ +export default function isRange(obj: unknown): obj is Range; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js new file mode 100644 index 00000000..59dca442 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isrange + */ +/** + * Checks if the object is a native DOM Range. + */ +export default function isRange(obj) { + return Object.prototype.toString.apply(obj) == '[object Range]'; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.d.ts new file mode 100644 index 00000000..b05bd413 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.d.ts @@ -0,0 +1,11 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/istext + */ +/** + * Checks if the object is a native DOM Text node. + */ +export default function isText(obj: unknown): obj is Text; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js new file mode 100644 index 00000000..a528e6ad --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/istext + */ +/** + * Checks if the object is a native DOM Text node. + */ +export default function isText(obj) { + return Object.prototype.toString.call(obj) == '[object Text]'; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvalidattributename.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvalidattributename.d.ts new file mode 100644 index 00000000..64551f00 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvalidattributename.d.ts @@ -0,0 +1,10 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * Checks if the given attribute name is valid in terms of HTML. + * + * @param name Attribute name. + */ +export default function isValidAttributeName(name: string): boolean; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvalidattributename.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvalidattributename.js new file mode 100644 index 00000000..19f4e67d --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvalidattributename.js @@ -0,0 +1,22 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isvalidattributename + */ +import global from './global.js'; +/** + * Checks if the given attribute name is valid in terms of HTML. + * + * @param name Attribute name. + */ +export default function isValidAttributeName(name) { + try { + global.document.createAttribute(name); + } + catch (error) { + return false; + } + return true; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.d.ts new file mode 100644 index 00000000..a387536e --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.d.ts @@ -0,0 +1,18 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isvisible + */ +/** + * Checks whether the element is visible to the user in DOM: + * + * * connected to the root of the document, + * * has no `display: none`, + * * has no ancestors with `display: none`. + * + * **Note**: This helper does not check whether the element is hidden by cropping, overflow, etc.. + * To check that, use {@link module:utils/dom/rect~Rect} instead. + */ +export default function isVisible(element: HTMLElement | null | undefined): boolean; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js new file mode 100644 index 00000000..08c80b6c --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js @@ -0,0 +1,20 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/isvisible + */ +/** + * Checks whether the element is visible to the user in DOM: + * + * * connected to the root of the document, + * * has no `display: none`, + * * has no ancestors with `display: none`. + * + * **Note**: This helper does not check whether the element is hidden by cropping, overflow, etc.. + * To check that, use {@link module:utils/dom/rect~Rect} instead. + */ +export default function isVisible(element) { + return !!(element && element.getClientRects && element.getClientRects().length); +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.d.ts new file mode 100644 index 00000000..16a56eac --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.d.ts @@ -0,0 +1,11 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/iswindow + */ +/** + * Checks if the object is a native DOM Window. + */ +export default function isWindow(obj: unknown): obj is Window; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js new file mode 100644 index 00000000..ddaa2472 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js @@ -0,0 +1,22 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/iswindow + */ +/** + * Checks if the object is a native DOM Window. + */ +export default function isWindow(obj) { + const stringifiedObject = Object.prototype.toString.apply(obj); + // Returns `true` for the `window` object in browser environments. + if (stringifiedObject == '[object Window]') { + return true; + } + // Returns `true` for the `window` object in the Electron environment. + if (stringifiedObject == '[object global]') { + return true; + } + return false; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/position.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/position.d.ts new file mode 100644 index 00000000..a1d8ca40 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/position.d.ts @@ -0,0 +1,211 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +import Rect, { type RectSource } from '@ckeditor/ckeditor5-utils/src/dom/rect.js'; +/** + * Calculates the `position: absolute` coordinates of a given element so it can be positioned with respect to the + * target in the visually most efficient way, taking various restrictions like viewport or limiter geometry + * into consideration. + * + * **Note**: If there are no position coordinates found that meet the requirements (arguments of this helper), + * `null` is returned. + * + * ```ts + * // The element which is to be positioned. + * const element = document.body.querySelector( '#toolbar' ); + * + * // A target to which the element is positioned relatively. + * const target = document.body.querySelector( '#container' ); + * + * // Finding the optimal coordinates for the positioning. + * const { left, top, name } = getOptimalPosition( { + * element: element, + * target: target, + * + * // The algorithm will chose among these positions to meet the requirements such + * // as "limiter" element or "fitInViewport", set below. The positions are considered + * // in the order of the array. + * positions: [ + * // + * // [ Target ] + * // +-----------------+ + * // | Element | + * // +-----------------+ + * // + * targetRect => ( { + * top: targetRect.bottom, + * left: targetRect.left, + * name: 'mySouthEastPosition' + * } ), + * + * // + * // +-----------------+ + * // | Element | + * // +-----------------+ + * // [ Target ] + * // + * ( targetRect, elementRect ) => ( { + * top: targetRect.top - elementRect.height, + * left: targetRect.left, + * name: 'myNorthEastPosition' + * } ) + * ], + * + * // Find a position such guarantees the element remains within visible boundaries of . + * limiter: document.body, + * + * // Find a position such guarantees the element remains within visible boundaries of the browser viewport. + * fitInViewport: true + * } ); + * + * // The best position which fits into document.body and the viewport. May be useful + * // to set proper class on the `element`. + * console.log( name ); // -> "myNorthEastPosition" + * + * // Using the absolute coordinates which has been found to position the element + * // as in the diagram depicting the "myNorthEastPosition" position. + * element.style.top = top; + * element.style.left = left; + * ``` + * + * @param options The input data and configuration of the helper. + */ +export declare function getOptimalPosition({ element, target, positions, limiter, fitInViewport, viewportOffsetConfig }: Options): DomPoint | null; +/** + * A position object which instances are created and used by the {@link module:utils/dom/position~getOptimalPosition} helper. + * + * {@link module:utils/dom/position~DomPoint#top} and {@link module:utils/dom/position~DomPoint#left} properties of the position instance + * translate directly to the `top` and `left` properties in CSS "`position: absolute` coordinate system". If set on the positioned element + * in DOM, they will make it display it in the right place in the viewport. + */ +export interface DomPoint { + /** + * Position name. + */ + readonly name?: string; + /** + * Additional position configuration, as passed from the {@link module:utils/dom/position~PositioningFunction positioning function}. + * + * This object can be use, for instance, to pass through presentation options used by the consumer of the + * {@link module:utils/dom/position~getOptimalPosition} helper. + */ + readonly config?: object; + /** + * The left value in pixels in the CSS `position: absolute` coordinate system. + * Set it on the positioned element in DOM to move it to the position. + */ + readonly left: number; + /** + * The top value in pixels in the CSS `position: absolute` coordinate system. + * Set it on the positioned element in DOM to move it to the position. + */ + readonly top: number; +} +/** + * The `getOptimalPosition()` helper options. + */ +export interface Options { + /** + * Element that is to be positioned. + */ + readonly element: HTMLElement; + /** + * Target with respect to which the `element` is to be positioned. + */ + readonly target: RectSource | (() => RectSource); + /** + * An array of positioning functions. + * + * **Note**: Positioning functions are processed in the order of preference. The first function that works + * in the current environment (e.g. offers the complete fit in the viewport geometry) will be picked by + * `getOptimalPosition()`. + * + * **Note**: Any positioning function returning `null` is ignored. + */ + readonly positions: ReadonlyArray; + /** + * When set, the algorithm will chose position which fits the most in the + * limiter's bounding rect. + */ + readonly limiter?: RectSource | (() => (RectSource | null)) | null; + /** + * When set, the algorithm will chose such a position which fits `element` + * the most inside visible viewport. + */ + readonly fitInViewport?: boolean; + /** + * Viewport offset config object. It restricts the visible viewport available to the `getOptimalPosition()` from each side. + * + * ```ts + * { + * top: 50, + * right: 50, + * bottom: 50, + * left: 50 + * } + * ``` + */ + readonly viewportOffsetConfig?: { + readonly top?: number; + readonly right?: number; + readonly bottom?: number; + readonly left?: number; + }; +} +/** + * A positioning function which, based on positioned element and target {@link module:utils/dom/rect~Rect Rects}, returns rect coordinates + * representing the geometrical relation between them. Used by the {@link module:utils/dom/position~getOptimalPosition} helper. + * + * ```ts + * // This simple position will place the element directly under the target, in the middle: + * // + * // [ Target ] + * // +-----------------+ + * // | Element | + * // +-----------------+ + * // + * const position = ( targetRect, elementRect, [ viewportRect ] ) => ( { + * top: targetRect.bottom, + * left: targetRect.left + targetRect.width / 2 - elementRect.width / 2, + * name: 'bottomMiddle', + * + * // Note: The config is optional. + * config: { + * zIndex: '999' + * } + * } ); + * ``` + * + * @param elementRect The rect of the element to be positioned. + * @param targetRect The rect of the target the element (its rect) is relatively positioned to. + * @param viewportRect The rect of the visual browser viewport. + * @returns When the function returns `null`, it will not be considered by {@link module:utils/dom/position~getOptimalPosition}. + */ +export type PositioningFunction = (elementRect: Rect, targetRect: Rect, viewportRect: Rect, limiterRect?: Rect) => PositioningFunctionResult | null; +/** + * The result of {@link module:utils/dom/position~PositioningFunction}. + */ +export interface PositioningFunctionResult { + /** + * The `top` value of the element rect that would represent the position. + */ + top: number; + /** + * The `left` value of the element rect that would represent the position. + */ + left: number; + /** + * The name of the position. It helps the user of the {@link module:utils/dom/position~getOptimalPosition} + * helper to recognize different positioning function results. It will pass through to the {@link module:utils/dom/position~DomPoint} + * returned by the helper. + */ + name?: string; + /** + * An optional configuration that will pass-through the {@link module:utils/dom/position~getOptimalPosition} helper + * to the {@link module:utils/dom/position~DomPoint} returned by this helper. + * This configuration may, for instance, let the user of {@link module:utils/dom/position~getOptimalPosition} know that this particular + * position comes with a certain presentation. + */ + config?: object; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js new file mode 100644 index 00000000..3e4c6a14 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js @@ -0,0 +1,313 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/position + */ +import global from './global.js'; +import Rect from './rect.js'; +import getPositionedAncestor from './getpositionedancestor.js'; +import { isFunction } from 'lodash-es'; +// @if CK_DEBUG_POSITION // const { +// @if CK_DEBUG_POSITION // default: RectDrawer, +// @if CK_DEBUG_POSITION // diagonalStylesBlack, +// @if CK_DEBUG_POSITION // diagonalStylesGreen, +// @if CK_DEBUG_POSITION // diagonalStylesRed +// @if CK_DEBUG_POSITION // } = require( '@ckeditor/ckeditor5-utils/tests/_utils/rectdrawer' ); +// @if CK_DEBUG_POSITION // const TARGET_RECT_STYLE = { +// @if CK_DEBUG_POSITION // outlineWidth: '2px', outlineStyle: 'dashed', outlineColor: 'blue', outlineOffset: '2px' +// @if CK_DEBUG_POSITION // }; +// @if CK_DEBUG_POSITION // const VISIBLE_TARGET_RECT_STYLE = { +// @if CK_DEBUG_POSITION // ...diagonalStylesBlack, +// @if CK_DEBUG_POSITION // opacity: '1', +// @if CK_DEBUG_POSITION // backgroundColor: '#00000033', +// @if CK_DEBUG_POSITION // outlineWidth: '2px' +// @if CK_DEBUG_POSITION // }; +// @if CK_DEBUG_POSITION // const VIEWPORT_RECT_STYLE = { +// @if CK_DEBUG_POSITION // outlineWidth: '2px', +// @if CK_DEBUG_POSITION // outlineOffset: '-2px', +// @if CK_DEBUG_POSITION // outlineStyle: 'solid', +// @if CK_DEBUG_POSITION // outlineColor: 'red' +// @if CK_DEBUG_POSITION // }; +// @if CK_DEBUG_POSITION // const VISIBLE_LIMITER_RECT_STYLE = { +// @if CK_DEBUG_POSITION // ...diagonalStylesGreen, +// @if CK_DEBUG_POSITION // outlineWidth: '2px', +// @if CK_DEBUG_POSITION // outlineOffset: '-2px' +// @if CK_DEBUG_POSITION // }; +// @if CK_DEBUG_POSITION // const ELEMENT_RECT_STYLE = { +// @if CK_DEBUG_POSITION // outlineWidth: '2px', outlineColor: 'orange', outlineOffset: '-2px' +// @if CK_DEBUG_POSITION // }; +// @if CK_DEBUG_POSITION // const CHOSEN_POSITION_RECT_STYLE = { +// @if CK_DEBUG_POSITION // opacity: .5, outlineColor: 'magenta', backgroundColor: 'magenta' +// @if CK_DEBUG_POSITION // }; +/** + * Calculates the `position: absolute` coordinates of a given element so it can be positioned with respect to the + * target in the visually most efficient way, taking various restrictions like viewport or limiter geometry + * into consideration. + * + * **Note**: If there are no position coordinates found that meet the requirements (arguments of this helper), + * `null` is returned. + * + * ```ts + * // The element which is to be positioned. + * const element = document.body.querySelector( '#toolbar' ); + * + * // A target to which the element is positioned relatively. + * const target = document.body.querySelector( '#container' ); + * + * // Finding the optimal coordinates for the positioning. + * const { left, top, name } = getOptimalPosition( { + * element: element, + * target: target, + * + * // The algorithm will chose among these positions to meet the requirements such + * // as "limiter" element or "fitInViewport", set below. The positions are considered + * // in the order of the array. + * positions: [ + * // + * // [ Target ] + * // +-----------------+ + * // | Element | + * // +-----------------+ + * // + * targetRect => ( { + * top: targetRect.bottom, + * left: targetRect.left, + * name: 'mySouthEastPosition' + * } ), + * + * // + * // +-----------------+ + * // | Element | + * // +-----------------+ + * // [ Target ] + * // + * ( targetRect, elementRect ) => ( { + * top: targetRect.top - elementRect.height, + * left: targetRect.left, + * name: 'myNorthEastPosition' + * } ) + * ], + * + * // Find a position such guarantees the element remains within visible boundaries of . + * limiter: document.body, + * + * // Find a position such guarantees the element remains within visible boundaries of the browser viewport. + * fitInViewport: true + * } ); + * + * // The best position which fits into document.body and the viewport. May be useful + * // to set proper class on the `element`. + * console.log( name ); // -> "myNorthEastPosition" + * + * // Using the absolute coordinates which has been found to position the element + * // as in the diagram depicting the "myNorthEastPosition" position. + * element.style.top = top; + * element.style.left = left; + * ``` + * + * @param options The input data and configuration of the helper. + */ +export function getOptimalPosition({ element, target, positions, limiter, fitInViewport, viewportOffsetConfig }) { + // If the {@link module:utils/dom/position~Options#target} is a function, use what it returns. + // https://github.com/ckeditor/ckeditor5-utils/issues/157 + if (isFunction(target)) { + target = target(); + } + // If the {@link module:utils/dom/position~Options#limiter} is a function, use what it returns. + // https://github.com/ckeditor/ckeditor5-ui/issues/260 + if (isFunction(limiter)) { + limiter = limiter(); + } + const positionedElementAncestor = getPositionedAncestor(element); + const constrainedViewportRect = getConstrainedViewportRect(viewportOffsetConfig); + const elementRect = new Rect(element); + const visibleTargetRect = getVisibleViewportIntersectionRect(target, constrainedViewportRect); + let bestPosition; + // @if CK_DEBUG_POSITION // const targetRect = new Rect( target ); + // @if CK_DEBUG_POSITION // RectDrawer.clear(); + // @if CK_DEBUG_POSITION // RectDrawer.draw( targetRect, TARGET_RECT_STYLE, 'Target' ); + // @if CK_DEBUG_POSITION // if ( constrainedViewportRect ) { + // @if CK_DEBUG_POSITION // RectDrawer.draw( constrainedViewportRect, VIEWPORT_RECT_STYLE, 'Viewport' ); + // @if CK_DEBUG_POSITION // } + // If the target got cropped by ancestors or went off the screen, positioning does not make any sense. + if (!visibleTargetRect || !constrainedViewportRect.getIntersection(visibleTargetRect)) { + return null; + } + // @if CK_DEBUG_POSITION // RectDrawer.draw( visibleTargetRect, VISIBLE_TARGET_RECT_STYLE, 'VisTgt' ); + const positionOptions = { + targetRect: visibleTargetRect, + elementRect, + positionedElementAncestor, + viewportRect: constrainedViewportRect + }; + // If there are no limits, just grab the very first position and be done with that drama. + if (!limiter && !fitInViewport) { + bestPosition = new PositionObject(positions[0], positionOptions); + } + else { + if (limiter) { + const visibleLimiterRect = getVisibleViewportIntersectionRect(limiter, constrainedViewportRect); + if (visibleLimiterRect) { + positionOptions.limiterRect = visibleLimiterRect; + // @if CK_DEBUG_POSITION // RectDrawer.draw( visibleLimiterRect, VISIBLE_LIMITER_RECT_STYLE, 'VisLim' ); + } + } + // If there's no best position found, i.e. when all intersections have no area because + // rects have no width or height, then just return `null` + bestPosition = getBestPosition(positions, positionOptions); + } + return bestPosition; +} +/** + * Returns intersection of visible source `Rect` with Viewport `Rect`. In case when source `Rect` is not visible + * or there is no intersection between source `Rect` and Viewport `Rect`, `null` will be returned. + */ +function getVisibleViewportIntersectionRect(source, viewportRect) { + const visibleSourceRect = new Rect(source).getVisible(); + if (!visibleSourceRect) { + return null; + } + return visibleSourceRect.getIntersection(viewportRect); +} +/** + * Returns a viewport `Rect` shrunk by the viewport offset config from all sides. + */ +function getConstrainedViewportRect(viewportOffsetConfig) { + viewportOffsetConfig = Object.assign({ top: 0, bottom: 0, left: 0, right: 0 }, viewportOffsetConfig); + const viewportRect = new Rect(global.window); + viewportRect.top += viewportOffsetConfig.top; + viewportRect.height -= viewportOffsetConfig.top; + viewportRect.bottom -= viewportOffsetConfig.bottom; + viewportRect.height -= viewportOffsetConfig.bottom; + return viewportRect; +} +/** + * For a given array of positioning functions, returns such that provides the best + * fit of the `elementRect` into the `limiterRect` and `viewportRect`. + */ +function getBestPosition(positions, options) { + const { elementRect } = options; + // This is when element is fully visible. + const elementRectArea = elementRect.getArea(); + const positionInstances = positions + .map(positioningFunction => new PositionObject(positioningFunction, options)) + // Some positioning functions may return `null` if they don't want to participate. + .filter(position => !!position.name); + let maxFitFactor = 0; + let bestPosition = null; + for (const position of positionInstances) { + const { limiterIntersectionArea, viewportIntersectionArea } = position; + // If a such position is found that element is fully contained by the limiter then, obviously, + // there will be no better one, so finishing. + if (limiterIntersectionArea === elementRectArea) { + // @if CK_DEBUG_POSITION // RectDrawer.draw( position._rect, CHOSEN_POSITION_RECT_STYLE, [ + // @if CK_DEBUG_POSITION // position.name, + // @if CK_DEBUG_POSITION // '100% fit', + // @if CK_DEBUG_POSITION // ].join( '\n' ) ); + return position; + } + // To maximize both viewport and limiter intersection areas we use distance on _viewportIntersectionArea + // and _limiterIntersectionArea plane (without sqrt because we are looking for max value). + const fitFactor = viewportIntersectionArea ** 2 + limiterIntersectionArea ** 2; + // @if CK_DEBUG_POSITION // RectDrawer.draw( position._rect, { opacity: .4 }, [ + // @if CK_DEBUG_POSITION // position.name, + // @if CK_DEBUG_POSITION // 'Vi=' + Math.round( viewportIntersectionArea ), + // @if CK_DEBUG_POSITION // 'Li=' + Math.round( limiterIntersectionArea ) + // @if CK_DEBUG_POSITION // ].join( '\n' ) ); + if (fitFactor > maxFitFactor) { + maxFitFactor = fitFactor; + bestPosition = position; + } + } + // @if CK_DEBUG_POSITION // if ( bestPosition ) { + // @if CK_DEBUG_POSITION // RectDrawer.draw( bestPosition._rect, CHOSEN_POSITION_RECT_STYLE ); + // @if CK_DEBUG_POSITION // } + return bestPosition; +} +/** + * A position class which instances are created and used by the {@link module:utils/dom/position~getOptimalPosition} helper. + * + * {@link module:utils/dom/position~Position#top} and {@link module:utils/dom/position~Position#left} properties of the position instance + * translate directly to the `top` and `left` properties in CSS "`position: absolute` coordinate system". If set on the positioned element + * in DOM, they will make it display it in the right place in the viewport. + */ +class PositionObject { + /** + * Creates an instance of the {@link module:utils/dom/position~PositionObject} class. + * + * @param positioningFunction function The function that defines the expected + * coordinates the positioned element should move to. + * @param options options object. + * @param options.elementRect The positioned element rect. + * @param options.targetRect The target element rect. + * @param options.viewportRect The viewport rect. + * @param options.limiterRect The limiter rect. + * @param options.positionedElementAncestor Nearest element ancestor element which CSS position is not "static". + */ + constructor(positioningFunction, options) { + const positioningFunctionOutput = positioningFunction(options.targetRect, options.elementRect, options.viewportRect, options.limiterRect); + // Nameless position for a function that didn't participate. + if (!positioningFunctionOutput) { + return; + } + const { left, top, name, config } = positioningFunctionOutput; + this.name = name; + this.config = config; + this._positioningFunctionCoordinates = { left, top }; + this._options = options; + } + /** + * The left value in pixels in the CSS `position: absolute` coordinate system. + * Set it on the positioned element in DOM to move it to the position. + */ + get left() { + return this._absoluteRect.left; + } + /** + * The top value in pixels in the CSS `position: absolute` coordinate system. + * Set it on the positioned element in DOM to move it to the position. + */ + get top() { + return this._absoluteRect.top; + } + /** + * An intersection area between positioned element and limiter within viewport constraints. + */ + get limiterIntersectionArea() { + const limiterRect = this._options.limiterRect; + if (limiterRect) { + return limiterRect.getIntersectionArea(this._rect); + } + return 0; + } + /** + * An intersection area between positioned element and viewport. + */ + get viewportIntersectionArea() { + const viewportRect = this._options.viewportRect; + return viewportRect.getIntersectionArea(this._rect); + } + /** + * An already positioned element rect. A clone of the element rect passed to the constructor + * but placed in the viewport according to the positioning function. + */ + get _rect() { + if (this._cachedRect) { + return this._cachedRect; + } + this._cachedRect = this._options.elementRect.clone().moveTo(this._positioningFunctionCoordinates.left, this._positioningFunctionCoordinates.top); + return this._cachedRect; + } + /** + * An already absolutely positioned element rect. See ({@link #_rect}). + */ + get _absoluteRect() { + if (this._cachedAbsoluteRect) { + return this._cachedAbsoluteRect; + } + this._cachedAbsoluteRect = this._rect.toAbsoluteRect(); + return this._cachedAbsoluteRect; + } +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.d.ts new file mode 100644 index 00000000..6ab21a7d --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.d.ts @@ -0,0 +1,195 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * A helper class representing a `ClientRect` object, e.g. value returned by + * the native `object.getBoundingClientRect()` method. Provides a set of methods + * to manipulate the rect and compare it against other rect instances. + */ +export default class Rect { + /** + * The "top" value of the rect. + * + * @readonly + */ + top: number; + /** + * The "right" value of the rect. + * + * @readonly + */ + right: number; + /** + * The "bottom" value of the rect. + * + * @readonly + */ + bottom: number; + /** + * The "left" value of the rect. + * + * @readonly + */ + left: number; + /** + * The "width" value of the rect. + * + * @readonly + */ + width: number; + /** + * The "height" value of the rect. + * + * @readonly + */ + height: number; + /** + * The object this rect is for. + * + * @readonly + */ + private _source; + /** + * Creates an instance of rect. + * + * ```ts + * // Rect of an HTMLElement. + * const rectA = new Rect( document.body ); + * + * // Rect of a DOM Range. + * const rectB = new Rect( document.getSelection().getRangeAt( 0 ) ); + * + * // Rect of a window (web browser viewport). + * const rectC = new Rect( window ); + * + * // Rect out of an object. + * const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } ); + * + * // Rect out of another Rect instance. + * const rectE = new Rect( rectD ); + * + * // Rect out of a ClientRect. + * const rectF = new Rect( document.body.getClientRects().item( 0 ) ); + * ``` + * + * **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any) + * ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders} + * to get the inner part of the rect. + * + * @param source A source object to create the rect. + */ + constructor(source: RectSource); + /** + * Returns a clone of the rect. + * + * @returns A cloned rect. + */ + clone(): Rect; + /** + * Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location. + * + * @param x Desired horizontal location. + * @param y Desired vertical location. + * @returns A rect which has been moved. + */ + moveTo(x: number, y: number): this; + /** + * Moves the rect in–place by a dedicated offset. + * + * @param x A horizontal offset. + * @param y A vertical offset + * @returns A rect which has been moved. + */ + moveBy(x: number, y: number): this; + /** + * Returns a new rect a a result of intersection with another rect. + */ + getIntersection(anotherRect: Rect): Rect | null; + /** + * Returns the area of intersection with another rect. + * + * @returns Area of intersection. + */ + getIntersectionArea(anotherRect: Rect): number; + /** + * Returns the area of the rect. + */ + getArea(): number; + /** + * Returns a new rect, a part of the original rect, which is actually visible to the user and is relative to the,`body`, + * e.g. an original rect cropped by parent element rects which have `overflow` set in CSS + * other than `"visible"`. + * + * If there's no such visible rect, which is when the rect is limited by one or many of + * the ancestors, `null` is returned. + * + * **Note**: This method does not consider the boundaries of the viewport (window). + * To get a rect cropped by all ancestors and the viewport, use an intersection such as: + * + * ```ts + * const visibleInViewportRect = new Rect( window ).getIntersection( new Rect( source ).getVisible() ); + * ``` + * + * @returns A visible rect instance or `null`, if there's none. + */ + getVisible(): Rect | null; + /** + * Checks if all property values ({@link #top}, {@link #left}, {@link #right}, + * {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect + * instances. + * + * @param anotherRect A rect instance to compare with. + * @returns `true` when Rects are equal. `false` otherwise. + */ + isEqual(anotherRect: Rect): boolean; + /** + * Checks whether a rect fully contains another rect instance. + * + * @param anotherRect + * @returns `true` if contains, `false` otherwise. + */ + contains(anotherRect: Rect): boolean; + /** + * Recalculates screen coordinates to coordinates relative to the positioned ancestor offset. + */ + toAbsoluteRect(): Rect; + /** + * Excludes scrollbars and CSS borders from the rect. + * + * * Borders are removed when {@link #_source} is an HTML element. + * * Scrollbars are excluded from HTML elements and the `window`. + * + * @returns A rect which has been updated. + */ + excludeScrollbarsAndBorders(): this; + /** + * Returns an array of rects of the given native DOM Range. + * + * @param range A native DOM range. + * @returns DOM Range rects. + */ + static getDomRangeRects(range: Range): Array; + /** + * Returns a bounding rectangle that contains all the given `rects`. + * + * @param rects A list of rectangles that should be contained in the result rectangle. + * @returns Bounding rectangle or `null` if no `rects` were given. + */ + static getBoundingRect(rects: Iterable): Rect | null; +} +/** + * A source of {@link module:utils/dom/rect~Rect}. + */ +export type RectSource = HTMLElement | Range | Window | RectLike; +/** + * An object that describes properties of `ClientRect` object. + */ +export interface RectLike { + readonly top: number; + readonly right: number; + readonly bottom: number; + readonly left: number; + readonly width: number; + readonly height: number; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js new file mode 100644 index 00000000..76a6001c --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js @@ -0,0 +1,474 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/rect + */ +import isRange from './isrange.js'; +import isWindow from './iswindow.js'; +import getBorderWidths from './getborderwidths.js'; +import isText from './istext.js'; +import getPositionedAncestor from './getpositionedancestor.js'; +import global from './global.js'; +const rectProperties = ['top', 'right', 'bottom', 'left', 'width', 'height']; +/** + * A helper class representing a `ClientRect` object, e.g. value returned by + * the native `object.getBoundingClientRect()` method. Provides a set of methods + * to manipulate the rect and compare it against other rect instances. + */ +export default class Rect { + /** + * Creates an instance of rect. + * + * ```ts + * // Rect of an HTMLElement. + * const rectA = new Rect( document.body ); + * + * // Rect of a DOM Range. + * const rectB = new Rect( document.getSelection().getRangeAt( 0 ) ); + * + * // Rect of a window (web browser viewport). + * const rectC = new Rect( window ); + * + * // Rect out of an object. + * const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } ); + * + * // Rect out of another Rect instance. + * const rectE = new Rect( rectD ); + * + * // Rect out of a ClientRect. + * const rectF = new Rect( document.body.getClientRects().item( 0 ) ); + * ``` + * + * **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any) + * ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders} + * to get the inner part of the rect. + * + * @param source A source object to create the rect. + */ + constructor(source) { + const isSourceRange = isRange(source); + Object.defineProperty(this, '_source', { + // If the source is a Rect instance, copy it's #_source. + value: source._source || source, + writable: true, + enumerable: false + }); + if (isDomElement(source) || isSourceRange) { + // The `Rect` class depends on `getBoundingClientRect` and `getClientRects` DOM methods. If the source + // of a rect in an HTML element or a DOM range but it does not belong to any rendered DOM tree, these methods + // will fail to obtain the geometry and the rect instance makes little sense to the features using it. + // To get rid of this warning make sure the source passed to the constructor is a descendant of `window.document.body`. + // @if CK_DEBUG // const sourceNode = isSourceRange ? source.startContainer : source; + // @if CK_DEBUG // if ( !sourceNode.ownerDocument || !sourceNode.ownerDocument.body.contains( sourceNode ) ) { + // @if CK_DEBUG // console.warn( + // @if CK_DEBUG // 'rect-source-not-in-dom: The source of this rect does not belong to any rendered DOM tree.', + // @if CK_DEBUG // { source } ); + // @if CK_DEBUG // } + if (isSourceRange) { + const rangeRects = Rect.getDomRangeRects(source); + copyRectProperties(this, Rect.getBoundingRect(rangeRects)); + } + else { + copyRectProperties(this, source.getBoundingClientRect()); + } + } + else if (isWindow(source)) { + const { innerWidth, innerHeight } = source; + copyRectProperties(this, { + top: 0, + right: innerWidth, + bottom: innerHeight, + left: 0, + width: innerWidth, + height: innerHeight + }); + } + else { + copyRectProperties(this, source); + } + } + /** + * Returns a clone of the rect. + * + * @returns A cloned rect. + */ + clone() { + return new Rect(this); + } + /** + * Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location. + * + * @param x Desired horizontal location. + * @param y Desired vertical location. + * @returns A rect which has been moved. + */ + moveTo(x, y) { + this.top = y; + this.right = x + this.width; + this.bottom = y + this.height; + this.left = x; + return this; + } + /** + * Moves the rect in–place by a dedicated offset. + * + * @param x A horizontal offset. + * @param y A vertical offset + * @returns A rect which has been moved. + */ + moveBy(x, y) { + this.top += y; + this.right += x; + this.left += x; + this.bottom += y; + return this; + } + /** + * Returns a new rect a a result of intersection with another rect. + */ + getIntersection(anotherRect) { + const rect = { + top: Math.max(this.top, anotherRect.top), + right: Math.min(this.right, anotherRect.right), + bottom: Math.min(this.bottom, anotherRect.bottom), + left: Math.max(this.left, anotherRect.left), + width: 0, + height: 0 + }; + rect.width = rect.right - rect.left; + rect.height = rect.bottom - rect.top; + if (rect.width < 0 || rect.height < 0) { + return null; + } + else { + const newRect = new Rect(rect); + newRect._source = this._source; + return newRect; + } + } + /** + * Returns the area of intersection with another rect. + * + * @returns Area of intersection. + */ + getIntersectionArea(anotherRect) { + const rect = this.getIntersection(anotherRect); + if (rect) { + return rect.getArea(); + } + else { + return 0; + } + } + /** + * Returns the area of the rect. + */ + getArea() { + return this.width * this.height; + } + /** + * Returns a new rect, a part of the original rect, which is actually visible to the user and is relative to the,`body`, + * e.g. an original rect cropped by parent element rects which have `overflow` set in CSS + * other than `"visible"`. + * + * If there's no such visible rect, which is when the rect is limited by one or many of + * the ancestors, `null` is returned. + * + * **Note**: This method does not consider the boundaries of the viewport (window). + * To get a rect cropped by all ancestors and the viewport, use an intersection such as: + * + * ```ts + * const visibleInViewportRect = new Rect( window ).getIntersection( new Rect( source ).getVisible() ); + * ``` + * + * @returns A visible rect instance or `null`, if there's none. + */ + getVisible() { + const source = this._source; + let visibleRect = this.clone(); + // There's no ancestor to crop with the overflow. + if (isBody(source)) { + return visibleRect; + } + let child = source; + let parent = source.parentNode || source.commonAncestorContainer; + let absolutelyPositionedChildElement; + // Check the ancestors all the way up to the . + while (parent && !isBody(parent)) { + const isParentOverflowVisible = getElementOverflow(parent) === 'visible'; + if (child instanceof HTMLElement && getElementPosition(child) === 'absolute') { + absolutelyPositionedChildElement = child; + } + const parentElementPosition = getElementPosition(parent); + // The child will be cropped only if it has `position: absolute` and the parent has `position: relative` + some overflow. + // Otherwise there's no chance of visual clipping and the parent can be skipped + // https://github.com/ckeditor/ckeditor5/issues/14107. + // + // condition: isParentOverflowVisible + // +---------------------------+ + // | #parent | + // | (overflow: visible) | + // | +-----------+---------------+ + // | | child | + // | +-----------+---------------+ + // +---------------------------+ + // + // condition: absolutelyPositionedChildElement && parentElementPosition === 'relative' && isParentOverflowVisible + // +---------------------------+ + // | parent | + // | (position: relative;) | + // | (overflow: visible;) | + // | +-----------+---------------+ + // | | child | + // | | (position: absolute;) | + // | +-----------+---------------+ + // +---------------------------+ + // + // condition: absolutelyPositionedChildElement && parentElementPosition !== 'relative' + // +---------------------------+ + // | parent | + // | (position: static;) | + // | +-----------+---------------+ + // | | child | + // | | (position: absolute;) | + // | +-----------+---------------+ + // +---------------------------+ + if (isParentOverflowVisible || + absolutelyPositionedChildElement && ((parentElementPosition === 'relative' && isParentOverflowVisible) || + parentElementPosition !== 'relative')) { + child = parent; + parent = parent.parentNode; + continue; + } + const parentRect = new Rect(parent); + const intersectionRect = visibleRect.getIntersection(parentRect); + if (intersectionRect) { + if (intersectionRect.getArea() < visibleRect.getArea()) { + // Reduce the visible rect to the intersection. + visibleRect = intersectionRect; + } + } + else { + // There's no intersection, the rect is completely invisible. + return null; + } + child = parent; + parent = parent.parentNode; + } + return visibleRect; + } + /** + * Checks if all property values ({@link #top}, {@link #left}, {@link #right}, + * {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect + * instances. + * + * @param anotherRect A rect instance to compare with. + * @returns `true` when Rects are equal. `false` otherwise. + */ + isEqual(anotherRect) { + for (const prop of rectProperties) { + if (this[prop] !== anotherRect[prop]) { + return false; + } + } + return true; + } + /** + * Checks whether a rect fully contains another rect instance. + * + * @param anotherRect + * @returns `true` if contains, `false` otherwise. + */ + contains(anotherRect) { + const intersectRect = this.getIntersection(anotherRect); + return !!(intersectRect && intersectRect.isEqual(anotherRect)); + } + /** + * Recalculates screen coordinates to coordinates relative to the positioned ancestor offset. + */ + toAbsoluteRect() { + const { scrollX, scrollY } = global.window; + const absoluteRect = this.clone().moveBy(scrollX, scrollY); + if (isDomElement(absoluteRect._source)) { + const positionedAncestor = getPositionedAncestor(absoluteRect._source); + if (positionedAncestor) { + shiftRectToCompensatePositionedAncestor(absoluteRect, positionedAncestor); + } + } + return absoluteRect; + } + /** + * Excludes scrollbars and CSS borders from the rect. + * + * * Borders are removed when {@link #_source} is an HTML element. + * * Scrollbars are excluded from HTML elements and the `window`. + * + * @returns A rect which has been updated. + */ + excludeScrollbarsAndBorders() { + const source = this._source; + let scrollBarWidth, scrollBarHeight, direction; + if (isWindow(source)) { + scrollBarWidth = source.innerWidth - source.document.documentElement.clientWidth; + scrollBarHeight = source.innerHeight - source.document.documentElement.clientHeight; + direction = source.getComputedStyle(source.document.documentElement).direction; + } + else { + const borderWidths = getBorderWidths(source); + scrollBarWidth = source.offsetWidth - source.clientWidth - borderWidths.left - borderWidths.right; + scrollBarHeight = source.offsetHeight - source.clientHeight - borderWidths.top - borderWidths.bottom; + direction = source.ownerDocument.defaultView.getComputedStyle(source).direction; + this.left += borderWidths.left; + this.top += borderWidths.top; + this.right -= borderWidths.right; + this.bottom -= borderWidths.bottom; + this.width = this.right - this.left; + this.height = this.bottom - this.top; + } + this.width -= scrollBarWidth; + if (direction === 'ltr') { + this.right -= scrollBarWidth; + } + else { + this.left += scrollBarWidth; + } + this.height -= scrollBarHeight; + this.bottom -= scrollBarHeight; + return this; + } + /** + * Returns an array of rects of the given native DOM Range. + * + * @param range A native DOM range. + * @returns DOM Range rects. + */ + static getDomRangeRects(range) { + const rects = []; + // Safari does not iterate over ClientRectList using for...of loop. + const clientRects = Array.from(range.getClientRects()); + if (clientRects.length) { + for (const rect of clientRects) { + rects.push(new Rect(rect)); + } + } + // If there's no client rects for the Range, use parent container's bounding rect + // instead and adjust rect's width to simulate the actual geometry of such range. + // https://github.com/ckeditor/ckeditor5-utils/issues/153 + // https://github.com/ckeditor/ckeditor5-ui/issues/317 + else { + let startContainer = range.startContainer; + if (isText(startContainer)) { + startContainer = startContainer.parentNode; + } + const rect = new Rect(startContainer.getBoundingClientRect()); + rect.right = rect.left; + rect.width = 0; + rects.push(rect); + } + return rects; + } + /** + * Returns a bounding rectangle that contains all the given `rects`. + * + * @param rects A list of rectangles that should be contained in the result rectangle. + * @returns Bounding rectangle or `null` if no `rects` were given. + */ + static getBoundingRect(rects) { + const boundingRectData = { + left: Number.POSITIVE_INFINITY, + top: Number.POSITIVE_INFINITY, + right: Number.NEGATIVE_INFINITY, + bottom: Number.NEGATIVE_INFINITY, + width: 0, + height: 0 + }; + let rectangleCount = 0; + for (const rect of rects) { + rectangleCount++; + boundingRectData.left = Math.min(boundingRectData.left, rect.left); + boundingRectData.top = Math.min(boundingRectData.top, rect.top); + boundingRectData.right = Math.max(boundingRectData.right, rect.right); + boundingRectData.bottom = Math.max(boundingRectData.bottom, rect.bottom); + } + if (rectangleCount == 0) { + return null; + } + boundingRectData.width = boundingRectData.right - boundingRectData.left; + boundingRectData.height = boundingRectData.bottom - boundingRectData.top; + return new Rect(boundingRectData); + } +} +/** + * Acquires all the rect properties from the passed source. + */ +function copyRectProperties(rect, source) { + for (const p of rectProperties) { + rect[p] = source[p]; + } +} +/** + * Checks if provided object is a HTML element. + */ +function isBody(value) { + if (!isDomElement(value)) { + return false; + } + return value === value.ownerDocument.body; +} +/** + * Checks if provided object "looks like" a DOM Element and has API required by `Rect` class. + */ +function isDomElement(value) { + // Note: earlier we used `isElement()` from lodash library, however that function is less performant because + // it makes complicated checks to make sure that given value is a DOM element. + return value !== null && typeof value === 'object' && value.nodeType === 1 && typeof value.getBoundingClientRect === 'function'; +} +/** + * Returns the value of the `position` style of an `HTMLElement`. + */ +function getElementPosition(element) { + return element instanceof HTMLElement ? element.ownerDocument.defaultView.getComputedStyle(element).position : 'static'; +} +/** + * Returns the value of the `overflow` style of an `HTMLElement` or a `Range`. + */ +function getElementOverflow(element) { + return element instanceof HTMLElement ? element.ownerDocument.defaultView.getComputedStyle(element).overflow : 'visible'; +} +/** + * For a given absolute Rect coordinates object and a positioned element ancestor, it updates its + * coordinates that make up for the position and the scroll of the ancestor. + * + * This is necessary because while Rects (and DOMRects) are relative to the browser's viewport, their coordinates + * are used in real–life to position elements with `position: absolute`, which are scoped by any positioned + * (and scrollable) ancestors. + */ +function shiftRectToCompensatePositionedAncestor(rect, positionedElementAncestor) { + const ancestorPosition = new Rect(positionedElementAncestor); + const ancestorBorderWidths = getBorderWidths(positionedElementAncestor); + let moveX = 0; + let moveY = 0; + // (https://github.com/ckeditor/ckeditor5-ui-default/issues/126) + // If there's some positioned ancestor of the panel, then its `Rect` must be taken into + // consideration. `Rect` is always relative to the viewport while `position: absolute` works + // with respect to that positioned ancestor. + moveX -= ancestorPosition.left; + moveY -= ancestorPosition.top; + // (https://github.com/ckeditor/ckeditor5-utils/issues/139) + // If there's some positioned ancestor of the panel, not only its position must be taken into + // consideration (see above) but also its internal scrolls. Scroll have an impact here because `Rect` + // is relative to the viewport (it doesn't care about scrolling), while `position: absolute` + // must compensate that scrolling. + moveX += positionedElementAncestor.scrollLeft; + moveY += positionedElementAncestor.scrollTop; + // (https://github.com/ckeditor/ckeditor5-utils/issues/139) + // If there's some positioned ancestor of the panel, then its `Rect` includes its CSS `borderWidth` + // while `position: absolute` positioning does not consider it. + // E.g. `{ position: absolute, top: 0, left: 0 }` means upper left corner of the element, + // not upper-left corner of its border. + moveX -= ancestorBorderWidths.left; + moveY -= ancestorBorderWidths.top; + rect.moveBy(moveX, moveY); +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.d.ts new file mode 100644 index 00000000..65e7a44a --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.d.ts @@ -0,0 +1,13 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/remove + */ +/** + * Removes given node from parent. + * + * @param node Node to remove. + */ +export default function remove(node: Node): void; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.js new file mode 100644 index 00000000..7942fdca --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.js @@ -0,0 +1,18 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/remove + */ +/** + * Removes given node from parent. + * + * @param node Node to remove. + */ +export default function remove(node) { + const parent = node.parentNode; + if (parent) { + parent.removeChild(node); + } +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.d.ts new file mode 100644 index 00000000..483d1aab --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.d.ts @@ -0,0 +1,74 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * A helper class which instances allow performing custom actions when native DOM elements are resized. + * + * ```ts + * const editableElement = editor.editing.view.getDomRoot(); + * + * const observer = new ResizeObserver( editableElement, entry => { + * console.log( 'The editable element has been resized in DOM.' ); + * console.log( entry.target ); // -> editableElement + * console.log( entry.contentRect.width ); // -> e.g. '423px' + * } ); + * ``` + * + * It uses the [native DOM resize observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) + * under the hood. + */ +export default class ResizeObserver { + /** + * The element observed by this observer. + */ + private readonly _element; + /** + * The callback executed each time {@link #_element} is resized. + */ + private readonly _callback; + /** + * The single native observer instance shared across all {@link module:utils/dom/resizeobserver~ResizeObserver} instances. + */ + private static _observerInstance; + /** + * A mapping of native DOM elements and their callbacks shared across all + * {@link module:utils/dom/resizeobserver~ResizeObserver} instances. + */ + private static _elementCallbacks; + /** + * Creates an instance of the `ResizeObserver` class. + * + * @param element A DOM element that is to be observed for resizing. Note that + * the element must be visible (i.e. not detached from DOM) for the observer to work. + * @param callback A function called when the observed element was resized. It passes + * the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) + * object with information about the resize event. + */ + constructor(element: Element, callback: (entry: ResizeObserverEntry) => void); + /** + * The element observed by this observer. + */ + get element(): Element; + /** + * Destroys the observer which disables the `callback` passed to the {@link #constructor}. + */ + destroy(): void; + /** + * Registers a new resize callback for the DOM element. + */ + private static _addElementCallback; + /** + * Removes a resize callback from the DOM element. If no callbacks are left + * for the element, it removes the element from the native observer. + */ + private static _deleteElementCallback; + /** + * Returns are registered resize callbacks for the DOM element. + */ + private static _getElementCallbacks; + /** + * Creates the single native observer shared across all `ResizeObserver` instances. + */ + private static _createObserver; +} diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js new file mode 100644 index 00000000..eb5610b9 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js @@ -0,0 +1,127 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/resizeobserver + */ +import global from './global.js'; +/** + * A helper class which instances allow performing custom actions when native DOM elements are resized. + * + * ```ts + * const editableElement = editor.editing.view.getDomRoot(); + * + * const observer = new ResizeObserver( editableElement, entry => { + * console.log( 'The editable element has been resized in DOM.' ); + * console.log( entry.target ); // -> editableElement + * console.log( entry.contentRect.width ); // -> e.g. '423px' + * } ); + * ``` + * + * It uses the [native DOM resize observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) + * under the hood. + */ +class ResizeObserver { + /** + * Creates an instance of the `ResizeObserver` class. + * + * @param element A DOM element that is to be observed for resizing. Note that + * the element must be visible (i.e. not detached from DOM) for the observer to work. + * @param callback A function called when the observed element was resized. It passes + * the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) + * object with information about the resize event. + */ + constructor(element, callback) { + // **Note**: For the maximum performance, this class ensures only a single instance of the native + // observer is used no matter how many instances of this class were created. + if (!ResizeObserver._observerInstance) { + ResizeObserver._createObserver(); + } + this._element = element; + this._callback = callback; + ResizeObserver._addElementCallback(element, callback); + ResizeObserver._observerInstance.observe(element); + } + /** + * The element observed by this observer. + */ + get element() { + return this._element; + } + /** + * Destroys the observer which disables the `callback` passed to the {@link #constructor}. + */ + destroy() { + ResizeObserver._deleteElementCallback(this._element, this._callback); + } + /** + * Registers a new resize callback for the DOM element. + */ + static _addElementCallback(element, callback) { + if (!ResizeObserver._elementCallbacks) { + ResizeObserver._elementCallbacks = new Map(); + } + let callbacks = ResizeObserver._elementCallbacks.get(element); + if (!callbacks) { + callbacks = new Set(); + ResizeObserver._elementCallbacks.set(element, callbacks); + } + callbacks.add(callback); + } + /** + * Removes a resize callback from the DOM element. If no callbacks are left + * for the element, it removes the element from the native observer. + */ + static _deleteElementCallback(element, callback) { + const callbacks = ResizeObserver._getElementCallbacks(element); + // Remove the element callback. Check if exist first in case someone + // called destroy() twice. + if (callbacks) { + callbacks.delete(callback); + // If no callbacks left for the element, also remove the element. + if (!callbacks.size) { + ResizeObserver._elementCallbacks.delete(element); + ResizeObserver._observerInstance.unobserve(element); + } + } + if (ResizeObserver._elementCallbacks && !ResizeObserver._elementCallbacks.size) { + ResizeObserver._observerInstance = null; + ResizeObserver._elementCallbacks = null; + } + } + /** + * Returns are registered resize callbacks for the DOM element. + */ + static _getElementCallbacks(element) { + if (!ResizeObserver._elementCallbacks) { + return null; + } + return ResizeObserver._elementCallbacks.get(element); + } + /** + * Creates the single native observer shared across all `ResizeObserver` instances. + */ + static _createObserver() { + ResizeObserver._observerInstance = new global.window.ResizeObserver(entries => { + for (const entry of entries) { + const callbacks = ResizeObserver._getElementCallbacks(entry.target); + if (callbacks) { + for (const callback of callbacks) { + callback(entry); + } + } + } + }); + } +} +/** + * The single native observer instance shared across all {@link module:utils/dom/resizeobserver~ResizeObserver} instances. + */ +ResizeObserver._observerInstance = null; +/** + * A mapping of native DOM elements and their callbacks shared across all + * {@link module:utils/dom/resizeobserver~ResizeObserver} instances. + */ +ResizeObserver._elementCallbacks = null; +export default ResizeObserver; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.d.ts b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.d.ts new file mode 100644 index 00000000..c2a62898 --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.d.ts @@ -0,0 +1,73 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +type IfTrue = T extends true ? true : never; +/** + * Makes any page `HTMLElement` or `Range` (`target`) visible inside the browser viewport. + * This helper will scroll all `target` ancestors and the web browser viewport to reveal the target to + * the user. If the `target` is already visible, nothing will happen. + * + * @param options Additional configuration of the scrolling behavior. + * @param options.target A target, which supposed to become visible to the user. + * @param options.viewportOffset An offset from the edge of the viewport (in pixels) + * the `target` will be moved by if the viewport is scrolled. It enhances the user experience + * by keeping the `target` some distance from the edge of the viewport and thus making it easier to + * read or edit by the user. + * @param options.ancestorOffset An offset from the boundary of scrollable ancestors (if any) + * the `target` will be moved by if the viewport is scrolled. It enhances the user experience + * by keeping the `target` some distance from the edge of the ancestors and thus making it easier to + * read or edit by the user. + * @param options.alignToTop When set `true`, the helper will make sure the `target` is scrolled up + * to the top boundary of the viewport and/or scrollable ancestors if scrolled up. When not set + * (default), the `target` will be revealed by scrolling as little as possible. This option will + * not affect `targets` that must be scrolled down because they will appear at the top of the boundary + * anyway. + * + * ``` + * scrollViewportToShowTarget() with scrollViewportToShowTarget() with + * Initial state alignToTop unset (default) alignToTop = true + * + * ┌────────────────────────────────┬─┐ ┌────────────────────────────────┬─┐ ┌────────────────────────────────┬─┐ + * │ │▲│ │ │▲│ │ [ Target to be revealed ] │▲│ + * │ │ │ │ │ │ │ │ │ + * │ │█│ │ │ │ │ │ │ + * │ │█│ │ │ │ │ │ │ + * │ │ │ │ │█│ │ │ │ + * │ │ │ │ │█│ │ │█│ + * │ │ │ │ │ │ │ │█│ + * │ │▼│ │ [ Target to be revealed ] │▼│ │ │▼│ + * └────────────────────────────────┴─┘ └────────────────────────────────┴─┘ └────────────────────────────────┴─┘ + * + * + * [ Target to be revealed ] + *``` + * + * @param options.forceScroll When set `true`, the `target` will be aligned to the top of the viewport + * and scrollable ancestors whether it is already visible or not. This option will only work when `alignToTop` + * is `true` + */ +export declare function scrollViewportToShowTarget>({ target, viewportOffset, ancestorOffset, alignToTop, forceScroll }: { + readonly target: HTMLElement | Range; + readonly viewportOffset?: number | { + top: number; + bottom: number; + left: number; + right: number; + }; + readonly ancestorOffset?: number; + readonly alignToTop?: T; + readonly forceScroll?: U; +}): void; +/** + * Makes any page `HTMLElement` or `Range` (target) visible within its scrollable ancestors, + * e.g. if they have `overflow: scroll` CSS style. + * + * @param target A target, which supposed to become visible to the user. + * @param ancestorOffset An offset between the target and the boundary of scrollable ancestors + * to be maintained while scrolling. + * @param limiterElement The outermost ancestor that should be scrolled. If specified, it can prevent + * scrolling the whole page. + */ +export declare function scrollAncestorsToShowTarget(target: HTMLElement | Range, ancestorOffset?: number, limiterElement?: HTMLElement): void; +export {}; diff --git a/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js new file mode 100644 index 00000000..1a09341c --- /dev/null +++ b/vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js @@ -0,0 +1,383 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ +/** + * @module utils/dom/scroll + */ +import isRange from './isrange.js'; +import Rect from './rect.js'; +import isText from './istext.js'; +/** + * Makes any page `HTMLElement` or `Range` (`target`) visible inside the browser viewport. + * This helper will scroll all `target` ancestors and the web browser viewport to reveal the target to + * the user. If the `target` is already visible, nothing will happen. + * + * @param options Additional configuration of the scrolling behavior. + * @param options.target A target, which supposed to become visible to the user. + * @param options.viewportOffset An offset from the edge of the viewport (in pixels) + * the `target` will be moved by if the viewport is scrolled. It enhances the user experience + * by keeping the `target` some distance from the edge of the viewport and thus making it easier to + * read or edit by the user. + * @param options.ancestorOffset An offset from the boundary of scrollable ancestors (if any) + * the `target` will be moved by if the viewport is scrolled. It enhances the user experience + * by keeping the `target` some distance from the edge of the ancestors and thus making it easier to + * read or edit by the user. + * @param options.alignToTop When set `true`, the helper will make sure the `target` is scrolled up + * to the top boundary of the viewport and/or scrollable ancestors if scrolled up. When not set + * (default), the `target` will be revealed by scrolling as little as possible. This option will + * not affect `targets` that must be scrolled down because they will appear at the top of the boundary + * anyway. + * + * ``` + * scrollViewportToShowTarget() with scrollViewportToShowTarget() with + * Initial state alignToTop unset (default) alignToTop = true + * + * ┌────────────────────────────────┬─┐ ┌────────────────────────────────┬─┐ ┌────────────────────────────────┬─┐ + * │ │▲│ │ │▲│ │ [ Target to be revealed ] │▲│ + * │ │ │ │ │ │ │ │ │ + * │ │█│ │ │ │ │ │ │ + * │ │█│ │ │ │ │ │ │ + * │ │ │ │ │█│ │ │ │ + * │ │ │ │ │█│ │ │█│ + * │ │ │ │ │ │ │ │█│ + * │ │▼│ │ [ Target to be revealed ] │▼│ │ │▼│ + * └────────────────────────────────┴─┘ └────────────────────────────────┴─┘ └────────────────────────────────┴─┘ + * + * + * [ Target to be revealed ] + *``` + * + * @param options.forceScroll When set `true`, the `target` will be aligned to the top of the viewport + * and scrollable ancestors whether it is already visible or not. This option will only work when `alignToTop` + * is `true` + */ +export function scrollViewportToShowTarget({ target, viewportOffset = 0, ancestorOffset = 0, alignToTop, forceScroll }) { + const targetWindow = getWindow(target); + let currentWindow = targetWindow; + let currentFrame = null; + viewportOffset = normalizeViewportOffset(viewportOffset); + // Iterate over all windows, starting from target's parent window up to window#top. + while (currentWindow) { + let firstAncestorToScroll; + // Let's scroll target's ancestors first to reveal it. Then, once the ancestor scrolls + // settled down, the algorithm can eventually scroll the viewport of the current window. + // + // Note: If the current window is target's **original** window (e.g. the first one), + // start scrolling the closest parent of the target. If not, scroll the closest parent + // of an iframe that resides in the current window. + if (currentWindow == targetWindow) { + firstAncestorToScroll = getParentElement(target); + } + else { + firstAncestorToScroll = getParentElement(currentFrame); + } + // Scroll the target's ancestors first. Once done, scrolling the viewport is easy. + scrollAncestorsToShowRect({ + parent: firstAncestorToScroll, + getRect: () => { + // Note: If the target does not belong to the current window **directly**, + // i.e. it resides in an iframe belonging to the window, obtain the target's rect + // in the coordinates of the current window. By default, a Rect returns geometry + // relative to the current window's viewport. To make it work in a parent window, + // it must be shifted. + return getRectRelativeToWindow(target, currentWindow); + }, + alignToTop, + ancestorOffset, + forceScroll + }); + // Obtain the rect of the target after it has been scrolled within its ancestors. + // It's time to scroll the viewport. + const targetRect = getRectRelativeToWindow(target, currentWindow); + scrollWindowToShowRect({ + window: currentWindow, + rect: targetRect, + viewportOffset, + alignToTop, + forceScroll + }); + if (currentWindow.parent != currentWindow) { + // Keep the reference to the