accessibility-developer-tools
Version:
This is a library of accessibility-related testing and utility code.
1,352 lines (1,182 loc) • 75.8 kB
JavaScript
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for element styles.
*
* @author arv@google.com (Erik Arvidsson)
* @author eae@google.com (Emil A Eklund)
* @see ../demos/inline_block_quirks.html
* @see ../demos/inline_block_standards.html
* @see ../demos/style_viewport.html
*/
goog.provide('goog.style');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagName');
goog.require('goog.dom.vendor');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.legacyconversions');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Rect');
goog.require('goog.math.Size');
goog.require('goog.object');
goog.require('goog.reflect');
goog.require('goog.string');
goog.require('goog.userAgent');
goog.forwardDeclare('goog.events.BrowserEvent');
goog.forwardDeclare('goog.events.Event');
/**
* Sets a style value on an element.
*
* This function is not indended to patch issues in the browser's style
* handling, but to allow easy programmatic access to setting dash-separated
* style properties. An example is setting a batch of properties from a data
* object without overwriting old styles. When possible, use native APIs:
* elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
* elem.style.cssText = 'property1: value1; property2: value2'.
*
* @param {Element} element The element to change.
* @param {string|Object} style If a string, a style name. If an object, a hash
* of style names to style values.
* @param {string|number|boolean=} opt_value If style was a string, then this
* should be the value.
*/
goog.style.setStyle = function(element, style, opt_value) {
if (goog.isString(style)) {
goog.style.setStyle_(element, opt_value, style);
} else {
for (var key in style) {
goog.style.setStyle_(element, style[key], key);
}
}
};
/**
* Sets a style value on an element, with parameters swapped to work with
* {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
* necessary.
* @param {Element} element The element to change.
* @param {string|number|boolean|undefined} value Style value.
* @param {string} style Style name.
* @private
*/
goog.style.setStyle_ = function(element, value, style) {
var propertyName = goog.style.getVendorJsStyleName_(element, style);
if (propertyName) {
// TODO(johnlenz): coerce to string?
element.style[propertyName] = /** @type {?} */ (value);
}
};
/**
* Style name cache that stores previous property name lookups.
*
* This is used by setStyle to speed up property lookups, entries look like:
* { StyleName: ActualPropertyName }
*
* @private {!Object<string, string>}
*/
goog.style.styleNameCache_ = {};
/**
* Returns the style property name in camel-case. If it does not exist and a
* vendor-specific version of the property does exist, then return the vendor-
* specific property name instead.
* @param {Element} element The element to change.
* @param {string} style Style name.
* @return {string} Vendor-specific style.
* @private
*/
goog.style.getVendorJsStyleName_ = function(element, style) {
var propertyName = goog.style.styleNameCache_[style];
if (!propertyName) {
var camelStyle = goog.string.toCamelCase(style);
propertyName = camelStyle;
if (element.style[camelStyle] === undefined) {
var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
goog.string.toTitleCase(camelStyle);
if (element.style[prefixedStyle] !== undefined) {
propertyName = prefixedStyle;
}
}
goog.style.styleNameCache_[style] = propertyName;
}
return propertyName;
};
/**
* Returns the style property name in CSS notation. If it does not exist and a
* vendor-specific version of the property does exist, then return the vendor-
* specific property name instead.
* @param {Element} element The element to change.
* @param {string} style Style name.
* @return {string} Vendor-specific style.
* @private
*/
goog.style.getVendorStyleName_ = function(element, style) {
var camelStyle = goog.string.toCamelCase(style);
if (element.style[camelStyle] === undefined) {
var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
goog.string.toTitleCase(camelStyle);
if (element.style[prefixedStyle] !== undefined) {
return goog.dom.vendor.getVendorPrefix() + '-' + style;
}
}
return style;
};
/**
* Retrieves an explicitly-set style value of a node. This returns '' if there
* isn't a style attribute on the element or if this style property has not been
* explicitly set in script.
*
* @param {Element} element Element to get style of.
* @param {string} property Property to get, css-style (if you have a camel-case
* property, use element.style[style]).
* @return {string} Style value.
*/
goog.style.getStyle = function(element, property) {
// element.style is '' for well-known properties which are unset.
// For for browser specific styles as 'filter' is undefined
// so we need to return '' explicitly to make it consistent across
// browsers.
var styleValue = element.style[goog.string.toCamelCase(property)];
// Using typeof here because of a bug in Safari 5.1, where this value
// was undefined, but === undefined returned false.
if (typeof(styleValue) !== 'undefined') {
return styleValue;
}
return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
'';
};
/**
* 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).
*
* @param {Element} element Element to get style of.
* @param {string} property Property to get (camel-case).
* @return {string} Style value.
*/
goog.style.getComputedStyle = function(element, property) {
var doc = goog.dom.getOwnerDocument(element);
if (doc.defaultView && doc.defaultView.getComputedStyle) {
var styles = doc.defaultView.getComputedStyle(element, null);
if (styles) {
// element.style[..] is undefined for browser specific styles
// as 'filter'.
return styles[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).
*
* @param {Element} element Element to get style of.
* @param {string} style Property to get (camel-case).
* @return {string} Style value.
*/
goog.style.getCascadedStyle = function(element, style) {
// TODO(nicksantos): This should be documented to return null. #fixTypes
return element.currentStyle ? element.currentStyle[style] : null;
};
/**
* 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.
*
* @param {Element} element Element to get style of.
* @param {string} style Property to get (must be camelCase, not css-style.).
* @return {string} Style value.
* @private
*/
goog.style.getStyle_ = function(element, style) {
return goog.style.getComputedStyle(element, style) ||
goog.style.getCascadedStyle(element, style) ||
(element.style && element.style[style]);
};
/**
* Retrieves the computed value of the box-sizing CSS attribute.
* Browser support: http://caniuse.com/css3-boxsizing.
* @param {!Element} element The element whose box-sizing to get.
* @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
* box-sizing is not supported (IE7 and below).
*/
goog.style.getComputedBoxSizing = function(element) {
return goog.style.getStyle_(element, 'boxSizing') ||
goog.style.getStyle_(element, 'MozBoxSizing') ||
goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
};
/**
* Retrieves the computed value of the position CSS attribute.
* @param {Element} element The element to get the position of.
* @return {string} Position value.
*/
goog.style.getComputedPosition = function(element) {
return goog.style.getStyle_(element, 'position');
};
/**
* Retrieves the computed background color string for a given element. The
* string returned is suitable for assigning to another element's
* background-color, but is not guaranteed to be in any particular string
* format. Accessing the color in a numeric form may not be possible in all
* browsers or with all input.
*
* If the background color for the element is defined as a hexadecimal value,
* the resulting string can be parsed by goog.color.parse in all supported
* browsers.
*
* Whether named colors like "red" or "lightblue" get translated into a
* format which can be parsed is browser dependent. Calling this function on
* transparent elements will return "transparent" in most browsers or
* "rgba(0, 0, 0, 0)" in WebKit.
* @param {Element} element The element to get the background color of.
* @return {string} The computed string value of the background color.
*/
goog.style.getBackgroundColor = function(element) {
return goog.style.getStyle_(element, 'backgroundColor');
};
/**
* Retrieves the computed value of the overflow-x CSS attribute.
* @param {Element} element The element to get the overflow-x of.
* @return {string} The computed string value of the overflow-x attribute.
*/
goog.style.getComputedOverflowX = function(element) {
return goog.style.getStyle_(element, 'overflowX');
};
/**
* Retrieves the computed value of the overflow-y CSS attribute.
* @param {Element} element The element to get the overflow-y of.
* @return {string} The computed string value of the overflow-y attribute.
*/
goog.style.getComputedOverflowY = function(element) {
return goog.style.getStyle_(element, 'overflowY');
};
/**
* Retrieves the computed value of the z-index CSS attribute.
* @param {Element} element The element to get the z-index of.
* @return {string|number} The computed value of the z-index attribute.
*/
goog.style.getComputedZIndex = function(element) {
return goog.style.getStyle_(element, 'zIndex');
};
/**
* Retrieves the computed value of the text-align CSS attribute.
* @param {Element} element The element to get the text-align of.
* @return {string} The computed string value of the text-align attribute.
*/
goog.style.getComputedTextAlign = function(element) {
return goog.style.getStyle_(element, 'textAlign');
};
/**
* Retrieves the computed value of the cursor CSS attribute.
* @param {Element} element The element to get the cursor of.
* @return {string} The computed string value of the cursor attribute.
*/
goog.style.getComputedCursor = function(element) {
return goog.style.getStyle_(element, 'cursor');
};
/**
* Retrieves the computed value of the CSS transform attribute.
* @param {Element} element The element to get the transform of.
* @return {string} The computed string representation of the transform matrix.
*/
goog.style.getComputedTransform = function(element) {
var property = goog.style.getVendorStyleName_(element, 'transform');
return goog.style.getStyle_(element, property) ||
goog.style.getStyle_(element, 'transform');
};
/**
* Sets the top/left values of an element. If no unit is specified in the
* argument then it will add px. The second argument is required if the first
* argument is a string or number and is ignored if the first argument
* is a coordinate.
* @param {Element} el Element to move.
* @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
* @param {string|number=} opt_arg2 Top position.
*/
goog.style.setPosition = function(el, arg1, opt_arg2) {
var x, y;
if (arg1 instanceof goog.math.Coordinate) {
x = arg1.x;
y = arg1.y;
} else {
x = arg1;
y = opt_arg2;
}
el.style.left = goog.style.getPixelStyleValue_(
/** @type {number|string} */ (x), false);
el.style.top = goog.style.getPixelStyleValue_(
/** @type {number|string} */ (y), false);
};
/**
* Gets the offsetLeft and offsetTop properties of an element and returns them
* in a Coordinate object
* @param {Element} element Element.
* @return {!goog.math.Coordinate} The position.
*/
goog.style.getPosition = function(element) {
return new goog.math.Coordinate(
/** @type {!HTMLElement} */ (element).offsetLeft,
/** @type {!HTMLElement} */ (element).offsetTop);
};
/**
* Returns the viewport element for a particular document
* @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
* of.
* @return {Element} document.documentElement or document.body.
*/
goog.style.getClientViewportElement = function(opt_node) {
var doc;
if (opt_node) {
doc = goog.dom.getOwnerDocument(opt_node);
} else {
doc = goog.dom.getDocument();
}
// In old IE versions the document.body represented the viewport
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
!goog.dom.getDomHelper(doc).isCss1CompatMode()) {
return doc.body;
}
return doc.documentElement;
};
/**
* Calculates the viewport coordinates relative to the page/document
* containing the node. The viewport may be the browser viewport for
* non-iframe document, or the iframe container for iframe'd document.
* @param {!Document} doc The document to use as the reference point.
* @return {!goog.math.Coordinate} The page offset of the viewport.
*/
goog.style.getViewportPageOffset = function(doc) {
var body = doc.body;
var documentElement = doc.documentElement;
var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
var scrollTop = body.scrollTop || documentElement.scrollTop;
return new goog.math.Coordinate(scrollLeft, scrollTop);
};
/**
* Gets the client rectangle of the DOM element.
*
* getBoundingClientRect is part of a new CSS object model draft (with a
* long-time presence in IE), replacing the error-prone parent offset
* computation and the now-deprecated Gecko getBoxObjectFor.
*
* This utility patches common browser bugs in getBoundingClientRect. It
* will fail if getBoundingClientRect is unsupported.
*
* If the element is not in the DOM, the result is undefined, and an error may
* be thrown depending on user agent.
*
* @param {!Element} el The element whose bounding rectangle is being queried.
* @return {Object} A native bounding rectangle with numerical left, top,
* right, and bottom. Reported by Firefox to be of object type ClientRect.
* @private
*/
goog.style.getBoundingClientRect_ = function(el) {
var rect;
try {
rect = el.getBoundingClientRect();
} catch (e) {
// In IE < 9, calling getBoundingClientRect on an orphan element raises an
// "Unspecified Error". All other browsers return zeros.
return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
}
// Patch the result in IE only, so that this function can be inlined if
// compiled for non-IE.
if (goog.userAgent.IE && el.ownerDocument.body) {
// In IE, most of the time, 2 extra pixels are added to the top and left
// due to the implicit 2-pixel inset border. In IE6/7 quirks mode and
// IE6 standards mode, this border can be overridden by setting the
// document element's border to zero -- thus, we cannot rely on the
// offset always being 2 pixels.
// In quirks mode, the offset can be determined by querying the body's
// clientLeft/clientTop, but in standards mode, it is found by querying
// the document element's clientLeft/clientTop. Since we already called
// getBoundingClientRect we have already forced a reflow, so it is not
// too expensive just to query them all.
// See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
var doc = el.ownerDocument;
rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
}
return rect;
};
/**
* Returns the first parent that could affect the position of a given element.
* @param {Element} element The element to get the offset parent for.
* @return {Element} The first offset parent or null if one cannot be found.
*/
goog.style.getOffsetParent = function(element) {
// element.offsetParent does the right thing in IE7 and below. In other
// browsers it only includes elements with position absolute, relative or
// fixed, not elements with overflow set to auto or scroll.
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
goog.asserts.assert(element && 'offsetParent' in element);
return element.offsetParent;
}
var doc = goog.dom.getOwnerDocument(element);
var positionStyle = goog.style.getStyle_(element, 'position');
var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
for (var parent = element.parentNode; parent && parent != doc;
parent = parent.parentNode) {
// Skip shadowDOM roots.
if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT && parent.host) {
parent = parent.host;
}
positionStyle =
goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
skipStatic = skipStatic && positionStyle == 'static' &&
parent != doc.documentElement && parent != doc.body;
if (!skipStatic &&
(parent.scrollWidth > parent.clientWidth ||
parent.scrollHeight > parent.clientHeight ||
positionStyle == 'fixed' || positionStyle == 'absolute' ||
positionStyle == 'relative')) {
return /** @type {!Element} */ (parent);
}
}
return null;
};
/**
* Calculates and returns the visible rectangle for a given element. Returns a
* box describing the visible portion of the nearest scrollable offset ancestor.
* Coordinates are given relative to the document.
*
* @param {Element} element Element to get the visible rect for.
* @return {goog.math.Box} Bounding elementBox describing the visible rect or
* null if scrollable ancestor isn't inside the visible viewport.
*/
goog.style.getVisibleRectForElement = function(element) {
var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
var dom = goog.dom.getDomHelper(element);
var body = dom.getDocument().body;
var documentElement = dom.getDocument().documentElement;
var scrollEl = dom.getDocumentScrollElement();
// Determine the size of the visible rect by climbing the dom accounting for
// all scrollable containers.
for (var el = element; el = goog.style.getOffsetParent(el);) {
// clientWidth is zero for inline block elements in IE.
// on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
if ((!goog.userAgent.IE || el.clientWidth != 0) &&
(!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
// body may have overflow set on it, yet we still get the entire
// viewport. In some browsers, el.offsetParent may be
// document.documentElement, so check for that too.
(el != body && el != documentElement &&
goog.style.getStyle_(el, 'overflow') != 'visible')) {
var pos = goog.style.getPageOffset(el);
var client = goog.style.getClientLeftTop(el);
pos.x += client.x;
pos.y += client.y;
visibleRect.top = Math.max(visibleRect.top, pos.y);
visibleRect.right = Math.min(visibleRect.right, pos.x + el.clientWidth);
visibleRect.bottom =
Math.min(visibleRect.bottom, pos.y + el.clientHeight);
visibleRect.left = Math.max(visibleRect.left, pos.x);
}
}
// Clip by window's viewport.
var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
visibleRect.left = Math.max(visibleRect.left, scrollX);
visibleRect.top = Math.max(visibleRect.top, scrollY);
var winSize = dom.getViewportSize();
visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
return visibleRect.top >= 0 && visibleRect.left >= 0 &&
visibleRect.bottom > visibleRect.top &&
visibleRect.right > visibleRect.left ?
visibleRect :
null;
};
/**
* Calculate the scroll position of {@code container} with the minimum amount so
* that the content and the borders of the given {@code 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.
*
* @param {Element} element The element to make visible.
* @param {Element=} opt_container The container to scroll. If not set, then the
* document scroll element will be used.
* @param {boolean=} opt_center Whether to center the element in the container.
* Defaults to false.
* @return {!goog.math.Coordinate} The new scroll position of the container,
* in form of goog.math.Coordinate(scrollLeft, scrollTop).
*/
goog.style.getContainerOffsetToScrollInto = function(
element, opt_container, opt_center) {
var container = opt_container || goog.dom.getDocumentScrollElement();
// Absolute position of the element's border's top left corner.
var elementPos = goog.style.getPageOffset(element);
// Absolute position of the container's border's top left corner.
var containerPos = goog.style.getPageOffset(container);
var containerBorder = goog.style.getBorderBox(container);
if (container == goog.dom.getDocumentScrollElement()) {
// The element position is calculated based on the page offset, and the
// document scroll element holds the scroll position within the page. We can
// use the scroll position to calculate the relative position from the
// element.
var relX = elementPos.x - container.scrollLeft;
var relY = elementPos.y - container.scrollTop;
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) {
// In older versions of IE getPageOffset(element) does not include the
// container border so it has to be added to accommodate.
relX += containerBorder.left;
relY += containerBorder.top;
}
} else {
// Relative pos. of the element's border box to the container's content box.
var relX = elementPos.x - containerPos.x - containerBorder.left;
var 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.
var elementSize = goog.style.getSizeWithDisplay_(element);
var spaceX = container.clientWidth - elementSize.width;
var spaceY = container.clientHeight - elementSize.height;
var scrollLeft = container.scrollLeft;
var 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 goog.math.Coordinate(scrollLeft, scrollTop);
};
/**
* Changes the scroll position of {@code container} with the minimum amount so
* that the content and the borders of the given {@code 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.
*
* @param {Element} element The element to make visible.
* @param {Element=} opt_container The container to scroll. If not set, then the
* document scroll element will be used.
* @param {boolean=} opt_center Whether to center the element in the container.
* Defaults to false.
*/
goog.style.scrollIntoContainerView = function(
element, opt_container, opt_center) {
var container = opt_container || goog.dom.getDocumentScrollElement();
var offset =
goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
container.scrollLeft = offset.x;
container.scrollTop = offset.y;
};
/**
* Returns clientLeft (width of the left border and, if the directionality is
* right to left, the vertical scrollbar) and clientTop as a coordinate object.
*
* @param {Element} el Element to get clientLeft for.
* @return {!goog.math.Coordinate} Client left and top.
*/
goog.style.getClientLeftTop = function(el) {
return new goog.math.Coordinate(el.clientLeft, el.clientTop);
};
/**
* Returns a Coordinate object relative to the top-left of the HTML document.
* Implemented as a single function to save having to do two recursive loops in
* opera and safari just to get both coordinates. If you just want one value do
* use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
* note if you call both those methods the tree will be analysed twice.
*
* @param {Element} el Element to get the page offset for.
* @return {!goog.math.Coordinate} The page offset.
*/
goog.style.getPageOffset = function(el) {
var doc = goog.dom.getOwnerDocument(el);
// TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
goog.asserts.assertObject(el, 'Parameter is required');
// NOTE(arv): If element is hidden (display none or disconnected or any the
// ancestors are hidden) we get (0,0) by default but we still do the
// accumulation of scroll position.
// TODO(arv): Should we check if the node is disconnected and in that case
// return (0,0)?
var pos = new goog.math.Coordinate(0, 0);
var viewportElement = goog.style.getClientViewportElement(doc);
if (el == viewportElement) {
// viewport is always at 0,0 as that defined the coordinate system for this
// function - this avoids special case checks in the code below
return pos;
}
var box = goog.style.getBoundingClientRect_(el);
// Must add the scroll coordinates in to get the absolute page offset
// of element since getBoundingClientRect returns relative coordinates to
// the viewport.
var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
pos.x = box.left + scrollCoord.x;
pos.y = box.top + scrollCoord.y;
return pos;
};
/**
* Returns the left coordinate of an element relative to the HTML document
* @param {Element} el Elements.
* @return {number} The left coordinate.
*/
goog.style.getPageOffsetLeft = function(el) {
return goog.style.getPageOffset(el).x;
};
/**
* Returns the top coordinate of an element relative to the HTML document
* @param {Element} el Elements.
* @return {number} The top coordinate.
*/
goog.style.getPageOffsetTop = function(el) {
return goog.style.getPageOffset(el).y;
};
/**
* Returns a Coordinate object relative to the top-left of an HTML document
* in an ancestor frame of this element. Used for measuring the position of
* an element inside a frame relative to a containing frame.
*
* @param {Element} el Element to get the page offset for.
* @param {Window} relativeWin The window to measure relative to. If relativeWin
* is not in the ancestor frame chain of the element, we measure relative to
* the top-most window.
* @return {!goog.math.Coordinate} The page offset.
*/
goog.style.getFramedPageOffset = function(el, relativeWin) {
var position = new goog.math.Coordinate(0, 0);
// Iterate up the ancestor frame chain, keeping track of the current window
// and the current element in that window.
var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
// MS Edge throws when accessing "parent" if el's containing iframe has been
// deleted.
if (!goog.reflect.canAccessProperty(currentWin, 'parent')) {
return position;
}
var currentEl = el;
do {
// if we're at the top window, we want to get the page offset.
// if we're at an inner frame, we only want to get the window position
// so that we can determine the actual page offset in the context of
// the outer window.
var offset = currentWin == relativeWin ?
goog.style.getPageOffset(currentEl) :
goog.style.getClientPositionForElement_(goog.asserts.assert(currentEl));
position.x += offset.x;
position.y += offset.y;
} while (currentWin && currentWin != relativeWin &&
currentWin != currentWin.parent &&
(currentEl = currentWin.frameElement) &&
(currentWin = currentWin.parent));
return position;
};
/**
* Translates the specified rect relative to origBase page, for newBase page.
* If origBase and newBase are the same, this function does nothing.
*
* @param {goog.math.Rect} rect The source rectangle relative to origBase page,
* and it will have the translated result.
* @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
* @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
* coordinate. This must be a DOM for an ancestor frame of origBase
* or the same as origBase.
*/
goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
if (origBase.getDocument() != newBase.getDocument()) {
var body = origBase.getDocument().body;
var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
// Adjust Body's margin.
pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
!origBase.isCss1CompatMode()) {
pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
}
rect.left += pos.x;
rect.top += pos.y;
}
};
/**
* Returns the position of an element relative to another element in the
* document. A relative to B
* @param {Element|Event|goog.events.Event} a Element or mouse event whose
* position we're calculating.
* @param {Element|Event|goog.events.Event} b Element or mouse event position
* is relative to.
* @return {!goog.math.Coordinate} The relative position.
*/
goog.style.getRelativePosition = function(a, b) {
var ap = goog.style.getClientPosition(a);
var bp = goog.style.getClientPosition(b);
return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
};
/**
* Returns the position of the event or the element's border box relative to
* the client viewport.
* @param {!Element} el Element whose position to get.
* @return {!goog.math.Coordinate} The position.
* @private
*/
goog.style.getClientPositionForElement_ = function(el) {
var box = goog.style.getBoundingClientRect_(el);
return new goog.math.Coordinate(box.left, box.top);
};
/**
* Returns the position of the event or the element's border box relative to
* the client viewport. If an event is passed, and if this event is a "touch"
* event, then the position of the first changedTouches will be returned.
* @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
* @return {!goog.math.Coordinate} The position.
*/
goog.style.getClientPosition = function(el) {
goog.asserts.assert(el);
if (el.nodeType == goog.dom.NodeType.ELEMENT) {
return goog.style.getClientPositionForElement_(
/** @type {!Element} */ (el));
} else {
var targetEvent = el.changedTouches ? el.changedTouches[0] : el;
return new goog.math.Coordinate(targetEvent.clientX, targetEvent.clientY);
}
};
/**
* Moves an element to the given coordinates relative to the client viewport.
* @param {Element} el Absolutely positioned element to set page offset for.
* It must be in the document.
* @param {number|goog.math.Coordinate} x Left position of the element's margin
* box or a coordinate object.
* @param {number=} opt_y Top position of the element's margin box.
*/
goog.style.setPageOffset = function(el, x, opt_y) {
// Get current pageoffset
var cur = goog.style.getPageOffset(el);
if (x instanceof goog.math.Coordinate) {
opt_y = x.y;
x = x.x;
}
// NOTE(arv): We cannot allow strings for x and y. We could but that would
// require us to manually transform between different units
// Work out deltas
var dx = goog.asserts.assertNumber(x) - cur.x;
var dy = Number(opt_y) - cur.y;
// Set position to current left/top + delta
goog.style.setPosition(
el, /** @type {!HTMLElement} */ (el).offsetLeft + dx,
/** @type {!HTMLElement} */ (el).offsetTop + dy);
};
/**
* Sets the width/height values of an element. If an argument is numeric,
* or a goog.math.Size is passed, it is assumed to be pixels and will add
* 'px' after converting it to an integer in string form. (This just sets the
* CSS width and height properties so it might set content-box or border-box
* size depending on the box model the browser is using.)
*
* @param {Element} element Element to set the size of.
* @param {string|number|goog.math.Size} w Width of the element, or a
* size object.
* @param {string|number=} opt_h Height of the element. Required if w is not a
* size object.
*/
goog.style.setSize = function(element, w, opt_h) {
var h;
if (w instanceof goog.math.Size) {
h = w.height;
w = w.width;
} else {
if (opt_h == undefined) {
throw Error('missing height argument');
}
h = opt_h;
}
goog.style.setWidth(element, /** @type {string|number} */ (w));
goog.style.setHeight(element, h);
};
/**
* Helper function to create a string to be set into a pixel-value style
* property of an element. Can round to the nearest integer value.
*
* @param {string|number} value The style value to be used. If a number,
* 'px' will be appended, otherwise the value will be applied directly.
* @param {boolean} round Whether to round the nearest integer (if property
* is a number).
* @return {string} The string value for the property.
* @private
*/
goog.style.getPixelStyleValue_ = function(value, round) {
if (typeof value == 'number') {
value = (round ? Math.round(value) : value) + 'px';
}
return value;
};
/**
* Set the height of an element. Sets the element's style property.
* @param {Element} element Element to set the height of.
* @param {string|number} height The height value to set. If a number, 'px'
* will be appended, otherwise the value will be applied directly.
*/
goog.style.setHeight = function(element, height) {
element.style.height = goog.style.getPixelStyleValue_(height, true);
};
/**
* Set the width of an element. Sets the element's style property.
* @param {Element} element Element to set the width of.
* @param {string|number} width The width value to set. If a number, 'px'
* will be appended, otherwise the value will be applied directly.
*/
goog.style.setWidth = function(element, width) {
element.style.width = goog.style.getPixelStyleValue_(width, true);
};
/**
* Gets the height and width of an element, even if its display is none.
*
* Specifically, this returns the height and width of the border box,
* irrespective of the box model in effect.
*
* Note that this function does not take CSS transforms into account. Please see
* {@code goog.style.getTransformedSize}.
* @param {Element} element Element to get size of.
* @return {!goog.math.Size} Object with width/height properties.
*/
goog.style.getSize = function(element) {
return goog.style.evaluateWithTemporaryDisplay_(
goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
};
/**
* Call {@code fn} on {@code element} such that {@code element}'s dimensions are
* accurate when it's passed to {@code fn}.
* @param {function(!Element): T} fn Function to call with {@code element} as
* an argument after temporarily changing {@code element}'s display such
* that its dimensions are accurate.
* @param {!Element} element Element (which may have display none) to use as
* argument to {@code fn}.
* @return {T} Value returned by calling {@code fn} with {@code element}.
* @template T
* @private
*/
goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
if (goog.style.getStyle_(element, 'display') != 'none') {
return fn(element);
}
var style = element.style;
var originalDisplay = style.display;
var originalVisibility = style.visibility;
var originalPosition = style.position;
style.visibility = 'hidden';
style.position = 'absolute';
style.display = 'inline';
var retVal = fn(element);
style.display = originalDisplay;
style.position = originalPosition;
style.visibility = originalVisibility;
return retVal;
};
/**
* Gets the height and width of an element when the display is not none.
* @param {Element} element Element to get size of.
* @return {!goog.math.Size} Object with width/height properties.
* @private
*/
goog.style.getSizeWithDisplay_ = function(element) {
var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth;
var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight;
var webkitOffsetsZero =
goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
element.getBoundingClientRect) {
// Fall back to calling getBoundingClientRect when offsetWidth or
// offsetHeight are not defined, or when they are zero in WebKit browsers.
// This makes sure that we return for the correct size for SVG elements, but
// will still return 0 on Webkit prior to 534.8, see
// http://trac.webkit.org/changeset/67252.
var clientRect = goog.style.getBoundingClientRect_(element);
return new goog.math.Size(
clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
}
return new goog.math.Size(offsetWidth, offsetHeight);
};
/**
* Gets the height and width of an element, post transform, even if its display
* is none.
*
* This is like {@code goog.style.getSize}, except:
* <ol>
* <li>Takes webkitTransforms such as rotate and scale into account.
* <li>Will return null if {@code element} doesn't respond to
* {@code getBoundingClientRect}.
* <li>Currently doesn't make sense on non-WebKit browsers which don't support
* webkitTransforms.
* </ol>
* @param {!Element} element Element to get size of.
* @return {goog.math.Size} Object with width/height properties.
*/
goog.style.getTransformedSize = function(element) {
if (!element.getBoundingClientRect) {
return null;
}
var clientRect = goog.style.evaluateWithTemporaryDisplay_(
goog.style.getBoundingClientRect_, element);
return new goog.math.Size(
clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
};
/**
* Returns a bounding rectangle for a given element in page space.
* @param {Element} element Element to get bounds of. Must not be display none.
* @return {!goog.math.Rect} Bounding rectangle for the element.
*/
goog.style.getBounds = function(element) {
var o = goog.style.getPageOffset(element);
var s = goog.style.getSize(element);
return new goog.math.Rect(o.x, o.y, s.width, s.height);
};
/**
* Converts a CSS selector in the form style-property to styleProperty.
* @param {*} selector CSS Selector.
* @return {string} Camel case selector.
* @deprecated Use goog.string.toCamelCase instead.
*/
goog.style.toCamelCase = function(selector) {
return goog.string.toCamelCase(String(selector));
};
/**
* Converts a CSS selector in the form styleProperty to style-property.
* @param {string} selector Camel case selector.
* @return {string} Selector cased.
* @deprecated Use goog.string.toSelectorCase instead.
*/
goog.style.toSelectorCase = function(selector) {
return goog.string.toSelectorCase(selector);
};
/**
* Gets the opacity of a node (x-browser). This gets the inline style opacity
* of the node, and does not take into account the cascaded or the computed
* style for this node.
* @param {Element} el Element whose opacity has to be found.
* @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
* if the opacity is not set.
*/
goog.style.getOpacity = function(el) {
goog.asserts.assert(el);
var style = el.style;
var result = '';
if ('opacity' in style) {
result = style.opacity;
} else if ('MozOpacity' in style) {
result = style.MozOpacity;
} else if ('filter' in style) {
var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
if (match) {
result = String(match[1] / 100);
}
}
return result == '' ? result : Number(result);
};
/**
* Sets the opacity of a node (x-browser).
* @param {Element} el Elements whose opacity has to be set.
* @param {number|string} alpha Opacity between 0 and 1 or an empty string
* {@code ''} to clear the opacity.
*/
goog.style.setOpacity = function(el, alpha) {
goog.asserts.assert(el);
var style = el.style;
if ('opacity' in style) {
style.opacity = alpha;
} else if ('MozOpacity' in style) {
style.MozOpacity = alpha;
} else if ('filter' in style) {
// TODO(arv): Overwriting the filter might have undesired side effects.
if (alpha === '') {
style.filter = '';
} else {
style.filter = 'alpha(opacity=' + (Number(alpha) * 100) + ')';
}
}
};
/**
* Sets the background of an element to a transparent image in a browser-
* independent manner.
*
* This function does not support repeating backgrounds or alternate background
* positions to match the behavior of Internet Explorer. It also does not
* support sizingMethods other than crop since they cannot be replicated in
* browsers other than Internet Explorer.
*
* @param {Element} el The element to set background on.
* @param {string} src The image source URL.
*/
goog.style.setTransparentBackgroundImage = function(el, src) {
var style = el.style;
// It is safe to use the style.filter in IE only. In Safari 'filter' is in
// style object but access to style.filter causes it to throw an exception.
// Note: IE8 supports images with an alpha channel.
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
// See TODO in setOpacity.
style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
'src="' + src + '", sizingMethod="crop")';
} else {
// Set style properties individually instead of using background shorthand
// to prevent overwriting a pre-existing background color.
style.backgroundImage = 'url(' + src + ')';
style.backgroundPosition = 'top left';
style.backgroundRepeat = 'no-repeat';
}
};
/**
* Clears the background image of an element in a browser independent manner.
* @param {Element} el The element to clear background image for.
*/
goog.style.clearTransparentBackgroundImage = function(el) {
var style = el.style;
if ('filter' in style) {
// See TODO in setOpacity.
style.filter = '';
} else {
// Set style properties individually instead of using background shorthand
// to prevent overwriting a pre-existing background color.
style.backgroundImage = 'none';
}
};
/**
* 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.)
*
* Caveat 1: if the inherited display property for the element is set to "none"
* by the stylesheets, that is the property that will be restored by a call to
* showElement(), effectively toggling the display between "none" and "none".
*
* Caveat 2: if the element display style is set inline (by setting either
* element.style.display or a style attribute in the HTML), a call to
* showElement will clear that setting and defer to the inherited style in the
* stylesheet.
* @param {Element} el Element to show or hide.
* @param {*} display True to render the element in its default style,
* false to disable rendering the element.
* @deprecated Use goog.style.setElementShown instead.
*/
goog.style.showElement = function(el, display) {
goog.style.setElementShown(el, display);
};
/**
* 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).
*
* Caveat 1: if the inherited display property for the element is set to "none"
* by the stylesheets, that is the property that will be restored by a call to
* setElementShown(), effectively toggling the display between "none" and
* "none".
*
* Caveat 2: if the element display style is set inline (by setting either
* element.style.display or a style attribute in the HTML), a call to
* setElementShown will clear that setting and defer to the inherited style in
* the stylesheet.
* @param {Element} el Element to show or hide.
* @param {*} isShown True to render the element in its default style,
* false to disable rendering the element.
*/
goog.style.setElementShown = function(el, isShown) {
el.style.display = isShown ? '' : 'none';
};
/**
* Test whether the given element has been shown or hidden via a call to
* {@link #setElementShown}.
*
* Note this is strictly a companion method for a call
* to {@link #setElementShown} and the same caveats apply; in particular, this
* method does not guarantee that the return value will be consistent with
* whether or not the element is actually visible.
*
* @param {Element} el The element to test.
* @return {boolean} Whether the element has been shown.
* @see #setElementShown
*/
goog.style.isElementShown = function(el) {
return el.style.display != 'none';
};
/**
* Installs the styles string into the window that contains opt_node. If
* opt_node is null, the main window is used.
* @param {string} stylesString The style string to install.
* @param {Node=} opt_node Node whose parent document should have the
* styles installed.
* @return {!Element|!StyleSheet} The style element created.
* @deprecated Use {@link #installSafeStyleSheet} instead.
*/
goog.style.installStyles = function(stylesString, opt_node) {
return goog.style.installSafeStyleSheet(
goog.html.legacyconversions.safeStyleSheetFromString(stylesString),
opt_node);
};
/**
* Installs the style sheet into the window that contains opt_node. If
* opt_node is null, the main window is used.
* @param {!goog.html.SafeStyleSheet} safeStyleSheet The style sheet to install.
* @param {?Node=} opt_node Node whose parent document should have the
* styles installed.
* @return {!Element|!StyleSheet} The style element created.
*/
goog.style.installSafeStyleSheet = function(safeStyleSheet, opt_node) {
var dh = goog.dom.getDomHelper(opt_node);
var styleSheet = null;
// IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
// undefined as of IE 11.
var doc = dh.getDocument();
if (goog.userAgent.IE && doc.createStyleSheet) {
styleSheet = doc.createStyleSheet();
goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet);
} else {
var head = dh.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0];
// In opera documents are not guaranteed to have a head element, thus we
// have to make sure one exists before using it.
if (!head) {
var body = dh.getElementsByTagNameAndClass(goog.dom.TagName.BODY)[0];
head = dh.createDom(goog.dom.TagName.HEAD);
body.parentNode.insertBefore(head, body);
}
styleSheet = dh.createDom(goog.dom.TagName.STYLE);
// NOTE(user): Setting styles after the style element has been appended
// to the head results in a nasty Webkit bug in certain scenarios. Please
// refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
// details.
goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet);
dh.appendChild(head, styleSheet);
}
return styleSheet;
};
/**
* Removes the styles added by {@link #installStyles}.
* @param {Element|StyleSheet} styleSheet The value returned by
* {@link #installStyles}.
*/
goog.style.uninstallStyles = function(styleSheet) {
var node = styleSheet.ownerNode || styleSheet.owningElement ||
/** @type {Element} */ (styleSheet);
goog.dom.removeNode(node);
};
/**
* Sets the content of a style element. The style element can be any valid
* style element. This element will have its content completely replaced by
* the stylesString.
* @param {Element|StyleSheet} element A stylesheet element as returned by
* installStyles.
* @param {string} stylesString The new content of the stylesheet.
* @deprecated Use {@link #setSafe