| 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 | }; |