/* * Copyright (c) 2010, 2023 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import {arrays, Dimension, Insets, objects, Point, Rectangle, scout, scrollbars} from '../index'; import $ from 'jquery'; export interface PrefSizeOptions { /** * When set to true the returned dimensions may contain fractional digits, otherwise the sizes are rounded up. Default is false. */ exact?: boolean; /** * Whether to include the margins in the returned size. Default is false. */ includeMargin?: boolean; /** * If true, the width and height properties are set to '' while measuring, thus allowing existing CSS rules to influence the sizes. * If set to false, the sizes are set to 'auto' or the corresponding hint values. Default is false. */ useCssSize?: boolean; /** * If useCssSize is false, this value is used as width (in pixels) instead of 'auto'. * Useful to get the preferred height for a given width. */ widthHint?: number; /** * Same as 'widthHint' but for the height. */ heightHint?: number; /** * Sets min/max-width/height in addition to with width/height if widthHint resp. heightHint is set. * The browser sometimes makes the element smaller or larger than specified by width/height, especially in a flex container. * To prevent that, set this option to true. Default is false, but may change in the future. */ enforceSizeHints?: boolean; /** * By default, the $elem's scrolling position is saved and restored during the execution of this method (because applying * intermediate styles for measurement might change the current position). If the calling method does that itself, you should * set this option to false in order to prevent overriding the stored scrolling position in $elem's data attributes. Default is true. */ restoreScrollPositions?: boolean; /** * If set, the $elem is checked for one of these classes. * If one of these classes is currently set on the $elem, a clone of the $elem without the classes is created and measured instead. See also {@link prefSizeWithoutAnimation}. */ animateClasses?: string[]; } export interface SizeOptions { /** * When set to true the returned dimensions may contain fractional digits, otherwise the sizes are rounded up. Default is false. */ exact?: boolean; /** * Whether to include the margins in the returned size. Default is false. */ includeMargin?: boolean; } export interface InsetsOptions { /** * Whether to include the margins in the returned insets. Default is false. */ includeMargin?: boolean; /** * Whether to include the paddings in the returned insets. Default is true. */ includePadding?: boolean; /** * Whether to include the borders in the returned insets. Default is true. */ includeBorder?: boolean; } export interface BoundsOptions { /** * When set to true the returned size may contain fractional digits, otherwise the sizes are rounded up. X and Y are not affected by this option. Default is false. */ exact?: boolean; /** * Whether to include the margins in the returned size. X and Y are not affected by this option. Default is false. */ includeMargin?: boolean; } function setBounds($comp: JQuery, x: number, y: number, width: number, height: number); function setBounds($comp: JQuery, bounds: Rectangle); function setBounds($comp: JQuery, xOrBounds: number | Rectangle, y?: number, width?: number, height?: number) { let bounds = xOrBounds instanceof Rectangle ? xOrBounds : new Rectangle(xOrBounds, y, width, height); $comp .cssLeft(bounds.x) .cssTop(bounds.y) .cssWidth(bounds.width) .cssHeight(bounds.height); } function setSize($comp: JQuery, width: number, height: number); function setSize($comp: JQuery, size: Dimension); function setSize($comp: JQuery, widthOrSize: Dimension | number, height?: number) { let size = widthOrSize instanceof Dimension ? widthOrSize : new Dimension(widthOrSize, height); $comp .cssWidth(size.width) .cssHeight(size.height); } function setLocation($comp: JQuery, x: number, y: number); function setLocation($comp: JQuery, location: Point); /** * Sets the location (CSS properties left, top) of the component. */ function setLocation($comp: JQuery, xOrPoint: number | Point, y?: number) { let point = xOrPoint instanceof Point ? xOrPoint : new Point(xOrPoint, y); $comp .cssLeft(point.x) .cssTop(point.y); } /** * Helpers for graphical operations */ export const graphics = { /** * Returns the preferred size of $elem. * * Precondition: $elem and its parents must not be hidden ('display: none' - other styles like 'visibility: hidden' * or 'opacity: 0' would be ok because in this case the browser reserves the space the element would be using). * * The `style` and `class` properties are temporarily altered to allow the element to assume its "natural size". * A marker CSS class `measure` is added that can be used to reset element-specific CSS constraints (e.g. flexbox). * * @param $elem * the jQuery element to measure * @param options * an optional options object. Shorthand version: If a boolean is passed instead * of an object, the value is automatically converted to the option "includeMargin". */ prefSize($elem: JQuery, options?: PrefSizeOptions | boolean): Dimension { // Return 0/0 if element is not displayed (display: none). // We don't use isVisible by purpose because isVisible returns false for elements with visibility: hidden which is wrong here (we would like to be able to measure hidden elements) if (!$elem[0] || $elem.isDisplayNone()) { return new Dimension(0, 0); } if (typeof options === 'boolean') { options = { includeMargin: options }; } else { options = options || {}; } let defaults = { includeMargin: false, useCssSize: false, widthHint: undefined, heightHint: undefined, restoreScrollPositions: true }; options = $.extend({}, defaults, options); if (options.animateClasses && options.animateClasses.length > 0) { return graphics.prefSizeWithoutAnimation($elem, options); } let oldStyle = $elem.attr('style'); let oldClass = $elem.attr('class'); let oldScrollLeft = $elem.scrollLeft(); let oldScrollTop = $elem.scrollTop(); if (options.restoreScrollPositions) { scrollbars.storeScrollPositions($elem); } // UseCssSize is necessary if the css rules have a fix height or width set. // Otherwise, setting the width/height to auto could result in a different size let newWidth = (options.useCssSize ? '' : scout.nvl(options.widthHint, 'auto')); let newHeight = (options.useCssSize ? '' : scout.nvl(options.heightHint, 'auto')); let cssProperties = { 'width': newWidth, 'height': newHeight }; if (scout.nvl(options.enforceSizeHints, false)) { if (objects.isNumber(newWidth)) { cssProperties['max-width'] = newWidth; cssProperties['min-width'] = newWidth; } if (objects.isNumber(newHeight)) { cssProperties['max-height'] = newHeight; cssProperties['min-height'] = newHeight; } } // modify properties which prevent reading the preferred size $elem.addClass('measure'); $elem.css(cssProperties); // measure let bcr = $elem[0].getBoundingClientRect(); let prefSize = new Dimension(bcr.width, bcr.height); if (options.includeMargin) { prefSize.width += $elem.cssMarginX(); prefSize.height += $elem.cssMarginY(); } // reset the modified style attribute $elem.attrOrRemove('style', oldStyle); $elem.attrOrRemove('class', oldClass); $elem.scrollLeft(oldScrollLeft); $elem.scrollTop(oldScrollTop); if (options.restoreScrollPositions) { scrollbars.restoreScrollPositions($elem); } return graphics.exactPrefSize(prefSize, options); }, /** * Ensure resulting numbers are integers. getBoundingClientRect() might correctly return fractional values * (because of the browser's sub-pixel rendering). However, if we use those numbers to set the size * of an element using CSS, it gets rounded or cut off. The behavior is not defined amongst different * browser engines. *
* Example: * - Measured size from this method: h = 345.239990234375 * - Set the size to an element: $elem.css('height', h + 'px') * - Results: * Firefox & Chrome