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