Files
blockly/core/utils/style.ts
Christopher Allen b0475b0c68 chore: Fix whitespace (#6243)
* fix: Remove spurious blank lines

  Remove extraneous blank lines introduced by deletion of
  'use strict'; pragmas.

  Also fix the location of the goog.declareModuleId call in
  core/utils/array.ts.

* fix: Add missing double-blank-line before body of modules

  Our convention is to have two blank lines between the imports (or
  module ID, if there are no imports) and the beginning of the body
  of the module.  Enforce this.

* fix: one addition format error for PR #6243
2022-06-24 19:33:39 +01:00

295 lines
11 KiB
TypeScript

/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Utilities for element styles.
* These methods are not specific to Blockly, and could be factored out into
* a JavaScript framework such as Closure.
*/
/**
* Utilities for element styles.
* These methods are not specific to Blockly, and could be factored out into
* a JavaScript framework such as Closure.
* @namespace Blockly.utils.style
*/
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.utils.style');
import {Coordinate} from './coordinate.js';
import {Rect} from './rect.js';
import {Size} from './size.js';
/**
* Gets the height and width of an element.
* Similar to Closure's goog.style.getSize
* @param element Element to get size of.
* @return Object with width/height properties.
* @alias Blockly.utils.style.getSize
*/
export function getSize(element: Element): Size {
if (getStyle(element, 'display') !== 'none') {
return getSizeWithDisplay(element);
}
// Evaluate size with a temporary element.
// AnyDuringMigration because: Property 'style' does not exist on type
// 'Element'.
const style = (element as AnyDuringMigration).style;
const originalDisplay = style.display;
const originalVisibility = style.visibility;
const originalPosition = style.position;
style.visibility = 'hidden';
style.position = 'absolute';
style.display = 'inline';
const offsetWidth = (element as HTMLElement).offsetWidth;
const offsetHeight = (element as HTMLElement).offsetHeight;
style.display = originalDisplay;
style.position = originalPosition;
style.visibility = originalVisibility;
return new Size(offsetWidth, offsetHeight);
}
/**
* Gets the height and width of an element when the display is not none.
* @param element Element to get size of.
* @return Object with width/height properties.
*/
function getSizeWithDisplay(element: Element): Size {
const offsetWidth = (element as HTMLElement).offsetWidth;
const offsetHeight = (element as HTMLElement).offsetHeight;
return new Size(offsetWidth, offsetHeight);
}
/**
* Cross-browser pseudo get computed style. It returns the computed style where
* available. If not available it tries the cascaded style value (IE
* currentStyle) and in worst case the inline style value. It shouldn't be
* called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
* discussion.
*
* Copied from Closure's goog.style.getStyle_
*
* @param element Element to get style of.
* @param style Property to get (must be camelCase, not CSS-style).
* @return Style value.
*/
function getStyle(element: Element, style: string): string {
// AnyDuringMigration because: Property 'style' does not exist on type
// 'Element'. AnyDuringMigration because: Property 'style' does not exist on
// type 'Element'.
return getComputedStyle(element, style) || getCascadedStyle(element, style) ||
(element as AnyDuringMigration).style &&
(element as AnyDuringMigration).style[style];
}
/**
* Retrieves a computed style value of a node. It returns empty string if the
* value cannot be computed (which will be the case in Internet Explorer) or
* "none" if the property requested is an SVG one and it has not been
* explicitly set (firefox and webkit).
*
* Copied from Closure's goog.style.getComputedStyle
*
* @param element Element to get style of.
* @param property Property to get (camel-case).
* @return Style value.
* @alias Blockly.utils.style.getComputedStyle
*/
export function getComputedStyle(element: Element, property: string): string {
if (document.defaultView && document.defaultView.getComputedStyle) {
const styles = document.defaultView.getComputedStyle(element, null);
if (styles) {
// element.style[..] is undefined for browser specific styles
// as 'filter'.
return (styles as AnyDuringMigration)[property] ||
styles.getPropertyValue(property) || '';
}
}
return '';
}
/**
* Gets the cascaded style value of a node, or null if the value cannot be
* computed (only Internet Explorer can do this).
*
* Copied from Closure's goog.style.getCascadedStyle
*
* @param element Element to get style of.
* @param style Property to get (camel-case).
* @return Style value.
* @alias Blockly.utils.style.getCascadedStyle
*/
export function getCascadedStyle(element: Element, style: string): string {
// AnyDuringMigration because: Property 'currentStyle' does not exist on type
// 'Element'. AnyDuringMigration because: Property 'currentStyle' does not
// exist on type 'Element'.
return (element as AnyDuringMigration).currentStyle ?
(element as AnyDuringMigration).currentStyle[style] :
'' as string;
}
/**
* Returns a Coordinate object relative to the top-left of the HTML document.
* Similar to Closure's goog.style.getPageOffset
* @param el Element to get the page offset for.
* @return The page offset.
* @alias Blockly.utils.style.getPageOffset
*/
export function getPageOffset(el: Element): Coordinate {
const pos = new Coordinate(0, 0);
const box = el.getBoundingClientRect();
const documentElement = document.documentElement;
// Must add the scroll coordinates in to get the absolute page offset
// of element since getBoundingClientRect returns relative coordinates to
// the viewport.
const scrollCoord = new Coordinate(
window.pageXOffset || documentElement.scrollLeft,
window.pageYOffset || documentElement.scrollTop);
pos.x = box.left + scrollCoord.x;
pos.y = box.top + scrollCoord.y;
return pos;
}
/**
* Calculates the viewport coordinates relative to the document.
* Similar to Closure's goog.style.getViewportPageOffset
* @return The page offset of the viewport.
* @alias Blockly.utils.style.getViewportPageOffset
*/
export function getViewportPageOffset(): Coordinate {
const body = document.body;
const documentElement = document.documentElement;
const scrollLeft = body.scrollLeft || documentElement.scrollLeft;
const scrollTop = body.scrollTop || documentElement.scrollTop;
return new Coordinate(scrollLeft, scrollTop);
}
/**
* Shows or hides an element from the page. Hiding the element is done by
* setting the display property to "none", removing the element from the
* rendering hierarchy so it takes up no space. To show the element, the default
* inherited display property is restored (defined either in stylesheets or by
* the browser's default style rules).
* Copied from Closure's goog.style.getViewportPageOffset
*
* @param el Element to show or hide.
* @param isShown True to render the element in its default style, false to
* disable rendering the element.
* @alias Blockly.utils.style.setElementShown
*/
export function setElementShown(el: Element, isShown: AnyDuringMigration) {
// AnyDuringMigration because: Property 'style' does not exist on type
// 'Element'.
(el as AnyDuringMigration).style.display = isShown ? '' : 'none';
}
/**
* Returns true if the element is using right to left (RTL) direction.
* Copied from Closure's goog.style.isRightToLeft
*
* @param el The element to test.
* @return True for right to left, false for left to right.
* @alias Blockly.utils.style.isRightToLeft
*/
export function isRightToLeft(el: Element): boolean {
return 'rtl' === getStyle(el, 'direction');
}
/**
* Gets the computed border widths (on all sides) in pixels
* Copied from Closure's goog.style.getBorderBox
* @param element The element to get the border widths for.
* @return The computed border widths.
* @alias Blockly.utils.style.getBorderBox
*/
export function getBorderBox(element: Element): Rect {
const left = parseFloat(getComputedStyle(element, 'borderLeftWidth'));
const right = parseFloat(getComputedStyle(element, 'borderRightWidth'));
const top = parseFloat(getComputedStyle(element, 'borderTopWidth'));
const bottom = parseFloat(getComputedStyle(element, 'borderBottomWidth'));
return new Rect(top, bottom, left, right);
}
/**
* Changes the scroll position of `container` with the minimum amount so
* that the content and the borders of the given `element` become visible.
* If the element is bigger than the container, its top left corner will be
* aligned as close to the container's top left corner as possible.
* Copied from Closure's goog.style.scrollIntoContainerView
*
* @param element The element to make visible.
* @param container The container to scroll. If not set, then the document
* scroll element will be used.
* @param opt_center Whether to center the element in the container.
* Defaults to false.
* @alias Blockly.utils.style.scrollIntoContainerView
*/
export function scrollIntoContainerView(
element: Element, container: Element, opt_center?: boolean) {
const offset = getContainerOffsetToScrollInto(element, container, opt_center);
container.scrollLeft = offset.x;
container.scrollTop = offset.y;
}
/**
* Calculate the scroll position of `container` with the minimum amount so
* that the content and the borders of the given `element` become visible.
* If the element is bigger than the container, its top left corner will be
* aligned as close to the container's top left corner as possible.
* Copied from Closure's goog.style.getContainerOffsetToScrollInto
*
* @param element The element to make visible.
* @param container The container to scroll. If not set, then the document
* scroll element will be used.
* @param opt_center Whether to center the element in the container.
* Defaults to false.
* @return The new scroll position of the container.
* @alias Blockly.utils.style.getContainerOffsetToScrollInto
*/
export function getContainerOffsetToScrollInto(
element: Element, container: Element, opt_center?: boolean): Coordinate {
// Absolute position of the element's border's top left corner.
const elementPos = getPageOffset(element);
// Absolute position of the container's border's top left corner.
const containerPos = getPageOffset(container);
const containerBorder = getBorderBox(container);
// Relative pos. of the element's border box to the container's content box.
const relX = elementPos.x - containerPos.x - containerBorder.left;
const relY = elementPos.y - containerPos.y - containerBorder.top;
// How much the element can move in the container, i.e. the difference between
// the element's bottom-right-most and top-left-most position where it's
// fully visible.
const elementSize = getSizeWithDisplay(element);
const spaceX = container.clientWidth - elementSize.width;
const spaceY = container.clientHeight - elementSize.height;
let scrollLeft = container.scrollLeft;
let scrollTop = container.scrollTop;
if (opt_center) {
// All browsers round non-integer scroll positions down.
scrollLeft += relX - spaceX / 2;
scrollTop += relY - spaceY / 2;
} else {
// This formula was designed to give the correct scroll values in the
// following cases:
// - element is higher than container (spaceY < 0) => scroll down by relY
// - element is not higher that container (spaceY >= 0):
// - it is above container (relY < 0) => scroll up by abs(relY)
// - it is below container (relY > spaceY) => scroll down by relY - spaceY
// - it is in the container => don't scroll
scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
}
return new Coordinate(scrollLeft, scrollTop);
}