# lib/goog/style/style.js

1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS-IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /**
16 | * @fileoverview Utilities for element styles.
17 | *
18 | * @author arv@google.com (Erik Arvidsson)
19 | * @author eae@google.com (Emil A Eklund)
20 | * @see ../demos/inline_block_quirks.html
21 | * @see ../demos/inline_block_standards.html
22 | * @see ../demos/style_viewport.html
23 | */
24 |
25 | goog.provide('goog.style');
26 |
27 |
28 | goog.require('goog.array');
29 | goog.require('goog.asserts');
30 | goog.require('goog.dom');
31 | goog.require('goog.dom.NodeType');
32 | goog.require('goog.dom.vendor');
33 | goog.require('goog.math.Box');
34 | goog.require('goog.math.Coordinate');
35 | goog.require('goog.math.Rect');
36 | goog.require('goog.math.Size');
37 | goog.require('goog.object');
38 | goog.require('goog.string');
39 | goog.require('goog.userAgent');
40 |
41 |
42 | /**
43 | * @define {boolean} Whether we know at compile time that
44 | * getBoundingClientRect() is present and bug-free on the browser.
45 | */
46 | goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false);
47 |
48 |
49 | /**
50 | * Sets a style value on an element.
51 | *
52 | * This function is not indended to patch issues in the browser's style
53 | * handling, but to allow easy programmatic access to setting dash-separated
54 | * style properties. An example is setting a batch of properties from a data
55 | * object without overwriting old styles. When possible, use native APIs:
56 | * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
57 | * elem.style.cssText = 'property1: value1; property2: value2'.
58 | *
59 | * @param {Element} element The element to change.
60 | * @param {string|Object} style If a string, a style name. If an object, a hash
61 | * of style names to style values.
62 | * @param {string|number|boolean=} opt_value If style was a string, then this
63 | * should be the value.
64 | */
65 | goog.style.setStyle = function(element, style, opt_value) {
66 | if (goog.isString(style)) {
67 | goog.style.setStyle_(element, opt_value, style);
68 | } else {
69 | for (var key in style) {
70 | goog.style.setStyle_(element, style[key], key);
71 | }
72 | }
73 | };
74 |
75 |
76 | /**
77 | * Sets a style value on an element, with parameters swapped to work with
78 | * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
79 | * necessary.
80 | * @param {Element} element The element to change.
81 | * @param {string|number|boolean|undefined} value Style value.
82 | * @param {string} style Style name.
83 | * @private
84 | */
85 | goog.style.setStyle_ = function(element, value, style) {
86 | var propertyName = goog.style.getVendorJsStyleName_(element, style);
87 |
88 | if (propertyName) {
89 | element.style[propertyName] = value;
90 | }
91 | };
92 |
93 |
94 | /**
95 | * Returns the style property name in camel-case. If it does not exist and a
96 | * vendor-specific version of the property does exist, then return the vendor-
97 | * specific property name instead.
98 | * @param {Element} element The element to change.
99 | * @param {string} style Style name.
100 | * @return {string} Vendor-specific style.
101 | * @private
102 | */
103 | goog.style.getVendorJsStyleName_ = function(element, style) {
104 | var camelStyle = goog.string.toCamelCase(style);
105 |
106 | if (element.style[camelStyle] === undefined) {
107 | var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
108 | goog.string.toTitleCase(camelStyle);
109 |
110 | if (element.style[prefixedStyle] !== undefined) {
111 | return prefixedStyle;
112 | }
113 | }
114 |
115 | return camelStyle;
116 | };
117 |
118 |
119 | /**
120 | * Returns the style property name in CSS notation. If it does not exist and a
121 | * vendor-specific version of the property does exist, then return the vendor-
122 | * specific property name instead.
123 | * @param {Element} element The element to change.
124 | * @param {string} style Style name.
125 | * @return {string} Vendor-specific style.
126 | * @private
127 | */
128 | goog.style.getVendorStyleName_ = function(element, style) {
129 | var camelStyle = goog.string.toCamelCase(style);
130 |
131 | if (element.style[camelStyle] === undefined) {
132 | var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
133 | goog.string.toTitleCase(camelStyle);
134 |
135 | if (element.style[prefixedStyle] !== undefined) {
136 | return goog.dom.vendor.getVendorPrefix() + '-' + style;
137 | }
138 | }
139 |
140 | return style;
141 | };
142 |
143 |
144 | /**
145 | * Retrieves an explicitly-set style value of a node. This returns '' if there
146 | * isn't a style attribute on the element or if this style property has not been
147 | * explicitly set in script.
148 | *
149 | * @param {Element} element Element to get style of.
150 | * @param {string} property Property to get, css-style (if you have a camel-case
151 | * property, use element.style[style]).
152 | * @return {string} Style value.
153 | */
154 | goog.style.getStyle = function(element, property) {
155 | // element.style is '' for well-known properties which are unset.
156 | // For for browser specific styles as 'filter' is undefined
157 | // so we need to return '' explicitly to make it consistent across
158 | // browsers.
159 | var styleValue = element.style[goog.string.toCamelCase(property)];
160 |
161 | // Using typeof here because of a bug in Safari 5.1, where this value
162 | // was undefined, but === undefined returned false.
163 | if (typeof(styleValue) !== 'undefined') {
164 | return styleValue;
165 | }
166 |
167 | return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
168 | '';
169 | };
170 |
171 |
172 | /**
173 | * Retrieves a computed style value of a node. It returns empty string if the
174 | * value cannot be computed (which will be the case in Internet Explorer) or
175 | * "none" if the property requested is an SVG one and it has not been
176 | * explicitly set (firefox and webkit).
177 | *
178 | * @param {Element} element Element to get style of.
179 | * @param {string} property Property to get (camel-case).
180 | * @return {string} Style value.
181 | */
182 | goog.style.getComputedStyle = function(element, property) {
183 | var doc = goog.dom.getOwnerDocument(element);
184 | if (doc.defaultView && doc.defaultView.getComputedStyle) {
185 | var styles = doc.defaultView.getComputedStyle(element, null);
186 | if (styles) {
187 | // element.style[..] is undefined for browser specific styles
188 | // as 'filter'.
189 | return styles[property] || styles.getPropertyValue(property) || '';
190 | }
191 | }
192 |
193 | return '';
194 | };
195 |
196 |
197 | /**
198 | * Gets the cascaded style value of a node, or null if the value cannot be
199 | * computed (only Internet Explorer can do this).
200 | *
201 | * @param {Element} element Element to get style of.
202 | * @param {string} style Property to get (camel-case).
203 | * @return {string} Style value.
204 | */
205 | goog.style.getCascadedStyle = function(element, style) {
206 | // TODO(nicksantos): This should be documented to return null. #fixTypes
207 | return element.currentStyle ? element.currentStyle[style] : null;
208 | };
209 |
210 |
211 | /**
212 | * Cross-browser pseudo get computed style. It returns the computed style where
213 | * available. If not available it tries the cascaded style value (IE
214 | * currentStyle) and in worst case the inline style value. It shouldn't be
215 | * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
216 | * discussion.
217 | *
218 | * @param {Element} element Element to get style of.
219 | * @param {string} style Property to get (must be camelCase, not css-style.).
220 | * @return {string} Style value.
221 | * @private
222 | */
223 | goog.style.getStyle_ = function(element, style) {
224 | return goog.style.getComputedStyle(element, style) ||
225 | goog.style.getCascadedStyle(element, style) ||
226 | (element.style && element.style[style]);
227 | };
228 |
229 |
230 | /**
231 | * Retrieves the computed value of the box-sizing CSS attribute.
232 | * Browser support: http://caniuse.com/css3-boxsizing.
233 | * @param {!Element} element The element whose box-sizing to get.
234 | * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
235 | * box-sizing is not supported (IE7 and below).
236 | */
237 | goog.style.getComputedBoxSizing = function(element) {
238 | return goog.style.getStyle_(element, 'boxSizing') ||
239 | goog.style.getStyle_(element, 'MozBoxSizing') ||
240 | goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
241 | };
242 |
243 |
244 | /**
245 | * Retrieves the computed value of the position CSS attribute.
246 | * @param {Element} element The element to get the position of.
247 | * @return {string} Position value.
248 | */
249 | goog.style.getComputedPosition = function(element) {
250 | return goog.style.getStyle_(element, 'position');
251 | };
252 |
253 |
254 | /**
255 | * Retrieves the computed background color string for a given element. The
256 | * string returned is suitable for assigning to another element's
257 | * background-color, but is not guaranteed to be in any particular string
258 | * format. Accessing the color in a numeric form may not be possible in all
259 | * browsers or with all input.
260 | *
261 | * If the background color for the element is defined as a hexadecimal value,
262 | * the resulting string can be parsed by goog.color.parse in all supported
263 | * browsers.
264 | *
265 | * Whether named colors like "red" or "lightblue" get translated into a
266 | * format which can be parsed is browser dependent. Calling this function on
267 | * transparent elements will return "transparent" in most browsers or
268 | * "rgba(0, 0, 0, 0)" in WebKit.
269 | * @param {Element} element The element to get the background color of.
270 | * @return {string} The computed string value of the background color.
271 | */
272 | goog.style.getBackgroundColor = function(element) {
273 | return goog.style.getStyle_(element, 'backgroundColor');
274 | };
275 |
276 |
277 | /**
278 | * Retrieves the computed value of the overflow-x CSS attribute.
279 | * @param {Element} element The element to get the overflow-x of.
280 | * @return {string} The computed string value of the overflow-x attribute.
281 | */
282 | goog.style.getComputedOverflowX = function(element) {
283 | return goog.style.getStyle_(element, 'overflowX');
284 | };
285 |
286 |
287 | /**
288 | * Retrieves the computed value of the overflow-y CSS attribute.
289 | * @param {Element} element The element to get the overflow-y of.
290 | * @return {string} The computed string value of the overflow-y attribute.
291 | */
292 | goog.style.getComputedOverflowY = function(element) {
293 | return goog.style.getStyle_(element, 'overflowY');
294 | };
295 |
296 |
297 | /**
298 | * Retrieves the computed value of the z-index CSS attribute.
299 | * @param {Element} element The element to get the z-index of.
300 | * @return {string|number} The computed value of the z-index attribute.
301 | */
302 | goog.style.getComputedZIndex = function(element) {
303 | return goog.style.getStyle_(element, 'zIndex');
304 | };
305 |
306 |
307 | /**
308 | * Retrieves the computed value of the text-align CSS attribute.
309 | * @param {Element} element The element to get the text-align of.
310 | * @return {string} The computed string value of the text-align attribute.
311 | */
312 | goog.style.getComputedTextAlign = function(element) {
313 | return goog.style.getStyle_(element, 'textAlign');
314 | };
315 |
316 |
317 | /**
318 | * Retrieves the computed value of the cursor CSS attribute.
319 | * @param {Element} element The element to get the cursor of.
320 | * @return {string} The computed string value of the cursor attribute.
321 | */
322 | goog.style.getComputedCursor = function(element) {
323 | return goog.style.getStyle_(element, 'cursor');
324 | };
325 |
326 |
327 | /**
328 | * Retrieves the computed value of the CSS transform attribute.
329 | * @param {Element} element The element to get the transform of.
330 | * @return {string} The computed string representation of the transform matrix.
331 | */
332 | goog.style.getComputedTransform = function(element) {
333 | var property = goog.style.getVendorStyleName_(element, 'transform');
334 | return goog.style.getStyle_(element, property) ||
335 | goog.style.getStyle_(element, 'transform');
336 | };
337 |
338 |
339 | /**
340 | * Sets the top/left values of an element. If no unit is specified in the
341 | * argument then it will add px. The second argument is required if the first
342 | * argument is a string or number and is ignored if the first argument
343 | * is a coordinate.
344 | * @param {Element} el Element to move.
345 | * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
346 | * @param {string|number=} opt_arg2 Top position.
347 | */
348 | goog.style.setPosition = function(el, arg1, opt_arg2) {
349 | var x, y;
350 | var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
351 | (goog.userAgent.MAC || goog.userAgent.X11) &&
352 | goog.userAgent.isVersionOrHigher('1.9');
353 |
354 | if (arg1 instanceof goog.math.Coordinate) {
355 | x = arg1.x;
356 | y = arg1.y;
357 | } else {
358 | x = arg1;
359 | y = opt_arg2;
360 | }
361 |
362 | // Round to the nearest pixel for buggy sub-pixel support.
363 | el.style.left = goog.style.getPixelStyleValue_(
364 | /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
365 | el.style.top = goog.style.getPixelStyleValue_(
366 | /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
367 | };
368 |
369 |
370 | /**
371 | * Gets the offsetLeft and offsetTop properties of an element and returns them
372 | * in a Coordinate object
373 | * @param {Element} element Element.
374 | * @return {!goog.math.Coordinate} The position.
375 | */
376 | goog.style.getPosition = function(element) {
377 | return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
378 | };
379 |
380 |
381 | /**
382 | * Returns the viewport element for a particular document
383 | * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
384 | * of.
385 | * @return {Element} document.documentElement or document.body.
386 | */
387 | goog.style.getClientViewportElement = function(opt_node) {
388 | var doc;
389 | if (opt_node) {
390 | doc = goog.dom.getOwnerDocument(opt_node);
391 | } else {
392 | doc = goog.dom.getDocument();
393 | }
394 |
395 | // In old IE versions the document.body represented the viewport
396 | if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
397 | !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
398 | return doc.body;
399 | }
400 | return doc.documentElement;
401 | };
402 |
403 |
404 | /**
405 | * Calculates the viewport coordinates relative to the page/document
406 | * containing the node. The viewport may be the browser viewport for
407 | * non-iframe document, or the iframe container for iframe'd document.
408 | * @param {!Document} doc The document to use as the reference point.
409 | * @return {!goog.math.Coordinate} The page offset of the viewport.
410 | */
411 | goog.style.getViewportPageOffset = function(doc) {
412 | var body = doc.body;
413 | var documentElement = doc.documentElement;
414 | var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
415 | var scrollTop = body.scrollTop || documentElement.scrollTop;
416 | return new goog.math.Coordinate(scrollLeft, scrollTop);
417 | };
418 |
419 |
420 | /**
421 | * Gets the client rectangle of the DOM element.
422 | *
423 | * getBoundingClientRect is part of a new CSS object model draft (with a
424 | * long-time presence in IE), replacing the error-prone parent offset
425 | * computation and the now-deprecated Gecko getBoxObjectFor.
426 | *
427 | * This utility patches common browser bugs in getBoundingClientRect. It
428 | * will fail if getBoundingClientRect is unsupported.
429 | *
430 | * If the element is not in the DOM, the result is undefined, and an error may
431 | * be thrown depending on user agent.
432 | *
433 | * @param {!Element} el The element whose bounding rectangle is being queried.
434 | * @return {Object} A native bounding rectangle with numerical left, top,
435 | * right, and bottom. Reported by Firefox to be of object type ClientRect.
436 | * @private
437 | */
438 | goog.style.getBoundingClientRect_ = function(el) {
439 | var rect;
440 | try {
441 | rect = el.getBoundingClientRect();
442 | } catch (e) {
443 | // In IE < 9, calling getBoundingClientRect on an orphan element raises an
444 | // "Unspecified Error". All other browsers return zeros.
445 | return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
446 | }
447 |
448 | // Patch the result in IE only, so that this function can be inlined if
449 | // compiled for non-IE.
450 | if (goog.userAgent.IE && el.ownerDocument.body) {
451 |
452 | // In IE, most of the time, 2 extra pixels are added to the top and left
453 | // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and
454 | // IE6 standards mode, this border can be overridden by setting the
455 | // document element's border to zero -- thus, we cannot rely on the
456 | // offset always being 2 pixels.
457 |
458 | // In quirks mode, the offset can be determined by querying the body's
459 | // clientLeft/clientTop, but in standards mode, it is found by querying
460 | // the document element's clientLeft/clientTop. Since we already called
461 | // getBoundingClientRect we have already forced a reflow, so it is not
462 | // too expensive just to query them all.
463 |
464 | // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
465 | var doc = el.ownerDocument;
466 | rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
467 | rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
468 | }
469 | return /** @type {Object} */ (rect);
470 | };
471 |
472 |
473 | /**
474 | * Returns the first parent that could affect the position of a given element.
475 | * @param {Element} element The element to get the offset parent for.
476 | * @return {Element} The first offset parent or null if one cannot be found.
477 | */
478 | goog.style.getOffsetParent = function(element) {
479 | // element.offsetParent does the right thing in IE7 and below. In other
480 | // browsers it only includes elements with position absolute, relative or
481 | // fixed, not elements with overflow set to auto or scroll.
482 | if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
483 | return element.offsetParent;
484 | }
485 |
486 | var doc = goog.dom.getOwnerDocument(element);
487 | var positionStyle = goog.style.getStyle_(element, 'position');
488 | var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
489 | for (var parent = element.parentNode; parent && parent != doc;
490 | parent = parent.parentNode) {
491 | positionStyle =
492 | goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
493 | skipStatic = skipStatic && positionStyle == 'static' &&
494 | parent != doc.documentElement && parent != doc.body;
495 | if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
496 | parent.scrollHeight > parent.clientHeight ||
497 | positionStyle == 'fixed' ||
498 | positionStyle == 'absolute' ||
499 | positionStyle == 'relative')) {
500 | return /** @type {!Element} */ (parent);
501 | }
502 | }
503 | return null;
504 | };
505 |
506 |
507 | /**
508 | * Calculates and returns the visible rectangle for a given element. Returns a
509 | * box describing the visible portion of the nearest scrollable offset ancestor.
510 | * Coordinates are given relative to the document.
511 | *
512 | * @param {Element} element Element to get the visible rect for.
513 | * @return {goog.math.Box} Bounding elementBox describing the visible rect or
514 | * null if scrollable ancestor isn't inside the visible viewport.
515 | */
516 | goog.style.getVisibleRectForElement = function(element) {
517 | var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
518 | var dom = goog.dom.getDomHelper(element);
519 | var body = dom.getDocument().body;
520 | var documentElement = dom.getDocument().documentElement;
521 | var scrollEl = dom.getDocumentScrollElement();
522 |
523 | // Determine the size of the visible rect by climbing the dom accounting for
524 | // all scrollable containers.
525 | for (var el = element; el = goog.style.getOffsetParent(el); ) {
526 | // clientWidth is zero for inline block elements in IE.
527 | // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
528 | if ((!goog.userAgent.IE || el.clientWidth != 0) &&
529 | (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
530 | // body may have overflow set on it, yet we still get the entire
531 | // viewport. In some browsers, el.offsetParent may be
532 | // document.documentElement, so check for that too.
533 | (el != body && el != documentElement &&
534 | goog.style.getStyle_(el, 'overflow') != 'visible')) {
535 | var pos = goog.style.getPageOffset(el);
536 | var client = goog.style.getClientLeftTop(el);
537 | pos.x += client.x;
538 | pos.y += client.y;
539 |
540 | visibleRect.top = Math.max(visibleRect.top, pos.y);
541 | visibleRect.right = Math.min(visibleRect.right,
542 | pos.x + el.clientWidth);
543 | visibleRect.bottom = Math.min(visibleRect.bottom,
544 | pos.y + el.clientHeight);
545 | visibleRect.left = Math.max(visibleRect.left, pos.x);
546 | }
547 | }
548 |
549 | // Clip by window's viewport.
550 | var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
551 | visibleRect.left = Math.max(visibleRect.left, scrollX);
552 | visibleRect.top = Math.max(visibleRect.top, scrollY);
553 | var winSize = dom.getViewportSize();
554 | visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
555 | visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
556 | return visibleRect.top >= 0 && visibleRect.left >= 0 &&
557 | visibleRect.bottom > visibleRect.top &&
558 | visibleRect.right > visibleRect.left ?
559 | visibleRect : null;
560 | };
561 |
562 |
563 | /**
564 | * Calculate the scroll position of {@code container} with the minimum amount so
565 | * that the content and the borders of the given {@code element} become visible.
566 | * If the element is bigger than the container, its top left corner will be
567 | * aligned as close to the container's top left corner as possible.
568 | *
569 | * @param {Element} element The element to make visible.
570 | * @param {Element} container The container to scroll.
571 | * @param {boolean=} opt_center Whether to center the element in the container.
572 | * Defaults to false.
573 | * @return {!goog.math.Coordinate} The new scroll position of the container,
574 | * in form of goog.math.Coordinate(scrollLeft, scrollTop).
575 | */
576 | goog.style.getContainerOffsetToScrollInto =
577 | function(element, container, opt_center) {
578 | // Absolute position of the element's border's top left corner.
579 | var elementPos = goog.style.getPageOffset(element);
580 | // Absolute position of the container's border's top left corner.
581 | var containerPos = goog.style.getPageOffset(container);
582 | var containerBorder = goog.style.getBorderBox(container);
583 | // Relative pos. of the element's border box to the container's content box.
584 | var relX = elementPos.x - containerPos.x - containerBorder.left;
585 | var relY = elementPos.y - containerPos.y - containerBorder.top;
586 | // How much the element can move in the container, i.e. the difference between
587 | // the element's bottom-right-most and top-left-most position where it's
588 | // fully visible.
589 | var spaceX = container.clientWidth - element.offsetWidth;
590 | var spaceY = container.clientHeight - element.offsetHeight;
591 |
592 | var scrollLeft = container.scrollLeft;
593 | var scrollTop = container.scrollTop;
594 | if (opt_center) {
595 | // All browsers round non-integer scroll positions down.
596 | scrollLeft += relX - spaceX / 2;
597 | scrollTop += relY - spaceY / 2;
598 | } else {
599 | // This formula was designed to give the correct scroll values in the
600 | // following cases:
601 | // - element is higher than container (spaceY < 0) => scroll down by relY
602 | // - element is not higher that container (spaceY >= 0):
603 | // - it is above container (relY < 0) => scroll up by abs(relY)
604 | // - it is below container (relY > spaceY) => scroll down by relY - spaceY
605 | // - it is in the container => don't scroll
606 | scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
607 | scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
608 | }
609 | return new goog.math.Coordinate(scrollLeft, scrollTop);
610 | };
611 |
612 |
613 | /**
614 | * Changes the scroll position of {@code container} with the minimum amount so
615 | * that the content and the borders of the given {@code element} become visible.
616 | * If the element is bigger than the container, its top left corner will be
617 | * aligned as close to the container's top left corner as possible.
618 | *
619 | * @param {Element} element The element to make visible.
620 | * @param {Element} container The container to scroll.
621 | * @param {boolean=} opt_center Whether to center the element in the container.
622 | * Defaults to false.
623 | */
624 | goog.style.scrollIntoContainerView = function(element, container, opt_center) {
625 | var offset =
626 | goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
627 | container.scrollLeft = offset.x;
628 | container.scrollTop = offset.y;
629 | };
630 |
631 |
632 | /**
633 | * Returns clientLeft (width of the left border and, if the directionality is
634 | * right to left, the vertical scrollbar) and clientTop as a coordinate object.
635 | *
636 | * @param {Element} el Element to get clientLeft for.
637 | * @return {!goog.math.Coordinate} Client left and top.
638 | */
639 | goog.style.getClientLeftTop = function(el) {
640 | // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see
641 | // https://bugzilla.mozilla.org/show_bug.cgi?id=111207
642 | if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) {
643 | var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth'));
644 | if (goog.style.isRightToLeft(el)) {
645 | var scrollbarWidth = el.offsetWidth - el.clientWidth - left -
646 | parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth'));
647 | left += scrollbarWidth;
648 | }
649 | return new goog.math.Coordinate(left,
650 | parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth')));
651 | }
652 |
653 | return new goog.math.Coordinate(el.clientLeft, el.clientTop);
654 | };
655 |
656 |
657 | /**
658 | * Returns a Coordinate object relative to the top-left of the HTML document.
659 | * Implemented as a single function to save having to do two recursive loops in
660 | * opera and safari just to get both coordinates. If you just want one value do
661 | * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
662 | * note if you call both those methods the tree will be analysed twice.
663 | *
664 | * @param {Element} el Element to get the page offset for.
665 | * @return {!goog.math.Coordinate} The page offset.
666 | */
667 | goog.style.getPageOffset = function(el) {
668 | var box, doc = goog.dom.getOwnerDocument(el);
669 | var positionStyle = goog.style.getStyle_(el, 'position');
670 | // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
671 | goog.asserts.assertObject(el, 'Parameter is required');
672 |
673 | // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the
674 | // position. When invoked for an element with position absolute and a negative
675 | // position though it can be off by one. Therefor the recursive implementation
676 | // is used in those (relatively rare) cases.
677 | var BUGGY_GECKO_BOX_OBJECT =
678 | !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS &&
679 | goog.userAgent.GECKO && doc.getBoxObjectFor &&
680 | !el.getBoundingClientRect && positionStyle == 'absolute' &&
681 | (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0);
682 |
683 | // NOTE(arv): If element is hidden (display none or disconnected or any the
684 | // ancestors are hidden) we get (0,0) by default but we still do the
685 | // accumulation of scroll position.
686 |
687 | // TODO(arv): Should we check if the node is disconnected and in that case
688 | // return (0,0)?
689 |
690 | var pos = new goog.math.Coordinate(0, 0);
691 | var viewportElement = goog.style.getClientViewportElement(doc);
692 | if (el == viewportElement) {
693 | // viewport is always at 0,0 as that defined the coordinate system for this
694 | // function - this avoids special case checks in the code below
695 | return pos;
696 | }
697 |
698 | // IE, Gecko 1.9+, and most modern WebKit.
699 | if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
700 | el.getBoundingClientRect) {
701 | box = goog.style.getBoundingClientRect_(el);
702 | // Must add the scroll coordinates in to get the absolute page offset
703 | // of element since getBoundingClientRect returns relative coordinates to
704 | // the viewport.
705 | var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
706 | pos.x = box.left + scrollCoord.x;
707 | pos.y = box.top + scrollCoord.y;
708 |
709 | // Gecko prior to 1.9.
710 | } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) {
711 | // Gecko ignores the scroll values for ancestors, up to 1.9. See:
712 | // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
713 | // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
714 |
715 | box = doc.getBoxObjectFor(el);
716 | // TODO(user): Fix the off-by-one error when window is scrolled down
717 | // or right more than 1 pixel. The viewport offset does not move in lock
718 | // step with the window scroll; it moves in increments of 2px and at
719 | // somewhat random intervals.
720 | var vpBox = doc.getBoxObjectFor(viewportElement);
721 | pos.x = box.screenX - vpBox.screenX;
722 | pos.y = box.screenY - vpBox.screenY;
723 |
724 | // Safari, Opera and Camino up to 1.0.4.
725 | } else {
726 | var parent = el;
727 | do {
728 | pos.x += parent.offsetLeft;
729 | pos.y += parent.offsetTop;
730 | // For safari/chrome, we need to add parent's clientLeft/Top as well.
731 | if (parent != el) {
732 | pos.x += parent.clientLeft || 0;
733 | pos.y += parent.clientTop || 0;
734 | }
735 | // In Safari when hit a position fixed element the rest of the offsets
736 | // are not correct.
737 | if (goog.userAgent.WEBKIT &&
738 | goog.style.getComputedPosition(parent) == 'fixed') {
739 | pos.x += doc.body.scrollLeft;
740 | pos.y += doc.body.scrollTop;
741 | break;
742 | }
743 | parent = parent.offsetParent;
744 | } while (parent && parent != el);
745 |
746 | // Opera & (safari absolute) incorrectly account for body offsetTop.
747 | if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT &&
748 | positionStyle == 'absolute')) {
749 | pos.y -= doc.body.offsetTop;
750 | }
751 |
752 | for (parent = el; (parent = goog.style.getOffsetParent(parent)) &&
753 | parent != doc.body && parent != viewportElement; ) {
754 | pos.x -= parent.scrollLeft;
755 | // Workaround for a bug in Opera 9.2 (and earlier) where table rows may
756 | // report an invalid scroll top value. The bug was fixed in Opera 9.5
757 | // however as that version supports getBoundingClientRect it won't
758 | // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965
759 | if (!goog.userAgent.OPERA || parent.tagName != 'TR') {
760 | pos.y -= parent.scrollTop;
761 | }
762 | }
763 | }
764 |
765 | return pos;
766 | };
767 |
768 |
769 | /**
770 | * Returns the left coordinate of an element relative to the HTML document
771 | * @param {Element} el Elements.
772 | * @return {number} The left coordinate.
773 | */
774 | goog.style.getPageOffsetLeft = function(el) {
775 | return goog.style.getPageOffset(el).x;
776 | };
777 |
778 |
779 | /**
780 | * Returns the top coordinate of an element relative to the HTML document
781 | * @param {Element} el Elements.
782 | * @return {number} The top coordinate.
783 | */
784 | goog.style.getPageOffsetTop = function(el) {
785 | return goog.style.getPageOffset(el).y;
786 | };
787 |
788 |
789 | /**
790 | * Returns a Coordinate object relative to the top-left of an HTML document
791 | * in an ancestor frame of this element. Used for measuring the position of
792 | * an element inside a frame relative to a containing frame.
793 | *
794 | * @param {Element} el Element to get the page offset for.
795 | * @param {Window} relativeWin The window to measure relative to. If relativeWin
796 | * is not in the ancestor frame chain of the element, we measure relative to
797 | * the top-most window.
798 | * @return {!goog.math.Coordinate} The page offset.
799 | */
800 | goog.style.getFramedPageOffset = function(el, relativeWin) {
801 | var position = new goog.math.Coordinate(0, 0);
802 |
803 | // Iterate up the ancestor frame chain, keeping track of the current window
804 | // and the current element in that window.
805 | var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
806 | var currentEl = el;
807 | do {
808 | // if we're at the top window, we want to get the page offset.
809 | // if we're at an inner frame, we only want to get the window position
810 | // so that we can determine the actual page offset in the context of
811 | // the outer window.
812 | var offset = currentWin == relativeWin ?
813 | goog.style.getPageOffset(currentEl) :
814 | goog.style.getClientPositionForElement_(
815 | goog.asserts.assert(currentEl));
816 |
817 | position.x += offset.x;
818 | position.y += offset.y;
819 | } while (currentWin && currentWin != relativeWin &&
820 | (currentEl = currentWin.frameElement) &&
821 | (currentWin = currentWin.parent));
822 |
823 | return position;
824 | };
825 |
826 |
827 | /**
828 | * Translates the specified rect relative to origBase page, for newBase page.
829 | * If origBase and newBase are the same, this function does nothing.
830 | *
831 | * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
832 | * and it will have the translated result.
833 | * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
834 | * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
835 | * coordinate. This must be a DOM for an ancestor frame of origBase
836 | * or the same as origBase.
837 | */
838 | goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
839 | if (origBase.getDocument() != newBase.getDocument()) {
840 | var body = origBase.getDocument().body;
841 | var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
842 |
843 | // Adjust Body's margin.
844 | pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
845 |
846 | if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
847 | !origBase.isCss1CompatMode()) {
848 | pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
849 | }
850 |
851 | rect.left += pos.x;
852 | rect.top += pos.y;
853 | }
854 | };
855 |
856 |
857 | /**
858 | * Returns the position of an element relative to another element in the
859 | * document. A relative to B
860 | * @param {Element|Event|goog.events.Event} a Element or mouse event whose
861 | * position we're calculating.
862 | * @param {Element|Event|goog.events.Event} b Element or mouse event position
863 | * is relative to.
864 | * @return {!goog.math.Coordinate} The relative position.
865 | */
866 | goog.style.getRelativePosition = function(a, b) {
867 | var ap = goog.style.getClientPosition(a);
868 | var bp = goog.style.getClientPosition(b);
869 | return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
870 | };
871 |
872 |
873 | /**
874 | * Returns the position of the event or the element's border box relative to
875 | * the client viewport.
876 | * @param {!Element} el Element whose position to get.
877 | * @return {!goog.math.Coordinate} The position.
878 | * @private
879 | */
880 | goog.style.getClientPositionForElement_ = function(el) {
881 | var pos;
882 | if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
883 | el.getBoundingClientRect) {
884 | // IE, Gecko 1.9+, and most modern WebKit
885 | var box = goog.style.getBoundingClientRect_(el);
886 | pos = new goog.math.Coordinate(box.left, box.top);
887 | } else {
888 | var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll();
889 | var pageCoord = goog.style.getPageOffset(el);
890 | pos = new goog.math.Coordinate(
891 | pageCoord.x - scrollCoord.x,
892 | pageCoord.y - scrollCoord.y);
893 | }
894 |
895 | // Gecko below version 12 doesn't add CSS translation to the client position
896 | // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do
897 | // so manually.
898 | if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) {
899 | return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el));
900 | } else {
901 | return pos;
902 | }
903 | };
904 |
905 |
906 | /**
907 | * Returns the position of the event or the element's border box relative to
908 | * the client viewport.
909 | * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
910 | * @return {!goog.math.Coordinate} The position.
911 | */
912 | goog.style.getClientPosition = function(el) {
913 | goog.asserts.assert(el);
914 | if (el.nodeType == goog.dom.NodeType.ELEMENT) {
915 | return goog.style.getClientPositionForElement_(
916 | /** @type {!Element} */ (el));
917 | } else {
918 | var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
919 | var be = /** @type {!goog.events.BrowserEvent} */ (el);
920 | var targetEvent = el;
921 |
922 | if (el.targetTouches && el.targetTouches.length) {
923 | targetEvent = el.targetTouches[0];
924 | } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches &&
925 | be.getBrowserEvent().targetTouches.length) {
926 | targetEvent = be.getBrowserEvent().targetTouches[0];
927 | }
928 |
929 | return new goog.math.Coordinate(
930 | targetEvent.clientX,
931 | targetEvent.clientY);
932 | }
933 | };
934 |
935 |
936 | /**
937 | * Moves an element to the given coordinates relative to the client viewport.
938 | * @param {Element} el Absolutely positioned element to set page offset for.
939 | * It must be in the document.
940 | * @param {number|goog.math.Coordinate} x Left position of the element's margin
941 | * box or a coordinate object.
942 | * @param {number=} opt_y Top position of the element's margin box.
943 | */
944 | goog.style.setPageOffset = function(el, x, opt_y) {
945 | // Get current pageoffset
946 | var cur = goog.style.getPageOffset(el);
947 |
948 | if (x instanceof goog.math.Coordinate) {
949 | opt_y = x.y;
950 | x = x.x;
951 | }
952 |
953 | // NOTE(arv): We cannot allow strings for x and y. We could but that would
954 | // require us to manually transform between different units
955 |
956 | // Work out deltas
957 | var dx = x - cur.x;
958 | var dy = opt_y - cur.y;
959 |
960 | // Set position to current left/top + delta
961 | goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
962 | };
963 |
964 |
965 | /**
966 | * Sets the width/height values of an element. If an argument is numeric,
967 | * or a goog.math.Size is passed, it is assumed to be pixels and will add
968 | * 'px' after converting it to an integer in string form. (This just sets the
969 | * CSS width and height properties so it might set content-box or border-box
970 | * size depending on the box model the browser is using.)
971 | *
972 | * @param {Element} element Element to set the size of.
973 | * @param {string|number|goog.math.Size} w Width of the element, or a
974 | * size object.
975 | * @param {string|number=} opt_h Height of the element. Required if w is not a
976 | * size object.
977 | */
978 | goog.style.setSize = function(element, w, opt_h) {
979 | var h;
980 | if (w instanceof goog.math.Size) {
981 | h = w.height;
982 | w = w.width;
983 | } else {
984 | if (opt_h == undefined) {
985 | throw Error('missing height argument');
986 | }
987 | h = opt_h;
988 | }
989 |
990 | goog.style.setWidth(element, /** @type {string|number} */ (w));
991 | goog.style.setHeight(element, /** @type {string|number} */ (h));
992 | };
993 |
994 |
995 | /**
996 | * Helper function to create a string to be set into a pixel-value style
997 | * property of an element. Can round to the nearest integer value.
998 | *
999 | * @param {string|number} value The style value to be used. If a number,
1000 | * 'px' will be appended, otherwise the value will be applied directly.
1001 | * @param {boolean} round Whether to round the nearest integer (if property
1002 | * is a number).
1003 | * @return {string} The string value for the property.
1004 | * @private
1005 | */
1006 | goog.style.getPixelStyleValue_ = function(value, round) {
1007 | if (typeof value == 'number') {
1008 | value = (round ? Math.round(value) : value) + 'px';
1009 | }
1010 |
1011 | return value;
1012 | };
1013 |
1014 |
1015 | /**
1016 | * Set the height of an element. Sets the element's style property.
1017 | * @param {Element} element Element to set the height of.
1018 | * @param {string|number} height The height value to set. If a number, 'px'
1019 | * will be appended, otherwise the value will be applied directly.
1020 | */
1021 | goog.style.setHeight = function(element, height) {
1022 | element.style.height = goog.style.getPixelStyleValue_(height, true);
1023 | };
1024 |
1025 |
1026 | /**
1027 | * Set the width of an element. Sets the element's style property.
1028 | * @param {Element} element Element to set the width of.
1029 | * @param {string|number} width The width value to set. If a number, 'px'
1030 | * will be appended, otherwise the value will be applied directly.
1031 | */
1032 | goog.style.setWidth = function(element, width) {
1033 | element.style.width = goog.style.getPixelStyleValue_(width, true);
1034 | };
1035 |
1036 |
1037 | /**
1038 | * Gets the height and width of an element, even if its display is none.
1039 | *
1040 | * Specifically, this returns the height and width of the border box,
1041 | * irrespective of the box model in effect.
1042 | *
1043 | * Note that this function does not take CSS transforms into account. Please see
1044 | * {@code goog.style.getTransformedSize}.
1045 | * @param {Element} element Element to get size of.
1046 | * @return {!goog.math.Size} Object with width/height properties.
1047 | */
1048 | goog.style.getSize = function(element) {
1049 | return goog.style.evaluateWithTemporaryDisplay_(
1050 | goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
1051 | };
1052 |
1053 |
1054 | /**
1055 | * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
1056 | * accurate when it's passed to {@code fn}.
1057 | * @param {function(!Element): T} fn Function to call with {@code element} as
1058 | * an argument after temporarily changing {@code element}'s display such
1059 | * that its dimensions are accurate.
1060 | * @param {!Element} element Element (which may have display none) to use as
1061 | * argument to {@code fn}.
1062 | * @return {T} Value returned by calling {@code fn} with {@code element}.
1063 | * @template T
1064 | * @private
1065 | */
1066 | goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
1067 | if (goog.style.getStyle_(element, 'display') != 'none') {
1068 | return fn(element);
1069 | }
1070 |
1071 | var style = element.style;
1072 | var originalDisplay = style.display;
1073 | var originalVisibility = style.visibility;
1074 | var originalPosition = style.position;
1075 |
1076 | style.visibility = 'hidden';
1077 | style.position = 'absolute';
1078 | style.display = 'inline';
1079 |
1080 | var retVal = fn(element);
1081 |
1082 | style.display = originalDisplay;
1083 | style.position = originalPosition;
1084 | style.visibility = originalVisibility;
1085 |
1086 | return retVal;
1087 | };
1088 |
1089 |
1090 | /**
1091 | * Gets the height and width of an element when the display is not none.
1092 | * @param {Element} element Element to get size of.
1093 | * @return {!goog.math.Size} Object with width/height properties.
1094 | * @private
1095 | */
1096 | goog.style.getSizeWithDisplay_ = function(element) {
1097 | var offsetWidth = element.offsetWidth;
1098 | var offsetHeight = element.offsetHeight;
1099 | var webkitOffsetsZero =
1100 | goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
1101 | if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
1102 | element.getBoundingClientRect) {
1103 | // Fall back to calling getBoundingClientRect when offsetWidth or
1104 | // offsetHeight are not defined, or when they are zero in WebKit browsers.
1105 | // This makes sure that we return for the correct size for SVG elements, but
1106 | // will still return 0 on Webkit prior to 534.8, see
1107 | // http://trac.webkit.org/changeset/67252.
1108 | var clientRect = goog.style.getBoundingClientRect_(element);
1109 | return new goog.math.Size(clientRect.right - clientRect.left,
1110 | clientRect.bottom - clientRect.top);
1111 | }
1112 | return new goog.math.Size(offsetWidth, offsetHeight);
1113 | };
1114 |
1115 |
1116 | /**
1117 | * Gets the height and width of an element, post transform, even if its display
1118 | * is none.
1119 | *
1120 | * This is like {@code goog.style.getSize}, except:
1121 | * <ol>
1122 | * <li>Takes webkitTransforms such as rotate and scale into account.
1123 | * <li>Will return null if {@code element} doesn't respond to
1124 | * {@code getBoundingClientRect}.
1125 | * <li>Currently doesn't make sense on non-WebKit browsers which don't support
1126 | * webkitTransforms.
1127 | * </ol>
1128 | * @param {!Element} element Element to get size of.
1129 | * @return {goog.math.Size} Object with width/height properties.
1130 | */
1131 | goog.style.getTransformedSize = function(element) {
1132 | if (!element.getBoundingClientRect) {
1133 | return null;
1134 | }
1135 |
1136 | var clientRect = goog.style.evaluateWithTemporaryDisplay_(
1137 | goog.style.getBoundingClientRect_, element);
1138 | return new goog.math.Size(clientRect.right - clientRect.left,
1139 | clientRect.bottom - clientRect.top);
1140 | };
1141 |
1142 |
1143 | /**
1144 | * Returns a bounding rectangle for a given element in page space.
1145 | * @param {Element} element Element to get bounds of. Must not be display none.
1146 | * @return {!goog.math.Rect} Bounding rectangle for the element.
1147 | */
1148 | goog.style.getBounds = function(element) {
1149 | var o = goog.style.getPageOffset(element);
1150 | var s = goog.style.getSize(element);
1151 | return new goog.math.Rect(o.x, o.y, s.width, s.height);
1152 | };
1153 |
1154 |
1155 | /**
1156 | * Converts a CSS selector in the form style-property to styleProperty.
1157 | * @param {*} selector CSS Selector.
1158 | * @return {string} Camel case selector.
1159 | * @deprecated Use goog.string.toCamelCase instead.
1160 | */
1161 | goog.style.toCamelCase = function(selector) {
1162 | return goog.string.toCamelCase(String(selector));
1163 | };
1164 |
1165 |
1166 | /**
1167 | * Converts a CSS selector in the form styleProperty to style-property.
1168 | * @param {string} selector Camel case selector.
1169 | * @return {string} Selector cased.
1170 | * @deprecated Use goog.string.toSelectorCase instead.
1171 | */
1172 | goog.style.toSelectorCase = function(selector) {
1173 | return goog.string.toSelectorCase(selector);
1174 | };
1175 |
1176 |
1177 | /**
1178 | * Gets the opacity of a node (x-browser). This gets the inline style opacity
1179 | * of the node, and does not take into account the cascaded or the computed
1180 | * style for this node.
1181 | * @param {Element} el Element whose opacity has to be found.
1182 | * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
1183 | * if the opacity is not set.
1184 | */
1185 | goog.style.getOpacity = function(el) {
1186 | var style = el.style;
1187 | var result = '';
1188 | if ('opacity' in style) {
1189 | result = style.opacity;
1190 | } else if ('MozOpacity' in style) {
1191 | result = style.MozOpacity;
1192 | } else if ('filter' in style) {
1193 | var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
1194 | if (match) {
1195 | result = String(match[1] / 100);
1196 | }
1197 | }
1198 | return result == '' ? result : Number(result);
1199 | };
1200 |
1201 |
1202 | /**
1203 | * Sets the opacity of a node (x-browser).
1204 | * @param {Element} el Elements whose opacity has to be set.
1205 | * @param {number|string} alpha Opacity between 0 and 1 or an empty string
1206 | * {@code ''} to clear the opacity.
1207 | */
1208 | goog.style.setOpacity = function(el, alpha) {
1209 | var style = el.style;
1210 | if ('opacity' in style) {
1211 | style.opacity = alpha;
1212 | } else if ('MozOpacity' in style) {
1213 | style.MozOpacity = alpha;
1214 | } else if ('filter' in style) {
1215 | // TODO(arv): Overwriting the filter might have undesired side effects.
1216 | if (alpha === '') {
1217 | style.filter = '';
1218 | } else {
1219 | style.filter = 'alpha(opacity=' + alpha * 100 + ')';
1220 | }
1221 | }
1222 | };
1223 |
1224 |
1225 | /**
1226 | * Sets the background of an element to a transparent image in a browser-
1227 | * independent manner.
1228 | *
1229 | * This function does not support repeating backgrounds or alternate background
1230 | * positions to match the behavior of Internet Explorer. It also does not
1231 | * support sizingMethods other than crop since they cannot be replicated in
1232 | * browsers other than Internet Explorer.
1233 | *
1234 | * @param {Element} el The element to set background on.
1235 | * @param {string} src The image source URL.
1236 | */
1237 | goog.style.setTransparentBackgroundImage = function(el, src) {
1238 | var style = el.style;
1239 | // It is safe to use the style.filter in IE only. In Safari 'filter' is in
1240 | // style object but access to style.filter causes it to throw an exception.
1241 | // Note: IE8 supports images with an alpha channel.
1242 | if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
1243 | // See TODO in setOpacity.
1244 | style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
1245 | 'src="' + src + '", sizingMethod="crop")';
1246 | } else {
1247 | // Set style properties individually instead of using background shorthand
1248 | // to prevent overwriting a pre-existing background color.
1249 | style.backgroundImage = 'url(' + src + ')';
1250 | style.backgroundPosition = 'top left';
1251 | style.backgroundRepeat = 'no-repeat';
1252 | }
1253 | };
1254 |
1255 |
1256 | /**
1257 | * Clears the background image of an element in a browser independent manner.
1258 | * @param {Element} el The element to clear background image for.
1259 | */
1260 | goog.style.clearTransparentBackgroundImage = function(el) {
1261 | var style = el.style;
1262 | if ('filter' in style) {
1263 | // See TODO in setOpacity.
1264 | style.filter = '';
1265 | } else {
1266 | // Set style properties individually instead of using background shorthand
1267 | // to prevent overwriting a pre-existing background color.
1268 | style.backgroundImage = 'none';
1269 | }
1270 | };
1271 |
1272 |
1273 | /**
1274 | * Shows or hides an element from the page. Hiding the element is done by
1275 | * setting the display property to "none", removing the element from the
1276 | * rendering hierarchy so it takes up no space. To show the element, the default
1277 | * inherited display property is restored (defined either in stylesheets or by
1278 | * the browser's default style rules.)
1279 | *
1280 | * Caveat 1: if the inherited display property for the element is set to "none"
1281 | * by the stylesheets, that is the property that will be restored by a call to
1282 | * showElement(), effectively toggling the display between "none" and "none".
1283 | *
1284 | * Caveat 2: if the element display style is set inline (by setting either
1285 | * element.style.display or a style attribute in the HTML), a call to
1286 | * showElement will clear that setting and defer to the inherited style in the
1287 | * stylesheet.
1288 | * @param {Element} el Element to show or hide.
1289 | * @param {*} display True to render the element in its default style,
1290 | * false to disable rendering the element.
1291 | * @deprecated Use goog.style.setElementShown instead.
1292 | */
1293 | goog.style.showElement = function(el, display) {
1294 | goog.style.setElementShown(el, display);
1295 | };
1296 |
1297 |
1298 | /**
1299 | * Shows or hides an element from the page. Hiding the element is done by
1300 | * setting the display property to "none", removing the element from the
1301 | * rendering hierarchy so it takes up no space. To show the element, the default
1302 | * inherited display property is restored (defined either in stylesheets or by
1303 | * the browser's default style rules).
1304 | *
1305 | * Caveat 1: if the inherited display property for the element is set to "none"
1306 | * by the stylesheets, that is the property that will be restored by a call to
1307 | * setElementShown(), effectively toggling the display between "none" and
1308 | * "none".
1309 | *
1310 | * Caveat 2: if the element display style is set inline (by setting either
1311 | * element.style.display or a style attribute in the HTML), a call to
1312 | * setElementShown will clear that setting and defer to the inherited style in
1313 | * the stylesheet.
1314 | * @param {Element} el Element to show or hide.
1315 | * @param {*} isShown True to render the element in its default style,
1316 | * false to disable rendering the element.
1317 | */
1318 | goog.style.setElementShown = function(el, isShown) {
1319 | el.style.display = isShown ? '' : 'none';
1320 | };
1321 |
1322 |
1323 | /**
1324 | * Test whether the given element has been shown or hidden via a call to
1325 | * {@link #setElementShown}.
1326 | *
1327 | * Note this is strictly a companion method for a call
1328 | * to {@link #setElementShown} and the same caveats apply; in particular, this
1329 | * method does not guarantee that the return value will be consistent with
1330 | * whether or not the element is actually visible.
1331 | *
1332 | * @param {Element} el The element to test.
1333 | * @return {boolean} Whether the element has been shown.
1334 | * @see #setElementShown
1335 | */
1336 | goog.style.isElementShown = function(el) {
1337 | return el.style.display != 'none';
1338 | };
1339 |
1340 |
1341 | /**
1342 | * Installs the styles string into the window that contains opt_element. If
1343 | * opt_element is null, the main window is used.
1344 | * @param {string} stylesString The style string to install.
1345 | * @param {Node=} opt_node Node whose parent document should have the
1346 | * styles installed.
1347 | * @return {Element|StyleSheet} The style element created.
1348 | */
1349 | goog.style.installStyles = function(stylesString, opt_node) {
1350 | var dh = goog.dom.getDomHelper(opt_node);
1351 | var styleSheet = null;
1352 |
1353 | // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
1354 | // undefined as of IE 11.
1355 | var doc = dh.getDocument();
1356 | if (goog.userAgent.IE && doc.createStyleSheet) {
1357 | styleSheet = doc.createStyleSheet();
1358 | goog.style.setStyles(styleSheet, stylesString);
1359 | } else {
1360 | var head = dh.getElementsByTagNameAndClass('head')[0];
1361 |
1362 | // In opera documents are not guaranteed to have a head element, thus we
1363 | // have to make sure one exists before using it.
1364 | if (!head) {
1365 | var body = dh.getElementsByTagNameAndClass('body')[0];
1366 | head = dh.createDom('head');
1367 | body.parentNode.insertBefore(head, body);
1368 | }
1369 | styleSheet = dh.createDom('style');
1370 | // NOTE(user): Setting styles after the style element has been appended
1371 | // to the head results in a nasty Webkit bug in certain scenarios. Please
1372 | // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
1373 | // details.
1374 | goog.style.setStyles(styleSheet, stylesString);
1375 | dh.appendChild(head, styleSheet);
1376 | }
1377 | return styleSheet;
1378 | };
1379 |
1380 |
1381 | /**
1382 | * Removes the styles added by {@link #installStyles}.
1383 | * @param {Element|StyleSheet} styleSheet The value returned by
1384 | * {@link #installStyles}.
1385 | */
1386 | goog.style.uninstallStyles = function(styleSheet) {
1387 | var node = styleSheet.ownerNode || styleSheet.owningElement ||
1388 | /** @type {Element} */ (styleSheet);
1389 | goog.dom.removeNode(node);
1390 | };
1391 |
1392 |
1393 | /**
1394 | * Sets the content of a style element. The style element can be any valid
1395 | * style element. This element will have its content completely replaced by
1396 | * the new stylesString.
1397 | * @param {Element|StyleSheet} element A stylesheet element as returned by
1398 | * installStyles.
1399 | * @param {string} stylesString The new content of the stylesheet.
1400 | */
1401 | goog.style.setStyles = function(element, stylesString) {
1402 | if (goog.userAgent.IE && goog.isDef(element.cssText)) {
1403 | // Adding the selectors individually caused the browser to hang if the
1404 | // selector was invalid or there were CSS comments. Setting the cssText of
1405 | // the style node works fine and ignores CSS that IE doesn't understand.
1406 | // However IE >= 11 doesn't support cssText any more, so we make sure that
1407 | // cssText is a defined property and otherwise fall back to innerHTML.
1408 | element.cssText = stylesString;
1409 | } else {
1410 | element.innerHTML = stylesString;
1411 | }
1412 | };
1413 |
1414 |
1415 | /**
1416 | * Sets 'white-space: pre-wrap' for a node (x-browser).
1417 | *
1418 | * There are as many ways of specifying pre-wrap as there are browsers.
1419 | *
1420 | * CSS3/IE8: white-space: pre-wrap;
1421 | * Mozilla: white-space: -moz-pre-wrap;
1422 | * Opera: white-space: -o-pre-wrap;
1423 | * IE6/7: white-space: pre; word-wrap: break-word;
1424 | *
1425 | * @param {Element} el Element to enable pre-wrap for.
1426 | */
1427 | goog.style.setPreWrap = function(el) {
1428 | var style = el.style;
1429 | if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
1430 | style.whiteSpace = 'pre';
1431 | style.wordWrap = 'break-word';
1432 | } else if (goog.userAgent.GECKO) {
1433 | style.whiteSpace = '-moz-pre-wrap';
1434 | } else {
1435 | style.whiteSpace = 'pre-wrap';
1436 | }
1437 | };
1438 |
1439 |
1440 | /**
1441 | * Sets 'display: inline-block' for an element (cross-browser).
1442 | * @param {Element} el Element to which the inline-block display style is to be
1443 | * applied.
1444 | * @see ../demos/inline_block_quirks.html
1445 | * @see ../demos/inline_block_standards.html
1446 | */
1447 | goog.style.setInlineBlock = function(el) {
1448 | var style = el.style;
1449 | // Without position:relative, weirdness ensues. Just accept it and move on.
1450 | style.position = 'relative';
1451 |
1452 | if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
1453 | // IE8 supports inline-block so fall through to the else
1454 | // Zoom:1 forces hasLayout, display:inline gives inline behavior.
1455 | style.zoom = '1';
1456 | style.display = 'inline';
1457 | } else if (goog.userAgent.GECKO) {
1458 | // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box
1459 | // is close enough.
1460 | style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' :
1461 | '-moz-inline-box';
1462 | } else {
1463 | // Opera, Webkit, and Safari seem to do OK with the standard inline-block
1464 | // style.
1465 | style.display = 'inline-block';
1466 | }
1467 | };
1468 |
1469 |
1470 | /**
1471 | * Returns true if the element is using right to left (rtl) direction.
1472 | * @param {Element} el The element to test.
1473 | * @return {boolean} True for right to left, false for left to right.
1474 | */
1475 | goog.style.isRightToLeft = function(el) {
1476 | return 'rtl' == goog.style.getStyle_(el, 'direction');
1477 | };
1478 |
1479 |
1480 | /**
1481 | * The CSS style property corresponding to an element being
1482 | * unselectable on the current browser platform (null if none).
1483 | * Opera and IE instead use a DOM attribute 'unselectable'.
1484 | * @type {?string}
1485 | * @private
1486 | */
1487 | goog.style.unselectableStyle_ =
1488 | goog.userAgent.GECKO ? 'MozUserSelect' :
1489 | goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
1490 | null;
1491 |
1492 |
1493 | /**
1494 | * Returns true if the element is set to be unselectable, false otherwise.
1495 | * Note that on some platforms (e.g. Mozilla), even if an element isn't set
1496 | * to be unselectable, it will behave as such if any of its ancestors is
1497 | * unselectable.
1498 | * @param {Element} el Element to check.
1499 | * @return {boolean} Whether the element is set to be unselectable.
1500 | */
1501 | goog.style.isUnselectable = function(el) {
1502 | if (goog.style.unselectableStyle_) {
1503 | return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
1504 | } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
1505 | return el.getAttribute('unselectable') == 'on';
1506 | }
1507 | return false;
1508 | };
1509 |
1510 |
1511 | /**
1512 | * Makes the element and its descendants selectable or unselectable. Note
1513 | * that on some platforms (e.g. Mozilla), even if an element isn't set to
1514 | * be unselectable, it will behave as such if any of its ancestors is
1515 | * unselectable.
1516 | * @param {Element} el The element to alter.
1517 | * @param {boolean} unselectable Whether the element and its descendants
1518 | * should be made unselectable.
1519 | * @param {boolean=} opt_noRecurse Whether to only alter the element's own
1520 | * selectable state, and leave its descendants alone; defaults to false.
1521 | */
1522 | goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
1523 | // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
1524 | var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
1525 | var name = goog.style.unselectableStyle_;
1526 | if (name) {
1527 | // Add/remove the appropriate CSS style to/from the element and its
1528 | // descendants.
1529 | var value = unselectable ? 'none' : '';
1530 | el.style[name] = value;
1531 | if (descendants) {
1532 | for (var i = 0, descendant; descendant = descendants[i]; i++) {
1533 | descendant.style[name] = value;
1534 | }
1535 | }
1536 | } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
1537 | // Toggle the 'unselectable' attribute on the element and its descendants.
1538 | var value = unselectable ? 'on' : '';
1539 | el.setAttribute('unselectable', value);
1540 | if (descendants) {
1541 | for (var i = 0, descendant; descendant = descendants[i]; i++) {
1542 | descendant.setAttribute('unselectable', value);
1543 | }
1544 | }
1545 | }
1546 | };
1547 |
1548 |
1549 | /**
1550 | * Gets the border box size for an element.
1551 | * @param {Element} element The element to get the size for.
1552 | * @return {!goog.math.Size} The border box size.
1553 | */
1554 | goog.style.getBorderBoxSize = function(element) {
1555 | return new goog.math.Size(element.offsetWidth, element.offsetHeight);
1556 | };
1557 |
1558 |
1559 | /**
1560 | * Sets the border box size of an element. This is potentially expensive in IE
1561 | * if the document is CSS1Compat mode
1562 | * @param {Element} element The element to set the size on.
1563 | * @param {goog.math.Size} size The new size.
1564 | */
1565 | goog.style.setBorderBoxSize = function(element, size) {
1566 | var doc = goog.dom.getOwnerDocument(element);
1567 | var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
1568 |
1569 | if (goog.userAgent.IE &&
1570 | !goog.userAgent.isVersionOrHigher('10') &&
1571 | (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
1572 | var style = element.style;
1573 | if (isCss1CompatMode) {
1574 | var paddingBox = goog.style.getPaddingBox(element);
1575 | var borderBox = goog.style.getBorderBox(element);
1576 | style.pixelWidth = size.width - borderBox.left - paddingBox.left -
1577 | paddingBox.right - borderBox.right;
1578 | style.pixelHeight = size.height - borderBox.top - paddingBox.top -
1579 | paddingBox.bottom - borderBox.bottom;
1580 | } else {
1581 | style.pixelWidth = size.width;
1582 | style.pixelHeight = size.height;
1583 | }
1584 | } else {
1585 | goog.style.setBoxSizingSize_(element, size, 'border-box');
1586 | }
1587 | };
1588 |
1589 |
1590 | /**
1591 | * Gets the content box size for an element. This is potentially expensive in
1592 | * all browsers.
1593 | * @param {Element} element The element to get the size for.
1594 | * @return {!goog.math.Size} The content box size.
1595 | */
1596 | goog.style.getContentBoxSize = function(element) {
1597 | var doc = goog.dom.getOwnerDocument(element);
1598 | var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
1599 | if (ieCurrentStyle &&
1600 | goog.dom.getDomHelper(doc).isCss1CompatMode() &&
1601 | ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
1602 | !ieCurrentStyle.boxSizing) {
1603 | // If IE in CSS1Compat mode than just use the width and height.
1604 | // If we have a boxSizing then fall back on measuring the borders etc.
1605 | var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
1606 | 'width', 'pixelWidth');
1607 | var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
1608 | 'height', 'pixelHeight');
1609 | return new goog.math.Size(width, height);
1610 | } else {
1611 | var borderBoxSize = goog.style.getBorderBoxSize(element);
1612 | var paddingBox = goog.style.getPaddingBox(element);
1613 | var borderBox = goog.style.getBorderBox(element);
1614 | return new goog.math.Size(borderBoxSize.width -
1615 | borderBox.left - paddingBox.left -
1616 | paddingBox.right - borderBox.right,
1617 | borderBoxSize.height -
1618 | borderBox.top - paddingBox.top -
1619 | paddingBox.bottom - borderBox.bottom);
1620 | }
1621 | };
1622 |
1623 |
1624 | /**
1625 | * Sets the content box size of an element. This is potentially expensive in IE
1626 | * if the document is BackCompat mode.
1627 | * @param {Element} element The element to set the size on.
1628 | * @param {goog.math.Size} size The new size.
1629 | */
1630 | goog.style.setContentBoxSize = function(element, size) {
1631 | var doc = goog.dom.getOwnerDocument(element);
1632 | var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
1633 | if (goog.userAgent.IE &&
1634 | !goog.userAgent.isVersionOrHigher('10') &&
1635 | (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
1636 | var style = element.style;
1637 | if (isCss1CompatMode) {
1638 | style.pixelWidth = size.width;
1639 | style.pixelHeight = size.height;
1640 | } else {
1641 | var paddingBox = goog.style.getPaddingBox(element);
1642 | var borderBox = goog.style.getBorderBox(element);
1643 | style.pixelWidth = size.width + borderBox.left + paddingBox.left +
1644 | paddingBox.right + borderBox.right;
1645 | style.pixelHeight = size.height + borderBox.top + paddingBox.top +
1646 | paddingBox.bottom + borderBox.bottom;
1647 | }
1648 | } else {
1649 | goog.style.setBoxSizingSize_(element, size, 'content-box');
1650 | }
1651 | };
1652 |
1653 |
1654 | /**
1655 | * Helper function that sets the box sizing as well as the width and height
1656 | * @param {Element} element The element to set the size on.
1657 | * @param {goog.math.Size} size The new size to set.
1658 | * @param {string} boxSizing The box-sizing value.
1659 | * @private
1660 | */
1661 | goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
1662 | var style = element.style;
1663 | if (goog.userAgent.GECKO) {
1664 | style.MozBoxSizing = boxSizing;
1665 | } else if (goog.userAgent.WEBKIT) {
1666 | style.WebkitBoxSizing = boxSizing;
1667 | } else {
1668 | // Includes IE8 and Opera 9.50+
1669 | style.boxSizing = boxSizing;
1670 | }
1671 |
1672 | // Setting this to a negative value will throw an exception on IE
1673 | // (and doesn't do anything different than setting it to 0).
1674 | style.width = Math.max(size.width, 0) + 'px';
1675 | style.height = Math.max(size.height, 0) + 'px';
1676 | };
1677 |
1678 |
1679 | /**
1680 | * IE specific function that converts a non pixel unit to pixels.
1681 | * @param {Element} element The element to convert the value for.
1682 | * @param {string} value The current value as a string. The value must not be
1683 | * ''.
1684 | * @param {string} name The CSS property name to use for the converstion. This
1685 | * should be 'left', 'top', 'width' or 'height'.
1686 | * @param {string} pixelName The CSS pixel property name to use to get the
1687 | * value in pixels.
1688 | * @return {number} The value in pixels.
1689 | * @private
1690 | */
1691 | goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
1692 | // Try if we already have a pixel value. IE does not do half pixels so we
1693 | // only check if it matches a number followed by 'px'.
1694 | if (/^\d+px?$/.test(value)) {
1695 | return parseInt(value, 10);
1696 | } else {
1697 | var oldStyleValue = element.style[name];
1698 | var oldRuntimeValue = element.runtimeStyle[name];
1699 | // set runtime style to prevent changes
1700 | element.runtimeStyle[name] = element.currentStyle[name];
1701 | element.style[name] = value;
1702 | var pixelValue = element.style[pixelName];
1703 | // restore
1704 | element.style[name] = oldStyleValue;
1705 | element.runtimeStyle[name] = oldRuntimeValue;
1706 | return pixelValue;
1707 | }
1708 | };
1709 |
1710 |
1711 | /**
1712 | * Helper function for getting the pixel padding or margin for IE.
1713 | * @param {Element} element The element to get the padding for.
1714 | * @param {string} propName The property name.
1715 | * @return {number} The pixel padding.
1716 | * @private
1717 | */
1718 | goog.style.getIePixelDistance_ = function(element, propName) {
1719 | var value = goog.style.getCascadedStyle(element, propName);
1720 | return value ?
1721 | goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
1722 | };
1723 |
1724 |
1725 | /**
1726 | * Gets the computed paddings or margins (on all sides) in pixels.
1727 | * @param {Element} element The element to get the padding for.
1728 | * @param {string} stylePrefix Pass 'padding' to retrieve the padding box,
1729 | * or 'margin' to retrieve the margin box.
1730 | * @return {!goog.math.Box} The computed paddings or margins.
1731 | * @private
1732 | */
1733 | goog.style.getBox_ = function(element, stylePrefix) {
1734 | if (goog.userAgent.IE) {
1735 | var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
1736 | var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
1737 | var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
1738 | var bottom = goog.style.getIePixelDistance_(
1739 | element, stylePrefix + 'Bottom');
1740 | return new goog.math.Box(top, right, bottom, left);
1741 | } else {
1742 | // On non-IE browsers, getComputedStyle is always non-null.
1743 | var left = /** @type {string} */ (
1744 | goog.style.getComputedStyle(element, stylePrefix + 'Left'));
1745 | var right = /** @type {string} */ (
1746 | goog.style.getComputedStyle(element, stylePrefix + 'Right'));
1747 | var top = /** @type {string} */ (
1748 | goog.style.getComputedStyle(element, stylePrefix + 'Top'));
1749 | var bottom = /** @type {string} */ (
1750 | goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));
1751 |
1752 | // NOTE(arv): Gecko can return floating point numbers for the computed
1753 | // style values.
1754 | return new goog.math.Box(parseFloat(top),
1755 | parseFloat(right),
1756 | parseFloat(bottom),
1757 | parseFloat(left));
1758 | }
1759 | };
1760 |
1761 |
1762 | /**
1763 | * Gets the computed paddings (on all sides) in pixels.
1764 | * @param {Element} element The element to get the padding for.
1765 | * @return {!goog.math.Box} The computed paddings.
1766 | */
1767 | goog.style.getPaddingBox = function(element) {
1768 | return goog.style.getBox_(element, 'padding');
1769 | };
1770 |
1771 |
1772 | /**
1773 | * Gets the computed margins (on all sides) in pixels.
1774 | * @param {Element} element The element to get the margins for.
1775 | * @return {!goog.math.Box} The computed margins.
1776 | */
1777 | goog.style.getMarginBox = function(element) {
1778 | return goog.style.getBox_(element, 'margin');
1779 | };
1780 |
1781 |
1782 | /**
1783 | * A map used to map the border width keywords to a pixel width.
1784 | * @type {Object}
1785 | * @private
1786 | */
1787 | goog.style.ieBorderWidthKeywords_ = {
1788 | 'thin': 2,
1789 | 'medium': 4,
1790 | 'thick': 6
1791 | };
1792 |
1793 |
1794 | /**
1795 | * Helper function for IE to get the pixel border.
1796 | * @param {Element} element The element to get the pixel border for.
1797 | * @param {string} prop The part of the property name.
1798 | * @return {number} The value in pixels.
1799 | * @private
1800 | */
1801 | goog.style.getIePixelBorder_ = function(element, prop) {
1802 | if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
1803 | return 0;
1804 | }
1805 | var width = goog.style.getCascadedStyle(element, prop + 'Width');
1806 | if (width in goog.style.ieBorderWidthKeywords_) {
1807 | return goog.style.ieBorderWidthKeywords_[width];
1808 | }
1809 | return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
1810 | };
1811 |
1812 |
1813 | /**
1814 | * Gets the computed border widths (on all sides) in pixels
1815 | * @param {Element} element The element to get the border widths for.
1816 | * @return {!goog.math.Box} The computed border widths.
1817 | */
1818 | goog.style.getBorderBox = function(element) {
1819 | if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
1820 | var left = goog.style.getIePixelBorder_(element, 'borderLeft');
1821 | var right = goog.style.getIePixelBorder_(element, 'borderRight');
1822 | var top = goog.style.getIePixelBorder_(element, 'borderTop');
1823 | var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
1824 | return new goog.math.Box(top, right, bottom, left);
1825 | } else {
1826 | // On non-IE browsers, getComputedStyle is always non-null.
1827 | var left = /** @type {string} */ (
1828 | goog.style.getComputedStyle(element, 'borderLeftWidth'));
1829 | var right = /** @type {string} */ (
1830 | goog.style.getComputedStyle(element, 'borderRightWidth'));
1831 | var top = /** @type {string} */ (
1832 | goog.style.getComputedStyle(element, 'borderTopWidth'));
1833 | var bottom = /** @type {string} */ (
1834 | goog.style.getComputedStyle(element, 'borderBottomWidth'));
1835 |
1836 | return new goog.math.Box(parseFloat(top),
1837 | parseFloat(right),
1838 | parseFloat(bottom),
1839 | parseFloat(left));
1840 | }
1841 | };
1842 |
1843 |
1844 | /**
1845 | * Returns the font face applied to a given node. Opera and IE should return
1846 | * the font actually displayed. Firefox returns the author's most-preferred
1847 | * font (whether the browser is capable of displaying it or not.)
1848 | * @param {Element} el The element whose font family is returned.
1849 | * @return {string} The font family applied to el.
1850 | */
1851 | goog.style.getFontFamily = function(el) {
1852 | var doc = goog.dom.getOwnerDocument(el);
1853 | var font = '';
1854 | // The moveToElementText method from the TextRange only works if the element
1855 | // is attached to the owner document.
1856 | if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
1857 | var range = doc.body.createTextRange();
1858 | range.moveToElementText(el);
1859 | /** @preserveTry */
1860 | try {
1861 | font = range.queryCommandValue('FontName');
1862 | } catch (e) {
1863 | // This is a workaround for a awkward exception.
1864 | // On some IE, there is an exception coming from it.
1865 | // The error description from this exception is:
1866 | // This window has already been registered as a drop target
1867 | // This is bogus description, likely due to a bug in ie.
1868 | font = '';
1869 | }
1870 | }
1871 | if (!font) {
1872 | // Note if for some reason IE can't derive FontName with a TextRange, we
1873 | // fallback to using currentStyle
1874 | font = goog.style.getStyle_(el, 'fontFamily');
1875 | }
1876 |
1877 | // Firefox returns the applied font-family string (author's list of
1878 | // preferred fonts.) We want to return the most-preferred font, in lieu of
1879 | // the *actually* applied font.
1880 | var fontsArray = font.split(',');
1881 | if (fontsArray.length > 1) font = fontsArray[0];
1882 |
1883 | // Sanitize for x-browser consistency:
1884 | // Strip quotes because browsers aren't consistent with how they're
1885 | // applied; Opera always encloses, Firefox sometimes, and IE never.
1886 | return goog.string.stripQuotes(font, '"\'');
1887 | };
1888 |
1889 |
1890 | /**
1891 | * Regular expression used for getLengthUnits.
1892 | * @type {RegExp}
1893 | * @private
1894 | */
1895 | goog.style.lengthUnitRegex_ = /[^\d]+$/;
1896 |
1897 |
1898 | /**
1899 | * Returns the units used for a CSS length measurement.
1900 | * @param {string} value A CSS length quantity.
1901 | * @return {?string} The units of measurement.
1902 | */
1903 | goog.style.getLengthUnits = function(value) {
1904 | var units = value.match(goog.style.lengthUnitRegex_);
1905 | return units && units[0] || null;
1906 | };
1907 |
1908 |
1909 | /**
1910 | * Map of absolute CSS length units
1911 | * @type {Object}
1912 | * @private
1913 | */
1914 | goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
1915 | 'cm' : 1,
1916 | 'in' : 1,
1917 | 'mm' : 1,
1918 | 'pc' : 1,
1919 | 'pt' : 1
1920 | };
1921 |
1922 |
1923 | /**
1924 | * Map of relative CSS length units that can be accurately converted to px
1925 | * font-size values using getIePixelValue_. Only units that are defined in
1926 | * relation to a font size are convertible (%, small, etc. are not).
1927 | * @type {Object}
1928 | * @private
1929 | */
1930 | goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
1931 | 'em' : 1,
1932 | 'ex' : 1
1933 | };
1934 |
1935 |
1936 | /**
1937 | * Returns the font size, in pixels, of text in an element.
1938 | * @param {Element} el The element whose font size is returned.
1939 | * @return {number} The font size (in pixels).
1940 | */
1941 | goog.style.getFontSize = function(el) {
1942 | var fontSize = goog.style.getStyle_(el, 'fontSize');
1943 | var sizeUnits = goog.style.getLengthUnits(fontSize);
1944 | if (fontSize && 'px' == sizeUnits) {
1945 | // NOTE(user): This could be parseFloat instead, but IE doesn't return
1946 | // decimal fractions in getStyle_ and Firefox reports the fractions, but
1947 | // ignores them when rendering. Interestingly enough, when we force the
1948 | // issue and size something to e.g., 50% of 25px, the browsers round in
1949 | // opposite directions with Firefox reporting 12px and IE 13px. I punt.
1950 | return parseInt(fontSize, 10);
1951 | }
1952 |
1953 | // In IE, we can convert absolute length units to a px value using
1954 | // goog.style.getIePixelValue_. Units defined in relation to a font size
1955 | // (em, ex) are applied relative to the element's parentNode and can also
1956 | // be converted.
1957 | if (goog.userAgent.IE) {
1958 | if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
1959 | return goog.style.getIePixelValue_(el,
1960 | fontSize,
1961 | 'left',
1962 | 'pixelLeft');
1963 | } else if (el.parentNode &&
1964 | el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
1965 | sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
1966 | // Check the parent size - if it is the same it means the relative size
1967 | // value is inherited and we therefore don't want to count it twice. If
1968 | // it is different, this element either has explicit style or has a CSS
1969 | // rule applying to it.
1970 | var parentElement = /** @type {!Element} */ (el.parentNode);
1971 | var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
1972 | return goog.style.getIePixelValue_(parentElement,
1973 | fontSize == parentSize ?
1974 | '1em' : fontSize,
1975 | 'left',
1976 | 'pixelLeft');
1977 | }
1978 | }
1979 |
1980 | // Sometimes we can't cleanly find the font size (some units relative to a
1981 | // node's parent's font size are difficult: %, smaller et al), so we create
1982 | // an invisible, absolutely-positioned span sized to be the height of an 'M'
1983 | // rendered in its parent's (i.e., our target element's) font size. This is
1984 | // the definition of CSS's font size attribute.
1985 | var sizeElement = goog.dom.createDom(
1986 | 'span',
1987 | {'style': 'visibility:hidden;position:absolute;' +
1988 | 'line-height:0;padding:0;margin:0;border:0;height:1em;'});
1989 | goog.dom.appendChild(el, sizeElement);
1990 | fontSize = sizeElement.offsetHeight;
1991 | goog.dom.removeNode(sizeElement);
1992 |
1993 | return fontSize;
1994 | };
1995 |
1996 |
1997 | /**
1998 | * Parses a style attribute value. Converts CSS property names to camel case.
1999 | * @param {string} value The style attribute value.
2000 | * @return {!Object} Map of CSS properties to string values.
2001 | */
2002 | goog.style.parseStyleAttribute = function(value) {
2003 | var result = {};
2004 | goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
2005 | var keyValue = pair.split(/\s*:\s*/);
2006 | if (keyValue.length == 2) {
2007 | result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
2008 | }
2009 | });
2010 | return result;
2011 | };
2012 |
2013 |
2014 | /**
2015 | * Reverse of parseStyleAttribute; that is, takes a style object and returns the
2016 | * corresponding attribute value. Converts camel case property names to proper
2017 | * CSS selector names.
2018 | * @param {Object} obj Map of CSS properties to values.
2019 | * @return {string} The style attribute value.
2020 | */
2021 | goog.style.toStyleAttribute = function(obj) {
2022 | var buffer = [];
2023 | goog.object.forEach(obj, function(value, key) {
2024 | buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
2025 | });
2026 | return buffer.join('');
2027 | };
2028 |
2029 |
2030 | /**
2031 | * Sets CSS float property on an element.
2032 | * @param {Element} el The element to set float property on.
2033 | * @param {string} value The value of float CSS property to set on this element.
2034 | */
2035 | goog.style.setFloat = function(el, value) {
2036 | el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
2037 | };
2038 |
2039 |
2040 | /**
2041 | * Gets value of explicitly-set float CSS property on an element.
2042 | * @param {Element} el The element to get float property of.
2043 | * @return {string} The value of explicitly-set float CSS property on this
2044 | * element.
2045 | */
2046 | goog.style.getFloat = function(el) {
2047 | return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
2048 | };
2049 |
2050 |
2051 | /**
2052 | * Returns the scroll bar width (represents the width of both horizontal
2053 | * and vertical scroll).
2054 | *
2055 | * @param {string=} opt_className An optional class name (or names) to apply
2056 | * to the invisible div created to measure the scrollbar. This is necessary
2057 | * if some scrollbars are styled differently than others.
2058 | * @return {number} The scroll bar width in px.
2059 | */
2060 | goog.style.getScrollbarWidth = function(opt_className) {
2061 | // Add two hidden divs. The child div is larger than the parent and
2062 | // forces scrollbars to appear on it.
2063 | // Using overflow:scroll does not work consistently with scrollbars that
2064 | // are styled with ::-webkit-scrollbar.
2065 | var outerDiv = goog.dom.createElement('div');
2066 | if (opt_className) {
2067 | outerDiv.className = opt_className;
2068 | }
2069 | outerDiv.style.cssText = 'overflow:auto;' +
2070 | 'position:absolute;top:0;width:100px;height:100px';
2071 | var innerDiv = goog.dom.createElement('div');
2072 | goog.style.setSize(innerDiv, '200px', '200px');
2073 | outerDiv.appendChild(innerDiv);
2074 | goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
2075 | var width = outerDiv.offsetWidth - outerDiv.clientWidth;
2076 | goog.dom.removeNode(outerDiv);
2077 | return width;
2078 | };
2079 |
2080 |
2081 | /**
2082 | * Regular expression to extract x and y translation components from a CSS
2083 | * transform Matrix representation.
2084 | *
2085 | * @type {!RegExp}
2086 | * @const
2087 | * @private
2088 | */
2089 | goog.style.MATRIX_TRANSLATION_REGEX_ =
2090 | new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
2091 | '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
2092 | '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
2093 |
2094 |
2095 | /**
2096 | * Returns the x,y translation component of any CSS transforms applied to the
2097 | * element, in pixels.
2098 | *
2099 | * @param {!Element} element The element to get the translation of.
2100 | * @return {!goog.math.Coordinate} The CSS translation of the element in px.
2101 | */
2102 | goog.style.getCssTranslation = function(element) {
2103 | var transform = goog.style.getComputedTransform(element);
2104 | if (!transform) {
2105 | return new goog.math.Coordinate(0, 0);
2106 | }
2107 | var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
2108 | if (!matches) {
2109 | return new goog.math.Coordinate(0, 0);
2110 | }
2111 | return new goog.math.Coordinate(parseFloat(matches[1]),
2112 | parseFloat(matches[2]));
2113 | };