OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 Polymer = {dom: 'shadow'}; |
| 6 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 7 // Use of this source code is governed by a BSD-style license that can be |
| 8 // found in the LICENSE file. |
| 9 |
| 10 /** |
| 11 * The global object. |
| 12 * @type {!Object} |
| 13 * @const |
| 14 */ |
| 15 var global = this; |
| 16 |
| 17 /** Platform, package, object property, and Event support. **/ |
| 18 var cr = function() { |
| 19 'use strict'; |
| 20 |
| 21 /** |
| 22 * Builds an object structure for the provided namespace path, |
| 23 * ensuring that names that already exist are not overwritten. For |
| 24 * example: |
| 25 * "a.b.c" -> a = {};a.b={};a.b.c={}; |
| 26 * @param {string} name Name of the object that this file defines. |
| 27 * @param {*=} opt_object The object to expose at the end of the path. |
| 28 * @param {Object=} opt_objectToExportTo The object to add the path to; |
| 29 * default is {@code global}. |
| 30 * @private |
| 31 */ |
| 32 function exportPath(name, opt_object, opt_objectToExportTo) { |
| 33 var parts = name.split('.'); |
| 34 var cur = opt_objectToExportTo || global; |
| 35 |
| 36 for (var part; parts.length && (part = parts.shift());) { |
| 37 if (!parts.length && opt_object !== undefined) { |
| 38 // last part and we have an object; use it |
| 39 cur[part] = opt_object; |
| 40 } else if (part in cur) { |
| 41 cur = cur[part]; |
| 42 } else { |
| 43 cur = cur[part] = {}; |
| 44 } |
| 45 } |
| 46 return cur; |
| 47 }; |
| 48 |
| 49 /** |
| 50 * Fires a property change event on the target. |
| 51 * @param {EventTarget} target The target to dispatch the event on. |
| 52 * @param {string} propertyName The name of the property that changed. |
| 53 * @param {*} newValue The new value for the property. |
| 54 * @param {*} oldValue The old value for the property. |
| 55 */ |
| 56 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { |
| 57 var e = new Event(propertyName + 'Change'); |
| 58 e.propertyName = propertyName; |
| 59 e.newValue = newValue; |
| 60 e.oldValue = oldValue; |
| 61 target.dispatchEvent(e); |
| 62 } |
| 63 |
| 64 /** |
| 65 * Converts a camelCase javascript property name to a hyphenated-lower-case |
| 66 * attribute name. |
| 67 * @param {string} jsName The javascript camelCase property name. |
| 68 * @return {string} The equivalent hyphenated-lower-case attribute name. |
| 69 */ |
| 70 function getAttributeName(jsName) { |
| 71 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); |
| 72 } |
| 73 |
| 74 /** |
| 75 * The kind of property to define in {@code defineProperty}. |
| 76 * @enum {string} |
| 77 * @const |
| 78 */ |
| 79 var PropertyKind = { |
| 80 /** |
| 81 * Plain old JS property where the backing data is stored as a "private" |
| 82 * field on the object. |
| 83 * Use for properties of any type. Type will not be checked. |
| 84 */ |
| 85 JS: 'js', |
| 86 |
| 87 /** |
| 88 * The property backing data is stored as an attribute on an element. |
| 89 * Use only for properties of type {string}. |
| 90 */ |
| 91 ATTR: 'attr', |
| 92 |
| 93 /** |
| 94 * The property backing data is stored as an attribute on an element. If the |
| 95 * element has the attribute then the value is true. |
| 96 * Use only for properties of type {boolean}. |
| 97 */ |
| 98 BOOL_ATTR: 'boolAttr' |
| 99 }; |
| 100 |
| 101 /** |
| 102 * Helper function for defineProperty that returns the getter to use for the |
| 103 * property. |
| 104 * @param {string} name The name of the property. |
| 105 * @param {PropertyKind} kind The kind of the property. |
| 106 * @return {function():*} The getter for the property. |
| 107 */ |
| 108 function getGetter(name, kind) { |
| 109 switch (kind) { |
| 110 case PropertyKind.JS: |
| 111 var privateName = name + '_'; |
| 112 return function() { |
| 113 return this[privateName]; |
| 114 }; |
| 115 case PropertyKind.ATTR: |
| 116 var attributeName = getAttributeName(name); |
| 117 return function() { |
| 118 return this.getAttribute(attributeName); |
| 119 }; |
| 120 case PropertyKind.BOOL_ATTR: |
| 121 var attributeName = getAttributeName(name); |
| 122 return function() { |
| 123 return this.hasAttribute(attributeName); |
| 124 }; |
| 125 } |
| 126 |
| 127 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax |
| 128 // the browser/unit tests to preprocess this file through grit. |
| 129 throw 'not reached'; |
| 130 } |
| 131 |
| 132 /** |
| 133 * Helper function for defineProperty that returns the setter of the right |
| 134 * kind. |
| 135 * @param {string} name The name of the property we are defining the setter |
| 136 * for. |
| 137 * @param {PropertyKind} kind The kind of property we are getting the |
| 138 * setter for. |
| 139 * @param {function(*, *):void=} opt_setHook A function to run after the |
| 140 * property is set, but before the propertyChange event is fired. |
| 141 * @return {function(*):void} The function to use as a setter. |
| 142 */ |
| 143 function getSetter(name, kind, opt_setHook) { |
| 144 switch (kind) { |
| 145 case PropertyKind.JS: |
| 146 var privateName = name + '_'; |
| 147 return function(value) { |
| 148 var oldValue = this[name]; |
| 149 if (value !== oldValue) { |
| 150 this[privateName] = value; |
| 151 if (opt_setHook) |
| 152 opt_setHook.call(this, value, oldValue); |
| 153 dispatchPropertyChange(this, name, value, oldValue); |
| 154 } |
| 155 }; |
| 156 |
| 157 case PropertyKind.ATTR: |
| 158 var attributeName = getAttributeName(name); |
| 159 return function(value) { |
| 160 var oldValue = this[name]; |
| 161 if (value !== oldValue) { |
| 162 if (value == undefined) |
| 163 this.removeAttribute(attributeName); |
| 164 else |
| 165 this.setAttribute(attributeName, value); |
| 166 if (opt_setHook) |
| 167 opt_setHook.call(this, value, oldValue); |
| 168 dispatchPropertyChange(this, name, value, oldValue); |
| 169 } |
| 170 }; |
| 171 |
| 172 case PropertyKind.BOOL_ATTR: |
| 173 var attributeName = getAttributeName(name); |
| 174 return function(value) { |
| 175 var oldValue = this[name]; |
| 176 if (value !== oldValue) { |
| 177 if (value) |
| 178 this.setAttribute(attributeName, name); |
| 179 else |
| 180 this.removeAttribute(attributeName); |
| 181 if (opt_setHook) |
| 182 opt_setHook.call(this, value, oldValue); |
| 183 dispatchPropertyChange(this, name, value, oldValue); |
| 184 } |
| 185 }; |
| 186 } |
| 187 |
| 188 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax |
| 189 // the browser/unit tests to preprocess this file through grit. |
| 190 throw 'not reached'; |
| 191 } |
| 192 |
| 193 /** |
| 194 * Defines a property on an object. When the setter changes the value a |
| 195 * property change event with the type {@code name + 'Change'} is fired. |
| 196 * @param {!Object} obj The object to define the property for. |
| 197 * @param {string} name The name of the property. |
| 198 * @param {PropertyKind=} opt_kind What kind of underlying storage to use. |
| 199 * @param {function(*, *):void=} opt_setHook A function to run after the |
| 200 * property is set, but before the propertyChange event is fired. |
| 201 */ |
| 202 function defineProperty(obj, name, opt_kind, opt_setHook) { |
| 203 if (typeof obj == 'function') |
| 204 obj = obj.prototype; |
| 205 |
| 206 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); |
| 207 |
| 208 if (!obj.__lookupGetter__(name)) |
| 209 obj.__defineGetter__(name, getGetter(name, kind)); |
| 210 |
| 211 if (!obj.__lookupSetter__(name)) |
| 212 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); |
| 213 } |
| 214 |
| 215 /** |
| 216 * Counter for use with createUid |
| 217 */ |
| 218 var uidCounter = 1; |
| 219 |
| 220 /** |
| 221 * @return {number} A new unique ID. |
| 222 */ |
| 223 function createUid() { |
| 224 return uidCounter++; |
| 225 } |
| 226 |
| 227 /** |
| 228 * Returns a unique ID for the item. This mutates the item so it needs to be |
| 229 * an object |
| 230 * @param {!Object} item The item to get the unique ID for. |
| 231 * @return {number} The unique ID for the item. |
| 232 */ |
| 233 function getUid(item) { |
| 234 if (item.hasOwnProperty('uid')) |
| 235 return item.uid; |
| 236 return item.uid = createUid(); |
| 237 } |
| 238 |
| 239 /** |
| 240 * Dispatches a simple event on an event target. |
| 241 * @param {!EventTarget} target The event target to dispatch the event on. |
| 242 * @param {string} type The type of the event. |
| 243 * @param {boolean=} opt_bubbles Whether the event bubbles or not. |
| 244 * @param {boolean=} opt_cancelable Whether the default action of the event |
| 245 * can be prevented. Default is true. |
| 246 * @return {boolean} If any of the listeners called {@code preventDefault} |
| 247 * during the dispatch this will return false. |
| 248 */ |
| 249 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { |
| 250 var e = new Event(type, { |
| 251 bubbles: opt_bubbles, |
| 252 cancelable: opt_cancelable === undefined || opt_cancelable |
| 253 }); |
| 254 return target.dispatchEvent(e); |
| 255 } |
| 256 |
| 257 /** |
| 258 * Calls |fun| and adds all the fields of the returned object to the object |
| 259 * named by |name|. For example, cr.define('cr.ui', function() { |
| 260 * function List() { |
| 261 * ... |
| 262 * } |
| 263 * function ListItem() { |
| 264 * ... |
| 265 * } |
| 266 * return { |
| 267 * List: List, |
| 268 * ListItem: ListItem, |
| 269 * }; |
| 270 * }); |
| 271 * defines the functions cr.ui.List and cr.ui.ListItem. |
| 272 * @param {string} name The name of the object that we are adding fields to. |
| 273 * @param {!Function} fun The function that will return an object containing |
| 274 * the names and values of the new fields. |
| 275 */ |
| 276 function define(name, fun) { |
| 277 var obj = exportPath(name); |
| 278 var exports = fun(); |
| 279 for (var propertyName in exports) { |
| 280 // Maybe we should check the prototype chain here? The current usage |
| 281 // pattern is always using an object literal so we only care about own |
| 282 // properties. |
| 283 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, |
| 284 propertyName); |
| 285 if (propertyDescriptor) |
| 286 Object.defineProperty(obj, propertyName, propertyDescriptor); |
| 287 } |
| 288 } |
| 289 |
| 290 /** |
| 291 * Adds a {@code getInstance} static method that always return the same |
| 292 * instance object. |
| 293 * @param {!Function} ctor The constructor for the class to add the static |
| 294 * method to. |
| 295 */ |
| 296 function addSingletonGetter(ctor) { |
| 297 ctor.getInstance = function() { |
| 298 return ctor.instance_ || (ctor.instance_ = new ctor()); |
| 299 }; |
| 300 } |
| 301 |
| 302 /** |
| 303 * Forwards public APIs to private implementations. |
| 304 * @param {Function} ctor Constructor that have private implementations in its |
| 305 * prototype. |
| 306 * @param {Array<string>} methods List of public method names that have their |
| 307 * underscored counterparts in constructor's prototype. |
| 308 * @param {string=} opt_target Selector for target node. |
| 309 */ |
| 310 function makePublic(ctor, methods, opt_target) { |
| 311 methods.forEach(function(method) { |
| 312 ctor[method] = function() { |
| 313 var target = opt_target ? document.getElementById(opt_target) : |
| 314 ctor.getInstance(); |
| 315 return target[method + '_'].apply(target, arguments); |
| 316 }; |
| 317 }); |
| 318 } |
| 319 |
| 320 /** |
| 321 * The mapping used by the sendWithCallback mechanism to tie the callback |
| 322 * supplied to an invocation of sendWithCallback with the WebUI response |
| 323 * sent by the browser in response to the chrome.send call. The mapping is |
| 324 * from ID to callback function; the ID is generated by sendWithCallback and |
| 325 * is unique across all invocations of said method. |
| 326 * @type {!Object<Function>} |
| 327 */ |
| 328 var chromeSendCallbackMap = Object.create(null); |
| 329 |
| 330 /** |
| 331 * The named method the WebUI handler calls directly in response to a |
| 332 * chrome.send call that expects a callback. The handler requires no knowledge |
| 333 * of the specific name of this method, as the name is passed to the handler |
| 334 * as the first argument in the arguments list of chrome.send. The handler |
| 335 * must pass the ID, also sent via the chrome.send arguments list, as the |
| 336 * first argument of the JS invocation; additionally, the handler may |
| 337 * supply any number of other arguments that will be forwarded to the |
| 338 * callback. |
| 339 * @param {string} id The unique ID identifying the callback method this |
| 340 * response is tied to. |
| 341 */ |
| 342 function webUIResponse(id) { |
| 343 chromeSendCallbackMap[id].apply( |
| 344 null, Array.prototype.slice.call(arguments, 1)); |
| 345 delete chromeSendCallbackMap[id]; |
| 346 } |
| 347 |
| 348 /** |
| 349 * A variation of chrome.send which allows the client to receive a direct |
| 350 * callback without requiring the handler to have specific knowledge of any |
| 351 * JS internal method names or state. The callback will be removed from the |
| 352 * mapping once it has fired. |
| 353 * @param {string} methodName The name of the WebUI handler API. |
| 354 * @param {Array|undefined} args Arguments for the method call sent to the |
| 355 * WebUI handler. Pass undefined if no args should be sent to the handler. |
| 356 * @param {Function} callback A callback function which is called (indirectly) |
| 357 * by the WebUI handler. |
| 358 */ |
| 359 function sendWithCallback(methodName, args, callback) { |
| 360 var id = methodName + createUid(); |
| 361 chromeSendCallbackMap[id] = callback; |
| 362 chrome.send(methodName, ['cr.webUIResponse', id].concat(args || [])); |
| 363 } |
| 364 |
| 365 /** |
| 366 * A registry of callbacks keyed by event name. Used by addWebUIListener to |
| 367 * register listeners. |
| 368 * @type {!Object<Array<Function>>} |
| 369 */ |
| 370 var webUIListenerMap = Object.create(null); |
| 371 |
| 372 /** |
| 373 * The named method the WebUI handler calls directly when an event occurs. |
| 374 * The WebUI handler must supply the name of the event as the first argument |
| 375 * of the JS invocation; additionally, the handler may supply any number of |
| 376 * other arguments that will be forwarded to the listener callbacks. |
| 377 * @param {string} event The name of the event that has occurred. |
| 378 */ |
| 379 function webUIListenerCallback(event) { |
| 380 var listenerCallbacks = webUIListenerMap[event]; |
| 381 for (var i = 0; i < listenerCallbacks.length; i++) { |
| 382 var callback = listenerCallbacks[i]; |
| 383 callback.apply(null, Array.prototype.slice.call(arguments, 1)); |
| 384 } |
| 385 } |
| 386 |
| 387 /** |
| 388 * Registers a listener for an event fired from WebUI handlers. Any number of |
| 389 * listeners may register for a single event. |
| 390 * @param {string} event The event to listen to. |
| 391 * @param {Function} callback The callback run when the event is fired. |
| 392 */ |
| 393 function addWebUIListener(event, callback) { |
| 394 if (event in webUIListenerMap) |
| 395 webUIListenerMap[event].push(callback); |
| 396 else |
| 397 webUIListenerMap[event] = [callback]; |
| 398 } |
| 399 |
| 400 return { |
| 401 addSingletonGetter: addSingletonGetter, |
| 402 createUid: createUid, |
| 403 define: define, |
| 404 defineProperty: defineProperty, |
| 405 dispatchPropertyChange: dispatchPropertyChange, |
| 406 dispatchSimpleEvent: dispatchSimpleEvent, |
| 407 exportPath: exportPath, |
| 408 getUid: getUid, |
| 409 makePublic: makePublic, |
| 410 webUIResponse: webUIResponse, |
| 411 sendWithCallback: sendWithCallback, |
| 412 webUIListenerCallback: webUIListenerCallback, |
| 413 addWebUIListener: addWebUIListener, |
| 414 PropertyKind: PropertyKind, |
| 415 |
| 416 get doc() { |
| 417 return document; |
| 418 }, |
| 419 |
| 420 /** Whether we are using a Mac or not. */ |
| 421 get isMac() { |
| 422 return /Mac/.test(navigator.platform); |
| 423 }, |
| 424 |
| 425 /** Whether this is on the Windows platform or not. */ |
| 426 get isWindows() { |
| 427 return /Win/.test(navigator.platform); |
| 428 }, |
| 429 |
| 430 /** Whether this is on chromeOS or not. */ |
| 431 get isChromeOS() { |
| 432 return /CrOS/.test(navigator.userAgent); |
| 433 }, |
| 434 |
| 435 /** Whether this is on vanilla Linux (not chromeOS). */ |
| 436 get isLinux() { |
| 437 return /Linux/.test(navigator.userAgent); |
| 438 }, |
| 439 }; |
| 440 }(); |
| 441 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 442 // Use of this source code is governed by a BSD-style license that can be |
| 443 // found in the LICENSE file. |
| 444 |
| 445 cr.define('cr.ui', function() { |
| 446 |
| 447 /** |
| 448 * Decorates elements as an instance of a class. |
| 449 * @param {string|!Element} source The way to find the element(s) to decorate. |
| 450 * If this is a string then {@code querySeletorAll} is used to find the |
| 451 * elements to decorate. |
| 452 * @param {!Function} constr The constructor to decorate with. The constr |
| 453 * needs to have a {@code decorate} function. |
| 454 */ |
| 455 function decorate(source, constr) { |
| 456 var elements; |
| 457 if (typeof source == 'string') |
| 458 elements = cr.doc.querySelectorAll(source); |
| 459 else |
| 460 elements = [source]; |
| 461 |
| 462 for (var i = 0, el; el = elements[i]; i++) { |
| 463 if (!(el instanceof constr)) |
| 464 constr.decorate(el); |
| 465 } |
| 466 } |
| 467 |
| 468 /** |
| 469 * Helper function for creating new element for define. |
| 470 */ |
| 471 function createElementHelper(tagName, opt_bag) { |
| 472 // Allow passing in ownerDocument to create in a different document. |
| 473 var doc; |
| 474 if (opt_bag && opt_bag.ownerDocument) |
| 475 doc = opt_bag.ownerDocument; |
| 476 else |
| 477 doc = cr.doc; |
| 478 return doc.createElement(tagName); |
| 479 } |
| 480 |
| 481 /** |
| 482 * Creates the constructor for a UI element class. |
| 483 * |
| 484 * Usage: |
| 485 * <pre> |
| 486 * var List = cr.ui.define('list'); |
| 487 * List.prototype = { |
| 488 * __proto__: HTMLUListElement.prototype, |
| 489 * decorate: function() { |
| 490 * ... |
| 491 * }, |
| 492 * ... |
| 493 * }; |
| 494 * </pre> |
| 495 * |
| 496 * @param {string|Function} tagNameOrFunction The tagName or |
| 497 * function to use for newly created elements. If this is a function it |
| 498 * needs to return a new element when called. |
| 499 * @return {function(Object=):Element} The constructor function which takes |
| 500 * an optional property bag. The function also has a static |
| 501 * {@code decorate} method added to it. |
| 502 */ |
| 503 function define(tagNameOrFunction) { |
| 504 var createFunction, tagName; |
| 505 if (typeof tagNameOrFunction == 'function') { |
| 506 createFunction = tagNameOrFunction; |
| 507 tagName = ''; |
| 508 } else { |
| 509 createFunction = createElementHelper; |
| 510 tagName = tagNameOrFunction; |
| 511 } |
| 512 |
| 513 /** |
| 514 * Creates a new UI element constructor. |
| 515 * @param {Object=} opt_propertyBag Optional bag of properties to set on the |
| 516 * object after created. The property {@code ownerDocument} is special |
| 517 * cased and it allows you to create the element in a different |
| 518 * document than the default. |
| 519 * @constructor |
| 520 */ |
| 521 function f(opt_propertyBag) { |
| 522 var el = createFunction(tagName, opt_propertyBag); |
| 523 f.decorate(el); |
| 524 for (var propertyName in opt_propertyBag) { |
| 525 el[propertyName] = opt_propertyBag[propertyName]; |
| 526 } |
| 527 return el; |
| 528 } |
| 529 |
| 530 /** |
| 531 * Decorates an element as a UI element class. |
| 532 * @param {!Element} el The element to decorate. |
| 533 */ |
| 534 f.decorate = function(el) { |
| 535 el.__proto__ = f.prototype; |
| 536 el.decorate(); |
| 537 }; |
| 538 |
| 539 return f; |
| 540 } |
| 541 |
| 542 /** |
| 543 * Input elements do not grow and shrink with their content. This is a simple |
| 544 * (and not very efficient) way of handling shrinking to content with support |
| 545 * for min width and limited by the width of the parent element. |
| 546 * @param {!HTMLElement} el The element to limit the width for. |
| 547 * @param {!HTMLElement} parentEl The parent element that should limit the |
| 548 * size. |
| 549 * @param {number} min The minimum width. |
| 550 * @param {number=} opt_scale Optional scale factor to apply to the width. |
| 551 */ |
| 552 function limitInputWidth(el, parentEl, min, opt_scale) { |
| 553 // Needs a size larger than borders |
| 554 el.style.width = '10px'; |
| 555 var doc = el.ownerDocument; |
| 556 var win = doc.defaultView; |
| 557 var computedStyle = win.getComputedStyle(el); |
| 558 var parentComputedStyle = win.getComputedStyle(parentEl); |
| 559 var rtl = computedStyle.direction == 'rtl'; |
| 560 |
| 561 // To get the max width we get the width of the treeItem minus the position |
| 562 // of the input. |
| 563 var inputRect = el.getBoundingClientRect(); // box-sizing |
| 564 var parentRect = parentEl.getBoundingClientRect(); |
| 565 var startPos = rtl ? parentRect.right - inputRect.right : |
| 566 inputRect.left - parentRect.left; |
| 567 |
| 568 // Add up border and padding of the input. |
| 569 var inner = parseInt(computedStyle.borderLeftWidth, 10) + |
| 570 parseInt(computedStyle.paddingLeft, 10) + |
| 571 parseInt(computedStyle.paddingRight, 10) + |
| 572 parseInt(computedStyle.borderRightWidth, 10); |
| 573 |
| 574 // We also need to subtract the padding of parent to prevent it to overflow. |
| 575 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : |
| 576 parseInt(parentComputedStyle.paddingRight, 10); |
| 577 |
| 578 var max = parentEl.clientWidth - startPos - inner - parentPadding; |
| 579 if (opt_scale) |
| 580 max *= opt_scale; |
| 581 |
| 582 function limit() { |
| 583 if (el.scrollWidth > max) { |
| 584 el.style.width = max + 'px'; |
| 585 } else { |
| 586 el.style.width = 0; |
| 587 var sw = el.scrollWidth; |
| 588 if (sw < min) { |
| 589 el.style.width = min + 'px'; |
| 590 } else { |
| 591 el.style.width = sw + 'px'; |
| 592 } |
| 593 } |
| 594 } |
| 595 |
| 596 el.addEventListener('input', limit); |
| 597 limit(); |
| 598 } |
| 599 |
| 600 /** |
| 601 * Takes a number and spits out a value CSS will be happy with. To avoid |
| 602 * subpixel layout issues, the value is rounded to the nearest integral value. |
| 603 * @param {number} pixels The number of pixels. |
| 604 * @return {string} e.g. '16px'. |
| 605 */ |
| 606 function toCssPx(pixels) { |
| 607 if (!window.isFinite(pixels)) |
| 608 console.error('Pixel value is not a number: ' + pixels); |
| 609 return Math.round(pixels) + 'px'; |
| 610 } |
| 611 |
| 612 /** |
| 613 * Users complain they occasionaly use doubleclicks instead of clicks |
| 614 * (http://crbug.com/140364). To fix it we freeze click handling for |
| 615 * the doubleclick time interval. |
| 616 * @param {MouseEvent} e Initial click event. |
| 617 */ |
| 618 function swallowDoubleClick(e) { |
| 619 var doc = e.target.ownerDocument; |
| 620 var counter = Math.min(1, e.detail); |
| 621 function swallow(e) { |
| 622 e.stopPropagation(); |
| 623 e.preventDefault(); |
| 624 } |
| 625 function onclick(e) { |
| 626 if (e.detail > counter) { |
| 627 counter = e.detail; |
| 628 // Swallow the click since it's a click inside the doubleclick timeout. |
| 629 swallow(e); |
| 630 } else { |
| 631 // Stop tracking clicks and let regular handling. |
| 632 doc.removeEventListener('dblclick', swallow, true); |
| 633 doc.removeEventListener('click', onclick, true); |
| 634 } |
| 635 } |
| 636 // The following 'click' event (if e.type == 'mouseup') mustn't be taken |
| 637 // into account (it mustn't stop tracking clicks). Start event listening |
| 638 // after zero timeout. |
| 639 setTimeout(function() { |
| 640 doc.addEventListener('click', onclick, true); |
| 641 doc.addEventListener('dblclick', swallow, true); |
| 642 }, 0); |
| 643 } |
| 644 |
| 645 return { |
| 646 decorate: decorate, |
| 647 define: define, |
| 648 limitInputWidth: limitInputWidth, |
| 649 toCssPx: toCssPx, |
| 650 swallowDoubleClick: swallowDoubleClick |
| 651 }; |
| 652 }); |
| 653 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 654 // Use of this source code is governed by a BSD-style license that can be |
| 655 // found in the LICENSE file. |
| 656 |
| 657 /** |
| 658 * @fileoverview A command is an abstraction of an action a user can do in the |
| 659 * UI. |
| 660 * |
| 661 * When the focus changes in the document for each command a canExecute event |
| 662 * is dispatched on the active element. By listening to this event you can |
| 663 * enable and disable the command by setting the event.canExecute property. |
| 664 * |
| 665 * When a command is executed a command event is dispatched on the active |
| 666 * element. Note that you should stop the propagation after you have handled the |
| 667 * command if there might be other command listeners higher up in the DOM tree. |
| 668 */ |
| 669 |
| 670 cr.define('cr.ui', function() { |
| 671 |
| 672 /** |
| 673 * This is used to identify keyboard shortcuts. |
| 674 * @param {string} shortcut The text used to describe the keys for this |
| 675 * keyboard shortcut. |
| 676 * @constructor |
| 677 */ |
| 678 function KeyboardShortcut(shortcut) { |
| 679 var mods = {}; |
| 680 var ident = ''; |
| 681 shortcut.split('-').forEach(function(part) { |
| 682 var partLc = part.toLowerCase(); |
| 683 switch (partLc) { |
| 684 case 'alt': |
| 685 case 'ctrl': |
| 686 case 'meta': |
| 687 case 'shift': |
| 688 mods[partLc + 'Key'] = true; |
| 689 break; |
| 690 default: |
| 691 if (ident) |
| 692 throw Error('Invalid shortcut'); |
| 693 ident = part; |
| 694 } |
| 695 }); |
| 696 |
| 697 this.ident_ = ident; |
| 698 this.mods_ = mods; |
| 699 } |
| 700 |
| 701 KeyboardShortcut.prototype = { |
| 702 /** |
| 703 * Whether the keyboard shortcut object matches a keyboard event. |
| 704 * @param {!Event} e The keyboard event object. |
| 705 * @return {boolean} Whether we found a match or not. |
| 706 */ |
| 707 matchesEvent: function(e) { |
| 708 if (e.keyIdentifier == this.ident_) { |
| 709 // All keyboard modifiers needs to match. |
| 710 var mods = this.mods_; |
| 711 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { |
| 712 return e[k] == !!mods[k]; |
| 713 }); |
| 714 } |
| 715 return false; |
| 716 } |
| 717 }; |
| 718 |
| 719 /** |
| 720 * Creates a new command element. |
| 721 * @constructor |
| 722 * @extends {HTMLElement} |
| 723 */ |
| 724 var Command = cr.ui.define('command'); |
| 725 |
| 726 Command.prototype = { |
| 727 __proto__: HTMLElement.prototype, |
| 728 |
| 729 /** |
| 730 * Initializes the command. |
| 731 */ |
| 732 decorate: function() { |
| 733 CommandManager.init(assert(this.ownerDocument)); |
| 734 |
| 735 if (this.hasAttribute('shortcut')) |
| 736 this.shortcut = this.getAttribute('shortcut'); |
| 737 }, |
| 738 |
| 739 /** |
| 740 * Executes the command by dispatching a command event on the given element. |
| 741 * If |element| isn't given, the active element is used instead. |
| 742 * If the command is {@code disabled} this does nothing. |
| 743 * @param {HTMLElement=} opt_element Optional element to dispatch event on. |
| 744 */ |
| 745 execute: function(opt_element) { |
| 746 if (this.disabled) |
| 747 return; |
| 748 var doc = this.ownerDocument; |
| 749 if (doc.activeElement) { |
| 750 var e = new Event('command', {bubbles: true}); |
| 751 e.command = this; |
| 752 |
| 753 (opt_element || doc.activeElement).dispatchEvent(e); |
| 754 } |
| 755 }, |
| 756 |
| 757 /** |
| 758 * Call this when there have been changes that might change whether the |
| 759 * command can be executed or not. |
| 760 * @param {Node=} opt_node Node for which to actuate command state. |
| 761 */ |
| 762 canExecuteChange: function(opt_node) { |
| 763 dispatchCanExecuteEvent(this, |
| 764 opt_node || this.ownerDocument.activeElement); |
| 765 }, |
| 766 |
| 767 /** |
| 768 * The keyboard shortcut that triggers the command. This is a string |
| 769 * consisting of a keyIdentifier (as reported by WebKit in keydown) as |
| 770 * well as optional key modifiers joinded with a '-'. |
| 771 * |
| 772 * Multiple keyboard shortcuts can be provided by separating them by |
| 773 * whitespace. |
| 774 * |
| 775 * For example: |
| 776 * "F1" |
| 777 * "U+0008-Meta" for Apple command backspace. |
| 778 * "U+0041-Ctrl" for Control A |
| 779 * "U+007F U+0008-Meta" for Delete and Command Backspace |
| 780 * |
| 781 * @type {string} |
| 782 */ |
| 783 shortcut_: '', |
| 784 get shortcut() { |
| 785 return this.shortcut_; |
| 786 }, |
| 787 set shortcut(shortcut) { |
| 788 var oldShortcut = this.shortcut_; |
| 789 if (shortcut !== oldShortcut) { |
| 790 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { |
| 791 return new KeyboardShortcut(shortcut); |
| 792 }); |
| 793 |
| 794 // Set this after the keyboardShortcuts_ since that might throw. |
| 795 this.shortcut_ = shortcut; |
| 796 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, |
| 797 oldShortcut); |
| 798 } |
| 799 }, |
| 800 |
| 801 /** |
| 802 * Whether the event object matches the shortcut for this command. |
| 803 * @param {!Event} e The key event object. |
| 804 * @return {boolean} Whether it matched or not. |
| 805 */ |
| 806 matchesEvent: function(e) { |
| 807 if (!this.keyboardShortcuts_) |
| 808 return false; |
| 809 |
| 810 return this.keyboardShortcuts_.some(function(keyboardShortcut) { |
| 811 return keyboardShortcut.matchesEvent(e); |
| 812 }); |
| 813 }, |
| 814 }; |
| 815 |
| 816 /** |
| 817 * The label of the command. |
| 818 */ |
| 819 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); |
| 820 |
| 821 /** |
| 822 * Whether the command is disabled or not. |
| 823 */ |
| 824 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); |
| 825 |
| 826 /** |
| 827 * Whether the command is hidden or not. |
| 828 */ |
| 829 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); |
| 830 |
| 831 /** |
| 832 * Whether the command is checked or not. |
| 833 */ |
| 834 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR); |
| 835 |
| 836 /** |
| 837 * The flag that prevents the shortcut text from being displayed on menu. |
| 838 * |
| 839 * If false, the keyboard shortcut text (eg. "Ctrl+X" for the cut command) |
| 840 * is displayed in menu when the command is assosiated with a menu item. |
| 841 * Otherwise, no text is displayed. |
| 842 */ |
| 843 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR); |
| 844 |
| 845 /** |
| 846 * Dispatches a canExecute event on the target. |
| 847 * @param {!cr.ui.Command} command The command that we are testing for. |
| 848 * @param {EventTarget} target The target element to dispatch the event on. |
| 849 */ |
| 850 function dispatchCanExecuteEvent(command, target) { |
| 851 var e = new CanExecuteEvent(command); |
| 852 target.dispatchEvent(e); |
| 853 command.disabled = !e.canExecute; |
| 854 } |
| 855 |
| 856 /** |
| 857 * The command managers for different documents. |
| 858 */ |
| 859 var commandManagers = {}; |
| 860 |
| 861 /** |
| 862 * Keeps track of the focused element and updates the commands when the focus |
| 863 * changes. |
| 864 * @param {!Document} doc The document that we are managing the commands for. |
| 865 * @constructor |
| 866 */ |
| 867 function CommandManager(doc) { |
| 868 doc.addEventListener('focus', this.handleFocus_.bind(this), true); |
| 869 // Make sure we add the listener to the bubbling phase so that elements can |
| 870 // prevent the command. |
| 871 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false); |
| 872 } |
| 873 |
| 874 /** |
| 875 * Initializes a command manager for the document as needed. |
| 876 * @param {!Document} doc The document to manage the commands for. |
| 877 */ |
| 878 CommandManager.init = function(doc) { |
| 879 var uid = cr.getUid(doc); |
| 880 if (!(uid in commandManagers)) { |
| 881 commandManagers[uid] = new CommandManager(doc); |
| 882 } |
| 883 }; |
| 884 |
| 885 CommandManager.prototype = { |
| 886 |
| 887 /** |
| 888 * Handles focus changes on the document. |
| 889 * @param {Event} e The focus event object. |
| 890 * @private |
| 891 * @suppress {checkTypes} |
| 892 * TODO(vitalyp): remove the suppression. |
| 893 */ |
| 894 handleFocus_: function(e) { |
| 895 var target = e.target; |
| 896 |
| 897 // Ignore focus on a menu button or command item. |
| 898 if (target.menu || target.command) |
| 899 return; |
| 900 |
| 901 var commands = Array.prototype.slice.call( |
| 902 target.ownerDocument.querySelectorAll('command')); |
| 903 |
| 904 commands.forEach(function(command) { |
| 905 dispatchCanExecuteEvent(command, target); |
| 906 }); |
| 907 }, |
| 908 |
| 909 /** |
| 910 * Handles the keydown event and routes it to the right command. |
| 911 * @param {!Event} e The keydown event. |
| 912 */ |
| 913 handleKeyDown_: function(e) { |
| 914 var target = e.target; |
| 915 var commands = Array.prototype.slice.call( |
| 916 target.ownerDocument.querySelectorAll('command')); |
| 917 |
| 918 for (var i = 0, command; command = commands[i]; i++) { |
| 919 if (command.matchesEvent(e)) { |
| 920 // When invoking a command via a shortcut, we have to manually check |
| 921 // if it can be executed, since focus might not have been changed |
| 922 // what would have updated the command's state. |
| 923 command.canExecuteChange(); |
| 924 |
| 925 if (!command.disabled) { |
| 926 e.preventDefault(); |
| 927 // We do not want any other element to handle this. |
| 928 e.stopPropagation(); |
| 929 command.execute(); |
| 930 return; |
| 931 } |
| 932 } |
| 933 } |
| 934 } |
| 935 }; |
| 936 |
| 937 /** |
| 938 * The event type used for canExecute events. |
| 939 * @param {!cr.ui.Command} command The command that we are evaluating. |
| 940 * @extends {Event} |
| 941 * @constructor |
| 942 * @class |
| 943 */ |
| 944 function CanExecuteEvent(command) { |
| 945 var e = new Event('canExecute', {bubbles: true, cancelable: true}); |
| 946 e.__proto__ = CanExecuteEvent.prototype; |
| 947 e.command = command; |
| 948 return e; |
| 949 } |
| 950 |
| 951 CanExecuteEvent.prototype = { |
| 952 __proto__: Event.prototype, |
| 953 |
| 954 /** |
| 955 * The current command |
| 956 * @type {cr.ui.Command} |
| 957 */ |
| 958 command: null, |
| 959 |
| 960 /** |
| 961 * Whether the target can execute the command. Setting this also stops the |
| 962 * propagation and prevents the default. Callers can tell if an event has |
| 963 * been handled via |this.defaultPrevented|. |
| 964 * @type {boolean} |
| 965 */ |
| 966 canExecute_: false, |
| 967 get canExecute() { |
| 968 return this.canExecute_; |
| 969 }, |
| 970 set canExecute(canExecute) { |
| 971 this.canExecute_ = !!canExecute; |
| 972 this.stopPropagation(); |
| 973 this.preventDefault(); |
| 974 } |
| 975 }; |
| 976 |
| 977 // Export |
| 978 return { |
| 979 Command: Command, |
| 980 CanExecuteEvent: CanExecuteEvent |
| 981 }; |
| 982 }); |
| 983 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 984 // Use of this source code is governed by a BSD-style license that can be |
| 985 // found in the LICENSE file. |
| 986 |
| 987 /** |
| 988 * @fileoverview Assertion support. |
| 989 */ |
| 990 |
| 991 /** |
| 992 * Verify |condition| is truthy and return |condition| if so. |
| 993 * @template T |
| 994 * @param {T} condition A condition to check for truthiness. Note that this |
| 995 * may be used to test whether a value is defined or not, and we don't want |
| 996 * to force a cast to Boolean. |
| 997 * @param {string=} opt_message A message to show on failure. |
| 998 * @return {T} A non-null |condition|. |
| 999 */ |
| 1000 function assert(condition, opt_message) { |
| 1001 'use strict'; |
| 1002 if (!condition) { |
| 1003 var msg = 'Assertion failed'; |
| 1004 if (opt_message) |
| 1005 msg = msg + ': ' + opt_message; |
| 1006 throw new Error(msg); |
| 1007 } |
| 1008 return condition; |
| 1009 } |
| 1010 |
| 1011 /** |
| 1012 * Call this from places in the code that should never be reached. |
| 1013 * |
| 1014 * For example, handling all the values of enum with a switch() like this: |
| 1015 * |
| 1016 * function getValueFromEnum(enum) { |
| 1017 * switch (enum) { |
| 1018 * case ENUM_FIRST_OF_TWO: |
| 1019 * return first |
| 1020 * case ENUM_LAST_OF_TWO: |
| 1021 * return last; |
| 1022 * } |
| 1023 * assertNotReached(); |
| 1024 * return document; |
| 1025 * } |
| 1026 * |
| 1027 * This code should only be hit in the case of serious programmer error or |
| 1028 * unexpected input. |
| 1029 * |
| 1030 * @param {string=} opt_message A message to show when this is hit. |
| 1031 */ |
| 1032 function assertNotReached(opt_message) { |
| 1033 throw new Error(opt_message || 'Unreachable code hit'); |
| 1034 } |
| 1035 |
| 1036 /** |
| 1037 * @param {*} value The value to check. |
| 1038 * @param {function(new: T, ...)} type A user-defined constructor. |
| 1039 * @param {string=} opt_message A message to show when this is hit. |
| 1040 * @return {T} |
| 1041 * @template T |
| 1042 */ |
| 1043 function assertInstanceof(value, type, opt_message) { |
| 1044 if (!(value instanceof type)) { |
| 1045 throw new Error(opt_message || |
| 1046 value + ' is not a[n] ' + (type.name || typeof type)); |
| 1047 } |
| 1048 return value; |
| 1049 }; |
| 1050 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 1051 // Use of this source code is governed by a BSD-style license that can be |
| 1052 // found in the LICENSE file. |
| 1053 |
| 1054 /** |
| 1055 * @fileoverview EventTracker is a simple class that manages the addition and |
| 1056 * removal of DOM event listeners. In particular, it keeps track of all |
| 1057 * listeners that have been added and makes it easy to remove some or all of |
| 1058 * them without requiring all the information again. This is particularly handy |
| 1059 * when the listener is a generated function such as a lambda or the result of |
| 1060 * calling Function.bind. |
| 1061 */ |
| 1062 |
| 1063 /** |
| 1064 * The type of the internal tracking entry. TODO(dbeam): move this back to |
| 1065 * EventTracker.Entry when https://github.com/google/closure-compiler/issues/544 |
| 1066 * is fixed. |
| 1067 * @typedef {{target: !EventTarget, |
| 1068 * eventType: string, |
| 1069 * listener: Function, |
| 1070 * capture: boolean}} |
| 1071 */ |
| 1072 var EventTrackerEntry; |
| 1073 |
| 1074 /** |
| 1075 * Create an EventTracker to track a set of events. |
| 1076 * EventTracker instances are typically tied 1:1 with other objects or |
| 1077 * DOM elements whose listeners should be removed when the object is disposed |
| 1078 * or the corresponding elements are removed from the DOM. |
| 1079 * @constructor |
| 1080 */ |
| 1081 function EventTracker() { |
| 1082 /** |
| 1083 * @type {Array<EventTrackerEntry>} |
| 1084 * @private |
| 1085 */ |
| 1086 this.listeners_ = []; |
| 1087 } |
| 1088 |
| 1089 EventTracker.prototype = { |
| 1090 /** |
| 1091 * Add an event listener - replacement for EventTarget.addEventListener. |
| 1092 * @param {!EventTarget} target The DOM target to add a listener to. |
| 1093 * @param {string} eventType The type of event to subscribe to. |
| 1094 * @param {EventListener|Function} listener The listener to add. |
| 1095 * @param {boolean=} opt_capture Whether to invoke during the capture phase. |
| 1096 */ |
| 1097 add: function(target, eventType, listener, opt_capture) { |
| 1098 var capture = !!opt_capture; |
| 1099 var h = { |
| 1100 target: target, |
| 1101 eventType: eventType, |
| 1102 listener: listener, |
| 1103 capture: capture, |
| 1104 }; |
| 1105 this.listeners_.push(h); |
| 1106 target.addEventListener(eventType, listener, capture); |
| 1107 }, |
| 1108 |
| 1109 /** |
| 1110 * Remove any specified event listeners added with this EventTracker. |
| 1111 * @param {!EventTarget} target The DOM target to remove a listener from. |
| 1112 * @param {?string} eventType The type of event to remove. |
| 1113 */ |
| 1114 remove: function(target, eventType) { |
| 1115 this.listeners_ = this.listeners_.filter(function(h) { |
| 1116 if (h.target == target && (!eventType || (h.eventType == eventType))) { |
| 1117 EventTracker.removeEventListener_(h); |
| 1118 return false; |
| 1119 } |
| 1120 return true; |
| 1121 }); |
| 1122 }, |
| 1123 |
| 1124 /** |
| 1125 * Remove all event listeners added with this EventTracker. |
| 1126 */ |
| 1127 removeAll: function() { |
| 1128 this.listeners_.forEach(EventTracker.removeEventListener_); |
| 1129 this.listeners_ = []; |
| 1130 } |
| 1131 }; |
| 1132 |
| 1133 /** |
| 1134 * Remove a single event listener given it's tracking entry. It's up to the |
| 1135 * caller to ensure the entry is removed from listeners_. |
| 1136 * @param {EventTrackerEntry} h The entry describing the listener to remove. |
| 1137 * @private |
| 1138 */ |
| 1139 EventTracker.removeEventListener_ = function(h) { |
| 1140 h.target.removeEventListener(h.eventType, h.listener, h.capture); |
| 1141 }; |
| 1142 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 1143 // Use of this source code is governed by a BSD-style license that can be |
| 1144 // found in the LICENSE file. |
| 1145 |
| 1146 cr.define('cr.ui', function() { |
| 1147 /** |
| 1148 * A class to manage grid of focusable elements in a 2D grid. For example, |
| 1149 * given this grid: |
| 1150 * |
| 1151 * focusable [focused] focusable (row: 0, col: 1) |
| 1152 * focusable focusable focusable |
| 1153 * focusable focusable focusable |
| 1154 * |
| 1155 * Pressing the down arrow would result in the focus moving down 1 row and |
| 1156 * keeping the same column: |
| 1157 * |
| 1158 * focusable focusable focusable |
| 1159 * focusable [focused] focusable (row: 1, col: 1) |
| 1160 * focusable focusable focusable |
| 1161 * |
| 1162 * And pressing right or tab at this point would move the focus to: |
| 1163 * |
| 1164 * focusable focusable focusable |
| 1165 * focusable focusable [focused] (row: 1, col: 2) |
| 1166 * focusable focusable focusable |
| 1167 * |
| 1168 * @constructor |
| 1169 * @implements {cr.ui.FocusRow.Delegate} |
| 1170 */ |
| 1171 function FocusGrid() { |
| 1172 /** @type {!Array<!cr.ui.FocusRow>} */ |
| 1173 this.rows = []; |
| 1174 } |
| 1175 |
| 1176 FocusGrid.prototype = { |
| 1177 /** @private {boolean} */ |
| 1178 ignoreFocusChange_: false, |
| 1179 |
| 1180 /** @override */ |
| 1181 onFocus: function(row, e) { |
| 1182 if (this.ignoreFocusChange_) |
| 1183 this.ignoreFocusChange_ = false; |
| 1184 else |
| 1185 this.lastFocused_ = e.currentTarget; |
| 1186 |
| 1187 this.rows.forEach(function(r) { r.makeActive(r == row); }); |
| 1188 }, |
| 1189 |
| 1190 /** @override */ |
| 1191 onKeydown: function(row, e) { |
| 1192 var rowIndex = this.rows.indexOf(row); |
| 1193 assert(rowIndex >= 0); |
| 1194 |
| 1195 var newRow = -1; |
| 1196 |
| 1197 if (e.keyIdentifier == 'Up') |
| 1198 newRow = rowIndex - 1; |
| 1199 else if (e.keyIdentifier == 'Down') |
| 1200 newRow = rowIndex + 1; |
| 1201 else if (e.keyIdentifier == 'PageUp') |
| 1202 newRow = 0; |
| 1203 else if (e.keyIdentifier == 'PageDown') |
| 1204 newRow = this.rows.length - 1; |
| 1205 |
| 1206 var rowToFocus = this.rows[newRow]; |
| 1207 if (rowToFocus) { |
| 1208 this.ignoreFocusChange_ = true; |
| 1209 rowToFocus.getEquivalentElement(this.lastFocused_).focus(); |
| 1210 e.preventDefault(); |
| 1211 return true; |
| 1212 } |
| 1213 |
| 1214 return false; |
| 1215 }, |
| 1216 |
| 1217 /** |
| 1218 * Unregisters event handlers and removes all |this.rows|. |
| 1219 */ |
| 1220 destroy: function() { |
| 1221 this.rows.forEach(function(row) { row.destroy(); }); |
| 1222 this.rows.length = 0; |
| 1223 }, |
| 1224 |
| 1225 /** |
| 1226 * @param {Node} target A target item to find in this grid. |
| 1227 * @return {number} The row index. -1 if not found. |
| 1228 */ |
| 1229 getRowIndexForTarget: function(target) { |
| 1230 for (var i = 0; i < this.rows.length; ++i) { |
| 1231 if (this.rows[i].getElements().indexOf(target) >= 0) |
| 1232 return i; |
| 1233 } |
| 1234 return -1; |
| 1235 }, |
| 1236 |
| 1237 /** |
| 1238 * @param {Element} root An element to search for. |
| 1239 * @return {?cr.ui.FocusRow} The row with root of |root| or null. |
| 1240 */ |
| 1241 getRowForRoot: function(root) { |
| 1242 for (var i = 0; i < this.rows.length; ++i) { |
| 1243 if (this.rows[i].root == root) |
| 1244 return this.rows[i]; |
| 1245 } |
| 1246 return null; |
| 1247 }, |
| 1248 |
| 1249 /** |
| 1250 * Adds |row| to the end of this list. |
| 1251 * @param {!cr.ui.FocusRow} row The row that needs to be added to this grid. |
| 1252 */ |
| 1253 addRow: function(row) { |
| 1254 this.addRowBefore(row, null); |
| 1255 }, |
| 1256 |
| 1257 /** |
| 1258 * Adds |row| before |nextRow|. If |nextRow| is not in the list or it's |
| 1259 * null, |row| is added to the end. |
| 1260 * @param {!cr.ui.FocusRow} row The row that needs to be added to this grid. |
| 1261 * @param {cr.ui.FocusRow} nextRow The row that should follow |row|. |
| 1262 */ |
| 1263 addRowBefore: function(row, nextRow) { |
| 1264 row.delegate = row.delegate || this; |
| 1265 |
| 1266 var nextRowIndex = this.rows.indexOf(nextRow); |
| 1267 if (nextRowIndex == -1) |
| 1268 this.rows.push(row); |
| 1269 else |
| 1270 this.rows.splice(nextRowIndex, 0, row); |
| 1271 }, |
| 1272 |
| 1273 /** |
| 1274 * Removes a row from the focus row. No-op if row is not in the grid. |
| 1275 * @param {cr.ui.FocusRow} row The row that needs to be removed. |
| 1276 */ |
| 1277 removeRow: function(row) { |
| 1278 var nextRowIndex = this.rows.indexOf(row); |
| 1279 if (nextRowIndex > -1) |
| 1280 this.rows.splice(nextRowIndex, 1); |
| 1281 }, |
| 1282 |
| 1283 /** |
| 1284 * Makes sure that at least one row is active. Should be called once, after |
| 1285 * adding all rows to FocusGrid. |
| 1286 */ |
| 1287 ensureRowActive: function() { |
| 1288 if (this.rows.length == 0) |
| 1289 return; |
| 1290 |
| 1291 for (var i = 0; i < this.rows.length; ++i) { |
| 1292 if (this.rows[i].isActive()) |
| 1293 return; |
| 1294 } |
| 1295 |
| 1296 this.rows[0].makeActive(true); |
| 1297 }, |
| 1298 }; |
| 1299 |
| 1300 return { |
| 1301 FocusGrid: FocusGrid, |
| 1302 }; |
| 1303 }); |
| 1304 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 1305 // Use of this source code is governed by a BSD-style license that can be |
| 1306 // found in the LICENSE file. |
| 1307 |
| 1308 // <include src="../../../../ui/webui/resources/js/assert.js"> |
| 1309 |
| 1310 /** |
| 1311 * Alias for document.getElementById. |
| 1312 * @param {string} id The ID of the element to find. |
| 1313 * @return {HTMLElement} The found element or null if not found. |
| 1314 */ |
| 1315 function $(id) { |
| 1316 return document.getElementById(id); |
| 1317 } |
| 1318 |
| 1319 /** |
| 1320 * Add an accessible message to the page that will be announced to |
| 1321 * users who have spoken feedback on, but will be invisible to all |
| 1322 * other users. It's removed right away so it doesn't clutter the DOM. |
| 1323 * @param {string} msg The text to be pronounced. |
| 1324 */ |
| 1325 function announceAccessibleMessage(msg) { |
| 1326 var element = document.createElement('div'); |
| 1327 element.setAttribute('aria-live', 'polite'); |
| 1328 element.style.position = 'relative'; |
| 1329 element.style.left = '-9999px'; |
| 1330 element.style.height = '0px'; |
| 1331 element.innerText = msg; |
| 1332 document.body.appendChild(element); |
| 1333 window.setTimeout(function() { |
| 1334 document.body.removeChild(element); |
| 1335 }, 0); |
| 1336 } |
| 1337 |
| 1338 /** |
| 1339 * Calls chrome.send with a callback and restores the original afterwards. |
| 1340 * @param {string} name The name of the message to send. |
| 1341 * @param {!Array} params The parameters to send. |
| 1342 * @param {string} callbackName The name of the function that the backend calls. |
| 1343 * @param {!Function} callback The function to call. |
| 1344 */ |
| 1345 function chromeSend(name, params, callbackName, callback) { |
| 1346 var old = global[callbackName]; |
| 1347 global[callbackName] = function() { |
| 1348 // restore |
| 1349 global[callbackName] = old; |
| 1350 |
| 1351 var args = Array.prototype.slice.call(arguments); |
| 1352 return callback.apply(global, args); |
| 1353 }; |
| 1354 chrome.send(name, params); |
| 1355 } |
| 1356 |
| 1357 /** |
| 1358 * Returns the scale factors supported by this platform for webui |
| 1359 * resources. |
| 1360 * @return {Array} The supported scale factors. |
| 1361 */ |
| 1362 function getSupportedScaleFactors() { |
| 1363 var supportedScaleFactors = []; |
| 1364 if (cr.isMac || cr.isChromeOS || cr.isWindows || cr.isLinux) { |
| 1365 // All desktop platforms support zooming which also updates the |
| 1366 // renderer's device scale factors (a.k.a devicePixelRatio), and |
| 1367 // these platforms has high DPI assets for 2.0x. Use 1x and 2x in |
| 1368 // image-set on these platforms so that the renderer can pick the |
| 1369 // closest image for the current device scale factor. |
| 1370 supportedScaleFactors.push(1); |
| 1371 supportedScaleFactors.push(2); |
| 1372 } else { |
| 1373 // For other platforms that use fixed device scale factor, use |
| 1374 // the window's device pixel ratio. |
| 1375 // TODO(oshima): Investigate if Android/iOS need to use image-set. |
| 1376 supportedScaleFactors.push(window.devicePixelRatio); |
| 1377 } |
| 1378 return supportedScaleFactors; |
| 1379 } |
| 1380 |
| 1381 /** |
| 1382 * Generates a CSS url string. |
| 1383 * @param {string} s The URL to generate the CSS url for. |
| 1384 * @return {string} The CSS url string. |
| 1385 */ |
| 1386 function url(s) { |
| 1387 // http://www.w3.org/TR/css3-values/#uris |
| 1388 // Parentheses, commas, whitespace characters, single quotes (') and double |
| 1389 // quotes (") appearing in a URI must be escaped with a backslash |
| 1390 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); |
| 1391 // WebKit has a bug when it comes to URLs that end with \ |
| 1392 // https://bugs.webkit.org/show_bug.cgi?id=28885 |
| 1393 if (/\\\\$/.test(s2)) { |
| 1394 // Add a space to work around the WebKit bug. |
| 1395 s2 += ' '; |
| 1396 } |
| 1397 return 'url("' + s2 + '")'; |
| 1398 } |
| 1399 |
| 1400 /** |
| 1401 * Returns the URL of the image, or an image set of URLs for the profile avatar. |
| 1402 * Default avatars have resources available for multiple scalefactors, whereas |
| 1403 * the GAIA profile image only comes in one size. |
| 1404 * |
| 1405 * @param {string} path The path of the image. |
| 1406 * @return {string} The url, or an image set of URLs of the avatar image. |
| 1407 */ |
| 1408 function getProfileAvatarIcon(path) { |
| 1409 var chromeThemePath = 'chrome://theme'; |
| 1410 var isDefaultAvatar = |
| 1411 (path.slice(0, chromeThemePath.length) == chromeThemePath); |
| 1412 return isDefaultAvatar ? imageset(path + '@scalefactorx'): url(path); |
| 1413 } |
| 1414 |
| 1415 /** |
| 1416 * Generates a CSS -webkit-image-set for a chrome:// url. |
| 1417 * An entry in the image set is added for each of getSupportedScaleFactors(). |
| 1418 * The scale-factor-specific url is generated by replacing the first instance of |
| 1419 * 'scalefactor' in |path| with the numeric scale factor. |
| 1420 * @param {string} path The URL to generate an image set for. |
| 1421 * 'scalefactor' should be a substring of |path|. |
| 1422 * @return {string} The CSS -webkit-image-set. |
| 1423 */ |
| 1424 function imageset(path) { |
| 1425 var supportedScaleFactors = getSupportedScaleFactors(); |
| 1426 |
| 1427 var replaceStartIndex = path.indexOf('scalefactor'); |
| 1428 if (replaceStartIndex < 0) |
| 1429 return url(path); |
| 1430 |
| 1431 var s = ''; |
| 1432 for (var i = 0; i < supportedScaleFactors.length; ++i) { |
| 1433 var scaleFactor = supportedScaleFactors[i]; |
| 1434 var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor + |
| 1435 path.substr(replaceStartIndex + 'scalefactor'.length); |
| 1436 |
| 1437 s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x'; |
| 1438 |
| 1439 if (i != supportedScaleFactors.length - 1) |
| 1440 s += ', '; |
| 1441 } |
| 1442 return '-webkit-image-set(' + s + ')'; |
| 1443 } |
| 1444 |
| 1445 /** |
| 1446 * Parses query parameters from Location. |
| 1447 * @param {Location} location The URL to generate the CSS url for. |
| 1448 * @return {Object} Dictionary containing name value pairs for URL |
| 1449 */ |
| 1450 function parseQueryParams(location) { |
| 1451 var params = {}; |
| 1452 var query = unescape(location.search.substring(1)); |
| 1453 var vars = query.split('&'); |
| 1454 for (var i = 0; i < vars.length; i++) { |
| 1455 var pair = vars[i].split('='); |
| 1456 params[pair[0]] = pair[1]; |
| 1457 } |
| 1458 return params; |
| 1459 } |
| 1460 |
| 1461 /** |
| 1462 * Creates a new URL by appending or replacing the given query key and value. |
| 1463 * Not supporting URL with username and password. |
| 1464 * @param {Location} location The original URL. |
| 1465 * @param {string} key The query parameter name. |
| 1466 * @param {string} value The query parameter value. |
| 1467 * @return {string} The constructed new URL. |
| 1468 */ |
| 1469 function setQueryParam(location, key, value) { |
| 1470 var query = parseQueryParams(location); |
| 1471 query[encodeURIComponent(key)] = encodeURIComponent(value); |
| 1472 |
| 1473 var newQuery = ''; |
| 1474 for (var q in query) { |
| 1475 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; |
| 1476 } |
| 1477 |
| 1478 return location.origin + location.pathname + newQuery + location.hash; |
| 1479 } |
| 1480 |
| 1481 /** |
| 1482 * @param {Node} el A node to search for ancestors with |className|. |
| 1483 * @param {string} className A class to search for. |
| 1484 * @return {Element} A node with class of |className| or null if none is found. |
| 1485 */ |
| 1486 function findAncestorByClass(el, className) { |
| 1487 return /** @type {Element} */(findAncestor(el, function(el) { |
| 1488 return el.classList && el.classList.contains(className); |
| 1489 })); |
| 1490 } |
| 1491 |
| 1492 /** |
| 1493 * Return the first ancestor for which the {@code predicate} returns true. |
| 1494 * @param {Node} node The node to check. |
| 1495 * @param {function(Node):boolean} predicate The function that tests the |
| 1496 * nodes. |
| 1497 * @return {Node} The found ancestor or null if not found. |
| 1498 */ |
| 1499 function findAncestor(node, predicate) { |
| 1500 var last = false; |
| 1501 while (node != null && !(last = predicate(node))) { |
| 1502 node = node.parentNode; |
| 1503 } |
| 1504 return last ? node : null; |
| 1505 } |
| 1506 |
| 1507 function swapDomNodes(a, b) { |
| 1508 var afterA = a.nextSibling; |
| 1509 if (afterA == b) { |
| 1510 swapDomNodes(b, a); |
| 1511 return; |
| 1512 } |
| 1513 var aParent = a.parentNode; |
| 1514 b.parentNode.replaceChild(a, b); |
| 1515 aParent.insertBefore(b, afterA); |
| 1516 } |
| 1517 |
| 1518 /** |
| 1519 * Disables text selection and dragging, with optional whitelist callbacks. |
| 1520 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function |
| 1521 * is defined and returns true, the onselectionstart event will be |
| 1522 * surpressed. |
| 1523 * @param {function(Event):boolean=} opt_allowDragStart Unless this function |
| 1524 * is defined and returns true, the ondragstart event will be surpressed. |
| 1525 */ |
| 1526 function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { |
| 1527 // Disable text selection. |
| 1528 document.onselectstart = function(e) { |
| 1529 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) |
| 1530 e.preventDefault(); |
| 1531 }; |
| 1532 |
| 1533 // Disable dragging. |
| 1534 document.ondragstart = function(e) { |
| 1535 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) |
| 1536 e.preventDefault(); |
| 1537 }; |
| 1538 } |
| 1539 |
| 1540 /** |
| 1541 * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead. |
| 1542 * Call this to stop clicks on <a href="#"> links from scrolling to the top of |
| 1543 * the page (and possibly showing a # in the link). |
| 1544 */ |
| 1545 function preventDefaultOnPoundLinkClicks() { |
| 1546 document.addEventListener('click', function(e) { |
| 1547 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) { |
| 1548 return el.tagName == 'A'; |
| 1549 }); |
| 1550 // Use getAttribute() to prevent URL normalization. |
| 1551 if (anchor && anchor.getAttribute('href') == '#') |
| 1552 e.preventDefault(); |
| 1553 }); |
| 1554 } |
| 1555 |
| 1556 /** |
| 1557 * Check the directionality of the page. |
| 1558 * @return {boolean} True if Chrome is running an RTL UI. |
| 1559 */ |
| 1560 function isRTL() { |
| 1561 return document.documentElement.dir == 'rtl'; |
| 1562 } |
| 1563 |
| 1564 /** |
| 1565 * Get an element that's known to exist by its ID. We use this instead of just |
| 1566 * calling getElementById and not checking the result because this lets us |
| 1567 * satisfy the JSCompiler type system. |
| 1568 * @param {string} id The identifier name. |
| 1569 * @return {!HTMLElement} the Element. |
| 1570 */ |
| 1571 function getRequiredElement(id) { |
| 1572 return assertInstanceof($(id), HTMLElement, |
| 1573 'Missing required element: ' + id); |
| 1574 } |
| 1575 |
| 1576 /** |
| 1577 * Query an element that's known to exist by a selector. We use this instead of |
| 1578 * just calling querySelector and not checking the result because this lets us |
| 1579 * satisfy the JSCompiler type system. |
| 1580 * @param {string} selectors CSS selectors to query the element. |
| 1581 * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional |
| 1582 * context object for querySelector. |
| 1583 * @return {!HTMLElement} the Element. |
| 1584 */ |
| 1585 function queryRequiredElement(selectors, opt_context) { |
| 1586 var element = (opt_context || document).querySelector(selectors); |
| 1587 return assertInstanceof(element, HTMLElement, |
| 1588 'Missing required element: ' + selectors); |
| 1589 } |
| 1590 |
| 1591 // Handle click on a link. If the link points to a chrome: or file: url, then |
| 1592 // call into the browser to do the navigation. |
| 1593 document.addEventListener('click', function(e) { |
| 1594 if (e.defaultPrevented) |
| 1595 return; |
| 1596 |
| 1597 var el = e.target; |
| 1598 if (el.nodeType == Node.ELEMENT_NODE && |
| 1599 el.webkitMatchesSelector('A, A *')) { |
| 1600 while (el.tagName != 'A') { |
| 1601 el = el.parentElement; |
| 1602 } |
| 1603 |
| 1604 if ((el.protocol == 'file:' || el.protocol == 'about:') && |
| 1605 (e.button == 0 || e.button == 1)) { |
| 1606 chrome.send('navigateToUrl', [ |
| 1607 el.href, |
| 1608 el.target, |
| 1609 e.button, |
| 1610 e.altKey, |
| 1611 e.ctrlKey, |
| 1612 e.metaKey, |
| 1613 e.shiftKey |
| 1614 ]); |
| 1615 e.preventDefault(); |
| 1616 } |
| 1617 } |
| 1618 }); |
| 1619 |
| 1620 /** |
| 1621 * Creates a new URL which is the old URL with a GET param of key=value. |
| 1622 * @param {string} url The base URL. There is not sanity checking on the URL so |
| 1623 * it must be passed in a proper format. |
| 1624 * @param {string} key The key of the param. |
| 1625 * @param {string} value The value of the param. |
| 1626 * @return {string} The new URL. |
| 1627 */ |
| 1628 function appendParam(url, key, value) { |
| 1629 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); |
| 1630 |
| 1631 if (url.indexOf('?') == -1) |
| 1632 return url + '?' + param; |
| 1633 return url + '&' + param; |
| 1634 } |
| 1635 |
| 1636 /** |
| 1637 * Creates a CSS -webkit-image-set for a favicon request. |
| 1638 * @param {string} url The url for the favicon. |
| 1639 * @param {number=} opt_size Optional preferred size of the favicon. |
| 1640 * @param {string=} opt_type Optional type of favicon to request. Valid values |
| 1641 * are 'favicon' and 'touch-icon'. Default is 'favicon'. |
| 1642 * @return {string} -webkit-image-set for the favicon. |
| 1643 */ |
| 1644 function getFaviconImageSet(url, opt_size, opt_type) { |
| 1645 var size = opt_size || 16; |
| 1646 var type = opt_type || 'favicon'; |
| 1647 return imageset( |
| 1648 'chrome://' + type + '/size/' + size + '@scalefactorx/' + url); |
| 1649 } |
| 1650 |
| 1651 /** |
| 1652 * Creates a new URL for a favicon request for the current device pixel ratio. |
| 1653 * The URL must be updated when the user moves the browser to a screen with a |
| 1654 * different device pixel ratio. Use getFaviconImageSet() for the updating to |
| 1655 * occur automatically. |
| 1656 * @param {string} url The url for the favicon. |
| 1657 * @param {number=} opt_size Optional preferred size of the favicon. |
| 1658 * @param {string=} opt_type Optional type of favicon to request. Valid values |
| 1659 * are 'favicon' and 'touch-icon'. Default is 'favicon'. |
| 1660 * @return {string} Updated URL for the favicon. |
| 1661 */ |
| 1662 function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) { |
| 1663 var size = opt_size || 16; |
| 1664 var type = opt_type || 'favicon'; |
| 1665 return 'chrome://' + type + '/size/' + size + '@' + |
| 1666 window.devicePixelRatio + 'x/' + url; |
| 1667 } |
| 1668 |
| 1669 /** |
| 1670 * Creates an element of a specified type with a specified class name. |
| 1671 * @param {string} type The node type. |
| 1672 * @param {string} className The class name to use. |
| 1673 * @return {Element} The created element. |
| 1674 */ |
| 1675 function createElementWithClassName(type, className) { |
| 1676 var elm = document.createElement(type); |
| 1677 elm.className = className; |
| 1678 return elm; |
| 1679 } |
| 1680 |
| 1681 /** |
| 1682 * webkitTransitionEnd does not always fire (e.g. when animation is aborted |
| 1683 * or when no paint happens during the animation). This function sets up |
| 1684 * a timer and emulate the event if it is not fired when the timer expires. |
| 1685 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd. |
| 1686 * @param {number} timeOut The maximum wait time in milliseconds for the |
| 1687 * webkitTransitionEnd to happen. |
| 1688 */ |
| 1689 function ensureTransitionEndEvent(el, timeOut) { |
| 1690 var fired = false; |
| 1691 el.addEventListener('webkitTransitionEnd', function f(e) { |
| 1692 el.removeEventListener('webkitTransitionEnd', f); |
| 1693 fired = true; |
| 1694 }); |
| 1695 window.setTimeout(function() { |
| 1696 if (!fired) |
| 1697 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true); |
| 1698 }, timeOut); |
| 1699 } |
| 1700 |
| 1701 /** |
| 1702 * Alias for document.scrollTop getter. |
| 1703 * @param {!HTMLDocument} doc The document node where information will be |
| 1704 * queried from. |
| 1705 * @return {number} The Y document scroll offset. |
| 1706 */ |
| 1707 function scrollTopForDocument(doc) { |
| 1708 return doc.documentElement.scrollTop || doc.body.scrollTop; |
| 1709 } |
| 1710 |
| 1711 /** |
| 1712 * Alias for document.scrollTop setter. |
| 1713 * @param {!HTMLDocument} doc The document node where information will be |
| 1714 * queried from. |
| 1715 * @param {number} value The target Y scroll offset. |
| 1716 */ |
| 1717 function setScrollTopForDocument(doc, value) { |
| 1718 doc.documentElement.scrollTop = doc.body.scrollTop = value; |
| 1719 } |
| 1720 |
| 1721 /** |
| 1722 * Alias for document.scrollLeft getter. |
| 1723 * @param {!HTMLDocument} doc The document node where information will be |
| 1724 * queried from. |
| 1725 * @return {number} The X document scroll offset. |
| 1726 */ |
| 1727 function scrollLeftForDocument(doc) { |
| 1728 return doc.documentElement.scrollLeft || doc.body.scrollLeft; |
| 1729 } |
| 1730 |
| 1731 /** |
| 1732 * Alias for document.scrollLeft setter. |
| 1733 * @param {!HTMLDocument} doc The document node where information will be |
| 1734 * queried from. |
| 1735 * @param {number} value The target X scroll offset. |
| 1736 */ |
| 1737 function setScrollLeftForDocument(doc, value) { |
| 1738 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; |
| 1739 } |
| 1740 |
| 1741 /** |
| 1742 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding. |
| 1743 * @param {string} original The original string. |
| 1744 * @return {string} The string with all the characters mentioned above replaced. |
| 1745 */ |
| 1746 function HTMLEscape(original) { |
| 1747 return original.replace(/&/g, '&') |
| 1748 .replace(/</g, '<') |
| 1749 .replace(/>/g, '>') |
| 1750 .replace(/"/g, '"') |
| 1751 .replace(/'/g, '''); |
| 1752 } |
| 1753 |
| 1754 /** |
| 1755 * Shortens the provided string (if necessary) to a string of length at most |
| 1756 * |maxLength|. |
| 1757 * @param {string} original The original string. |
| 1758 * @param {number} maxLength The maximum length allowed for the string. |
| 1759 * @return {string} The original string if its length does not exceed |
| 1760 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...' |
| 1761 * appended. |
| 1762 */ |
| 1763 function elide(original, maxLength) { |
| 1764 if (original.length <= maxLength) |
| 1765 return original; |
| 1766 return original.substring(0, maxLength - 1) + '\u2026'; |
| 1767 }; |
| 1768 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 1769 // Use of this source code is governed by a BSD-style license that can be |
| 1770 // found in the LICENSE file. |
| 1771 |
| 1772 cr.define('downloads', function() { |
| 1773 /** |
| 1774 * @param {string} chromeSendName |
| 1775 * @return {function(string):void} A chrome.send() callback with curried name. |
| 1776 */ |
| 1777 function chromeSendWithId(chromeSendName) { |
| 1778 return function(id) { chrome.send(chromeSendName, [id]); }; |
| 1779 } |
| 1780 |
| 1781 /** @constructor */ |
| 1782 function ActionService() {} |
| 1783 |
| 1784 ActionService.prototype = { |
| 1785 /** @param {string} id ID of the download to cancel. */ |
| 1786 cancel: chromeSendWithId('cancel'), |
| 1787 |
| 1788 /** Instructs the browser to clear all finished downloads. */ |
| 1789 clearAll: function() { |
| 1790 if (loadTimeData.getBoolean('allowDeletingHistory')) { |
| 1791 chrome.send('clearAll'); |
| 1792 this.search(''); |
| 1793 } |
| 1794 }, |
| 1795 |
| 1796 /** @param {string} id ID of the dangerous download to discard. */ |
| 1797 discardDangerous: chromeSendWithId('discardDangerous'), |
| 1798 |
| 1799 /** @param {string} url URL of a file to download. */ |
| 1800 download: function(url) { |
| 1801 var a = document.createElement('a'); |
| 1802 a.href = url; |
| 1803 a.setAttribute('download', ''); |
| 1804 a.click(); |
| 1805 }, |
| 1806 |
| 1807 /** @param {string} id ID of the download that the user started dragging. */ |
| 1808 drag: chromeSendWithId('drag'), |
| 1809 |
| 1810 /** |
| 1811 * @return {boolean} Whether the user is currently searching for downloads |
| 1812 * (i.e. has a non-empty search term). |
| 1813 */ |
| 1814 isSearching: function() { |
| 1815 return this.searchText_.length > 0; |
| 1816 }, |
| 1817 |
| 1818 /** Opens the current local destination for downloads. */ |
| 1819 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'), |
| 1820 |
| 1821 /** |
| 1822 * @param {string} id ID of the download to run locally on the user's box. |
| 1823 */ |
| 1824 openFile: chromeSendWithId('openFile'), |
| 1825 |
| 1826 /** @param {string} id ID the of the progressing download to pause. */ |
| 1827 pause: chromeSendWithId('pause'), |
| 1828 |
| 1829 /** @param {string} id ID of the finished download to remove. */ |
| 1830 remove: chromeSendWithId('remove'), |
| 1831 |
| 1832 /** @param {string} id ID of the paused download to resume. */ |
| 1833 resume: chromeSendWithId('resume'), |
| 1834 |
| 1835 /** |
| 1836 * @param {string} id ID of the dangerous download to save despite |
| 1837 * warnings. |
| 1838 */ |
| 1839 saveDangerous: chromeSendWithId('saveDangerous'), |
| 1840 |
| 1841 /** @param {string} searchText What to search for. */ |
| 1842 search: function(searchText) { |
| 1843 if (this.searchText_ == searchText) |
| 1844 return; |
| 1845 |
| 1846 this.searchText_ = searchText; |
| 1847 |
| 1848 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']). |
| 1849 function trim(s) { return s.trim(); } |
| 1850 chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim)); |
| 1851 }, |
| 1852 |
| 1853 /** |
| 1854 * Shows the local folder a finished download resides in. |
| 1855 * @param {string} id ID of the download to show. |
| 1856 */ |
| 1857 show: chromeSendWithId('show'), |
| 1858 |
| 1859 /** Undo download removal. */ |
| 1860 undo: chrome.send.bind(chrome, 'undo'), |
| 1861 }; |
| 1862 |
| 1863 cr.addSingletonGetter(ActionService); |
| 1864 |
| 1865 return {ActionService: ActionService}; |
| 1866 }); |
| 1867 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 1868 // Use of this source code is governed by a BSD-style license that can be |
| 1869 // found in the LICENSE file. |
| 1870 |
| 1871 cr.define('downloads', function() { |
| 1872 /** |
| 1873 * Explains why a download is in DANGEROUS state. |
| 1874 * @enum {string} |
| 1875 */ |
| 1876 var DangerType = { |
| 1877 NOT_DANGEROUS: 'NOT_DANGEROUS', |
| 1878 DANGEROUS_FILE: 'DANGEROUS_FILE', |
| 1879 DANGEROUS_URL: 'DANGEROUS_URL', |
| 1880 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT', |
| 1881 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT', |
| 1882 DANGEROUS_HOST: 'DANGEROUS_HOST', |
| 1883 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED', |
| 1884 }; |
| 1885 |
| 1886 /** |
| 1887 * The states a download can be in. These correspond to states defined in |
| 1888 * DownloadsDOMHandler::CreateDownloadItemValue |
| 1889 * @enum {string} |
| 1890 */ |
| 1891 var States = { |
| 1892 IN_PROGRESS: 'IN_PROGRESS', |
| 1893 CANCELLED: 'CANCELLED', |
| 1894 COMPLETE: 'COMPLETE', |
| 1895 PAUSED: 'PAUSED', |
| 1896 DANGEROUS: 'DANGEROUS', |
| 1897 INTERRUPTED: 'INTERRUPTED', |
| 1898 }; |
| 1899 |
| 1900 return { |
| 1901 DangerType: DangerType, |
| 1902 States: States, |
| 1903 }; |
| 1904 }); |
| 1905 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 1906 // Use of this source code is governed by a BSD-style license that can be |
| 1907 // found in the LICENSE file. |
| 1908 |
| 1909 cr.define('cr.ui', function() { |
| 1910 /** |
| 1911 * A class to manage focus between given horizontally arranged elements. |
| 1912 * |
| 1913 * Pressing left cycles backward and pressing right cycles forward in item |
| 1914 * order. Pressing Home goes to the beginning of the list and End goes to the |
| 1915 * end of the list. |
| 1916 * |
| 1917 * If an item in this row is focused, it'll stay active (accessible via tab). |
| 1918 * If no items in this row are focused, the row can stay active until focus |
| 1919 * changes to a node inside |this.boundary_|. If |boundary| isn't specified, |
| 1920 * any focus change deactivates the row. |
| 1921 * |
| 1922 * @param {!Element} root The root of this focus row. Focus classes are |
| 1923 * applied to |root| and all added elements must live within |root|. |
| 1924 * @param {?Node} boundary Focus events are ignored outside of this node. |
| 1925 * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate. |
| 1926 * @constructor |
| 1927 */ |
| 1928 function FocusRow(root, boundary, opt_delegate) { |
| 1929 /** @type {!Element} */ |
| 1930 this.root = root; |
| 1931 |
| 1932 /** @private {!Node} */ |
| 1933 this.boundary_ = boundary || document; |
| 1934 |
| 1935 /** @type {cr.ui.FocusRow.Delegate|undefined} */ |
| 1936 this.delegate = opt_delegate; |
| 1937 |
| 1938 /** @protected {!EventTracker} */ |
| 1939 this.eventTracker = new EventTracker; |
| 1940 } |
| 1941 |
| 1942 /** @interface */ |
| 1943 FocusRow.Delegate = function() {}; |
| 1944 |
| 1945 FocusRow.Delegate.prototype = { |
| 1946 /** |
| 1947 * Called when a key is pressed while on a FocusRow's item. If true is |
| 1948 * returned, further processing is skipped. |
| 1949 * @param {!cr.ui.FocusRow} row The row that detected a keydown. |
| 1950 * @param {!Event} e |
| 1951 * @return {boolean} Whether the event was handled. |
| 1952 */ |
| 1953 onKeydown: assertNotReached, |
| 1954 |
| 1955 /** |
| 1956 * @param {!cr.ui.FocusRow} row |
| 1957 * @param {!Event} e |
| 1958 */ |
| 1959 onFocus: assertNotReached, |
| 1960 }; |
| 1961 |
| 1962 /** @const {string} */ |
| 1963 FocusRow.ACTIVE_CLASS = 'focus-row-active'; |
| 1964 |
| 1965 /** |
| 1966 * Whether it's possible that |element| can be focused. |
| 1967 * @param {Element} element |
| 1968 * @return {boolean} Whether the item is focusable. |
| 1969 */ |
| 1970 FocusRow.isFocusable = function(element) { |
| 1971 if (!element || element.disabled) |
| 1972 return false; |
| 1973 |
| 1974 // We don't check that element.tabIndex >= 0 here because inactive rows set |
| 1975 // a tabIndex of -1. |
| 1976 |
| 1977 function isVisible(element) { |
| 1978 assertInstanceof(element, Element); |
| 1979 |
| 1980 var style = window.getComputedStyle(element); |
| 1981 if (style.visibility == 'hidden' || style.display == 'none') |
| 1982 return false; |
| 1983 |
| 1984 var parent = element.parentNode; |
| 1985 if (!parent) |
| 1986 return false; |
| 1987 |
| 1988 if (parent == element.ownerDocument || parent instanceof DocumentFragment) |
| 1989 return true; |
| 1990 |
| 1991 return isVisible(parent); |
| 1992 } |
| 1993 |
| 1994 return isVisible(element); |
| 1995 }; |
| 1996 |
| 1997 FocusRow.prototype = { |
| 1998 /** |
| 1999 * Register a new type of focusable element (or add to an existing one). |
| 2000 * |
| 2001 * Example: an (X) button might be 'delete' or 'close'. |
| 2002 * |
| 2003 * When FocusRow is used within a FocusGrid, these types are used to |
| 2004 * determine equivalent controls when Up/Down are pressed to change rows. |
| 2005 * |
| 2006 * Another example: mutually exclusive controls that hide eachother on |
| 2007 * activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause') |
| 2008 * to indicate they're equivalent. |
| 2009 * |
| 2010 * @param {string} type The type of element to track focus of. |
| 2011 * @param {string} query The selector of the element from this row's root. |
| 2012 * @return {boolean} Whether a new item was added. |
| 2013 */ |
| 2014 addItem: function(type, query) { |
| 2015 assert(type); |
| 2016 |
| 2017 var element = this.root.querySelector(query); |
| 2018 if (!element) |
| 2019 return false; |
| 2020 |
| 2021 element.setAttribute('focus-type', type); |
| 2022 element.tabIndex = this.isActive() ? 0 : -1; |
| 2023 |
| 2024 this.eventTracker.add(element, 'blur', this.onBlur_.bind(this)); |
| 2025 this.eventTracker.add(element, 'focus', this.onFocus_.bind(this)); |
| 2026 this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this)); |
| 2027 this.eventTracker.add(element, 'mousedown', |
| 2028 this.onMousedown_.bind(this)); |
| 2029 return true; |
| 2030 }, |
| 2031 |
| 2032 /** Dereferences nodes and removes event handlers. */ |
| 2033 destroy: function() { |
| 2034 this.eventTracker.removeAll(); |
| 2035 }, |
| 2036 |
| 2037 /** |
| 2038 * @param {Element} sampleElement An element for to find an equivalent for. |
| 2039 * @return {!Element} An equivalent element to focus for |sampleElement|. |
| 2040 * @protected |
| 2041 */ |
| 2042 getCustomEquivalent: function(sampleElement) { |
| 2043 return assert(this.getFirstFocusable()); |
| 2044 }, |
| 2045 |
| 2046 /** |
| 2047 * @return {!Array<!Element>} All registered elements (regardless of |
| 2048 * focusability). |
| 2049 */ |
| 2050 getElements: function() { |
| 2051 var elements = this.root.querySelectorAll('[focus-type]'); |
| 2052 return Array.prototype.slice.call(elements); |
| 2053 }, |
| 2054 |
| 2055 /** |
| 2056 * Find the element that best matches |sampleElement|. |
| 2057 * @param {!Element} sampleElement An element from a row of the same type |
| 2058 * which previously held focus. |
| 2059 * @return {!Element} The element that best matches sampleElement. |
| 2060 */ |
| 2061 getEquivalentElement: function(sampleElement) { |
| 2062 if (this.getFocusableElements().indexOf(sampleElement) >= 0) |
| 2063 return sampleElement; |
| 2064 |
| 2065 var sampleFocusType = this.getTypeForElement(sampleElement); |
| 2066 if (sampleFocusType) { |
| 2067 var sameType = this.getFirstFocusable(sampleFocusType); |
| 2068 if (sameType) |
| 2069 return sameType; |
| 2070 } |
| 2071 |
| 2072 return this.getCustomEquivalent(sampleElement); |
| 2073 }, |
| 2074 |
| 2075 /** |
| 2076 * @param {string=} opt_type An optional type to search for. |
| 2077 * @return {?Element} The first focusable element with |type|. |
| 2078 */ |
| 2079 getFirstFocusable: function(opt_type) { |
| 2080 var filter = opt_type ? '="' + opt_type + '"' : ''; |
| 2081 var elements = this.root.querySelectorAll('[focus-type' + filter + ']'); |
| 2082 for (var i = 0; i < elements.length; ++i) { |
| 2083 if (cr.ui.FocusRow.isFocusable(elements[i])) |
| 2084 return elements[i]; |
| 2085 } |
| 2086 return null; |
| 2087 }, |
| 2088 |
| 2089 /** @return {!Array<!Element>} Registered, focusable elements. */ |
| 2090 getFocusableElements: function() { |
| 2091 return this.getElements().filter(cr.ui.FocusRow.isFocusable); |
| 2092 }, |
| 2093 |
| 2094 /** |
| 2095 * @param {!Element} element An element to determine a focus type for. |
| 2096 * @return {string} The focus type for |element| or '' if none. |
| 2097 */ |
| 2098 getTypeForElement: function(element) { |
| 2099 return element.getAttribute('focus-type') || ''; |
| 2100 }, |
| 2101 |
| 2102 /** @return {boolean} Whether this row is currently active. */ |
| 2103 isActive: function() { |
| 2104 return this.root.classList.contains(FocusRow.ACTIVE_CLASS); |
| 2105 }, |
| 2106 |
| 2107 /** |
| 2108 * Enables/disables the tabIndex of the focusable elements in the FocusRow. |
| 2109 * tabIndex can be set properly. |
| 2110 * @param {boolean} active True if tab is allowed for this row. |
| 2111 */ |
| 2112 makeActive: function(active) { |
| 2113 if (active == this.isActive()) |
| 2114 return; |
| 2115 |
| 2116 this.getElements().forEach(function(element) { |
| 2117 element.tabIndex = active ? 0 : -1; |
| 2118 }); |
| 2119 |
| 2120 this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active); |
| 2121 }, |
| 2122 |
| 2123 /** |
| 2124 * @param {!Event} e |
| 2125 * @private |
| 2126 */ |
| 2127 onBlur_: function(e) { |
| 2128 if (!this.boundary_.contains(/** @type {Node} */(e.relatedTarget))) |
| 2129 return; |
| 2130 |
| 2131 if (this.getFocusableElements().indexOf(e.currentTarget) >= 0) |
| 2132 this.makeActive(false); |
| 2133 }, |
| 2134 |
| 2135 /** |
| 2136 * @param {!Event} e |
| 2137 * @private |
| 2138 */ |
| 2139 onFocus_: function(e) { |
| 2140 if (this.delegate) |
| 2141 this.delegate.onFocus(this, e); |
| 2142 }, |
| 2143 |
| 2144 /** |
| 2145 * @param {!Event} e A mousedown event. |
| 2146 * @private |
| 2147 */ |
| 2148 onMousedown_: function(e) { |
| 2149 // Only accept left mouse clicks. |
| 2150 if (e.button) |
| 2151 return; |
| 2152 |
| 2153 // Allow the element under the mouse cursor to be focusable. |
| 2154 if (!e.currentTarget.disabled) |
| 2155 e.currentTarget.tabIndex = 0; |
| 2156 }, |
| 2157 |
| 2158 /** |
| 2159 * @param {Event} e The keydown event. |
| 2160 * @private |
| 2161 */ |
| 2162 onKeydown_: function(e) { |
| 2163 var elements = this.getFocusableElements(); |
| 2164 var elementIndex = elements.indexOf(e.currentTarget); |
| 2165 assert(elementIndex >= 0); |
| 2166 |
| 2167 if (this.delegate && this.delegate.onKeydown(this, e)) |
| 2168 return; |
| 2169 |
| 2170 if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) |
| 2171 return; |
| 2172 |
| 2173 var index = -1; |
| 2174 |
| 2175 if (e.keyIdentifier == 'Left') |
| 2176 index = elementIndex + (isRTL() ? 1 : -1); |
| 2177 else if (e.keyIdentifier == 'Right') |
| 2178 index = elementIndex + (isRTL() ? -1 : 1); |
| 2179 else if (e.keyIdentifier == 'Home') |
| 2180 index = 0; |
| 2181 else if (e.keyIdentifier == 'End') |
| 2182 index = elements.length - 1; |
| 2183 |
| 2184 var elementToFocus = elements[index]; |
| 2185 if (elementToFocus) { |
| 2186 this.getEquivalentElement(elementToFocus).focus(); |
| 2187 e.preventDefault(); |
| 2188 } |
| 2189 }, |
| 2190 }; |
| 2191 |
| 2192 return { |
| 2193 FocusRow: FocusRow, |
| 2194 }; |
| 2195 }); |
| 2196 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2197 // Use of this source code is governed by a BSD-style license that can be |
| 2198 // found in the LICENSE file. |
| 2199 |
| 2200 cr.define('downloads', function() { |
| 2201 /** |
| 2202 * @param {!Element} root |
| 2203 * @param {?Node} boundary |
| 2204 * @constructor |
| 2205 * @extends {cr.ui.FocusRow} |
| 2206 */ |
| 2207 function FocusRow(root, boundary) { |
| 2208 cr.ui.FocusRow.call(this, root, boundary); |
| 2209 this.addItems(); |
| 2210 } |
| 2211 |
| 2212 FocusRow.prototype = { |
| 2213 __proto__: cr.ui.FocusRow.prototype, |
| 2214 |
| 2215 addItems: function() { |
| 2216 this.destroy(); |
| 2217 |
| 2218 this.addItem('name-file-link', |
| 2219 'content.is-active:not(.show-progress):not(.dangerous) #name'); |
| 2220 assert(this.addItem('name-file-link', '#file-link')); |
| 2221 assert(this.addItem('url', '#url')); |
| 2222 this.addItem('show-retry', '#show'); |
| 2223 this.addItem('show-retry', '#retry'); |
| 2224 this.addItem('pause-resume', '#pause'); |
| 2225 this.addItem('pause-resume', '#resume'); |
| 2226 this.addItem('cancel', '#cancel'); |
| 2227 this.addItem('controlled-by', '#controlled-by a'); |
| 2228 this.addItem('danger-remove-discard', '#discard'); |
| 2229 this.addItem('restore-save', '#save'); |
| 2230 this.addItem('danger-remove-discard', '#danger-remove'); |
| 2231 this.addItem('restore-save', '#restore'); |
| 2232 assert(this.addItem('remove', '#remove')); |
| 2233 |
| 2234 // TODO(dbeam): it would be nice to do this asynchronously (so if multiple |
| 2235 // templates get rendered we only re-add once), but Manager#updateItem_() |
| 2236 // relies on the DOM being re-rendered synchronously. |
| 2237 this.eventTracker.add(this.root, 'dom-change', this.addItems.bind(this)); |
| 2238 }, |
| 2239 }; |
| 2240 |
| 2241 return {FocusRow: FocusRow}; |
| 2242 }); |
| 2243 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2244 // Use of this source code is governed by a BSD-style license that can be |
| 2245 // found in the LICENSE file. |
| 2246 |
| 2247 // Action links are elements that are used to perform an in-page navigation or |
| 2248 // action (e.g. showing a dialog). |
| 2249 // |
| 2250 // They look like normal anchor (<a>) tags as their text color is blue. However, |
| 2251 // they're subtly different as they're not initially underlined (giving users a |
| 2252 // clue that underlined links navigate while action links don't). |
| 2253 // |
| 2254 // Action links look very similar to normal links when hovered (hand cursor, |
| 2255 // underlined). This gives the user an idea that clicking this link will do |
| 2256 // something similar to navigation but in the same page. |
| 2257 // |
| 2258 // They can be created in JavaScript like this: |
| 2259 // |
| 2260 // var link = document.createElement('a', 'action-link'); // Note second arg. |
| 2261 // |
| 2262 // or with a constructor like this: |
| 2263 // |
| 2264 // var link = new ActionLink(); |
| 2265 // |
| 2266 // They can be used easily from HTML as well, like so: |
| 2267 // |
| 2268 // <a is="action-link">Click me!</a> |
| 2269 // |
| 2270 // NOTE: <action-link> and document.createElement('action-link') don't work. |
| 2271 |
| 2272 /** |
| 2273 * @constructor |
| 2274 * @extends {HTMLAnchorElement} |
| 2275 */ |
| 2276 var ActionLink = document.registerElement('action-link', { |
| 2277 prototype: { |
| 2278 __proto__: HTMLAnchorElement.prototype, |
| 2279 |
| 2280 /** @this {ActionLink} */ |
| 2281 createdCallback: function() { |
| 2282 // Action links can start disabled (e.g. <a is="action-link" disabled>). |
| 2283 this.tabIndex = this.disabled ? -1 : 0; |
| 2284 |
| 2285 if (!this.hasAttribute('role')) |
| 2286 this.setAttribute('role', 'link'); |
| 2287 |
| 2288 this.addEventListener('keydown', function(e) { |
| 2289 if (!this.disabled && e.keyIdentifier == 'Enter') { |
| 2290 // Schedule a click asynchronously because other 'keydown' handlers |
| 2291 // may still run later (e.g. document.addEventListener('keydown')). |
| 2292 // Specifically options dialogs break when this timeout isn't here. |
| 2293 // NOTE: this affects the "trusted" state of the ensuing click. I |
| 2294 // haven't found anything that breaks because of this (yet). |
| 2295 window.setTimeout(this.click.bind(this), 0); |
| 2296 } |
| 2297 }); |
| 2298 |
| 2299 function preventDefault(e) { |
| 2300 e.preventDefault(); |
| 2301 } |
| 2302 |
| 2303 function removePreventDefault() { |
| 2304 document.removeEventListener('selectstart', preventDefault); |
| 2305 document.removeEventListener('mouseup', removePreventDefault); |
| 2306 } |
| 2307 |
| 2308 this.addEventListener('mousedown', function() { |
| 2309 // This handlers strives to match the behavior of <a href="...">. |
| 2310 |
| 2311 // While the mouse is down, prevent text selection from dragging. |
| 2312 document.addEventListener('selectstart', preventDefault); |
| 2313 document.addEventListener('mouseup', removePreventDefault); |
| 2314 |
| 2315 // If focus started via mouse press, don't show an outline. |
| 2316 if (document.activeElement != this) |
| 2317 this.classList.add('no-outline'); |
| 2318 }); |
| 2319 |
| 2320 this.addEventListener('blur', function() { |
| 2321 this.classList.remove('no-outline'); |
| 2322 }); |
| 2323 }, |
| 2324 |
| 2325 /** @type {boolean} */ |
| 2326 set disabled(disabled) { |
| 2327 if (disabled) |
| 2328 HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', ''); |
| 2329 else |
| 2330 HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled'); |
| 2331 this.tabIndex = disabled ? -1 : 0; |
| 2332 }, |
| 2333 get disabled() { |
| 2334 return this.hasAttribute('disabled'); |
| 2335 }, |
| 2336 |
| 2337 /** @override */ |
| 2338 setAttribute: function(attr, val) { |
| 2339 if (attr.toLowerCase() == 'disabled') |
| 2340 this.disabled = true; |
| 2341 else |
| 2342 HTMLAnchorElement.prototype.setAttribute.apply(this, arguments); |
| 2343 }, |
| 2344 |
| 2345 /** @override */ |
| 2346 removeAttribute: function(attr) { |
| 2347 if (attr.toLowerCase() == 'disabled') |
| 2348 this.disabled = false; |
| 2349 else |
| 2350 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); |
| 2351 }, |
| 2352 }, |
| 2353 |
| 2354 extends: 'a', |
| 2355 }); |
| 2356 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2357 // Use of this source code is governed by a BSD-style license that can be |
| 2358 // found in the LICENSE file. |
| 2359 |
| 2360 /** @typedef {{img: HTMLImageElement, url: string}} */ |
| 2361 var LoadIconRequest; |
| 2362 |
| 2363 cr.define('downloads', function() { |
| 2364 /** |
| 2365 * @param {number} maxAllowed The maximum number of simultaneous downloads |
| 2366 * allowed. |
| 2367 * @constructor |
| 2368 */ |
| 2369 function ThrottledIconLoader(maxAllowed) { |
| 2370 assert(maxAllowed > 0); |
| 2371 |
| 2372 /** @private {number} */ |
| 2373 this.maxAllowed_ = maxAllowed; |
| 2374 |
| 2375 /** @private {!Array<!LoadIconRequest>} */ |
| 2376 this.requests_ = []; |
| 2377 } |
| 2378 |
| 2379 ThrottledIconLoader.prototype = { |
| 2380 /** @private {number} */ |
| 2381 loading_: 0, |
| 2382 |
| 2383 /** |
| 2384 * Load the provided |url| into |img.src| after appending ?scale=. |
| 2385 * @param {!HTMLImageElement} img An <img> to show the loaded image in. |
| 2386 * @param {string} url A remote image URL to load. |
| 2387 */ |
| 2388 loadScaledIcon: function(img, url) { |
| 2389 var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x'; |
| 2390 if (img.src == scaledUrl) |
| 2391 return; |
| 2392 |
| 2393 this.requests_.push({img: img, url: scaledUrl}); |
| 2394 this.loadNextIcon_(); |
| 2395 }, |
| 2396 |
| 2397 /** @private */ |
| 2398 loadNextIcon_: function() { |
| 2399 if (this.loading_ > this.maxAllowed_ || !this.requests_.length) |
| 2400 return; |
| 2401 |
| 2402 var request = this.requests_.shift(); |
| 2403 var img = request.img; |
| 2404 |
| 2405 img.onabort = img.onerror = img.onload = function() { |
| 2406 this.loading_--; |
| 2407 this.loadNextIcon_(); |
| 2408 }.bind(this); |
| 2409 |
| 2410 this.loading_++; |
| 2411 img.src = request.url; |
| 2412 }, |
| 2413 }; |
| 2414 |
| 2415 return {ThrottledIconLoader: ThrottledIconLoader}; |
| 2416 }); |
| 2417 // Copyright 2014 Google Inc. All rights reserved. |
| 2418 // |
| 2419 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 2420 // you may not use this file except in compliance with the License. |
| 2421 // You may obtain a copy of the License at |
| 2422 // |
| 2423 // http://www.apache.org/licenses/LICENSE-2.0 |
| 2424 // |
| 2425 // Unless required by applicable law or agreed to in writing, software |
| 2426 // distributed under the License is distributed on an "AS IS" BASIS, |
| 2427 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 2428 // See the License for the specific language governing permissions and |
| 2429 // limitations under the License. |
| 2430 |
| 2431 !function(a,b){b["true"]=a;var c={},d={},e={},f=null;!function(a){function b(a){
if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}functi
on c(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,t
his._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal"
,this._easing="linear"}function d(b,d){var e=new c;return d&&(e.fill="both",e.du
ration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNam
es(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof e[c]||"duration"
==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==s.indexOf(b[
c]))return;if("direction"==c&&-1==t.indexOf(b[c]))return;if("playbackRate"==c&&1
!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use A
nimation.playbackRate instead."))return;e[c]=b[c]}}):e.duration=b,e}function e(a
){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function f(
b,c){b=a.numericTimingToObject(b);var e=d(b,c);return e._easing=i(e.easing),e}fu
nction g(a,b,c,d){return 0>a||a>1||0>c||c>1?B:function(e){function f(a,b,c){retu
rn 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(0==e||1==e)return e;for(var g=0,h=1;
;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}}
function h(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c%
d}}function i(a){var b=z.exec(a);if(b)return g.apply(this,b.slice(1).map(Number)
);var c=A.exec(a);if(c)return h(Number(c[1]),{start:u,middle:v,end:w}[c[2]]);var
d=x[a];return d?d:B}function j(a){return Math.abs(k(a)/a.playbackRate)}function
k(a){return a.duration*a.iterations}function l(a,b,c){return null==b?C:b<c.dela
y?D:b>=c.delay+a?E:F}function m(a,b,c,d,e){switch(d){case D:return"backwards"==b
||"both"==b?0:null;case F:return c-e;case E:return"forwards"==b||"both"==b?a:nul
l;case C:return null}}function n(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playb
ackRate+c}function o(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&&
(e.iterations+e.iterationStart)%1==0?a:c%a}function p(a,b,c,d){return 0===c?0:b=
=a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function q(a,b,c,d){var e=a%2
>=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g=
f?c:b-c,h=g/b;return b*d.easing(h)}function r(a,b,c){var d=l(a,b,c),e=m(a,c.fill
,b,d,c.delay);if(null===e)return null;if(0===a)return d===D?0:1;var f=c.iteratio
nStart*c.duration,g=n(a,e,f,c),h=o(c.duration,k(c),g,f,c),i=p(c.duration,h,g,c);
return q(i,c.duration,h,c)/c.duration}var s="backwards|forwards|both|none".split
("|"),t="reverse|alternate|alternate-reverse".split("|");c.prototype={_setMember
:function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this.
_effect._timing=a.normalizeTimingInput(a.normalizeTimingInput(this._effect._timi
ngInput)),this._effect.activeDuration=a.calculateActiveDuration(this._effect._ti
ming),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimati
on())},get playbackRate(){return this._playbackRate},set delay(a){this._setMembe
r("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("e
ndDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember("
fill",a)},get fill(){return this._fill},set iterationStart(a){this._setMember("i
terationStart",a)},get iterationStart(){return this._iterationStart},set duratio
n(a){this._setMember("duration",a)},get duration(){return this._duration},set di
rection(a){this._setMember("direction",a)},get direction(){return this._directio
n},set easing(a){this._setMember("easing",a)},get easing(){return this._easing},
set iterations(a){this._setMember("iterations",a)},get iterations(){return this.
_iterations}};var u=1,v=.5,w=0,x={ease:g(.25,.1,.25,1),"ease-in":g(.42,0,1,1),"e
ase-out":g(0,0,.58,1),"ease-in-out":g(.42,0,.58,1),"step-start":h(1,u),"step-mid
dle":h(1,v),"step-end":h(1,w)},y="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",z=new RegE
xp("cubic-bezier\\("+y+","+y+","+y+","+y+"\\)"),A=/steps\(\s*(\d+)\s*,\s*(start|
middle|end)\s*\)/,B=function(a){return a},C=0,D=1,E=2,F=3;a.cloneTimingInput=b,a
.makeTiming=d,a.numericTimingToObject=e,a.normalizeTimingInput=f,a.calculateActi
veDuration=j,a.calculateTimeFraction=r,a.calculatePhase=l,a.toTimingFunction=i}(
c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){va
r g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)}
}else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset&
&(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].of
fset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].off
set=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError
("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var
e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null!
=f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be num
bers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"N
otSupportedError",message:"add compositing is not supported"};f="easing"==e?a.to
TimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0=
=d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;h<e.lengt
h;h++){var i=e[h].offset;if(null!=i){if(g>i)throw{code:DOMException.INVALID_MODI
FICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely
sorted by offset. Sort or specify offsets."};g=i}else f=!1}return e=e.filter(fun
ction(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={background:["backgrou
ndImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAtt
achment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderT
opColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle"
,"borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth",
"borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBott
omWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor",
"borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLef
tWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius"
,"borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],bord
erRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["b
orderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth",
"borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flex
Shrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fo
ntWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","margin
Left"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingT
op","paddingRight","paddingBottom","paddingLeft"]},f=document.createElementNS("h
ttp://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={b
orderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,fontSiz
e:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-la
rge":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth
:g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px
transparent"}};a.normalizeKeyframes=d}(c,f),function(a){var b={};a.isDeprecated=
function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth(
h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecate
d and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecat
ed=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Err
or(b+" "+f+" no longer supported. "+d)}}(c),function(){if(document.documentEleme
nt.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play|
currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split(
"|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}!function(a,b){funct
ion c(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easi
ng"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][
d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!=
g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupporte
dError",message:"Partial keyframes are not supported"}}return b}function d(a){va
r c=[];for(var d in a)for(var e=a[d],f=0;f<e.length-1;f++){var g=e[f].offset,h=e
[f+1].offset,i=e[f].value,j=e[f+1].value;g==h&&(1==h?i=j:j=i),c.push({startTime:
g,endTime:h,easing:e[f].easing,property:d,interpolation:b.propertyInterpolation(
d,i,j)})}return c.sort(function(a,b){return a.startTime-b.startTime}),c}b.conver
tEffectInput=function(e){var f=a.normalizeKeyframes(e),g=c(f),h=d(g);return func
tion(a,c){if(null!=c)h.filter(function(a){return 0>=c&&0==a.startTime||c>=1&&1==
a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTim
e,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpol
ation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clea
r(a,d)}}}(c,d,f),function(a){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}fu
nction c(a,c,d){for(var e=0;e<d.length;e++){var f=d[e];b(a,c,f),/-/.test(f)&&b(a
,c,f.replace(/-(.)/g,function(a,b){return b.toUpperCase()}))}}function d(b,c,d){
if("initial"==c||"initial"==d){var g=b.replace(/-(.)/g,function(a,b){return b.to
UpperCase()});"initial"==c&&(c=f[g]),"initial"==d&&(d=f[g])}for(var h=c==d?[]:e[
b],i=0;h&&i<h.length;i++){var j=h[i][0](c),k=h[i][0](d);if(void 0!==j&&void 0!==
k){var l=h[i][1](j,k);if(l){var m=a.Interpolation.apply(null,l);return function(
a){return 0==a?c:1==a?d:m(a)}}}}return a.Interpolation(!1,!0,function(a){return
a?d:c})}var e={};a.addPropertiesHandler=c;var f={backgroundColor:"transparent",b
ackgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadiu
s:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"c
urrentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWi
dth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius:
"0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0
px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto"
,left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginL
eft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",min
Height:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0
px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px",
paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transpare
nt",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto
",wordSpacing:"normal",zIndex:"auto"};a.propertyInterpolation=d}(d,f),function(a
,b){function c(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calc
ulateTimeFraction(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d._isCurr
ent=function(d){var e=a.calculatePhase(c,d,b);return e===PhaseActive||e===PhaseB
efore},d}b.KeyframeEffect=function(d,e,f){var g,h=c(a.normalizeTimingInput(f)),i
=b.convertEffectInput(e),j=function(){i(d,g)};return j._update=function(a){retur
n g=h(a),null!==g},j._clear=function(){i(d,null)},j._hasSameTarget=function(a){r
eturn d===a},j._isCurrent=h._isCurrent,j._totalDuration=h._totalDuration,j},b.Nu
llEffect=function(a){var b=function(){a&&(a(),a=null)};return b._update=function
(){return null},b._totalDuration=0,b._isCurrent=function(){return!1},b._hasSameT
arget=function(){return!1},b}}(c,d,f),function(a){a.apply=function(b,c,d){b.styl
e[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(d,
f),function(a){window.Element.prototype.animate=function(b,c){return a.timeline.
_play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number"
==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boole
an"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;f<a.length;
f++)e.push(b(a[f],c[f],d));return e}throw"Mismatched interpolation arguments "+a
+":"+c}a.Interpolation=function(a,c,d){return function(e){return d(b(a,c,e))}}}(
d,f),function(a,b){a.sequenceNumber=0;var c=function(a,b,c){this.target=a,this.c
urrentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancela
ble=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TA
RGET,this.timeStamp=Date.now()};b.Animation=function(b){this._sequenceNumber=a.s
equenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._p
laybackRate=1,this._inTimeline=!0,this._finishedFlag=!1,this.onfinish=null,this.
_finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._i
dle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function
(){this._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime
?-1:this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(th
is._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function(
a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._cur
rentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get c
urrentTime(){return this._idle||this._currentTimePending?null:this._currentTime}
,set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startT
ime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._cur
rentTimePending=!1,this._currentTime!=a&&(this._tickCurrentTime(a,!0),b.invalida
teEffects()))},get startTime(){return this._startTime},set startTime(a){a=+a,isN
aN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this.
_timeline.currentTime-this._startTime)*this.playbackRate),b.invalidateEffects())
},get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._
playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null,"
paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.curr
entTime=b)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._c
urrentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},ge
t _totalDuration(){return this._effect._totalDuration},get playState(){return th
is._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this
._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished"
:"running"},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(thi
s._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null,b
.invalidateEffects()),this._finishedFlag=!1,b.restart(),this._idle=!1,this._ensu
reAlive()},pause:function(){this._isFinished||this._paused||this._idle||(this._c
urrentTimePending=!0),this._startTime=null,this._paused=!0},finish:function(){th
is._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._sta
rtTime=this._totalDuration-this.currentTime,this._currentTimePending=!1)},cancel
:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this.currentTime=0,
this._startTime=null,this._effect._update(null),b.invalidateEffects(),b.restart(
))},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:funct
ion(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},remove
EventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b
);c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){var b=this._i
sFinished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._curren
tTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTime
out(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag
=b},_tick:function(a){return this._idle||this._paused||(null==this._startTime?th
is.startTime=a-this._currentTime/this.playbackRate:this._isFinished||this._tickC
urrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,t
his._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(c,d,f),
function(a,b){function c(a){var b=i;i=[],a<s.currentTime&&(a=s.currentTime),g(a)
,b.forEach(function(b){b[1](a)}),o&&g(a),f(),l=void 0}function d(a,b){return a._
sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTi
me=window.performance&&performance.now?performance.now():0}function f(){p.forEac
h(function(a){a()}),p.length=0}function g(a){n=!1;var c=b.timeline;c.currentTime
=a,c._animations.sort(d),m=!1;var e=c._animations;c._animations=[];var f=[],g=[]
;e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._ef
fect):f.push(b._effect),b._isFinished||b._paused||b._idle||(m=!0),b._inTimeline}
),p.push.apply(p,f),p.push.apply(p,g),c._animations.push.apply(c._animations,e),
o=!1,m&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i
=[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h
(c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function(
b){b[0]==a&&(b[1]=function(){})})},e.prototype={_play:function(c){c._timing=a.no
rmalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timel
ine=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var k,l=
void 0,k=function(){return void 0==l&&(l=performance.now()),l},m=!1,n=!1;b.resta
rt=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n};var o
=!1;b.invalidateEffects=function(){o=!0};var p=[],q=1e3/60,r=window.getComputedS
tyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable
:!0,value:function(){if(o){var a=k();a-s.currentTime>0&&(s.currentTime+=q*(Math.
floor((a-s.currentTime)/q)+1)),g(s.currentTime)}return f(),r.apply(this,argument
s)}});var s=new e;b.timeline=s}(c,d,f),function(a){function b(a,b){var c=a.exec(
b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void
0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace
(/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e
);if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g
[1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++)
if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,0>=c))break;var e=a(b.substr(
0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c&
&d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b)
;return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return
d?d:[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrim
med(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}return""==
c?d:void 0}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k
=0;j>k;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.pus
h(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b
](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h=
0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0]
),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(functi
on(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+
=f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a
.consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRep
eated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(d),functio
n(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);if(c)return d
.inset=!0,c;var c=a.consumeLengthOrPercent(b);if(c)return d.lengths.push(c[0]),c
;var c=a.consumeColor(b);return c?(d.color=c[0],c):void 0}var d={inset:!1,length
s:[],color:null},e=a.consumeRepeated(c,/^/,b);return e&&e[0].length?[d,e[1]]:voi
d 0}function c(c){var d=a.consumeRepeated(b,/^,/,c);return d&&""==d[1]?d[0]:void
0}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.len
gth);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.l
engths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color
){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDime
nsions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[
2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1
],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f+
+)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function
f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(v
ar g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f
(d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.b
ind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(d),fu
nction(a){function b(a){return a.toFixed(3).replace(".000","")}function c(a,b,c)
{return Math.min(b,Math.max(a,c))}function d(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/
.test(a)?Number(a):void 0}function e(a,c){return[a,c,b]}function f(a,b){return 0
!=a?h(0,1/0)(a,b):void 0}function g(a,b){return[a,b,function(a){return Math.roun
d(c(1,1/0,a))}]}function h(a,d){return function(e,f){return[e,f,function(e){retu
rn b(c(a,d,e))}]}}function i(a,b){return[a,b,Math.round]}a.clamp=c,a.addProperti
esHandler(d,h(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandle
r(d,h(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(d,f,["fle
x-grow","flex-shrink"]),a.addPropertiesHandler(d,g,["orphans","widows"]),a.addPr
opertiesHandler(d,i,["z-index"]),a.parseNumber=d,a.mergeNumbers=e,a.numberToStri
ng=b}(d,f),function(a){function b(a,b){return"visible"==a||"visible"==b?[0,1,fun
ction(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b
,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.f
illStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle)
{e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var
d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(b,c){return[b,c,function(
b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++)
b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"r
gba("+b.join(",")+")"}]}var d=document.createElementNS("http://www.w3.org/1999/x
html","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandl
er(b,c,["background-color","border-bottom-color","border-left-color","border-rig
ht-color","border-top-color","color","outline-color","text-decoration-color"]),a
.consumeColor=a.consumeParenthesised.bind(null,b),a.mergeColors=c}(d,f),function
(a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)retu
rn{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.rep
lace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.repl
ace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\
s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],
g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var
h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g"
),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(a,b){return e(a,b,!
0)}function e(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&&
f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return
c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c=
Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+
c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,n
ew RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|g
rad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a.
consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.con
sumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consum
eRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l(
a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o=
a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPrope
rtiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom
-width","border-image-width","border-left-width","border-right-width","border-to
p-width","flex-basis","font-size","height","line-height","max-height","max-width
","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radi
us","border-bottom-right-radius","border-top-left-radius","border-top-right-radi
us","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right
","margin-top","min-height","min-width","outline-offset","padding-bottom","paddi
ng-left","padding-right","padding-top","perspective","right","shape-margin","tex
t-indent","top","vertical-align","word-spacing"])}(d,f),function(a){function b(b
){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){va
r d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consum
eToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeT
oken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(b,c){
return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"au
to";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}func
tion e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d,"
, ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"])}(d,f),func
tion(a){function b(a){return function(b){var c=0;return a.map(function(a){return
a===j?b[c++]:a})}}function c(a){return a}function d(b){if(b=b.toLowerCase().tri
m(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);)
{if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=m[g];if(!h)return;var i
=c[2].split(","),j=h[0];if(j.length<i.length)return;for(var n=[],o=0;o<j.length;
o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?l:a.parseAngl
e(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()]
(q):{a:l,n:n[0],t:k}[r],void 0===p)return;n.push(p)}if(e.push({t:g,d:n}),d.lastI
ndex==b.length)return e}}function e(a){return a.toFixed(6).replace(".000000","")
}function f(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeM
atrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var f=a.
makeMatrixDecomposition(c)}return null==d[0]||null==f[0]?[[!1],[!0],function(a){
return a?c[0].d:b[0].d}]:(d[0].push(0),f[0].push(1),[d,f,function(b){var c=a.qua
t(d[0][3],f[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(e).join
(",");return h}])}function g(a){return a.replace(/[xy]/,"")}function h(a){return
a.replace(/(x|y|z|3d)?$/,"3d")}function i(b,c){var d=a.makeMatrixDecomposition&
&!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var i=0;i<b.leng
th;i++){var j=b[i].t,k=b[i].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(f
unction(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return
b})})}}var n=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"==
a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.l
ength){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else fo
r(var i=0;i<b.length;i++){var j,s=b[i].t,t=c[i].t,u=b[i].d,v=c[i].d,w=m[s],x=m[t
];if(n(s,t)){if(!d)return;var r=f([b[i]],[c[i]]);o.push(r[0]),p.push(r[1]),q.pus
h(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&g(s)==g(t))j=g(s),u=w[
2](u),v=x[2](v);else{if(!w[1]||!x[1]||h(s)!=h(t)){if(!d)return;var r=f(b,c);o=[r
[0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=h(s),u=w[1](u),v=x[1](v)}for(var y=[
],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mer
geDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z)
,q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(functi
on(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix
"==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).jo
in(" ")}]}var j=null,k={px:0},l={deg:0},m={matrix:["NNNNNN",[j,j,0,0,j,j,0,0,0,0
,1,0,j,j,0,1],c],matrix3d:["NNNNNNNNNNNNNNNN",c],rotate:["A"],rotatex:["A"],rota
tey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",b([j,j,1
]),c],scalex:["N",b([j,1,1]),b([j,1])],scaley:["N",b([1,j,1]),b([1,j])],scalez:[
"N",b([1,1,j])],scale3d:["NNN",c],skew:["Aa",null,c],skewx:["A",null,b([j,l])],s
kewy:["A",null,b([l,j])],translate:["Tt",b([j,j,k]),c],translatex:["T",b([j,k,k]
),b([j,k])],translatey:["T",b([k,j,k]),b([k,j])],translatez:["L",b([k,k,j])],tra
nslate3d:["TTL",c]};a.addPropertiesHandler(d,i,["transform"])}(d,f),function(a){
function b(a,b){b.concat([a]).forEach(function(b){b in document.documentElement.
style&&(c[a]=b)})}var c={};b("transform",["webkitTransform","msTransform"]),b("t
ransformOrigin",["webkitTransformOrigin"]),b("perspective",["webkitPerspective"]
),b("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){
return c[a]||a}}(d,f)}(),!function(a,b){function c(a){var b=window.document.time
line;b.currentTime=a,b._discardAnimations(),0==b._animations.length?e=!1:request
AnimationFrame(c)}var d=window.requestAnimationFrame;window.requestAnimationFram
e=function(a){return d(function(b){window.document.timeline._updateAnimationsPro
mises(),a(b),window.document.timeline._updateAnimationsPromises()})},b.Animation
Timeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTime
line.prototype={getAnimations:function(){return this._discardAnimations(),this._
animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromise
s=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_dis
cardAnimations:function(){this._updateAnimationsPromises(),this._animations=this
._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playSta
te})},_play:function(a){var c=new b.Animation(a,this);return this._animations.pu
sh(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c
._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var
e=!1;b.restartWebAnimationsNextTick=function(){e||(e=!0,requestAnimationFrame(c)
)};var f=new b.AnimationTimeline;b.timeline=f;try{Object.defineProperty(window.d
ocument,"timeline",{configurable:!0,get:function(){return f}})}catch(g){}try{win
dow.document.timeline=f}catch(g){}}(c,e,f),function(a,b){b.animationsWithPromise
s=[],b.Animation=function(b,c){if(this.effect=b,b&&(b._animation=this),!c)throw
new Error("Animation with null timeline is not supported");this._timeline=c,this
._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGro
up=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._ol
dPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),th
is._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=th
is._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?(
this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolve
ReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise&
&b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0):
"finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromi
se=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finished
Promise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d
,e,f=this._animation?!0:!1;f&&(a=this.playbackRate,c=this._paused,d=this.startTi
me,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,thi
s._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&
&(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindA
nimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||
this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnim
ationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effe
ct._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate=
a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&&
(this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises() |
| 2432 },_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this.
effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChil
dren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration
(c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&thi
s._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b]
._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChild
Animations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.d
elay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var
d=window.document.timeline._play(c);this._childAnimations.push(d),d.playbackRate
=this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,t
his._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.gr
oupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.s
tartTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.s
tartTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)},
get timeline(){return this._timeline},get playState(){return this._animation?thi
s._animation.playState:"idle"},get finished(){return window.Promise?(this._finis
hedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromise
s.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinish
edPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOM
Exception.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState
&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animatio
n Promises require JavaScript Promise constructor"),null)},get ready(){return wi
ndow.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b
.animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){
this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function(
){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==t
his.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("A
nimation Promises require JavaScript Promise constructor"),null)},get onfinish()
{return this._onfinish},set onfinish(a){"function"==typeof a?(this._onfinish=a,t
his._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this)):(t
his._animation.onfinish=a,this.onfinish=this._animation.onfinish)},get currentTi
me(){this._updatePromises();var a=this._animation.currentTime;return this._updat
ePromises(),a},set currentTime(a){this._updatePromises(),this._animation.current
Time=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachC
hild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){r
eturn this._animation.startTime},set startTime(a){this._updatePromises(),this._a
nimation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),
this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get p
laybackRate(){return this._animation.playbackRate},set playbackRate(a){this._upd
atePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEac
hChild(function(b){b.playbackRate=a}),"paused"!=this.playState&&"idle"!=this.pla
yState&&this.play(),null!==b&&(this.currentTime=b),this._updatePromises()},play:
function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==thi
s._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),thi
s._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.curr
entTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this
._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._ani
mation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this.
_paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this
._animation.finish(),this._register(),this._updatePromises()},cancel:function(){
this._updatePromises(),this._animation.cancel(),this._register(),this._removeChi
ldAnimations(),this._updatePromises()},reverse:function(){this._updatePromises()
;var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a)
{a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventLi
stener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this,
b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)},
removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wr
apper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;)
this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this.
effect.children&&this._childAnimations.length<this.effect.children.length&&this.
_constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(thi
s,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration
)}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.curre
ntTime;null!==e&&(e=a.calculateTimeFraction(a.calculateActiveDuration(d),e,d)),(
null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animatio
n}(c,e,f),function(a,b){function c(b){this._frames=a.normalizeKeyframes(b)}funct
ion d(){for(var a=!1;h.length;){var b=h.shift();b._updateChildren(),a=!0}return
a}var e=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a
instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)e(a.children[
b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._pa
rent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._p
arent.children.indexOf(d),1),d._parent=null,e(d)):d._animation&&d._animation.eff
ect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d
._animation._callback&&(d._animation._callback._animation=null),d._animation._re
buildUnderlyingAnimation(),e(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.Keyfr
ameEffect=function(b,d,e){return this.target=b,this._parent=null,e=a.numericTimi
ngToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTi
mingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"funct
ion"==typeof d?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeE
ffect.onsample instead."),this._normalizedKeyframes=d):this._normalizedKeyframes
=new c(d),this._keyframes=d,this.activeDuration=a.calculateActiveDuration(this._
timing),this},b.KeyframeEffect.prototype={getFrames:function(){return"function"=
=typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyf
rames._frames},set onsample(a){if("function"==typeof this.getFrames())throw new
Error("Setting onsample on custom effect KeyframeEffect is not supported.");this
._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get
parent(){return this._parent},clone:function(){if("function"==typeof this.getFr
ames())throw new Error("Cloning custom effects is not supported.");var b=new Key
frameEffect(this.target,[],a.cloneTimingInput(this._timingInput));return b._norm
alizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove
:function(){b.removeMulti([this])}};var f=Element.prototype.animate;Element.prot
otype.animate=function(a,c){return b.timeline._play(new b.KeyframeEffect(this,a,
c))};var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newU
nderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||g,c=a._key
frames;"function"==typeof c&&(c=[]);var d=a._timingInput}else var b=g,c=[],d=0;r
eturn f.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&"
function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect(
a)};var h=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==h.
length&&requestAnimationFrame(d),h.push(a))};var i=window.getComputedStyle;Objec
t.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:
function(){window.document.timeline._updateAnimationsPromises();var a=i.apply(th
is,arguments);return d()&&(a=i.apply(this,arguments)),window.document.timeline._
updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.El
ement.prototype.getAnimations=function(){return document.timeline.getAnimations(
).filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))}
}(c,e,f),function(a,b){function c(a){a._registered||(a._registered=!0,f.push(a),
g||(g=!0,requestAnimationFrame(d)))}function d(){var a=f;f=[],a.sort(function(a,
b){return a._sequenceNumber-b._sequenceNumber}),a=a.filter(function(a){a();var b
=a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a
._registered=!1),a._registered}),f.push.apply(f,a),f.length?(g=!0,requestAnimati
onFrame(d)):g=!1}var e=(document.createElementNS("http://www.w3.org/1999/xhtml",
"div"),0);b.bindAnimationForCustomEffect=function(b){var d,f=b.effect.target,g="
function"==typeof b.effect.getFrames();d=g?b.effect.getFrames():b.effect._onsamp
le;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var
c=j._animation?j._animation.currentTime:null;null!==c&&(c=a.calculateTimeFracti
on(a.calculateActiveDuration(h),c,h),isNaN(c)&&(c=null)),c!==i&&(g?d(c,f,b.effec
t):d(c,b.effect,b.effect._animation)),i=c};j._animation=b,j._registered=!1,j._se
quenceNumber=e++,b._callback=j,c(j)};var f=[],g=!1;b.Animation.prototype._regist
er=function(){this._callback&&c(this._callback)}}(c,e,f),function(a,b){function
c(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function d(b,c){
this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericT
imingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normaliz
eTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this,
"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}wind
ow.SequenceEffect=function(){d.apply(this,arguments)},window.GroupEffect=functio
n(){d.apply(this,arguments)},d.prototype={_isAncestor:function(a){for(var b=this
;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a
=this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._
parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent
:function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_put
Child:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prep
end an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type
:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for(
var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);th
is._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)}
,prepend:function(){this._putChild(arguments,!1)},get parent(){return this._pare
nt},get firstChild(){return this.children.length?this.children[0]:null},get last
Child(){return this.children.length?this.children[this.children.length-1]:null},
clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this
.children.length;d++)c.push(this.children[d].clone());return this instanceof Gro
upEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.remov
eMulti([this])}},window.SequenceEffect.prototype=Object.create(d.prototype),Obje
ct.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function
(){var a=0;return this.children.forEach(function(b){a+=c(b)}),Math.max(a,0)}}),w
indow.GroupEffect.prototype=Object.create(d.prototype),Object.defineProperty(win
dow.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.c
hildren.forEach(function(b){a=Math.max(a,c(b))}),a}}),b.newUnderlyingAnimationFo
rGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;return c&&"pendin
g"!=c.playState&&c.effect?null==b?void c._removeChildAnimations():0==b&&c.playba
ckRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateTimeFract
ion(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(funct
ion(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0:void 0},g=new
KeyframeEffect(null,[],c._timing);return g.onsample=f,d=b.timeline._play(g)},b.b
indAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitSt
artTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChil
dDuration=c}(c,e,f)}({},function(){return this}()); |
| 2433 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2434 // Use of this source code is governed by a BSD-style license that can be |
| 2435 // found in the LICENSE file. |
| 2436 |
| 2437 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js"> |
| 2438 |
| 2439 i18nTemplate.process(document, loadTimeData); |
| 2440 (function () { |
| 2441 function resolve() { |
| 2442 document.body.removeAttribute('unresolved'); |
| 2443 } |
| 2444 if (window.WebComponents) { |
| 2445 addEventListener('WebComponentsReady', resolve); |
| 2446 } else { |
| 2447 if (document.readyState === 'interactive' || document.readyState === 'complete')
{ |
| 2448 resolve(); |
| 2449 } else { |
| 2450 addEventListener('DOMContentLoaded', resolve); |
| 2451 } |
| 2452 } |
| 2453 }()); |
| 2454 window.Polymer = { |
| 2455 Settings: function () { |
| 2456 var user = window.Polymer || {}; |
| 2457 location.search.slice(1).split('&').forEach(function (o) { |
| 2458 o = o.split('='); |
| 2459 o[0] && (user[o[0]] = o[1] || true); |
| 2460 }); |
| 2461 var wantShadow = user.dom === 'shadow'; |
| 2462 var hasShadow = Boolean(Element.prototype.createShadowRoot); |
| 2463 var nativeShadow = hasShadow && !window.ShadowDOMPolyfill; |
| 2464 var useShadow = wantShadow && hasShadow; |
| 2465 var hasNativeImports = Boolean('import' in document.createElement('link')); |
| 2466 var useNativeImports = hasNativeImports; |
| 2467 var useNativeCustomElements = !window.CustomElements || window.CustomElements.us
eNative; |
| 2468 return { |
| 2469 wantShadow: wantShadow, |
| 2470 hasShadow: hasShadow, |
| 2471 nativeShadow: nativeShadow, |
| 2472 useShadow: useShadow, |
| 2473 useNativeShadow: useShadow && nativeShadow, |
| 2474 useNativeImports: useNativeImports, |
| 2475 useNativeCustomElements: useNativeCustomElements |
| 2476 }; |
| 2477 }() |
| 2478 }; |
| 2479 (function () { |
| 2480 var userPolymer = window.Polymer; |
| 2481 window.Polymer = function (prototype) { |
| 2482 if (typeof prototype === 'function') { |
| 2483 prototype = prototype.prototype; |
| 2484 } |
| 2485 if (!prototype) { |
| 2486 prototype = {}; |
| 2487 } |
| 2488 var factory = desugar(prototype); |
| 2489 prototype = factory.prototype; |
| 2490 var options = { prototype: prototype }; |
| 2491 if (prototype.extends) { |
| 2492 options.extends = prototype.extends; |
| 2493 } |
| 2494 Polymer.telemetry._registrate(prototype); |
| 2495 document.registerElement(prototype.is, options); |
| 2496 return factory; |
| 2497 }; |
| 2498 var desugar = function (prototype) { |
| 2499 var base = Polymer.Base; |
| 2500 if (prototype.extends) { |
| 2501 base = Polymer.Base._getExtendedPrototype(prototype.extends); |
| 2502 } |
| 2503 prototype = Polymer.Base.chainObject(prototype, base); |
| 2504 prototype.registerCallback(); |
| 2505 return prototype.constructor; |
| 2506 }; |
| 2507 window.Polymer = Polymer; |
| 2508 if (userPolymer) { |
| 2509 for (var i in userPolymer) { |
| 2510 Polymer[i] = userPolymer[i]; |
| 2511 } |
| 2512 } |
| 2513 Polymer.Class = desugar; |
| 2514 }()); |
| 2515 Polymer.telemetry = { |
| 2516 registrations: [], |
| 2517 _regLog: function (prototype) { |
| 2518 console.log('[' + prototype.is + ']: registered'); |
| 2519 }, |
| 2520 _registrate: function (prototype) { |
| 2521 this.registrations.push(prototype); |
| 2522 Polymer.log && this._regLog(prototype); |
| 2523 }, |
| 2524 dumpRegistrations: function () { |
| 2525 this.registrations.forEach(this._regLog); |
| 2526 } |
| 2527 }; |
| 2528 Object.defineProperty(window, 'currentImport', { |
| 2529 enumerable: true, |
| 2530 configurable: true, |
| 2531 get: function () { |
| 2532 return (document._currentScript || document.currentScript).ownerDocument; |
| 2533 } |
| 2534 }); |
| 2535 Polymer.RenderStatus = { |
| 2536 _ready: false, |
| 2537 _callbacks: [], |
| 2538 whenReady: function (cb) { |
| 2539 if (this._ready) { |
| 2540 cb(); |
| 2541 } else { |
| 2542 this._callbacks.push(cb); |
| 2543 } |
| 2544 }, |
| 2545 _makeReady: function () { |
| 2546 this._ready = true; |
| 2547 this._callbacks.forEach(function (cb) { |
| 2548 cb(); |
| 2549 }); |
| 2550 this._callbacks = []; |
| 2551 }, |
| 2552 _catchFirstRender: function () { |
| 2553 requestAnimationFrame(function () { |
| 2554 Polymer.RenderStatus._makeReady(); |
| 2555 }); |
| 2556 } |
| 2557 }; |
| 2558 if (window.HTMLImports) { |
| 2559 HTMLImports.whenReady(function () { |
| 2560 Polymer.RenderStatus._catchFirstRender(); |
| 2561 }); |
| 2562 } else { |
| 2563 Polymer.RenderStatus._catchFirstRender(); |
| 2564 } |
| 2565 Polymer.ImportStatus = Polymer.RenderStatus; |
| 2566 Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady; |
| 2567 Polymer.Base = { |
| 2568 __isPolymerInstance__: true, |
| 2569 _addFeature: function (feature) { |
| 2570 this.extend(this, feature); |
| 2571 }, |
| 2572 registerCallback: function () { |
| 2573 this._desugarBehaviors(); |
| 2574 this._doBehavior('beforeRegister'); |
| 2575 this._registerFeatures(); |
| 2576 this._doBehavior('registered'); |
| 2577 }, |
| 2578 createdCallback: function () { |
| 2579 Polymer.telemetry.instanceCount++; |
| 2580 this.root = this; |
| 2581 this._doBehavior('created'); |
| 2582 this._initFeatures(); |
| 2583 }, |
| 2584 attachedCallback: function () { |
| 2585 Polymer.RenderStatus.whenReady(function () { |
| 2586 this.isAttached = true; |
| 2587 this._doBehavior('attached'); |
| 2588 }.bind(this)); |
| 2589 }, |
| 2590 detachedCallback: function () { |
| 2591 this.isAttached = false; |
| 2592 this._doBehavior('detached'); |
| 2593 }, |
| 2594 attributeChangedCallback: function (name) { |
| 2595 this._attributeChangedImpl(name); |
| 2596 this._doBehavior('attributeChanged', arguments); |
| 2597 }, |
| 2598 _attributeChangedImpl: function (name) { |
| 2599 this._setAttributeToProperty(this, name); |
| 2600 }, |
| 2601 extend: function (prototype, api) { |
| 2602 if (prototype && api) { |
| 2603 Object.getOwnPropertyNames(api).forEach(function (n) { |
| 2604 this.copyOwnProperty(n, api, prototype); |
| 2605 }, this); |
| 2606 } |
| 2607 return prototype || api; |
| 2608 }, |
| 2609 mixin: function (target, source) { |
| 2610 for (var i in source) { |
| 2611 target[i] = source[i]; |
| 2612 } |
| 2613 return target; |
| 2614 }, |
| 2615 copyOwnProperty: function (name, source, target) { |
| 2616 var pd = Object.getOwnPropertyDescriptor(source, name); |
| 2617 if (pd) { |
| 2618 Object.defineProperty(target, name, pd); |
| 2619 } |
| 2620 }, |
| 2621 _log: console.log.apply.bind(console.log, console), |
| 2622 _warn: console.warn.apply.bind(console.warn, console), |
| 2623 _error: console.error.apply.bind(console.error, console), |
| 2624 _logf: function () { |
| 2625 return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(argum
ents, 0)); |
| 2626 } |
| 2627 }; |
| 2628 Polymer.Base._logPrefix = function () { |
| 2629 var color = window.chrome || /firefox/i.test(navigator.userAgent); |
| 2630 return color ? [ |
| 2631 '%c[%s::%s]:', |
| 2632 'font-weight: bold; background-color:#EEEE00;' |
| 2633 ] : ['[%s::%s]:']; |
| 2634 }(); |
| 2635 Polymer.Base.chainObject = function (object, inherited) { |
| 2636 if (object && inherited && object !== inherited) { |
| 2637 if (!Object.__proto__) { |
| 2638 object = Polymer.Base.extend(Object.create(inherited), object); |
| 2639 } |
| 2640 object.__proto__ = inherited; |
| 2641 } |
| 2642 return object; |
| 2643 }; |
| 2644 Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype); |
| 2645 if (window.CustomElements) { |
| 2646 Polymer.instanceof = CustomElements.instanceof; |
| 2647 } else { |
| 2648 Polymer.instanceof = function (obj, ctor) { |
| 2649 return obj instanceof ctor; |
| 2650 }; |
| 2651 } |
| 2652 Polymer.isInstance = function (obj) { |
| 2653 return Boolean(obj && obj.__isPolymerInstance__); |
| 2654 }; |
| 2655 Polymer.telemetry.instanceCount = 0; |
| 2656 (function () { |
| 2657 var modules = {}; |
| 2658 var lcModules = {}; |
| 2659 var findModule = function (id) { |
| 2660 return modules[id] || lcModules[id.toLowerCase()]; |
| 2661 }; |
| 2662 var DomModule = function () { |
| 2663 return document.createElement('dom-module'); |
| 2664 }; |
| 2665 DomModule.prototype = Object.create(HTMLElement.prototype); |
| 2666 Polymer.Base.extend(DomModule.prototype, { |
| 2667 constructor: DomModule, |
| 2668 createdCallback: function () { |
| 2669 this.register(); |
| 2670 }, |
| 2671 register: function (id) { |
| 2672 var id = id || this.id || this.getAttribute('name') || this.getAttribute('is'); |
| 2673 if (id) { |
| 2674 this.id = id; |
| 2675 modules[id] = this; |
| 2676 lcModules[id.toLowerCase()] = this; |
| 2677 } |
| 2678 }, |
| 2679 import: function (id, selector) { |
| 2680 if (id) { |
| 2681 var m = findModule(id); |
| 2682 if (!m) { |
| 2683 forceDocumentUpgrade(); |
| 2684 m = findModule(id); |
| 2685 } |
| 2686 if (m && selector) { |
| 2687 m = m.querySelector(selector); |
| 2688 } |
| 2689 return m; |
| 2690 } |
| 2691 } |
| 2692 }); |
| 2693 var cePolyfill = window.CustomElements && !CustomElements.useNative; |
| 2694 document.registerElement('dom-module', DomModule); |
| 2695 function forceDocumentUpgrade() { |
| 2696 if (cePolyfill) { |
| 2697 var script = document._currentScript || document.currentScript; |
| 2698 var doc = script && script.ownerDocument; |
| 2699 if (doc) { |
| 2700 CustomElements.upgradeAll(doc); |
| 2701 } |
| 2702 } |
| 2703 } |
| 2704 }()); |
| 2705 Polymer.Base._addFeature({ |
| 2706 _prepIs: function () { |
| 2707 if (!this.is) { |
| 2708 var module = (document._currentScript || document.currentScript).parentNode; |
| 2709 if (module.localName === 'dom-module') { |
| 2710 var id = module.id || module.getAttribute('name') || module.getAttribute('is'); |
| 2711 this.is = id; |
| 2712 } |
| 2713 } |
| 2714 if (this.is) { |
| 2715 this.is = this.is.toLowerCase(); |
| 2716 } |
| 2717 } |
| 2718 }); |
| 2719 Polymer.Base._addFeature({ |
| 2720 behaviors: [], |
| 2721 _desugarBehaviors: function () { |
| 2722 if (this.behaviors.length) { |
| 2723 this.behaviors = this._desugarSomeBehaviors(this.behaviors); |
| 2724 } |
| 2725 }, |
| 2726 _desugarSomeBehaviors: function (behaviors) { |
| 2727 behaviors = this._flattenBehaviorsList(behaviors); |
| 2728 for (var i = behaviors.length - 1; i >= 0; i--) { |
| 2729 this._mixinBehavior(behaviors[i]); |
| 2730 } |
| 2731 return behaviors; |
| 2732 }, |
| 2733 _flattenBehaviorsList: function (behaviors) { |
| 2734 var flat = []; |
| 2735 behaviors.forEach(function (b) { |
| 2736 if (b instanceof Array) { |
| 2737 flat = flat.concat(this._flattenBehaviorsList(b)); |
| 2738 } else if (b) { |
| 2739 flat.push(b); |
| 2740 } else { |
| 2741 this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for miss
ing or 404 import')); |
| 2742 } |
| 2743 }, this); |
| 2744 return flat; |
| 2745 }, |
| 2746 _mixinBehavior: function (b) { |
| 2747 Object.getOwnPropertyNames(b).forEach(function (n) { |
| 2748 switch (n) { |
| 2749 case 'hostAttributes': |
| 2750 case 'registered': |
| 2751 case 'properties': |
| 2752 case 'observers': |
| 2753 case 'listeners': |
| 2754 case 'created': |
| 2755 case 'attached': |
| 2756 case 'detached': |
| 2757 case 'attributeChanged': |
| 2758 case 'configure': |
| 2759 case 'ready': |
| 2760 break; |
| 2761 default: |
| 2762 if (!this.hasOwnProperty(n)) { |
| 2763 this.copyOwnProperty(n, b, this); |
| 2764 } |
| 2765 break; |
| 2766 } |
| 2767 }, this); |
| 2768 }, |
| 2769 _prepBehaviors: function () { |
| 2770 this._prepFlattenedBehaviors(this.behaviors); |
| 2771 }, |
| 2772 _prepFlattenedBehaviors: function (behaviors) { |
| 2773 for (var i = 0, l = behaviors.length; i < l; i++) { |
| 2774 this._prepBehavior(behaviors[i]); |
| 2775 } |
| 2776 this._prepBehavior(this); |
| 2777 }, |
| 2778 _doBehavior: function (name, args) { |
| 2779 this.behaviors.forEach(function (b) { |
| 2780 this._invokeBehavior(b, name, args); |
| 2781 }, this); |
| 2782 this._invokeBehavior(this, name, args); |
| 2783 }, |
| 2784 _invokeBehavior: function (b, name, args) { |
| 2785 var fn = b[name]; |
| 2786 if (fn) { |
| 2787 fn.apply(this, args || Polymer.nar); |
| 2788 } |
| 2789 }, |
| 2790 _marshalBehaviors: function () { |
| 2791 this.behaviors.forEach(function (b) { |
| 2792 this._marshalBehavior(b); |
| 2793 }, this); |
| 2794 this._marshalBehavior(this); |
| 2795 } |
| 2796 }); |
| 2797 Polymer.Base._addFeature({ |
| 2798 _getExtendedPrototype: function (tag) { |
| 2799 return this._getExtendedNativePrototype(tag); |
| 2800 }, |
| 2801 _nativePrototypes: {}, |
| 2802 _getExtendedNativePrototype: function (tag) { |
| 2803 var p = this._nativePrototypes[tag]; |
| 2804 if (!p) { |
| 2805 var np = this.getNativePrototype(tag); |
| 2806 p = this.extend(Object.create(np), Polymer.Base); |
| 2807 this._nativePrototypes[tag] = p; |
| 2808 } |
| 2809 return p; |
| 2810 }, |
| 2811 getNativePrototype: function (tag) { |
| 2812 return Object.getPrototypeOf(document.createElement(tag)); |
| 2813 } |
| 2814 }); |
| 2815 Polymer.Base._addFeature({ |
| 2816 _prepConstructor: function () { |
| 2817 this._factoryArgs = this.extends ? [ |
| 2818 this.extends, |
| 2819 this.is |
| 2820 ] : [this.is]; |
| 2821 var ctor = function () { |
| 2822 return this._factory(arguments); |
| 2823 }; |
| 2824 if (this.hasOwnProperty('extends')) { |
| 2825 ctor.extends = this.extends; |
| 2826 } |
| 2827 Object.defineProperty(this, 'constructor', { |
| 2828 value: ctor, |
| 2829 writable: true, |
| 2830 configurable: true |
| 2831 }); |
| 2832 ctor.prototype = this; |
| 2833 }, |
| 2834 _factory: function (args) { |
| 2835 var elt = document.createElement.apply(document, this._factoryArgs); |
| 2836 if (this.factoryImpl) { |
| 2837 this.factoryImpl.apply(elt, args); |
| 2838 } |
| 2839 return elt; |
| 2840 } |
| 2841 }); |
| 2842 Polymer.nob = Object.create(null); |
| 2843 Polymer.Base._addFeature({ |
| 2844 properties: {}, |
| 2845 getPropertyInfo: function (property) { |
| 2846 var info = this._getPropertyInfo(property, this.properties); |
| 2847 if (!info) { |
| 2848 this.behaviors.some(function (b) { |
| 2849 return info = this._getPropertyInfo(property, b.properties); |
| 2850 }, this); |
| 2851 } |
| 2852 return info || Polymer.nob; |
| 2853 }, |
| 2854 _getPropertyInfo: function (property, properties) { |
| 2855 var p = properties && properties[property]; |
| 2856 if (typeof p === 'function') { |
| 2857 p = properties[property] = { type: p }; |
| 2858 } |
| 2859 if (p) { |
| 2860 p.defined = true; |
| 2861 } |
| 2862 return p; |
| 2863 } |
| 2864 }); |
| 2865 Polymer.CaseMap = { |
| 2866 _caseMap: {}, |
| 2867 dashToCamelCase: function (dash) { |
| 2868 var mapped = Polymer.CaseMap._caseMap[dash]; |
| 2869 if (mapped) { |
| 2870 return mapped; |
| 2871 } |
| 2872 if (dash.indexOf('-') < 0) { |
| 2873 return Polymer.CaseMap._caseMap[dash] = dash; |
| 2874 } |
| 2875 return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) { |
| 2876 return m[1].toUpperCase(); |
| 2877 }); |
| 2878 }, |
| 2879 camelToDashCase: function (camel) { |
| 2880 var mapped = Polymer.CaseMap._caseMap[camel]; |
| 2881 if (mapped) { |
| 2882 return mapped; |
| 2883 } |
| 2884 return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function
(g) { |
| 2885 return g[0] + '-' + g[1].toLowerCase(); |
| 2886 }); |
| 2887 } |
| 2888 }; |
| 2889 Polymer.Base._addFeature({ |
| 2890 _prepAttributes: function () { |
| 2891 this._aggregatedAttributes = {}; |
| 2892 }, |
| 2893 _addHostAttributes: function (attributes) { |
| 2894 if (attributes) { |
| 2895 this.mixin(this._aggregatedAttributes, attributes); |
| 2896 } |
| 2897 }, |
| 2898 _marshalHostAttributes: function () { |
| 2899 this._applyAttributes(this, this._aggregatedAttributes); |
| 2900 }, |
| 2901 _applyAttributes: function (node, attr$) { |
| 2902 for (var n in attr$) { |
| 2903 if (!this.hasAttribute(n) && n !== 'class') { |
| 2904 this.serializeValueToAttribute(attr$[n], n, this); |
| 2905 } |
| 2906 } |
| 2907 }, |
| 2908 _marshalAttributes: function () { |
| 2909 this._takeAttributesToModel(this); |
| 2910 }, |
| 2911 _takeAttributesToModel: function (model) { |
| 2912 for (var i = 0, l = this.attributes.length; i < l; i++) { |
| 2913 this._setAttributeToProperty(model, this.attributes[i].name); |
| 2914 } |
| 2915 }, |
| 2916 _setAttributeToProperty: function (model, attrName) { |
| 2917 if (!this._serializing) { |
| 2918 var propName = Polymer.CaseMap.dashToCamelCase(attrName); |
| 2919 var info = this.getPropertyInfo(propName); |
| 2920 if (info.defined || this._propertyEffects && this._propertyEffects[propName]) { |
| 2921 var val = this.getAttribute(attrName); |
| 2922 model[propName] = this.deserialize(val, info.type); |
| 2923 } |
| 2924 } |
| 2925 }, |
| 2926 _serializing: false, |
| 2927 reflectPropertyToAttribute: function (name) { |
| 2928 this._serializing = true; |
| 2929 this.serializeValueToAttribute(this[name], Polymer.CaseMap.camelToDashCase(name)
); |
| 2930 this._serializing = false; |
| 2931 }, |
| 2932 serializeValueToAttribute: function (value, attribute, node) { |
| 2933 var str = this.serialize(value); |
| 2934 (node || this)[str === undefined ? 'removeAttribute' : 'setAttribute'](attribute
, str); |
| 2935 }, |
| 2936 deserialize: function (value, type) { |
| 2937 switch (type) { |
| 2938 case Number: |
| 2939 value = Number(value); |
| 2940 break; |
| 2941 case Boolean: |
| 2942 value = value !== null; |
| 2943 break; |
| 2944 case Object: |
| 2945 try { |
| 2946 value = JSON.parse(value); |
| 2947 } catch (x) { |
| 2948 } |
| 2949 break; |
| 2950 case Array: |
| 2951 try { |
| 2952 value = JSON.parse(value); |
| 2953 } catch (x) { |
| 2954 value = null; |
| 2955 console.warn('Polymer::Attributes: couldn`t decode Array as JSON'); |
| 2956 } |
| 2957 break; |
| 2958 case Date: |
| 2959 value = new Date(value); |
| 2960 break; |
| 2961 case String: |
| 2962 default: |
| 2963 break; |
| 2964 } |
| 2965 return value; |
| 2966 }, |
| 2967 serialize: function (value) { |
| 2968 switch (typeof value) { |
| 2969 case 'boolean': |
| 2970 return value ? '' : undefined; |
| 2971 case 'object': |
| 2972 if (value instanceof Date) { |
| 2973 return value; |
| 2974 } else if (value) { |
| 2975 try { |
| 2976 return JSON.stringify(value); |
| 2977 } catch (x) { |
| 2978 return ''; |
| 2979 } |
| 2980 } |
| 2981 default: |
| 2982 return value != null ? value : undefined; |
| 2983 } |
| 2984 } |
| 2985 }); |
| 2986 Polymer.Base._addFeature({ |
| 2987 _setupDebouncers: function () { |
| 2988 this._debouncers = {}; |
| 2989 }, |
| 2990 debounce: function (jobName, callback, wait) { |
| 2991 return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[
jobName], callback, wait); |
| 2992 }, |
| 2993 isDebouncerActive: function (jobName) { |
| 2994 var debouncer = this._debouncers[jobName]; |
| 2995 return debouncer && debouncer.finish; |
| 2996 }, |
| 2997 flushDebouncer: function (jobName) { |
| 2998 var debouncer = this._debouncers[jobName]; |
| 2999 if (debouncer) { |
| 3000 debouncer.complete(); |
| 3001 } |
| 3002 }, |
| 3003 cancelDebouncer: function (jobName) { |
| 3004 var debouncer = this._debouncers[jobName]; |
| 3005 if (debouncer) { |
| 3006 debouncer.stop(); |
| 3007 } |
| 3008 } |
| 3009 }); |
| 3010 Polymer.version = '1.1.4'; |
| 3011 Polymer.Base._addFeature({ |
| 3012 _registerFeatures: function () { |
| 3013 this._prepIs(); |
| 3014 this._prepAttributes(); |
| 3015 this._prepBehaviors(); |
| 3016 this._prepConstructor(); |
| 3017 }, |
| 3018 _prepBehavior: function (b) { |
| 3019 this._addHostAttributes(b.hostAttributes); |
| 3020 }, |
| 3021 _marshalBehavior: function (b) { |
| 3022 }, |
| 3023 _initFeatures: function () { |
| 3024 this._marshalHostAttributes(); |
| 3025 this._setupDebouncers(); |
| 3026 this._marshalBehaviors(); |
| 3027 } |
| 3028 }); |
| 3029 Polymer.Base._addFeature({ |
| 3030 _prepTemplate: function () { |
| 3031 this._template = this._template || Polymer.DomModule.import(this.is, 'template')
; |
| 3032 if (this._template && this._template.hasAttribute('is')) { |
| 3033 this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not
be a type-extension, found', this._template, 'Move inside simple <template>.'))
; |
| 3034 } |
| 3035 if (this._template && !this._template.content && HTMLTemplateElement.bootstrap)
{ |
| 3036 HTMLTemplateElement.decorate(this._template); |
| 3037 HTMLTemplateElement.bootstrap(this._template.content); |
| 3038 } |
| 3039 }, |
| 3040 _stampTemplate: function () { |
| 3041 if (this._template) { |
| 3042 this.root = this.instanceTemplate(this._template); |
| 3043 } |
| 3044 }, |
| 3045 instanceTemplate: function (template) { |
| 3046 var dom = document.importNode(template._content || template.content, true); |
| 3047 return dom; |
| 3048 } |
| 3049 }); |
| 3050 (function () { |
| 3051 var baseAttachedCallback = Polymer.Base.attachedCallback; |
| 3052 Polymer.Base._addFeature({ |
| 3053 _hostStack: [], |
| 3054 ready: function () { |
| 3055 }, |
| 3056 _pushHost: function (host) { |
| 3057 this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.l
ength - 1]; |
| 3058 if (host && host._clients) { |
| 3059 host._clients.push(this); |
| 3060 } |
| 3061 this._beginHost(); |
| 3062 }, |
| 3063 _beginHost: function () { |
| 3064 Polymer.Base._hostStack.push(this); |
| 3065 if (!this._clients) { |
| 3066 this._clients = []; |
| 3067 } |
| 3068 }, |
| 3069 _popHost: function () { |
| 3070 Polymer.Base._hostStack.pop(); |
| 3071 }, |
| 3072 _tryReady: function () { |
| 3073 if (this._canReady()) { |
| 3074 this._ready(); |
| 3075 } |
| 3076 }, |
| 3077 _canReady: function () { |
| 3078 return !this.dataHost || this.dataHost._clientsReadied; |
| 3079 }, |
| 3080 _ready: function () { |
| 3081 this._beforeClientsReady(); |
| 3082 this._setupRoot(); |
| 3083 this._readyClients(); |
| 3084 this._afterClientsReady(); |
| 3085 this._readySelf(); |
| 3086 }, |
| 3087 _readyClients: function () { |
| 3088 this._beginDistribute(); |
| 3089 var c$ = this._clients; |
| 3090 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 3091 c._ready(); |
| 3092 } |
| 3093 this._finishDistribute(); |
| 3094 this._clientsReadied = true; |
| 3095 this._clients = null; |
| 3096 }, |
| 3097 _readySelf: function () { |
| 3098 this._doBehavior('ready'); |
| 3099 this._readied = true; |
| 3100 if (this._attachedPending) { |
| 3101 this._attachedPending = false; |
| 3102 this.attachedCallback(); |
| 3103 } |
| 3104 }, |
| 3105 _beforeClientsReady: function () { |
| 3106 }, |
| 3107 _afterClientsReady: function () { |
| 3108 }, |
| 3109 _beforeAttached: function () { |
| 3110 }, |
| 3111 attachedCallback: function () { |
| 3112 if (this._readied) { |
| 3113 this._beforeAttached(); |
| 3114 baseAttachedCallback.call(this); |
| 3115 } else { |
| 3116 this._attachedPending = true; |
| 3117 } |
| 3118 } |
| 3119 }); |
| 3120 }()); |
| 3121 Polymer.ArraySplice = function () { |
| 3122 function newSplice(index, removed, addedCount) { |
| 3123 return { |
| 3124 index: index, |
| 3125 removed: removed, |
| 3126 addedCount: addedCount |
| 3127 }; |
| 3128 } |
| 3129 var EDIT_LEAVE = 0; |
| 3130 var EDIT_UPDATE = 1; |
| 3131 var EDIT_ADD = 2; |
| 3132 var EDIT_DELETE = 3; |
| 3133 function ArraySplice() { |
| 3134 } |
| 3135 ArraySplice.prototype = { |
| 3136 calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, o
ldEnd) { |
| 3137 var rowCount = oldEnd - oldStart + 1; |
| 3138 var columnCount = currentEnd - currentStart + 1; |
| 3139 var distances = new Array(rowCount); |
| 3140 for (var i = 0; i < rowCount; i++) { |
| 3141 distances[i] = new Array(columnCount); |
| 3142 distances[i][0] = i; |
| 3143 } |
| 3144 for (var j = 0; j < columnCount; j++) |
| 3145 distances[0][j] = j; |
| 3146 for (var i = 1; i < rowCount; i++) { |
| 3147 for (var j = 1; j < columnCount; j++) { |
| 3148 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| 3149 distances[i][j] = distances[i - 1][j - 1]; |
| 3150 else { |
| 3151 var north = distances[i - 1][j] + 1; |
| 3152 var west = distances[i][j - 1] + 1; |
| 3153 distances[i][j] = north < west ? north : west; |
| 3154 } |
| 3155 } |
| 3156 } |
| 3157 return distances; |
| 3158 }, |
| 3159 spliceOperationsFromEditDistances: function (distances) { |
| 3160 var i = distances.length - 1; |
| 3161 var j = distances[0].length - 1; |
| 3162 var current = distances[i][j]; |
| 3163 var edits = []; |
| 3164 while (i > 0 || j > 0) { |
| 3165 if (i == 0) { |
| 3166 edits.push(EDIT_ADD); |
| 3167 j--; |
| 3168 continue; |
| 3169 } |
| 3170 if (j == 0) { |
| 3171 edits.push(EDIT_DELETE); |
| 3172 i--; |
| 3173 continue; |
| 3174 } |
| 3175 var northWest = distances[i - 1][j - 1]; |
| 3176 var west = distances[i - 1][j]; |
| 3177 var north = distances[i][j - 1]; |
| 3178 var min; |
| 3179 if (west < north) |
| 3180 min = west < northWest ? west : northWest; |
| 3181 else |
| 3182 min = north < northWest ? north : northWest; |
| 3183 if (min == northWest) { |
| 3184 if (northWest == current) { |
| 3185 edits.push(EDIT_LEAVE); |
| 3186 } else { |
| 3187 edits.push(EDIT_UPDATE); |
| 3188 current = northWest; |
| 3189 } |
| 3190 i--; |
| 3191 j--; |
| 3192 } else if (min == west) { |
| 3193 edits.push(EDIT_DELETE); |
| 3194 i--; |
| 3195 current = west; |
| 3196 } else { |
| 3197 edits.push(EDIT_ADD); |
| 3198 j--; |
| 3199 current = north; |
| 3200 } |
| 3201 } |
| 3202 edits.reverse(); |
| 3203 return edits; |
| 3204 }, |
| 3205 calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd)
{ |
| 3206 var prefixCount = 0; |
| 3207 var suffixCount = 0; |
| 3208 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| 3209 if (currentStart == 0 && oldStart == 0) |
| 3210 prefixCount = this.sharedPrefix(current, old, minLength); |
| 3211 if (currentEnd == current.length && oldEnd == old.length) |
| 3212 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| 3213 currentStart += prefixCount; |
| 3214 oldStart += prefixCount; |
| 3215 currentEnd -= suffixCount; |
| 3216 oldEnd -= suffixCount; |
| 3217 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| 3218 return []; |
| 3219 if (currentStart == currentEnd) { |
| 3220 var splice = newSplice(currentStart, [], 0); |
| 3221 while (oldStart < oldEnd) |
| 3222 splice.removed.push(old[oldStart++]); |
| 3223 return [splice]; |
| 3224 } else if (oldStart == oldEnd) |
| 3225 return [newSplice(currentStart, [], currentEnd - currentStart)]; |
| 3226 var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current,
currentStart, currentEnd, old, oldStart, oldEnd)); |
| 3227 var splice = undefined; |
| 3228 var splices = []; |
| 3229 var index = currentStart; |
| 3230 var oldIndex = oldStart; |
| 3231 for (var i = 0; i < ops.length; i++) { |
| 3232 switch (ops[i]) { |
| 3233 case EDIT_LEAVE: |
| 3234 if (splice) { |
| 3235 splices.push(splice); |
| 3236 splice = undefined; |
| 3237 } |
| 3238 index++; |
| 3239 oldIndex++; |
| 3240 break; |
| 3241 case EDIT_UPDATE: |
| 3242 if (!splice) |
| 3243 splice = newSplice(index, [], 0); |
| 3244 splice.addedCount++; |
| 3245 index++; |
| 3246 splice.removed.push(old[oldIndex]); |
| 3247 oldIndex++; |
| 3248 break; |
| 3249 case EDIT_ADD: |
| 3250 if (!splice) |
| 3251 splice = newSplice(index, [], 0); |
| 3252 splice.addedCount++; |
| 3253 index++; |
| 3254 break; |
| 3255 case EDIT_DELETE: |
| 3256 if (!splice) |
| 3257 splice = newSplice(index, [], 0); |
| 3258 splice.removed.push(old[oldIndex]); |
| 3259 oldIndex++; |
| 3260 break; |
| 3261 } |
| 3262 } |
| 3263 if (splice) { |
| 3264 splices.push(splice); |
| 3265 } |
| 3266 return splices; |
| 3267 }, |
| 3268 sharedPrefix: function (current, old, searchLength) { |
| 3269 for (var i = 0; i < searchLength; i++) |
| 3270 if (!this.equals(current[i], old[i])) |
| 3271 return i; |
| 3272 return searchLength; |
| 3273 }, |
| 3274 sharedSuffix: function (current, old, searchLength) { |
| 3275 var index1 = current.length; |
| 3276 var index2 = old.length; |
| 3277 var count = 0; |
| 3278 while (count < searchLength && this.equals(current[--index1], old[--index2])) |
| 3279 count++; |
| 3280 return count; |
| 3281 }, |
| 3282 calculateSplices: function (current, previous) { |
| 3283 return this.calcSplices(current, 0, current.length, previous, 0, previous.length
); |
| 3284 }, |
| 3285 equals: function (currentValue, previousValue) { |
| 3286 return currentValue === previousValue; |
| 3287 } |
| 3288 }; |
| 3289 return new ArraySplice(); |
| 3290 }(); |
| 3291 Polymer.EventApi = function () { |
| 3292 var Settings = Polymer.Settings; |
| 3293 var EventApi = function (event) { |
| 3294 this.event = event; |
| 3295 }; |
| 3296 if (Settings.useShadow) { |
| 3297 EventApi.prototype = { |
| 3298 get rootTarget() { |
| 3299 return this.event.path[0]; |
| 3300 }, |
| 3301 get localTarget() { |
| 3302 return this.event.target; |
| 3303 }, |
| 3304 get path() { |
| 3305 return this.event.path; |
| 3306 } |
| 3307 }; |
| 3308 } else { |
| 3309 EventApi.prototype = { |
| 3310 get rootTarget() { |
| 3311 return this.event.target; |
| 3312 }, |
| 3313 get localTarget() { |
| 3314 var current = this.event.currentTarget; |
| 3315 var currentRoot = current && Polymer.dom(current).getOwnerRoot(); |
| 3316 var p$ = this.path; |
| 3317 for (var i = 0; i < p$.length; i++) { |
| 3318 if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) { |
| 3319 return p$[i]; |
| 3320 } |
| 3321 } |
| 3322 }, |
| 3323 get path() { |
| 3324 if (!this.event._path) { |
| 3325 var path = []; |
| 3326 var o = this.rootTarget; |
| 3327 while (o) { |
| 3328 path.push(o); |
| 3329 o = Polymer.dom(o).parentNode || o.host; |
| 3330 } |
| 3331 path.push(window); |
| 3332 this.event._path = path; |
| 3333 } |
| 3334 return this.event._path; |
| 3335 } |
| 3336 }; |
| 3337 } |
| 3338 var factory = function (event) { |
| 3339 if (!event.__eventApi) { |
| 3340 event.__eventApi = new EventApi(event); |
| 3341 } |
| 3342 return event.__eventApi; |
| 3343 }; |
| 3344 return { factory: factory }; |
| 3345 }(); |
| 3346 Polymer.domInnerHTML = function () { |
| 3347 var escapeAttrRegExp = /[&\u00A0"]/g; |
| 3348 var escapeDataRegExp = /[&\u00A0<>]/g; |
| 3349 function escapeReplace(c) { |
| 3350 switch (c) { |
| 3351 case '&': |
| 3352 return '&'; |
| 3353 case '<': |
| 3354 return '<'; |
| 3355 case '>': |
| 3356 return '>'; |
| 3357 case '"': |
| 3358 return '"'; |
| 3359 case '\xA0': |
| 3360 return ' '; |
| 3361 } |
| 3362 } |
| 3363 function escapeAttr(s) { |
| 3364 return s.replace(escapeAttrRegExp, escapeReplace); |
| 3365 } |
| 3366 function escapeData(s) { |
| 3367 return s.replace(escapeDataRegExp, escapeReplace); |
| 3368 } |
| 3369 function makeSet(arr) { |
| 3370 var set = {}; |
| 3371 for (var i = 0; i < arr.length; i++) { |
| 3372 set[arr[i]] = true; |
| 3373 } |
| 3374 return set; |
| 3375 } |
| 3376 var voidElements = makeSet([ |
| 3377 'area', |
| 3378 'base', |
| 3379 'br', |
| 3380 'col', |
| 3381 'command', |
| 3382 'embed', |
| 3383 'hr', |
| 3384 'img', |
| 3385 'input', |
| 3386 'keygen', |
| 3387 'link', |
| 3388 'meta', |
| 3389 'param', |
| 3390 'source', |
| 3391 'track', |
| 3392 'wbr' |
| 3393 ]); |
| 3394 var plaintextParents = makeSet([ |
| 3395 'style', |
| 3396 'script', |
| 3397 'xmp', |
| 3398 'iframe', |
| 3399 'noembed', |
| 3400 'noframes', |
| 3401 'plaintext', |
| 3402 'noscript' |
| 3403 ]); |
| 3404 function getOuterHTML(node, parentNode, composed) { |
| 3405 switch (node.nodeType) { |
| 3406 case Node.ELEMENT_NODE: |
| 3407 var tagName = node.localName; |
| 3408 var s = '<' + tagName; |
| 3409 var attrs = node.attributes; |
| 3410 for (var i = 0, attr; attr = attrs[i]; i++) { |
| 3411 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; |
| 3412 } |
| 3413 s += '>'; |
| 3414 if (voidElements[tagName]) { |
| 3415 return s; |
| 3416 } |
| 3417 return s + getInnerHTML(node, composed) + '</' + tagName + '>'; |
| 3418 case Node.TEXT_NODE: |
| 3419 var data = node.data; |
| 3420 if (parentNode && plaintextParents[parentNode.localName]) { |
| 3421 return data; |
| 3422 } |
| 3423 return escapeData(data); |
| 3424 case Node.COMMENT_NODE: |
| 3425 return '<!--' + node.data + '-->'; |
| 3426 default: |
| 3427 console.error(node); |
| 3428 throw new Error('not implemented'); |
| 3429 } |
| 3430 } |
| 3431 function getInnerHTML(node, composed) { |
| 3432 if (node instanceof HTMLTemplateElement) |
| 3433 node = node.content; |
| 3434 var s = ''; |
| 3435 var c$ = Polymer.dom(node).childNodes; |
| 3436 c$ = composed ? node._composedChildren : c$; |
| 3437 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { |
| 3438 s += getOuterHTML(child, node, composed); |
| 3439 } |
| 3440 return s; |
| 3441 } |
| 3442 return { getInnerHTML: getInnerHTML }; |
| 3443 }(); |
| 3444 Polymer.DomApi = function () { |
| 3445 'use strict'; |
| 3446 var Settings = Polymer.Settings; |
| 3447 var getInnerHTML = Polymer.domInnerHTML.getInnerHTML; |
| 3448 var nativeInsertBefore = Element.prototype.insertBefore; |
| 3449 var nativeRemoveChild = Element.prototype.removeChild; |
| 3450 var nativeAppendChild = Element.prototype.appendChild; |
| 3451 var nativeCloneNode = Element.prototype.cloneNode; |
| 3452 var nativeImportNode = Document.prototype.importNode; |
| 3453 var DomApi = function (node) { |
| 3454 this.node = node; |
| 3455 if (this.patch) { |
| 3456 this.patch(); |
| 3457 } |
| 3458 }; |
| 3459 if (window.wrap && Settings.useShadow && !Settings.useNativeShadow) { |
| 3460 DomApi = function (node) { |
| 3461 this.node = wrap(node); |
| 3462 if (this.patch) { |
| 3463 this.patch(); |
| 3464 } |
| 3465 }; |
| 3466 } |
| 3467 DomApi.prototype = { |
| 3468 flush: function () { |
| 3469 Polymer.dom.flush(); |
| 3470 }, |
| 3471 _lazyDistribute: function (host) { |
| 3472 if (host.shadyRoot && host.shadyRoot._distributionClean) { |
| 3473 host.shadyRoot._distributionClean = false; |
| 3474 Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent)); |
| 3475 } |
| 3476 }, |
| 3477 appendChild: function (node) { |
| 3478 return this._addNode(node); |
| 3479 }, |
| 3480 insertBefore: function (node, ref_node) { |
| 3481 return this._addNode(node, ref_node); |
| 3482 }, |
| 3483 _addNode: function (node, ref_node) { |
| 3484 this._removeNodeFromHost(node, true); |
| 3485 var addedInsertionPoint; |
| 3486 var root = this.getOwnerRoot(); |
| 3487 if (root) { |
| 3488 addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node); |
| 3489 } |
| 3490 if (this._nodeHasLogicalChildren(this.node)) { |
| 3491 if (ref_node) { |
| 3492 var children = this.childNodes; |
| 3493 var index = children.indexOf(ref_node); |
| 3494 if (index < 0) { |
| 3495 throw Error('The ref_node to be inserted before is not a child ' + 'of this node
'); |
| 3496 } |
| 3497 } |
| 3498 this._addLogicalInfo(node, this.node, index); |
| 3499 } |
| 3500 this._addNodeToHost(node); |
| 3501 if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode
(node)) { |
| 3502 if (ref_node) { |
| 3503 ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) :
ref_node; |
| 3504 } |
| 3505 var container = this.node._isShadyRoot ? this.node.host : this.node; |
| 3506 addToComposedParent(container, node, ref_node); |
| 3507 if (ref_node) { |
| 3508 nativeInsertBefore.call(container, node, ref_node); |
| 3509 } else { |
| 3510 nativeAppendChild.call(container, node); |
| 3511 } |
| 3512 } |
| 3513 if (addedInsertionPoint) { |
| 3514 this._updateInsertionPoints(root.host); |
| 3515 } |
| 3516 return node; |
| 3517 }, |
| 3518 removeChild: function (node) { |
| 3519 if (factory(node).parentNode !== this.node) { |
| 3520 console.warn('The node to be removed is not a child of this node', node); |
| 3521 } |
| 3522 this._removeNodeFromHost(node); |
| 3523 if (!this._maybeDistribute(node, this.node)) { |
| 3524 var container = this.node._isShadyRoot ? this.node.host : this.node; |
| 3525 if (container === node.parentNode) { |
| 3526 removeFromComposedParent(container, node); |
| 3527 nativeRemoveChild.call(container, node); |
| 3528 } |
| 3529 } |
| 3530 return node; |
| 3531 }, |
| 3532 replaceChild: function (node, ref_node) { |
| 3533 this.insertBefore(node, ref_node); |
| 3534 this.removeChild(ref_node); |
| 3535 return node; |
| 3536 }, |
| 3537 _hasCachedOwnerRoot: function (node) { |
| 3538 return Boolean(node._ownerShadyRoot !== undefined); |
| 3539 }, |
| 3540 getOwnerRoot: function () { |
| 3541 return this._ownerShadyRootForNode(this.node); |
| 3542 }, |
| 3543 _ownerShadyRootForNode: function (node) { |
| 3544 if (!node) { |
| 3545 return; |
| 3546 } |
| 3547 if (node._ownerShadyRoot === undefined) { |
| 3548 var root; |
| 3549 if (node._isShadyRoot) { |
| 3550 root = node; |
| 3551 } else { |
| 3552 var parent = Polymer.dom(node).parentNode; |
| 3553 if (parent) { |
| 3554 root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent); |
| 3555 } else { |
| 3556 root = null; |
| 3557 } |
| 3558 } |
| 3559 node._ownerShadyRoot = root; |
| 3560 } |
| 3561 return node._ownerShadyRoot; |
| 3562 }, |
| 3563 _maybeDistribute: function (node, parent) { |
| 3564 var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noCon
tent && Polymer.dom(node).querySelector(CONTENT); |
| 3565 var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType
!== Node.DOCUMENT_FRAGMENT_NODE; |
| 3566 var hasContent = fragContent || node.localName === CONTENT; |
| 3567 if (hasContent) { |
| 3568 var root = this._ownerShadyRootForNode(parent); |
| 3569 if (root) { |
| 3570 var host = root.host; |
| 3571 this._lazyDistribute(host); |
| 3572 } |
| 3573 } |
| 3574 var parentNeedsDist = this._parentNeedsDistribution(parent); |
| 3575 if (parentNeedsDist) { |
| 3576 this._lazyDistribute(parent); |
| 3577 } |
| 3578 return parentNeedsDist || hasContent && !wrappedContent; |
| 3579 }, |
| 3580 _maybeAddInsertionPoint: function (node, parent) { |
| 3581 var added; |
| 3582 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) { |
| 3583 var c$ = factory(node).querySelectorAll(CONTENT); |
| 3584 for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) { |
| 3585 np = factory(n).parentNode; |
| 3586 if (np === node) { |
| 3587 np = parent; |
| 3588 } |
| 3589 na = this._maybeAddInsertionPoint(n, np); |
| 3590 added = added || na; |
| 3591 } |
| 3592 } else if (node.localName === CONTENT) { |
| 3593 saveLightChildrenIfNeeded(parent); |
| 3594 saveLightChildrenIfNeeded(node); |
| 3595 added = true; |
| 3596 } |
| 3597 return added; |
| 3598 }, |
| 3599 _tryRemoveUndistributedNode: function (node) { |
| 3600 if (this.node.shadyRoot) { |
| 3601 var parent = getComposedParent(node); |
| 3602 if (parent) { |
| 3603 nativeRemoveChild.call(parent, node); |
| 3604 } |
| 3605 return true; |
| 3606 } |
| 3607 }, |
| 3608 _updateInsertionPoints: function (host) { |
| 3609 var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelector
All(CONTENT); |
| 3610 for (var i = 0, c; i < i$.length; i++) { |
| 3611 c = i$[i]; |
| 3612 saveLightChildrenIfNeeded(c); |
| 3613 saveLightChildrenIfNeeded(factory(c).parentNode); |
| 3614 } |
| 3615 }, |
| 3616 _nodeHasLogicalChildren: function (node) { |
| 3617 return Boolean(node._lightChildren !== undefined); |
| 3618 }, |
| 3619 _parentNeedsDistribution: function (parent) { |
| 3620 return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot); |
| 3621 }, |
| 3622 _removeNodeFromHost: function (node, ensureComposedRemoval) { |
| 3623 var hostNeedsDist; |
| 3624 var root; |
| 3625 var parent = node._lightParent; |
| 3626 if (parent) { |
| 3627 factory(node)._distributeParent(); |
| 3628 root = this._ownerShadyRootForNode(node); |
| 3629 if (root) { |
| 3630 root.host._elementRemove(node); |
| 3631 hostNeedsDist = this._removeDistributedChildren(root, node); |
| 3632 } |
| 3633 this._removeLogicalInfo(node, node._lightParent); |
| 3634 } |
| 3635 this._removeOwnerShadyRoot(node); |
| 3636 if (root && hostNeedsDist) { |
| 3637 this._updateInsertionPoints(root.host); |
| 3638 this._lazyDistribute(root.host); |
| 3639 } else if (ensureComposedRemoval) { |
| 3640 removeFromComposedParent(getComposedParent(node), node); |
| 3641 } |
| 3642 }, |
| 3643 _removeDistributedChildren: function (root, container) { |
| 3644 var hostNeedsDist; |
| 3645 var ip$ = root._insertionPoints; |
| 3646 for (var i = 0; i < ip$.length; i++) { |
| 3647 var content = ip$[i]; |
| 3648 if (this._contains(container, content)) { |
| 3649 var dc$ = factory(content).getDistributedNodes(); |
| 3650 for (var j = 0; j < dc$.length; j++) { |
| 3651 hostNeedsDist = true; |
| 3652 var node = dc$[j]; |
| 3653 var parent = node.parentNode; |
| 3654 if (parent) { |
| 3655 removeFromComposedParent(parent, node); |
| 3656 nativeRemoveChild.call(parent, node); |
| 3657 } |
| 3658 } |
| 3659 } |
| 3660 } |
| 3661 return hostNeedsDist; |
| 3662 }, |
| 3663 _contains: function (container, node) { |
| 3664 while (node) { |
| 3665 if (node == container) { |
| 3666 return true; |
| 3667 } |
| 3668 node = factory(node).parentNode; |
| 3669 } |
| 3670 }, |
| 3671 _addNodeToHost: function (node) { |
| 3672 var root = this.getOwnerRoot(); |
| 3673 if (root) { |
| 3674 root.host._elementAdd(node); |
| 3675 } |
| 3676 }, |
| 3677 _addLogicalInfo: function (node, container, index) { |
| 3678 var children = factory(container).childNodes; |
| 3679 index = index === undefined ? children.length : index; |
| 3680 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 3681 var c$ = Array.prototype.slice.call(node.childNodes); |
| 3682 for (var i = 0, n; i < c$.length && (n = c$[i]); i++) { |
| 3683 children.splice(index++, 0, n); |
| 3684 n._lightParent = container; |
| 3685 } |
| 3686 } else { |
| 3687 children.splice(index, 0, node); |
| 3688 node._lightParent = container; |
| 3689 } |
| 3690 }, |
| 3691 _removeLogicalInfo: function (node, container) { |
| 3692 var children = factory(container).childNodes; |
| 3693 var index = children.indexOf(node); |
| 3694 if (index < 0 || container !== node._lightParent) { |
| 3695 throw Error('The node to be removed is not a child of this node'); |
| 3696 } |
| 3697 children.splice(index, 1); |
| 3698 node._lightParent = null; |
| 3699 }, |
| 3700 _removeOwnerShadyRoot: function (node) { |
| 3701 if (this._hasCachedOwnerRoot(node)) { |
| 3702 var c$ = factory(node).childNodes; |
| 3703 for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) { |
| 3704 this._removeOwnerShadyRoot(n); |
| 3705 } |
| 3706 } |
| 3707 node._ownerShadyRoot = undefined; |
| 3708 }, |
| 3709 _firstComposedNode: function (content) { |
| 3710 var n$ = factory(content).getDistributedNodes(); |
| 3711 for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) { |
| 3712 p$ = factory(n).getDestinationInsertionPoints(); |
| 3713 if (p$[p$.length - 1] === content) { |
| 3714 return n; |
| 3715 } |
| 3716 } |
| 3717 }, |
| 3718 querySelector: function (selector) { |
| 3719 return this.querySelectorAll(selector)[0]; |
| 3720 }, |
| 3721 querySelectorAll: function (selector) { |
| 3722 return this._query(function (n) { |
| 3723 return matchesSelector.call(n, selector); |
| 3724 }, this.node); |
| 3725 }, |
| 3726 _query: function (matcher, node) { |
| 3727 node = node || this.node; |
| 3728 var list = []; |
| 3729 this._queryElements(factory(node).childNodes, matcher, list); |
| 3730 return list; |
| 3731 }, |
| 3732 _queryElements: function (elements, matcher, list) { |
| 3733 for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) { |
| 3734 if (c.nodeType === Node.ELEMENT_NODE) { |
| 3735 this._queryElement(c, matcher, list); |
| 3736 } |
| 3737 } |
| 3738 }, |
| 3739 _queryElement: function (node, matcher, list) { |
| 3740 if (matcher(node)) { |
| 3741 list.push(node); |
| 3742 } |
| 3743 this._queryElements(factory(node).childNodes, matcher, list); |
| 3744 }, |
| 3745 getDestinationInsertionPoints: function () { |
| 3746 return this.node._destinationInsertionPoints || []; |
| 3747 }, |
| 3748 getDistributedNodes: function () { |
| 3749 return this.node._distributedNodes || []; |
| 3750 }, |
| 3751 queryDistributedElements: function (selector) { |
| 3752 var c$ = this.childNodes; |
| 3753 var list = []; |
| 3754 this._distributedFilter(selector, c$, list); |
| 3755 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 3756 if (c.localName === CONTENT) { |
| 3757 this._distributedFilter(selector, factory(c).getDistributedNodes(), list); |
| 3758 } |
| 3759 } |
| 3760 return list; |
| 3761 }, |
| 3762 _distributedFilter: function (selector, list, results) { |
| 3763 results = results || []; |
| 3764 for (var i = 0, l = list.length, d; i < l && (d = list[i]); i++) { |
| 3765 if (d.nodeType === Node.ELEMENT_NODE && d.localName !== CONTENT && matchesSelect
or.call(d, selector)) { |
| 3766 results.push(d); |
| 3767 } |
| 3768 } |
| 3769 return results; |
| 3770 }, |
| 3771 _clear: function () { |
| 3772 while (this.childNodes.length) { |
| 3773 this.removeChild(this.childNodes[0]); |
| 3774 } |
| 3775 }, |
| 3776 setAttribute: function (name, value) { |
| 3777 this.node.setAttribute(name, value); |
| 3778 this._distributeParent(); |
| 3779 }, |
| 3780 removeAttribute: function (name) { |
| 3781 this.node.removeAttribute(name); |
| 3782 this._distributeParent(); |
| 3783 }, |
| 3784 _distributeParent: function () { |
| 3785 if (this._parentNeedsDistribution(this.parentNode)) { |
| 3786 this._lazyDistribute(this.parentNode); |
| 3787 } |
| 3788 }, |
| 3789 cloneNode: function (deep) { |
| 3790 var n = nativeCloneNode.call(this.node, false); |
| 3791 if (deep) { |
| 3792 var c$ = this.childNodes; |
| 3793 var d = factory(n); |
| 3794 for (var i = 0, nc; i < c$.length; i++) { |
| 3795 nc = factory(c$[i]).cloneNode(true); |
| 3796 d.appendChild(nc); |
| 3797 } |
| 3798 } |
| 3799 return n; |
| 3800 }, |
| 3801 importNode: function (externalNode, deep) { |
| 3802 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; |
| 3803 var n = nativeImportNode.call(doc, externalNode, false); |
| 3804 if (deep) { |
| 3805 var c$ = factory(externalNode).childNodes; |
| 3806 var d = factory(n); |
| 3807 for (var i = 0, nc; i < c$.length; i++) { |
| 3808 nc = factory(doc).importNode(c$[i], true); |
| 3809 d.appendChild(nc); |
| 3810 } |
| 3811 } |
| 3812 return n; |
| 3813 } |
| 3814 }; |
| 3815 Object.defineProperty(DomApi.prototype, 'classList', { |
| 3816 get: function () { |
| 3817 if (!this._classList) { |
| 3818 this._classList = new DomApi.ClassList(this); |
| 3819 } |
| 3820 return this._classList; |
| 3821 }, |
| 3822 configurable: true |
| 3823 }); |
| 3824 DomApi.ClassList = function (host) { |
| 3825 this.domApi = host; |
| 3826 this.node = host.node; |
| 3827 }; |
| 3828 DomApi.ClassList.prototype = { |
| 3829 add: function () { |
| 3830 this.node.classList.add.apply(this.node.classList, arguments); |
| 3831 this.domApi._distributeParent(); |
| 3832 }, |
| 3833 remove: function () { |
| 3834 this.node.classList.remove.apply(this.node.classList, arguments); |
| 3835 this.domApi._distributeParent(); |
| 3836 }, |
| 3837 toggle: function () { |
| 3838 this.node.classList.toggle.apply(this.node.classList, arguments); |
| 3839 this.domApi._distributeParent(); |
| 3840 }, |
| 3841 contains: function () { |
| 3842 return this.node.classList.contains.apply(this.node.classList, arguments); |
| 3843 } |
| 3844 }; |
| 3845 if (!Settings.useShadow) { |
| 3846 Object.defineProperties(DomApi.prototype, { |
| 3847 childNodes: { |
| 3848 get: function () { |
| 3849 var c$ = getLightChildren(this.node); |
| 3850 return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$); |
| 3851 }, |
| 3852 configurable: true |
| 3853 }, |
| 3854 children: { |
| 3855 get: function () { |
| 3856 return Array.prototype.filter.call(this.childNodes, function (n) { |
| 3857 return n.nodeType === Node.ELEMENT_NODE; |
| 3858 }); |
| 3859 }, |
| 3860 configurable: true |
| 3861 }, |
| 3862 parentNode: { |
| 3863 get: function () { |
| 3864 return this.node._lightParent || getComposedParent(this.node); |
| 3865 }, |
| 3866 configurable: true |
| 3867 }, |
| 3868 firstChild: { |
| 3869 get: function () { |
| 3870 return this.childNodes[0]; |
| 3871 }, |
| 3872 configurable: true |
| 3873 }, |
| 3874 lastChild: { |
| 3875 get: function () { |
| 3876 var c$ = this.childNodes; |
| 3877 return c$[c$.length - 1]; |
| 3878 }, |
| 3879 configurable: true |
| 3880 }, |
| 3881 nextSibling: { |
| 3882 get: function () { |
| 3883 var c$ = this.parentNode && factory(this.parentNode).childNodes; |
| 3884 if (c$) { |
| 3885 return c$[Array.prototype.indexOf.call(c$, this.node) + 1]; |
| 3886 } |
| 3887 }, |
| 3888 configurable: true |
| 3889 }, |
| 3890 previousSibling: { |
| 3891 get: function () { |
| 3892 var c$ = this.parentNode && factory(this.parentNode).childNodes; |
| 3893 if (c$) { |
| 3894 return c$[Array.prototype.indexOf.call(c$, this.node) - 1]; |
| 3895 } |
| 3896 }, |
| 3897 configurable: true |
| 3898 }, |
| 3899 firstElementChild: { |
| 3900 get: function () { |
| 3901 return this.children[0]; |
| 3902 }, |
| 3903 configurable: true |
| 3904 }, |
| 3905 lastElementChild: { |
| 3906 get: function () { |
| 3907 var c$ = this.children; |
| 3908 return c$[c$.length - 1]; |
| 3909 }, |
| 3910 configurable: true |
| 3911 }, |
| 3912 nextElementSibling: { |
| 3913 get: function () { |
| 3914 var c$ = this.parentNode && factory(this.parentNode).children; |
| 3915 if (c$) { |
| 3916 return c$[Array.prototype.indexOf.call(c$, this.node) + 1]; |
| 3917 } |
| 3918 }, |
| 3919 configurable: true |
| 3920 }, |
| 3921 previousElementSibling: { |
| 3922 get: function () { |
| 3923 var c$ = this.parentNode && factory(this.parentNode).children; |
| 3924 if (c$) { |
| 3925 return c$[Array.prototype.indexOf.call(c$, this.node) - 1]; |
| 3926 } |
| 3927 }, |
| 3928 configurable: true |
| 3929 }, |
| 3930 textContent: { |
| 3931 get: function () { |
| 3932 var nt = this.node.nodeType; |
| 3933 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { |
| 3934 return this.node.textContent; |
| 3935 } else { |
| 3936 var tc = []; |
| 3937 for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) { |
| 3938 if (c.nodeType !== Node.COMMENT_NODE) { |
| 3939 tc.push(c.textContent); |
| 3940 } |
| 3941 } |
| 3942 return tc.join(''); |
| 3943 } |
| 3944 }, |
| 3945 set: function (text) { |
| 3946 var nt = this.node.nodeType; |
| 3947 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { |
| 3948 this.node.textContent = text; |
| 3949 } else { |
| 3950 this._clear(); |
| 3951 if (text) { |
| 3952 this.appendChild(document.createTextNode(text)); |
| 3953 } |
| 3954 } |
| 3955 }, |
| 3956 configurable: true |
| 3957 }, |
| 3958 innerHTML: { |
| 3959 get: function () { |
| 3960 var nt = this.node.nodeType; |
| 3961 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { |
| 3962 return null; |
| 3963 } else { |
| 3964 return getInnerHTML(this.node); |
| 3965 } |
| 3966 }, |
| 3967 set: function (text) { |
| 3968 var nt = this.node.nodeType; |
| 3969 if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) { |
| 3970 this._clear(); |
| 3971 var d = document.createElement('div'); |
| 3972 d.innerHTML = text; |
| 3973 var c$ = Array.prototype.slice.call(d.childNodes); |
| 3974 for (var i = 0; i < c$.length; i++) { |
| 3975 this.appendChild(c$[i]); |
| 3976 } |
| 3977 } |
| 3978 }, |
| 3979 configurable: true |
| 3980 } |
| 3981 }); |
| 3982 DomApi.prototype._getComposedInnerHTML = function () { |
| 3983 return getInnerHTML(this.node, true); |
| 3984 }; |
| 3985 } else { |
| 3986 var forwardMethods = [ |
| 3987 'cloneNode', |
| 3988 'appendChild', |
| 3989 'insertBefore', |
| 3990 'removeChild', |
| 3991 'replaceChild' |
| 3992 ]; |
| 3993 forwardMethods.forEach(function (name) { |
| 3994 DomApi.prototype[name] = function () { |
| 3995 return this.node[name].apply(this.node, arguments); |
| 3996 }; |
| 3997 }); |
| 3998 DomApi.prototype.querySelectorAll = function (selector) { |
| 3999 return Array.prototype.slice.call(this.node.querySelectorAll(selector)); |
| 4000 }; |
| 4001 DomApi.prototype.getOwnerRoot = function () { |
| 4002 var n = this.node; |
| 4003 while (n) { |
| 4004 if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) { |
| 4005 return n; |
| 4006 } |
| 4007 n = n.parentNode; |
| 4008 } |
| 4009 }; |
| 4010 DomApi.prototype.importNode = function (externalNode, deep) { |
| 4011 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; |
| 4012 return doc.importNode(externalNode, deep); |
| 4013 }; |
| 4014 DomApi.prototype.getDestinationInsertionPoints = function () { |
| 4015 var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInse
rtionPoints(); |
| 4016 return n$ ? Array.prototype.slice.call(n$) : []; |
| 4017 }; |
| 4018 DomApi.prototype.getDistributedNodes = function () { |
| 4019 var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes(); |
| 4020 return n$ ? Array.prototype.slice.call(n$) : []; |
| 4021 }; |
| 4022 DomApi.prototype._distributeParent = function () { |
| 4023 }; |
| 4024 Object.defineProperties(DomApi.prototype, { |
| 4025 childNodes: { |
| 4026 get: function () { |
| 4027 return Array.prototype.slice.call(this.node.childNodes); |
| 4028 }, |
| 4029 configurable: true |
| 4030 }, |
| 4031 children: { |
| 4032 get: function () { |
| 4033 return Array.prototype.slice.call(this.node.children); |
| 4034 }, |
| 4035 configurable: true |
| 4036 }, |
| 4037 textContent: { |
| 4038 get: function () { |
| 4039 return this.node.textContent; |
| 4040 }, |
| 4041 set: function (value) { |
| 4042 return this.node.textContent = value; |
| 4043 }, |
| 4044 configurable: true |
| 4045 }, |
| 4046 innerHTML: { |
| 4047 get: function () { |
| 4048 return this.node.innerHTML; |
| 4049 }, |
| 4050 set: function (value) { |
| 4051 return this.node.innerHTML = value; |
| 4052 }, |
| 4053 configurable: true |
| 4054 } |
| 4055 }); |
| 4056 var forwardProperties = [ |
| 4057 'parentNode', |
| 4058 'firstChild', |
| 4059 'lastChild', |
| 4060 'nextSibling', |
| 4061 'previousSibling', |
| 4062 'firstElementChild', |
| 4063 'lastElementChild', |
| 4064 'nextElementSibling', |
| 4065 'previousElementSibling' |
| 4066 ]; |
| 4067 forwardProperties.forEach(function (name) { |
| 4068 Object.defineProperty(DomApi.prototype, name, { |
| 4069 get: function () { |
| 4070 return this.node[name]; |
| 4071 }, |
| 4072 configurable: true |
| 4073 }); |
| 4074 }); |
| 4075 } |
| 4076 var CONTENT = 'content'; |
| 4077 var factory = function (node, patch) { |
| 4078 node = node || document; |
| 4079 if (!node.__domApi) { |
| 4080 node.__domApi = new DomApi(node, patch); |
| 4081 } |
| 4082 return node.__domApi; |
| 4083 }; |
| 4084 Polymer.dom = function (obj, patch) { |
| 4085 if (obj instanceof Event) { |
| 4086 return Polymer.EventApi.factory(obj); |
| 4087 } else { |
| 4088 return factory(obj, patch); |
| 4089 } |
| 4090 }; |
| 4091 Polymer.Base.extend(Polymer.dom, { |
| 4092 _flushGuard: 0, |
| 4093 _FLUSH_MAX: 100, |
| 4094 _needsTakeRecords: !Polymer.Settings.useNativeCustomElements, |
| 4095 _debouncers: [], |
| 4096 _finishDebouncer: null, |
| 4097 flush: function () { |
| 4098 for (var i = 0; i < this._debouncers.length; i++) { |
| 4099 this._debouncers[i].complete(); |
| 4100 } |
| 4101 if (this._finishDebouncer) { |
| 4102 this._finishDebouncer.complete(); |
| 4103 } |
| 4104 this._flushPolyfills(); |
| 4105 if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { |
| 4106 this._flushGuard++; |
| 4107 this.flush(); |
| 4108 } else { |
| 4109 if (this._flushGuard >= this._FLUSH_MAX) { |
| 4110 console.warn('Polymer.dom.flush aborted. Flush may not be complete.'); |
| 4111 } |
| 4112 this._flushGuard = 0; |
| 4113 } |
| 4114 }, |
| 4115 _flushPolyfills: function () { |
| 4116 if (this._needsTakeRecords) { |
| 4117 CustomElements.takeRecords(); |
| 4118 } |
| 4119 }, |
| 4120 addDebouncer: function (debouncer) { |
| 4121 this._debouncers.push(debouncer); |
| 4122 this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlus
h); |
| 4123 }, |
| 4124 _finishFlush: function () { |
| 4125 Polymer.dom._debouncers = []; |
| 4126 } |
| 4127 }); |
| 4128 function getLightChildren(node) { |
| 4129 var children = node._lightChildren; |
| 4130 return children ? children : node.childNodes; |
| 4131 } |
| 4132 function getComposedChildren(node) { |
| 4133 if (!node._composedChildren) { |
| 4134 node._composedChildren = Array.prototype.slice.call(node.childNodes); |
| 4135 } |
| 4136 return node._composedChildren; |
| 4137 } |
| 4138 function addToComposedParent(parent, node, ref_node) { |
| 4139 var children = getComposedChildren(parent); |
| 4140 var i = ref_node ? children.indexOf(ref_node) : -1; |
| 4141 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 4142 var fragChildren = getComposedChildren(node); |
| 4143 for (var j = 0; j < fragChildren.length; j++) { |
| 4144 addNodeToComposedChildren(fragChildren[j], parent, children, i + j); |
| 4145 } |
| 4146 node._composedChildren = null; |
| 4147 } else { |
| 4148 addNodeToComposedChildren(node, parent, children, i); |
| 4149 } |
| 4150 } |
| 4151 function getComposedParent(node) { |
| 4152 return node.__patched ? node._composedParent : node.parentNode; |
| 4153 } |
| 4154 function addNodeToComposedChildren(node, parent, children, i) { |
| 4155 node._composedParent = parent; |
| 4156 children.splice(i >= 0 ? i : children.length, 0, node); |
| 4157 } |
| 4158 function removeFromComposedParent(parent, node) { |
| 4159 node._composedParent = null; |
| 4160 if (parent) { |
| 4161 var children = getComposedChildren(parent); |
| 4162 var i = children.indexOf(node); |
| 4163 if (i >= 0) { |
| 4164 children.splice(i, 1); |
| 4165 } |
| 4166 } |
| 4167 } |
| 4168 function saveLightChildrenIfNeeded(node) { |
| 4169 if (!node._lightChildren) { |
| 4170 var c$ = Array.prototype.slice.call(node.childNodes); |
| 4171 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { |
| 4172 child._lightParent = child._lightParent || node; |
| 4173 } |
| 4174 node._lightChildren = c$; |
| 4175 } |
| 4176 } |
| 4177 function hasInsertionPoint(root) { |
| 4178 return Boolean(root && root._insertionPoints.length); |
| 4179 } |
| 4180 var p = Element.prototype; |
| 4181 var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector ||
p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector; |
| 4182 return { |
| 4183 getLightChildren: getLightChildren, |
| 4184 getComposedParent: getComposedParent, |
| 4185 getComposedChildren: getComposedChildren, |
| 4186 removeFromComposedParent: removeFromComposedParent, |
| 4187 saveLightChildrenIfNeeded: saveLightChildrenIfNeeded, |
| 4188 matchesSelector: matchesSelector, |
| 4189 hasInsertionPoint: hasInsertionPoint, |
| 4190 ctor: DomApi, |
| 4191 factory: factory |
| 4192 }; |
| 4193 }(); |
| 4194 (function () { |
| 4195 Polymer.Base._addFeature({ |
| 4196 _prepShady: function () { |
| 4197 this._useContent = this._useContent || Boolean(this._template); |
| 4198 }, |
| 4199 _poolContent: function () { |
| 4200 if (this._useContent) { |
| 4201 saveLightChildrenIfNeeded(this); |
| 4202 } |
| 4203 }, |
| 4204 _setupRoot: function () { |
| 4205 if (this._useContent) { |
| 4206 this._createLocalRoot(); |
| 4207 if (!this.dataHost) { |
| 4208 upgradeLightChildren(this._lightChildren); |
| 4209 } |
| 4210 } |
| 4211 }, |
| 4212 _createLocalRoot: function () { |
| 4213 this.shadyRoot = this.root; |
| 4214 this.shadyRoot._distributionClean = false; |
| 4215 this.shadyRoot._isShadyRoot = true; |
| 4216 this.shadyRoot._dirtyRoots = []; |
| 4217 var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasConte
nt ? this.shadyRoot.querySelectorAll('content') : []; |
| 4218 saveLightChildrenIfNeeded(this.shadyRoot); |
| 4219 for (var i = 0, c; i < i$.length; i++) { |
| 4220 c = i$[i]; |
| 4221 saveLightChildrenIfNeeded(c); |
| 4222 saveLightChildrenIfNeeded(c.parentNode); |
| 4223 } |
| 4224 this.shadyRoot.host = this; |
| 4225 }, |
| 4226 get domHost() { |
| 4227 var root = Polymer.dom(this).getOwnerRoot(); |
| 4228 return root && root.host; |
| 4229 }, |
| 4230 distributeContent: function (updateInsertionPoints) { |
| 4231 if (this.shadyRoot) { |
| 4232 var dom = Polymer.dom(this); |
| 4233 if (updateInsertionPoints) { |
| 4234 dom._updateInsertionPoints(this); |
| 4235 } |
| 4236 var host = getTopDistributingHost(this); |
| 4237 dom._lazyDistribute(host); |
| 4238 } |
| 4239 }, |
| 4240 _distributeContent: function () { |
| 4241 if (this._useContent && !this.shadyRoot._distributionClean) { |
| 4242 this._beginDistribute(); |
| 4243 this._distributeDirtyRoots(); |
| 4244 this._finishDistribute(); |
| 4245 } |
| 4246 }, |
| 4247 _beginDistribute: function () { |
| 4248 if (this._useContent && hasInsertionPoint(this.shadyRoot)) { |
| 4249 this._resetDistribution(); |
| 4250 this._distributePool(this.shadyRoot, this._collectPool()); |
| 4251 } |
| 4252 }, |
| 4253 _distributeDirtyRoots: function () { |
| 4254 var c$ = this.shadyRoot._dirtyRoots; |
| 4255 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 4256 c._distributeContent(); |
| 4257 } |
| 4258 this.shadyRoot._dirtyRoots = []; |
| 4259 }, |
| 4260 _finishDistribute: function () { |
| 4261 if (this._useContent) { |
| 4262 this.shadyRoot._distributionClean = true; |
| 4263 if (hasInsertionPoint(this.shadyRoot)) { |
| 4264 this._composeTree(); |
| 4265 } else { |
| 4266 if (!this.shadyRoot._hasDistributed) { |
| 4267 this.textContent = ''; |
| 4268 this._composedChildren = null; |
| 4269 this.appendChild(this.shadyRoot); |
| 4270 } else { |
| 4271 var children = this._composeNode(this); |
| 4272 this._updateChildNodes(this, children); |
| 4273 } |
| 4274 } |
| 4275 this.shadyRoot._hasDistributed = true; |
| 4276 } |
| 4277 }, |
| 4278 elementMatches: function (selector, node) { |
| 4279 node = node || this; |
| 4280 return matchesSelector.call(node, selector); |
| 4281 }, |
| 4282 _resetDistribution: function () { |
| 4283 var children = getLightChildren(this); |
| 4284 for (var i = 0; i < children.length; i++) { |
| 4285 var child = children[i]; |
| 4286 if (child._destinationInsertionPoints) { |
| 4287 child._destinationInsertionPoints = undefined; |
| 4288 } |
| 4289 if (isInsertionPoint(child)) { |
| 4290 clearDistributedDestinationInsertionPoints(child); |
| 4291 } |
| 4292 } |
| 4293 var root = this.shadyRoot; |
| 4294 var p$ = root._insertionPoints; |
| 4295 for (var j = 0; j < p$.length; j++) { |
| 4296 p$[j]._distributedNodes = []; |
| 4297 } |
| 4298 }, |
| 4299 _collectPool: function () { |
| 4300 var pool = []; |
| 4301 var children = getLightChildren(this); |
| 4302 for (var i = 0; i < children.length; i++) { |
| 4303 var child = children[i]; |
| 4304 if (isInsertionPoint(child)) { |
| 4305 pool.push.apply(pool, child._distributedNodes); |
| 4306 } else { |
| 4307 pool.push(child); |
| 4308 } |
| 4309 } |
| 4310 return pool; |
| 4311 }, |
| 4312 _distributePool: function (node, pool) { |
| 4313 var p$ = node._insertionPoints; |
| 4314 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) { |
| 4315 this._distributeInsertionPoint(p, pool); |
| 4316 maybeRedistributeParent(p, this); |
| 4317 } |
| 4318 }, |
| 4319 _distributeInsertionPoint: function (content, pool) { |
| 4320 var anyDistributed = false; |
| 4321 for (var i = 0, l = pool.length, node; i < l; i++) { |
| 4322 node = pool[i]; |
| 4323 if (!node) { |
| 4324 continue; |
| 4325 } |
| 4326 if (this._matchesContentSelect(node, content)) { |
| 4327 distributeNodeInto(node, content); |
| 4328 pool[i] = undefined; |
| 4329 anyDistributed = true; |
| 4330 } |
| 4331 } |
| 4332 if (!anyDistributed) { |
| 4333 var children = getLightChildren(content); |
| 4334 for (var j = 0; j < children.length; j++) { |
| 4335 distributeNodeInto(children[j], content); |
| 4336 } |
| 4337 } |
| 4338 }, |
| 4339 _composeTree: function () { |
| 4340 this._updateChildNodes(this, this._composeNode(this)); |
| 4341 var p$ = this.shadyRoot._insertionPoints; |
| 4342 for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) { |
| 4343 parent = p._lightParent || p.parentNode; |
| 4344 if (!parent._useContent && parent !== this && parent !== this.shadyRoot) { |
| 4345 this._updateChildNodes(parent, this._composeNode(parent)); |
| 4346 } |
| 4347 } |
| 4348 }, |
| 4349 _composeNode: function (node) { |
| 4350 var children = []; |
| 4351 var c$ = getLightChildren(node.shadyRoot || node); |
| 4352 for (var i = 0; i < c$.length; i++) { |
| 4353 var child = c$[i]; |
| 4354 if (isInsertionPoint(child)) { |
| 4355 var distributedNodes = child._distributedNodes; |
| 4356 for (var j = 0; j < distributedNodes.length; j++) { |
| 4357 var distributedNode = distributedNodes[j]; |
| 4358 if (isFinalDestination(child, distributedNode)) { |
| 4359 children.push(distributedNode); |
| 4360 } |
| 4361 } |
| 4362 } else { |
| 4363 children.push(child); |
| 4364 } |
| 4365 } |
| 4366 return children; |
| 4367 }, |
| 4368 _updateChildNodes: function (container, children) { |
| 4369 var composed = getComposedChildren(container); |
| 4370 var splices = Polymer.ArraySplice.calculateSplices(children, composed); |
| 4371 for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) { |
| 4372 for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) { |
| 4373 if (getComposedParent(n) === container) { |
| 4374 remove(n); |
| 4375 } |
| 4376 composed.splice(s.index + d, 1); |
| 4377 } |
| 4378 d -= s.addedCount; |
| 4379 } |
| 4380 for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) { |
| 4381 next = composed[s.index]; |
| 4382 for (var j = s.index, n; j < s.index + s.addedCount; j++) { |
| 4383 n = children[j]; |
| 4384 insertBefore(container, n, next); |
| 4385 composed.splice(j, 0, n); |
| 4386 } |
| 4387 } |
| 4388 ensureComposedParent(container, children); |
| 4389 }, |
| 4390 _matchesContentSelect: function (node, contentElement) { |
| 4391 var select = contentElement.getAttribute('select'); |
| 4392 if (!select) { |
| 4393 return true; |
| 4394 } |
| 4395 select = select.trim(); |
| 4396 if (!select) { |
| 4397 return true; |
| 4398 } |
| 4399 if (!(node instanceof Element)) { |
| 4400 return false; |
| 4401 } |
| 4402 var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/; |
| 4403 if (!validSelectors.test(select)) { |
| 4404 return false; |
| 4405 } |
| 4406 return this.elementMatches(select, node); |
| 4407 }, |
| 4408 _elementAdd: function () { |
| 4409 }, |
| 4410 _elementRemove: function () { |
| 4411 } |
| 4412 }); |
| 4413 var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded; |
| 4414 var getLightChildren = Polymer.DomApi.getLightChildren; |
| 4415 var matchesSelector = Polymer.DomApi.matchesSelector; |
| 4416 var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint; |
| 4417 var getComposedChildren = Polymer.DomApi.getComposedChildren; |
| 4418 var getComposedParent = Polymer.DomApi.getComposedParent; |
| 4419 var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent; |
| 4420 function distributeNodeInto(child, insertionPoint) { |
| 4421 insertionPoint._distributedNodes.push(child); |
| 4422 var points = child._destinationInsertionPoints; |
| 4423 if (!points) { |
| 4424 child._destinationInsertionPoints = [insertionPoint]; |
| 4425 } else { |
| 4426 points.push(insertionPoint); |
| 4427 } |
| 4428 } |
| 4429 function clearDistributedDestinationInsertionPoints(content) { |
| 4430 var e$ = content._distributedNodes; |
| 4431 if (e$) { |
| 4432 for (var i = 0; i < e$.length; i++) { |
| 4433 var d = e$[i]._destinationInsertionPoints; |
| 4434 if (d) { |
| 4435 d.splice(d.indexOf(content) + 1, d.length); |
| 4436 } |
| 4437 } |
| 4438 } |
| 4439 } |
| 4440 function maybeRedistributeParent(content, host) { |
| 4441 var parent = content._lightParent; |
| 4442 if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent.
shadyRoot._distributionClean) { |
| 4443 parent.shadyRoot._distributionClean = false; |
| 4444 host.shadyRoot._dirtyRoots.push(parent); |
| 4445 } |
| 4446 } |
| 4447 function isFinalDestination(insertionPoint, node) { |
| 4448 var points = node._destinationInsertionPoints; |
| 4449 return points && points[points.length - 1] === insertionPoint; |
| 4450 } |
| 4451 function isInsertionPoint(node) { |
| 4452 return node.localName == 'content'; |
| 4453 } |
| 4454 var nativeInsertBefore = Element.prototype.insertBefore; |
| 4455 var nativeRemoveChild = Element.prototype.removeChild; |
| 4456 function insertBefore(parentNode, newChild, refChild) { |
| 4457 var newChildParent = getComposedParent(newChild); |
| 4458 if (newChildParent !== parentNode) { |
| 4459 removeFromComposedParent(newChildParent, newChild); |
| 4460 } |
| 4461 remove(newChild); |
| 4462 nativeInsertBefore.call(parentNode, newChild, refChild || null); |
| 4463 newChild._composedParent = parentNode; |
| 4464 } |
| 4465 function remove(node) { |
| 4466 var parentNode = getComposedParent(node); |
| 4467 if (parentNode) { |
| 4468 node._composedParent = null; |
| 4469 nativeRemoveChild.call(parentNode, node); |
| 4470 } |
| 4471 } |
| 4472 function ensureComposedParent(parent, children) { |
| 4473 for (var i = 0, n; i < children.length; i++) { |
| 4474 children[i]._composedParent = parent; |
| 4475 } |
| 4476 } |
| 4477 function getTopDistributingHost(host) { |
| 4478 while (host && hostNeedsRedistribution(host)) { |
| 4479 host = host.domHost; |
| 4480 } |
| 4481 return host; |
| 4482 } |
| 4483 function hostNeedsRedistribution(host) { |
| 4484 var c$ = Polymer.dom(host).children; |
| 4485 for (var i = 0, c; i < c$.length; i++) { |
| 4486 c = c$[i]; |
| 4487 if (c.localName === 'content') { |
| 4488 return host.domHost; |
| 4489 } |
| 4490 } |
| 4491 } |
| 4492 var needsUpgrade = window.CustomElements && !CustomElements.useNative; |
| 4493 function upgradeLightChildren(children) { |
| 4494 if (needsUpgrade && children) { |
| 4495 for (var i = 0; i < children.length; i++) { |
| 4496 CustomElements.upgrade(children[i]); |
| 4497 } |
| 4498 } |
| 4499 } |
| 4500 }()); |
| 4501 if (Polymer.Settings.useShadow) { |
| 4502 Polymer.Base._addFeature({ |
| 4503 _poolContent: function () { |
| 4504 }, |
| 4505 _beginDistribute: function () { |
| 4506 }, |
| 4507 distributeContent: function () { |
| 4508 }, |
| 4509 _distributeContent: function () { |
| 4510 }, |
| 4511 _finishDistribute: function () { |
| 4512 }, |
| 4513 _createLocalRoot: function () { |
| 4514 this.createShadowRoot(); |
| 4515 this.shadowRoot.appendChild(this.root); |
| 4516 this.root = this.shadowRoot; |
| 4517 } |
| 4518 }); |
| 4519 } |
| 4520 Polymer.DomModule = document.createElement('dom-module'); |
| 4521 Polymer.Base._addFeature({ |
| 4522 _registerFeatures: function () { |
| 4523 this._prepIs(); |
| 4524 this._prepAttributes(); |
| 4525 this._prepBehaviors(); |
| 4526 this._prepConstructor(); |
| 4527 this._prepTemplate(); |
| 4528 this._prepShady(); |
| 4529 }, |
| 4530 _prepBehavior: function (b) { |
| 4531 this._addHostAttributes(b.hostAttributes); |
| 4532 }, |
| 4533 _initFeatures: function () { |
| 4534 this._poolContent(); |
| 4535 this._pushHost(); |
| 4536 this._stampTemplate(); |
| 4537 this._popHost(); |
| 4538 this._marshalHostAttributes(); |
| 4539 this._setupDebouncers(); |
| 4540 this._marshalBehaviors(); |
| 4541 this._tryReady(); |
| 4542 }, |
| 4543 _marshalBehavior: function (b) { |
| 4544 } |
| 4545 }); |
| 4546 Polymer.nar = []; |
| 4547 Polymer.Annotations = { |
| 4548 parseAnnotations: function (template) { |
| 4549 var list = []; |
| 4550 var content = template._content || template.content; |
| 4551 this._parseNodeAnnotations(content, list); |
| 4552 return list; |
| 4553 }, |
| 4554 _parseNodeAnnotations: function (node, list) { |
| 4555 return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, li
st) : this._parseElementAnnotations(node, list); |
| 4556 }, |
| 4557 _testEscape: function (value) { |
| 4558 var escape = value.slice(0, 2); |
| 4559 if (escape === '{{' || escape === '[[') { |
| 4560 return escape; |
| 4561 } |
| 4562 }, |
| 4563 _parseTextNodeAnnotation: function (node, list) { |
| 4564 var v = node.textContent; |
| 4565 var escape = this._testEscape(v); |
| 4566 if (escape) { |
| 4567 node.textContent = ' '; |
| 4568 var annote = { |
| 4569 bindings: [{ |
| 4570 kind: 'text', |
| 4571 mode: escape[0], |
| 4572 value: v.slice(2, -2).trim() |
| 4573 }] |
| 4574 }; |
| 4575 list.push(annote); |
| 4576 return annote; |
| 4577 } |
| 4578 }, |
| 4579 _parseElementAnnotations: function (element, list) { |
| 4580 var annote = { |
| 4581 bindings: [], |
| 4582 events: [] |
| 4583 }; |
| 4584 if (element.localName === 'content') { |
| 4585 list._hasContent = true; |
| 4586 } |
| 4587 this._parseChildNodesAnnotations(element, annote, list); |
| 4588 if (element.attributes) { |
| 4589 this._parseNodeAttributeAnnotations(element, annote, list); |
| 4590 if (this.prepElement) { |
| 4591 this.prepElement(element); |
| 4592 } |
| 4593 } |
| 4594 if (annote.bindings.length || annote.events.length || annote.id) { |
| 4595 list.push(annote); |
| 4596 } |
| 4597 return annote; |
| 4598 }, |
| 4599 _parseChildNodesAnnotations: function (root, annote, list, callback) { |
| 4600 if (root.firstChild) { |
| 4601 for (var i = 0, node = root.firstChild; node; node = node.nextSibling, i++) { |
| 4602 if (node.localName === 'template' && !node.hasAttribute('preserve-content')) { |
| 4603 this._parseTemplate(node, i, list, annote); |
| 4604 } |
| 4605 if (node.nodeType === Node.TEXT_NODE) { |
| 4606 var n = node.nextSibling; |
| 4607 while (n && n.nodeType === Node.TEXT_NODE) { |
| 4608 node.textContent += n.textContent; |
| 4609 root.removeChild(n); |
| 4610 n = n.nextSibling; |
| 4611 } |
| 4612 } |
| 4613 var childAnnotation = this._parseNodeAnnotations(node, list, callback); |
| 4614 if (childAnnotation) { |
| 4615 childAnnotation.parent = annote; |
| 4616 childAnnotation.index = i; |
| 4617 } |
| 4618 } |
| 4619 } |
| 4620 }, |
| 4621 _parseTemplate: function (node, index, list, parent) { |
| 4622 var content = document.createDocumentFragment(); |
| 4623 content._notes = this.parseAnnotations(node); |
| 4624 content.appendChild(node.content); |
| 4625 list.push({ |
| 4626 bindings: Polymer.nar, |
| 4627 events: Polymer.nar, |
| 4628 templateContent: content, |
| 4629 parent: parent, |
| 4630 index: index |
| 4631 }); |
| 4632 }, |
| 4633 _parseNodeAttributeAnnotations: function (node, annotation) { |
| 4634 for (var i = node.attributes.length - 1, a; a = node.attributes[i]; i--) { |
| 4635 var n = a.name, v = a.value; |
| 4636 if (n === 'id' && !this._testEscape(v)) { |
| 4637 annotation.id = v; |
| 4638 } else if (n.slice(0, 3) === 'on-') { |
| 4639 node.removeAttribute(n); |
| 4640 annotation.events.push({ |
| 4641 name: n.slice(3), |
| 4642 value: v |
| 4643 }); |
| 4644 } else { |
| 4645 var b = this._parseNodeAttributeAnnotation(node, n, v); |
| 4646 if (b) { |
| 4647 annotation.bindings.push(b); |
| 4648 } |
| 4649 } |
| 4650 } |
| 4651 }, |
| 4652 _parseNodeAttributeAnnotation: function (node, n, v) { |
| 4653 var escape = this._testEscape(v); |
| 4654 if (escape) { |
| 4655 var customEvent; |
| 4656 var name = n; |
| 4657 var mode = escape[0]; |
| 4658 v = v.slice(2, -2).trim(); |
| 4659 var not = false; |
| 4660 if (v[0] == '!') { |
| 4661 v = v.substring(1); |
| 4662 not = true; |
| 4663 } |
| 4664 var kind = 'property'; |
| 4665 if (n[n.length - 1] == '$') { |
| 4666 name = n.slice(0, -1); |
| 4667 kind = 'attribute'; |
| 4668 } |
| 4669 var notifyEvent, colon; |
| 4670 if (mode == '{' && (colon = v.indexOf('::')) > 0) { |
| 4671 notifyEvent = v.substring(colon + 2); |
| 4672 v = v.substring(0, colon); |
| 4673 customEvent = true; |
| 4674 } |
| 4675 if (node.localName == 'input' && n == 'value') { |
| 4676 node.setAttribute(n, ''); |
| 4677 } |
| 4678 node.removeAttribute(n); |
| 4679 if (kind === 'property') { |
| 4680 name = Polymer.CaseMap.dashToCamelCase(name); |
| 4681 } |
| 4682 return { |
| 4683 kind: kind, |
| 4684 mode: mode, |
| 4685 name: name, |
| 4686 value: v, |
| 4687 negate: not, |
| 4688 event: notifyEvent, |
| 4689 customEvent: customEvent |
| 4690 }; |
| 4691 } |
| 4692 }, |
| 4693 _localSubTree: function (node, host) { |
| 4694 return node === host ? node.childNodes : node._lightChildren || node.childNodes; |
| 4695 }, |
| 4696 findAnnotatedNode: function (root, annote) { |
| 4697 var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote
.parent); |
| 4698 return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.i
ndex]; |
| 4699 } |
| 4700 }; |
| 4701 (function () { |
| 4702 function resolveCss(cssText, ownerDocument) { |
| 4703 return cssText.replace(CSS_URL_RX, function (m, pre, url, post) { |
| 4704 return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + po
st; |
| 4705 }); |
| 4706 } |
| 4707 function resolveAttrs(element, ownerDocument) { |
| 4708 for (var name in URL_ATTRS) { |
| 4709 var a$ = URL_ATTRS[name]; |
| 4710 for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) { |
| 4711 if (name === '*' || element.localName === name) { |
| 4712 at = element.attributes[a]; |
| 4713 v = at && at.value; |
| 4714 if (v && v.search(BINDING_RX) < 0) { |
| 4715 at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocume
nt); |
| 4716 } |
| 4717 } |
| 4718 } |
| 4719 } |
| 4720 } |
| 4721 function resolve(url, ownerDocument) { |
| 4722 if (url && url[0] === '#') { |
| 4723 return url; |
| 4724 } |
| 4725 var resolver = getUrlResolver(ownerDocument); |
| 4726 resolver.href = url; |
| 4727 return resolver.href || url; |
| 4728 } |
| 4729 var tempDoc; |
| 4730 var tempDocBase; |
| 4731 function resolveUrl(url, baseUri) { |
| 4732 if (!tempDoc) { |
| 4733 tempDoc = document.implementation.createHTMLDocument('temp'); |
| 4734 tempDocBase = tempDoc.createElement('base'); |
| 4735 tempDoc.head.appendChild(tempDocBase); |
| 4736 } |
| 4737 tempDocBase.href = baseUri; |
| 4738 return resolve(url, tempDoc); |
| 4739 } |
| 4740 function getUrlResolver(ownerDocument) { |
| 4741 return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocume
nt.createElement('a')); |
| 4742 } |
| 4743 var CSS_URL_RX = /(url\()([^)]*)(\))/g; |
| 4744 var URL_ATTRS = { |
| 4745 '*': [ |
| 4746 'href', |
| 4747 'src', |
| 4748 'style', |
| 4749 'url' |
| 4750 ], |
| 4751 form: ['action'] |
| 4752 }; |
| 4753 var BINDING_RX = /\{\{|\[\[/; |
| 4754 Polymer.ResolveUrl = { |
| 4755 resolveCss: resolveCss, |
| 4756 resolveAttrs: resolveAttrs, |
| 4757 resolveUrl: resolveUrl |
| 4758 }; |
| 4759 }()); |
| 4760 Polymer.Base._addFeature({ |
| 4761 _prepAnnotations: function () { |
| 4762 if (!this._template) { |
| 4763 this._notes = []; |
| 4764 } else { |
| 4765 Polymer.Annotations.prepElement = this._prepElement.bind(this); |
| 4766 if (this._template._content && this._template._content._notes) { |
| 4767 this._notes = this._template._content._notes; |
| 4768 } else { |
| 4769 this._notes = Polymer.Annotations.parseAnnotations(this._template); |
| 4770 } |
| 4771 this._processAnnotations(this._notes); |
| 4772 Polymer.Annotations.prepElement = null; |
| 4773 } |
| 4774 }, |
| 4775 _processAnnotations: function (notes) { |
| 4776 for (var i = 0; i < notes.length; i++) { |
| 4777 var note = notes[i]; |
| 4778 for (var j = 0; j < note.bindings.length; j++) { |
| 4779 var b = note.bindings[j]; |
| 4780 b.signature = this._parseMethod(b.value); |
| 4781 if (!b.signature) { |
| 4782 b.model = this._modelForPath(b.value); |
| 4783 } |
| 4784 } |
| 4785 if (note.templateContent) { |
| 4786 this._processAnnotations(note.templateContent._notes); |
| 4787 var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(n
ote.templateContent._notes); |
| 4788 var bindings = []; |
| 4789 for (var prop in pp) { |
| 4790 bindings.push({ |
| 4791 index: note.index, |
| 4792 kind: 'property', |
| 4793 mode: '{', |
| 4794 name: '_parent_' + prop, |
| 4795 model: prop, |
| 4796 value: prop |
| 4797 }); |
| 4798 } |
| 4799 note.bindings = note.bindings.concat(bindings); |
| 4800 } |
| 4801 } |
| 4802 }, |
| 4803 _discoverTemplateParentProps: function (notes) { |
| 4804 var pp = {}; |
| 4805 notes.forEach(function (n) { |
| 4806 n.bindings.forEach(function (b) { |
| 4807 if (b.signature) { |
| 4808 var args = b.signature.args; |
| 4809 for (var k = 0; k < args.length; k++) { |
| 4810 pp[args[k].model] = true; |
| 4811 } |
| 4812 } else { |
| 4813 pp[b.model] = true; |
| 4814 } |
| 4815 }); |
| 4816 if (n.templateContent) { |
| 4817 var tpp = n.templateContent._parentProps; |
| 4818 Polymer.Base.mixin(pp, tpp); |
| 4819 } |
| 4820 }); |
| 4821 return pp; |
| 4822 }, |
| 4823 _prepElement: function (element) { |
| 4824 Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument); |
| 4825 }, |
| 4826 _findAnnotatedNode: Polymer.Annotations.findAnnotatedNode, |
| 4827 _marshalAnnotationReferences: function () { |
| 4828 if (this._template) { |
| 4829 this._marshalIdNodes(); |
| 4830 this._marshalAnnotatedNodes(); |
| 4831 this._marshalAnnotatedListeners(); |
| 4832 } |
| 4833 }, |
| 4834 _configureAnnotationReferences: function () { |
| 4835 this._configureTemplateContent(); |
| 4836 }, |
| 4837 _configureTemplateContent: function () { |
| 4838 this._notes.forEach(function (note, i) { |
| 4839 if (note.templateContent) { |
| 4840 this._nodes[i]._content = note.templateContent; |
| 4841 } |
| 4842 }, this); |
| 4843 }, |
| 4844 _marshalIdNodes: function () { |
| 4845 this.$ = {}; |
| 4846 this._notes.forEach(function (a) { |
| 4847 if (a.id) { |
| 4848 this.$[a.id] = this._findAnnotatedNode(this.root, a); |
| 4849 } |
| 4850 }, this); |
| 4851 }, |
| 4852 _marshalAnnotatedNodes: function () { |
| 4853 if (this._nodes) { |
| 4854 this._nodes = this._nodes.map(function (a) { |
| 4855 return this._findAnnotatedNode(this.root, a); |
| 4856 }, this); |
| 4857 } |
| 4858 }, |
| 4859 _marshalAnnotatedListeners: function () { |
| 4860 this._notes.forEach(function (a) { |
| 4861 if (a.events && a.events.length) { |
| 4862 var node = this._findAnnotatedNode(this.root, a); |
| 4863 a.events.forEach(function (e) { |
| 4864 this.listen(node, e.name, e.value); |
| 4865 }, this); |
| 4866 } |
| 4867 }, this); |
| 4868 } |
| 4869 }); |
| 4870 Polymer.Base._addFeature({ |
| 4871 listeners: {}, |
| 4872 _listenListeners: function (listeners) { |
| 4873 var node, name, key; |
| 4874 for (key in listeners) { |
| 4875 if (key.indexOf('.') < 0) { |
| 4876 node = this; |
| 4877 name = key; |
| 4878 } else { |
| 4879 name = key.split('.'); |
| 4880 node = this.$[name[0]]; |
| 4881 name = name[1]; |
| 4882 } |
| 4883 this.listen(node, name, listeners[key]); |
| 4884 } |
| 4885 }, |
| 4886 listen: function (node, eventName, methodName) { |
| 4887 this._listen(node, eventName, this._createEventHandler(node, eventName, methodNa
me)); |
| 4888 }, |
| 4889 _boundListenerKey: function (eventName, methodName) { |
| 4890 return eventName + ':' + methodName; |
| 4891 }, |
| 4892 _recordEventHandler: function (host, eventName, target, methodName, handler) { |
| 4893 var hbl = host.__boundListeners; |
| 4894 if (!hbl) { |
| 4895 hbl = host.__boundListeners = new WeakMap(); |
| 4896 } |
| 4897 var bl = hbl.get(target); |
| 4898 if (!bl) { |
| 4899 bl = {}; |
| 4900 hbl.set(target, bl); |
| 4901 } |
| 4902 var key = this._boundListenerKey(eventName, methodName); |
| 4903 bl[key] = handler; |
| 4904 }, |
| 4905 _recallEventHandler: function (host, eventName, target, methodName) { |
| 4906 var hbl = host.__boundListeners; |
| 4907 if (!hbl) { |
| 4908 return; |
| 4909 } |
| 4910 var bl = hbl.get(target); |
| 4911 if (!bl) { |
| 4912 return; |
| 4913 } |
| 4914 var key = this._boundListenerKey(eventName, methodName); |
| 4915 return bl[key]; |
| 4916 }, |
| 4917 _createEventHandler: function (node, eventName, methodName) { |
| 4918 var host = this; |
| 4919 var handler = function (e) { |
| 4920 if (host[methodName]) { |
| 4921 host[methodName](e, e.detail); |
| 4922 } else { |
| 4923 host._warn(host._logf('_createEventHandler', 'listener method `' + methodName +
'` not defined')); |
| 4924 } |
| 4925 }; |
| 4926 this._recordEventHandler(host, eventName, node, methodName, handler); |
| 4927 return handler; |
| 4928 }, |
| 4929 unlisten: function (node, eventName, methodName) { |
| 4930 var handler = this._recallEventHandler(this, eventName, node, methodName); |
| 4931 if (handler) { |
| 4932 this._unlisten(node, eventName, handler); |
| 4933 } |
| 4934 }, |
| 4935 _listen: function (node, eventName, handler) { |
| 4936 node.addEventListener(eventName, handler); |
| 4937 }, |
| 4938 _unlisten: function (node, eventName, handler) { |
| 4939 node.removeEventListener(eventName, handler); |
| 4940 } |
| 4941 }); |
| 4942 (function () { |
| 4943 'use strict'; |
| 4944 var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string'; |
| 4945 var GESTURE_KEY = '__polymerGestures'; |
| 4946 var HANDLED_OBJ = '__polymerGesturesHandled'; |
| 4947 var TOUCH_ACTION = '__polymerGesturesTouchAction'; |
| 4948 var TAP_DISTANCE = 25; |
| 4949 var TRACK_DISTANCE = 5; |
| 4950 var TRACK_LENGTH = 2; |
| 4951 var MOUSE_TIMEOUT = 2500; |
| 4952 var MOUSE_EVENTS = [ |
| 4953 'mousedown', |
| 4954 'mousemove', |
| 4955 'mouseup', |
| 4956 'click' |
| 4957 ]; |
| 4958 var MOUSE_WHICH_TO_BUTTONS = [ |
| 4959 0, |
| 4960 1, |
| 4961 4, |
| 4962 2 |
| 4963 ]; |
| 4964 var MOUSE_HAS_BUTTONS = function () { |
| 4965 try { |
| 4966 return new MouseEvent('test', { buttons: 1 }).buttons === 1; |
| 4967 } catch (e) { |
| 4968 return false; |
| 4969 } |
| 4970 }(); |
| 4971 var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/); |
| 4972 var mouseCanceller = function (mouseEvent) { |
| 4973 mouseEvent[HANDLED_OBJ] = { skip: true }; |
| 4974 if (mouseEvent.type === 'click') { |
| 4975 var path = Polymer.dom(mouseEvent).path; |
| 4976 for (var i = 0; i < path.length; i++) { |
| 4977 if (path[i] === POINTERSTATE.mouse.target) { |
| 4978 return; |
| 4979 } |
| 4980 } |
| 4981 mouseEvent.preventDefault(); |
| 4982 mouseEvent.stopPropagation(); |
| 4983 } |
| 4984 }; |
| 4985 function setupTeardownMouseCanceller(setup) { |
| 4986 for (var i = 0, en; i < MOUSE_EVENTS.length; i++) { |
| 4987 en = MOUSE_EVENTS[i]; |
| 4988 if (setup) { |
| 4989 document.addEventListener(en, mouseCanceller, true); |
| 4990 } else { |
| 4991 document.removeEventListener(en, mouseCanceller, true); |
| 4992 } |
| 4993 } |
| 4994 } |
| 4995 function ignoreMouse() { |
| 4996 if (IS_TOUCH_ONLY) { |
| 4997 return; |
| 4998 } |
| 4999 if (!POINTERSTATE.mouse.mouseIgnoreJob) { |
| 5000 setupTeardownMouseCanceller(true); |
| 5001 } |
| 5002 var unset = function () { |
| 5003 setupTeardownMouseCanceller(); |
| 5004 POINTERSTATE.mouse.target = null; |
| 5005 POINTERSTATE.mouse.mouseIgnoreJob = null; |
| 5006 }; |
| 5007 POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgn
oreJob, unset, MOUSE_TIMEOUT); |
| 5008 } |
| 5009 function hasLeftMouseButton(ev) { |
| 5010 var type = ev.type; |
| 5011 if (MOUSE_EVENTS.indexOf(type) === -1) { |
| 5012 return false; |
| 5013 } |
| 5014 if (type === 'mousemove') { |
| 5015 var buttons = ev.buttons === undefined ? 1 : ev.buttons; |
| 5016 if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) { |
| 5017 buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0; |
| 5018 } |
| 5019 return Boolean(buttons & 1); |
| 5020 } else { |
| 5021 var button = ev.button === undefined ? 0 : ev.button; |
| 5022 return button === 0; |
| 5023 } |
| 5024 } |
| 5025 function isSyntheticClick(ev) { |
| 5026 if (ev.type === 'click') { |
| 5027 if (ev.detail === 0) { |
| 5028 return true; |
| 5029 } |
| 5030 var t = Gestures.findOriginalTarget(ev); |
| 5031 var bcr = t.getBoundingClientRect(); |
| 5032 var x = ev.pageX, y = ev.pageY; |
| 5033 return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom)); |
| 5034 } |
| 5035 return false; |
| 5036 } |
| 5037 var POINTERSTATE = { |
| 5038 mouse: { |
| 5039 target: null, |
| 5040 mouseIgnoreJob: null |
| 5041 }, |
| 5042 touch: { |
| 5043 x: 0, |
| 5044 y: 0, |
| 5045 id: -1, |
| 5046 scrollDecided: false |
| 5047 } |
| 5048 }; |
| 5049 function firstTouchAction(ev) { |
| 5050 var path = Polymer.dom(ev).path; |
| 5051 var ta = 'auto'; |
| 5052 for (var i = 0, n; i < path.length; i++) { |
| 5053 n = path[i]; |
| 5054 if (n[TOUCH_ACTION]) { |
| 5055 ta = n[TOUCH_ACTION]; |
| 5056 break; |
| 5057 } |
| 5058 } |
| 5059 return ta; |
| 5060 } |
| 5061 function trackDocument(stateObj, movefn, upfn) { |
| 5062 stateObj.movefn = movefn; |
| 5063 stateObj.upfn = upfn; |
| 5064 document.addEventListener('mousemove', movefn); |
| 5065 document.addEventListener('mouseup', upfn); |
| 5066 } |
| 5067 function untrackDocument(stateObj) { |
| 5068 document.removeEventListener('mousemove', stateObj.movefn); |
| 5069 document.removeEventListener('mouseup', stateObj.upfn); |
| 5070 } |
| 5071 var Gestures = { |
| 5072 gestures: {}, |
| 5073 recognizers: [], |
| 5074 deepTargetFind: function (x, y) { |
| 5075 var node = document.elementFromPoint(x, y); |
| 5076 var next = node; |
| 5077 while (next && next.shadowRoot) { |
| 5078 next = next.shadowRoot.elementFromPoint(x, y); |
| 5079 if (next) { |
| 5080 node = next; |
| 5081 } |
| 5082 } |
| 5083 return node; |
| 5084 }, |
| 5085 findOriginalTarget: function (ev) { |
| 5086 if (ev.path) { |
| 5087 return ev.path[0]; |
| 5088 } |
| 5089 return ev.target; |
| 5090 }, |
| 5091 handleNative: function (ev) { |
| 5092 var handled; |
| 5093 var type = ev.type; |
| 5094 var node = ev.currentTarget; |
| 5095 var gobj = node[GESTURE_KEY]; |
| 5096 var gs = gobj[type]; |
| 5097 if (!gs) { |
| 5098 return; |
| 5099 } |
| 5100 if (!ev[HANDLED_OBJ]) { |
| 5101 ev[HANDLED_OBJ] = {}; |
| 5102 if (type.slice(0, 5) === 'touch') { |
| 5103 var t = ev.changedTouches[0]; |
| 5104 if (type === 'touchstart') { |
| 5105 if (ev.touches.length === 1) { |
| 5106 POINTERSTATE.touch.id = t.identifier; |
| 5107 } |
| 5108 } |
| 5109 if (POINTERSTATE.touch.id !== t.identifier) { |
| 5110 return; |
| 5111 } |
| 5112 if (!HAS_NATIVE_TA) { |
| 5113 if (type === 'touchstart' || type === 'touchmove') { |
| 5114 Gestures.handleTouchAction(ev); |
| 5115 } |
| 5116 } |
| 5117 if (type === 'touchend') { |
| 5118 POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget; |
| 5119 ignoreMouse(true); |
| 5120 } |
| 5121 } |
| 5122 } |
| 5123 handled = ev[HANDLED_OBJ]; |
| 5124 if (handled.skip) { |
| 5125 return; |
| 5126 } |
| 5127 var recognizers = Gestures.recognizers; |
| 5128 for (var i = 0, r; i < recognizers.length; i++) { |
| 5129 r = recognizers[i]; |
| 5130 if (gs[r.name] && !handled[r.name]) { |
| 5131 if (r.flow && r.flow.start.indexOf(ev.type) > -1) { |
| 5132 if (r.reset) { |
| 5133 r.reset(); |
| 5134 } |
| 5135 } |
| 5136 } |
| 5137 } |
| 5138 for (var i = 0, r; i < recognizers.length; i++) { |
| 5139 r = recognizers[i]; |
| 5140 if (gs[r.name] && !handled[r.name]) { |
| 5141 handled[r.name] = true; |
| 5142 r[type](ev); |
| 5143 } |
| 5144 } |
| 5145 }, |
| 5146 handleTouchAction: function (ev) { |
| 5147 var t = ev.changedTouches[0]; |
| 5148 var type = ev.type; |
| 5149 if (type === 'touchstart') { |
| 5150 POINTERSTATE.touch.x = t.clientX; |
| 5151 POINTERSTATE.touch.y = t.clientY; |
| 5152 POINTERSTATE.touch.scrollDecided = false; |
| 5153 } else if (type === 'touchmove') { |
| 5154 if (POINTERSTATE.touch.scrollDecided) { |
| 5155 return; |
| 5156 } |
| 5157 POINTERSTATE.touch.scrollDecided = true; |
| 5158 var ta = firstTouchAction(ev); |
| 5159 var prevent = false; |
| 5160 var dx = Math.abs(POINTERSTATE.touch.x - t.clientX); |
| 5161 var dy = Math.abs(POINTERSTATE.touch.y - t.clientY); |
| 5162 if (!ev.cancelable) { |
| 5163 } else if (ta === 'none') { |
| 5164 prevent = true; |
| 5165 } else if (ta === 'pan-x') { |
| 5166 prevent = dy > dx; |
| 5167 } else if (ta === 'pan-y') { |
| 5168 prevent = dx > dy; |
| 5169 } |
| 5170 if (prevent) { |
| 5171 ev.preventDefault(); |
| 5172 } else { |
| 5173 Gestures.prevent('track'); |
| 5174 } |
| 5175 } |
| 5176 }, |
| 5177 add: function (node, evType, handler) { |
| 5178 var recognizer = this.gestures[evType]; |
| 5179 var deps = recognizer.deps; |
| 5180 var name = recognizer.name; |
| 5181 var gobj = node[GESTURE_KEY]; |
| 5182 if (!gobj) { |
| 5183 node[GESTURE_KEY] = gobj = {}; |
| 5184 } |
| 5185 for (var i = 0, dep, gd; i < deps.length; i++) { |
| 5186 dep = deps[i]; |
| 5187 if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) { |
| 5188 continue; |
| 5189 } |
| 5190 gd = gobj[dep]; |
| 5191 if (!gd) { |
| 5192 gobj[dep] = gd = { _count: 0 }; |
| 5193 } |
| 5194 if (gd._count === 0) { |
| 5195 node.addEventListener(dep, this.handleNative); |
| 5196 } |
| 5197 gd[name] = (gd[name] || 0) + 1; |
| 5198 gd._count = (gd._count || 0) + 1; |
| 5199 } |
| 5200 node.addEventListener(evType, handler); |
| 5201 if (recognizer.touchAction) { |
| 5202 this.setTouchAction(node, recognizer.touchAction); |
| 5203 } |
| 5204 }, |
| 5205 remove: function (node, evType, handler) { |
| 5206 var recognizer = this.gestures[evType]; |
| 5207 var deps = recognizer.deps; |
| 5208 var name = recognizer.name; |
| 5209 var gobj = node[GESTURE_KEY]; |
| 5210 if (gobj) { |
| 5211 for (var i = 0, dep, gd; i < deps.length; i++) { |
| 5212 dep = deps[i]; |
| 5213 gd = gobj[dep]; |
| 5214 if (gd && gd[name]) { |
| 5215 gd[name] = (gd[name] || 1) - 1; |
| 5216 gd._count = (gd._count || 1) - 1; |
| 5217 if (gd._count === 0) { |
| 5218 node.removeEventListener(dep, this.handleNative); |
| 5219 } |
| 5220 } |
| 5221 } |
| 5222 } |
| 5223 node.removeEventListener(evType, handler); |
| 5224 }, |
| 5225 register: function (recog) { |
| 5226 this.recognizers.push(recog); |
| 5227 for (var i = 0; i < recog.emits.length; i++) { |
| 5228 this.gestures[recog.emits[i]] = recog; |
| 5229 } |
| 5230 }, |
| 5231 findRecognizerByEvent: function (evName) { |
| 5232 for (var i = 0, r; i < this.recognizers.length; i++) { |
| 5233 r = this.recognizers[i]; |
| 5234 for (var j = 0, n; j < r.emits.length; j++) { |
| 5235 n = r.emits[j]; |
| 5236 if (n === evName) { |
| 5237 return r; |
| 5238 } |
| 5239 } |
| 5240 } |
| 5241 return null; |
| 5242 }, |
| 5243 setTouchAction: function (node, value) { |
| 5244 if (HAS_NATIVE_TA) { |
| 5245 node.style.touchAction = value; |
| 5246 } |
| 5247 node[TOUCH_ACTION] = value; |
| 5248 }, |
| 5249 fire: function (target, type, detail) { |
| 5250 var ev = Polymer.Base.fire(type, detail, { |
| 5251 node: target, |
| 5252 bubbles: true, |
| 5253 cancelable: true |
| 5254 }); |
| 5255 if (ev.defaultPrevented) { |
| 5256 var se = detail.sourceEvent; |
| 5257 if (se && se.preventDefault) { |
| 5258 se.preventDefault(); |
| 5259 } |
| 5260 } |
| 5261 }, |
| 5262 prevent: function (evName) { |
| 5263 var recognizer = this.findRecognizerByEvent(evName); |
| 5264 if (recognizer.info) { |
| 5265 recognizer.info.prevent = true; |
| 5266 } |
| 5267 } |
| 5268 }; |
| 5269 Gestures.register({ |
| 5270 name: 'downup', |
| 5271 deps: [ |
| 5272 'mousedown', |
| 5273 'touchstart', |
| 5274 'touchend' |
| 5275 ], |
| 5276 flow: { |
| 5277 start: [ |
| 5278 'mousedown', |
| 5279 'touchstart' |
| 5280 ], |
| 5281 end: [ |
| 5282 'mouseup', |
| 5283 'touchend' |
| 5284 ] |
| 5285 }, |
| 5286 emits: [ |
| 5287 'down', |
| 5288 'up' |
| 5289 ], |
| 5290 info: { |
| 5291 movefn: function () { |
| 5292 }, |
| 5293 upfn: function () { |
| 5294 } |
| 5295 }, |
| 5296 reset: function () { |
| 5297 untrackDocument(this.info); |
| 5298 }, |
| 5299 mousedown: function (e) { |
| 5300 if (!hasLeftMouseButton(e)) { |
| 5301 return; |
| 5302 } |
| 5303 var t = Gestures.findOriginalTarget(e); |
| 5304 var self = this; |
| 5305 var movefn = function movefn(e) { |
| 5306 if (!hasLeftMouseButton(e)) { |
| 5307 self.fire('up', t, e); |
| 5308 untrackDocument(self.info); |
| 5309 } |
| 5310 }; |
| 5311 var upfn = function upfn(e) { |
| 5312 if (hasLeftMouseButton(e)) { |
| 5313 self.fire('up', t, e); |
| 5314 } |
| 5315 untrackDocument(self.info); |
| 5316 }; |
| 5317 trackDocument(this.info, movefn, upfn); |
| 5318 this.fire('down', t, e); |
| 5319 }, |
| 5320 touchstart: function (e) { |
| 5321 this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]); |
| 5322 }, |
| 5323 touchend: function (e) { |
| 5324 this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]); |
| 5325 }, |
| 5326 fire: function (type, target, event) { |
| 5327 var self = this; |
| 5328 Gestures.fire(target, type, { |
| 5329 x: event.clientX, |
| 5330 y: event.clientY, |
| 5331 sourceEvent: event, |
| 5332 prevent: Gestures.prevent.bind(Gestures) |
| 5333 }); |
| 5334 } |
| 5335 }); |
| 5336 Gestures.register({ |
| 5337 name: 'track', |
| 5338 touchAction: 'none', |
| 5339 deps: [ |
| 5340 'mousedown', |
| 5341 'touchstart', |
| 5342 'touchmove', |
| 5343 'touchend' |
| 5344 ], |
| 5345 flow: { |
| 5346 start: [ |
| 5347 'mousedown', |
| 5348 'touchstart' |
| 5349 ], |
| 5350 end: [ |
| 5351 'mouseup', |
| 5352 'touchend' |
| 5353 ] |
| 5354 }, |
| 5355 emits: ['track'], |
| 5356 info: { |
| 5357 x: 0, |
| 5358 y: 0, |
| 5359 state: 'start', |
| 5360 started: false, |
| 5361 moves: [], |
| 5362 addMove: function (move) { |
| 5363 if (this.moves.length > TRACK_LENGTH) { |
| 5364 this.moves.shift(); |
| 5365 } |
| 5366 this.moves.push(move); |
| 5367 }, |
| 5368 movefn: function () { |
| 5369 }, |
| 5370 upfn: function () { |
| 5371 }, |
| 5372 prevent: false |
| 5373 }, |
| 5374 reset: function () { |
| 5375 this.info.state = 'start'; |
| 5376 this.info.started = false; |
| 5377 this.info.moves = []; |
| 5378 this.info.x = 0; |
| 5379 this.info.y = 0; |
| 5380 this.info.prevent = false; |
| 5381 untrackDocument(this.info); |
| 5382 }, |
| 5383 hasMovedEnough: function (x, y) { |
| 5384 if (this.info.prevent) { |
| 5385 return false; |
| 5386 } |
| 5387 if (this.info.started) { |
| 5388 return true; |
| 5389 } |
| 5390 var dx = Math.abs(this.info.x - x); |
| 5391 var dy = Math.abs(this.info.y - y); |
| 5392 return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE; |
| 5393 }, |
| 5394 mousedown: function (e) { |
| 5395 if (!hasLeftMouseButton(e)) { |
| 5396 return; |
| 5397 } |
| 5398 var t = Gestures.findOriginalTarget(e); |
| 5399 var self = this; |
| 5400 var movefn = function movefn(e) { |
| 5401 var x = e.clientX, y = e.clientY; |
| 5402 if (self.hasMovedEnough(x, y)) { |
| 5403 self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : '
start'; |
| 5404 self.info.addMove({ |
| 5405 x: x, |
| 5406 y: y |
| 5407 }); |
| 5408 if (!hasLeftMouseButton(e)) { |
| 5409 self.info.state = 'end'; |
| 5410 untrackDocument(self.info); |
| 5411 } |
| 5412 self.fire(t, e); |
| 5413 self.info.started = true; |
| 5414 } |
| 5415 }; |
| 5416 var upfn = function upfn(e) { |
| 5417 if (self.info.started) { |
| 5418 Gestures.prevent('tap'); |
| 5419 movefn(e); |
| 5420 } |
| 5421 untrackDocument(self.info); |
| 5422 }; |
| 5423 trackDocument(this.info, movefn, upfn); |
| 5424 this.info.x = e.clientX; |
| 5425 this.info.y = e.clientY; |
| 5426 }, |
| 5427 touchstart: function (e) { |
| 5428 var ct = e.changedTouches[0]; |
| 5429 this.info.x = ct.clientX; |
| 5430 this.info.y = ct.clientY; |
| 5431 }, |
| 5432 touchmove: function (e) { |
| 5433 var t = Gestures.findOriginalTarget(e); |
| 5434 var ct = e.changedTouches[0]; |
| 5435 var x = ct.clientX, y = ct.clientY; |
| 5436 if (this.hasMovedEnough(x, y)) { |
| 5437 this.info.addMove({ |
| 5438 x: x, |
| 5439 y: y |
| 5440 }); |
| 5441 this.fire(t, ct); |
| 5442 this.info.state = 'track'; |
| 5443 this.info.started = true; |
| 5444 } |
| 5445 }, |
| 5446 touchend: function (e) { |
| 5447 var t = Gestures.findOriginalTarget(e); |
| 5448 var ct = e.changedTouches[0]; |
| 5449 if (this.info.started) { |
| 5450 Gestures.prevent('tap'); |
| 5451 this.info.state = 'end'; |
| 5452 this.info.addMove({ |
| 5453 x: ct.clientX, |
| 5454 y: ct.clientY |
| 5455 }); |
| 5456 this.fire(t, ct); |
| 5457 } |
| 5458 }, |
| 5459 fire: function (target, touch) { |
| 5460 var secondlast = this.info.moves[this.info.moves.length - 2]; |
| 5461 var lastmove = this.info.moves[this.info.moves.length - 1]; |
| 5462 var dx = lastmove.x - this.info.x; |
| 5463 var dy = lastmove.y - this.info.y; |
| 5464 var ddx, ddy = 0; |
| 5465 if (secondlast) { |
| 5466 ddx = lastmove.x - secondlast.x; |
| 5467 ddy = lastmove.y - secondlast.y; |
| 5468 } |
| 5469 return Gestures.fire(target, 'track', { |
| 5470 state: this.info.state, |
| 5471 x: touch.clientX, |
| 5472 y: touch.clientY, |
| 5473 dx: dx, |
| 5474 dy: dy, |
| 5475 ddx: ddx, |
| 5476 ddy: ddy, |
| 5477 sourceEvent: touch, |
| 5478 hover: function () { |
| 5479 return Gestures.deepTargetFind(touch.clientX, touch.clientY); |
| 5480 } |
| 5481 }); |
| 5482 } |
| 5483 }); |
| 5484 Gestures.register({ |
| 5485 name: 'tap', |
| 5486 deps: [ |
| 5487 'mousedown', |
| 5488 'click', |
| 5489 'touchstart', |
| 5490 'touchend' |
| 5491 ], |
| 5492 flow: { |
| 5493 start: [ |
| 5494 'mousedown', |
| 5495 'touchstart' |
| 5496 ], |
| 5497 end: [ |
| 5498 'click', |
| 5499 'touchend' |
| 5500 ] |
| 5501 }, |
| 5502 emits: ['tap'], |
| 5503 info: { |
| 5504 x: NaN, |
| 5505 y: NaN, |
| 5506 prevent: false |
| 5507 }, |
| 5508 reset: function () { |
| 5509 this.info.x = NaN; |
| 5510 this.info.y = NaN; |
| 5511 this.info.prevent = false; |
| 5512 }, |
| 5513 save: function (e) { |
| 5514 this.info.x = e.clientX; |
| 5515 this.info.y = e.clientY; |
| 5516 }, |
| 5517 mousedown: function (e) { |
| 5518 if (hasLeftMouseButton(e)) { |
| 5519 this.save(e); |
| 5520 } |
| 5521 }, |
| 5522 click: function (e) { |
| 5523 if (hasLeftMouseButton(e)) { |
| 5524 this.forward(e); |
| 5525 } |
| 5526 }, |
| 5527 touchstart: function (e) { |
| 5528 this.save(e.changedTouches[0]); |
| 5529 }, |
| 5530 touchend: function (e) { |
| 5531 this.forward(e.changedTouches[0]); |
| 5532 }, |
| 5533 forward: function (e) { |
| 5534 var dx = Math.abs(e.clientX - this.info.x); |
| 5535 var dy = Math.abs(e.clientY - this.info.y); |
| 5536 var t = Gestures.findOriginalTarget(e); |
| 5537 if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSynt
heticClick(e)) { |
| 5538 if (!this.info.prevent) { |
| 5539 Gestures.fire(t, 'tap', { |
| 5540 x: e.clientX, |
| 5541 y: e.clientY, |
| 5542 sourceEvent: e |
| 5543 }); |
| 5544 } |
| 5545 } |
| 5546 } |
| 5547 }); |
| 5548 var DIRECTION_MAP = { |
| 5549 x: 'pan-x', |
| 5550 y: 'pan-y', |
| 5551 none: 'none', |
| 5552 all: 'auto' |
| 5553 }; |
| 5554 Polymer.Base._addFeature({ |
| 5555 _listen: function (node, eventName, handler) { |
| 5556 if (Gestures.gestures[eventName]) { |
| 5557 Gestures.add(node, eventName, handler); |
| 5558 } else { |
| 5559 node.addEventListener(eventName, handler); |
| 5560 } |
| 5561 }, |
| 5562 _unlisten: function (node, eventName, handler) { |
| 5563 if (Gestures.gestures[eventName]) { |
| 5564 Gestures.remove(node, eventName, handler); |
| 5565 } else { |
| 5566 node.removeEventListener(eventName, handler); |
| 5567 } |
| 5568 }, |
| 5569 setScrollDirection: function (direction, node) { |
| 5570 node = node || this; |
| 5571 Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto'); |
| 5572 } |
| 5573 }); |
| 5574 Polymer.Gestures = Gestures; |
| 5575 }()); |
| 5576 Polymer.Async = { |
| 5577 _currVal: 0, |
| 5578 _lastVal: 0, |
| 5579 _callbacks: [], |
| 5580 _twiddleContent: 0, |
| 5581 _twiddle: document.createTextNode(''), |
| 5582 run: function (callback, waitTime) { |
| 5583 if (waitTime > 0) { |
| 5584 return ~setTimeout(callback, waitTime); |
| 5585 } else { |
| 5586 this._twiddle.textContent = this._twiddleContent++; |
| 5587 this._callbacks.push(callback); |
| 5588 return this._currVal++; |
| 5589 } |
| 5590 }, |
| 5591 cancel: function (handle) { |
| 5592 if (handle < 0) { |
| 5593 clearTimeout(~handle); |
| 5594 } else { |
| 5595 var idx = handle - this._lastVal; |
| 5596 if (idx >= 0) { |
| 5597 if (!this._callbacks[idx]) { |
| 5598 throw 'invalid async handle: ' + handle; |
| 5599 } |
| 5600 this._callbacks[idx] = null; |
| 5601 } |
| 5602 } |
| 5603 }, |
| 5604 _atEndOfMicrotask: function () { |
| 5605 var len = this._callbacks.length; |
| 5606 for (var i = 0; i < len; i++) { |
| 5607 var cb = this._callbacks[i]; |
| 5608 if (cb) { |
| 5609 try { |
| 5610 cb(); |
| 5611 } catch (e) { |
| 5612 i++; |
| 5613 this._callbacks.splice(0, i); |
| 5614 this._lastVal += i; |
| 5615 this._twiddle.textContent = this._twiddleContent++; |
| 5616 throw e; |
| 5617 } |
| 5618 } |
| 5619 } |
| 5620 this._callbacks.splice(0, len); |
| 5621 this._lastVal += len; |
| 5622 } |
| 5623 }; |
| 5624 new (window.MutationObserver || JsMutationObserver)(Polymer.Async._atEndOfMicrot
ask.bind(Polymer.Async)).observe(Polymer.Async._twiddle, { characterData: true }
); |
| 5625 Polymer.Debounce = function () { |
| 5626 var Async = Polymer.Async; |
| 5627 var Debouncer = function (context) { |
| 5628 this.context = context; |
| 5629 this.boundComplete = this.complete.bind(this); |
| 5630 }; |
| 5631 Debouncer.prototype = { |
| 5632 go: function (callback, wait) { |
| 5633 var h; |
| 5634 this.finish = function () { |
| 5635 Async.cancel(h); |
| 5636 }; |
| 5637 h = Async.run(this.boundComplete, wait); |
| 5638 this.callback = callback; |
| 5639 }, |
| 5640 stop: function () { |
| 5641 if (this.finish) { |
| 5642 this.finish(); |
| 5643 this.finish = null; |
| 5644 } |
| 5645 }, |
| 5646 complete: function () { |
| 5647 if (this.finish) { |
| 5648 this.stop(); |
| 5649 this.callback.call(this.context); |
| 5650 } |
| 5651 } |
| 5652 }; |
| 5653 function debounce(debouncer, callback, wait) { |
| 5654 if (debouncer) { |
| 5655 debouncer.stop(); |
| 5656 } else { |
| 5657 debouncer = new Debouncer(this); |
| 5658 } |
| 5659 debouncer.go(callback, wait); |
| 5660 return debouncer; |
| 5661 } |
| 5662 return debounce; |
| 5663 }(); |
| 5664 Polymer.Base._addFeature({ |
| 5665 $$: function (slctr) { |
| 5666 return Polymer.dom(this.root).querySelector(slctr); |
| 5667 }, |
| 5668 toggleClass: function (name, bool, node) { |
| 5669 node = node || this; |
| 5670 if (arguments.length == 1) { |
| 5671 bool = !node.classList.contains(name); |
| 5672 } |
| 5673 if (bool) { |
| 5674 Polymer.dom(node).classList.add(name); |
| 5675 } else { |
| 5676 Polymer.dom(node).classList.remove(name); |
| 5677 } |
| 5678 }, |
| 5679 toggleAttribute: function (name, bool, node) { |
| 5680 node = node || this; |
| 5681 if (arguments.length == 1) { |
| 5682 bool = !node.hasAttribute(name); |
| 5683 } |
| 5684 if (bool) { |
| 5685 Polymer.dom(node).setAttribute(name, ''); |
| 5686 } else { |
| 5687 Polymer.dom(node).removeAttribute(name); |
| 5688 } |
| 5689 }, |
| 5690 classFollows: function (name, toElement, fromElement) { |
| 5691 if (fromElement) { |
| 5692 Polymer.dom(fromElement).classList.remove(name); |
| 5693 } |
| 5694 if (toElement) { |
| 5695 Polymer.dom(toElement).classList.add(name); |
| 5696 } |
| 5697 }, |
| 5698 attributeFollows: function (name, toElement, fromElement) { |
| 5699 if (fromElement) { |
| 5700 Polymer.dom(fromElement).removeAttribute(name); |
| 5701 } |
| 5702 if (toElement) { |
| 5703 Polymer.dom(toElement).setAttribute(name, ''); |
| 5704 } |
| 5705 }, |
| 5706 getContentChildNodes: function (slctr) { |
| 5707 var content = Polymer.dom(this.root).querySelector(slctr || 'content'); |
| 5708 return content ? Polymer.dom(content).getDistributedNodes() : []; |
| 5709 }, |
| 5710 getContentChildren: function (slctr) { |
| 5711 return this.getContentChildNodes(slctr).filter(function (n) { |
| 5712 return n.nodeType === Node.ELEMENT_NODE; |
| 5713 }); |
| 5714 }, |
| 5715 fire: function (type, detail, options) { |
| 5716 options = options || Polymer.nob; |
| 5717 var node = options.node || this; |
| 5718 var detail = detail === null || detail === undefined ? Polymer.nob : detail; |
| 5719 var bubbles = options.bubbles === undefined ? true : options.bubbles; |
| 5720 var cancelable = Boolean(options.cancelable); |
| 5721 var event = new CustomEvent(type, { |
| 5722 bubbles: Boolean(bubbles), |
| 5723 cancelable: cancelable, |
| 5724 detail: detail |
| 5725 }); |
| 5726 node.dispatchEvent(event); |
| 5727 return event; |
| 5728 }, |
| 5729 async: function (callback, waitTime) { |
| 5730 return Polymer.Async.run(callback.bind(this), waitTime); |
| 5731 }, |
| 5732 cancelAsync: function (handle) { |
| 5733 Polymer.Async.cancel(handle); |
| 5734 }, |
| 5735 arrayDelete: function (path, item) { |
| 5736 var index; |
| 5737 if (Array.isArray(path)) { |
| 5738 index = path.indexOf(item); |
| 5739 if (index >= 0) { |
| 5740 return path.splice(index, 1); |
| 5741 } |
| 5742 } else { |
| 5743 var arr = this.get(path); |
| 5744 index = arr.indexOf(item); |
| 5745 if (index >= 0) { |
| 5746 return this.splice(path, index, 1); |
| 5747 } |
| 5748 } |
| 5749 }, |
| 5750 transform: function (transform, node) { |
| 5751 node = node || this; |
| 5752 node.style.webkitTransform = transform; |
| 5753 node.style.transform = transform; |
| 5754 }, |
| 5755 translate3d: function (x, y, z, node) { |
| 5756 node = node || this; |
| 5757 this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node); |
| 5758 }, |
| 5759 importHref: function (href, onload, onerror) { |
| 5760 var l = document.createElement('link'); |
| 5761 l.rel = 'import'; |
| 5762 l.href = href; |
| 5763 if (onload) { |
| 5764 l.onload = onload.bind(this); |
| 5765 } |
| 5766 if (onerror) { |
| 5767 l.onerror = onerror.bind(this); |
| 5768 } |
| 5769 document.head.appendChild(l); |
| 5770 return l; |
| 5771 }, |
| 5772 create: function (tag, props) { |
| 5773 var elt = document.createElement(tag); |
| 5774 if (props) { |
| 5775 for (var n in props) { |
| 5776 elt[n] = props[n]; |
| 5777 } |
| 5778 } |
| 5779 return elt; |
| 5780 } |
| 5781 }); |
| 5782 Polymer.Bind = { |
| 5783 prepareModel: function (model) { |
| 5784 model._propertyEffects = {}; |
| 5785 model._bindListeners = []; |
| 5786 Polymer.Base.mixin(model, this._modelApi); |
| 5787 }, |
| 5788 _modelApi: { |
| 5789 _notifyChange: function (property) { |
| 5790 var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed'; |
| 5791 Polymer.Base.fire(eventName, { value: this[property] }, { |
| 5792 bubbles: false, |
| 5793 node: this |
| 5794 }); |
| 5795 }, |
| 5796 _propertySetter: function (property, value, effects, fromAbove) { |
| 5797 var old = this.__data__[property]; |
| 5798 if (old !== value && (old === old || value === value)) { |
| 5799 this.__data__[property] = value; |
| 5800 if (typeof value == 'object') { |
| 5801 this._clearPath(property); |
| 5802 } |
| 5803 if (this._propertyChanged) { |
| 5804 this._propertyChanged(property, value, old); |
| 5805 } |
| 5806 if (effects) { |
| 5807 this._effectEffects(property, value, effects, old, fromAbove); |
| 5808 } |
| 5809 } |
| 5810 return old; |
| 5811 }, |
| 5812 __setProperty: function (property, value, quiet, node) { |
| 5813 node = node || this; |
| 5814 var effects = node._propertyEffects && node._propertyEffects[property]; |
| 5815 if (effects) { |
| 5816 node._propertySetter(property, value, effects, quiet); |
| 5817 } else { |
| 5818 node[property] = value; |
| 5819 } |
| 5820 }, |
| 5821 _effectEffects: function (property, value, effects, old, fromAbove) { |
| 5822 effects.forEach(function (fx) { |
| 5823 var fn = Polymer.Bind['_' + fx.kind + 'Effect']; |
| 5824 if (fn) { |
| 5825 fn.call(this, property, value, fx.effect, old, fromAbove); |
| 5826 } |
| 5827 }, this); |
| 5828 }, |
| 5829 _clearPath: function (path) { |
| 5830 for (var prop in this.__data__) { |
| 5831 if (prop.indexOf(path + '.') === 0) { |
| 5832 this.__data__[prop] = undefined; |
| 5833 } |
| 5834 } |
| 5835 } |
| 5836 }, |
| 5837 ensurePropertyEffects: function (model, property) { |
| 5838 var fx = model._propertyEffects[property]; |
| 5839 if (!fx) { |
| 5840 fx = model._propertyEffects[property] = []; |
| 5841 } |
| 5842 return fx; |
| 5843 }, |
| 5844 addPropertyEffect: function (model, property, kind, effect) { |
| 5845 var fx = this.ensurePropertyEffects(model, property); |
| 5846 fx.push({ |
| 5847 kind: kind, |
| 5848 effect: effect |
| 5849 }); |
| 5850 }, |
| 5851 createBindings: function (model) { |
| 5852 var fx$ = model._propertyEffects; |
| 5853 if (fx$) { |
| 5854 for (var n in fx$) { |
| 5855 var fx = fx$[n]; |
| 5856 fx.sort(this._sortPropertyEffects); |
| 5857 this._createAccessors(model, n, fx); |
| 5858 } |
| 5859 } |
| 5860 }, |
| 5861 _sortPropertyEffects: function () { |
| 5862 var EFFECT_ORDER = { |
| 5863 'compute': 0, |
| 5864 'annotation': 1, |
| 5865 'computedAnnotation': 2, |
| 5866 'reflect': 3, |
| 5867 'notify': 4, |
| 5868 'observer': 5, |
| 5869 'complexObserver': 6, |
| 5870 'function': 7 |
| 5871 }; |
| 5872 return function (a, b) { |
| 5873 return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind]; |
| 5874 }; |
| 5875 }(), |
| 5876 _createAccessors: function (model, property, effects) { |
| 5877 var defun = { |
| 5878 get: function () { |
| 5879 return this.__data__[property]; |
| 5880 } |
| 5881 }; |
| 5882 var setter = function (value) { |
| 5883 this._propertySetter(property, value, effects); |
| 5884 }; |
| 5885 var info = model.getPropertyInfo && model.getPropertyInfo(property); |
| 5886 if (info && info.readOnly) { |
| 5887 if (!info.computed) { |
| 5888 model['_set' + this.upper(property)] = setter; |
| 5889 } |
| 5890 } else { |
| 5891 defun.set = setter; |
| 5892 } |
| 5893 Object.defineProperty(model, property, defun); |
| 5894 }, |
| 5895 upper: function (name) { |
| 5896 return name[0].toUpperCase() + name.substring(1); |
| 5897 }, |
| 5898 _addAnnotatedListener: function (model, index, property, path, event) { |
| 5899 var fn = this._notedListenerFactory(property, path, this._isStructured(path), th
is._isEventBogus); |
| 5900 var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed'; |
| 5901 model._bindListeners.push({ |
| 5902 index: index, |
| 5903 property: property, |
| 5904 path: path, |
| 5905 changedFn: fn, |
| 5906 event: eventName |
| 5907 }); |
| 5908 }, |
| 5909 _isStructured: function (path) { |
| 5910 return path.indexOf('.') > 0; |
| 5911 }, |
| 5912 _isEventBogus: function (e, target) { |
| 5913 return e.path && e.path[0] !== target; |
| 5914 }, |
| 5915 _notedListenerFactory: function (property, path, isStructured, bogusTest) { |
| 5916 return function (e, target) { |
| 5917 if (!bogusTest(e, target)) { |
| 5918 if (e.detail && e.detail.path) { |
| 5919 this.notifyPath(this._fixPath(path, property, e.detail.path), e.detail.value); |
| 5920 } else { |
| 5921 var value = target[property]; |
| 5922 if (!isStructured) { |
| 5923 this[path] = target[property]; |
| 5924 } else { |
| 5925 if (this.__data__[path] != value) { |
| 5926 this.set(path, value); |
| 5927 } |
| 5928 } |
| 5929 } |
| 5930 } |
| 5931 }; |
| 5932 }, |
| 5933 prepareInstance: function (inst) { |
| 5934 inst.__data__ = Object.create(null); |
| 5935 }, |
| 5936 setupBindListeners: function (inst) { |
| 5937 inst._bindListeners.forEach(function (info) { |
| 5938 var node = inst._nodes[info.index]; |
| 5939 node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn
)); |
| 5940 }); |
| 5941 } |
| 5942 }; |
| 5943 Polymer.Base.extend(Polymer.Bind, { |
| 5944 _shouldAddListener: function (effect) { |
| 5945 return effect.name && effect.mode === '{' && !effect.negate && effect.kind != 'a
ttribute'; |
| 5946 }, |
| 5947 _annotationEffect: function (source, value, effect) { |
| 5948 if (source != effect.value) { |
| 5949 value = this.get(effect.value); |
| 5950 this.__data__[effect.value] = value; |
| 5951 } |
| 5952 var calc = effect.negate ? !value : value; |
| 5953 if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) { |
| 5954 return this._applyEffectValue(calc, effect); |
| 5955 } |
| 5956 }, |
| 5957 _reflectEffect: function (source) { |
| 5958 this.reflectPropertyToAttribute(source); |
| 5959 }, |
| 5960 _notifyEffect: function (source, value, effect, old, fromAbove) { |
| 5961 if (!fromAbove) { |
| 5962 this._notifyChange(source); |
| 5963 } |
| 5964 }, |
| 5965 _functionEffect: function (source, value, fn, old, fromAbove) { |
| 5966 fn.call(this, source, value, old, fromAbove); |
| 5967 }, |
| 5968 _observerEffect: function (source, value, effect, old) { |
| 5969 var fn = this[effect.method]; |
| 5970 if (fn) { |
| 5971 fn.call(this, value, old); |
| 5972 } else { |
| 5973 this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + '
` not defined')); |
| 5974 } |
| 5975 }, |
| 5976 _complexObserverEffect: function (source, value, effect) { |
| 5977 var fn = this[effect.method]; |
| 5978 if (fn) { |
| 5979 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); |
| 5980 if (args) { |
| 5981 fn.apply(this, args); |
| 5982 } |
| 5983 } else { |
| 5984 this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.met
hod + '` not defined')); |
| 5985 } |
| 5986 }, |
| 5987 _computeEffect: function (source, value, effect) { |
| 5988 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); |
| 5989 if (args) { |
| 5990 var fn = this[effect.method]; |
| 5991 if (fn) { |
| 5992 this.__setProperty(effect.property, fn.apply(this, args)); |
| 5993 } else { |
| 5994 this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '`
not defined')); |
| 5995 } |
| 5996 } |
| 5997 }, |
| 5998 _annotatedComputationEffect: function (source, value, effect) { |
| 5999 var computedHost = this._rootDataHost || this; |
| 6000 var fn = computedHost[effect.method]; |
| 6001 if (fn) { |
| 6002 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); |
| 6003 if (args) { |
| 6004 var computedvalue = fn.apply(computedHost, args); |
| 6005 if (effect.negate) { |
| 6006 computedvalue = !computedvalue; |
| 6007 } |
| 6008 this._applyEffectValue(computedvalue, effect); |
| 6009 } |
| 6010 } else { |
| 6011 computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute me
thod `' + effect.method + '` not defined')); |
| 6012 } |
| 6013 }, |
| 6014 _marshalArgs: function (model, effect, path, value) { |
| 6015 var values = []; |
| 6016 var args = effect.args; |
| 6017 for (var i = 0, l = args.length; i < l; i++) { |
| 6018 var arg = args[i]; |
| 6019 var name = arg.name; |
| 6020 var v; |
| 6021 if (arg.literal) { |
| 6022 v = arg.value; |
| 6023 } else if (arg.structured) { |
| 6024 v = Polymer.Base.get(name, model); |
| 6025 } else { |
| 6026 v = model[name]; |
| 6027 } |
| 6028 if (args.length > 1 && v === undefined) { |
| 6029 return; |
| 6030 } |
| 6031 if (arg.wildcard) { |
| 6032 var baseChanged = name.indexOf(path + '.') === 0; |
| 6033 var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged; |
| 6034 values[i] = { |
| 6035 path: matches ? path : name, |
| 6036 value: matches ? value : v, |
| 6037 base: v |
| 6038 }; |
| 6039 } else { |
| 6040 values[i] = v; |
| 6041 } |
| 6042 } |
| 6043 return values; |
| 6044 } |
| 6045 }); |
| 6046 Polymer.Base._addFeature({ |
| 6047 _addPropertyEffect: function (property, kind, effect) { |
| 6048 Polymer.Bind.addPropertyEffect(this, property, kind, effect); |
| 6049 }, |
| 6050 _prepEffects: function () { |
| 6051 Polymer.Bind.prepareModel(this); |
| 6052 this._addAnnotationEffects(this._notes); |
| 6053 }, |
| 6054 _prepBindings: function () { |
| 6055 Polymer.Bind.createBindings(this); |
| 6056 }, |
| 6057 _addPropertyEffects: function (properties) { |
| 6058 if (properties) { |
| 6059 for (var p in properties) { |
| 6060 var prop = properties[p]; |
| 6061 if (prop.observer) { |
| 6062 this._addObserverEffect(p, prop.observer); |
| 6063 } |
| 6064 if (prop.computed) { |
| 6065 prop.readOnly = true; |
| 6066 this._addComputedEffect(p, prop.computed); |
| 6067 } |
| 6068 if (prop.notify) { |
| 6069 this._addPropertyEffect(p, 'notify'); |
| 6070 } |
| 6071 if (prop.reflectToAttribute) { |
| 6072 this._addPropertyEffect(p, 'reflect'); |
| 6073 } |
| 6074 if (prop.readOnly) { |
| 6075 Polymer.Bind.ensurePropertyEffects(this, p); |
| 6076 } |
| 6077 } |
| 6078 } |
| 6079 }, |
| 6080 _addComputedEffect: function (name, expression) { |
| 6081 var sig = this._parseMethod(expression); |
| 6082 sig.args.forEach(function (arg) { |
| 6083 this._addPropertyEffect(arg.model, 'compute', { |
| 6084 method: sig.method, |
| 6085 args: sig.args, |
| 6086 trigger: arg, |
| 6087 property: name |
| 6088 }); |
| 6089 }, this); |
| 6090 }, |
| 6091 _addObserverEffect: function (property, observer) { |
| 6092 this._addPropertyEffect(property, 'observer', { |
| 6093 method: observer, |
| 6094 property: property |
| 6095 }); |
| 6096 }, |
| 6097 _addComplexObserverEffects: function (observers) { |
| 6098 if (observers) { |
| 6099 observers.forEach(function (observer) { |
| 6100 this._addComplexObserverEffect(observer); |
| 6101 }, this); |
| 6102 } |
| 6103 }, |
| 6104 _addComplexObserverEffect: function (observer) { |
| 6105 var sig = this._parseMethod(observer); |
| 6106 sig.args.forEach(function (arg) { |
| 6107 this._addPropertyEffect(arg.model, 'complexObserver', { |
| 6108 method: sig.method, |
| 6109 args: sig.args, |
| 6110 trigger: arg |
| 6111 }); |
| 6112 }, this); |
| 6113 }, |
| 6114 _addAnnotationEffects: function (notes) { |
| 6115 this._nodes = []; |
| 6116 notes.forEach(function (note) { |
| 6117 var index = this._nodes.push(note) - 1; |
| 6118 note.bindings.forEach(function (binding) { |
| 6119 this._addAnnotationEffect(binding, index); |
| 6120 }, this); |
| 6121 }, this); |
| 6122 }, |
| 6123 _addAnnotationEffect: function (note, index) { |
| 6124 if (Polymer.Bind._shouldAddListener(note)) { |
| 6125 Polymer.Bind._addAnnotatedListener(this, index, note.name, note.value, note.even
t); |
| 6126 } |
| 6127 if (note.signature) { |
| 6128 this._addAnnotatedComputationEffect(note, index); |
| 6129 } else { |
| 6130 note.index = index; |
| 6131 this._addPropertyEffect(note.model, 'annotation', note); |
| 6132 } |
| 6133 }, |
| 6134 _addAnnotatedComputationEffect: function (note, index) { |
| 6135 var sig = note.signature; |
| 6136 if (sig.static) { |
| 6137 this.__addAnnotatedComputationEffect('__static__', index, note, sig, null); |
| 6138 } else { |
| 6139 sig.args.forEach(function (arg) { |
| 6140 if (!arg.literal) { |
| 6141 this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg); |
| 6142 } |
| 6143 }, this); |
| 6144 } |
| 6145 }, |
| 6146 __addAnnotatedComputationEffect: function (property, index, note, sig, trigger)
{ |
| 6147 this._addPropertyEffect(property, 'annotatedComputation', { |
| 6148 index: index, |
| 6149 kind: note.kind, |
| 6150 property: note.name, |
| 6151 negate: note.negate, |
| 6152 method: sig.method, |
| 6153 args: sig.args, |
| 6154 trigger: trigger |
| 6155 }); |
| 6156 }, |
| 6157 _parseMethod: function (expression) { |
| 6158 var m = expression.match(/([^\s]+)\((.*)\)/); |
| 6159 if (m) { |
| 6160 var sig = { |
| 6161 method: m[1], |
| 6162 static: true |
| 6163 }; |
| 6164 if (m[2].trim()) { |
| 6165 var args = m[2].replace(/\\,/g, ',').split(','); |
| 6166 return this._parseArgs(args, sig); |
| 6167 } else { |
| 6168 sig.args = Polymer.nar; |
| 6169 return sig; |
| 6170 } |
| 6171 } |
| 6172 }, |
| 6173 _parseArgs: function (argList, sig) { |
| 6174 sig.args = argList.map(function (rawArg) { |
| 6175 var arg = this._parseArg(rawArg); |
| 6176 if (!arg.literal) { |
| 6177 sig.static = false; |
| 6178 } |
| 6179 return arg; |
| 6180 }, this); |
| 6181 return sig; |
| 6182 }, |
| 6183 _parseArg: function (rawArg) { |
| 6184 var arg = rawArg.trim().replace(/,/g, ',').replace(/\\(.)/g, '$1'); |
| 6185 var a = { |
| 6186 name: arg, |
| 6187 model: this._modelForPath(arg) |
| 6188 }; |
| 6189 var fc = arg[0]; |
| 6190 if (fc === '-') { |
| 6191 fc = arg[1]; |
| 6192 } |
| 6193 if (fc >= '0' && fc <= '9') { |
| 6194 fc = '#'; |
| 6195 } |
| 6196 switch (fc) { |
| 6197 case '\'': |
| 6198 case '"': |
| 6199 a.value = arg.slice(1, -1); |
| 6200 a.literal = true; |
| 6201 break; |
| 6202 case '#': |
| 6203 a.value = Number(arg); |
| 6204 a.literal = true; |
| 6205 break; |
| 6206 } |
| 6207 if (!a.literal) { |
| 6208 a.structured = arg.indexOf('.') > 0; |
| 6209 if (a.structured) { |
| 6210 a.wildcard = arg.slice(-2) == '.*'; |
| 6211 if (a.wildcard) { |
| 6212 a.name = arg.slice(0, -2); |
| 6213 } |
| 6214 } |
| 6215 } |
| 6216 return a; |
| 6217 }, |
| 6218 _marshalInstanceEffects: function () { |
| 6219 Polymer.Bind.prepareInstance(this); |
| 6220 Polymer.Bind.setupBindListeners(this); |
| 6221 }, |
| 6222 _applyEffectValue: function (value, info) { |
| 6223 var node = this._nodes[info.index]; |
| 6224 var property = info.property || info.name || 'textContent'; |
| 6225 if (info.kind == 'attribute') { |
| 6226 this.serializeValueToAttribute(value, property, node); |
| 6227 } else { |
| 6228 if (property === 'className') { |
| 6229 value = this._scopeElementClass(node, value); |
| 6230 } |
| 6231 if (property === 'textContent' || node.localName == 'input' && property == 'valu
e') { |
| 6232 value = value == undefined ? '' : value; |
| 6233 } |
| 6234 return node[property] = value; |
| 6235 } |
| 6236 }, |
| 6237 _executeStaticEffects: function () { |
| 6238 if (this._propertyEffects.__static__) { |
| 6239 this._effectEffects('__static__', null, this._propertyEffects.__static__); |
| 6240 } |
| 6241 } |
| 6242 }); |
| 6243 Polymer.Base._addFeature({ |
| 6244 _setupConfigure: function (initialConfig) { |
| 6245 this._config = {}; |
| 6246 for (var i in initialConfig) { |
| 6247 if (initialConfig[i] !== undefined) { |
| 6248 this._config[i] = initialConfig[i]; |
| 6249 } |
| 6250 } |
| 6251 this._handlers = []; |
| 6252 }, |
| 6253 _marshalAttributes: function () { |
| 6254 this._takeAttributesToModel(this._config); |
| 6255 }, |
| 6256 _attributeChangedImpl: function (name) { |
| 6257 var model = this._clientsReadied ? this : this._config; |
| 6258 this._setAttributeToProperty(model, name); |
| 6259 }, |
| 6260 _configValue: function (name, value) { |
| 6261 this._config[name] = value; |
| 6262 }, |
| 6263 _beforeClientsReady: function () { |
| 6264 this._configure(); |
| 6265 }, |
| 6266 _configure: function () { |
| 6267 this._configureAnnotationReferences(); |
| 6268 this._aboveConfig = this.mixin({}, this._config); |
| 6269 var config = {}; |
| 6270 this.behaviors.forEach(function (b) { |
| 6271 this._configureProperties(b.properties, config); |
| 6272 }, this); |
| 6273 this._configureProperties(this.properties, config); |
| 6274 this._mixinConfigure(config, this._aboveConfig); |
| 6275 this._config = config; |
| 6276 this._distributeConfig(this._config); |
| 6277 }, |
| 6278 _configureProperties: function (properties, config) { |
| 6279 for (var i in properties) { |
| 6280 var c = properties[i]; |
| 6281 if (c.value !== undefined) { |
| 6282 var value = c.value; |
| 6283 if (typeof value == 'function') { |
| 6284 value = value.call(this, this._config); |
| 6285 } |
| 6286 config[i] = value; |
| 6287 } |
| 6288 } |
| 6289 }, |
| 6290 _mixinConfigure: function (a, b) { |
| 6291 for (var prop in b) { |
| 6292 if (!this.getPropertyInfo(prop).readOnly) { |
| 6293 a[prop] = b[prop]; |
| 6294 } |
| 6295 } |
| 6296 }, |
| 6297 _distributeConfig: function (config) { |
| 6298 var fx$ = this._propertyEffects; |
| 6299 if (fx$) { |
| 6300 for (var p in config) { |
| 6301 var fx = fx$[p]; |
| 6302 if (fx) { |
| 6303 for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) { |
| 6304 if (x.kind === 'annotation') { |
| 6305 var node = this._nodes[x.effect.index]; |
| 6306 if (node._configValue) { |
| 6307 var value = p === x.effect.value ? config[p] : this.get(x.effect.value, config); |
| 6308 node._configValue(x.effect.name, value); |
| 6309 } |
| 6310 } |
| 6311 } |
| 6312 } |
| 6313 } |
| 6314 } |
| 6315 }, |
| 6316 _afterClientsReady: function () { |
| 6317 this._executeStaticEffects(); |
| 6318 this._applyConfig(this._config, this._aboveConfig); |
| 6319 this._flushHandlers(); |
| 6320 }, |
| 6321 _applyConfig: function (config, aboveConfig) { |
| 6322 for (var n in config) { |
| 6323 if (this[n] === undefined) { |
| 6324 this.__setProperty(n, config[n], n in aboveConfig); |
| 6325 } |
| 6326 } |
| 6327 }, |
| 6328 _notifyListener: function (fn, e) { |
| 6329 if (!this._clientsReadied) { |
| 6330 this._queueHandler([ |
| 6331 fn, |
| 6332 e, |
| 6333 e.target |
| 6334 ]); |
| 6335 } else { |
| 6336 return fn.call(this, e, e.target); |
| 6337 } |
| 6338 }, |
| 6339 _queueHandler: function (args) { |
| 6340 this._handlers.push(args); |
| 6341 }, |
| 6342 _flushHandlers: function () { |
| 6343 var h$ = this._handlers; |
| 6344 for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) { |
| 6345 h[0].call(this, h[1], h[2]); |
| 6346 } |
| 6347 this._handlers = []; |
| 6348 } |
| 6349 }); |
| 6350 (function () { |
| 6351 'use strict'; |
| 6352 Polymer.Base._addFeature({ |
| 6353 notifyPath: function (path, value, fromAbove) { |
| 6354 var old = this._propertySetter(path, value); |
| 6355 if (old !== value && (old === old || value === value)) { |
| 6356 this._pathEffector(path, value); |
| 6357 if (!fromAbove) { |
| 6358 this._notifyPath(path, value); |
| 6359 } |
| 6360 return true; |
| 6361 } |
| 6362 }, |
| 6363 _getPathParts: function (path) { |
| 6364 if (Array.isArray(path)) { |
| 6365 var parts = []; |
| 6366 for (var i = 0; i < path.length; i++) { |
| 6367 var args = path[i].toString().split('.'); |
| 6368 for (var j = 0; j < args.length; j++) { |
| 6369 parts.push(args[j]); |
| 6370 } |
| 6371 } |
| 6372 return parts; |
| 6373 } else { |
| 6374 return path.toString().split('.'); |
| 6375 } |
| 6376 }, |
| 6377 set: function (path, value, root) { |
| 6378 var prop = root || this; |
| 6379 var parts = this._getPathParts(path); |
| 6380 var array; |
| 6381 var last = parts[parts.length - 1]; |
| 6382 if (parts.length > 1) { |
| 6383 for (var i = 0; i < parts.length - 1; i++) { |
| 6384 var part = parts[i]; |
| 6385 prop = prop[part]; |
| 6386 if (array && parseInt(part) == part) { |
| 6387 parts[i] = Polymer.Collection.get(array).getKey(prop); |
| 6388 } |
| 6389 if (!prop) { |
| 6390 return; |
| 6391 } |
| 6392 array = Array.isArray(prop) ? prop : null; |
| 6393 } |
| 6394 if (array && parseInt(last) == last) { |
| 6395 var coll = Polymer.Collection.get(array); |
| 6396 var old = prop[last]; |
| 6397 var key = coll.getKey(old); |
| 6398 parts[i] = key; |
| 6399 coll.setItem(key, value); |
| 6400 } |
| 6401 prop[last] = value; |
| 6402 if (!root) { |
| 6403 this.notifyPath(parts.join('.'), value); |
| 6404 } |
| 6405 } else { |
| 6406 prop[path] = value; |
| 6407 } |
| 6408 }, |
| 6409 get: function (path, root) { |
| 6410 var prop = root || this; |
| 6411 var parts = this._getPathParts(path); |
| 6412 var last = parts.pop(); |
| 6413 while (parts.length) { |
| 6414 prop = prop[parts.shift()]; |
| 6415 if (!prop) { |
| 6416 return; |
| 6417 } |
| 6418 } |
| 6419 return prop[last]; |
| 6420 }, |
| 6421 _pathEffector: function (path, value) { |
| 6422 var model = this._modelForPath(path); |
| 6423 var fx$ = this._propertyEffects[model]; |
| 6424 if (fx$) { |
| 6425 fx$.forEach(function (fx) { |
| 6426 var fxFn = this['_' + fx.kind + 'PathEffect']; |
| 6427 if (fxFn) { |
| 6428 fxFn.call(this, path, value, fx.effect); |
| 6429 } |
| 6430 }, this); |
| 6431 } |
| 6432 if (this._boundPaths) { |
| 6433 this._notifyBoundPaths(path, value); |
| 6434 } |
| 6435 }, |
| 6436 _annotationPathEffect: function (path, value, effect) { |
| 6437 if (effect.value === path || effect.value.indexOf(path + '.') === 0) { |
| 6438 Polymer.Bind._annotationEffect.call(this, path, value, effect); |
| 6439 } else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) { |
| 6440 var node = this._nodes[effect.index]; |
| 6441 if (node && node.notifyPath) { |
| 6442 var p = this._fixPath(effect.name, effect.value, path); |
| 6443 node.notifyPath(p, value, true); |
| 6444 } |
| 6445 } |
| 6446 }, |
| 6447 _complexObserverPathEffect: function (path, value, effect) { |
| 6448 if (this._pathMatchesEffect(path, effect)) { |
| 6449 Polymer.Bind._complexObserverEffect.call(this, path, value, effect); |
| 6450 } |
| 6451 }, |
| 6452 _computePathEffect: function (path, value, effect) { |
| 6453 if (this._pathMatchesEffect(path, effect)) { |
| 6454 Polymer.Bind._computeEffect.call(this, path, value, effect); |
| 6455 } |
| 6456 }, |
| 6457 _annotatedComputationPathEffect: function (path, value, effect) { |
| 6458 if (this._pathMatchesEffect(path, effect)) { |
| 6459 Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect); |
| 6460 } |
| 6461 }, |
| 6462 _pathMatchesEffect: function (path, effect) { |
| 6463 var effectArg = effect.trigger.name; |
| 6464 return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigge
r.wildcard && path.indexOf(effectArg) === 0; |
| 6465 }, |
| 6466 linkPaths: function (to, from) { |
| 6467 this._boundPaths = this._boundPaths || {}; |
| 6468 if (from) { |
| 6469 this._boundPaths[to] = from; |
| 6470 } else { |
| 6471 this.unlinkPaths(to); |
| 6472 } |
| 6473 }, |
| 6474 unlinkPaths: function (path) { |
| 6475 if (this._boundPaths) { |
| 6476 delete this._boundPaths[path]; |
| 6477 } |
| 6478 }, |
| 6479 _notifyBoundPaths: function (path, value) { |
| 6480 for (var a in this._boundPaths) { |
| 6481 var b = this._boundPaths[a]; |
| 6482 if (path.indexOf(a + '.') == 0) { |
| 6483 this.notifyPath(this._fixPath(b, a, path), value); |
| 6484 } else if (path.indexOf(b + '.') == 0) { |
| 6485 this.notifyPath(this._fixPath(a, b, path), value); |
| 6486 } |
| 6487 } |
| 6488 }, |
| 6489 _fixPath: function (property, root, path) { |
| 6490 return property + path.slice(root.length); |
| 6491 }, |
| 6492 _notifyPath: function (path, value) { |
| 6493 var rootName = this._modelForPath(path); |
| 6494 var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName); |
| 6495 var eventName = dashCaseName + this._EVENT_CHANGED; |
| 6496 this.fire(eventName, { |
| 6497 path: path, |
| 6498 value: value |
| 6499 }, { bubbles: false }); |
| 6500 }, |
| 6501 _modelForPath: function (path) { |
| 6502 var dot = path.indexOf('.'); |
| 6503 return dot < 0 ? path : path.slice(0, dot); |
| 6504 }, |
| 6505 _EVENT_CHANGED: '-changed', |
| 6506 _notifySplice: function (array, path, index, added, removed) { |
| 6507 var splices = [{ |
| 6508 index: index, |
| 6509 addedCount: added, |
| 6510 removed: removed, |
| 6511 object: array, |
| 6512 type: 'splice' |
| 6513 }]; |
| 6514 var change = { |
| 6515 keySplices: Polymer.Collection.applySplices(array, splices), |
| 6516 indexSplices: splices |
| 6517 }; |
| 6518 this.set(path + '.splices', change); |
| 6519 if (added != removed.length) { |
| 6520 this.notifyPath(path + '.length', array.length); |
| 6521 } |
| 6522 change.keySplices = null; |
| 6523 change.indexSplices = null; |
| 6524 }, |
| 6525 push: function (path) { |
| 6526 var array = this.get(path); |
| 6527 var args = Array.prototype.slice.call(arguments, 1); |
| 6528 var len = array.length; |
| 6529 var ret = array.push.apply(array, args); |
| 6530 if (args.length) { |
| 6531 this._notifySplice(array, path, len, args.length, []); |
| 6532 } |
| 6533 return ret; |
| 6534 }, |
| 6535 pop: function (path) { |
| 6536 var array = this.get(path); |
| 6537 var hadLength = Boolean(array.length); |
| 6538 var args = Array.prototype.slice.call(arguments, 1); |
| 6539 var ret = array.pop.apply(array, args); |
| 6540 if (hadLength) { |
| 6541 this._notifySplice(array, path, array.length, 0, [ret]); |
| 6542 } |
| 6543 return ret; |
| 6544 }, |
| 6545 splice: function (path, start, deleteCount) { |
| 6546 var array = this.get(path); |
| 6547 if (start < 0) { |
| 6548 start = array.length - Math.floor(-start); |
| 6549 } else { |
| 6550 start = Math.floor(start); |
| 6551 } |
| 6552 if (!start) { |
| 6553 start = 0; |
| 6554 } |
| 6555 var args = Array.prototype.slice.call(arguments, 1); |
| 6556 var ret = array.splice.apply(array, args); |
| 6557 var addedCount = Math.max(args.length - 2, 0); |
| 6558 if (addedCount || ret.length) { |
| 6559 this._notifySplice(array, path, start, addedCount, ret); |
| 6560 } |
| 6561 return ret; |
| 6562 }, |
| 6563 shift: function (path) { |
| 6564 var array = this.get(path); |
| 6565 var hadLength = Boolean(array.length); |
| 6566 var args = Array.prototype.slice.call(arguments, 1); |
| 6567 var ret = array.shift.apply(array, args); |
| 6568 if (hadLength) { |
| 6569 this._notifySplice(array, path, 0, 0, [ret]); |
| 6570 } |
| 6571 return ret; |
| 6572 }, |
| 6573 unshift: function (path) { |
| 6574 var array = this.get(path); |
| 6575 var args = Array.prototype.slice.call(arguments, 1); |
| 6576 var ret = array.unshift.apply(array, args); |
| 6577 if (args.length) { |
| 6578 this._notifySplice(array, path, 0, args.length, []); |
| 6579 } |
| 6580 return ret; |
| 6581 } |
| 6582 }); |
| 6583 }()); |
| 6584 Polymer.Base._addFeature({ |
| 6585 resolveUrl: function (url) { |
| 6586 var module = Polymer.DomModule.import(this.is); |
| 6587 var root = ''; |
| 6588 if (module) { |
| 6589 var assetPath = module.getAttribute('assetpath') || ''; |
| 6590 root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI); |
| 6591 } |
| 6592 return Polymer.ResolveUrl.resolveUrl(url, root); |
| 6593 } |
| 6594 }); |
| 6595 Polymer.CssParse = function () { |
| 6596 var api = { |
| 6597 parse: function (text) { |
| 6598 text = this._clean(text); |
| 6599 return this._parseCss(this._lex(text), text); |
| 6600 }, |
| 6601 _clean: function (cssText) { |
| 6602 return cssText.replace(this._rx.comments, '').replace(this._rx.port, ''); |
| 6603 }, |
| 6604 _lex: function (text) { |
| 6605 var root = { |
| 6606 start: 0, |
| 6607 end: text.length |
| 6608 }; |
| 6609 var n = root; |
| 6610 for (var i = 0, s = 0, l = text.length; i < l; i++) { |
| 6611 switch (text[i]) { |
| 6612 case this.OPEN_BRACE: |
| 6613 if (!n.rules) { |
| 6614 n.rules = []; |
| 6615 } |
| 6616 var p = n; |
| 6617 var previous = p.rules[p.rules.length - 1]; |
| 6618 n = { |
| 6619 start: i + 1, |
| 6620 parent: p, |
| 6621 previous: previous |
| 6622 }; |
| 6623 p.rules.push(n); |
| 6624 break; |
| 6625 case this.CLOSE_BRACE: |
| 6626 n.end = i + 1; |
| 6627 n = n.parent || root; |
| 6628 break; |
| 6629 } |
| 6630 } |
| 6631 return root; |
| 6632 }, |
| 6633 _parseCss: function (node, text) { |
| 6634 var t = text.substring(node.start, node.end - 1); |
| 6635 node.parsedCssText = node.cssText = t.trim(); |
| 6636 if (node.parent) { |
| 6637 var ss = node.previous ? node.previous.end : node.parent.start; |
| 6638 t = text.substring(ss, node.start - 1); |
| 6639 t = t.substring(t.lastIndexOf(';') + 1); |
| 6640 var s = node.parsedSelector = node.selector = t.trim(); |
| 6641 node.atRule = s.indexOf(this.AT_START) === 0; |
| 6642 if (node.atRule) { |
| 6643 if (s.indexOf(this.MEDIA_START) === 0) { |
| 6644 node.type = this.types.MEDIA_RULE; |
| 6645 } else if (s.match(this._rx.keyframesRule)) { |
| 6646 node.type = this.types.KEYFRAMES_RULE; |
| 6647 } |
| 6648 } else { |
| 6649 if (s.indexOf(this.VAR_START) === 0) { |
| 6650 node.type = this.types.MIXIN_RULE; |
| 6651 } else { |
| 6652 node.type = this.types.STYLE_RULE; |
| 6653 } |
| 6654 } |
| 6655 } |
| 6656 var r$ = node.rules; |
| 6657 if (r$) { |
| 6658 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { |
| 6659 this._parseCss(r, text); |
| 6660 } |
| 6661 } |
| 6662 return node; |
| 6663 }, |
| 6664 stringify: function (node, preserveProperties, text) { |
| 6665 text = text || ''; |
| 6666 var cssText = ''; |
| 6667 if (node.cssText || node.rules) { |
| 6668 var r$ = node.rules; |
| 6669 if (r$ && (preserveProperties || !this._hasMixinRules(r$))) { |
| 6670 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { |
| 6671 cssText = this.stringify(r, preserveProperties, cssText); |
| 6672 } |
| 6673 } else { |
| 6674 cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssTex
t); |
| 6675 cssText = cssText.trim(); |
| 6676 if (cssText) { |
| 6677 cssText = ' ' + cssText + '\n'; |
| 6678 } |
| 6679 } |
| 6680 } |
| 6681 if (cssText) { |
| 6682 if (node.selector) { |
| 6683 text += node.selector + ' ' + this.OPEN_BRACE + '\n'; |
| 6684 } |
| 6685 text += cssText; |
| 6686 if (node.selector) { |
| 6687 text += this.CLOSE_BRACE + '\n\n'; |
| 6688 } |
| 6689 } |
| 6690 return text; |
| 6691 }, |
| 6692 _hasMixinRules: function (rules) { |
| 6693 return rules[0].selector.indexOf(this.VAR_START) >= 0; |
| 6694 }, |
| 6695 removeCustomProps: function (cssText) { |
| 6696 return cssText; |
| 6697 }, |
| 6698 removeCustomPropAssignment: function (cssText) { |
| 6699 return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, ''); |
| 6700 }, |
| 6701 removeCustomPropApply: function (cssText) { |
| 6702 return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, ''); |
| 6703 }, |
| 6704 types: { |
| 6705 STYLE_RULE: 1, |
| 6706 KEYFRAMES_RULE: 7, |
| 6707 MEDIA_RULE: 4, |
| 6708 MIXIN_RULE: 1000 |
| 6709 }, |
| 6710 OPEN_BRACE: '{', |
| 6711 CLOSE_BRACE: '}', |
| 6712 _rx: { |
| 6713 comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim, |
| 6714 port: /@import[^;]*;/gim, |
| 6715 customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim, |
| 6716 mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim, |
| 6717 mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim, |
| 6718 varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim, |
| 6719 keyframesRule: /^@[^\s]*keyframes/ |
| 6720 }, |
| 6721 VAR_START: '--', |
| 6722 MEDIA_START: '@media', |
| 6723 AT_START: '@' |
| 6724 }; |
| 6725 return api; |
| 6726 }(); |
| 6727 Polymer.StyleUtil = function () { |
| 6728 return { |
| 6729 MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template', |
| 6730 INCLUDE_ATTR: 'include', |
| 6731 toCssText: function (rules, callback, preserveProperties) { |
| 6732 if (typeof rules === 'string') { |
| 6733 rules = this.parser.parse(rules); |
| 6734 } |
| 6735 if (callback) { |
| 6736 this.forEachStyleRule(rules, callback); |
| 6737 } |
| 6738 return this.parser.stringify(rules, preserveProperties); |
| 6739 }, |
| 6740 forRulesInStyles: function (styles, callback) { |
| 6741 if (styles) { |
| 6742 for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) { |
| 6743 this.forEachStyleRule(this.rulesForStyle(s), callback); |
| 6744 } |
| 6745 } |
| 6746 }, |
| 6747 rulesForStyle: function (style) { |
| 6748 if (!style.__cssRules && style.textContent) { |
| 6749 style.__cssRules = this.parser.parse(style.textContent); |
| 6750 } |
| 6751 return style.__cssRules; |
| 6752 }, |
| 6753 clearStyleRules: function (style) { |
| 6754 style.__cssRules = null; |
| 6755 }, |
| 6756 forEachStyleRule: function (node, callback) { |
| 6757 if (!node) { |
| 6758 return; |
| 6759 } |
| 6760 var s = node.parsedSelector; |
| 6761 var skipRules = false; |
| 6762 if (node.type === this.ruleTypes.STYLE_RULE) { |
| 6763 callback(node); |
| 6764 } else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.rul
eTypes.MIXIN_RULE) { |
| 6765 skipRules = true; |
| 6766 } |
| 6767 var r$ = node.rules; |
| 6768 if (r$ && !skipRules) { |
| 6769 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { |
| 6770 this.forEachStyleRule(r, callback); |
| 6771 } |
| 6772 } |
| 6773 }, |
| 6774 applyCss: function (cssText, moniker, target, afterNode) { |
| 6775 var style = document.createElement('style'); |
| 6776 if (moniker) { |
| 6777 style.setAttribute('scope', moniker); |
| 6778 } |
| 6779 style.textContent = cssText; |
| 6780 target = target || document.head; |
| 6781 if (!afterNode) { |
| 6782 var n$ = target.querySelectorAll('style[scope]'); |
| 6783 afterNode = n$[n$.length - 1]; |
| 6784 } |
| 6785 target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChi
ld); |
| 6786 return style; |
| 6787 }, |
| 6788 cssFromModules: function (moduleIds, warnIfNotFound) { |
| 6789 var modules = moduleIds.trim().split(' '); |
| 6790 var cssText = ''; |
| 6791 for (var i = 0; i < modules.length; i++) { |
| 6792 cssText += this.cssFromModule(modules[i], warnIfNotFound); |
| 6793 } |
| 6794 return cssText; |
| 6795 }, |
| 6796 cssFromModule: function (moduleId, warnIfNotFound) { |
| 6797 var m = Polymer.DomModule.import(moduleId); |
| 6798 if (m && !m._cssText) { |
| 6799 m._cssText = this._cssFromElement(m); |
| 6800 } |
| 6801 if (!m && warnIfNotFound) { |
| 6802 console.warn('Could not find style data in module named', moduleId); |
| 6803 } |
| 6804 return m && m._cssText || ''; |
| 6805 }, |
| 6806 _cssFromElement: function (element) { |
| 6807 var cssText = ''; |
| 6808 var content = element.content || element; |
| 6809 var e$ = Array.prototype.slice.call(content.querySelectorAll(this.MODULE_STYLES_
SELECTOR)); |
| 6810 for (var i = 0, e; i < e$.length; i++) { |
| 6811 e = e$[i]; |
| 6812 if (e.localName === 'template') { |
| 6813 cssText += this._cssFromElement(e); |
| 6814 } else { |
| 6815 if (e.localName === 'style') { |
| 6816 var include = e.getAttribute(this.INCLUDE_ATTR); |
| 6817 if (include) { |
| 6818 cssText += this.cssFromModules(include, true); |
| 6819 } |
| 6820 e = e.__appliedElement || e; |
| 6821 e.parentNode.removeChild(e); |
| 6822 cssText += this.resolveCss(e.textContent, element.ownerDocument); |
| 6823 } else if (e.import && e.import.body) { |
| 6824 cssText += this.resolveCss(e.import.body.textContent, e.import); |
| 6825 } |
| 6826 } |
| 6827 } |
| 6828 return cssText; |
| 6829 }, |
| 6830 resolveCss: Polymer.ResolveUrl.resolveCss, |
| 6831 parser: Polymer.CssParse, |
| 6832 ruleTypes: Polymer.CssParse.types |
| 6833 }; |
| 6834 }(); |
| 6835 Polymer.StyleTransformer = function () { |
| 6836 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 6837 var styleUtil = Polymer.StyleUtil; |
| 6838 var api = { |
| 6839 dom: function (node, scope, useAttr, shouldRemoveScope) { |
| 6840 this._transformDom(node, scope || '', useAttr, shouldRemoveScope); |
| 6841 }, |
| 6842 _transformDom: function (node, selector, useAttr, shouldRemoveScope) { |
| 6843 if (node.setAttribute) { |
| 6844 this.element(node, selector, useAttr, shouldRemoveScope); |
| 6845 } |
| 6846 var c$ = Polymer.dom(node).childNodes; |
| 6847 for (var i = 0; i < c$.length; i++) { |
| 6848 this._transformDom(c$[i], selector, useAttr, shouldRemoveScope); |
| 6849 } |
| 6850 }, |
| 6851 element: function (element, scope, useAttr, shouldRemoveScope) { |
| 6852 if (useAttr) { |
| 6853 if (shouldRemoveScope) { |
| 6854 element.removeAttribute(SCOPE_NAME); |
| 6855 } else { |
| 6856 element.setAttribute(SCOPE_NAME, scope); |
| 6857 } |
| 6858 } else { |
| 6859 if (scope) { |
| 6860 if (element.classList) { |
| 6861 if (shouldRemoveScope) { |
| 6862 element.classList.remove(SCOPE_NAME); |
| 6863 element.classList.remove(scope); |
| 6864 } else { |
| 6865 element.classList.add(SCOPE_NAME); |
| 6866 element.classList.add(scope); |
| 6867 } |
| 6868 } else if (element.getAttribute) { |
| 6869 var c = element.getAttribute(CLASS); |
| 6870 if (shouldRemoveScope) { |
| 6871 if (c) { |
| 6872 element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, '')); |
| 6873 } |
| 6874 } else { |
| 6875 element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope); |
| 6876 } |
| 6877 } |
| 6878 } |
| 6879 } |
| 6880 }, |
| 6881 elementStyles: function (element, callback) { |
| 6882 var styles = element._styles; |
| 6883 var cssText = ''; |
| 6884 for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) { |
| 6885 var rules = styleUtil.rulesForStyle(s); |
| 6886 cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules,
element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n'; |
| 6887 } |
| 6888 return cssText.trim(); |
| 6889 }, |
| 6890 css: function (rules, scope, ext, callback, useAttr) { |
| 6891 var hostScope = this._calcHostScope(scope, ext); |
| 6892 scope = this._calcElementScope(scope, useAttr); |
| 6893 var self = this; |
| 6894 return styleUtil.toCssText(rules, function (rule) { |
| 6895 if (!rule.isScoped) { |
| 6896 self.rule(rule, scope, hostScope); |
| 6897 rule.isScoped = true; |
| 6898 } |
| 6899 if (callback) { |
| 6900 callback(rule, scope, hostScope); |
| 6901 } |
| 6902 }); |
| 6903 }, |
| 6904 _calcElementScope: function (scope, useAttr) { |
| 6905 if (scope) { |
| 6906 return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX +
scope; |
| 6907 } else { |
| 6908 return ''; |
| 6909 } |
| 6910 }, |
| 6911 _calcHostScope: function (scope, ext) { |
| 6912 return ext ? '[is=' + scope + ']' : scope; |
| 6913 }, |
| 6914 rule: function (rule, scope, hostScope) { |
| 6915 this._transformRule(rule, this._transformComplexSelector, scope, hostScope); |
| 6916 }, |
| 6917 _transformRule: function (rule, transformer, scope, hostScope) { |
| 6918 var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP); |
| 6919 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) { |
| 6920 p$[i] = transformer.call(this, p, scope, hostScope); |
| 6921 } |
| 6922 rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP); |
| 6923 }, |
| 6924 _transformComplexSelector: function (selector, scope, hostScope) { |
| 6925 var stop = false; |
| 6926 var hostContext = false; |
| 6927 var self = this; |
| 6928 selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) { |
| 6929 if (!stop) { |
| 6930 var info = self._transformCompoundSelector(s, c, scope, hostScope); |
| 6931 stop = stop || info.stop; |
| 6932 hostContext = hostContext || info.hostContext; |
| 6933 c = info.combinator; |
| 6934 s = info.value; |
| 6935 } else { |
| 6936 s = s.replace(SCOPE_JUMP, ' '); |
| 6937 } |
| 6938 return c + s; |
| 6939 }); |
| 6940 if (hostContext) { |
| 6941 selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) { |
| 6942 return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre +
hostScope + paren + post; |
| 6943 }); |
| 6944 } |
| 6945 return selector; |
| 6946 }, |
| 6947 _transformCompoundSelector: function (selector, combinator, scope, hostScope) { |
| 6948 var jumpIndex = selector.search(SCOPE_JUMP); |
| 6949 var hostContext = false; |
| 6950 if (selector.indexOf(HOST_CONTEXT) >= 0) { |
| 6951 hostContext = true; |
| 6952 } else if (selector.indexOf(HOST) >= 0) { |
| 6953 selector = selector.replace(HOST_PAREN, function (m, host, paren) { |
| 6954 return hostScope + paren; |
| 6955 }); |
| 6956 selector = selector.replace(HOST, hostScope); |
| 6957 } else if (jumpIndex !== 0) { |
| 6958 selector = scope ? this._transformSimpleSelector(selector, scope) : selector; |
| 6959 } |
| 6960 if (selector.indexOf(CONTENT) >= 0) { |
| 6961 combinator = ''; |
| 6962 } |
| 6963 var stop; |
| 6964 if (jumpIndex >= 0) { |
| 6965 selector = selector.replace(SCOPE_JUMP, ' '); |
| 6966 stop = true; |
| 6967 } |
| 6968 return { |
| 6969 value: selector, |
| 6970 combinator: combinator, |
| 6971 stop: stop, |
| 6972 hostContext: hostContext |
| 6973 }; |
| 6974 }, |
| 6975 _transformSimpleSelector: function (selector, scope) { |
| 6976 var p$ = selector.split(PSEUDO_PREFIX); |
| 6977 p$[0] += scope; |
| 6978 return p$.join(PSEUDO_PREFIX); |
| 6979 }, |
| 6980 documentRule: function (rule) { |
| 6981 rule.selector = rule.parsedSelector; |
| 6982 this.normalizeRootSelector(rule); |
| 6983 if (!nativeShadow) { |
| 6984 this._transformRule(rule, this._transformDocumentSelector); |
| 6985 } |
| 6986 }, |
| 6987 normalizeRootSelector: function (rule) { |
| 6988 if (rule.selector === ROOT) { |
| 6989 rule.selector = 'body'; |
| 6990 } |
| 6991 }, |
| 6992 _transformDocumentSelector: function (selector) { |
| 6993 return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCO
PE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELE
CTOR); |
| 6994 }, |
| 6995 SCOPE_NAME: 'style-scope' |
| 6996 }; |
| 6997 var SCOPE_NAME = api.SCOPE_NAME; |
| 6998 var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME +
')'; |
| 6999 var COMPLEX_SELECTOR_SEP = ','; |
| 7000 var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g; |
| 7001 var HOST = ':host'; |
| 7002 var ROOT = ':root'; |
| 7003 var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g; |
| 7004 var HOST_CONTEXT = ':host-context'; |
| 7005 var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\)
)(.*)/; |
| 7006 var CONTENT = '::content'; |
| 7007 var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//; |
| 7008 var CSS_CLASS_PREFIX = '.'; |
| 7009 var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~='; |
| 7010 var CSS_ATTR_SUFFIX = ']'; |
| 7011 var PSEUDO_PREFIX = ':'; |
| 7012 var CLASS = 'class'; |
| 7013 return api; |
| 7014 }(); |
| 7015 Polymer.StyleExtends = function () { |
| 7016 var styleUtil = Polymer.StyleUtil; |
| 7017 return { |
| 7018 hasExtends: function (cssText) { |
| 7019 return Boolean(cssText.match(this.rx.EXTEND)); |
| 7020 }, |
| 7021 transform: function (style) { |
| 7022 var rules = styleUtil.rulesForStyle(style); |
| 7023 var self = this; |
| 7024 styleUtil.forEachStyleRule(rules, function (rule) { |
| 7025 var map = self._mapRule(rule); |
| 7026 if (rule.parent) { |
| 7027 var m; |
| 7028 while (m = self.rx.EXTEND.exec(rule.cssText)) { |
| 7029 var extend = m[1]; |
| 7030 var extendor = self._findExtendor(extend, rule); |
| 7031 if (extendor) { |
| 7032 self._extendRule(rule, extendor); |
| 7033 } |
| 7034 } |
| 7035 } |
| 7036 rule.cssText = rule.cssText.replace(self.rx.EXTEND, ''); |
| 7037 }); |
| 7038 return styleUtil.toCssText(rules, function (rule) { |
| 7039 if (rule.selector.match(self.rx.STRIP)) { |
| 7040 rule.cssText = ''; |
| 7041 } |
| 7042 }, true); |
| 7043 }, |
| 7044 _mapRule: function (rule) { |
| 7045 if (rule.parent) { |
| 7046 var map = rule.parent.map || (rule.parent.map = {}); |
| 7047 var parts = rule.selector.split(','); |
| 7048 for (var i = 0, p; i < parts.length; i++) { |
| 7049 p = parts[i]; |
| 7050 map[p.trim()] = rule; |
| 7051 } |
| 7052 return map; |
| 7053 } |
| 7054 }, |
| 7055 _findExtendor: function (extend, rule) { |
| 7056 return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findEx
tendor(extend, rule.parent); |
| 7057 }, |
| 7058 _extendRule: function (target, source) { |
| 7059 if (target.parent !== source.parent) { |
| 7060 this._cloneAndAddRuleToParent(source, target.parent); |
| 7061 } |
| 7062 target.extends = target.extends || (target.extends = []); |
| 7063 target.extends.push(source); |
| 7064 source.selector = source.selector.replace(this.rx.STRIP, ''); |
| 7065 source.selector = (source.selector && source.selector + ',\n') + target.selector
; |
| 7066 if (source.extends) { |
| 7067 source.extends.forEach(function (e) { |
| 7068 this._extendRule(target, e); |
| 7069 }, this); |
| 7070 } |
| 7071 }, |
| 7072 _cloneAndAddRuleToParent: function (rule, parent) { |
| 7073 rule = Object.create(rule); |
| 7074 rule.parent = parent; |
| 7075 if (rule.extends) { |
| 7076 rule.extends = rule.extends.slice(); |
| 7077 } |
| 7078 parent.rules.push(rule); |
| 7079 }, |
| 7080 rx: { |
| 7081 EXTEND: /@extends\(([^)]*)\)\s*?;/gim, |
| 7082 STRIP: /%[^,]*$/ |
| 7083 } |
| 7084 }; |
| 7085 }(); |
| 7086 (function () { |
| 7087 var prepElement = Polymer.Base._prepElement; |
| 7088 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7089 var styleUtil = Polymer.StyleUtil; |
| 7090 var styleTransformer = Polymer.StyleTransformer; |
| 7091 var styleExtends = Polymer.StyleExtends; |
| 7092 Polymer.Base._addFeature({ |
| 7093 _prepElement: function (element) { |
| 7094 if (this._encapsulateStyle) { |
| 7095 styleTransformer.element(element, this.is, this._scopeCssViaAttr); |
| 7096 } |
| 7097 prepElement.call(this, element); |
| 7098 }, |
| 7099 _prepStyles: function () { |
| 7100 if (this._encapsulateStyle === undefined) { |
| 7101 this._encapsulateStyle = !nativeShadow && Boolean(this._template); |
| 7102 } |
| 7103 this._styles = this._collectStyles(); |
| 7104 var cssText = styleTransformer.elementStyles(this); |
| 7105 if (cssText && this._template) { |
| 7106 var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.c
ontent : null); |
| 7107 if (!nativeShadow) { |
| 7108 this._scopeStyle = style; |
| 7109 } |
| 7110 } |
| 7111 }, |
| 7112 _collectStyles: function () { |
| 7113 var styles = []; |
| 7114 var cssText = '', m$ = this.styleModules; |
| 7115 if (m$) { |
| 7116 for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) { |
| 7117 cssText += styleUtil.cssFromModule(m); |
| 7118 } |
| 7119 } |
| 7120 cssText += styleUtil.cssFromModule(this.is); |
| 7121 if (cssText) { |
| 7122 var style = document.createElement('style'); |
| 7123 style.textContent = cssText; |
| 7124 if (styleExtends.hasExtends(style.textContent)) { |
| 7125 cssText = styleExtends.transform(style); |
| 7126 } |
| 7127 styles.push(style); |
| 7128 } |
| 7129 return styles; |
| 7130 }, |
| 7131 _elementAdd: function (node) { |
| 7132 if (this._encapsulateStyle) { |
| 7133 if (node.__styleScoped) { |
| 7134 node.__styleScoped = false; |
| 7135 } else { |
| 7136 styleTransformer.dom(node, this.is, this._scopeCssViaAttr); |
| 7137 } |
| 7138 } |
| 7139 }, |
| 7140 _elementRemove: function (node) { |
| 7141 if (this._encapsulateStyle) { |
| 7142 styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true); |
| 7143 } |
| 7144 }, |
| 7145 scopeSubtree: function (container, shouldObserve) { |
| 7146 if (nativeShadow) { |
| 7147 return; |
| 7148 } |
| 7149 var self = this; |
| 7150 var scopify = function (node) { |
| 7151 if (node.nodeType === Node.ELEMENT_NODE) { |
| 7152 node.className = self._scopeElementClass(node, node.className); |
| 7153 var n$ = node.querySelectorAll('*'); |
| 7154 Array.prototype.forEach.call(n$, function (n) { |
| 7155 n.className = self._scopeElementClass(n, n.className); |
| 7156 }); |
| 7157 } |
| 7158 }; |
| 7159 scopify(container); |
| 7160 if (shouldObserve) { |
| 7161 var mo = new MutationObserver(function (mxns) { |
| 7162 mxns.forEach(function (m) { |
| 7163 if (m.addedNodes) { |
| 7164 for (var i = 0; i < m.addedNodes.length; i++) { |
| 7165 scopify(m.addedNodes[i]); |
| 7166 } |
| 7167 } |
| 7168 }); |
| 7169 }); |
| 7170 mo.observe(container, { |
| 7171 childList: true, |
| 7172 subtree: true |
| 7173 }); |
| 7174 return mo; |
| 7175 } |
| 7176 } |
| 7177 }); |
| 7178 }()); |
| 7179 Polymer.StyleProperties = function () { |
| 7180 'use strict'; |
| 7181 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7182 var matchesSelector = Polymer.DomApi.matchesSelector; |
| 7183 var styleUtil = Polymer.StyleUtil; |
| 7184 var styleTransformer = Polymer.StyleTransformer; |
| 7185 return { |
| 7186 decorateStyles: function (styles) { |
| 7187 var self = this, props = {}; |
| 7188 styleUtil.forRulesInStyles(styles, function (rule) { |
| 7189 self.decorateRule(rule); |
| 7190 self.collectPropertiesInCssText(rule.propertyInfo.cssText, props); |
| 7191 }); |
| 7192 var names = []; |
| 7193 for (var i in props) { |
| 7194 names.push(i); |
| 7195 } |
| 7196 return names; |
| 7197 }, |
| 7198 decorateRule: function (rule) { |
| 7199 if (rule.propertyInfo) { |
| 7200 return rule.propertyInfo; |
| 7201 } |
| 7202 var info = {}, properties = {}; |
| 7203 var hasProperties = this.collectProperties(rule, properties); |
| 7204 if (hasProperties) { |
| 7205 info.properties = properties; |
| 7206 rule.rules = null; |
| 7207 } |
| 7208 info.cssText = this.collectCssText(rule); |
| 7209 rule.propertyInfo = info; |
| 7210 return info; |
| 7211 }, |
| 7212 collectProperties: function (rule, properties) { |
| 7213 var info = rule.propertyInfo; |
| 7214 if (info) { |
| 7215 if (info.properties) { |
| 7216 Polymer.Base.mixin(properties, info.properties); |
| 7217 return true; |
| 7218 } |
| 7219 } else { |
| 7220 var m, rx = this.rx.VAR_ASSIGN; |
| 7221 var cssText = rule.parsedCssText; |
| 7222 var any; |
| 7223 while (m = rx.exec(cssText)) { |
| 7224 properties[m[1]] = (m[2] || m[3]).trim(); |
| 7225 any = true; |
| 7226 } |
| 7227 return any; |
| 7228 } |
| 7229 }, |
| 7230 collectCssText: function (rule) { |
| 7231 var customCssText = ''; |
| 7232 var cssText = rule.parsedCssText; |
| 7233 cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, '')
; |
| 7234 var parts = cssText.split(';'); |
| 7235 for (var i = 0, p; i < parts.length; i++) { |
| 7236 p = parts[i]; |
| 7237 if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) { |
| 7238 customCssText += p + ';\n'; |
| 7239 } |
| 7240 } |
| 7241 return customCssText; |
| 7242 }, |
| 7243 collectPropertiesInCssText: function (cssText, props) { |
| 7244 var m; |
| 7245 while (m = this.rx.VAR_CAPTURE.exec(cssText)) { |
| 7246 props[m[1]] = true; |
| 7247 var def = m[2]; |
| 7248 if (def && def.match(this.rx.IS_VAR)) { |
| 7249 props[def] = true; |
| 7250 } |
| 7251 } |
| 7252 }, |
| 7253 reify: function (props) { |
| 7254 var names = Object.getOwnPropertyNames(props); |
| 7255 for (var i = 0, n; i < names.length; i++) { |
| 7256 n = names[i]; |
| 7257 props[n] = this.valueForProperty(props[n], props); |
| 7258 } |
| 7259 }, |
| 7260 valueForProperty: function (property, props) { |
| 7261 if (property) { |
| 7262 if (property.indexOf(';') >= 0) { |
| 7263 property = this.valueForProperties(property, props); |
| 7264 } else { |
| 7265 var self = this; |
| 7266 var fn = function (all, prefix, value, fallback) { |
| 7267 var propertyValue = self.valueForProperty(props[value], props) || (props[fallbac
k] ? self.valueForProperty(props[fallback], props) : fallback); |
| 7268 return prefix + (propertyValue || ''); |
| 7269 }; |
| 7270 property = property.replace(this.rx.VAR_MATCH, fn); |
| 7271 } |
| 7272 } |
| 7273 return property && property.trim() || ''; |
| 7274 }, |
| 7275 valueForProperties: function (property, props) { |
| 7276 var parts = property.split(';'); |
| 7277 for (var i = 0, p, m; i < parts.length; i++) { |
| 7278 if (p = parts[i]) { |
| 7279 m = p.match(this.rx.MIXIN_MATCH); |
| 7280 if (m) { |
| 7281 p = this.valueForProperty(props[m[1]], props); |
| 7282 } else { |
| 7283 var pp = p.split(':'); |
| 7284 if (pp[1]) { |
| 7285 pp[1] = pp[1].trim(); |
| 7286 pp[1] = this.valueForProperty(pp[1], props) || pp[1]; |
| 7287 } |
| 7288 p = pp.join(':'); |
| 7289 } |
| 7290 parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || ''; |
| 7291 } |
| 7292 } |
| 7293 return parts.join(';'); |
| 7294 }, |
| 7295 applyProperties: function (rule, props) { |
| 7296 var output = ''; |
| 7297 if (!rule.propertyInfo) { |
| 7298 this.decorateRule(rule); |
| 7299 } |
| 7300 if (rule.propertyInfo.cssText) { |
| 7301 output = this.valueForProperties(rule.propertyInfo.cssText, props); |
| 7302 } |
| 7303 rule.cssText = output; |
| 7304 }, |
| 7305 propertyDataFromStyles: function (styles, element) { |
| 7306 var props = {}, self = this; |
| 7307 var o = [], i = 0; |
| 7308 styleUtil.forRulesInStyles(styles, function (rule) { |
| 7309 if (!rule.propertyInfo) { |
| 7310 self.decorateRule(rule); |
| 7311 } |
| 7312 if (element && rule.propertyInfo.properties && matchesSelector.call(element, rul
e.transformedSelector || rule.parsedSelector)) { |
| 7313 self.collectProperties(rule, props); |
| 7314 addToBitMask(i, o); |
| 7315 } |
| 7316 i++; |
| 7317 }); |
| 7318 return { |
| 7319 properties: props, |
| 7320 key: o |
| 7321 }; |
| 7322 }, |
| 7323 scopePropertiesFromStyles: function (styles) { |
| 7324 if (!styles._scopeStyleProperties) { |
| 7325 styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SC
OPE_SELECTORS); |
| 7326 } |
| 7327 return styles._scopeStyleProperties; |
| 7328 }, |
| 7329 hostPropertiesFromStyles: function (styles) { |
| 7330 if (!styles._hostStyleProperties) { |
| 7331 styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOS
T_SELECTORS); |
| 7332 } |
| 7333 return styles._hostStyleProperties; |
| 7334 }, |
| 7335 selectedPropertiesFromStyles: function (styles, selectors) { |
| 7336 var props = {}, self = this; |
| 7337 styleUtil.forRulesInStyles(styles, function (rule) { |
| 7338 if (!rule.propertyInfo) { |
| 7339 self.decorateRule(rule); |
| 7340 } |
| 7341 for (var i = 0; i < selectors.length; i++) { |
| 7342 if (rule.parsedSelector === selectors[i]) { |
| 7343 self.collectProperties(rule, props); |
| 7344 return; |
| 7345 } |
| 7346 } |
| 7347 }); |
| 7348 return props; |
| 7349 }, |
| 7350 transformStyles: function (element, properties, scopeSelector) { |
| 7351 var self = this; |
| 7352 var hostSelector = styleTransformer._calcHostScope(element.is, element.extends); |
| 7353 var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]'
: hostSelector; |
| 7354 var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFF
IX); |
| 7355 return styleTransformer.elementStyles(element, function (rule) { |
| 7356 self.applyProperties(rule, properties); |
| 7357 if (rule.cssText && !nativeShadow) { |
| 7358 self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeS
elector); |
| 7359 } |
| 7360 }); |
| 7361 }, |
| 7362 _scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) { |
| 7363 rule.transformedSelector = rule.transformedSelector || rule.selector; |
| 7364 var selector = rule.transformedSelector; |
| 7365 var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' :
'.' + scopeId; |
| 7366 var parts = selector.split(','); |
| 7367 for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) { |
| 7368 parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : sco
pe + ' ' + p; |
| 7369 } |
| 7370 rule.selector = parts.join(','); |
| 7371 }, |
| 7372 applyElementScopeSelector: function (element, selector, old, viaAttr) { |
| 7373 var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.cl
assName; |
| 7374 var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME +
' ' + selector; |
| 7375 if (c !== v) { |
| 7376 if (viaAttr) { |
| 7377 element.setAttribute(styleTransformer.SCOPE_NAME, v); |
| 7378 } else { |
| 7379 element.className = v; |
| 7380 } |
| 7381 } |
| 7382 }, |
| 7383 applyElementStyle: function (element, properties, selector, style) { |
| 7384 var cssText = style ? style.textContent || '' : this.transformStyles(element, pr
operties, selector); |
| 7385 var s = element._customStyle; |
| 7386 if (s && !nativeShadow && s !== style) { |
| 7387 s._useCount--; |
| 7388 if (s._useCount <= 0 && s.parentNode) { |
| 7389 s.parentNode.removeChild(s); |
| 7390 } |
| 7391 } |
| 7392 if (nativeShadow || (!style || !style.parentNode)) { |
| 7393 if (nativeShadow && element._customStyle) { |
| 7394 element._customStyle.textContent = cssText; |
| 7395 style = element._customStyle; |
| 7396 } else if (cssText) { |
| 7397 style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null
, element._scopeStyle); |
| 7398 } |
| 7399 } |
| 7400 if (style) { |
| 7401 style._useCount = style._useCount || 0; |
| 7402 if (element._customStyle != style) { |
| 7403 style._useCount++; |
| 7404 } |
| 7405 element._customStyle = style; |
| 7406 } |
| 7407 return style; |
| 7408 }, |
| 7409 mixinCustomStyle: function (props, customStyle) { |
| 7410 var v; |
| 7411 for (var i in customStyle) { |
| 7412 v = customStyle[i]; |
| 7413 if (v || v === 0) { |
| 7414 props[i] = v; |
| 7415 } |
| 7416 } |
| 7417 }, |
| 7418 rx: { |
| 7419 VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/
gi, |
| 7420 MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i, |
| 7421 VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\))
)[\s]*?\)/gi, |
| 7422 VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi, |
| 7423 IS_VAR: /^--/, |
| 7424 BRACKETED: /\{[^}]*\}/g, |
| 7425 HOST_PREFIX: '(?:^|[^.#[:])', |
| 7426 HOST_SUFFIX: '($|[.:[\\s>+~])' |
| 7427 }, |
| 7428 HOST_SELECTORS: [':host'], |
| 7429 SCOPE_SELECTORS: [':root'], |
| 7430 XSCOPE_NAME: 'x-scope' |
| 7431 }; |
| 7432 function addToBitMask(n, bits) { |
| 7433 var o = parseInt(n / 32); |
| 7434 var v = 1 << n % 32; |
| 7435 bits[o] = (bits[o] || 0) | v; |
| 7436 } |
| 7437 }(); |
| 7438 (function () { |
| 7439 Polymer.StyleCache = function () { |
| 7440 this.cache = {}; |
| 7441 }; |
| 7442 Polymer.StyleCache.prototype = { |
| 7443 MAX: 100, |
| 7444 store: function (is, data, keyValues, keyStyles) { |
| 7445 data.keyValues = keyValues; |
| 7446 data.styles = keyStyles; |
| 7447 var s$ = this.cache[is] = this.cache[is] || []; |
| 7448 s$.push(data); |
| 7449 if (s$.length > this.MAX) { |
| 7450 s$.shift(); |
| 7451 } |
| 7452 }, |
| 7453 retrieve: function (is, keyValues, keyStyles) { |
| 7454 var cache = this.cache[is]; |
| 7455 if (cache) { |
| 7456 for (var i = cache.length - 1, data; i >= 0; i--) { |
| 7457 data = cache[i]; |
| 7458 if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues))
{ |
| 7459 return data; |
| 7460 } |
| 7461 } |
| 7462 } |
| 7463 }, |
| 7464 clear: function () { |
| 7465 this.cache = {}; |
| 7466 }, |
| 7467 _objectsEqual: function (target, source) { |
| 7468 var t, s; |
| 7469 for (var i in target) { |
| 7470 t = target[i], s = source[i]; |
| 7471 if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s))
{ |
| 7472 return false; |
| 7473 } |
| 7474 } |
| 7475 if (Array.isArray(target)) { |
| 7476 return target.length === source.length; |
| 7477 } |
| 7478 return true; |
| 7479 }, |
| 7480 _objectsStrictlyEqual: function (target, source) { |
| 7481 return this._objectsEqual(target, source) && this._objectsEqual(source, target); |
| 7482 } |
| 7483 }; |
| 7484 }()); |
| 7485 Polymer.StyleDefaults = function () { |
| 7486 var styleProperties = Polymer.StyleProperties; |
| 7487 var styleUtil = Polymer.StyleUtil; |
| 7488 var StyleCache = Polymer.StyleCache; |
| 7489 var api = { |
| 7490 _styles: [], |
| 7491 _properties: null, |
| 7492 customStyle: {}, |
| 7493 _styleCache: new StyleCache(), |
| 7494 addStyle: function (style) { |
| 7495 this._styles.push(style); |
| 7496 this._properties = null; |
| 7497 }, |
| 7498 get _styleProperties() { |
| 7499 if (!this._properties) { |
| 7500 styleProperties.decorateStyles(this._styles); |
| 7501 this._styles._scopeStyleProperties = null; |
| 7502 this._properties = styleProperties.scopePropertiesFromStyles(this._styles); |
| 7503 styleProperties.mixinCustomStyle(this._properties, this.customStyle); |
| 7504 styleProperties.reify(this._properties); |
| 7505 } |
| 7506 return this._properties; |
| 7507 }, |
| 7508 _needsStyleProperties: function () { |
| 7509 }, |
| 7510 _computeStyleProperties: function () { |
| 7511 return this._styleProperties; |
| 7512 }, |
| 7513 updateStyles: function (properties) { |
| 7514 this._properties = null; |
| 7515 if (properties) { |
| 7516 Polymer.Base.mixin(this.customStyle, properties); |
| 7517 } |
| 7518 this._styleCache.clear(); |
| 7519 for (var i = 0, s; i < this._styles.length; i++) { |
| 7520 s = this._styles[i]; |
| 7521 s = s.__importElement || s; |
| 7522 s._apply(); |
| 7523 } |
| 7524 } |
| 7525 }; |
| 7526 return api; |
| 7527 }(); |
| 7528 (function () { |
| 7529 'use strict'; |
| 7530 var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute; |
| 7531 var propertyUtils = Polymer.StyleProperties; |
| 7532 var styleTransformer = Polymer.StyleTransformer; |
| 7533 var styleUtil = Polymer.StyleUtil; |
| 7534 var styleDefaults = Polymer.StyleDefaults; |
| 7535 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7536 Polymer.Base._addFeature({ |
| 7537 _prepStyleProperties: function () { |
| 7538 this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._
styles) : []; |
| 7539 }, |
| 7540 customStyle: {}, |
| 7541 _setupStyleProperties: function () { |
| 7542 this.customStyle = {}; |
| 7543 }, |
| 7544 _needsStyleProperties: function () { |
| 7545 return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length
); |
| 7546 }, |
| 7547 _beforeAttached: function () { |
| 7548 if (!this._scopeSelector && this._needsStyleProperties()) { |
| 7549 this._updateStyleProperties(); |
| 7550 } |
| 7551 }, |
| 7552 _findStyleHost: function () { |
| 7553 var e = this, root; |
| 7554 while (root = Polymer.dom(e).getOwnerRoot()) { |
| 7555 if (Polymer.isInstance(root.host)) { |
| 7556 return root.host; |
| 7557 } |
| 7558 e = root.host; |
| 7559 } |
| 7560 return styleDefaults; |
| 7561 }, |
| 7562 _updateStyleProperties: function () { |
| 7563 var info, scope = this._findStyleHost(); |
| 7564 if (!scope._styleCache) { |
| 7565 scope._styleCache = new Polymer.StyleCache(); |
| 7566 } |
| 7567 var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this); |
| 7568 scopeData.key.customStyle = this.customStyle; |
| 7569 info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles); |
| 7570 var scopeCached = Boolean(info); |
| 7571 if (scopeCached) { |
| 7572 this._styleProperties = info._styleProperties; |
| 7573 } else { |
| 7574 this._computeStyleProperties(scopeData.properties); |
| 7575 } |
| 7576 this._computeOwnStyleProperties(); |
| 7577 if (!scopeCached) { |
| 7578 info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles); |
| 7579 } |
| 7580 var globalCached = Boolean(info) && !scopeCached; |
| 7581 var style = this._applyStyleProperties(info); |
| 7582 if (!scopeCached) { |
| 7583 style = style && nativeShadow ? style.cloneNode(true) : style; |
| 7584 info = { |
| 7585 style: style, |
| 7586 _scopeSelector: this._scopeSelector, |
| 7587 _styleProperties: this._styleProperties |
| 7588 }; |
| 7589 scopeData.key.customStyle = {}; |
| 7590 this.mixin(scopeData.key.customStyle, this.customStyle); |
| 7591 scope._styleCache.store(this.is, info, scopeData.key, this._styles); |
| 7592 if (!globalCached) { |
| 7593 styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._s
tyles); |
| 7594 } |
| 7595 } |
| 7596 }, |
| 7597 _computeStyleProperties: function (scopeProps) { |
| 7598 var scope = this._findStyleHost(); |
| 7599 if (!scope._styleProperties) { |
| 7600 scope._computeStyleProperties(); |
| 7601 } |
| 7602 var props = Object.create(scope._styleProperties); |
| 7603 this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles)); |
| 7604 scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, t
his).properties; |
| 7605 this.mixin(props, scopeProps); |
| 7606 this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles)); |
| 7607 propertyUtils.mixinCustomStyle(props, this.customStyle); |
| 7608 propertyUtils.reify(props); |
| 7609 this._styleProperties = props; |
| 7610 }, |
| 7611 _computeOwnStyleProperties: function () { |
| 7612 var props = {}; |
| 7613 for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) { |
| 7614 n = this._ownStylePropertyNames[i]; |
| 7615 props[n] = this._styleProperties[n]; |
| 7616 } |
| 7617 this._ownStyleProperties = props; |
| 7618 }, |
| 7619 _scopeCount: 0, |
| 7620 _applyStyleProperties: function (info) { |
| 7621 var oldScopeSelector = this._scopeSelector; |
| 7622 this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto_
_._scopeCount++; |
| 7623 var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._s
copeSelector, info && info.style); |
| 7624 if (!nativeShadow) { |
| 7625 propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelec
tor, this._scopeCssViaAttr); |
| 7626 } |
| 7627 return style; |
| 7628 }, |
| 7629 serializeValueToAttribute: function (value, attribute, node) { |
| 7630 node = node || this; |
| 7631 if (attribute === 'class' && !nativeShadow) { |
| 7632 var host = node === this ? this.domHost || this.dataHost : this; |
| 7633 if (host) { |
| 7634 value = host._scopeElementClass(node, value); |
| 7635 } |
| 7636 } |
| 7637 node = Polymer.dom(node); |
| 7638 serializeValueToAttribute.call(this, value, attribute, node); |
| 7639 }, |
| 7640 _scopeElementClass: function (element, selector) { |
| 7641 if (!nativeShadow && !this._scopeCssViaAttr) { |
| 7642 selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scope
Selector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : ''); |
| 7643 } |
| 7644 return selector; |
| 7645 }, |
| 7646 updateStyles: function (properties) { |
| 7647 if (this.isAttached) { |
| 7648 if (properties) { |
| 7649 this.mixin(this.customStyle, properties); |
| 7650 } |
| 7651 if (this._needsStyleProperties()) { |
| 7652 this._updateStyleProperties(); |
| 7653 } else { |
| 7654 this._styleProperties = null; |
| 7655 } |
| 7656 if (this._styleCache) { |
| 7657 this._styleCache.clear(); |
| 7658 } |
| 7659 this._updateRootStyles(); |
| 7660 } |
| 7661 }, |
| 7662 _updateRootStyles: function (root) { |
| 7663 root = root || this.root; |
| 7664 var c$ = Polymer.dom(root)._query(function (e) { |
| 7665 return e.shadyRoot || e.shadowRoot; |
| 7666 }); |
| 7667 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 7668 if (c.updateStyles) { |
| 7669 c.updateStyles(); |
| 7670 } |
| 7671 } |
| 7672 } |
| 7673 }); |
| 7674 Polymer.updateStyles = function (properties) { |
| 7675 styleDefaults.updateStyles(properties); |
| 7676 Polymer.Base._updateRootStyles(document); |
| 7677 }; |
| 7678 var styleCache = new Polymer.StyleCache(); |
| 7679 Polymer.customStyleCache = styleCache; |
| 7680 var SCOPE_NAME = styleTransformer.SCOPE_NAME; |
| 7681 var XSCOPE_NAME = propertyUtils.XSCOPE_NAME; |
| 7682 }()); |
| 7683 Polymer.Base._addFeature({ |
| 7684 _registerFeatures: function () { |
| 7685 this._prepIs(); |
| 7686 this._prepAttributes(); |
| 7687 this._prepConstructor(); |
| 7688 this._prepTemplate(); |
| 7689 this._prepStyles(); |
| 7690 this._prepStyleProperties(); |
| 7691 this._prepAnnotations(); |
| 7692 this._prepEffects(); |
| 7693 this._prepBehaviors(); |
| 7694 this._prepBindings(); |
| 7695 this._prepShady(); |
| 7696 }, |
| 7697 _prepBehavior: function (b) { |
| 7698 this._addPropertyEffects(b.properties); |
| 7699 this._addComplexObserverEffects(b.observers); |
| 7700 this._addHostAttributes(b.hostAttributes); |
| 7701 }, |
| 7702 _initFeatures: function () { |
| 7703 this._poolContent(); |
| 7704 this._setupConfigure(); |
| 7705 this._setupStyleProperties(); |
| 7706 this._pushHost(); |
| 7707 this._stampTemplate(); |
| 7708 this._popHost(); |
| 7709 this._marshalAnnotationReferences(); |
| 7710 this._setupDebouncers(); |
| 7711 this._marshalInstanceEffects(); |
| 7712 this._marshalHostAttributes(); |
| 7713 this._marshalBehaviors(); |
| 7714 this._marshalAttributes(); |
| 7715 this._tryReady(); |
| 7716 }, |
| 7717 _marshalBehavior: function (b) { |
| 7718 this._listenListeners(b.listeners); |
| 7719 } |
| 7720 }); |
| 7721 (function () { |
| 7722 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7723 var propertyUtils = Polymer.StyleProperties; |
| 7724 var styleUtil = Polymer.StyleUtil; |
| 7725 var cssParse = Polymer.CssParse; |
| 7726 var styleDefaults = Polymer.StyleDefaults; |
| 7727 var styleTransformer = Polymer.StyleTransformer; |
| 7728 Polymer({ |
| 7729 is: 'custom-style', |
| 7730 extends: 'style', |
| 7731 properties: { include: String }, |
| 7732 ready: function () { |
| 7733 this._tryApply(); |
| 7734 }, |
| 7735 attached: function () { |
| 7736 this._tryApply(); |
| 7737 }, |
| 7738 _tryApply: function () { |
| 7739 if (!this._appliesToDocument) { |
| 7740 if (this.parentNode && this.parentNode.localName !== 'dom-module') { |
| 7741 this._appliesToDocument = true; |
| 7742 var e = this.__appliedElement || this; |
| 7743 styleDefaults.addStyle(e); |
| 7744 if (e.textContent || this.include) { |
| 7745 this._apply(); |
| 7746 } else { |
| 7747 var observer = new MutationObserver(function () { |
| 7748 observer.disconnect(); |
| 7749 this._apply(); |
| 7750 }.bind(this)); |
| 7751 observer.observe(e, { childList: true }); |
| 7752 } |
| 7753 } |
| 7754 } |
| 7755 }, |
| 7756 _apply: function () { |
| 7757 var e = this.__appliedElement || this; |
| 7758 if (this.include) { |
| 7759 e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent; |
| 7760 } |
| 7761 if (e.textContent) { |
| 7762 styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) { |
| 7763 styleTransformer.documentRule(rule); |
| 7764 }); |
| 7765 this._applyCustomProperties(e); |
| 7766 } |
| 7767 }, |
| 7768 _applyCustomProperties: function (element) { |
| 7769 this._computeStyleProperties(); |
| 7770 var props = this._styleProperties; |
| 7771 var rules = styleUtil.rulesForStyle(element); |
| 7772 element.textContent = styleUtil.toCssText(rules, function (rule) { |
| 7773 var css = rule.cssText = rule.parsedCssText; |
| 7774 if (rule.propertyInfo && rule.propertyInfo.cssText) { |
| 7775 css = cssParse.removeCustomPropAssignment(css); |
| 7776 rule.cssText = propertyUtils.valueForProperties(css, props); |
| 7777 } |
| 7778 }); |
| 7779 } |
| 7780 }); |
| 7781 }()); |
| 7782 Polymer.Templatizer = { |
| 7783 properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } }, |
| 7784 _instanceProps: Polymer.nob, |
| 7785 _parentPropPrefix: '_parent_', |
| 7786 templatize: function (template) { |
| 7787 if (!template._content) { |
| 7788 template._content = template.content; |
| 7789 } |
| 7790 if (template._content._ctor) { |
| 7791 this.ctor = template._content._ctor; |
| 7792 this._prepParentProperties(this.ctor.prototype, template); |
| 7793 return; |
| 7794 } |
| 7795 var archetype = Object.create(Polymer.Base); |
| 7796 this._customPrepAnnotations(archetype, template); |
| 7797 archetype._prepEffects(); |
| 7798 this._customPrepEffects(archetype); |
| 7799 archetype._prepBehaviors(); |
| 7800 archetype._prepBindings(); |
| 7801 this._prepParentProperties(archetype, template); |
| 7802 archetype._notifyPath = this._notifyPathImpl; |
| 7803 archetype._scopeElementClass = this._scopeElementClassImpl; |
| 7804 archetype.listen = this._listenImpl; |
| 7805 archetype._showHideChildren = this._showHideChildrenImpl; |
| 7806 var _constructor = this._constructorImpl; |
| 7807 var ctor = function TemplateInstance(model, host) { |
| 7808 _constructor.call(this, model, host); |
| 7809 }; |
| 7810 ctor.prototype = archetype; |
| 7811 archetype.constructor = ctor; |
| 7812 template._content._ctor = ctor; |
| 7813 this.ctor = ctor; |
| 7814 }, |
| 7815 _getRootDataHost: function () { |
| 7816 return this.dataHost && this.dataHost._rootDataHost || this.dataHost; |
| 7817 }, |
| 7818 _showHideChildrenImpl: function (hide) { |
| 7819 var c = this._children; |
| 7820 for (var i = 0; i < c.length; i++) { |
| 7821 var n = c[i]; |
| 7822 if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) { |
| 7823 if (n.nodeType === Node.TEXT_NODE) { |
| 7824 if (hide) { |
| 7825 n.__polymerTextContent__ = n.textContent; |
| 7826 n.textContent = ''; |
| 7827 } else { |
| 7828 n.textContent = n.__polymerTextContent__; |
| 7829 } |
| 7830 } else if (n.style) { |
| 7831 if (hide) { |
| 7832 n.__polymerDisplay__ = n.style.display; |
| 7833 n.style.display = 'none'; |
| 7834 } else { |
| 7835 n.style.display = n.__polymerDisplay__; |
| 7836 } |
| 7837 } |
| 7838 } |
| 7839 n.__hideTemplateChildren__ = hide; |
| 7840 } |
| 7841 }, |
| 7842 _debounceTemplate: function (fn) { |
| 7843 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn)); |
| 7844 }, |
| 7845 _flushTemplates: function (debouncerExpired) { |
| 7846 Polymer.dom.flush(); |
| 7847 }, |
| 7848 _customPrepEffects: function (archetype) { |
| 7849 var parentProps = archetype._parentProps; |
| 7850 for (var prop in parentProps) { |
| 7851 archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop
)); |
| 7852 } |
| 7853 for (var prop in this._instanceProps) { |
| 7854 archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector(
prop)); |
| 7855 } |
| 7856 }, |
| 7857 _customPrepAnnotations: function (archetype, template) { |
| 7858 archetype._template = template; |
| 7859 var c = template._content; |
| 7860 if (!c._notes) { |
| 7861 var rootDataHost = archetype._rootDataHost; |
| 7862 if (rootDataHost) { |
| 7863 Polymer.Annotations.prepElement = rootDataHost._prepElement.bind(rootDataHost); |
| 7864 } |
| 7865 c._notes = Polymer.Annotations.parseAnnotations(template); |
| 7866 Polymer.Annotations.prepElement = null; |
| 7867 this._processAnnotations(c._notes); |
| 7868 } |
| 7869 archetype._notes = c._notes; |
| 7870 archetype._parentProps = c._parentProps; |
| 7871 }, |
| 7872 _prepParentProperties: function (archetype, template) { |
| 7873 var parentProps = this._parentProps = archetype._parentProps; |
| 7874 if (this._forwardParentProp && parentProps) { |
| 7875 var proto = archetype._parentPropProto; |
| 7876 var prop; |
| 7877 if (!proto) { |
| 7878 for (prop in this._instanceProps) { |
| 7879 delete parentProps[prop]; |
| 7880 } |
| 7881 proto = archetype._parentPropProto = Object.create(null); |
| 7882 if (template != this) { |
| 7883 Polymer.Bind.prepareModel(proto); |
| 7884 } |
| 7885 for (prop in parentProps) { |
| 7886 var parentProp = this._parentPropPrefix + prop; |
| 7887 var effects = [ |
| 7888 { |
| 7889 kind: 'function', |
| 7890 effect: this._createForwardPropEffector(prop) |
| 7891 }, |
| 7892 { kind: 'notify' } |
| 7893 ]; |
| 7894 Polymer.Bind._createAccessors(proto, parentProp, effects); |
| 7895 } |
| 7896 } |
| 7897 if (template != this) { |
| 7898 Polymer.Bind.prepareInstance(template); |
| 7899 template._forwardParentProp = this._forwardParentProp.bind(this); |
| 7900 } |
| 7901 this._extendTemplate(template, proto); |
| 7902 } |
| 7903 }, |
| 7904 _createForwardPropEffector: function (prop) { |
| 7905 return function (source, value) { |
| 7906 this._forwardParentProp(prop, value); |
| 7907 }; |
| 7908 }, |
| 7909 _createHostPropEffector: function (prop) { |
| 7910 var prefix = this._parentPropPrefix; |
| 7911 return function (source, value) { |
| 7912 this.dataHost[prefix + prop] = value; |
| 7913 }; |
| 7914 }, |
| 7915 _createInstancePropEffector: function (prop) { |
| 7916 return function (source, value, old, fromAbove) { |
| 7917 if (!fromAbove) { |
| 7918 this.dataHost._forwardInstanceProp(this, prop, value); |
| 7919 } |
| 7920 }; |
| 7921 }, |
| 7922 _extendTemplate: function (template, proto) { |
| 7923 Object.getOwnPropertyNames(proto).forEach(function (n) { |
| 7924 var val = template[n]; |
| 7925 var pd = Object.getOwnPropertyDescriptor(proto, n); |
| 7926 Object.defineProperty(template, n, pd); |
| 7927 if (val !== undefined) { |
| 7928 template._propertySetter(n, val); |
| 7929 } |
| 7930 }); |
| 7931 }, |
| 7932 _showHideChildren: function (hidden) { |
| 7933 }, |
| 7934 _forwardInstancePath: function (inst, path, value) { |
| 7935 }, |
| 7936 _forwardInstanceProp: function (inst, prop, value) { |
| 7937 }, |
| 7938 _notifyPathImpl: function (path, value) { |
| 7939 var dataHost = this.dataHost; |
| 7940 var dot = path.indexOf('.'); |
| 7941 var root = dot < 0 ? path : path.slice(0, dot); |
| 7942 dataHost._forwardInstancePath.call(dataHost, this, path, value); |
| 7943 if (root in dataHost._parentProps) { |
| 7944 dataHost.notifyPath(dataHost._parentPropPrefix + path, value); |
| 7945 } |
| 7946 }, |
| 7947 _pathEffector: function (path, value, fromAbove) { |
| 7948 if (this._forwardParentPath) { |
| 7949 if (path.indexOf(this._parentPropPrefix) === 0) { |
| 7950 this._forwardParentPath(path.substring(8), value); |
| 7951 } |
| 7952 } |
| 7953 Polymer.Base._pathEffector.apply(this, arguments); |
| 7954 }, |
| 7955 _constructorImpl: function (model, host) { |
| 7956 this._rootDataHost = host._getRootDataHost(); |
| 7957 this._setupConfigure(model); |
| 7958 this._pushHost(host); |
| 7959 this.root = this.instanceTemplate(this._template); |
| 7960 this.root.__noContent = !this._notes._hasContent; |
| 7961 this.root.__styleScoped = true; |
| 7962 this._popHost(); |
| 7963 this._marshalAnnotatedNodes(); |
| 7964 this._marshalInstanceEffects(); |
| 7965 this._marshalAnnotatedListeners(); |
| 7966 var children = []; |
| 7967 for (var n = this.root.firstChild; n; n = n.nextSibling) { |
| 7968 children.push(n); |
| 7969 n._templateInstance = this; |
| 7970 } |
| 7971 this._children = children; |
| 7972 if (host.__hideTemplateChildren__) { |
| 7973 this._showHideChildren(true); |
| 7974 } |
| 7975 this._tryReady(); |
| 7976 }, |
| 7977 _listenImpl: function (node, eventName, methodName) { |
| 7978 var model = this; |
| 7979 var host = this._rootDataHost; |
| 7980 var handler = host._createEventHandler(node, eventName, methodName); |
| 7981 var decorated = function (e) { |
| 7982 e.model = model; |
| 7983 handler(e); |
| 7984 }; |
| 7985 host._listen(node, eventName, decorated); |
| 7986 }, |
| 7987 _scopeElementClassImpl: function (node, value) { |
| 7988 var host = this._rootDataHost; |
| 7989 if (host) { |
| 7990 return host._scopeElementClass(node, value); |
| 7991 } |
| 7992 }, |
| 7993 stamp: function (model) { |
| 7994 model = model || {}; |
| 7995 if (this._parentProps) { |
| 7996 for (var prop in this._parentProps) { |
| 7997 model[prop] = this[this._parentPropPrefix + prop]; |
| 7998 } |
| 7999 } |
| 8000 return new this.ctor(model, this); |
| 8001 }, |
| 8002 modelForElement: function (el) { |
| 8003 var model; |
| 8004 while (el) { |
| 8005 if (model = el._templateInstance) { |
| 8006 if (model.dataHost != this) { |
| 8007 el = model.dataHost; |
| 8008 } else { |
| 8009 return model; |
| 8010 } |
| 8011 } else { |
| 8012 el = el.parentNode; |
| 8013 } |
| 8014 } |
| 8015 } |
| 8016 }; |
| 8017 Polymer({ |
| 8018 is: 'dom-template', |
| 8019 extends: 'template', |
| 8020 behaviors: [Polymer.Templatizer], |
| 8021 ready: function () { |
| 8022 this.templatize(this); |
| 8023 } |
| 8024 }); |
| 8025 Polymer._collections = new WeakMap(); |
| 8026 Polymer.Collection = function (userArray) { |
| 8027 Polymer._collections.set(userArray, this); |
| 8028 this.userArray = userArray; |
| 8029 this.store = userArray.slice(); |
| 8030 this.initMap(); |
| 8031 }; |
| 8032 Polymer.Collection.prototype = { |
| 8033 constructor: Polymer.Collection, |
| 8034 initMap: function () { |
| 8035 var omap = this.omap = new WeakMap(); |
| 8036 var pmap = this.pmap = {}; |
| 8037 var s = this.store; |
| 8038 for (var i = 0; i < s.length; i++) { |
| 8039 var item = s[i]; |
| 8040 if (item && typeof item == 'object') { |
| 8041 omap.set(item, i); |
| 8042 } else { |
| 8043 pmap[item] = i; |
| 8044 } |
| 8045 } |
| 8046 }, |
| 8047 add: function (item) { |
| 8048 var key = this.store.push(item) - 1; |
| 8049 if (item && typeof item == 'object') { |
| 8050 this.omap.set(item, key); |
| 8051 } else { |
| 8052 this.pmap[item] = key; |
| 8053 } |
| 8054 return key; |
| 8055 }, |
| 8056 removeKey: function (key) { |
| 8057 this._removeFromMap(this.store[key]); |
| 8058 delete this.store[key]; |
| 8059 }, |
| 8060 _removeFromMap: function (item) { |
| 8061 if (item && typeof item == 'object') { |
| 8062 this.omap.delete(item); |
| 8063 } else { |
| 8064 delete this.pmap[item]; |
| 8065 } |
| 8066 }, |
| 8067 remove: function (item) { |
| 8068 var key = this.getKey(item); |
| 8069 this.removeKey(key); |
| 8070 return key; |
| 8071 }, |
| 8072 getKey: function (item) { |
| 8073 if (item && typeof item == 'object') { |
| 8074 return this.omap.get(item); |
| 8075 } else { |
| 8076 return this.pmap[item]; |
| 8077 } |
| 8078 }, |
| 8079 getKeys: function () { |
| 8080 return Object.keys(this.store); |
| 8081 }, |
| 8082 setItem: function (key, item) { |
| 8083 var old = this.store[key]; |
| 8084 if (old) { |
| 8085 this._removeFromMap(old); |
| 8086 } |
| 8087 if (item && typeof item == 'object') { |
| 8088 this.omap.set(item, key); |
| 8089 } else { |
| 8090 this.pmap[item] = key; |
| 8091 } |
| 8092 this.store[key] = item; |
| 8093 }, |
| 8094 getItem: function (key) { |
| 8095 return this.store[key]; |
| 8096 }, |
| 8097 getItems: function () { |
| 8098 var items = [], store = this.store; |
| 8099 for (var key in store) { |
| 8100 items.push(store[key]); |
| 8101 } |
| 8102 return items; |
| 8103 }, |
| 8104 _applySplices: function (splices) { |
| 8105 var keyMap = {}, key, i; |
| 8106 splices.forEach(function (s) { |
| 8107 s.addedKeys = []; |
| 8108 for (i = 0; i < s.removed.length; i++) { |
| 8109 key = this.getKey(s.removed[i]); |
| 8110 keyMap[key] = keyMap[key] ? null : -1; |
| 8111 } |
| 8112 for (i = 0; i < s.addedCount; i++) { |
| 8113 var item = this.userArray[s.index + i]; |
| 8114 key = this.getKey(item); |
| 8115 key = key === undefined ? this.add(item) : key; |
| 8116 keyMap[key] = keyMap[key] ? null : 1; |
| 8117 s.addedKeys.push(key); |
| 8118 } |
| 8119 }, this); |
| 8120 var removed = []; |
| 8121 var added = []; |
| 8122 for (var key in keyMap) { |
| 8123 if (keyMap[key] < 0) { |
| 8124 this.removeKey(key); |
| 8125 removed.push(key); |
| 8126 } |
| 8127 if (keyMap[key] > 0) { |
| 8128 added.push(key); |
| 8129 } |
| 8130 } |
| 8131 return [{ |
| 8132 removed: removed, |
| 8133 added: added |
| 8134 }]; |
| 8135 } |
| 8136 }; |
| 8137 Polymer.Collection.get = function (userArray) { |
| 8138 return Polymer._collections.get(userArray) || new Polymer.Collection(userArray); |
| 8139 }; |
| 8140 Polymer.Collection.applySplices = function (userArray, splices) { |
| 8141 var coll = Polymer._collections.get(userArray); |
| 8142 return coll ? coll._applySplices(splices) : null; |
| 8143 }; |
| 8144 Polymer({ |
| 8145 is: 'dom-repeat', |
| 8146 extends: 'template', |
| 8147 properties: { |
| 8148 items: { type: Array }, |
| 8149 as: { |
| 8150 type: String, |
| 8151 value: 'item' |
| 8152 }, |
| 8153 indexAs: { |
| 8154 type: String, |
| 8155 value: 'index' |
| 8156 }, |
| 8157 sort: { |
| 8158 type: Function, |
| 8159 observer: '_sortChanged' |
| 8160 }, |
| 8161 filter: { |
| 8162 type: Function, |
| 8163 observer: '_filterChanged' |
| 8164 }, |
| 8165 observe: { |
| 8166 type: String, |
| 8167 observer: '_observeChanged' |
| 8168 }, |
| 8169 delay: Number |
| 8170 }, |
| 8171 behaviors: [Polymer.Templatizer], |
| 8172 observers: ['_itemsChanged(items.*)'], |
| 8173 created: function () { |
| 8174 this._instances = []; |
| 8175 }, |
| 8176 detached: function () { |
| 8177 for (var i = 0; i < this._instances.length; i++) { |
| 8178 this._detachRow(i); |
| 8179 } |
| 8180 }, |
| 8181 attached: function () { |
| 8182 var parentNode = Polymer.dom(this).parentNode; |
| 8183 for (var i = 0; i < this._instances.length; i++) { |
| 8184 Polymer.dom(parentNode).insertBefore(this._instances[i].root, this); |
| 8185 } |
| 8186 }, |
| 8187 ready: function () { |
| 8188 this._instanceProps = { __key__: true }; |
| 8189 this._instanceProps[this.as] = true; |
| 8190 this._instanceProps[this.indexAs] = true; |
| 8191 if (!this.ctor) { |
| 8192 this.templatize(this); |
| 8193 } |
| 8194 }, |
| 8195 _sortChanged: function () { |
| 8196 var dataHost = this._getRootDataHost(); |
| 8197 var sort = this.sort; |
| 8198 this._sortFn = sort && (typeof sort == 'function' ? sort : function () { |
| 8199 return dataHost[sort].apply(dataHost, arguments); |
| 8200 }); |
| 8201 this._needFullRefresh = true; |
| 8202 if (this.items) { |
| 8203 this._debounceTemplate(this._render); |
| 8204 } |
| 8205 }, |
| 8206 _filterChanged: function () { |
| 8207 var dataHost = this._getRootDataHost(); |
| 8208 var filter = this.filter; |
| 8209 this._filterFn = filter && (typeof filter == 'function' ? filter : function () { |
| 8210 return dataHost[filter].apply(dataHost, arguments); |
| 8211 }); |
| 8212 this._needFullRefresh = true; |
| 8213 if (this.items) { |
| 8214 this._debounceTemplate(this._render); |
| 8215 } |
| 8216 }, |
| 8217 _observeChanged: function () { |
| 8218 this._observePaths = this.observe && this.observe.replace('.*', '.').split(' '); |
| 8219 }, |
| 8220 _itemsChanged: function (change) { |
| 8221 if (change.path == 'items') { |
| 8222 if (Array.isArray(this.items)) { |
| 8223 this.collection = Polymer.Collection.get(this.items); |
| 8224 } else if (!this.items) { |
| 8225 this.collection = null; |
| 8226 } else { |
| 8227 this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', t
his.items)); |
| 8228 } |
| 8229 this._keySplices = []; |
| 8230 this._indexSplices = []; |
| 8231 this._needFullRefresh = true; |
| 8232 this._debounceTemplate(this._render); |
| 8233 } else if (change.path == 'items.splices') { |
| 8234 this._keySplices = this._keySplices.concat(change.value.keySplices); |
| 8235 this._indexSplices = this._indexSplices.concat(change.value.indexSplices); |
| 8236 this._debounceTemplate(this._render); |
| 8237 } else { |
| 8238 var subpath = change.path.slice(6); |
| 8239 this._forwardItemPath(subpath, change.value); |
| 8240 this._checkObservedPaths(subpath); |
| 8241 } |
| 8242 }, |
| 8243 _checkObservedPaths: function (path) { |
| 8244 if (this._observePaths) { |
| 8245 path = path.substring(path.indexOf('.') + 1); |
| 8246 var paths = this._observePaths; |
| 8247 for (var i = 0; i < paths.length; i++) { |
| 8248 if (path.indexOf(paths[i]) === 0) { |
| 8249 this._needFullRefresh = true; |
| 8250 if (this.delay) { |
| 8251 this.debounce('render', this._render, this.delay); |
| 8252 } else { |
| 8253 this._debounceTemplate(this._render); |
| 8254 } |
| 8255 return; |
| 8256 } |
| 8257 } |
| 8258 } |
| 8259 }, |
| 8260 render: function () { |
| 8261 this._needFullRefresh = true; |
| 8262 this._debounceTemplate(this._render); |
| 8263 this._flushTemplates(); |
| 8264 }, |
| 8265 _render: function () { |
| 8266 var c = this.collection; |
| 8267 if (this._needFullRefresh) { |
| 8268 this._applyFullRefresh(); |
| 8269 this._needFullRefresh = false; |
| 8270 } else { |
| 8271 if (this._sortFn) { |
| 8272 this._applySplicesUserSort(this._keySplices); |
| 8273 } else { |
| 8274 if (this._filterFn) { |
| 8275 this._applyFullRefresh(); |
| 8276 } else { |
| 8277 this._applySplicesArrayOrder(this._indexSplices); |
| 8278 } |
| 8279 } |
| 8280 } |
| 8281 this._keySplices = []; |
| 8282 this._indexSplices = []; |
| 8283 var keyToIdx = this._keyToInstIdx = {}; |
| 8284 for (var i = 0; i < this._instances.length; i++) { |
| 8285 var inst = this._instances[i]; |
| 8286 keyToIdx[inst.__key__] = i; |
| 8287 inst.__setProperty(this.indexAs, i, true); |
| 8288 } |
| 8289 this.fire('dom-change'); |
| 8290 }, |
| 8291 _applyFullRefresh: function () { |
| 8292 var c = this.collection; |
| 8293 var keys; |
| 8294 if (this._sortFn) { |
| 8295 keys = c ? c.getKeys() : []; |
| 8296 } else { |
| 8297 keys = []; |
| 8298 var items = this.items; |
| 8299 if (items) { |
| 8300 for (var i = 0; i < items.length; i++) { |
| 8301 keys.push(c.getKey(items[i])); |
| 8302 } |
| 8303 } |
| 8304 } |
| 8305 if (this._filterFn) { |
| 8306 keys = keys.filter(function (a) { |
| 8307 return this._filterFn(c.getItem(a)); |
| 8308 }, this); |
| 8309 } |
| 8310 if (this._sortFn) { |
| 8311 keys.sort(function (a, b) { |
| 8312 return this._sortFn(c.getItem(a), c.getItem(b)); |
| 8313 }.bind(this)); |
| 8314 } |
| 8315 for (var i = 0; i < keys.length; i++) { |
| 8316 var key = keys[i]; |
| 8317 var inst = this._instances[i]; |
| 8318 if (inst) { |
| 8319 inst.__setProperty('__key__', key, true); |
| 8320 inst.__setProperty(this.as, c.getItem(key), true); |
| 8321 } else { |
| 8322 this._instances.push(this._insertRow(i, key)); |
| 8323 } |
| 8324 } |
| 8325 for (; i < this._instances.length; i++) { |
| 8326 this._detachRow(i); |
| 8327 } |
| 8328 this._instances.splice(keys.length, this._instances.length - keys.length); |
| 8329 }, |
| 8330 _keySort: function (a, b) { |
| 8331 return this.collection.getKey(a) - this.collection.getKey(b); |
| 8332 }, |
| 8333 _numericSort: function (a, b) { |
| 8334 return a - b; |
| 8335 }, |
| 8336 _applySplicesUserSort: function (splices) { |
| 8337 var c = this.collection; |
| 8338 var instances = this._instances; |
| 8339 var keyMap = {}; |
| 8340 var pool = []; |
| 8341 var sortFn = this._sortFn || this._keySort.bind(this); |
| 8342 splices.forEach(function (s) { |
| 8343 for (var i = 0; i < s.removed.length; i++) { |
| 8344 var key = s.removed[i]; |
| 8345 keyMap[key] = keyMap[key] ? null : -1; |
| 8346 } |
| 8347 for (var i = 0; i < s.added.length; i++) { |
| 8348 var key = s.added[i]; |
| 8349 keyMap[key] = keyMap[key] ? null : 1; |
| 8350 } |
| 8351 }, this); |
| 8352 var removedIdxs = []; |
| 8353 var addedKeys = []; |
| 8354 for (var key in keyMap) { |
| 8355 if (keyMap[key] === -1) { |
| 8356 removedIdxs.push(this._keyToInstIdx[key]); |
| 8357 } |
| 8358 if (keyMap[key] === 1) { |
| 8359 addedKeys.push(key); |
| 8360 } |
| 8361 } |
| 8362 if (removedIdxs.length) { |
| 8363 removedIdxs.sort(this._numericSort); |
| 8364 for (var i = removedIdxs.length - 1; i >= 0; i--) { |
| 8365 var idx = removedIdxs[i]; |
| 8366 if (idx !== undefined) { |
| 8367 pool.push(this._detachRow(idx)); |
| 8368 instances.splice(idx, 1); |
| 8369 } |
| 8370 } |
| 8371 } |
| 8372 if (addedKeys.length) { |
| 8373 if (this._filterFn) { |
| 8374 addedKeys = addedKeys.filter(function (a) { |
| 8375 return this._filterFn(c.getItem(a)); |
| 8376 }, this); |
| 8377 } |
| 8378 addedKeys.sort(function (a, b) { |
| 8379 return this._sortFn(c.getItem(a), c.getItem(b)); |
| 8380 }.bind(this)); |
| 8381 var start = 0; |
| 8382 for (var i = 0; i < addedKeys.length; i++) { |
| 8383 start = this._insertRowUserSort(start, addedKeys[i], pool); |
| 8384 } |
| 8385 } |
| 8386 }, |
| 8387 _insertRowUserSort: function (start, key, pool) { |
| 8388 var c = this.collection; |
| 8389 var item = c.getItem(key); |
| 8390 var end = this._instances.length - 1; |
| 8391 var idx = -1; |
| 8392 var sortFn = this._sortFn || this._keySort.bind(this); |
| 8393 while (start <= end) { |
| 8394 var mid = start + end >> 1; |
| 8395 var midKey = this._instances[mid].__key__; |
| 8396 var cmp = sortFn(c.getItem(midKey), item); |
| 8397 if (cmp < 0) { |
| 8398 start = mid + 1; |
| 8399 } else if (cmp > 0) { |
| 8400 end = mid - 1; |
| 8401 } else { |
| 8402 idx = mid; |
| 8403 break; |
| 8404 } |
| 8405 } |
| 8406 if (idx < 0) { |
| 8407 idx = end + 1; |
| 8408 } |
| 8409 this._instances.splice(idx, 0, this._insertRow(idx, key, pool)); |
| 8410 return idx; |
| 8411 }, |
| 8412 _applySplicesArrayOrder: function (splices) { |
| 8413 var pool = []; |
| 8414 var c = this.collection; |
| 8415 splices.forEach(function (s) { |
| 8416 for (var i = 0; i < s.removed.length; i++) { |
| 8417 var inst = this._detachRow(s.index + i); |
| 8418 if (!inst.isPlaceholder) { |
| 8419 pool.push(inst); |
| 8420 } |
| 8421 } |
| 8422 this._instances.splice(s.index, s.removed.length); |
| 8423 for (var i = 0; i < s.addedKeys.length; i++) { |
| 8424 var inst = { |
| 8425 isPlaceholder: true, |
| 8426 key: s.addedKeys[i] |
| 8427 }; |
| 8428 this._instances.splice(s.index + i, 0, inst); |
| 8429 } |
| 8430 }, this); |
| 8431 for (var i = this._instances.length - 1; i >= 0; i--) { |
| 8432 var inst = this._instances[i]; |
| 8433 if (inst.isPlaceholder) { |
| 8434 this._instances[i] = this._insertRow(i, inst.key, pool, true); |
| 8435 } |
| 8436 } |
| 8437 }, |
| 8438 _detachRow: function (idx) { |
| 8439 var inst = this._instances[idx]; |
| 8440 if (!inst.isPlaceholder) { |
| 8441 var parentNode = Polymer.dom(this).parentNode; |
| 8442 for (var i = 0; i < inst._children.length; i++) { |
| 8443 var el = inst._children[i]; |
| 8444 Polymer.dom(inst.root).appendChild(el); |
| 8445 } |
| 8446 } |
| 8447 return inst; |
| 8448 }, |
| 8449 _insertRow: function (idx, key, pool, replace) { |
| 8450 var inst; |
| 8451 if (inst = pool && pool.pop()) { |
| 8452 inst.__setProperty(this.as, this.collection.getItem(key), true); |
| 8453 inst.__setProperty('__key__', key, true); |
| 8454 } else { |
| 8455 inst = this._generateRow(idx, key); |
| 8456 } |
| 8457 var beforeRow = this._instances[replace ? idx + 1 : idx]; |
| 8458 var beforeNode = beforeRow ? beforeRow._children[0] : this; |
| 8459 var parentNode = Polymer.dom(this).parentNode; |
| 8460 Polymer.dom(parentNode).insertBefore(inst.root, beforeNode); |
| 8461 return inst; |
| 8462 }, |
| 8463 _generateRow: function (idx, key) { |
| 8464 var model = { __key__: key }; |
| 8465 model[this.as] = this.collection.getItem(key); |
| 8466 model[this.indexAs] = idx; |
| 8467 var inst = this.stamp(model); |
| 8468 return inst; |
| 8469 }, |
| 8470 _showHideChildren: function (hidden) { |
| 8471 for (var i = 0; i < this._instances.length; i++) { |
| 8472 this._instances[i]._showHideChildren(hidden); |
| 8473 } |
| 8474 }, |
| 8475 _forwardInstanceProp: function (inst, prop, value) { |
| 8476 if (prop == this.as) { |
| 8477 var idx; |
| 8478 if (this._sortFn || this._filterFn) { |
| 8479 idx = this.items.indexOf(this.collection.getItem(inst.__key__)); |
| 8480 } else { |
| 8481 idx = inst[this.indexAs]; |
| 8482 } |
| 8483 this.set('items.' + idx, value); |
| 8484 } |
| 8485 }, |
| 8486 _forwardInstancePath: function (inst, path, value) { |
| 8487 if (path.indexOf(this.as + '.') === 0) { |
| 8488 this.notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1),
value); |
| 8489 } |
| 8490 }, |
| 8491 _forwardParentProp: function (prop, value) { |
| 8492 this._instances.forEach(function (inst) { |
| 8493 inst.__setProperty(prop, value, true); |
| 8494 }, this); |
| 8495 }, |
| 8496 _forwardParentPath: function (path, value) { |
| 8497 this._instances.forEach(function (inst) { |
| 8498 inst.notifyPath(path, value, true); |
| 8499 }, this); |
| 8500 }, |
| 8501 _forwardItemPath: function (path, value) { |
| 8502 if (this._keyToInstIdx) { |
| 8503 var dot = path.indexOf('.'); |
| 8504 var key = path.substring(0, dot < 0 ? path.length : dot); |
| 8505 var idx = this._keyToInstIdx[key]; |
| 8506 var inst = this._instances[idx]; |
| 8507 if (inst) { |
| 8508 if (dot >= 0) { |
| 8509 path = this.as + '.' + path.substring(dot + 1); |
| 8510 inst.notifyPath(path, value, true); |
| 8511 } else { |
| 8512 inst.__setProperty(this.as, value, true); |
| 8513 } |
| 8514 } |
| 8515 } |
| 8516 }, |
| 8517 itemForElement: function (el) { |
| 8518 var instance = this.modelForElement(el); |
| 8519 return instance && instance[this.as]; |
| 8520 }, |
| 8521 keyForElement: function (el) { |
| 8522 var instance = this.modelForElement(el); |
| 8523 return instance && instance.__key__; |
| 8524 }, |
| 8525 indexForElement: function (el) { |
| 8526 var instance = this.modelForElement(el); |
| 8527 return instance && instance[this.indexAs]; |
| 8528 } |
| 8529 }); |
| 8530 Polymer({ |
| 8531 is: 'array-selector', |
| 8532 properties: { |
| 8533 items: { |
| 8534 type: Array, |
| 8535 observer: 'clearSelection' |
| 8536 }, |
| 8537 multi: { |
| 8538 type: Boolean, |
| 8539 value: false, |
| 8540 observer: 'clearSelection' |
| 8541 }, |
| 8542 selected: { |
| 8543 type: Object, |
| 8544 notify: true |
| 8545 }, |
| 8546 selectedItem: { |
| 8547 type: Object, |
| 8548 notify: true |
| 8549 }, |
| 8550 toggle: { |
| 8551 type: Boolean, |
| 8552 value: false |
| 8553 } |
| 8554 }, |
| 8555 clearSelection: function () { |
| 8556 if (Array.isArray(this.selected)) { |
| 8557 for (var i = 0; i < this.selected.length; i++) { |
| 8558 this.unlinkPaths('selected.' + i); |
| 8559 } |
| 8560 } else { |
| 8561 this.unlinkPaths('selected'); |
| 8562 } |
| 8563 if (this.multi) { |
| 8564 if (!this.selected || this.selected.length) { |
| 8565 this.selected = []; |
| 8566 this._selectedColl = Polymer.Collection.get(this.selected); |
| 8567 } |
| 8568 } else { |
| 8569 this.selected = null; |
| 8570 this._selectedColl = null; |
| 8571 } |
| 8572 this.selectedItem = null; |
| 8573 }, |
| 8574 isSelected: function (item) { |
| 8575 if (this.multi) { |
| 8576 return this._selectedColl.getKey(item) !== undefined; |
| 8577 } else { |
| 8578 return this.selected == item; |
| 8579 } |
| 8580 }, |
| 8581 deselect: function (item) { |
| 8582 if (this.multi) { |
| 8583 if (this.isSelected(item)) { |
| 8584 var skey = this._selectedColl.getKey(item); |
| 8585 this.arrayDelete('selected', item); |
| 8586 this.unlinkPaths('selected.' + skey); |
| 8587 } |
| 8588 } else { |
| 8589 this.selected = null; |
| 8590 this.selectedItem = null; |
| 8591 this.unlinkPaths('selected'); |
| 8592 this.unlinkPaths('selectedItem'); |
| 8593 } |
| 8594 }, |
| 8595 select: function (item) { |
| 8596 var icol = Polymer.Collection.get(this.items); |
| 8597 var key = icol.getKey(item); |
| 8598 if (this.multi) { |
| 8599 if (this.isSelected(item)) { |
| 8600 if (this.toggle) { |
| 8601 this.deselect(item); |
| 8602 } |
| 8603 } else { |
| 8604 this.push('selected', item); |
| 8605 skey = this._selectedColl.getKey(item); |
| 8606 this.linkPaths('selected.' + skey, 'items.' + key); |
| 8607 } |
| 8608 } else { |
| 8609 if (this.toggle && item == this.selected) { |
| 8610 this.deselect(); |
| 8611 } else { |
| 8612 this.selected = item; |
| 8613 this.selectedItem = item; |
| 8614 this.linkPaths('selected', 'items.' + key); |
| 8615 this.linkPaths('selectedItem', 'items.' + key); |
| 8616 } |
| 8617 } |
| 8618 } |
| 8619 }); |
| 8620 Polymer({ |
| 8621 is: 'dom-if', |
| 8622 extends: 'template', |
| 8623 properties: { |
| 8624 'if': { |
| 8625 type: Boolean, |
| 8626 value: false, |
| 8627 observer: '_queueRender' |
| 8628 }, |
| 8629 restamp: { |
| 8630 type: Boolean, |
| 8631 value: false, |
| 8632 observer: '_queueRender' |
| 8633 } |
| 8634 }, |
| 8635 behaviors: [Polymer.Templatizer], |
| 8636 _queueRender: function () { |
| 8637 this._debounceTemplate(this._render); |
| 8638 }, |
| 8639 detached: function () { |
| 8640 this._teardownInstance(); |
| 8641 }, |
| 8642 attached: function () { |
| 8643 if (this.if && this.ctor) { |
| 8644 this.async(this._ensureInstance); |
| 8645 } |
| 8646 }, |
| 8647 render: function () { |
| 8648 this._flushTemplates(); |
| 8649 }, |
| 8650 _render: function () { |
| 8651 if (this.if) { |
| 8652 if (!this.ctor) { |
| 8653 this.templatize(this); |
| 8654 } |
| 8655 this._ensureInstance(); |
| 8656 this._showHideChildren(); |
| 8657 } else if (this.restamp) { |
| 8658 this._teardownInstance(); |
| 8659 } |
| 8660 if (!this.restamp && this._instance) { |
| 8661 this._showHideChildren(); |
| 8662 } |
| 8663 if (this.if != this._lastIf) { |
| 8664 this.fire('dom-change'); |
| 8665 this._lastIf = this.if; |
| 8666 } |
| 8667 }, |
| 8668 _ensureInstance: function () { |
| 8669 if (!this._instance) { |
| 8670 this._instance = this.stamp(); |
| 8671 var root = this._instance.root; |
| 8672 var parent = Polymer.dom(Polymer.dom(this).parentNode); |
| 8673 parent.insertBefore(root, this); |
| 8674 } |
| 8675 }, |
| 8676 _teardownInstance: function () { |
| 8677 if (this._instance) { |
| 8678 var c = this._instance._children; |
| 8679 if (c) { |
| 8680 var parent = Polymer.dom(Polymer.dom(c[0]).parentNode); |
| 8681 c.forEach(function (n) { |
| 8682 parent.removeChild(n); |
| 8683 }); |
| 8684 } |
| 8685 this._instance = null; |
| 8686 } |
| 8687 }, |
| 8688 _showHideChildren: function () { |
| 8689 var hidden = this.__hideTemplateChildren__ || !this.if; |
| 8690 if (this._instance) { |
| 8691 this._instance._showHideChildren(hidden); |
| 8692 } |
| 8693 }, |
| 8694 _forwardParentProp: function (prop, value) { |
| 8695 if (this._instance) { |
| 8696 this._instance[prop] = value; |
| 8697 } |
| 8698 }, |
| 8699 _forwardParentPath: function (path, value) { |
| 8700 if (this._instance) { |
| 8701 this._instance.notifyPath(path, value, true); |
| 8702 } |
| 8703 } |
| 8704 }); |
| 8705 Polymer({ |
| 8706 is: 'dom-bind', |
| 8707 extends: 'template', |
| 8708 created: function () { |
| 8709 Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this)); |
| 8710 }, |
| 8711 _ensureReady: function () { |
| 8712 if (!this._readied) { |
| 8713 this._readySelf(); |
| 8714 } |
| 8715 }, |
| 8716 _markImportsReady: function () { |
| 8717 this._importsReady = true; |
| 8718 this._ensureReady(); |
| 8719 }, |
| 8720 _registerFeatures: function () { |
| 8721 this._prepConstructor(); |
| 8722 }, |
| 8723 _insertChildren: function () { |
| 8724 var parentDom = Polymer.dom(Polymer.dom(this).parentNode); |
| 8725 parentDom.insertBefore(this.root, this); |
| 8726 }, |
| 8727 _removeChildren: function () { |
| 8728 if (this._children) { |
| 8729 for (var i = 0; i < this._children.length; i++) { |
| 8730 this.root.appendChild(this._children[i]); |
| 8731 } |
| 8732 } |
| 8733 }, |
| 8734 _initFeatures: function () { |
| 8735 }, |
| 8736 _scopeElementClass: function (element, selector) { |
| 8737 if (this.dataHost) { |
| 8738 return this.dataHost._scopeElementClass(element, selector); |
| 8739 } else { |
| 8740 return selector; |
| 8741 } |
| 8742 }, |
| 8743 _prepConfigure: function () { |
| 8744 var config = {}; |
| 8745 for (var prop in this._propertyEffects) { |
| 8746 config[prop] = this[prop]; |
| 8747 } |
| 8748 this._setupConfigure = this._setupConfigure.bind(this, config); |
| 8749 }, |
| 8750 attached: function () { |
| 8751 if (this._importsReady) { |
| 8752 this.render(); |
| 8753 } |
| 8754 }, |
| 8755 detached: function () { |
| 8756 this._removeChildren(); |
| 8757 }, |
| 8758 render: function () { |
| 8759 this._ensureReady(); |
| 8760 if (!this._children) { |
| 8761 this._template = this; |
| 8762 this._prepAnnotations(); |
| 8763 this._prepEffects(); |
| 8764 this._prepBehaviors(); |
| 8765 this._prepConfigure(); |
| 8766 this._prepBindings(); |
| 8767 Polymer.Base._initFeatures.call(this); |
| 8768 this._children = Array.prototype.slice.call(this.root.childNodes); |
| 8769 } |
| 8770 this._insertChildren(); |
| 8771 this.fire('dom-change'); |
| 8772 } |
| 8773 }); |
| 8774 (function() { |
| 8775 |
| 8776 'use strict'; |
| 8777 |
| 8778 var SHADOW_WHEN_SCROLLING = 1; |
| 8779 var SHADOW_ALWAYS = 2; |
| 8780 |
| 8781 |
| 8782 var MODE_CONFIGS = { |
| 8783 |
| 8784 outerScroll: { |
| 8785 'scroll': true |
| 8786 }, |
| 8787 |
| 8788 shadowMode: { |
| 8789 'standard': SHADOW_ALWAYS, |
| 8790 'waterfall': SHADOW_WHEN_SCROLLING, |
| 8791 'waterfall-tall': SHADOW_WHEN_SCROLLING |
| 8792 }, |
| 8793 |
| 8794 tallMode: { |
| 8795 'waterfall-tall': true |
| 8796 } |
| 8797 }; |
| 8798 |
| 8799 Polymer({ |
| 8800 |
| 8801 is: 'paper-header-panel', |
| 8802 |
| 8803 /** |
| 8804 * Fired when the content has been scrolled. `event.detail.target` return
s |
| 8805 * the scrollable element which you can use to access scroll info such as |
| 8806 * `scrollTop`. |
| 8807 * |
| 8808 * <paper-header-panel on-content-scroll="scrollHandler"> |
| 8809 * ... |
| 8810 * </paper-header-panel> |
| 8811 * |
| 8812 * |
| 8813 * scrollHandler: function(event) { |
| 8814 * var scroller = event.detail.target; |
| 8815 * console.log(scroller.scrollTop); |
| 8816 * } |
| 8817 * |
| 8818 * @event content-scroll |
| 8819 */ |
| 8820 |
| 8821 properties: { |
| 8822 |
| 8823 /** |
| 8824 * Controls header and scrolling behavior. Options are |
| 8825 * `standard`, `seamed`, `waterfall`, `waterfall-tall`, `scroll` and |
| 8826 * `cover`. Default is `standard`. |
| 8827 * |
| 8828 * `standard`: The header is a step above the panel. The header will con
sume the |
| 8829 * panel at the point of entry, preventing it from passing through to th
e |
| 8830 * opposite side. |
| 8831 * |
| 8832 * `seamed`: The header is presented as seamed with the panel. |
| 8833 * |
| 8834 * `waterfall`: Similar to standard mode, but header is initially presen
ted as |
| 8835 * seamed with panel, but then separates to form the step. |
| 8836 * |
| 8837 * `waterfall-tall`: The header is initially taller (`tall` class is add
ed to |
| 8838 * the header). As the user scrolls, the header separates (forming an e
dge) |
| 8839 * while condensing (`tall` class is removed from the header). |
| 8840 * |
| 8841 * `scroll`: The header keeps its seam with the panel, and is pushed off
screen. |
| 8842 * |
| 8843 * `cover`: The panel covers the whole `paper-header-panel` including th
e |
| 8844 * header. This allows user to style the panel in such a way that the pa
nel is |
| 8845 * partially covering the header. |
| 8846 * |
| 8847 * <paper-header-panel mode="cover"> |
| 8848 * <paper-toolbar class="tall"> |
| 8849 * <core-icon-button icon="menu"></core-icon-button> |
| 8850 * </paper-toolbar> |
| 8851 * <div class="content"></div> |
| 8852 * </paper-header-panel> |
| 8853 */ |
| 8854 mode: { |
| 8855 type: String, |
| 8856 value: 'standard', |
| 8857 observer: '_modeChanged', |
| 8858 reflectToAttribute: true |
| 8859 }, |
| 8860 |
| 8861 /** |
| 8862 * If true, the drop-shadow is always shown no matter what mode is set t
o. |
| 8863 */ |
| 8864 shadow: { |
| 8865 type: Boolean, |
| 8866 value: false |
| 8867 }, |
| 8868 |
| 8869 /** |
| 8870 * The class used in waterfall-tall mode. Change this if the header |
| 8871 * accepts a different class for toggling height, e.g. "medium-tall" |
| 8872 */ |
| 8873 tallClass: { |
| 8874 type: String, |
| 8875 value: 'tall' |
| 8876 }, |
| 8877 |
| 8878 /** |
| 8879 * If true, the scroller is at the top |
| 8880 */ |
| 8881 atTop: { |
| 8882 type: Boolean, |
| 8883 value: true, |
| 8884 readOnly: true |
| 8885 } |
| 8886 }, |
| 8887 |
| 8888 observers: [ |
| 8889 '_computeDropShadowHidden(atTop, mode, shadow)' |
| 8890 ], |
| 8891 |
| 8892 ready: function() { |
| 8893 this.scrollHandler = this._scroll.bind(this); |
| 8894 this._addListener(); |
| 8895 |
| 8896 // Run `scroll` logic once to initialze class names, etc. |
| 8897 this._keepScrollingState(); |
| 8898 }, |
| 8899 |
| 8900 detached: function() { |
| 8901 this._removeListener(); |
| 8902 }, |
| 8903 |
| 8904 /** |
| 8905 * Returns the header element |
| 8906 * |
| 8907 * @property header |
| 8908 * @type Object |
| 8909 */ |
| 8910 get header() { |
| 8911 return Polymer.dom(this.$.headerContent).getDistributedNodes()[0]; |
| 8912 }, |
| 8913 |
| 8914 /** |
| 8915 * Returns the scrollable element. |
| 8916 * |
| 8917 * @property scroller |
| 8918 * @type Object |
| 8919 */ |
| 8920 get scroller() { |
| 8921 return this._getScrollerForMode(this.mode); |
| 8922 }, |
| 8923 |
| 8924 /** |
| 8925 * Returns true if the scroller has a visible shadow. |
| 8926 * |
| 8927 * @property visibleShadow |
| 8928 * @type Boolean |
| 8929 */ |
| 8930 get visibleShadow() { |
| 8931 return this.$.dropShadow.classList.contains('has-shadow'); |
| 8932 }, |
| 8933 |
| 8934 _computeDropShadowHidden: function(atTop, mode, shadow) { |
| 8935 |
| 8936 var shadowMode = MODE_CONFIGS.shadowMode[mode]; |
| 8937 |
| 8938 if (this.shadow) { |
| 8939 this.toggleClass('has-shadow', true, this.$.dropShadow); |
| 8940 |
| 8941 } else if (shadowMode === SHADOW_ALWAYS) { |
| 8942 this.toggleClass('has-shadow', true, this.$.dropShadow); |
| 8943 |
| 8944 } else if (shadowMode === SHADOW_WHEN_SCROLLING && !atTop) { |
| 8945 this.toggleClass('has-shadow', true, this.$.dropShadow); |
| 8946 |
| 8947 } else { |
| 8948 this.toggleClass('has-shadow', false, this.$.dropShadow); |
| 8949 |
| 8950 } |
| 8951 }, |
| 8952 |
| 8953 _computeMainContainerClass: function(mode) { |
| 8954 // TODO: It will be useful to have a utility for classes |
| 8955 // e.g. Polymer.Utils.classes({ foo: true }); |
| 8956 |
| 8957 var classes = {}; |
| 8958 |
| 8959 classes['flex'] = mode !== 'cover'; |
| 8960 |
| 8961 return Object.keys(classes).filter( |
| 8962 function(className) { |
| 8963 return classes[className]; |
| 8964 }).join(' '); |
| 8965 }, |
| 8966 |
| 8967 _addListener: function() { |
| 8968 this.scroller.addEventListener('scroll', this.scrollHandler, false); |
| 8969 }, |
| 8970 |
| 8971 _removeListener: function() { |
| 8972 this.scroller.removeEventListener('scroll', this.scrollHandler); |
| 8973 }, |
| 8974 |
| 8975 _modeChanged: function(newMode, oldMode) { |
| 8976 var configs = MODE_CONFIGS; |
| 8977 var header = this.header; |
| 8978 var animateDuration = 200; |
| 8979 |
| 8980 if (header) { |
| 8981 // in tallMode it may add tallClass to the header; so do the cleanup |
| 8982 // when mode is changed from tallMode to not tallMode |
| 8983 if (configs.tallMode[oldMode] && !configs.tallMode[newMode]) { |
| 8984 header.classList.remove(this.tallClass); |
| 8985 this.async(function() { |
| 8986 header.classList.remove('animate'); |
| 8987 }, animateDuration); |
| 8988 } else { |
| 8989 header.classList.toggle('animate', configs.tallMode[newMode]); |
| 8990 } |
| 8991 } |
| 8992 this._keepScrollingState(); |
| 8993 }, |
| 8994 |
| 8995 _keepScrollingState: function() { |
| 8996 var main = this.scroller; |
| 8997 var header = this.header; |
| 8998 |
| 8999 this._setAtTop(main.scrollTop === 0); |
| 9000 |
| 9001 if (header && this.tallClass && MODE_CONFIGS.tallMode[this.mode]) { |
| 9002 this.toggleClass(this.tallClass, this.atTop || |
| 9003 header.classList.contains(this.tallClass) && |
| 9004 main.scrollHeight < this.offsetHeight, header); |
| 9005 } |
| 9006 }, |
| 9007 |
| 9008 _scroll: function() { |
| 9009 this._keepScrollingState(); |
| 9010 this.fire('content-scroll', {target: this.scroller}, {bubbles: false}); |
| 9011 }, |
| 9012 |
| 9013 _getScrollerForMode: function(mode) { |
| 9014 return MODE_CONFIGS.outerScroll[mode] ? |
| 9015 this : this.$.mainContainer; |
| 9016 } |
| 9017 |
| 9018 }); |
| 9019 |
| 9020 })(); |
| 9021 Polymer({ |
| 9022 is: 'paper-material', |
| 9023 |
| 9024 properties: { |
| 9025 |
| 9026 /** |
| 9027 * The z-depth of this element, from 0-5. Setting to 0 will remove the |
| 9028 * shadow, and each increasing number greater than 0 will be "deeper" |
| 9029 * than the last. |
| 9030 * |
| 9031 * @attribute elevation |
| 9032 * @type number |
| 9033 * @default 1 |
| 9034 */ |
| 9035 elevation: { |
| 9036 type: Number, |
| 9037 reflectToAttribute: true, |
| 9038 value: 1 |
| 9039 }, |
| 9040 |
| 9041 /** |
| 9042 * Set this to true to animate the shadow when setting a new |
| 9043 * `elevation` value. |
| 9044 * |
| 9045 * @attribute animated |
| 9046 * @type boolean |
| 9047 * @default false |
| 9048 */ |
| 9049 animated: { |
| 9050 type: Boolean, |
| 9051 reflectToAttribute: true, |
| 9052 value: false |
| 9053 } |
| 9054 } |
| 9055 }); |
| 9056 (function() { |
| 9057 'use strict'; |
| 9058 |
| 9059 /** |
| 9060 * Chrome uses an older version of DOM Level 3 Keyboard Events |
| 9061 * |
| 9062 * Most keys are labeled as text, but some are Unicode codepoints. |
| 9063 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set |
| 9064 */ |
| 9065 var KEY_IDENTIFIER = { |
| 9066 'U+0009': 'tab', |
| 9067 'U+001B': 'esc', |
| 9068 'U+0020': 'space', |
| 9069 'U+002A': '*', |
| 9070 'U+0030': '0', |
| 9071 'U+0031': '1', |
| 9072 'U+0032': '2', |
| 9073 'U+0033': '3', |
| 9074 'U+0034': '4', |
| 9075 'U+0035': '5', |
| 9076 'U+0036': '6', |
| 9077 'U+0037': '7', |
| 9078 'U+0038': '8', |
| 9079 'U+0039': '9', |
| 9080 'U+0041': 'a', |
| 9081 'U+0042': 'b', |
| 9082 'U+0043': 'c', |
| 9083 'U+0044': 'd', |
| 9084 'U+0045': 'e', |
| 9085 'U+0046': 'f', |
| 9086 'U+0047': 'g', |
| 9087 'U+0048': 'h', |
| 9088 'U+0049': 'i', |
| 9089 'U+004A': 'j', |
| 9090 'U+004B': 'k', |
| 9091 'U+004C': 'l', |
| 9092 'U+004D': 'm', |
| 9093 'U+004E': 'n', |
| 9094 'U+004F': 'o', |
| 9095 'U+0050': 'p', |
| 9096 'U+0051': 'q', |
| 9097 'U+0052': 'r', |
| 9098 'U+0053': 's', |
| 9099 'U+0054': 't', |
| 9100 'U+0055': 'u', |
| 9101 'U+0056': 'v', |
| 9102 'U+0057': 'w', |
| 9103 'U+0058': 'x', |
| 9104 'U+0059': 'y', |
| 9105 'U+005A': 'z', |
| 9106 'U+007F': 'del' |
| 9107 }; |
| 9108 |
| 9109 /** |
| 9110 * Special table for KeyboardEvent.keyCode. |
| 9111 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er |
| 9112 * than that. |
| 9113 * |
| 9114 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode |
| 9115 */ |
| 9116 var KEY_CODE = { |
| 9117 9: 'tab', |
| 9118 13: 'enter', |
| 9119 27: 'esc', |
| 9120 33: 'pageup', |
| 9121 34: 'pagedown', |
| 9122 35: 'end', |
| 9123 36: 'home', |
| 9124 32: 'space', |
| 9125 37: 'left', |
| 9126 38: 'up', |
| 9127 39: 'right', |
| 9128 40: 'down', |
| 9129 46: 'del', |
| 9130 106: '*' |
| 9131 }; |
| 9132 |
| 9133 /** |
| 9134 * MODIFIER_KEYS maps the short name for modifier keys used in a key |
| 9135 * combo string to the property name that references those same keys |
| 9136 * in a KeyboardEvent instance. |
| 9137 */ |
| 9138 var MODIFIER_KEYS = { |
| 9139 'shift': 'shiftKey', |
| 9140 'ctrl': 'ctrlKey', |
| 9141 'alt': 'altKey', |
| 9142 'meta': 'metaKey' |
| 9143 }; |
| 9144 |
| 9145 /** |
| 9146 * KeyboardEvent.key is mostly represented by printable character made by |
| 9147 * the keyboard, with unprintable keys labeled nicely. |
| 9148 * |
| 9149 * However, on OS X, Alt+char can make a Unicode character that follows an |
| 9150 * Apple-specific mapping. In this case, we |
| 9151 * fall back to .keyCode. |
| 9152 */ |
| 9153 var KEY_CHAR = /[a-z0-9*]/; |
| 9154 |
| 9155 /** |
| 9156 * Matches a keyIdentifier string. |
| 9157 */ |
| 9158 var IDENT_CHAR = /U\+/; |
| 9159 |
| 9160 /** |
| 9161 * Matches arrow keys in Gecko 27.0+ |
| 9162 */ |
| 9163 var ARROW_KEY = /^arrow/; |
| 9164 |
| 9165 /** |
| 9166 * Matches space keys everywhere (notably including IE10's exceptional name |
| 9167 * `spacebar`). |
| 9168 */ |
| 9169 var SPACE_KEY = /^space(bar)?/; |
| 9170 |
| 9171 function transformKey(key) { |
| 9172 var validKey = ''; |
| 9173 if (key) { |
| 9174 var lKey = key.toLowerCase(); |
| 9175 if (lKey.length == 1) { |
| 9176 if (KEY_CHAR.test(lKey)) { |
| 9177 validKey = lKey; |
| 9178 } |
| 9179 } else if (ARROW_KEY.test(lKey)) { |
| 9180 validKey = lKey.replace('arrow', ''); |
| 9181 } else if (SPACE_KEY.test(lKey)) { |
| 9182 validKey = 'space'; |
| 9183 } else if (lKey == 'multiply') { |
| 9184 // numpad '*' can map to Multiply on IE/Windows |
| 9185 validKey = '*'; |
| 9186 } else { |
| 9187 validKey = lKey; |
| 9188 } |
| 9189 } |
| 9190 return validKey; |
| 9191 } |
| 9192 |
| 9193 function transformKeyIdentifier(keyIdent) { |
| 9194 var validKey = ''; |
| 9195 if (keyIdent) { |
| 9196 if (IDENT_CHAR.test(keyIdent)) { |
| 9197 validKey = KEY_IDENTIFIER[keyIdent]; |
| 9198 } else { |
| 9199 validKey = keyIdent.toLowerCase(); |
| 9200 } |
| 9201 } |
| 9202 return validKey; |
| 9203 } |
| 9204 |
| 9205 function transformKeyCode(keyCode) { |
| 9206 var validKey = ''; |
| 9207 if (Number(keyCode)) { |
| 9208 if (keyCode >= 65 && keyCode <= 90) { |
| 9209 // ascii a-z |
| 9210 // lowercase is 32 offset from uppercase |
| 9211 validKey = String.fromCharCode(32 + keyCode); |
| 9212 } else if (keyCode >= 112 && keyCode <= 123) { |
| 9213 // function keys f1-f12 |
| 9214 validKey = 'f' + (keyCode - 112); |
| 9215 } else if (keyCode >= 48 && keyCode <= 57) { |
| 9216 // top 0-9 keys |
| 9217 validKey = String(48 - keyCode); |
| 9218 } else if (keyCode >= 96 && keyCode <= 105) { |
| 9219 // num pad 0-9 |
| 9220 validKey = String(96 - keyCode); |
| 9221 } else { |
| 9222 validKey = KEY_CODE[keyCode]; |
| 9223 } |
| 9224 } |
| 9225 return validKey; |
| 9226 } |
| 9227 |
| 9228 function normalizedKeyForEvent(keyEvent) { |
| 9229 // fall back from .key, to .keyIdentifier, to .keyCode, and then to |
| 9230 // .detail.key to support artificial keyboard events |
| 9231 return transformKey(keyEvent.key) || |
| 9232 transformKeyIdentifier(keyEvent.keyIdentifier) || |
| 9233 transformKeyCode(keyEvent.keyCode) || |
| 9234 transformKey(keyEvent.detail.key) || ''; |
| 9235 } |
| 9236 |
| 9237 function keyComboMatchesEvent(keyCombo, keyEvent) { |
| 9238 return normalizedKeyForEvent(keyEvent) === keyCombo.key && |
| 9239 !!keyEvent.shiftKey === !!keyCombo.shiftKey && |
| 9240 !!keyEvent.ctrlKey === !!keyCombo.ctrlKey && |
| 9241 !!keyEvent.altKey === !!keyCombo.altKey && |
| 9242 !!keyEvent.metaKey === !!keyCombo.metaKey; |
| 9243 } |
| 9244 |
| 9245 function parseKeyComboString(keyComboString) { |
| 9246 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP
art) { |
| 9247 var eventParts = keyComboPart.split(':'); |
| 9248 var keyName = eventParts[0]; |
| 9249 var event = eventParts[1]; |
| 9250 |
| 9251 if (keyName in MODIFIER_KEYS) { |
| 9252 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true; |
| 9253 } else { |
| 9254 parsedKeyCombo.key = keyName; |
| 9255 parsedKeyCombo.event = event || 'keydown'; |
| 9256 } |
| 9257 |
| 9258 return parsedKeyCombo; |
| 9259 }, { |
| 9260 combo: keyComboString.split(':').shift() |
| 9261 }); |
| 9262 } |
| 9263 |
| 9264 function parseEventString(eventString) { |
| 9265 return eventString.split(' ').map(function(keyComboString) { |
| 9266 return parseKeyComboString(keyComboString); |
| 9267 }); |
| 9268 } |
| 9269 |
| 9270 |
| 9271 /** |
| 9272 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces
sing |
| 9273 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3
.org/TR/wai-aria-practices/#kbd_general_binding). |
| 9274 * The element takes care of browser differences with respect to Keyboard ev
ents |
| 9275 * and uses an expressive syntax to filter key presses. |
| 9276 * |
| 9277 * Use the `keyBindings` prototype property to express what combination of k
eys |
| 9278 * will trigger the event to fire. |
| 9279 * |
| 9280 * Use the `key-event-target` attribute to set up event handlers on a specif
ic |
| 9281 * node. |
| 9282 * The `keys-pressed` event will fire when one of the key combinations set w
ith the |
| 9283 * `keys` property is pressed. |
| 9284 * |
| 9285 * @demo demo/index.html |
| 9286 * @polymerBehavior |
| 9287 */ |
| 9288 Polymer.IronA11yKeysBehavior = { |
| 9289 properties: { |
| 9290 /** |
| 9291 * The HTMLElement that will be firing relevant KeyboardEvents. |
| 9292 */ |
| 9293 keyEventTarget: { |
| 9294 type: Object, |
| 9295 value: function() { |
| 9296 return this; |
| 9297 } |
| 9298 }, |
| 9299 |
| 9300 _boundKeyHandlers: { |
| 9301 type: Array, |
| 9302 value: function() { |
| 9303 return []; |
| 9304 } |
| 9305 }, |
| 9306 |
| 9307 // We use this due to a limitation in IE10 where instances will have |
| 9308 // own properties of everything on the "prototype". |
| 9309 _imperativeKeyBindings: { |
| 9310 type: Object, |
| 9311 value: function() { |
| 9312 return {}; |
| 9313 } |
| 9314 } |
| 9315 }, |
| 9316 |
| 9317 observers: [ |
| 9318 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' |
| 9319 ], |
| 9320 |
| 9321 keyBindings: {}, |
| 9322 |
| 9323 registered: function() { |
| 9324 this._prepKeyBindings(); |
| 9325 }, |
| 9326 |
| 9327 attached: function() { |
| 9328 this._listenKeyEventListeners(); |
| 9329 }, |
| 9330 |
| 9331 detached: function() { |
| 9332 this._unlistenKeyEventListeners(); |
| 9333 }, |
| 9334 |
| 9335 /** |
| 9336 * Can be used to imperatively add a key binding to the implementing |
| 9337 * element. This is the imperative equivalent of declaring a keybinding |
| 9338 * in the `keyBindings` prototype property. |
| 9339 */ |
| 9340 addOwnKeyBinding: function(eventString, handlerName) { |
| 9341 this._imperativeKeyBindings[eventString] = handlerName; |
| 9342 this._prepKeyBindings(); |
| 9343 this._resetKeyEventListeners(); |
| 9344 }, |
| 9345 |
| 9346 /** |
| 9347 * When called, will remove all imperatively-added key bindings. |
| 9348 */ |
| 9349 removeOwnKeyBindings: function() { |
| 9350 this._imperativeKeyBindings = {}; |
| 9351 this._prepKeyBindings(); |
| 9352 this._resetKeyEventListeners(); |
| 9353 }, |
| 9354 |
| 9355 keyboardEventMatchesKeys: function(event, eventString) { |
| 9356 var keyCombos = parseEventString(eventString); |
| 9357 var index; |
| 9358 |
| 9359 for (index = 0; index < keyCombos.length; ++index) { |
| 9360 if (keyComboMatchesEvent(keyCombos[index], event)) { |
| 9361 return true; |
| 9362 } |
| 9363 } |
| 9364 |
| 9365 return false; |
| 9366 }, |
| 9367 |
| 9368 _collectKeyBindings: function() { |
| 9369 var keyBindings = this.behaviors.map(function(behavior) { |
| 9370 return behavior.keyBindings; |
| 9371 }); |
| 9372 |
| 9373 if (keyBindings.indexOf(this.keyBindings) === -1) { |
| 9374 keyBindings.push(this.keyBindings); |
| 9375 } |
| 9376 |
| 9377 return keyBindings; |
| 9378 }, |
| 9379 |
| 9380 _prepKeyBindings: function() { |
| 9381 this._keyBindings = {}; |
| 9382 |
| 9383 this._collectKeyBindings().forEach(function(keyBindings) { |
| 9384 for (var eventString in keyBindings) { |
| 9385 this._addKeyBinding(eventString, keyBindings[eventString]); |
| 9386 } |
| 9387 }, this); |
| 9388 |
| 9389 for (var eventString in this._imperativeKeyBindings) { |
| 9390 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); |
| 9391 } |
| 9392 }, |
| 9393 |
| 9394 _addKeyBinding: function(eventString, handlerName) { |
| 9395 parseEventString(eventString).forEach(function(keyCombo) { |
| 9396 this._keyBindings[keyCombo.event] = |
| 9397 this._keyBindings[keyCombo.event] || []; |
| 9398 |
| 9399 this._keyBindings[keyCombo.event].push([ |
| 9400 keyCombo, |
| 9401 handlerName |
| 9402 ]); |
| 9403 }, this); |
| 9404 }, |
| 9405 |
| 9406 _resetKeyEventListeners: function() { |
| 9407 this._unlistenKeyEventListeners(); |
| 9408 |
| 9409 if (this.isAttached) { |
| 9410 this._listenKeyEventListeners(); |
| 9411 } |
| 9412 }, |
| 9413 |
| 9414 _listenKeyEventListeners: function() { |
| 9415 Object.keys(this._keyBindings).forEach(function(eventName) { |
| 9416 var keyBindings = this._keyBindings[eventName]; |
| 9417 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings); |
| 9418 |
| 9419 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH
andler]); |
| 9420 |
| 9421 this.keyEventTarget.addEventListener(eventName, boundKeyHandler); |
| 9422 }, this); |
| 9423 }, |
| 9424 |
| 9425 _unlistenKeyEventListeners: function() { |
| 9426 var keyHandlerTuple; |
| 9427 var keyEventTarget; |
| 9428 var eventName; |
| 9429 var boundKeyHandler; |
| 9430 |
| 9431 while (this._boundKeyHandlers.length) { |
| 9432 // My kingdom for block-scope binding and destructuring assignment.. |
| 9433 keyHandlerTuple = this._boundKeyHandlers.pop(); |
| 9434 keyEventTarget = keyHandlerTuple[0]; |
| 9435 eventName = keyHandlerTuple[1]; |
| 9436 boundKeyHandler = keyHandlerTuple[2]; |
| 9437 |
| 9438 keyEventTarget.removeEventListener(eventName, boundKeyHandler); |
| 9439 } |
| 9440 }, |
| 9441 |
| 9442 _onKeyBindingEvent: function(keyBindings, event) { |
| 9443 keyBindings.forEach(function(keyBinding) { |
| 9444 var keyCombo = keyBinding[0]; |
| 9445 var handlerName = keyBinding[1]; |
| 9446 |
| 9447 if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event))
{ |
| 9448 this._triggerKeyHandler(keyCombo, handlerName, event); |
| 9449 } |
| 9450 }, this); |
| 9451 }, |
| 9452 |
| 9453 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { |
| 9454 var detail = Object.create(keyCombo); |
| 9455 detail.keyboardEvent = keyboardEvent; |
| 9456 |
| 9457 this[handlerName].call(this, new CustomEvent(keyCombo.event, { |
| 9458 detail: detail |
| 9459 })); |
| 9460 } |
| 9461 }; |
| 9462 })(); |
| 9463 (function() { |
| 9464 var Utility = { |
| 9465 distance: function(x1, y1, x2, y2) { |
| 9466 var xDelta = (x1 - x2); |
| 9467 var yDelta = (y1 - y2); |
| 9468 |
| 9469 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); |
| 9470 }, |
| 9471 |
| 9472 now: window.performance && window.performance.now ? |
| 9473 window.performance.now.bind(window.performance) : Date.now |
| 9474 }; |
| 9475 |
| 9476 /** |
| 9477 * @param {HTMLElement} element |
| 9478 * @constructor |
| 9479 */ |
| 9480 function ElementMetrics(element) { |
| 9481 this.element = element; |
| 9482 this.width = this.boundingRect.width; |
| 9483 this.height = this.boundingRect.height; |
| 9484 |
| 9485 this.size = Math.max(this.width, this.height); |
| 9486 } |
| 9487 |
| 9488 ElementMetrics.prototype = { |
| 9489 get boundingRect () { |
| 9490 return this.element.getBoundingClientRect(); |
| 9491 }, |
| 9492 |
| 9493 furthestCornerDistanceFrom: function(x, y) { |
| 9494 var topLeft = Utility.distance(x, y, 0, 0); |
| 9495 var topRight = Utility.distance(x, y, this.width, 0); |
| 9496 var bottomLeft = Utility.distance(x, y, 0, this.height); |
| 9497 var bottomRight = Utility.distance(x, y, this.width, this.height); |
| 9498 |
| 9499 return Math.max(topLeft, topRight, bottomLeft, bottomRight); |
| 9500 } |
| 9501 }; |
| 9502 |
| 9503 /** |
| 9504 * @param {HTMLElement} element |
| 9505 * @constructor |
| 9506 */ |
| 9507 function Ripple(element) { |
| 9508 this.element = element; |
| 9509 this.color = window.getComputedStyle(element).color; |
| 9510 |
| 9511 this.wave = document.createElement('div'); |
| 9512 this.waveContainer = document.createElement('div'); |
| 9513 this.wave.style.backgroundColor = this.color; |
| 9514 this.wave.classList.add('wave'); |
| 9515 this.waveContainer.classList.add('wave-container'); |
| 9516 Polymer.dom(this.waveContainer).appendChild(this.wave); |
| 9517 |
| 9518 this.resetInteractionState(); |
| 9519 } |
| 9520 |
| 9521 Ripple.MAX_RADIUS = 300; |
| 9522 |
| 9523 Ripple.prototype = { |
| 9524 get recenters() { |
| 9525 return this.element.recenters; |
| 9526 }, |
| 9527 |
| 9528 get center() { |
| 9529 return this.element.center; |
| 9530 }, |
| 9531 |
| 9532 get mouseDownElapsed() { |
| 9533 var elapsed; |
| 9534 |
| 9535 if (!this.mouseDownStart) { |
| 9536 return 0; |
| 9537 } |
| 9538 |
| 9539 elapsed = Utility.now() - this.mouseDownStart; |
| 9540 |
| 9541 if (this.mouseUpStart) { |
| 9542 elapsed -= this.mouseUpElapsed; |
| 9543 } |
| 9544 |
| 9545 return elapsed; |
| 9546 }, |
| 9547 |
| 9548 get mouseUpElapsed() { |
| 9549 return this.mouseUpStart ? |
| 9550 Utility.now () - this.mouseUpStart : 0; |
| 9551 }, |
| 9552 |
| 9553 get mouseDownElapsedSeconds() { |
| 9554 return this.mouseDownElapsed / 1000; |
| 9555 }, |
| 9556 |
| 9557 get mouseUpElapsedSeconds() { |
| 9558 return this.mouseUpElapsed / 1000; |
| 9559 }, |
| 9560 |
| 9561 get mouseInteractionSeconds() { |
| 9562 return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds; |
| 9563 }, |
| 9564 |
| 9565 get initialOpacity() { |
| 9566 return this.element.initialOpacity; |
| 9567 }, |
| 9568 |
| 9569 get opacityDecayVelocity() { |
| 9570 return this.element.opacityDecayVelocity; |
| 9571 }, |
| 9572 |
| 9573 get radius() { |
| 9574 var width2 = this.containerMetrics.width * this.containerMetrics.width; |
| 9575 var height2 = this.containerMetrics.height * this.containerMetrics.heigh
t; |
| 9576 var waveRadius = Math.min( |
| 9577 Math.sqrt(width2 + height2), |
| 9578 Ripple.MAX_RADIUS |
| 9579 ) * 1.1 + 5; |
| 9580 |
| 9581 var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS); |
| 9582 var timeNow = this.mouseInteractionSeconds / duration; |
| 9583 var size = waveRadius * (1 - Math.pow(80, -timeNow)); |
| 9584 |
| 9585 return Math.abs(size); |
| 9586 }, |
| 9587 |
| 9588 get opacity() { |
| 9589 if (!this.mouseUpStart) { |
| 9590 return this.initialOpacity; |
| 9591 } |
| 9592 |
| 9593 return Math.max( |
| 9594 0, |
| 9595 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe
locity |
| 9596 ); |
| 9597 }, |
| 9598 |
| 9599 get outerOpacity() { |
| 9600 // Linear increase in background opacity, capped at the opacity |
| 9601 // of the wavefront (waveOpacity). |
| 9602 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; |
| 9603 var waveOpacity = this.opacity; |
| 9604 |
| 9605 return Math.max( |
| 9606 0, |
| 9607 Math.min(outerOpacity, waveOpacity) |
| 9608 ); |
| 9609 }, |
| 9610 |
| 9611 get isOpacityFullyDecayed() { |
| 9612 return this.opacity < 0.01 && |
| 9613 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); |
| 9614 }, |
| 9615 |
| 9616 get isRestingAtMaxRadius() { |
| 9617 return this.opacity >= this.initialOpacity && |
| 9618 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); |
| 9619 }, |
| 9620 |
| 9621 get isAnimationComplete() { |
| 9622 return this.mouseUpStart ? |
| 9623 this.isOpacityFullyDecayed : this.isRestingAtMaxRadius; |
| 9624 }, |
| 9625 |
| 9626 get translationFraction() { |
| 9627 return Math.min( |
| 9628 1, |
| 9629 this.radius / this.containerMetrics.size * 2 / Math.sqrt(2) |
| 9630 ); |
| 9631 }, |
| 9632 |
| 9633 get xNow() { |
| 9634 if (this.xEnd) { |
| 9635 return this.xStart + this.translationFraction * (this.xEnd - this.xSta
rt); |
| 9636 } |
| 9637 |
| 9638 return this.xStart; |
| 9639 }, |
| 9640 |
| 9641 get yNow() { |
| 9642 if (this.yEnd) { |
| 9643 return this.yStart + this.translationFraction * (this.yEnd - this.ySta
rt); |
| 9644 } |
| 9645 |
| 9646 return this.yStart; |
| 9647 }, |
| 9648 |
| 9649 get isMouseDown() { |
| 9650 return this.mouseDownStart && !this.mouseUpStart; |
| 9651 }, |
| 9652 |
| 9653 resetInteractionState: function() { |
| 9654 this.maxRadius = 0; |
| 9655 this.mouseDownStart = 0; |
| 9656 this.mouseUpStart = 0; |
| 9657 |
| 9658 this.xStart = 0; |
| 9659 this.yStart = 0; |
| 9660 this.xEnd = 0; |
| 9661 this.yEnd = 0; |
| 9662 this.slideDistance = 0; |
| 9663 |
| 9664 this.containerMetrics = new ElementMetrics(this.element); |
| 9665 }, |
| 9666 |
| 9667 draw: function() { |
| 9668 var scale; |
| 9669 var translateString; |
| 9670 var dx; |
| 9671 var dy; |
| 9672 |
| 9673 this.wave.style.opacity = this.opacity; |
| 9674 |
| 9675 scale = this.radius / (this.containerMetrics.size / 2); |
| 9676 dx = this.xNow - (this.containerMetrics.width / 2); |
| 9677 dy = this.yNow - (this.containerMetrics.height / 2); |
| 9678 |
| 9679 |
| 9680 // 2d transform for safari because of border-radius and overflow:hidden
clipping bug. |
| 9681 // https://bugs.webkit.org/show_bug.cgi?id=98538 |
| 9682 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' +
dy + 'px)'; |
| 9683 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy +
'px, 0)'; |
| 9684 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; |
| 9685 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; |
| 9686 }, |
| 9687 |
| 9688 /** @param {Event=} event */ |
| 9689 downAction: function(event) { |
| 9690 var xCenter = this.containerMetrics.width / 2; |
| 9691 var yCenter = this.containerMetrics.height / 2; |
| 9692 |
| 9693 this.resetInteractionState(); |
| 9694 this.mouseDownStart = Utility.now(); |
| 9695 |
| 9696 if (this.center) { |
| 9697 this.xStart = xCenter; |
| 9698 this.yStart = yCenter; |
| 9699 this.slideDistance = Utility.distance( |
| 9700 this.xStart, this.yStart, this.xEnd, this.yEnd |
| 9701 ); |
| 9702 } else { |
| 9703 this.xStart = event ? |
| 9704 event.detail.x - this.containerMetrics.boundingRect.left : |
| 9705 this.containerMetrics.width / 2; |
| 9706 this.yStart = event ? |
| 9707 event.detail.y - this.containerMetrics.boundingRect.top : |
| 9708 this.containerMetrics.height / 2; |
| 9709 } |
| 9710 |
| 9711 if (this.recenters) { |
| 9712 this.xEnd = xCenter; |
| 9713 this.yEnd = yCenter; |
| 9714 this.slideDistance = Utility.distance( |
| 9715 this.xStart, this.yStart, this.xEnd, this.yEnd |
| 9716 ); |
| 9717 } |
| 9718 |
| 9719 this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom( |
| 9720 this.xStart, |
| 9721 this.yStart |
| 9722 ); |
| 9723 |
| 9724 this.waveContainer.style.top = |
| 9725 (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px'
; |
| 9726 this.waveContainer.style.left = |
| 9727 (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px'; |
| 9728 |
| 9729 this.waveContainer.style.width = this.containerMetrics.size + 'px'; |
| 9730 this.waveContainer.style.height = this.containerMetrics.size + 'px'; |
| 9731 }, |
| 9732 |
| 9733 /** @param {Event=} event */ |
| 9734 upAction: function(event) { |
| 9735 if (!this.isMouseDown) { |
| 9736 return; |
| 9737 } |
| 9738 |
| 9739 this.mouseUpStart = Utility.now(); |
| 9740 }, |
| 9741 |
| 9742 remove: function() { |
| 9743 Polymer.dom(this.waveContainer.parentNode).removeChild( |
| 9744 this.waveContainer |
| 9745 ); |
| 9746 } |
| 9747 }; |
| 9748 |
| 9749 Polymer({ |
| 9750 is: 'paper-ripple', |
| 9751 |
| 9752 behaviors: [ |
| 9753 Polymer.IronA11yKeysBehavior |
| 9754 ], |
| 9755 |
| 9756 properties: { |
| 9757 /** |
| 9758 * The initial opacity set on the wave. |
| 9759 * |
| 9760 * @attribute initialOpacity |
| 9761 * @type number |
| 9762 * @default 0.25 |
| 9763 */ |
| 9764 initialOpacity: { |
| 9765 type: Number, |
| 9766 value: 0.25 |
| 9767 }, |
| 9768 |
| 9769 /** |
| 9770 * How fast (opacity per second) the wave fades out. |
| 9771 * |
| 9772 * @attribute opacityDecayVelocity |
| 9773 * @type number |
| 9774 * @default 0.8 |
| 9775 */ |
| 9776 opacityDecayVelocity: { |
| 9777 type: Number, |
| 9778 value: 0.8 |
| 9779 }, |
| 9780 |
| 9781 /** |
| 9782 * If true, ripples will exhibit a gravitational pull towards |
| 9783 * the center of their container as they fade away. |
| 9784 * |
| 9785 * @attribute recenters |
| 9786 * @type boolean |
| 9787 * @default false |
| 9788 */ |
| 9789 recenters: { |
| 9790 type: Boolean, |
| 9791 value: false |
| 9792 }, |
| 9793 |
| 9794 /** |
| 9795 * If true, ripples will center inside its container |
| 9796 * |
| 9797 * @attribute recenters |
| 9798 * @type boolean |
| 9799 * @default false |
| 9800 */ |
| 9801 center: { |
| 9802 type: Boolean, |
| 9803 value: false |
| 9804 }, |
| 9805 |
| 9806 /** |
| 9807 * A list of the visual ripples. |
| 9808 * |
| 9809 * @attribute ripples |
| 9810 * @type Array |
| 9811 * @default [] |
| 9812 */ |
| 9813 ripples: { |
| 9814 type: Array, |
| 9815 value: function() { |
| 9816 return []; |
| 9817 } |
| 9818 }, |
| 9819 |
| 9820 /** |
| 9821 * True when there are visible ripples animating within the |
| 9822 * element. |
| 9823 */ |
| 9824 animating: { |
| 9825 type: Boolean, |
| 9826 readOnly: true, |
| 9827 reflectToAttribute: true, |
| 9828 value: false |
| 9829 }, |
| 9830 |
| 9831 /** |
| 9832 * If true, the ripple will remain in the "down" state until `holdDown` |
| 9833 * is set to false again. |
| 9834 */ |
| 9835 holdDown: { |
| 9836 type: Boolean, |
| 9837 value: false, |
| 9838 observer: '_holdDownChanged' |
| 9839 }, |
| 9840 |
| 9841 _animating: { |
| 9842 type: Boolean |
| 9843 }, |
| 9844 |
| 9845 _boundAnimate: { |
| 9846 type: Function, |
| 9847 value: function() { |
| 9848 return this.animate.bind(this); |
| 9849 } |
| 9850 } |
| 9851 }, |
| 9852 |
| 9853 get target () { |
| 9854 var ownerRoot = Polymer.dom(this).getOwnerRoot(); |
| 9855 var target; |
| 9856 |
| 9857 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE |
| 9858 target = ownerRoot.host; |
| 9859 } else { |
| 9860 target = this.parentNode; |
| 9861 } |
| 9862 |
| 9863 return target; |
| 9864 }, |
| 9865 |
| 9866 keyBindings: { |
| 9867 'enter:keydown': '_onEnterKeydown', |
| 9868 'space:keydown': '_onSpaceKeydown', |
| 9869 'space:keyup': '_onSpaceKeyup' |
| 9870 }, |
| 9871 |
| 9872 attached: function() { |
| 9873 this.listen(this.target, 'up', 'upAction'); |
| 9874 this.listen(this.target, 'down', 'downAction'); |
| 9875 |
| 9876 if (!this.target.hasAttribute('noink')) { |
| 9877 this.keyEventTarget = this.target; |
| 9878 } |
| 9879 }, |
| 9880 |
| 9881 get shouldKeepAnimating () { |
| 9882 for (var index = 0; index < this.ripples.length; ++index) { |
| 9883 if (!this.ripples[index].isAnimationComplete) { |
| 9884 return true; |
| 9885 } |
| 9886 } |
| 9887 |
| 9888 return false; |
| 9889 }, |
| 9890 |
| 9891 simulatedRipple: function() { |
| 9892 this.downAction(null); |
| 9893 |
| 9894 // Please see polymer/polymer#1305 |
| 9895 this.async(function() { |
| 9896 this.upAction(); |
| 9897 }, 1); |
| 9898 }, |
| 9899 |
| 9900 /** @param {Event=} event */ |
| 9901 downAction: function(event) { |
| 9902 if (this.holdDown && this.ripples.length > 0) { |
| 9903 return; |
| 9904 } |
| 9905 |
| 9906 var ripple = this.addRipple(); |
| 9907 |
| 9908 ripple.downAction(event); |
| 9909 |
| 9910 if (!this._animating) { |
| 9911 this.animate(); |
| 9912 } |
| 9913 }, |
| 9914 |
| 9915 /** @param {Event=} event */ |
| 9916 upAction: function(event) { |
| 9917 if (this.holdDown) { |
| 9918 return; |
| 9919 } |
| 9920 |
| 9921 this.ripples.forEach(function(ripple) { |
| 9922 ripple.upAction(event); |
| 9923 }); |
| 9924 |
| 9925 this.animate(); |
| 9926 }, |
| 9927 |
| 9928 onAnimationComplete: function() { |
| 9929 this._animating = false; |
| 9930 this.$.background.style.backgroundColor = null; |
| 9931 this.fire('transitionend'); |
| 9932 }, |
| 9933 |
| 9934 addRipple: function() { |
| 9935 var ripple = new Ripple(this); |
| 9936 |
| 9937 Polymer.dom(this.$.waves).appendChild(ripple.waveContainer); |
| 9938 this.$.background.style.backgroundColor = ripple.color; |
| 9939 this.ripples.push(ripple); |
| 9940 |
| 9941 this._setAnimating(true); |
| 9942 |
| 9943 return ripple; |
| 9944 }, |
| 9945 |
| 9946 removeRipple: function(ripple) { |
| 9947 var rippleIndex = this.ripples.indexOf(ripple); |
| 9948 |
| 9949 if (rippleIndex < 0) { |
| 9950 return; |
| 9951 } |
| 9952 |
| 9953 this.ripples.splice(rippleIndex, 1); |
| 9954 |
| 9955 ripple.remove(); |
| 9956 |
| 9957 if (!this.ripples.length) { |
| 9958 this._setAnimating(false); |
| 9959 } |
| 9960 }, |
| 9961 |
| 9962 animate: function() { |
| 9963 var index; |
| 9964 var ripple; |
| 9965 |
| 9966 this._animating = true; |
| 9967 |
| 9968 for (index = 0; index < this.ripples.length; ++index) { |
| 9969 ripple = this.ripples[index]; |
| 9970 |
| 9971 ripple.draw(); |
| 9972 |
| 9973 this.$.background.style.opacity = ripple.outerOpacity; |
| 9974 |
| 9975 if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) { |
| 9976 this.removeRipple(ripple); |
| 9977 } |
| 9978 } |
| 9979 |
| 9980 if (!this.shouldKeepAnimating && this.ripples.length === 0) { |
| 9981 this.onAnimationComplete(); |
| 9982 } else { |
| 9983 window.requestAnimationFrame(this._boundAnimate); |
| 9984 } |
| 9985 }, |
| 9986 |
| 9987 _onEnterKeydown: function() { |
| 9988 this.downAction(); |
| 9989 this.async(this.upAction, 1); |
| 9990 }, |
| 9991 |
| 9992 _onSpaceKeydown: function() { |
| 9993 this.downAction(); |
| 9994 }, |
| 9995 |
| 9996 _onSpaceKeyup: function() { |
| 9997 this.upAction(); |
| 9998 }, |
| 9999 |
| 10000 _holdDownChanged: function(holdDown) { |
| 10001 if (holdDown) { |
| 10002 this.downAction(); |
| 10003 } else { |
| 10004 this.upAction(); |
| 10005 } |
| 10006 } |
| 10007 }); |
| 10008 })(); |
| 10009 /** |
| 10010 * @demo demo/index.html |
| 10011 * @polymerBehavior |
| 10012 */ |
| 10013 Polymer.IronControlState = { |
| 10014 |
| 10015 properties: { |
| 10016 |
| 10017 /** |
| 10018 * If true, the element currently has focus. |
| 10019 */ |
| 10020 focused: { |
| 10021 type: Boolean, |
| 10022 value: false, |
| 10023 notify: true, |
| 10024 readOnly: true, |
| 10025 reflectToAttribute: true |
| 10026 }, |
| 10027 |
| 10028 /** |
| 10029 * If true, the user cannot interact with this element. |
| 10030 */ |
| 10031 disabled: { |
| 10032 type: Boolean, |
| 10033 value: false, |
| 10034 notify: true, |
| 10035 observer: '_disabledChanged', |
| 10036 reflectToAttribute: true |
| 10037 }, |
| 10038 |
| 10039 _oldTabIndex: { |
| 10040 type: Number |
| 10041 }, |
| 10042 |
| 10043 _boundFocusBlurHandler: { |
| 10044 type: Function, |
| 10045 value: function() { |
| 10046 return this._focusBlurHandler.bind(this); |
| 10047 } |
| 10048 } |
| 10049 |
| 10050 }, |
| 10051 |
| 10052 observers: [ |
| 10053 '_changedControlState(focused, disabled)' |
| 10054 ], |
| 10055 |
| 10056 ready: function() { |
| 10057 this.addEventListener('focus', this._boundFocusBlurHandler, true); |
| 10058 this.addEventListener('blur', this._boundFocusBlurHandler, true); |
| 10059 }, |
| 10060 |
| 10061 _focusBlurHandler: function(event) { |
| 10062 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will |
| 10063 // eventually become `this` due to retargeting; if we are not in |
| 10064 // ShadowDOM land, `event.target` will eventually become `this` due |
| 10065 // to the second conditional which fires a synthetic event (that is also |
| 10066 // handled). In either case, we can disregard `event.path`. |
| 10067 |
| 10068 if (event.target === this) { |
| 10069 var focused = event.type === 'focus'; |
| 10070 this._setFocused(focused); |
| 10071 } else if (!this.shadowRoot) { |
| 10072 this.fire(event.type, {sourceEvent: event}, { |
| 10073 node: this, |
| 10074 bubbles: event.bubbles, |
| 10075 cancelable: event.cancelable |
| 10076 }); |
| 10077 } |
| 10078 }, |
| 10079 |
| 10080 _disabledChanged: function(disabled, old) { |
| 10081 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
| 10082 this.style.pointerEvents = disabled ? 'none' : ''; |
| 10083 if (disabled) { |
| 10084 this._oldTabIndex = this.tabIndex; |
| 10085 this.focused = false; |
| 10086 this.tabIndex = -1; |
| 10087 } else if (this._oldTabIndex !== undefined) { |
| 10088 this.tabIndex = this._oldTabIndex; |
| 10089 } |
| 10090 }, |
| 10091 |
| 10092 _changedControlState: function() { |
| 10093 // _controlStateChanged is abstract, follow-on behaviors may implement it |
| 10094 if (this._controlStateChanged) { |
| 10095 this._controlStateChanged(); |
| 10096 } |
| 10097 } |
| 10098 |
| 10099 }; |
| 10100 /** |
| 10101 * @demo demo/index.html |
| 10102 * @polymerBehavior Polymer.IronButtonState |
| 10103 */ |
| 10104 Polymer.IronButtonStateImpl = { |
| 10105 |
| 10106 properties: { |
| 10107 |
| 10108 /** |
| 10109 * If true, the user is currently holding down the button. |
| 10110 */ |
| 10111 pressed: { |
| 10112 type: Boolean, |
| 10113 readOnly: true, |
| 10114 value: false, |
| 10115 reflectToAttribute: true, |
| 10116 observer: '_pressedChanged' |
| 10117 }, |
| 10118 |
| 10119 /** |
| 10120 * If true, the button toggles the active state with each tap or press |
| 10121 * of the spacebar. |
| 10122 */ |
| 10123 toggles: { |
| 10124 type: Boolean, |
| 10125 value: false, |
| 10126 reflectToAttribute: true |
| 10127 }, |
| 10128 |
| 10129 /** |
| 10130 * If true, the button is a toggle and is currently in the active state. |
| 10131 */ |
| 10132 active: { |
| 10133 type: Boolean, |
| 10134 value: false, |
| 10135 notify: true, |
| 10136 reflectToAttribute: true |
| 10137 }, |
| 10138 |
| 10139 /** |
| 10140 * True if the element is currently being pressed by a "pointer," which |
| 10141 * is loosely defined as mouse or touch input (but specifically excluding |
| 10142 * keyboard input). |
| 10143 */ |
| 10144 pointerDown: { |
| 10145 type: Boolean, |
| 10146 readOnly: true, |
| 10147 value: false |
| 10148 }, |
| 10149 |
| 10150 /** |
| 10151 * True if the input device that caused the element to receive focus |
| 10152 * was a keyboard. |
| 10153 */ |
| 10154 receivedFocusFromKeyboard: { |
| 10155 type: Boolean, |
| 10156 readOnly: true |
| 10157 }, |
| 10158 |
| 10159 /** |
| 10160 * The aria attribute to be set if the button is a toggle and in the |
| 10161 * active state. |
| 10162 */ |
| 10163 ariaActiveAttribute: { |
| 10164 type: String, |
| 10165 value: 'aria-pressed', |
| 10166 observer: '_ariaActiveAttributeChanged' |
| 10167 } |
| 10168 }, |
| 10169 |
| 10170 listeners: { |
| 10171 down: '_downHandler', |
| 10172 up: '_upHandler', |
| 10173 tap: '_tapHandler' |
| 10174 }, |
| 10175 |
| 10176 observers: [ |
| 10177 '_detectKeyboardFocus(focused)', |
| 10178 '_activeChanged(active, ariaActiveAttribute)' |
| 10179 ], |
| 10180 |
| 10181 keyBindings: { |
| 10182 'enter:keydown': '_asyncClick', |
| 10183 'space:keydown': '_spaceKeyDownHandler', |
| 10184 'space:keyup': '_spaceKeyUpHandler', |
| 10185 }, |
| 10186 |
| 10187 _mouseEventRe: /^mouse/, |
| 10188 |
| 10189 _tapHandler: function() { |
| 10190 if (this.toggles) { |
| 10191 // a tap is needed to toggle the active state |
| 10192 this._userActivate(!this.active); |
| 10193 } else { |
| 10194 this.active = false; |
| 10195 } |
| 10196 }, |
| 10197 |
| 10198 _detectKeyboardFocus: function(focused) { |
| 10199 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); |
| 10200 }, |
| 10201 |
| 10202 // to emulate native checkbox, (de-)activations from a user interaction fire |
| 10203 // 'change' events |
| 10204 _userActivate: function(active) { |
| 10205 if (this.active !== active) { |
| 10206 this.active = active; |
| 10207 this.fire('change'); |
| 10208 } |
| 10209 }, |
| 10210 |
| 10211 _eventSourceIsPrimaryInput: function(event) { |
| 10212 event = event.detail.sourceEvent || event; |
| 10213 |
| 10214 // Always true for non-mouse events.... |
| 10215 if (!this._mouseEventRe.test(event.type)) { |
| 10216 return true; |
| 10217 } |
| 10218 |
| 10219 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons |
| 10220 if ('buttons' in event) { |
| 10221 return event.buttons === 1; |
| 10222 } |
| 10223 |
| 10224 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which |
| 10225 if (typeof event.which === 'number') { |
| 10226 return event.which < 2; |
| 10227 } |
| 10228 |
| 10229 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button |
| 10230 return event.button < 1; |
| 10231 }, |
| 10232 |
| 10233 _downHandler: function(event) { |
| 10234 if (!this._eventSourceIsPrimaryInput(event)) { |
| 10235 return; |
| 10236 } |
| 10237 |
| 10238 this._setPointerDown(true); |
| 10239 this._setPressed(true); |
| 10240 this._setReceivedFocusFromKeyboard(false); |
| 10241 }, |
| 10242 |
| 10243 _upHandler: function() { |
| 10244 this._setPointerDown(false); |
| 10245 this._setPressed(false); |
| 10246 }, |
| 10247 |
| 10248 _spaceKeyDownHandler: function(event) { |
| 10249 var keyboardEvent = event.detail.keyboardEvent; |
| 10250 keyboardEvent.preventDefault(); |
| 10251 keyboardEvent.stopImmediatePropagation(); |
| 10252 this._setPressed(true); |
| 10253 }, |
| 10254 |
| 10255 _spaceKeyUpHandler: function() { |
| 10256 if (this.pressed) { |
| 10257 this._asyncClick(); |
| 10258 } |
| 10259 this._setPressed(false); |
| 10260 }, |
| 10261 |
| 10262 // trigger click asynchronously, the asynchrony is useful to allow one |
| 10263 // event handler to unwind before triggering another event |
| 10264 _asyncClick: function() { |
| 10265 this.async(function() { |
| 10266 this.click(); |
| 10267 }, 1); |
| 10268 }, |
| 10269 |
| 10270 // any of these changes are considered a change to button state |
| 10271 |
| 10272 _pressedChanged: function(pressed) { |
| 10273 this._changedButtonState(); |
| 10274 }, |
| 10275 |
| 10276 _ariaActiveAttributeChanged: function(value, oldValue) { |
| 10277 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { |
| 10278 this.removeAttribute(oldValue); |
| 10279 } |
| 10280 }, |
| 10281 |
| 10282 _activeChanged: function(active, ariaActiveAttribute) { |
| 10283 if (this.toggles) { |
| 10284 this.setAttribute(this.ariaActiveAttribute, |
| 10285 active ? 'true' : 'false'); |
| 10286 } else { |
| 10287 this.removeAttribute(this.ariaActiveAttribute); |
| 10288 } |
| 10289 this._changedButtonState(); |
| 10290 }, |
| 10291 |
| 10292 _controlStateChanged: function() { |
| 10293 if (this.disabled) { |
| 10294 this._setPressed(false); |
| 10295 } else { |
| 10296 this._changedButtonState(); |
| 10297 } |
| 10298 }, |
| 10299 |
| 10300 // provide hook for follow-on behaviors to react to button-state |
| 10301 |
| 10302 _changedButtonState: function() { |
| 10303 if (this._buttonStateChanged) { |
| 10304 this._buttonStateChanged(); // abstract |
| 10305 } |
| 10306 } |
| 10307 |
| 10308 }; |
| 10309 |
| 10310 /** @polymerBehavior */ |
| 10311 Polymer.IronButtonState = [ |
| 10312 Polymer.IronA11yKeysBehavior, |
| 10313 Polymer.IronButtonStateImpl |
| 10314 ]; |
| 10315 /** @polymerBehavior */ |
| 10316 Polymer.PaperButtonBehaviorImpl = { |
| 10317 |
| 10318 properties: { |
| 10319 |
| 10320 _elevation: { |
| 10321 type: Number |
| 10322 } |
| 10323 |
| 10324 }, |
| 10325 |
| 10326 observers: [ |
| 10327 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom
Keyboard)' |
| 10328 ], |
| 10329 |
| 10330 hostAttributes: { |
| 10331 role: 'button', |
| 10332 tabindex: '0' |
| 10333 }, |
| 10334 |
| 10335 _calculateElevation: function() { |
| 10336 var e = 1; |
| 10337 if (this.disabled) { |
| 10338 e = 0; |
| 10339 } else if (this.active || this.pressed) { |
| 10340 e = 4; |
| 10341 } else if (this.receivedFocusFromKeyboard) { |
| 10342 e = 3; |
| 10343 } |
| 10344 this._elevation = e; |
| 10345 } |
| 10346 }; |
| 10347 |
| 10348 /** @polymerBehavior */ |
| 10349 Polymer.PaperButtonBehavior = [ |
| 10350 Polymer.IronButtonState, |
| 10351 Polymer.IronControlState, |
| 10352 Polymer.PaperButtonBehaviorImpl |
| 10353 ]; |
| 10354 Polymer({ |
| 10355 is: 'paper-button', |
| 10356 |
| 10357 behaviors: [ |
| 10358 Polymer.PaperButtonBehavior |
| 10359 ], |
| 10360 |
| 10361 properties: { |
| 10362 /** |
| 10363 * If true, the button should be styled with a shadow. |
| 10364 */ |
| 10365 raised: { |
| 10366 type: Boolean, |
| 10367 reflectToAttribute: true, |
| 10368 value: false, |
| 10369 observer: '_calculateElevation' |
| 10370 } |
| 10371 }, |
| 10372 |
| 10373 _calculateElevation: function() { |
| 10374 if (!this.raised) { |
| 10375 this._elevation = 0; |
| 10376 } else { |
| 10377 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); |
| 10378 } |
| 10379 }, |
| 10380 |
| 10381 _computeContentClass: function(receivedFocusFromKeyboard) { |
| 10382 var className = 'content '; |
| 10383 if (receivedFocusFromKeyboard) { |
| 10384 className += ' keyboard-focus'; |
| 10385 } |
| 10386 return className; |
| 10387 } |
| 10388 }); |
| 10389 /** |
| 10390 * `iron-range-behavior` provides the behavior for something with a minimum to m
aximum range. |
| 10391 * |
| 10392 * @demo demo/index.html |
| 10393 * @polymerBehavior |
| 10394 */ |
| 10395 Polymer.IronRangeBehavior = { |
| 10396 |
| 10397 properties: { |
| 10398 |
| 10399 /** |
| 10400 * The number that represents the current value. |
| 10401 */ |
| 10402 value: { |
| 10403 type: Number, |
| 10404 value: 0, |
| 10405 notify: true, |
| 10406 reflectToAttribute: true |
| 10407 }, |
| 10408 |
| 10409 /** |
| 10410 * The number that indicates the minimum value of the range. |
| 10411 */ |
| 10412 min: { |
| 10413 type: Number, |
| 10414 value: 0, |
| 10415 notify: true |
| 10416 }, |
| 10417 |
| 10418 /** |
| 10419 * The number that indicates the maximum value of the range. |
| 10420 */ |
| 10421 max: { |
| 10422 type: Number, |
| 10423 value: 100, |
| 10424 notify: true |
| 10425 }, |
| 10426 |
| 10427 /** |
| 10428 * Specifies the value granularity of the range's value. |
| 10429 */ |
| 10430 step: { |
| 10431 type: Number, |
| 10432 value: 1, |
| 10433 notify: true |
| 10434 }, |
| 10435 |
| 10436 /** |
| 10437 * Returns the ratio of the value. |
| 10438 */ |
| 10439 ratio: { |
| 10440 type: Number, |
| 10441 value: 0, |
| 10442 readOnly: true, |
| 10443 notify: true |
| 10444 }, |
| 10445 }, |
| 10446 |
| 10447 observers: [ |
| 10448 '_update(value, min, max, step)' |
| 10449 ], |
| 10450 |
| 10451 _calcRatio: function(value) { |
| 10452 return (this._clampValue(value) - this.min) / (this.max - this.min); |
| 10453 }, |
| 10454 |
| 10455 _clampValue: function(value) { |
| 10456 return Math.min(this.max, Math.max(this.min, this._calcStep(value))); |
| 10457 }, |
| 10458 |
| 10459 _calcStep: function(value) { |
| 10460 /** |
| 10461 * if we calculate the step using |
| 10462 * `Math.round(value / step) * step` we may hit a precision point issue |
| 10463 * eg. 0.1 * 0.2 = 0.020000000000000004 |
| 10464 * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html |
| 10465 * |
| 10466 * as a work around we can divide by the reciprocal of `step` |
| 10467 */ |
| 10468 // polymer/issues/2493 |
| 10469 value = parseFloat(value); |
| 10470 return this.step ? (Math.round((value + this.min) / this.step) / (1 / this.s
tep)) - this.min : value; |
| 10471 }, |
| 10472 |
| 10473 _validateValue: function() { |
| 10474 var v = this._clampValue(this.value); |
| 10475 this.value = this.oldValue = isNaN(v) ? this.oldValue : v; |
| 10476 return this.value !== v; |
| 10477 }, |
| 10478 |
| 10479 _update: function() { |
| 10480 this._validateValue(); |
| 10481 this._setRatio(this._calcRatio(this.value) * 100); |
| 10482 } |
| 10483 |
| 10484 }; |
| 10485 Polymer({ |
| 10486 |
| 10487 is: 'paper-progress', |
| 10488 |
| 10489 behaviors: [ |
| 10490 Polymer.IronRangeBehavior |
| 10491 ], |
| 10492 |
| 10493 properties: { |
| 10494 |
| 10495 /** |
| 10496 * The number that represents the current secondary progress. |
| 10497 */ |
| 10498 secondaryProgress: { |
| 10499 type: Number, |
| 10500 value: 0 |
| 10501 }, |
| 10502 |
| 10503 /** |
| 10504 * The secondary ratio |
| 10505 */ |
| 10506 secondaryRatio: { |
| 10507 type: Number, |
| 10508 value: 0, |
| 10509 readOnly: true |
| 10510 }, |
| 10511 |
| 10512 /** |
| 10513 * Use an indeterminate progress indicator. |
| 10514 */ |
| 10515 indeterminate: { |
| 10516 type: Boolean, |
| 10517 value: false, |
| 10518 observer: '_toggleIndeterminate' |
| 10519 }, |
| 10520 |
| 10521 /** |
| 10522 * True if the progress is disabled. |
| 10523 */ |
| 10524 disabled: { |
| 10525 type: Boolean, |
| 10526 value: false, |
| 10527 reflectToAttribute: true, |
| 10528 observer: '_disabledChanged' |
| 10529 } |
| 10530 }, |
| 10531 |
| 10532 observers: [ |
| 10533 '_progressChanged(secondaryProgress, value, min, max)' |
| 10534 ], |
| 10535 |
| 10536 hostAttributes: { |
| 10537 role: 'progressbar' |
| 10538 }, |
| 10539 |
| 10540 _toggleIndeterminate: function(indeterminate) { |
| 10541 // If we use attribute/class binding, the animation sometimes doesn't tran
slate properly |
| 10542 // on Safari 7.1. So instead, we toggle the class here in the update metho
d. |
| 10543 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress); |
| 10544 }, |
| 10545 |
| 10546 _transformProgress: function(progress, ratio) { |
| 10547 var transform = 'scaleX(' + (ratio / 100) + ')'; |
| 10548 progress.style.transform = progress.style.webkitTransform = transform; |
| 10549 }, |
| 10550 |
| 10551 _mainRatioChanged: function(ratio) { |
| 10552 this._transformProgress(this.$.primaryProgress, ratio); |
| 10553 }, |
| 10554 |
| 10555 _progressChanged: function(secondaryProgress, value, min, max) { |
| 10556 secondaryProgress = this._clampValue(secondaryProgress); |
| 10557 value = this._clampValue(value); |
| 10558 |
| 10559 var secondaryRatio = this._calcRatio(secondaryProgress) * 100; |
| 10560 var mainRatio = this._calcRatio(value) * 100; |
| 10561 |
| 10562 this._setSecondaryRatio(secondaryRatio); |
| 10563 this._transformProgress(this.$.secondaryProgress, secondaryRatio); |
| 10564 this._transformProgress(this.$.primaryProgress, mainRatio); |
| 10565 |
| 10566 this.secondaryProgress = secondaryProgress; |
| 10567 |
| 10568 this.setAttribute('aria-valuenow', value); |
| 10569 this.setAttribute('aria-valuemin', min); |
| 10570 this.setAttribute('aria-valuemax', max); |
| 10571 }, |
| 10572 |
| 10573 _disabledChanged: function(disabled) { |
| 10574 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
| 10575 }, |
| 10576 |
| 10577 _hideSecondaryProgress: function(secondaryRatio) { |
| 10578 return secondaryRatio === 0; |
| 10579 } |
| 10580 |
| 10581 }); |
| 10582 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 10583 // Use of this source code is governed by a BSD-style license that can be |
| 10584 // found in the LICENSE file. |
| 10585 |
| 10586 cr.define('downloads', function() { |
| 10587 var Item = Polymer({ |
| 10588 is: 'downloads-item', |
| 10589 |
| 10590 /** |
| 10591 * @param {!downloads.ThrottledIconLoader} iconLoader |
| 10592 */ |
| 10593 factoryImpl: function(iconLoader) { |
| 10594 /** @private {!downloads.ThrottledIconLoader} */ |
| 10595 this.iconLoader_ = iconLoader; |
| 10596 }, |
| 10597 |
| 10598 properties: { |
| 10599 data: { |
| 10600 type: Object, |
| 10601 }, |
| 10602 |
| 10603 hideDate: { |
| 10604 type: Boolean, |
| 10605 value: true, |
| 10606 }, |
| 10607 |
| 10608 readyPromise: { |
| 10609 type: Object, |
| 10610 value: function() { |
| 10611 return new Promise(function(resolve, reject) { |
| 10612 this.resolveReadyPromise_ = resolve; |
| 10613 }.bind(this)); |
| 10614 }, |
| 10615 }, |
| 10616 |
| 10617 completelyOnDisk_: { |
| 10618 computed: 'computeCompletelyOnDisk_(' + |
| 10619 'data.state, data.file_externally_removed)', |
| 10620 type: Boolean, |
| 10621 value: true, |
| 10622 }, |
| 10623 |
| 10624 controlledBy_: { |
| 10625 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)', |
| 10626 type: String, |
| 10627 value: '', |
| 10628 }, |
| 10629 |
| 10630 i18n_: { |
| 10631 readOnly: true, |
| 10632 type: Object, |
| 10633 value: function() { |
| 10634 return { |
| 10635 cancel: loadTimeData.getString('controlCancel'), |
| 10636 discard: loadTimeData.getString('dangerDiscard'), |
| 10637 pause: loadTimeData.getString('controlPause'), |
| 10638 remove: loadTimeData.getString('controlRemoveFromList'), |
| 10639 resume: loadTimeData.getString('controlResume'), |
| 10640 restore: loadTimeData.getString('dangerRestore'), |
| 10641 retry: loadTimeData.getString('controlRetry'), |
| 10642 save: loadTimeData.getString('dangerSave'), |
| 10643 }; |
| 10644 }, |
| 10645 }, |
| 10646 |
| 10647 isActive_: { |
| 10648 computed: 'computeIsActive_(' + |
| 10649 'data.state, data.file_externally_removed)', |
| 10650 type: Boolean, |
| 10651 value: true, |
| 10652 }, |
| 10653 |
| 10654 isDangerous_: { |
| 10655 computed: 'computeIsDangerous_(data.state)', |
| 10656 type: Boolean, |
| 10657 value: false, |
| 10658 }, |
| 10659 |
| 10660 isInProgress_: { |
| 10661 computed: 'computeIsInProgress_(data.state)', |
| 10662 type: Boolean, |
| 10663 value: false, |
| 10664 }, |
| 10665 |
| 10666 showCancel_: { |
| 10667 computed: 'computeShowCancel_(data.state)', |
| 10668 type: Boolean, |
| 10669 value: false, |
| 10670 }, |
| 10671 |
| 10672 showProgress_: { |
| 10673 computed: 'computeShowProgress_(showCancel_, data.percent)', |
| 10674 type: Boolean, |
| 10675 value: false, |
| 10676 }, |
| 10677 |
| 10678 isMalware_: { |
| 10679 computed: 'computeIsMalware_(isDangerous_, data.danger_type)', |
| 10680 type: Boolean, |
| 10681 value: false, |
| 10682 }, |
| 10683 }, |
| 10684 |
| 10685 observers: [ |
| 10686 // TODO(dbeam): this gets called way more when I observe data.by_ext_id |
| 10687 // and data.by_ext_name directly. Why? |
| 10688 'observeControlledBy_(controlledBy_)', |
| 10689 ], |
| 10690 |
| 10691 ready: function() { |
| 10692 this.content = this.$.content; |
| 10693 this.resolveReadyPromise_(); |
| 10694 }, |
| 10695 |
| 10696 /** @param {!downloads.Data} data */ |
| 10697 update: function(data) { |
| 10698 this.data = data; |
| 10699 |
| 10700 if (!this.isDangerous_) { |
| 10701 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path); |
| 10702 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon); |
| 10703 } |
| 10704 }, |
| 10705 |
| 10706 /** @private */ |
| 10707 computeClass_: function() { |
| 10708 var classes = []; |
| 10709 |
| 10710 if (this.isActive_) |
| 10711 classes.push('is-active'); |
| 10712 |
| 10713 if (this.isDangerous_) |
| 10714 classes.push('dangerous'); |
| 10715 |
| 10716 if (this.showProgress_) |
| 10717 classes.push('show-progress'); |
| 10718 |
| 10719 return classes.join(' '); |
| 10720 }, |
| 10721 |
| 10722 /** @private */ |
| 10723 computeCompletelyOnDisk_: function() { |
| 10724 return this.data.state == downloads.States.COMPLETE && |
| 10725 !this.data.file_externally_removed; |
| 10726 }, |
| 10727 |
| 10728 /** @private */ |
| 10729 computeControlledBy_: function() { |
| 10730 if (!this.data.by_ext_id || !this.data.by_ext_name) |
| 10731 return ''; |
| 10732 |
| 10733 var url = 'chrome://extensions#' + this.data.by_ext_id; |
| 10734 var name = this.data.by_ext_name; |
| 10735 return loadTimeData.getStringF('controlledByUrl', url, name); |
| 10736 }, |
| 10737 |
| 10738 /** @private */ |
| 10739 computeDate_: function() { |
| 10740 if (this.hideDate) |
| 10741 return ''; |
| 10742 return assert(this.data.since_string || this.data.date_string); |
| 10743 }, |
| 10744 |
| 10745 /** @private */ |
| 10746 computeDescription_: function() { |
| 10747 var data = this.data; |
| 10748 |
| 10749 switch (data.state) { |
| 10750 case downloads.States.DANGEROUS: |
| 10751 var fileName = data.file_name; |
| 10752 switch (data.danger_type) { |
| 10753 case downloads.DangerType.DANGEROUS_FILE: |
| 10754 return loadTimeData.getStringF('dangerFileDesc', fileName); |
| 10755 case downloads.DangerType.DANGEROUS_URL: |
| 10756 return loadTimeData.getString('dangerUrlDesc'); |
| 10757 case downloads.DangerType.DANGEROUS_CONTENT: // Fall through. |
| 10758 case downloads.DangerType.DANGEROUS_HOST: |
| 10759 return loadTimeData.getStringF('dangerContentDesc', fileName); |
| 10760 case downloads.DangerType.UNCOMMON_CONTENT: |
| 10761 return loadTimeData.getStringF('dangerUncommonDesc', fileName); |
| 10762 case downloads.DangerType.POTENTIALLY_UNWANTED: |
| 10763 return loadTimeData.getStringF('dangerSettingsDesc', fileName); |
| 10764 } |
| 10765 break; |
| 10766 |
| 10767 case downloads.States.IN_PROGRESS: |
| 10768 case downloads.States.PAUSED: // Fallthrough. |
| 10769 return data.progress_status_text; |
| 10770 } |
| 10771 |
| 10772 return ''; |
| 10773 }, |
| 10774 |
| 10775 /** @private */ |
| 10776 computeIsActive_: function() { |
| 10777 return this.data.state != downloads.States.CANCELLED && |
| 10778 this.data.state != downloads.States.INTERRUPTED && |
| 10779 !this.data.file_externally_removed; |
| 10780 }, |
| 10781 |
| 10782 /** @private */ |
| 10783 computeIsDangerous_: function() { |
| 10784 return this.data.state == downloads.States.DANGEROUS; |
| 10785 }, |
| 10786 |
| 10787 /** @private */ |
| 10788 computeIsInProgress_: function() { |
| 10789 return this.data.state == downloads.States.IN_PROGRESS; |
| 10790 }, |
| 10791 |
| 10792 /** @private */ |
| 10793 computeIsMalware_: function() { |
| 10794 return this.isDangerous_ && |
| 10795 (this.data.danger_type == downloads.DangerType.DANGEROUS_CONTENT || |
| 10796 this.data.danger_type == downloads.DangerType.DANGEROUS_HOST || |
| 10797 this.data.danger_type == downloads.DangerType.DANGEROUS_URL || |
| 10798 this.data.danger_type == downloads.DangerType.POTENTIALLY_UNWANTED); |
| 10799 }, |
| 10800 |
| 10801 /** @private */ |
| 10802 computeRemoveStyle_: function() { |
| 10803 var canDelete = loadTimeData.getBoolean('allowDeletingHistory'); |
| 10804 var hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete; |
| 10805 return hideRemove ? 'visibility: hidden' : ''; |
| 10806 }, |
| 10807 |
| 10808 /** @private */ |
| 10809 computeShowCancel_: function() { |
| 10810 return this.data.state == downloads.States.IN_PROGRESS || |
| 10811 this.data.state == downloads.States.PAUSED; |
| 10812 }, |
| 10813 |
| 10814 /** @private */ |
| 10815 computeShowProgress_: function() { |
| 10816 return this.showCancel_ && this.data.percent >= -1; |
| 10817 }, |
| 10818 |
| 10819 /** @private */ |
| 10820 computeTag_: function() { |
| 10821 switch (this.data.state) { |
| 10822 case downloads.States.CANCELLED: |
| 10823 return loadTimeData.getString('statusCancelled'); |
| 10824 |
| 10825 case downloads.States.INTERRUPTED: |
| 10826 return this.data.last_reason_text; |
| 10827 |
| 10828 case downloads.States.COMPLETE: |
| 10829 return this.data.file_externally_removed ? |
| 10830 loadTimeData.getString('statusRemoved') : ''; |
| 10831 } |
| 10832 |
| 10833 return ''; |
| 10834 }, |
| 10835 |
| 10836 /** @private */ |
| 10837 isIndeterminate_: function() { |
| 10838 return this.data.percent == -1; |
| 10839 }, |
| 10840 |
| 10841 /** @private */ |
| 10842 observeControlledBy_: function() { |
| 10843 this.$['controlled-by'].innerHTML = this.controlledBy_; |
| 10844 }, |
| 10845 |
| 10846 /** @private */ |
| 10847 onCancelClick_: function() { |
| 10848 downloads.ActionService.getInstance().cancel(this.data.id); |
| 10849 }, |
| 10850 |
| 10851 /** @private */ |
| 10852 onDiscardDangerous_: function() { |
| 10853 downloads.ActionService.getInstance().discardDangerous(this.data.id); |
| 10854 }, |
| 10855 |
| 10856 /** |
| 10857 * @private |
| 10858 * @param {Event} e |
| 10859 */ |
| 10860 onDragStart_: function(e) { |
| 10861 e.preventDefault(); |
| 10862 downloads.ActionService.getInstance().drag(this.data.id); |
| 10863 }, |
| 10864 |
| 10865 /** |
| 10866 * @param {Event} e |
| 10867 * @private |
| 10868 */ |
| 10869 onFileLinkClick_: function(e) { |
| 10870 e.preventDefault(); |
| 10871 downloads.ActionService.getInstance().openFile(this.data.id); |
| 10872 }, |
| 10873 |
| 10874 /** @private */ |
| 10875 onPauseClick_: function() { |
| 10876 downloads.ActionService.getInstance().pause(this.data.id); |
| 10877 }, |
| 10878 |
| 10879 /** @private */ |
| 10880 onRemoveClick_: function() { |
| 10881 downloads.ActionService.getInstance().remove(this.data.id); |
| 10882 }, |
| 10883 |
| 10884 /** @private */ |
| 10885 onResumeClick_: function() { |
| 10886 downloads.ActionService.getInstance().resume(this.data.id); |
| 10887 }, |
| 10888 |
| 10889 /** @private */ |
| 10890 onRetryClick_: function() { |
| 10891 downloads.ActionService.getInstance().download(this.data.url); |
| 10892 }, |
| 10893 |
| 10894 /** @private */ |
| 10895 onSaveDangerous_: function() { |
| 10896 downloads.ActionService.getInstance().saveDangerous(this.data.id); |
| 10897 }, |
| 10898 |
| 10899 /** @private */ |
| 10900 onShowClick_: function() { |
| 10901 downloads.ActionService.getInstance().show(this.data.id); |
| 10902 }, |
| 10903 }); |
| 10904 |
| 10905 return {Item: Item}; |
| 10906 }); |
| 10907 (function() { |
| 10908 |
| 10909 // monostate data |
| 10910 var metaDatas = {}; |
| 10911 var metaArrays = {}; |
| 10912 |
| 10913 Polymer.IronMeta = Polymer({ |
| 10914 |
| 10915 is: 'iron-meta', |
| 10916 |
| 10917 properties: { |
| 10918 |
| 10919 /** |
| 10920 * The type of meta-data. All meta-data of the same type is stored |
| 10921 * together. |
| 10922 */ |
| 10923 type: { |
| 10924 type: String, |
| 10925 value: 'default', |
| 10926 observer: '_typeChanged' |
| 10927 }, |
| 10928 |
| 10929 /** |
| 10930 * The key used to store `value` under the `type` namespace. |
| 10931 */ |
| 10932 key: { |
| 10933 type: String, |
| 10934 observer: '_keyChanged' |
| 10935 }, |
| 10936 |
| 10937 /** |
| 10938 * The meta-data to store or retrieve. |
| 10939 */ |
| 10940 value: { |
| 10941 type: Object, |
| 10942 notify: true, |
| 10943 observer: '_valueChanged' |
| 10944 }, |
| 10945 |
| 10946 /** |
| 10947 * If true, `value` is set to the iron-meta instance itself. |
| 10948 */ |
| 10949 self: { |
| 10950 type: Boolean, |
| 10951 observer: '_selfChanged' |
| 10952 }, |
| 10953 |
| 10954 /** |
| 10955 * Array of all meta-data values for the given type. |
| 10956 */ |
| 10957 list: { |
| 10958 type: Array, |
| 10959 notify: true |
| 10960 } |
| 10961 |
| 10962 }, |
| 10963 |
| 10964 /** |
| 10965 * Only runs if someone invokes the factory/constructor directly |
| 10966 * e.g. `new Polymer.IronMeta()` |
| 10967 */ |
| 10968 factoryImpl: function(config) { |
| 10969 if (config) { |
| 10970 for (var n in config) { |
| 10971 switch(n) { |
| 10972 case 'type': |
| 10973 case 'key': |
| 10974 case 'value': |
| 10975 this[n] = config[n]; |
| 10976 break; |
| 10977 } |
| 10978 } |
| 10979 } |
| 10980 }, |
| 10981 |
| 10982 created: function() { |
| 10983 // TODO(sjmiles): good for debugging? |
| 10984 this._metaDatas = metaDatas; |
| 10985 this._metaArrays = metaArrays; |
| 10986 }, |
| 10987 |
| 10988 _keyChanged: function(key, old) { |
| 10989 this._resetRegistration(old); |
| 10990 }, |
| 10991 |
| 10992 _valueChanged: function(value) { |
| 10993 this._resetRegistration(this.key); |
| 10994 }, |
| 10995 |
| 10996 _selfChanged: function(self) { |
| 10997 if (self) { |
| 10998 this.value = this; |
| 10999 } |
| 11000 }, |
| 11001 |
| 11002 _typeChanged: function(type) { |
| 11003 this._unregisterKey(this.key); |
| 11004 if (!metaDatas[type]) { |
| 11005 metaDatas[type] = {}; |
| 11006 } |
| 11007 this._metaData = metaDatas[type]; |
| 11008 if (!metaArrays[type]) { |
| 11009 metaArrays[type] = []; |
| 11010 } |
| 11011 this.list = metaArrays[type]; |
| 11012 this._registerKeyValue(this.key, this.value); |
| 11013 }, |
| 11014 |
| 11015 /** |
| 11016 * Retrieves meta data value by key. |
| 11017 * |
| 11018 * @method byKey |
| 11019 * @param {string} key The key of the meta-data to be returned. |
| 11020 * @return {*} |
| 11021 */ |
| 11022 byKey: function(key) { |
| 11023 return this._metaData && this._metaData[key]; |
| 11024 }, |
| 11025 |
| 11026 _resetRegistration: function(oldKey) { |
| 11027 this._unregisterKey(oldKey); |
| 11028 this._registerKeyValue(this.key, this.value); |
| 11029 }, |
| 11030 |
| 11031 _unregisterKey: function(key) { |
| 11032 this._unregister(key, this._metaData, this.list); |
| 11033 }, |
| 11034 |
| 11035 _registerKeyValue: function(key, value) { |
| 11036 this._register(key, value, this._metaData, this.list); |
| 11037 }, |
| 11038 |
| 11039 _register: function(key, value, data, list) { |
| 11040 if (key && data && value !== undefined) { |
| 11041 data[key] = value; |
| 11042 list.push(value); |
| 11043 } |
| 11044 }, |
| 11045 |
| 11046 _unregister: function(key, data, list) { |
| 11047 if (key && data) { |
| 11048 if (key in data) { |
| 11049 var value = data[key]; |
| 11050 delete data[key]; |
| 11051 this.arrayDelete(list, value); |
| 11052 } |
| 11053 } |
| 11054 } |
| 11055 |
| 11056 }); |
| 11057 |
| 11058 /** |
| 11059 `iron-meta-query` can be used to access infomation stored in `iron-meta`. |
| 11060 |
| 11061 Examples: |
| 11062 |
| 11063 If I create an instance like this: |
| 11064 |
| 11065 <iron-meta key="info" value="foo/bar"></iron-meta> |
| 11066 |
| 11067 Note that value="foo/bar" is the metadata I've defined. I could define more |
| 11068 attributes or use child nodes to define additional metadata. |
| 11069 |
| 11070 Now I can access that element (and it's metadata) from any `iron-meta-query`
instance: |
| 11071 |
| 11072 var value = new Polymer.IronMetaQuery({key: 'info'}).value; |
| 11073 |
| 11074 @group Polymer Iron Elements |
| 11075 @element iron-meta-query |
| 11076 */ |
| 11077 Polymer.IronMetaQuery = Polymer({ |
| 11078 |
| 11079 is: 'iron-meta-query', |
| 11080 |
| 11081 properties: { |
| 11082 |
| 11083 /** |
| 11084 * The type of meta-data. All meta-data of the same type is stored |
| 11085 * together. |
| 11086 */ |
| 11087 type: { |
| 11088 type: String, |
| 11089 value: 'default', |
| 11090 observer: '_typeChanged' |
| 11091 }, |
| 11092 |
| 11093 /** |
| 11094 * Specifies a key to use for retrieving `value` from the `type` |
| 11095 * namespace. |
| 11096 */ |
| 11097 key: { |
| 11098 type: String, |
| 11099 observer: '_keyChanged' |
| 11100 }, |
| 11101 |
| 11102 /** |
| 11103 * The meta-data to store or retrieve. |
| 11104 */ |
| 11105 value: { |
| 11106 type: Object, |
| 11107 notify: true, |
| 11108 readOnly: true |
| 11109 }, |
| 11110 |
| 11111 /** |
| 11112 * Array of all meta-data values for the given type. |
| 11113 */ |
| 11114 list: { |
| 11115 type: Array, |
| 11116 notify: true |
| 11117 } |
| 11118 |
| 11119 }, |
| 11120 |
| 11121 /** |
| 11122 * Actually a factory method, not a true constructor. Only runs if |
| 11123 * someone invokes it directly (via `new Polymer.IronMeta()`); |
| 11124 */ |
| 11125 factoryImpl: function(config) { |
| 11126 if (config) { |
| 11127 for (var n in config) { |
| 11128 switch(n) { |
| 11129 case 'type': |
| 11130 case 'key': |
| 11131 this[n] = config[n]; |
| 11132 break; |
| 11133 } |
| 11134 } |
| 11135 } |
| 11136 }, |
| 11137 |
| 11138 created: function() { |
| 11139 // TODO(sjmiles): good for debugging? |
| 11140 this._metaDatas = metaDatas; |
| 11141 this._metaArrays = metaArrays; |
| 11142 }, |
| 11143 |
| 11144 _keyChanged: function(key) { |
| 11145 this._setValue(this._metaData && this._metaData[key]); |
| 11146 }, |
| 11147 |
| 11148 _typeChanged: function(type) { |
| 11149 this._metaData = metaDatas[type]; |
| 11150 this.list = metaArrays[type]; |
| 11151 if (this.key) { |
| 11152 this._keyChanged(this.key); |
| 11153 } |
| 11154 }, |
| 11155 |
| 11156 /** |
| 11157 * Retrieves meta data value by key. |
| 11158 * @param {string} key The key of the meta-data to be returned. |
| 11159 * @return {*} |
| 11160 */ |
| 11161 byKey: function(key) { |
| 11162 return this._metaData && this._metaData[key]; |
| 11163 } |
| 11164 |
| 11165 }); |
| 11166 |
| 11167 })(); |
| 11168 Polymer({ |
| 11169 |
| 11170 is: 'iron-icon', |
| 11171 |
| 11172 properties: { |
| 11173 |
| 11174 /** |
| 11175 * The name of the icon to use. The name should be of the form: |
| 11176 * `iconset_name:icon_name`. |
| 11177 */ |
| 11178 icon: { |
| 11179 type: String, |
| 11180 observer: '_iconChanged' |
| 11181 }, |
| 11182 |
| 11183 /** |
| 11184 * The name of the theme to used, if one is specified by the |
| 11185 * iconset. |
| 11186 */ |
| 11187 theme: { |
| 11188 type: String, |
| 11189 observer: '_updateIcon' |
| 11190 }, |
| 11191 |
| 11192 /** |
| 11193 * If using iron-icon without an iconset, you can set the src to be |
| 11194 * the URL of an individual icon image file. Note that this will take |
| 11195 * precedence over a given icon attribute. |
| 11196 */ |
| 11197 src: { |
| 11198 type: String, |
| 11199 observer: '_srcChanged' |
| 11200 }, |
| 11201 |
| 11202 /** |
| 11203 * @type {!Polymer.IronMeta} |
| 11204 */ |
| 11205 _meta: { |
| 11206 value: Polymer.Base.create('iron-meta', {type: 'iconset'}) |
| 11207 } |
| 11208 |
| 11209 }, |
| 11210 |
| 11211 _DEFAULT_ICONSET: 'icons', |
| 11212 |
| 11213 _iconChanged: function(icon) { |
| 11214 var parts = (icon || '').split(':'); |
| 11215 this._iconName = parts.pop(); |
| 11216 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET; |
| 11217 this._updateIcon(); |
| 11218 }, |
| 11219 |
| 11220 _srcChanged: function(src) { |
| 11221 this._updateIcon(); |
| 11222 }, |
| 11223 |
| 11224 _usesIconset: function() { |
| 11225 return this.icon || !this.src; |
| 11226 }, |
| 11227 |
| 11228 /** @suppress {visibility} */ |
| 11229 _updateIcon: function() { |
| 11230 if (this._usesIconset()) { |
| 11231 if (this._iconsetName) { |
| 11232 this._iconset = /** @type {?Polymer.Iconset} */ ( |
| 11233 this._meta.byKey(this._iconsetName)); |
| 11234 if (this._iconset) { |
| 11235 this._iconset.applyIcon(this, this._iconName, this.theme); |
| 11236 this.unlisten(window, 'iron-iconset-added', '_updateIcon'); |
| 11237 } else { |
| 11238 this.listen(window, 'iron-iconset-added', '_updateIcon'); |
| 11239 } |
| 11240 } |
| 11241 } else { |
| 11242 if (!this._img) { |
| 11243 this._img = document.createElement('img'); |
| 11244 this._img.style.width = '100%'; |
| 11245 this._img.style.height = '100%'; |
| 11246 this._img.draggable = false; |
| 11247 } |
| 11248 this._img.src = this.src; |
| 11249 Polymer.dom(this.root).appendChild(this._img); |
| 11250 } |
| 11251 } |
| 11252 |
| 11253 }); |
| 11254 /** |
| 11255 * The `iron-iconset-svg` element allows users to define their own icon sets |
| 11256 * that contain svg icons. The svg icon elements should be children of the |
| 11257 * `iron-iconset-svg` element. Multiple icons should be given distinct id's. |
| 11258 * |
| 11259 * Using svg elements to create icons has a few advantages over traditional |
| 11260 * bitmap graphics like jpg or png. Icons that use svg are vector based so the
y |
| 11261 * are resolution independent and should look good on any device. They are |
| 11262 * stylable via css. Icons can be themed, colorized, and even animated. |
| 11263 * |
| 11264 * Example: |
| 11265 * |
| 11266 * <iron-iconset-svg name="my-svg-icons" size="24"> |
| 11267 * <svg> |
| 11268 * <defs> |
| 11269 * <g id="shape"> |
| 11270 * <rect x="50" y="50" width="50" height="50" /> |
| 11271 * <circle cx="50" cy="50" r="50" /> |
| 11272 * </g> |
| 11273 * </defs> |
| 11274 * </svg> |
| 11275 * </iron-iconset-svg> |
| 11276 * |
| 11277 * This will automatically register the icon set "my-svg-icons" to the iconset |
| 11278 * database. To use these icons from within another element, make a |
| 11279 * `iron-iconset` element and call the `byId` method |
| 11280 * to retrieve a given iconset. To apply a particular icon inside an |
| 11281 * element use the `applyIcon` method. For example: |
| 11282 * |
| 11283 * iconset.applyIcon(iconNode, 'car'); |
| 11284 * |
| 11285 * @element iron-iconset-svg |
| 11286 * @demo demo/index.html |
| 11287 */ |
| 11288 Polymer({ |
| 11289 |
| 11290 is: 'iron-iconset-svg', |
| 11291 |
| 11292 properties: { |
| 11293 |
| 11294 /** |
| 11295 * The name of the iconset. |
| 11296 * |
| 11297 * @attribute name |
| 11298 * @type string |
| 11299 */ |
| 11300 name: { |
| 11301 type: String, |
| 11302 observer: '_nameChanged' |
| 11303 }, |
| 11304 |
| 11305 /** |
| 11306 * The size of an individual icon. Note that icons must be square. |
| 11307 * |
| 11308 * @attribute iconSize |
| 11309 * @type number |
| 11310 * @default 24 |
| 11311 */ |
| 11312 size: { |
| 11313 type: Number, |
| 11314 value: 24 |
| 11315 } |
| 11316 |
| 11317 }, |
| 11318 |
| 11319 /** |
| 11320 * Construct an array of all icon names in this iconset. |
| 11321 * |
| 11322 * @return {!Array} Array of icon names. |
| 11323 */ |
| 11324 getIconNames: function() { |
| 11325 this._icons = this._createIconMap(); |
| 11326 return Object.keys(this._icons).map(function(n) { |
| 11327 return this.name + ':' + n; |
| 11328 }, this); |
| 11329 }, |
| 11330 |
| 11331 /** |
| 11332 * Applies an icon to the given element. |
| 11333 * |
| 11334 * An svg icon is prepended to the element's shadowRoot if it exists, |
| 11335 * otherwise to the element itself. |
| 11336 * |
| 11337 * @method applyIcon |
| 11338 * @param {Element} element Element to which the icon is applied. |
| 11339 * @param {string} iconName Name of the icon to apply. |
| 11340 * @return {Element} The svg element which renders the icon. |
| 11341 */ |
| 11342 applyIcon: function(element, iconName) { |
| 11343 // insert svg element into shadow root, if it exists |
| 11344 element = element.root || element; |
| 11345 // Remove old svg element |
| 11346 this.removeIcon(element); |
| 11347 // install new svg element |
| 11348 var svg = this._cloneIcon(iconName); |
| 11349 if (svg) { |
| 11350 var pde = Polymer.dom(element); |
| 11351 pde.insertBefore(svg, pde.childNodes[0]); |
| 11352 return element._svgIcon = svg; |
| 11353 } |
| 11354 return null; |
| 11355 }, |
| 11356 |
| 11357 /** |
| 11358 * Remove an icon from the given element by undoing the changes effected |
| 11359 * by `applyIcon`. |
| 11360 * |
| 11361 * @param {Element} element The element from which the icon is removed. |
| 11362 */ |
| 11363 removeIcon: function(element) { |
| 11364 // Remove old svg element |
| 11365 if (element._svgIcon) { |
| 11366 Polymer.dom(element).removeChild(element._svgIcon); |
| 11367 element._svgIcon = null; |
| 11368 } |
| 11369 }, |
| 11370 |
| 11371 /** |
| 11372 * |
| 11373 * When name is changed, register iconset metadata |
| 11374 * |
| 11375 */ |
| 11376 _nameChanged: function() { |
| 11377 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); |
| 11378 this.async(function() { |
| 11379 this.fire('iron-iconset-added', this, {node: window}); |
| 11380 }); |
| 11381 }, |
| 11382 |
| 11383 /** |
| 11384 * Create a map of child SVG elements by id. |
| 11385 * |
| 11386 * @return {!Object} Map of id's to SVG elements. |
| 11387 */ |
| 11388 _createIconMap: function() { |
| 11389 // Objects chained to Object.prototype (`{}`) have members. Specifically, |
| 11390 // on FF there is a `watch` method that confuses the icon map, so we |
| 11391 // need to use a null-based object here. |
| 11392 var icons = Object.create(null); |
| 11393 Polymer.dom(this).querySelectorAll('[id]') |
| 11394 .forEach(function(icon) { |
| 11395 icons[icon.id] = icon; |
| 11396 }); |
| 11397 return icons; |
| 11398 }, |
| 11399 |
| 11400 /** |
| 11401 * Produce installable clone of the SVG element matching `id` in this |
| 11402 * iconset, or `undefined` if there is no matching element. |
| 11403 * |
| 11404 * @return {Element} Returns an installable clone of the SVG element |
| 11405 * matching `id`. |
| 11406 */ |
| 11407 _cloneIcon: function(id) { |
| 11408 // create the icon map on-demand, since the iconset itself has no discrete |
| 11409 // signal to know when it's children are fully parsed |
| 11410 this._icons = this._icons || this._createIconMap(); |
| 11411 return this._prepareSvgClone(this._icons[id], this.size); |
| 11412 }, |
| 11413 |
| 11414 /** |
| 11415 * @param {Element} sourceSvg |
| 11416 * @param {number} size |
| 11417 * @return {Element} |
| 11418 */ |
| 11419 _prepareSvgClone: function(sourceSvg, size) { |
| 11420 if (sourceSvg) { |
| 11421 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
| 11422 svg.setAttribute('viewBox', ['0', '0', size, size].join(' ')); |
| 11423 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
| 11424 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/
370136 |
| 11425 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a
shadow-root |
| 11426 svg.style.cssText = 'pointer-events: none; display: block; width: 100%;
height: 100%;'; |
| 11427 svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id'); |
| 11428 return svg; |
| 11429 } |
| 11430 return null; |
| 11431 } |
| 11432 |
| 11433 }); |
| 11434 Polymer({ |
| 11435 is: 'paper-item', |
| 11436 |
| 11437 hostAttributes: { |
| 11438 role: 'listitem', |
| 11439 tabindex: '0' |
| 11440 }, |
| 11441 |
| 11442 behaviors: [ |
| 11443 Polymer.IronControlState, |
| 11444 Polymer.IronButtonState |
| 11445 ] |
| 11446 }); |
| 11447 /** |
| 11448 * @param {!Function} selectCallback |
| 11449 * @constructor |
| 11450 */ |
| 11451 Polymer.IronSelection = function(selectCallback) { |
| 11452 this.selection = []; |
| 11453 this.selectCallback = selectCallback; |
| 11454 }; |
| 11455 |
| 11456 Polymer.IronSelection.prototype = { |
| 11457 |
| 11458 /** |
| 11459 * Retrieves the selected item(s). |
| 11460 * |
| 11461 * @method get |
| 11462 * @returns Returns the selected item(s). If the multi property is true, |
| 11463 * `get` will return an array, otherwise it will return |
| 11464 * the selected item or undefined if there is no selection. |
| 11465 */ |
| 11466 get: function() { |
| 11467 return this.multi ? this.selection.slice() : this.selection[0]; |
| 11468 }, |
| 11469 |
| 11470 /** |
| 11471 * Clears all the selection except the ones indicated. |
| 11472 * |
| 11473 * @method clear |
| 11474 * @param {Array} excludes items to be excluded. |
| 11475 */ |
| 11476 clear: function(excludes) { |
| 11477 this.selection.slice().forEach(function(item) { |
| 11478 if (!excludes || excludes.indexOf(item) < 0) { |
| 11479 this.setItemSelected(item, false); |
| 11480 } |
| 11481 }, this); |
| 11482 }, |
| 11483 |
| 11484 /** |
| 11485 * Indicates if a given item is selected. |
| 11486 * |
| 11487 * @method isSelected |
| 11488 * @param {*} item The item whose selection state should be checked. |
| 11489 * @returns Returns true if `item` is selected. |
| 11490 */ |
| 11491 isSelected: function(item) { |
| 11492 return this.selection.indexOf(item) >= 0; |
| 11493 }, |
| 11494 |
| 11495 /** |
| 11496 * Sets the selection state for a given item to either selected or deselecte
d. |
| 11497 * |
| 11498 * @method setItemSelected |
| 11499 * @param {*} item The item to select. |
| 11500 * @param {boolean} isSelected True for selected, false for deselected. |
| 11501 */ |
| 11502 setItemSelected: function(item, isSelected) { |
| 11503 if (item != null) { |
| 11504 if (isSelected) { |
| 11505 this.selection.push(item); |
| 11506 } else { |
| 11507 var i = this.selection.indexOf(item); |
| 11508 if (i >= 0) { |
| 11509 this.selection.splice(i, 1); |
| 11510 } |
| 11511 } |
| 11512 if (this.selectCallback) { |
| 11513 this.selectCallback(item, isSelected); |
| 11514 } |
| 11515 } |
| 11516 }, |
| 11517 |
| 11518 /** |
| 11519 * Sets the selection state for a given item. If the `multi` property |
| 11520 * is true, then the selected state of `item` will be toggled; otherwise |
| 11521 * the `item` will be selected. |
| 11522 * |
| 11523 * @method select |
| 11524 * @param {*} item The item to select. |
| 11525 */ |
| 11526 select: function(item) { |
| 11527 if (this.multi) { |
| 11528 this.toggle(item); |
| 11529 } else if (this.get() !== item) { |
| 11530 this.setItemSelected(this.get(), false); |
| 11531 this.setItemSelected(item, true); |
| 11532 } |
| 11533 }, |
| 11534 |
| 11535 /** |
| 11536 * Toggles the selection state for `item`. |
| 11537 * |
| 11538 * @method toggle |
| 11539 * @param {*} item The item to toggle. |
| 11540 */ |
| 11541 toggle: function(item) { |
| 11542 this.setItemSelected(item, !this.isSelected(item)); |
| 11543 } |
| 11544 |
| 11545 }; |
| 11546 /** @polymerBehavior */ |
| 11547 Polymer.IronSelectableBehavior = { |
| 11548 |
| 11549 /** |
| 11550 * Fired when iron-selector is activated (selected or deselected). |
| 11551 * It is fired before the selected items are changed. |
| 11552 * Cancel the event to abort selection. |
| 11553 * |
| 11554 * @event iron-activate |
| 11555 */ |
| 11556 |
| 11557 /** |
| 11558 * Fired when an item is selected |
| 11559 * |
| 11560 * @event iron-select |
| 11561 */ |
| 11562 |
| 11563 /** |
| 11564 * Fired when an item is deselected |
| 11565 * |
| 11566 * @event iron-deselect |
| 11567 */ |
| 11568 |
| 11569 /** |
| 11570 * Fired when the list of selectable items changes (e.g., items are |
| 11571 * added or removed). The detail of the event is a list of mutation |
| 11572 * records that describe what changed. |
| 11573 * |
| 11574 * @event iron-items-changed |
| 11575 */ |
| 11576 |
| 11577 properties: { |
| 11578 |
| 11579 /** |
| 11580 * If you want to use the attribute value of an element for `selected` ins
tead of the index, |
| 11581 * set this to the name of the attribute. |
| 11582 */ |
| 11583 attrForSelected: { |
| 11584 type: String, |
| 11585 value: null |
| 11586 }, |
| 11587 |
| 11588 /** |
| 11589 * Gets or sets the selected element. The default is to use the index of t
he item. |
| 11590 */ |
| 11591 selected: { |
| 11592 type: String, |
| 11593 notify: true |
| 11594 }, |
| 11595 |
| 11596 /** |
| 11597 * Returns the currently selected item. |
| 11598 */ |
| 11599 selectedItem: { |
| 11600 type: Object, |
| 11601 readOnly: true, |
| 11602 notify: true |
| 11603 }, |
| 11604 |
| 11605 /** |
| 11606 * The event that fires from items when they are selected. Selectable |
| 11607 * will listen for this event from items and update the selection state. |
| 11608 * Set to empty string to listen to no events. |
| 11609 */ |
| 11610 activateEvent: { |
| 11611 type: String, |
| 11612 value: 'tap', |
| 11613 observer: '_activateEventChanged' |
| 11614 }, |
| 11615 |
| 11616 /** |
| 11617 * This is a CSS selector string. If this is set, only items that match t
he CSS selector |
| 11618 * are selectable. |
| 11619 */ |
| 11620 selectable: String, |
| 11621 |
| 11622 /** |
| 11623 * The class to set on elements when selected. |
| 11624 */ |
| 11625 selectedClass: { |
| 11626 type: String, |
| 11627 value: 'iron-selected' |
| 11628 }, |
| 11629 |
| 11630 /** |
| 11631 * The attribute to set on elements when selected. |
| 11632 */ |
| 11633 selectedAttribute: { |
| 11634 type: String, |
| 11635 value: null |
| 11636 }, |
| 11637 |
| 11638 /** |
| 11639 * The set of excluded elements where the key is the `localName` |
| 11640 * of the element that will be ignored from the item list. |
| 11641 * |
| 11642 * @type {object} |
| 11643 * @default {template: 1} |
| 11644 */ |
| 11645 excludedLocalNames: { |
| 11646 type: Object, |
| 11647 value: function() { |
| 11648 return { |
| 11649 'template': 1 |
| 11650 }; |
| 11651 } |
| 11652 } |
| 11653 }, |
| 11654 |
| 11655 observers: [ |
| 11656 '_updateSelected(attrForSelected, selected)' |
| 11657 ], |
| 11658 |
| 11659 created: function() { |
| 11660 this._bindFilterItem = this._filterItem.bind(this); |
| 11661 this._selection = new Polymer.IronSelection(this._applySelection.bind(this
)); |
| 11662 }, |
| 11663 |
| 11664 attached: function() { |
| 11665 this._observer = this._observeItems(this); |
| 11666 this._contentObserver = this._observeContent(this); |
| 11667 if (!this.selectedItem && this.selected) { |
| 11668 this._updateSelected(this.attrForSelected,this.selected) |
| 11669 } |
| 11670 }, |
| 11671 |
| 11672 detached: function() { |
| 11673 if (this._observer) { |
| 11674 this._observer.disconnect(); |
| 11675 } |
| 11676 if (this._contentObserver) { |
| 11677 this._contentObserver.disconnect(); |
| 11678 } |
| 11679 this._removeListener(this.activateEvent); |
| 11680 }, |
| 11681 |
| 11682 /** |
| 11683 * Returns an array of selectable items. |
| 11684 * |
| 11685 * @property items |
| 11686 * @type Array |
| 11687 */ |
| 11688 get items() { |
| 11689 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable ||
'*'); |
| 11690 return Array.prototype.filter.call(nodes, this._bindFilterItem); |
| 11691 }, |
| 11692 |
| 11693 /** |
| 11694 * Returns the index of the given item. |
| 11695 * |
| 11696 * @method indexOf |
| 11697 * @param {Object} item |
| 11698 * @returns Returns the index of the item |
| 11699 */ |
| 11700 indexOf: function(item) { |
| 11701 return this.items.indexOf(item); |
| 11702 }, |
| 11703 |
| 11704 /** |
| 11705 * Selects the given value. |
| 11706 * |
| 11707 * @method select |
| 11708 * @param {string} value the value to select. |
| 11709 */ |
| 11710 select: function(value) { |
| 11711 this.selected = value; |
| 11712 }, |
| 11713 |
| 11714 /** |
| 11715 * Selects the previous item. |
| 11716 * |
| 11717 * @method selectPrevious |
| 11718 */ |
| 11719 selectPrevious: function() { |
| 11720 var length = this.items.length; |
| 11721 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len
gth; |
| 11722 this.selected = this._indexToValue(index); |
| 11723 }, |
| 11724 |
| 11725 /** |
| 11726 * Selects the next item. |
| 11727 * |
| 11728 * @method selectNext |
| 11729 */ |
| 11730 selectNext: function() { |
| 11731 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l
ength; |
| 11732 this.selected = this._indexToValue(index); |
| 11733 }, |
| 11734 |
| 11735 _addListener: function(eventName) { |
| 11736 this.listen(this, eventName, '_activateHandler'); |
| 11737 }, |
| 11738 |
| 11739 _removeListener: function(eventName) { |
| 11740 this.unlisten(this, eventName, '_activateHandler'); |
| 11741 }, |
| 11742 |
| 11743 _activateEventChanged: function(eventName, old) { |
| 11744 this._removeListener(old); |
| 11745 this._addListener(eventName); |
| 11746 }, |
| 11747 |
| 11748 _updateSelected: function() { |
| 11749 this._selectSelected(this.selected); |
| 11750 }, |
| 11751 |
| 11752 _selectSelected: function(selected) { |
| 11753 this._selection.select(this._valueToItem(this.selected)); |
| 11754 }, |
| 11755 |
| 11756 _filterItem: function(node) { |
| 11757 return !this.excludedLocalNames[node.localName]; |
| 11758 }, |
| 11759 |
| 11760 _valueToItem: function(value) { |
| 11761 return (value == null) ? null : this.items[this._valueToIndex(value)]; |
| 11762 }, |
| 11763 |
| 11764 _valueToIndex: function(value) { |
| 11765 if (this.attrForSelected) { |
| 11766 for (var i = 0, item; item = this.items[i]; i++) { |
| 11767 if (this._valueForItem(item) == value) { |
| 11768 return i; |
| 11769 } |
| 11770 } |
| 11771 } else { |
| 11772 return Number(value); |
| 11773 } |
| 11774 }, |
| 11775 |
| 11776 _indexToValue: function(index) { |
| 11777 if (this.attrForSelected) { |
| 11778 var item = this.items[index]; |
| 11779 if (item) { |
| 11780 return this._valueForItem(item); |
| 11781 } |
| 11782 } else { |
| 11783 return index; |
| 11784 } |
| 11785 }, |
| 11786 |
| 11787 _valueForItem: function(item) { |
| 11788 return item[this.attrForSelected] || item.getAttribute(this.attrForSelecte
d); |
| 11789 }, |
| 11790 |
| 11791 _applySelection: function(item, isSelected) { |
| 11792 if (this.selectedClass) { |
| 11793 this.toggleClass(this.selectedClass, isSelected, item); |
| 11794 } |
| 11795 if (this.selectedAttribute) { |
| 11796 this.toggleAttribute(this.selectedAttribute, isSelected, item); |
| 11797 } |
| 11798 this._selectionChange(); |
| 11799 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); |
| 11800 }, |
| 11801 |
| 11802 _selectionChange: function() { |
| 11803 this._setSelectedItem(this._selection.get()); |
| 11804 }, |
| 11805 |
| 11806 // observe content changes under the given node. |
| 11807 _observeContent: function(node) { |
| 11808 var content = node.querySelector('content'); |
| 11809 if (content && content.parentElement === node) { |
| 11810 return this._observeItems(node.domHost); |
| 11811 } |
| 11812 }, |
| 11813 |
| 11814 // observe items change under the given node. |
| 11815 _observeItems: function(node) { |
| 11816 // TODO(cdata): Update this when we get distributed children changed. |
| 11817 var observer = new MutationObserver(function(mutations) { |
| 11818 // Let other interested parties know about the change so that |
| 11819 // we don't have to recreate mutation observers everywher. |
| 11820 this.fire('iron-items-changed', mutations, { |
| 11821 bubbles: false, |
| 11822 cancelable: false |
| 11823 }); |
| 11824 |
| 11825 if (this.selected != null) { |
| 11826 this._updateSelected(); |
| 11827 } |
| 11828 }.bind(this)); |
| 11829 observer.observe(node, { |
| 11830 childList: true, |
| 11831 subtree: true |
| 11832 }); |
| 11833 return observer; |
| 11834 }, |
| 11835 |
| 11836 _activateHandler: function(e) { |
| 11837 var t = e.target; |
| 11838 var items = this.items; |
| 11839 while (t && t != this) { |
| 11840 var i = items.indexOf(t); |
| 11841 if (i >= 0) { |
| 11842 var value = this._indexToValue(i); |
| 11843 this._itemActivate(value, t); |
| 11844 return; |
| 11845 } |
| 11846 t = t.parentNode; |
| 11847 } |
| 11848 }, |
| 11849 |
| 11850 _itemActivate: function(value, item) { |
| 11851 if (!this.fire('iron-activate', |
| 11852 {selected: value, item: item}, {cancelable: true}).defaultPrevented) { |
| 11853 this.select(value); |
| 11854 } |
| 11855 } |
| 11856 |
| 11857 }; |
| 11858 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ |
| 11859 Polymer.IronMultiSelectableBehaviorImpl = { |
| 11860 properties: { |
| 11861 |
| 11862 /** |
| 11863 * If true, multiple selections are allowed. |
| 11864 */ |
| 11865 multi: { |
| 11866 type: Boolean, |
| 11867 value: false, |
| 11868 observer: 'multiChanged' |
| 11869 }, |
| 11870 |
| 11871 /** |
| 11872 * Gets or sets the selected elements. This is used instead of `selected`
when `multi` |
| 11873 * is true. |
| 11874 */ |
| 11875 selectedValues: { |
| 11876 type: Array, |
| 11877 notify: true |
| 11878 }, |
| 11879 |
| 11880 /** |
| 11881 * Returns an array of currently selected items. |
| 11882 */ |
| 11883 selectedItems: { |
| 11884 type: Array, |
| 11885 readOnly: true, |
| 11886 notify: true |
| 11887 }, |
| 11888 |
| 11889 }, |
| 11890 |
| 11891 observers: [ |
| 11892 '_updateSelected(attrForSelected, selectedValues)' |
| 11893 ], |
| 11894 |
| 11895 /** |
| 11896 * Selects the given value. If the `multi` property is true, then the select
ed state of the |
| 11897 * `value` will be toggled; otherwise the `value` will be selected. |
| 11898 * |
| 11899 * @method select |
| 11900 * @param {string} value the value to select. |
| 11901 */ |
| 11902 select: function(value) { |
| 11903 if (this.multi) { |
| 11904 if (this.selectedValues) { |
| 11905 this._toggleSelected(value); |
| 11906 } else { |
| 11907 this.selectedValues = [value]; |
| 11908 } |
| 11909 } else { |
| 11910 this.selected = value; |
| 11911 } |
| 11912 }, |
| 11913 |
| 11914 multiChanged: function(multi) { |
| 11915 this._selection.multi = multi; |
| 11916 }, |
| 11917 |
| 11918 _updateSelected: function() { |
| 11919 if (this.multi) { |
| 11920 this._selectMulti(this.selectedValues); |
| 11921 } else { |
| 11922 this._selectSelected(this.selected); |
| 11923 } |
| 11924 }, |
| 11925 |
| 11926 _selectMulti: function(values) { |
| 11927 this._selection.clear(); |
| 11928 if (values) { |
| 11929 for (var i = 0; i < values.length; i++) { |
| 11930 this._selection.setItemSelected(this._valueToItem(values[i]), true); |
| 11931 } |
| 11932 } |
| 11933 }, |
| 11934 |
| 11935 _selectionChange: function() { |
| 11936 var s = this._selection.get(); |
| 11937 if (this.multi) { |
| 11938 this._setSelectedItems(s); |
| 11939 } else { |
| 11940 this._setSelectedItems([s]); |
| 11941 this._setSelectedItem(s); |
| 11942 } |
| 11943 }, |
| 11944 |
| 11945 _toggleSelected: function(value) { |
| 11946 var i = this.selectedValues.indexOf(value); |
| 11947 var unselected = i < 0; |
| 11948 if (unselected) { |
| 11949 this.push('selectedValues',value); |
| 11950 } else { |
| 11951 this.splice('selectedValues',i,1); |
| 11952 } |
| 11953 this._selection.setItemSelected(this._valueToItem(value), unselected); |
| 11954 } |
| 11955 }; |
| 11956 |
| 11957 /** @polymerBehavior */ |
| 11958 Polymer.IronMultiSelectableBehavior = [ |
| 11959 Polymer.IronSelectableBehavior, |
| 11960 Polymer.IronMultiSelectableBehaviorImpl |
| 11961 ]; |
| 11962 /** |
| 11963 * `Polymer.IronMenuBehavior` implements accessible menu behavior. |
| 11964 * |
| 11965 * @demo demo/index.html |
| 11966 * @polymerBehavior Polymer.IronMenuBehavior |
| 11967 */ |
| 11968 Polymer.IronMenuBehaviorImpl = { |
| 11969 |
| 11970 properties: { |
| 11971 |
| 11972 /** |
| 11973 * Returns the currently focused item. |
| 11974 * @type {?Object} |
| 11975 */ |
| 11976 focusedItem: { |
| 11977 observer: '_focusedItemChanged', |
| 11978 readOnly: true, |
| 11979 type: Object |
| 11980 }, |
| 11981 |
| 11982 /** |
| 11983 * The attribute to use on menu items to look up the item title. Typing th
e first |
| 11984 * letter of an item when the menu is open focuses that item. If unset, `t
extContent` |
| 11985 * will be used. |
| 11986 */ |
| 11987 attrForItemTitle: { |
| 11988 type: String |
| 11989 } |
| 11990 }, |
| 11991 |
| 11992 hostAttributes: { |
| 11993 'role': 'menu', |
| 11994 'tabindex': '0' |
| 11995 }, |
| 11996 |
| 11997 observers: [ |
| 11998 '_updateMultiselectable(multi)' |
| 11999 ], |
| 12000 |
| 12001 listeners: { |
| 12002 'focus': '_onFocus', |
| 12003 'keydown': '_onKeydown', |
| 12004 'iron-items-changed': '_onIronItemsChanged' |
| 12005 }, |
| 12006 |
| 12007 keyBindings: { |
| 12008 'up': '_onUpKey', |
| 12009 'down': '_onDownKey', |
| 12010 'esc': '_onEscKey', |
| 12011 'shift+tab:keydown': '_onShiftTabDown' |
| 12012 }, |
| 12013 |
| 12014 attached: function() { |
| 12015 this._resetTabindices(); |
| 12016 }, |
| 12017 |
| 12018 /** |
| 12019 * Selects the given value. If the `multi` property is true, then the select
ed state of the |
| 12020 * `value` will be toggled; otherwise the `value` will be selected. |
| 12021 * |
| 12022 * @param {string} value the value to select. |
| 12023 */ |
| 12024 select: function(value) { |
| 12025 if (this._defaultFocusAsync) { |
| 12026 this.cancelAsync(this._defaultFocusAsync); |
| 12027 this._defaultFocusAsync = null; |
| 12028 } |
| 12029 var item = this._valueToItem(value); |
| 12030 if (item && item.hasAttribute('disabled')) return; |
| 12031 this._setFocusedItem(item); |
| 12032 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); |
| 12033 }, |
| 12034 |
| 12035 /** |
| 12036 * Resets all tabindex attributes to the appropriate value based on the |
| 12037 * current selection state. The appropriate value is `0` (focusable) for |
| 12038 * the default selected item, and `-1` (not keyboard focusable) for all |
| 12039 * other items. |
| 12040 */ |
| 12041 _resetTabindices: function() { |
| 12042 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[
0]) : this.selectedItem; |
| 12043 |
| 12044 this.items.forEach(function(item) { |
| 12045 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); |
| 12046 }, this); |
| 12047 }, |
| 12048 |
| 12049 /** |
| 12050 * Sets appropriate ARIA based on whether or not the menu is meant to be |
| 12051 * multi-selectable. |
| 12052 * |
| 12053 * @param {boolean} multi True if the menu should be multi-selectable. |
| 12054 */ |
| 12055 _updateMultiselectable: function(multi) { |
| 12056 if (multi) { |
| 12057 this.setAttribute('aria-multiselectable', 'true'); |
| 12058 } else { |
| 12059 this.removeAttribute('aria-multiselectable'); |
| 12060 } |
| 12061 }, |
| 12062 |
| 12063 /** |
| 12064 * Given a KeyboardEvent, this method will focus the appropriate item in the |
| 12065 * menu (if there is a relevant item, and it is possible to focus it). |
| 12066 * |
| 12067 * @param {KeyboardEvent} event A KeyboardEvent. |
| 12068 */ |
| 12069 _focusWithKeyboardEvent: function(event) { |
| 12070 for (var i = 0, item; item = this.items[i]; i++) { |
| 12071 var attr = this.attrForItemTitle || 'textContent'; |
| 12072 var title = item[attr] || item.getAttribute(attr); |
| 12073 if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCod
e(event.keyCode).toLowerCase()) { |
| 12074 this._setFocusedItem(item); |
| 12075 break; |
| 12076 } |
| 12077 } |
| 12078 }, |
| 12079 |
| 12080 /** |
| 12081 * Focuses the previous item (relative to the currently focused item) in the |
| 12082 * menu. |
| 12083 */ |
| 12084 _focusPrevious: function() { |
| 12085 var length = this.items.length; |
| 12086 var index = (Number(this.indexOf(this.focusedItem)) - 1 + length) % length
; |
| 12087 this._setFocusedItem(this.items[index]); |
| 12088 }, |
| 12089 |
| 12090 /** |
| 12091 * Focuses the next item (relative to the currently focused item) in the |
| 12092 * menu. |
| 12093 */ |
| 12094 _focusNext: function() { |
| 12095 var index = (Number(this.indexOf(this.focusedItem)) + 1) % this.items.leng
th; |
| 12096 this._setFocusedItem(this.items[index]); |
| 12097 }, |
| 12098 |
| 12099 /** |
| 12100 * Mutates items in the menu based on provided selection details, so that |
| 12101 * all items correctly reflect selection state. |
| 12102 * |
| 12103 * @param {Element} item An item in the menu. |
| 12104 * @param {boolean} isSelected True if the item should be shown in a |
| 12105 * selected state, otherwise false. |
| 12106 */ |
| 12107 _applySelection: function(item, isSelected) { |
| 12108 if (isSelected) { |
| 12109 item.setAttribute('aria-selected', 'true'); |
| 12110 } else { |
| 12111 item.removeAttribute('aria-selected'); |
| 12112 } |
| 12113 |
| 12114 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); |
| 12115 }, |
| 12116 |
| 12117 /** |
| 12118 * Discretely updates tabindex values among menu items as the focused item |
| 12119 * changes. |
| 12120 * |
| 12121 * @param {Element} focusedItem The element that is currently focused. |
| 12122 * @param {?Element} old The last element that was considered focused, if |
| 12123 * applicable. |
| 12124 */ |
| 12125 _focusedItemChanged: function(focusedItem, old) { |
| 12126 old && old.setAttribute('tabindex', '-1'); |
| 12127 if (focusedItem) { |
| 12128 focusedItem.setAttribute('tabindex', '0'); |
| 12129 focusedItem.focus(); |
| 12130 } |
| 12131 }, |
| 12132 |
| 12133 /** |
| 12134 * A handler that responds to mutation changes related to the list of items |
| 12135 * in the menu. |
| 12136 * |
| 12137 * @param {CustomEvent} event An event containing mutation records as its |
| 12138 * detail. |
| 12139 */ |
| 12140 _onIronItemsChanged: function(event) { |
| 12141 var mutations = event.detail; |
| 12142 var mutation; |
| 12143 var index; |
| 12144 |
| 12145 for (index = 0; index < mutations.length; ++index) { |
| 12146 mutation = mutations[index]; |
| 12147 |
| 12148 if (mutation.addedNodes.length) { |
| 12149 this._resetTabindices(); |
| 12150 break; |
| 12151 } |
| 12152 } |
| 12153 }, |
| 12154 |
| 12155 /** |
| 12156 * Handler that is called when a shift+tab keypress is detected by the menu. |
| 12157 * |
| 12158 * @param {CustomEvent} event A key combination event. |
| 12159 */ |
| 12160 _onShiftTabDown: function(event) { |
| 12161 var oldTabIndex; |
| 12162 |
| 12163 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; |
| 12164 |
| 12165 oldTabIndex = this.getAttribute('tabindex'); |
| 12166 |
| 12167 this.setAttribute('tabindex', '-1'); |
| 12168 |
| 12169 this.async(function() { |
| 12170 this.setAttribute('tabindex', oldTabIndex); |
| 12171 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
| 12172 // NOTE(cdata): polymer/polymer#1305 |
| 12173 }, 1); |
| 12174 }, |
| 12175 |
| 12176 /** |
| 12177 * Handler that is called when the menu receives focus. |
| 12178 * |
| 12179 * @param {FocusEvent} event A focus event. |
| 12180 */ |
| 12181 _onFocus: function(event) { |
| 12182 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { |
| 12183 return; |
| 12184 } |
| 12185 // do not focus the menu itself |
| 12186 this.blur(); |
| 12187 // clear the cached focus item |
| 12188 this._setFocusedItem(null); |
| 12189 this._defaultFocusAsync = this.async(function() { |
| 12190 // focus the selected item when the menu receives focus, or the first it
em |
| 12191 // if no item is selected |
| 12192 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem
s[0]) : this.selectedItem; |
| 12193 if (selectedItem) { |
| 12194 this._setFocusedItem(selectedItem); |
| 12195 } else { |
| 12196 this._setFocusedItem(this.items[0]); |
| 12197 } |
| 12198 // async 100ms to wait for `select` to get called from `_itemActivate` |
| 12199 }, 100); |
| 12200 }, |
| 12201 |
| 12202 /** |
| 12203 * Handler that is called when the up key is pressed. |
| 12204 * |
| 12205 * @param {CustomEvent} event A key combination event. |
| 12206 */ |
| 12207 _onUpKey: function(event) { |
| 12208 // up and down arrows moves the focus |
| 12209 this._focusPrevious(); |
| 12210 }, |
| 12211 |
| 12212 /** |
| 12213 * Handler that is called when the down key is pressed. |
| 12214 * |
| 12215 * @param {CustomEvent} event A key combination event. |
| 12216 */ |
| 12217 _onDownKey: function(event) { |
| 12218 this._focusNext(); |
| 12219 }, |
| 12220 |
| 12221 /** |
| 12222 * Handler that is called when the esc key is pressed. |
| 12223 * |
| 12224 * @param {CustomEvent} event A key combination event. |
| 12225 */ |
| 12226 _onEscKey: function(event) { |
| 12227 // esc blurs the control |
| 12228 this.focusedItem.blur(); |
| 12229 }, |
| 12230 |
| 12231 /** |
| 12232 * Handler that is called when a keydown event is detected. |
| 12233 * |
| 12234 * @param {KeyboardEvent} event A keyboard event. |
| 12235 */ |
| 12236 _onKeydown: function(event) { |
| 12237 if (this.keyboardEventMatchesKeys(event, 'up down esc')) { |
| 12238 return; |
| 12239 } |
| 12240 |
| 12241 // all other keys focus the menu item starting with that character |
| 12242 this._focusWithKeyboardEvent(event); |
| 12243 } |
| 12244 }; |
| 12245 |
| 12246 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
| 12247 |
| 12248 /** @polymerBehavior Polymer.IronMenuBehavior */ |
| 12249 Polymer.IronMenuBehavior = [ |
| 12250 Polymer.IronMultiSelectableBehavior, |
| 12251 Polymer.IronA11yKeysBehavior, |
| 12252 Polymer.IronMenuBehaviorImpl |
| 12253 ]; |
| 12254 (function() { |
| 12255 |
| 12256 Polymer({ |
| 12257 |
| 12258 is: 'paper-menu', |
| 12259 |
| 12260 behaviors: [ |
| 12261 Polymer.IronMenuBehavior |
| 12262 ] |
| 12263 |
| 12264 }); |
| 12265 |
| 12266 })(); |
| 12267 /** |
| 12268 * `IronResizableBehavior` is a behavior that can be used in Polymer elements
to |
| 12269 * coordinate the flow of resize events between "resizers" (elements that cont
rol the |
| 12270 * size or hidden state of their children) and "resizables" (elements that nee
d to be |
| 12271 * notified when they are resized or un-hidden by their parents in order to ta
ke |
| 12272 * action on their new measurements). |
| 12273 * Elements that perform measurement should add the `IronResizableBehavior` be
havior to |
| 12274 * their element definition and listen for the `iron-resize` event on themselv
es. |
| 12275 * This event will be fired when they become showing after having been hidden, |
| 12276 * when they are resized explicitly by another resizable, or when the window h
as been |
| 12277 * resized. |
| 12278 * Note, the `iron-resize` event is non-bubbling. |
| 12279 * |
| 12280 * @polymerBehavior Polymer.IronResizableBehavior |
| 12281 * @demo demo/index.html |
| 12282 **/ |
| 12283 Polymer.IronResizableBehavior = { |
| 12284 properties: { |
| 12285 /** |
| 12286 * The closest ancestor element that implements `IronResizableBehavior`. |
| 12287 */ |
| 12288 _parentResizable: { |
| 12289 type: Object, |
| 12290 observer: '_parentResizableChanged' |
| 12291 }, |
| 12292 |
| 12293 /** |
| 12294 * True if this element is currently notifying its descedant elements of |
| 12295 * resize. |
| 12296 */ |
| 12297 _notifyingDescendant: { |
| 12298 type: Boolean, |
| 12299 value: false |
| 12300 } |
| 12301 }, |
| 12302 |
| 12303 listeners: { |
| 12304 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' |
| 12305 }, |
| 12306 |
| 12307 created: function() { |
| 12308 // We don't really need property effects on these, and also we want them |
| 12309 // to be created before the `_parentResizable` observer fires: |
| 12310 this._interestedResizables = []; |
| 12311 this._boundNotifyResize = this.notifyResize.bind(this); |
| 12312 }, |
| 12313 |
| 12314 attached: function() { |
| 12315 this.fire('iron-request-resize-notifications', null, { |
| 12316 node: this, |
| 12317 bubbles: true, |
| 12318 cancelable: true |
| 12319 }); |
| 12320 |
| 12321 if (!this._parentResizable) { |
| 12322 window.addEventListener('resize', this._boundNotifyResize); |
| 12323 this.notifyResize(); |
| 12324 } |
| 12325 }, |
| 12326 |
| 12327 detached: function() { |
| 12328 if (this._parentResizable) { |
| 12329 this._parentResizable.stopResizeNotificationsFor(this); |
| 12330 } else { |
| 12331 window.removeEventListener('resize', this._boundNotifyResize); |
| 12332 } |
| 12333 |
| 12334 this._parentResizable = null; |
| 12335 }, |
| 12336 |
| 12337 /** |
| 12338 * Can be called to manually notify a resizable and its descendant |
| 12339 * resizables of a resize change. |
| 12340 */ |
| 12341 notifyResize: function() { |
| 12342 if (!this.isAttached) { |
| 12343 return; |
| 12344 } |
| 12345 |
| 12346 this._interestedResizables.forEach(function(resizable) { |
| 12347 if (this.resizerShouldNotify(resizable)) { |
| 12348 this._notifyDescendant(resizable); |
| 12349 } |
| 12350 }, this); |
| 12351 |
| 12352 this._fireResize(); |
| 12353 }, |
| 12354 |
| 12355 /** |
| 12356 * Used to assign the closest resizable ancestor to this resizable |
| 12357 * if the ancestor detects a request for notifications. |
| 12358 */ |
| 12359 assignParentResizable: function(parentResizable) { |
| 12360 this._parentResizable = parentResizable; |
| 12361 }, |
| 12362 |
| 12363 /** |
| 12364 * Used to remove a resizable descendant from the list of descendants |
| 12365 * that should be notified of a resize change. |
| 12366 */ |
| 12367 stopResizeNotificationsFor: function(target) { |
| 12368 var index = this._interestedResizables.indexOf(target); |
| 12369 |
| 12370 if (index > -1) { |
| 12371 this._interestedResizables.splice(index, 1); |
| 12372 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); |
| 12373 } |
| 12374 }, |
| 12375 |
| 12376 /** |
| 12377 * This method can be overridden to filter nested elements that should or |
| 12378 * should not be notified by the current element. Return true if an element |
| 12379 * should be notified, or false if it should not be notified. |
| 12380 * |
| 12381 * @param {HTMLElement} element A candidate descendant element that |
| 12382 * implements `IronResizableBehavior`. |
| 12383 * @return {boolean} True if the `element` should be notified of resize. |
| 12384 */ |
| 12385 resizerShouldNotify: function(element) { return true; }, |
| 12386 |
| 12387 _onDescendantIronResize: function(event) { |
| 12388 if (this._notifyingDescendant) { |
| 12389 event.stopPropagation(); |
| 12390 return; |
| 12391 } |
| 12392 |
| 12393 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the |
| 12394 // otherwise non-bubbling event "just work." We do it manually here for |
| 12395 // the case where Polymer is not using shadow roots for whatever reason: |
| 12396 if (!Polymer.Settings.useShadow) { |
| 12397 this._fireResize(); |
| 12398 } |
| 12399 }, |
| 12400 |
| 12401 _fireResize: function() { |
| 12402 this.fire('iron-resize', null, { |
| 12403 node: this, |
| 12404 bubbles: false |
| 12405 }); |
| 12406 }, |
| 12407 |
| 12408 _onIronRequestResizeNotifications: function(event) { |
| 12409 var target = event.path ? event.path[0] : event.target; |
| 12410 |
| 12411 if (target === this) { |
| 12412 return; |
| 12413 } |
| 12414 |
| 12415 if (this._interestedResizables.indexOf(target) === -1) { |
| 12416 this._interestedResizables.push(target); |
| 12417 this.listen(target, 'iron-resize', '_onDescendantIronResize'); |
| 12418 } |
| 12419 |
| 12420 target.assignParentResizable(this); |
| 12421 this._notifyDescendant(target); |
| 12422 |
| 12423 event.stopPropagation(); |
| 12424 }, |
| 12425 |
| 12426 _parentResizableChanged: function(parentResizable) { |
| 12427 if (parentResizable) { |
| 12428 window.removeEventListener('resize', this._boundNotifyResize); |
| 12429 } |
| 12430 }, |
| 12431 |
| 12432 _notifyDescendant: function(descendant) { |
| 12433 // NOTE(cdata): In IE10, attached is fired on children first, so it's |
| 12434 // important not to notify them if the parent is not attached yet (or |
| 12435 // else they will get redundantly notified when the parent attaches). |
| 12436 if (!this.isAttached) { |
| 12437 return; |
| 12438 } |
| 12439 |
| 12440 this._notifyingDescendant = true; |
| 12441 descendant.notifyResize(); |
| 12442 this._notifyingDescendant = false; |
| 12443 } |
| 12444 }; |
| 12445 /** |
| 12446 Polymer.IronFitBehavior fits an element in another element using `max-height` an
d `max-width`, and |
| 12447 optionally centers it in the window or another element. |
| 12448 |
| 12449 The element will only be sized and/or positioned if it has not already been size
d and/or positioned |
| 12450 by CSS. |
| 12451 |
| 12452 CSS properties | Action |
| 12453 -----------------------------|------------------------------------------- |
| 12454 `position` set | Element is not centered horizontally or verticall
y |
| 12455 `top` or `bottom` set | Element is not vertically centered |
| 12456 `left` or `right` set | Element is not horizontally centered |
| 12457 `max-height` or `height` set | Element respects `max-height` or `height` |
| 12458 `max-width` or `width` set | Element respects `max-width` or `width` |
| 12459 |
| 12460 @demo demo/index.html |
| 12461 @polymerBehavior |
| 12462 */ |
| 12463 |
| 12464 Polymer.IronFitBehavior = { |
| 12465 |
| 12466 properties: { |
| 12467 |
| 12468 /** |
| 12469 * The element that will receive a `max-height`/`width`. By default it is
the same as `this`, |
| 12470 * but it can be set to a child element. This is useful, for example, for
implementing a |
| 12471 * scrolling region inside the element. |
| 12472 * @type {!Element} |
| 12473 */ |
| 12474 sizingTarget: { |
| 12475 type: Object, |
| 12476 value: function() { |
| 12477 return this; |
| 12478 } |
| 12479 }, |
| 12480 |
| 12481 /** |
| 12482 * The element to fit `this` into. |
| 12483 */ |
| 12484 fitInto: { |
| 12485 type: Object, |
| 12486 value: window |
| 12487 }, |
| 12488 |
| 12489 /** |
| 12490 * Set to true to auto-fit on attach. |
| 12491 */ |
| 12492 autoFitOnAttach: { |
| 12493 type: Boolean, |
| 12494 value: false |
| 12495 }, |
| 12496 |
| 12497 /** @type {?Object} */ |
| 12498 _fitInfo: { |
| 12499 type: Object |
| 12500 } |
| 12501 |
| 12502 }, |
| 12503 |
| 12504 get _fitWidth() { |
| 12505 var fitWidth; |
| 12506 if (this.fitInto === window) { |
| 12507 fitWidth = this.fitInto.innerWidth; |
| 12508 } else { |
| 12509 fitWidth = this.fitInto.getBoundingClientRect().width; |
| 12510 } |
| 12511 return fitWidth; |
| 12512 }, |
| 12513 |
| 12514 get _fitHeight() { |
| 12515 var fitHeight; |
| 12516 if (this.fitInto === window) { |
| 12517 fitHeight = this.fitInto.innerHeight; |
| 12518 } else { |
| 12519 fitHeight = this.fitInto.getBoundingClientRect().height; |
| 12520 } |
| 12521 return fitHeight; |
| 12522 }, |
| 12523 |
| 12524 get _fitLeft() { |
| 12525 var fitLeft; |
| 12526 if (this.fitInto === window) { |
| 12527 fitLeft = 0; |
| 12528 } else { |
| 12529 fitLeft = this.fitInto.getBoundingClientRect().left; |
| 12530 } |
| 12531 return fitLeft; |
| 12532 }, |
| 12533 |
| 12534 get _fitTop() { |
| 12535 var fitTop; |
| 12536 if (this.fitInto === window) { |
| 12537 fitTop = 0; |
| 12538 } else { |
| 12539 fitTop = this.fitInto.getBoundingClientRect().top; |
| 12540 } |
| 12541 return fitTop; |
| 12542 }, |
| 12543 |
| 12544 attached: function() { |
| 12545 if (this.autoFitOnAttach) { |
| 12546 if (window.getComputedStyle(this).display === 'none') { |
| 12547 setTimeout(function() { |
| 12548 this.fit(); |
| 12549 }.bind(this)); |
| 12550 } else { |
| 12551 this.fit(); |
| 12552 } |
| 12553 } |
| 12554 }, |
| 12555 |
| 12556 /** |
| 12557 * Fits and optionally centers the element into the window, or `fitInfo` if
specified. |
| 12558 */ |
| 12559 fit: function() { |
| 12560 this._discoverInfo(); |
| 12561 this.constrain(); |
| 12562 this.center(); |
| 12563 }, |
| 12564 |
| 12565 /** |
| 12566 * Memoize information needed to position and size the target element. |
| 12567 */ |
| 12568 _discoverInfo: function() { |
| 12569 if (this._fitInfo) { |
| 12570 return; |
| 12571 } |
| 12572 var target = window.getComputedStyle(this); |
| 12573 var sizer = window.getComputedStyle(this.sizingTarget); |
| 12574 this._fitInfo = { |
| 12575 inlineStyle: { |
| 12576 top: this.style.top || '', |
| 12577 left: this.style.left || '' |
| 12578 }, |
| 12579 positionedBy: { |
| 12580 vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto'
? |
| 12581 'bottom' : null), |
| 12582 horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'aut
o' ? |
| 12583 'right' : null), |
| 12584 css: target.position |
| 12585 }, |
| 12586 sizedBy: { |
| 12587 height: sizer.maxHeight !== 'none', |
| 12588 width: sizer.maxWidth !== 'none' |
| 12589 }, |
| 12590 margin: { |
| 12591 top: parseInt(target.marginTop, 10) || 0, |
| 12592 right: parseInt(target.marginRight, 10) || 0, |
| 12593 bottom: parseInt(target.marginBottom, 10) || 0, |
| 12594 left: parseInt(target.marginLeft, 10) || 0 |
| 12595 } |
| 12596 }; |
| 12597 }, |
| 12598 |
| 12599 /** |
| 12600 * Resets the target element's position and size constraints, and clear |
| 12601 * the memoized data. |
| 12602 */ |
| 12603 resetFit: function() { |
| 12604 if (!this._fitInfo || !this._fitInfo.sizedBy.height) { |
| 12605 this.sizingTarget.style.maxHeight = ''; |
| 12606 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : ''; |
| 12607 } |
| 12608 if (!this._fitInfo || !this._fitInfo.sizedBy.width) { |
| 12609 this.sizingTarget.style.maxWidth = ''; |
| 12610 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : ''; |
| 12611 } |
| 12612 if (this._fitInfo) { |
| 12613 this.style.position = this._fitInfo.positionedBy.css; |
| 12614 } |
| 12615 this._fitInfo = null; |
| 12616 }, |
| 12617 |
| 12618 /** |
| 12619 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
the element, |
| 12620 * the window, or the `fitInfo` element has been resized. |
| 12621 */ |
| 12622 refit: function() { |
| 12623 this.resetFit(); |
| 12624 this.fit(); |
| 12625 }, |
| 12626 |
| 12627 /** |
| 12628 * Constrains the size of the element to the window or `fitInfo` by setting
`max-height` |
| 12629 * and/or `max-width`. |
| 12630 */ |
| 12631 constrain: function() { |
| 12632 var info = this._fitInfo; |
| 12633 // position at (0px, 0px) if not already positioned, so we can measure the
natural size. |
| 12634 if (!this._fitInfo.positionedBy.vertically) { |
| 12635 this.style.top = '0px'; |
| 12636 } |
| 12637 if (!this._fitInfo.positionedBy.horizontally) { |
| 12638 this.style.left = '0px'; |
| 12639 } |
| 12640 // need border-box for margin/padding |
| 12641 this.sizingTarget.style.boxSizing = 'border-box'; |
| 12642 // constrain the width and height if not already set |
| 12643 var rect = this.getBoundingClientRect(); |
| 12644 if (!info.sizedBy.height) { |
| 12645 this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom',
'Height'); |
| 12646 } |
| 12647 if (!info.sizedBy.width) { |
| 12648 this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right
', 'Width'); |
| 12649 } |
| 12650 }, |
| 12651 |
| 12652 _sizeDimension: function(rect, positionedBy, start, end, extent) { |
| 12653 var info = this._fitInfo; |
| 12654 var max = extent === 'Width' ? this._fitWidth : this._fitHeight; |
| 12655 var flip = (positionedBy === end); |
| 12656 var offset = flip ? max - rect[end] : rect[start]; |
| 12657 var margin = info.margin[flip ? start : end]; |
| 12658 var offsetExtent = 'offset' + extent; |
| 12659 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; |
| 12660 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; |
| 12661 }, |
| 12662 |
| 12663 /** |
| 12664 * Centers horizontally and vertically if not already positioned. This also
sets |
| 12665 * `position:fixed`. |
| 12666 */ |
| 12667 center: function() { |
| 12668 if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.
horizontally) { |
| 12669 // need position:fixed to center |
| 12670 this.style.position = 'fixed'; |
| 12671 } |
| 12672 if (!this._fitInfo.positionedBy.vertically) { |
| 12673 var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop; |
| 12674 top -= this._fitInfo.margin.top; |
| 12675 this.style.top = top + 'px'; |
| 12676 } |
| 12677 if (!this._fitInfo.positionedBy.horizontally) { |
| 12678 var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft; |
| 12679 left -= this._fitInfo.margin.left; |
| 12680 this.style.left = left + 'px'; |
| 12681 } |
| 12682 } |
| 12683 |
| 12684 }; |
| 12685 Polymer.IronOverlayManager = (function() { |
| 12686 |
| 12687 var overlays = []; |
| 12688 var DEFAULT_Z = 10; |
| 12689 var backdrops = []; |
| 12690 |
| 12691 // track overlays for z-index and focus managemant |
| 12692 function addOverlay(overlay) { |
| 12693 var z0 = currentOverlayZ(); |
| 12694 overlays.push(overlay); |
| 12695 var z1 = currentOverlayZ(); |
| 12696 if (z1 <= z0) { |
| 12697 applyOverlayZ(overlay, z0); |
| 12698 } |
| 12699 } |
| 12700 |
| 12701 function removeOverlay(overlay) { |
| 12702 var i = overlays.indexOf(overlay); |
| 12703 if (i >= 0) { |
| 12704 overlays.splice(i, 1); |
| 12705 setZ(overlay, ''); |
| 12706 } |
| 12707 } |
| 12708 |
| 12709 function applyOverlayZ(overlay, aboveZ) { |
| 12710 setZ(overlay, aboveZ + 2); |
| 12711 } |
| 12712 |
| 12713 function setZ(element, z) { |
| 12714 element.style.zIndex = z; |
| 12715 } |
| 12716 |
| 12717 function currentOverlay() { |
| 12718 var i = overlays.length - 1; |
| 12719 while (overlays[i] && !overlays[i].opened) { |
| 12720 --i; |
| 12721 } |
| 12722 return overlays[i]; |
| 12723 } |
| 12724 |
| 12725 function currentOverlayZ() { |
| 12726 var z; |
| 12727 var current = currentOverlay(); |
| 12728 if (current) { |
| 12729 var z1 = window.getComputedStyle(current).zIndex; |
| 12730 if (!isNaN(z1)) { |
| 12731 z = Number(z1); |
| 12732 } |
| 12733 } |
| 12734 return z || DEFAULT_Z; |
| 12735 } |
| 12736 |
| 12737 function focusOverlay() { |
| 12738 var current = currentOverlay(); |
| 12739 // We have to be careful to focus the next overlay _after_ any current |
| 12740 // transitions are complete (due to the state being toggled prior to the |
| 12741 // transition). Otherwise, we risk infinite recursion when a transitioning |
| 12742 // (closed) overlay becomes the current overlay. |
| 12743 // |
| 12744 // NOTE: We make the assumption that any overlay that completes a transiti
on |
| 12745 // will call into focusOverlay to kick the process back off. Currently: |
| 12746 // transitionend -> _applyFocus -> focusOverlay. |
| 12747 if (current && !current.transitioning) { |
| 12748 current._applyFocus(); |
| 12749 } |
| 12750 } |
| 12751 |
| 12752 function trackBackdrop(element) { |
| 12753 // backdrops contains the overlays with a backdrop that are currently |
| 12754 // visible |
| 12755 if (element.opened) { |
| 12756 backdrops.push(element); |
| 12757 } else { |
| 12758 var index = backdrops.indexOf(element); |
| 12759 if (index >= 0) { |
| 12760 backdrops.splice(index, 1); |
| 12761 } |
| 12762 } |
| 12763 } |
| 12764 |
| 12765 function getBackdrops() { |
| 12766 return backdrops; |
| 12767 } |
| 12768 |
| 12769 return { |
| 12770 addOverlay: addOverlay, |
| 12771 removeOverlay: removeOverlay, |
| 12772 currentOverlay: currentOverlay, |
| 12773 currentOverlayZ: currentOverlayZ, |
| 12774 focusOverlay: focusOverlay, |
| 12775 trackBackdrop: trackBackdrop, |
| 12776 getBackdrops: getBackdrops |
| 12777 }; |
| 12778 |
| 12779 })(); |
| 12780 (function() { |
| 12781 |
| 12782 Polymer({ |
| 12783 |
| 12784 is: 'iron-overlay-backdrop', |
| 12785 |
| 12786 properties: { |
| 12787 |
| 12788 /** |
| 12789 * Returns true if the backdrop is opened. |
| 12790 */ |
| 12791 opened: { |
| 12792 readOnly: true, |
| 12793 reflectToAttribute: true, |
| 12794 type: Boolean, |
| 12795 value: false |
| 12796 }, |
| 12797 |
| 12798 _manager: { |
| 12799 type: Object, |
| 12800 value: Polymer.IronOverlayManager |
| 12801 } |
| 12802 |
| 12803 }, |
| 12804 |
| 12805 /** |
| 12806 * Appends the backdrop to document body and sets its `z-index` to be below
the latest overlay. |
| 12807 */ |
| 12808 prepare: function() { |
| 12809 if (!this.parentNode) { |
| 12810 Polymer.dom(document.body).appendChild(this); |
| 12811 this.style.zIndex = this._manager.currentOverlayZ() - 1; |
| 12812 } |
| 12813 }, |
| 12814 |
| 12815 /** |
| 12816 * Shows the backdrop if needed. |
| 12817 */ |
| 12818 open: function() { |
| 12819 // only need to make the backdrop visible if this is called by the first o
verlay with a backdrop |
| 12820 if (this._manager.getBackdrops().length < 2) { |
| 12821 this._setOpened(true); |
| 12822 } |
| 12823 }, |
| 12824 |
| 12825 /** |
| 12826 * Hides the backdrop if needed. |
| 12827 */ |
| 12828 close: function() { |
| 12829 // only need to make the backdrop invisible if this is called by the last
overlay with a backdrop |
| 12830 if (this._manager.getBackdrops().length < 2) { |
| 12831 this._setOpened(false); |
| 12832 } |
| 12833 }, |
| 12834 |
| 12835 /** |
| 12836 * Removes the backdrop from document body if needed. |
| 12837 */ |
| 12838 complete: function() { |
| 12839 // only remove the backdrop if there are no more overlays with backdrops |
| 12840 if (this._manager.getBackdrops().length === 0 && this.parentNode) { |
| 12841 Polymer.dom(this.parentNode).removeChild(this); |
| 12842 } |
| 12843 } |
| 12844 |
| 12845 }); |
| 12846 |
| 12847 })(); |
| 12848 /** |
| 12849 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays |
| 12850 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety |
| 12851 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. |
| 12852 |
| 12853 ### Closing and canceling |
| 12854 |
| 12855 A dialog may be hidden by closing or canceling. The difference between close and
cancel is user |
| 12856 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, |
| 12857 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is |
| 12858 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. |
| 12859 `close()` should be called explicitly by the implementer when the user interacts
with a control |
| 12860 in the overlay element. |
| 12861 |
| 12862 ### Positioning |
| 12863 |
| 12864 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can |
| 12865 position and size it manually using CSS. See `Polymer.IronFitBehavior`. |
| 12866 |
| 12867 ### Backdrop |
| 12868 |
| 12869 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is |
| 12870 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling |
| 12871 options. |
| 12872 |
| 12873 ### Limitations |
| 12874 |
| 12875 The element is styled to appear on top of other content by setting its `z-index`
property. You |
| 12876 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking |
| 12877 context. You should place this element as a child of `<body>` whenever possible. |
| 12878 |
| 12879 @demo demo/index.html |
| 12880 @polymerBehavior Polymer.IronOverlayBehavior |
| 12881 */ |
| 12882 |
| 12883 Polymer.IronOverlayBehaviorImpl = { |
| 12884 |
| 12885 properties: { |
| 12886 |
| 12887 /** |
| 12888 * True if the overlay is currently displayed. |
| 12889 */ |
| 12890 opened: { |
| 12891 observer: '_openedChanged', |
| 12892 type: Boolean, |
| 12893 value: false, |
| 12894 notify: true |
| 12895 }, |
| 12896 |
| 12897 /** |
| 12898 * True if the overlay was canceled when it was last closed. |
| 12899 */ |
| 12900 canceled: { |
| 12901 observer: '_canceledChanged', |
| 12902 readOnly: true, |
| 12903 type: Boolean, |
| 12904 value: false |
| 12905 }, |
| 12906 |
| 12907 /** |
| 12908 * Set to true to display a backdrop behind the overlay. |
| 12909 */ |
| 12910 withBackdrop: { |
| 12911 type: Boolean, |
| 12912 value: false |
| 12913 }, |
| 12914 |
| 12915 /** |
| 12916 * Set to true to disable auto-focusing the overlay or child nodes with |
| 12917 * the `autofocus` attribute` when the overlay is opened. |
| 12918 */ |
| 12919 noAutoFocus: { |
| 12920 type: Boolean, |
| 12921 value: false |
| 12922 }, |
| 12923 |
| 12924 /** |
| 12925 * Set to true to disable canceling the overlay with the ESC key. |
| 12926 */ |
| 12927 noCancelOnEscKey: { |
| 12928 type: Boolean, |
| 12929 value: false |
| 12930 }, |
| 12931 |
| 12932 /** |
| 12933 * Set to true to disable canceling the overlay by clicking outside it. |
| 12934 */ |
| 12935 noCancelOnOutsideClick: { |
| 12936 type: Boolean, |
| 12937 value: false |
| 12938 }, |
| 12939 |
| 12940 /** |
| 12941 * Returns the reason this dialog was last closed. |
| 12942 */ |
| 12943 closingReason: { |
| 12944 // was a getter before, but needs to be a property so other |
| 12945 // behaviors can override this. |
| 12946 type: Object |
| 12947 }, |
| 12948 |
| 12949 _manager: { |
| 12950 type: Object, |
| 12951 value: Polymer.IronOverlayManager |
| 12952 }, |
| 12953 |
| 12954 _boundOnCaptureClick: { |
| 12955 type: Function, |
| 12956 value: function() { |
| 12957 return this._onCaptureClick.bind(this); |
| 12958 } |
| 12959 }, |
| 12960 |
| 12961 _boundOnCaptureKeydown: { |
| 12962 type: Function, |
| 12963 value: function() { |
| 12964 return this._onCaptureKeydown.bind(this); |
| 12965 } |
| 12966 } |
| 12967 |
| 12968 }, |
| 12969 |
| 12970 listeners: { |
| 12971 'tap': '_onClick', |
| 12972 'iron-resize': '_onIronResize' |
| 12973 }, |
| 12974 |
| 12975 /** |
| 12976 * The backdrop element. |
| 12977 * @type Node |
| 12978 */ |
| 12979 get backdropElement() { |
| 12980 return this._backdrop; |
| 12981 }, |
| 12982 |
| 12983 get _focusNode() { |
| 12984 return Polymer.dom(this).querySelector('[autofocus]') || this; |
| 12985 }, |
| 12986 |
| 12987 registered: function() { |
| 12988 this._backdrop = document.createElement('iron-overlay-backdrop'); |
| 12989 }, |
| 12990 |
| 12991 ready: function() { |
| 12992 this._ensureSetup(); |
| 12993 if (this._callOpenedWhenReady) { |
| 12994 this._openedChanged(); |
| 12995 } |
| 12996 }, |
| 12997 |
| 12998 detached: function() { |
| 12999 this.opened = false; |
| 13000 this._completeBackdrop(); |
| 13001 this._manager.removeOverlay(this); |
| 13002 }, |
| 13003 |
| 13004 /** |
| 13005 * Toggle the opened state of the overlay. |
| 13006 */ |
| 13007 toggle: function() { |
| 13008 this.opened = !this.opened; |
| 13009 }, |
| 13010 |
| 13011 /** |
| 13012 * Open the overlay. |
| 13013 */ |
| 13014 open: function() { |
| 13015 this.opened = true; |
| 13016 this.closingReason = {canceled: false}; |
| 13017 }, |
| 13018 |
| 13019 /** |
| 13020 * Close the overlay. |
| 13021 */ |
| 13022 close: function() { |
| 13023 this.opened = false; |
| 13024 this._setCanceled(false); |
| 13025 }, |
| 13026 |
| 13027 /** |
| 13028 * Cancels the overlay. |
| 13029 */ |
| 13030 cancel: function() { |
| 13031 this.opened = false; |
| 13032 this._setCanceled(true); |
| 13033 }, |
| 13034 |
| 13035 _ensureSetup: function() { |
| 13036 if (this._overlaySetup) { |
| 13037 return; |
| 13038 } |
| 13039 this._overlaySetup = true; |
| 13040 this.style.outline = 'none'; |
| 13041 this.style.display = 'none'; |
| 13042 }, |
| 13043 |
| 13044 _openedChanged: function() { |
| 13045 if (this.opened) { |
| 13046 this.removeAttribute('aria-hidden'); |
| 13047 } else { |
| 13048 this.setAttribute('aria-hidden', 'true'); |
| 13049 } |
| 13050 |
| 13051 // wait to call after ready only if we're initially open |
| 13052 if (!this._overlaySetup) { |
| 13053 this._callOpenedWhenReady = this.opened; |
| 13054 return; |
| 13055 } |
| 13056 if (this._openChangedAsync) { |
| 13057 this.cancelAsync(this._openChangedAsync); |
| 13058 } |
| 13059 |
| 13060 this._toggleListeners(); |
| 13061 |
| 13062 if (this.opened) { |
| 13063 this._prepareRenderOpened(); |
| 13064 } |
| 13065 |
| 13066 // async here to allow overlay layer to become visible. |
| 13067 this._openChangedAsync = this.async(function() { |
| 13068 // overlay becomes visible here |
| 13069 this.style.display = ''; |
| 13070 // force layout to ensure transitions will go |
| 13071 /** @suppress {suspiciousCode} */ this.offsetWidth; |
| 13072 if (this.opened) { |
| 13073 this._renderOpened(); |
| 13074 } else { |
| 13075 this._renderClosed(); |
| 13076 } |
| 13077 this._openChangedAsync = null; |
| 13078 }); |
| 13079 |
| 13080 }, |
| 13081 |
| 13082 _canceledChanged: function() { |
| 13083 this.closingReason = this.closingReason || {}; |
| 13084 this.closingReason.canceled = this.canceled; |
| 13085 }, |
| 13086 |
| 13087 _toggleListener: function(enable, node, event, boundListener, capture) { |
| 13088 if (enable) { |
| 13089 // enable document-wide tap recognizer |
| 13090 if (event === 'tap') { |
| 13091 Polymer.Gestures.add(document, 'tap', null); |
| 13092 } |
| 13093 node.addEventListener(event, boundListener, capture); |
| 13094 } else { |
| 13095 // disable document-wide tap recognizer |
| 13096 if (event === 'tap') { |
| 13097 Polymer.Gestures.remove(document, 'tap', null); |
| 13098 } |
| 13099 node.removeEventListener(event, boundListener, capture); |
| 13100 } |
| 13101 }, |
| 13102 |
| 13103 _toggleListeners: function() { |
| 13104 if (this._toggleListenersAsync) { |
| 13105 this.cancelAsync(this._toggleListenersAsync); |
| 13106 } |
| 13107 // async so we don't auto-close immediately via a click. |
| 13108 this._toggleListenersAsync = this.async(function() { |
| 13109 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureC
lick, true); |
| 13110 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt
ureKeydown, true); |
| 13111 this._toggleListenersAsync = null; |
| 13112 }, 1); |
| 13113 }, |
| 13114 |
| 13115 // tasks which must occur before opening; e.g. making the element visible |
| 13116 _prepareRenderOpened: function() { |
| 13117 this._manager.addOverlay(this); |
| 13118 |
| 13119 if (this.withBackdrop) { |
| 13120 this.backdropElement.prepare(); |
| 13121 this._manager.trackBackdrop(this); |
| 13122 } |
| 13123 |
| 13124 this._preparePositioning(); |
| 13125 this.fit(); |
| 13126 this._finishPositioning(); |
| 13127 }, |
| 13128 |
| 13129 // tasks which cause the overlay to actually open; typically play an |
| 13130 // animation |
| 13131 _renderOpened: function() { |
| 13132 if (this.withBackdrop) { |
| 13133 this.backdropElement.open(); |
| 13134 } |
| 13135 this._finishRenderOpened(); |
| 13136 }, |
| 13137 |
| 13138 _renderClosed: function() { |
| 13139 if (this.withBackdrop) { |
| 13140 this.backdropElement.close(); |
| 13141 } |
| 13142 this._finishRenderClosed(); |
| 13143 }, |
| 13144 |
| 13145 _onTransitionend: function(event) { |
| 13146 // make sure this is our transition event. |
| 13147 if (event && event.target !== this) { |
| 13148 return; |
| 13149 } |
| 13150 if (this.opened) { |
| 13151 this._finishRenderOpened(); |
| 13152 } else { |
| 13153 this._finishRenderClosed(); |
| 13154 } |
| 13155 }, |
| 13156 |
| 13157 _finishRenderOpened: function() { |
| 13158 // focus the child node with [autofocus] |
| 13159 if (!this.noAutoFocus) { |
| 13160 this._focusNode.focus(); |
| 13161 } |
| 13162 |
| 13163 this.fire('iron-overlay-opened'); |
| 13164 |
| 13165 this._squelchNextResize = true; |
| 13166 this.async(this.notifyResize); |
| 13167 }, |
| 13168 |
| 13169 _finishRenderClosed: function() { |
| 13170 // hide the overlay and remove the backdrop |
| 13171 this.resetFit(); |
| 13172 this.style.display = 'none'; |
| 13173 this._completeBackdrop(); |
| 13174 this._manager.removeOverlay(this); |
| 13175 |
| 13176 this._focusNode.blur(); |
| 13177 // focus the next overlay, if there is one |
| 13178 this._manager.focusOverlay(); |
| 13179 |
| 13180 this.fire('iron-overlay-closed', this.closingReason); |
| 13181 |
| 13182 this._squelchNextResize = true; |
| 13183 this.async(this.notifyResize); |
| 13184 }, |
| 13185 |
| 13186 _completeBackdrop: function() { |
| 13187 if (this.withBackdrop) { |
| 13188 this._manager.trackBackdrop(this); |
| 13189 this.backdropElement.complete(); |
| 13190 } |
| 13191 }, |
| 13192 |
| 13193 _preparePositioning: function() { |
| 13194 this.style.transition = this.style.webkitTransition = 'none'; |
| 13195 this.style.transform = this.style.webkitTransform = 'none'; |
| 13196 this.style.display = ''; |
| 13197 }, |
| 13198 |
| 13199 _finishPositioning: function() { |
| 13200 this.style.display = 'none'; |
| 13201 this.style.transform = this.style.webkitTransform = ''; |
| 13202 // force layout to avoid application of transform |
| 13203 /** @suppress {suspiciousCode} */ this.offsetWidth; |
| 13204 this.style.transition = this.style.webkitTransition = ''; |
| 13205 }, |
| 13206 |
| 13207 _applyFocus: function() { |
| 13208 if (this.opened) { |
| 13209 if (!this.noAutoFocus) { |
| 13210 this._focusNode.focus(); |
| 13211 } |
| 13212 } else { |
| 13213 this._focusNode.blur(); |
| 13214 this._manager.focusOverlay(); |
| 13215 } |
| 13216 }, |
| 13217 |
| 13218 _onCaptureClick: function(event) { |
| 13219 // attempt to close asynchronously and prevent the close of a tap event is
immediately heard |
| 13220 // on target. This is because in shadow dom due to event retargetting even
t.target is not |
| 13221 // useful. |
| 13222 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi
s)) { |
| 13223 this._cancelJob = this.async(function() { |
| 13224 this.cancel(); |
| 13225 }, 10); |
| 13226 } |
| 13227 }, |
| 13228 |
| 13229 _onClick: function(event) { |
| 13230 if (this._cancelJob) { |
| 13231 this.cancelAsync(this._cancelJob); |
| 13232 this._cancelJob = null; |
| 13233 } |
| 13234 }, |
| 13235 |
| 13236 _onCaptureKeydown: function(event) { |
| 13237 var ESC = 27; |
| 13238 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { |
| 13239 this.cancel(); |
| 13240 event.stopPropagation(); |
| 13241 } |
| 13242 }, |
| 13243 |
| 13244 _onIronResize: function() { |
| 13245 if (this._squelchNextResize) { |
| 13246 this._squelchNextResize = false; |
| 13247 return; |
| 13248 } |
| 13249 if (this.opened) { |
| 13250 this.refit(); |
| 13251 } |
| 13252 } |
| 13253 |
| 13254 /** |
| 13255 * Fired after the `iron-overlay` opens. |
| 13256 * @event iron-overlay-opened |
| 13257 */ |
| 13258 |
| 13259 /** |
| 13260 * Fired after the `iron-overlay` closes. |
| 13261 * @event iron-overlay-closed |
| 13262 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute |
| 13263 */ |
| 13264 }; |
| 13265 |
| 13266 /** @polymerBehavior */ |
| 13267 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; |
| 13268 /** |
| 13269 * Use `Polymer.NeonAnimationBehavior` to implement an animation. |
| 13270 * @polymerBehavior |
| 13271 */ |
| 13272 Polymer.NeonAnimationBehavior = { |
| 13273 |
| 13274 properties: { |
| 13275 |
| 13276 /** |
| 13277 * Defines the animation timing. |
| 13278 */ |
| 13279 animationTiming: { |
| 13280 type: Object, |
| 13281 value: function() { |
| 13282 return { |
| 13283 duration: 500, |
| 13284 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', |
| 13285 fill: 'both' |
| 13286 } |
| 13287 } |
| 13288 } |
| 13289 |
| 13290 }, |
| 13291 |
| 13292 registered: function() { |
| 13293 new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constru
ctor}); |
| 13294 }, |
| 13295 |
| 13296 /** |
| 13297 * Do any animation configuration here. |
| 13298 */ |
| 13299 // configure: function(config) { |
| 13300 // }, |
| 13301 |
| 13302 /** |
| 13303 * Returns the animation timing by mixing in properties from `config` to the
defaults defined |
| 13304 * by the animation. |
| 13305 */ |
| 13306 timingFromConfig: function(config) { |
| 13307 if (config.timing) { |
| 13308 for (var property in config.timing) { |
| 13309 this.animationTiming[property] = config.timing[property]; |
| 13310 } |
| 13311 } |
| 13312 return this.animationTiming; |
| 13313 }, |
| 13314 |
| 13315 /** |
| 13316 * Sets `transform` and `transformOrigin` properties along with the prefixed
versions. |
| 13317 */ |
| 13318 setPrefixedProperty: function(node, property, value) { |
| 13319 var map = { |
| 13320 'transform': ['webkitTransform'], |
| 13321 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] |
| 13322 }; |
| 13323 var prefixes = map[property]; |
| 13324 for (var prefix, index = 0; prefix = prefixes[index]; index++) { |
| 13325 node.style[prefix] = value; |
| 13326 } |
| 13327 node.style[property] = value; |
| 13328 }, |
| 13329 |
| 13330 /** |
| 13331 * Called when the animation finishes. |
| 13332 */ |
| 13333 complete: function() {} |
| 13334 |
| 13335 }; |
| 13336 Polymer({ |
| 13337 |
| 13338 is: 'opaque-animation', |
| 13339 |
| 13340 behaviors: [ |
| 13341 Polymer.NeonAnimationBehavior |
| 13342 ], |
| 13343 |
| 13344 configure: function(config) { |
| 13345 var node = config.node; |
| 13346 node.style.opacity = '0'; |
| 13347 this._effect = new KeyframeEffect(node, [ |
| 13348 {'opacity': '1'}, |
| 13349 {'opacity': '1'} |
| 13350 ], this.timingFromConfig(config)); |
| 13351 return this._effect; |
| 13352 }, |
| 13353 |
| 13354 complete: function(config) { |
| 13355 config.node.style.opacity = ''; |
| 13356 } |
| 13357 |
| 13358 }); |
| 13359 /** |
| 13360 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim
ations for use with |
| 13361 * elements implementing `Polymer.NeonAnimationRunnerBehavior`. |
| 13362 * @polymerBehavior |
| 13363 */ |
| 13364 Polymer.NeonAnimatableBehavior = { |
| 13365 |
| 13366 properties: { |
| 13367 |
| 13368 /** |
| 13369 * Animation configuration. See README for more info. |
| 13370 */ |
| 13371 animationConfig: { |
| 13372 type: Object |
| 13373 }, |
| 13374 |
| 13375 /** |
| 13376 * Convenience property for setting an 'entry' animation. Do not set `anim
ationConfig.entry` |
| 13377 * manually if using this. The animated node is set to `this` if using thi
s property. |
| 13378 */ |
| 13379 entryAnimation: { |
| 13380 observer: '_entryAnimationChanged', |
| 13381 type: String |
| 13382 }, |
| 13383 |
| 13384 /** |
| 13385 * Convenience property for setting an 'exit' animation. Do not set `anima
tionConfig.exit` |
| 13386 * manually if using this. The animated node is set to `this` if using thi
s property. |
| 13387 */ |
| 13388 exitAnimation: { |
| 13389 observer: '_exitAnimationChanged', |
| 13390 type: String |
| 13391 } |
| 13392 |
| 13393 }, |
| 13394 |
| 13395 _entryAnimationChanged: function() { |
| 13396 this.animationConfig = this.animationConfig || {}; |
| 13397 if (this.entryAnimation !== 'fade-in-animation') { |
| 13398 // insert polyfill hack |
| 13399 this.animationConfig['entry'] = [{ |
| 13400 name: 'opaque-animation', |
| 13401 node: this |
| 13402 }, { |
| 13403 name: this.entryAnimation, |
| 13404 node: this |
| 13405 }]; |
| 13406 } else { |
| 13407 this.animationConfig['entry'] = [{ |
| 13408 name: this.entryAnimation, |
| 13409 node: this |
| 13410 }]; |
| 13411 } |
| 13412 }, |
| 13413 |
| 13414 _exitAnimationChanged: function() { |
| 13415 this.animationConfig = this.animationConfig || {}; |
| 13416 this.animationConfig['exit'] = [{ |
| 13417 name: this.exitAnimation, |
| 13418 node: this |
| 13419 }]; |
| 13420 }, |
| 13421 |
| 13422 _copyProperties: function(config1, config2) { |
| 13423 // shallowly copy properties from config2 to config1 |
| 13424 for (var property in config2) { |
| 13425 config1[property] = config2[property]; |
| 13426 } |
| 13427 }, |
| 13428 |
| 13429 _cloneConfig: function(config) { |
| 13430 var clone = { |
| 13431 isClone: true |
| 13432 }; |
| 13433 this._copyProperties(clone, config); |
| 13434 return clone; |
| 13435 }, |
| 13436 |
| 13437 _getAnimationConfigRecursive: function(type, map, allConfigs) { |
| 13438 if (!this.animationConfig) { |
| 13439 return; |
| 13440 } |
| 13441 |
| 13442 // type is optional |
| 13443 var thisConfig; |
| 13444 if (type) { |
| 13445 thisConfig = this.animationConfig[type]; |
| 13446 } else { |
| 13447 thisConfig = this.animationConfig; |
| 13448 } |
| 13449 |
| 13450 if (!Array.isArray(thisConfig)) { |
| 13451 thisConfig = [thisConfig]; |
| 13452 } |
| 13453 |
| 13454 // iterate animations and recurse to process configurations from child nod
es |
| 13455 if (thisConfig) { |
| 13456 for (var config, index = 0; config = thisConfig[index]; index++) { |
| 13457 if (config.animatable) { |
| 13458 config.animatable._getAnimationConfigRecursive(config.type || type,
map, allConfigs); |
| 13459 } else { |
| 13460 if (config.id) { |
| 13461 var cachedConfig = map[config.id]; |
| 13462 if (cachedConfig) { |
| 13463 // merge configurations with the same id, making a clone lazily |
| 13464 if (!cachedConfig.isClone) { |
| 13465 map[config.id] = this._cloneConfig(cachedConfig) |
| 13466 cachedConfig = map[config.id]; |
| 13467 } |
| 13468 this._copyProperties(cachedConfig, config); |
| 13469 } else { |
| 13470 // put any configs with an id into a map |
| 13471 map[config.id] = config; |
| 13472 } |
| 13473 } else { |
| 13474 allConfigs.push(config); |
| 13475 } |
| 13476 } |
| 13477 } |
| 13478 } |
| 13479 }, |
| 13480 |
| 13481 /** |
| 13482 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this
method to configure |
| 13483 * an animation with an optional type. Elements implementing `Polymer.NeonAn
imatableBehavior` |
| 13484 * should define the property `animationConfig`, which is either a configura
tion object |
| 13485 * or a map of animation type to array of configuration objects. |
| 13486 */ |
| 13487 getAnimationConfig: function(type) { |
| 13488 var map = []; |
| 13489 var allConfigs = []; |
| 13490 this._getAnimationConfigRecursive(type, map, allConfigs); |
| 13491 // append the configurations saved in the map to the array |
| 13492 for (var key in map) { |
| 13493 allConfigs.push(map[key]); |
| 13494 } |
| 13495 return allConfigs; |
| 13496 } |
| 13497 |
| 13498 }; |
| 13499 /** |
| 13500 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations. |
| 13501 * |
| 13502 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior |
| 13503 */ |
| 13504 Polymer.NeonAnimationRunnerBehaviorImpl = { |
| 13505 |
| 13506 properties: { |
| 13507 |
| 13508 _animationMeta: { |
| 13509 type: Object, |
| 13510 value: function() { |
| 13511 return new Polymer.IronMeta({type: 'animation'}); |
| 13512 } |
| 13513 }, |
| 13514 |
| 13515 /** @type {?Object} */ |
| 13516 _player: { |
| 13517 type: Object |
| 13518 } |
| 13519 |
| 13520 }, |
| 13521 |
| 13522 _configureAnimationEffects: function(allConfigs) { |
| 13523 var allAnimations = []; |
| 13524 if (allConfigs.length > 0) { |
| 13525 for (var config, index = 0; config = allConfigs[index]; index++) { |
| 13526 var animationConstructor = this._animationMeta.byKey(config.name); |
| 13527 if (animationConstructor) { |
| 13528 var animation = animationConstructor && new animationConstructor(); |
| 13529 var effect = animation.configure(config); |
| 13530 if (effect) { |
| 13531 allAnimations.push({ |
| 13532 animation: animation, |
| 13533 config: config, |
| 13534 effect: effect |
| 13535 }); |
| 13536 } |
| 13537 } else { |
| 13538 console.warn(this.is + ':', config.name, 'not found!'); |
| 13539 } |
| 13540 } |
| 13541 } |
| 13542 return allAnimations; |
| 13543 }, |
| 13544 |
| 13545 _runAnimationEffects: function(allEffects) { |
| 13546 return document.timeline.play(new GroupEffect(allEffects)); |
| 13547 }, |
| 13548 |
| 13549 _completeAnimations: function(allAnimations) { |
| 13550 for (var animation, index = 0; animation = allAnimations[index]; index++)
{ |
| 13551 animation.animation.complete(animation.config); |
| 13552 } |
| 13553 }, |
| 13554 |
| 13555 /** |
| 13556 * Plays an animation with an optional `type`. |
| 13557 * @param {string=} type |
| 13558 * @param {!Object=} cookie |
| 13559 */ |
| 13560 playAnimation: function(type, cookie) { |
| 13561 var allConfigs = this.getAnimationConfig(type); |
| 13562 if (!allConfigs) { |
| 13563 return; |
| 13564 } |
| 13565 var allAnimations = this._configureAnimationEffects(allConfigs); |
| 13566 var allEffects = allAnimations.map(function(animation) { |
| 13567 return animation.effect; |
| 13568 }); |
| 13569 |
| 13570 if (allEffects.length > 0) { |
| 13571 this._player = this._runAnimationEffects(allEffects); |
| 13572 this._player.onfinish = function() { |
| 13573 this._completeAnimations(allAnimations); |
| 13574 |
| 13575 if (this._player) { |
| 13576 this._player.cancel(); |
| 13577 this._player = null; |
| 13578 } |
| 13579 |
| 13580 this.fire('neon-animation-finish', cookie, {bubbles: false}); |
| 13581 }.bind(this); |
| 13582 |
| 13583 } else { |
| 13584 this.fire('neon-animation-finish', cookie, {bubbles: false}); |
| 13585 } |
| 13586 }, |
| 13587 |
| 13588 /** |
| 13589 * Cancels the currently running animation. |
| 13590 */ |
| 13591 cancelAnimation: function() { |
| 13592 if (this._player) { |
| 13593 this._player.cancel(); |
| 13594 } |
| 13595 } |
| 13596 }; |
| 13597 |
| 13598 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ |
| 13599 Polymer.NeonAnimationRunnerBehavior = [ |
| 13600 Polymer.NeonAnimatableBehavior, |
| 13601 Polymer.NeonAnimationRunnerBehaviorImpl |
| 13602 ]; |
| 13603 (function() { |
| 13604 'use strict'; |
| 13605 |
| 13606 /** |
| 13607 * The IronDropdownScrollManager is intended to provide a central source |
| 13608 * of authority and control over which elements in a document are currently |
| 13609 * allowed to scroll. |
| 13610 */ |
| 13611 |
| 13612 Polymer.IronDropdownScrollManager = { |
| 13613 |
| 13614 /** |
| 13615 * The current element that defines the DOM boundaries of the |
| 13616 * scroll lock. This is always the most recently locking element. |
| 13617 */ |
| 13618 get currentLockingElement() { |
| 13619 return this._lockingElements[this._lockingElements.length - 1]; |
| 13620 }, |
| 13621 |
| 13622 |
| 13623 /** |
| 13624 * Returns true if the provided element is "scroll locked," which is to |
| 13625 * say that it cannot be scrolled via pointer or keyboard interactions. |
| 13626 * |
| 13627 * @param {HTMLElement} element An HTML element instance which may or may |
| 13628 * not be scroll locked. |
| 13629 */ |
| 13630 elementIsScrollLocked: function(element) { |
| 13631 var currentLockingElement = this.currentLockingElement; |
| 13632 var scrollLocked; |
| 13633 |
| 13634 if (this._hasCachedLockedElement(element)) { |
| 13635 return true; |
| 13636 } |
| 13637 |
| 13638 if (this._hasCachedUnlockedElement(element)) { |
| 13639 return false; |
| 13640 } |
| 13641 |
| 13642 scrollLocked = !!currentLockingElement && |
| 13643 currentLockingElement !== element && |
| 13644 !this._composedTreeContains(currentLockingElement, element); |
| 13645 |
| 13646 if (scrollLocked) { |
| 13647 this._lockedElementCache.push(element); |
| 13648 } else { |
| 13649 this._unlockedElementCache.push(element); |
| 13650 } |
| 13651 |
| 13652 return scrollLocked; |
| 13653 }, |
| 13654 |
| 13655 /** |
| 13656 * Push an element onto the current scroll lock stack. The most recently |
| 13657 * pushed element and its children will be considered scrollable. All |
| 13658 * other elements will not be scrollable. |
| 13659 * |
| 13660 * Scroll locking is implemented as a stack so that cases such as |
| 13661 * dropdowns within dropdowns are handled well. |
| 13662 * |
| 13663 * @param {HTMLElement} element The element that should lock scroll. |
| 13664 */ |
| 13665 pushScrollLock: function(element) { |
| 13666 if (this._lockingElements.length === 0) { |
| 13667 this._lockScrollInteractions(); |
| 13668 } |
| 13669 |
| 13670 this._lockingElements.push(element); |
| 13671 |
| 13672 this._lockedElementCache = []; |
| 13673 this._unlockedElementCache = []; |
| 13674 }, |
| 13675 |
| 13676 /** |
| 13677 * Remove an element from the scroll lock stack. The element being |
| 13678 * removed does not need to be the most recently pushed element. However, |
| 13679 * the scroll lock constraints only change when the most recently pushed |
| 13680 * element is removed. |
| 13681 * |
| 13682 * @param {HTMLElement} element The element to remove from the scroll |
| 13683 * lock stack. |
| 13684 */ |
| 13685 removeScrollLock: function(element) { |
| 13686 var index = this._lockingElements.indexOf(element); |
| 13687 |
| 13688 if (index === -1) { |
| 13689 return; |
| 13690 } |
| 13691 |
| 13692 this._lockingElements.splice(index, 1); |
| 13693 |
| 13694 this._lockedElementCache = []; |
| 13695 this._unlockedElementCache = []; |
| 13696 |
| 13697 if (this._lockingElements.length === 0) { |
| 13698 this._unlockScrollInteractions(); |
| 13699 } |
| 13700 }, |
| 13701 |
| 13702 _lockingElements: [], |
| 13703 |
| 13704 _lockedElementCache: null, |
| 13705 |
| 13706 _unlockedElementCache: null, |
| 13707 |
| 13708 _originalBodyStyles: {}, |
| 13709 |
| 13710 _isScrollingKeypress: function(event) { |
| 13711 return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys( |
| 13712 event, 'pageup pagedown home end up left down right'); |
| 13713 }, |
| 13714 |
| 13715 _hasCachedLockedElement: function(element) { |
| 13716 return this._lockedElementCache.indexOf(element) > -1; |
| 13717 }, |
| 13718 |
| 13719 _hasCachedUnlockedElement: function(element) { |
| 13720 return this._unlockedElementCache.indexOf(element) > -1; |
| 13721 }, |
| 13722 |
| 13723 _composedTreeContains: function(element, child) { |
| 13724 // NOTE(cdata): This method iterates over content elements and their |
| 13725 // corresponding distributed nodes to implement a contains-like method |
| 13726 // that pierces through the composed tree of the ShadowDOM. Results of |
| 13727 // this operation are cached (elsewhere) on a per-scroll-lock basis, to |
| 13728 // guard against potentially expensive lookups happening repeatedly as |
| 13729 // a user scrolls / touchmoves. |
| 13730 var contentElements; |
| 13731 var distributedNodes; |
| 13732 var contentIndex; |
| 13733 var nodeIndex; |
| 13734 |
| 13735 if (element.contains(child)) { |
| 13736 return true; |
| 13737 } |
| 13738 |
| 13739 contentElements = Polymer.dom(element).querySelectorAll('content'); |
| 13740 |
| 13741 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { |
| 13742 |
| 13743 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); |
| 13744 |
| 13745 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ |
| 13746 |
| 13747 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ |
| 13748 return true; |
| 13749 } |
| 13750 } |
| 13751 } |
| 13752 |
| 13753 return false; |
| 13754 }, |
| 13755 |
| 13756 _scrollInteractionHandler: function(event) { |
| 13757 if (Polymer |
| 13758 .IronDropdownScrollManager |
| 13759 .elementIsScrollLocked(event.target)) { |
| 13760 if (event.type === 'keydown' && |
| 13761 !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) { |
| 13762 return; |
| 13763 } |
| 13764 |
| 13765 event.preventDefault(); |
| 13766 } |
| 13767 }, |
| 13768 |
| 13769 _lockScrollInteractions: function() { |
| 13770 // Memoize body inline styles: |
| 13771 this._originalBodyStyles.overflow = document.body.style.overflow; |
| 13772 this._originalBodyStyles.overflowX = document.body.style.overflowX; |
| 13773 this._originalBodyStyles.overflowY = document.body.style.overflowY; |
| 13774 |
| 13775 // Disable overflow scrolling on body: |
| 13776 // TODO(cdata): It is technically not sufficient to hide overflow on |
| 13777 // body alone. A better solution might be to traverse all ancestors of |
| 13778 // the current scroll locking element and hide overflow on them. This |
| 13779 // becomes expensive, though, as it would have to be redone every time |
| 13780 // a new scroll locking element is added. |
| 13781 document.body.style.overflow = 'hidden'; |
| 13782 document.body.style.overflowX = 'hidden'; |
| 13783 document.body.style.overflowY = 'hidden'; |
| 13784 |
| 13785 // Modern `wheel` event for mouse wheel scrolling: |
| 13786 window.addEventListener('wheel', this._scrollInteractionHandler, true); |
| 13787 // Older, non-standard `mousewheel` event for some FF: |
| 13788 window.addEventListener('mousewheel', this._scrollInteractionHandler, tr
ue); |
| 13789 // IE: |
| 13790 window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler
, true); |
| 13791 // Mobile devices can scroll on touch move: |
| 13792 window.addEventListener('touchmove', this._scrollInteractionHandler, tru
e); |
| 13793 // Capture keydown to prevent scrolling keys (pageup, pagedown etc.) |
| 13794 document.addEventListener('keydown', this._scrollInteractionHandler, tru
e); |
| 13795 }, |
| 13796 |
| 13797 _unlockScrollInteractions: function() { |
| 13798 document.body.style.overflow = this._originalBodyStyles.overflow; |
| 13799 document.body.style.overflowX = this._originalBodyStyles.overflowX; |
| 13800 document.body.style.overflowY = this._originalBodyStyles.overflowY; |
| 13801 |
| 13802 window.removeEventListener('wheel', this._scrollInteractionHandler, true
); |
| 13803 window.removeEventListener('mousewheel', this._scrollInteractionHandler,
true); |
| 13804 window.removeEventListener('DOMMouseScroll', this._scrollInteractionHand
ler, true); |
| 13805 window.removeEventListener('touchmove', this._scrollInteractionHandler,
true); |
| 13806 document.removeEventListener('keydown', this._scrollInteractionHandler,
true); |
| 13807 } |
| 13808 }; |
| 13809 })(); |
| 13810 (function() { |
| 13811 'use strict'; |
| 13812 |
| 13813 Polymer({ |
| 13814 is: 'iron-dropdown', |
| 13815 |
| 13816 behaviors: [ |
| 13817 Polymer.IronControlState, |
| 13818 Polymer.IronA11yKeysBehavior, |
| 13819 Polymer.IronOverlayBehavior, |
| 13820 Polymer.NeonAnimationRunnerBehavior |
| 13821 ], |
| 13822 |
| 13823 properties: { |
| 13824 /** |
| 13825 * The orientation against which to align the dropdown content |
| 13826 * horizontally relative to the dropdown trigger. |
| 13827 */ |
| 13828 horizontalAlign: { |
| 13829 type: String, |
| 13830 value: 'left', |
| 13831 reflectToAttribute: true |
| 13832 }, |
| 13833 |
| 13834 /** |
| 13835 * The orientation against which to align the dropdown content |
| 13836 * vertically relative to the dropdown trigger. |
| 13837 */ |
| 13838 verticalAlign: { |
| 13839 type: String, |
| 13840 value: 'top', |
| 13841 reflectToAttribute: true |
| 13842 }, |
| 13843 |
| 13844 /** |
| 13845 * A pixel value that will be added to the position calculated for the |
| 13846 * given `horizontalAlign`. Use a negative value to offset to the |
| 13847 * left, or a positive value to offset to the right. |
| 13848 */ |
| 13849 horizontalOffset: { |
| 13850 type: Number, |
| 13851 value: 0, |
| 13852 notify: true |
| 13853 }, |
| 13854 |
| 13855 /** |
| 13856 * A pixel value that will be added to the position calculated for the |
| 13857 * given `verticalAlign`. Use a negative value to offset towards the |
| 13858 * top, or a positive value to offset towards the bottom. |
| 13859 */ |
| 13860 verticalOffset: { |
| 13861 type: Number, |
| 13862 value: 0, |
| 13863 notify: true |
| 13864 }, |
| 13865 |
| 13866 /** |
| 13867 * The element that should be used to position the dropdown when |
| 13868 * it is opened. |
| 13869 */ |
| 13870 positionTarget: { |
| 13871 type: Object, |
| 13872 observer: '_positionTargetChanged' |
| 13873 }, |
| 13874 |
| 13875 /** |
| 13876 * An animation config. If provided, this will be used to animate the |
| 13877 * opening of the dropdown. |
| 13878 */ |
| 13879 openAnimationConfig: { |
| 13880 type: Object |
| 13881 }, |
| 13882 |
| 13883 /** |
| 13884 * An animation config. If provided, this will be used to animate the |
| 13885 * closing of the dropdown. |
| 13886 */ |
| 13887 closeAnimationConfig: { |
| 13888 type: Object |
| 13889 }, |
| 13890 |
| 13891 /** |
| 13892 * If provided, this will be the element that will be focused when |
| 13893 * the dropdown opens. |
| 13894 */ |
| 13895 focusTarget: { |
| 13896 type: Object |
| 13897 }, |
| 13898 |
| 13899 /** |
| 13900 * Set to true to disable animations when opening and closing the |
| 13901 * dropdown. |
| 13902 */ |
| 13903 noAnimations: { |
| 13904 type: Boolean, |
| 13905 value: false |
| 13906 }, |
| 13907 |
| 13908 /** |
| 13909 * By default, the dropdown will constrain scrolling on the page |
| 13910 * to itself when opened. |
| 13911 * Set to true in order to prevent scroll from being constrained |
| 13912 * to the dropdown when it opens. |
| 13913 */ |
| 13914 allowOutsideScroll: { |
| 13915 type: Boolean, |
| 13916 value: false |
| 13917 }, |
| 13918 |
| 13919 /** |
| 13920 * We memoize the positionTarget bounding rectangle so that we can |
| 13921 * limit the number of times it is queried per resize / relayout. |
| 13922 * @type {?Object} |
| 13923 */ |
| 13924 _positionRectMemo: { |
| 13925 type: Object |
| 13926 } |
| 13927 }, |
| 13928 |
| 13929 listeners: { |
| 13930 'neon-animation-finish': '_onNeonAnimationFinish' |
| 13931 }, |
| 13932 |
| 13933 observers: [ |
| 13934 '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset
, horizontalOffset)' |
| 13935 ], |
| 13936 |
| 13937 attached: function() { |
| 13938 if (this.positionTarget === undefined) { |
| 13939 this.positionTarget = this._defaultPositionTarget; |
| 13940 } |
| 13941 }, |
| 13942 |
| 13943 /** |
| 13944 * The element that is contained by the dropdown, if any. |
| 13945 */ |
| 13946 get containedElement() { |
| 13947 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 13948 }, |
| 13949 |
| 13950 /** |
| 13951 * The element that should be focused when the dropdown opens. |
| 13952 */ |
| 13953 get _focusTarget() { |
| 13954 return this.focusTarget || this.containedElement; |
| 13955 }, |
| 13956 |
| 13957 /** |
| 13958 * The element that should be used to position the dropdown when |
| 13959 * it opens, if no position target is configured. |
| 13960 */ |
| 13961 get _defaultPositionTarget() { |
| 13962 var parent = Polymer.dom(this).parentNode; |
| 13963 |
| 13964 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 13965 parent = parent.host; |
| 13966 } |
| 13967 |
| 13968 return parent; |
| 13969 }, |
| 13970 |
| 13971 /** |
| 13972 * The bounding rect of the position target. |
| 13973 */ |
| 13974 get _positionRect() { |
| 13975 if (!this._positionRectMemo && this.positionTarget) { |
| 13976 this._positionRectMemo = this.positionTarget.getBoundingClientRect()
; |
| 13977 } |
| 13978 |
| 13979 return this._positionRectMemo; |
| 13980 }, |
| 13981 |
| 13982 /** |
| 13983 * The horizontal offset value used to position the dropdown. |
| 13984 */ |
| 13985 get _horizontalAlignTargetValue() { |
| 13986 var target; |
| 13987 |
| 13988 if (this.horizontalAlign === 'right') { |
| 13989 target = document.documentElement.clientWidth - this._positionRect.r
ight; |
| 13990 } else { |
| 13991 target = this._positionRect.left; |
| 13992 } |
| 13993 |
| 13994 target += this.horizontalOffset; |
| 13995 |
| 13996 return Math.max(target, 0); |
| 13997 }, |
| 13998 |
| 13999 /** |
| 14000 * The vertical offset value used to position the dropdown. |
| 14001 */ |
| 14002 get _verticalAlignTargetValue() { |
| 14003 var target; |
| 14004 |
| 14005 if (this.verticalAlign === 'bottom') { |
| 14006 target = document.documentElement.clientHeight - this._positionRect.
bottom; |
| 14007 } else { |
| 14008 target = this._positionRect.top; |
| 14009 } |
| 14010 |
| 14011 target += this.verticalOffset; |
| 14012 |
| 14013 return Math.max(target, 0); |
| 14014 }, |
| 14015 |
| 14016 /** |
| 14017 * Called when the value of `opened` changes. |
| 14018 * |
| 14019 * @param {boolean} opened True if the dropdown is opened. |
| 14020 */ |
| 14021 _openedChanged: function(opened) { |
| 14022 if (opened && this.disabled) { |
| 14023 this.cancel(); |
| 14024 } else { |
| 14025 this.cancelAnimation(); |
| 14026 this._prepareDropdown(); |
| 14027 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); |
| 14028 } |
| 14029 |
| 14030 if (this.opened) { |
| 14031 this._focusContent(); |
| 14032 } |
| 14033 }, |
| 14034 |
| 14035 /** |
| 14036 * Overridden from `IronOverlayBehavior`. |
| 14037 */ |
| 14038 _renderOpened: function() { |
| 14039 if (!this.allowOutsideScroll) { |
| 14040 Polymer.IronDropdownScrollManager.pushScrollLock(this); |
| 14041 } |
| 14042 |
| 14043 if (!this.noAnimations && this.animationConfig && this.animationConfig
.open) { |
| 14044 this.$.contentWrapper.classList.add('animating'); |
| 14045 this.playAnimation('open'); |
| 14046 } else { |
| 14047 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; |
| 14048 } |
| 14049 }, |
| 14050 |
| 14051 /** |
| 14052 * Overridden from `IronOverlayBehavior`. |
| 14053 */ |
| 14054 _renderClosed: function() { |
| 14055 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
| 14056 if (!this.noAnimations && this.animationConfig && this.animationConfig
.close) { |
| 14057 this.$.contentWrapper.classList.add('animating'); |
| 14058 this.playAnimation('close'); |
| 14059 } else { |
| 14060 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; |
| 14061 } |
| 14062 }, |
| 14063 |
| 14064 /** |
| 14065 * Called when animation finishes on the dropdown (when opening or |
| 14066 * closing). Responsible for "completing" the process of opening or |
| 14067 * closing the dropdown by positioning it or setting its display to |
| 14068 * none. |
| 14069 */ |
| 14070 _onNeonAnimationFinish: function() { |
| 14071 this.$.contentWrapper.classList.remove('animating'); |
| 14072 if (this.opened) { |
| 14073 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this); |
| 14074 } else { |
| 14075 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this); |
| 14076 } |
| 14077 }, |
| 14078 |
| 14079 /** |
| 14080 * Called when an `iron-resize` event fires. |
| 14081 */ |
| 14082 _onIronResize: function() { |
| 14083 var containedElement = this.containedElement; |
| 14084 var scrollTop; |
| 14085 var scrollLeft; |
| 14086 |
| 14087 if (containedElement) { |
| 14088 scrollTop = containedElement.scrollTop; |
| 14089 scrollLeft = containedElement.scrollLeft; |
| 14090 } |
| 14091 |
| 14092 if (this.opened) { |
| 14093 this._updateOverlayPosition(); |
| 14094 } |
| 14095 |
| 14096 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments); |
| 14097 |
| 14098 if (containedElement) { |
| 14099 containedElement.scrollTop = scrollTop; |
| 14100 containedElement.scrollLeft = scrollLeft; |
| 14101 } |
| 14102 }, |
| 14103 |
| 14104 /** |
| 14105 * Called when the `positionTarget` property changes. |
| 14106 */ |
| 14107 _positionTargetChanged: function() { |
| 14108 this._updateOverlayPosition(); |
| 14109 }, |
| 14110 |
| 14111 /** |
| 14112 * Constructs the final animation config from different properties used |
| 14113 * to configure specific parts of the opening and closing animations. |
| 14114 */ |
| 14115 _updateAnimationConfig: function() { |
| 14116 var animationConfig = {}; |
| 14117 var animations = []; |
| 14118 |
| 14119 if (this.openAnimationConfig) { |
| 14120 // NOTE(cdata): When making `display:none` elements visible in Safar
i, |
| 14121 // the element will paint once in a fully visible state, causing the |
| 14122 // dropdown to flash before it fades in. We prepend an |
| 14123 // `opaque-animation` to fix this problem: |
| 14124 animationConfig.open = [{ |
| 14125 name: 'opaque-animation', |
| 14126 }].concat(this.openAnimationConfig); |
| 14127 animations = animations.concat(animationConfig.open); |
| 14128 } |
| 14129 |
| 14130 if (this.closeAnimationConfig) { |
| 14131 animationConfig.close = this.closeAnimationConfig; |
| 14132 animations = animations.concat(animationConfig.close); |
| 14133 } |
| 14134 |
| 14135 animations.forEach(function(animation) { |
| 14136 animation.node = this.containedElement; |
| 14137 }, this); |
| 14138 |
| 14139 this.animationConfig = animationConfig; |
| 14140 }, |
| 14141 |
| 14142 /** |
| 14143 * Prepares the dropdown for opening by updating measured layout |
| 14144 * values. |
| 14145 */ |
| 14146 _prepareDropdown: function() { |
| 14147 this.sizingTarget = this.containedElement || this.sizingTarget; |
| 14148 this._updateAnimationConfig(); |
| 14149 this._updateOverlayPosition(); |
| 14150 }, |
| 14151 |
| 14152 /** |
| 14153 * Updates the overlay position based on configured horizontal |
| 14154 * and vertical alignment, and re-memoizes these values for the sake |
| 14155 * of behavior in `IronFitBehavior`. |
| 14156 */ |
| 14157 _updateOverlayPosition: function() { |
| 14158 this._positionRectMemo = null; |
| 14159 |
| 14160 if (!this.positionTarget) { |
| 14161 return; |
| 14162 } |
| 14163 |
| 14164 this.style[this.horizontalAlign] = |
| 14165 this._horizontalAlignTargetValue + 'px'; |
| 14166 |
| 14167 this.style[this.verticalAlign] = |
| 14168 this._verticalAlignTargetValue + 'px'; |
| 14169 |
| 14170 // NOTE(cdata): We re-memoize inline styles here, otherwise |
| 14171 // calling `refit` from `IronFitBehavior` will reset inline styles |
| 14172 // to whatever they were when the dropdown first opened. |
| 14173 if (this._fitInfo) { |
| 14174 this._fitInfo.inlineStyle[this.horizontalAlign] = |
| 14175 this.style[this.horizontalAlign]; |
| 14176 |
| 14177 this._fitInfo.inlineStyle[this.verticalAlign] = |
| 14178 this.style[this.verticalAlign]; |
| 14179 } |
| 14180 }, |
| 14181 |
| 14182 /** |
| 14183 * Focuses the configured focus target. |
| 14184 */ |
| 14185 _focusContent: function() { |
| 14186 // NOTE(cdata): This is async so that it can attempt the focus after |
| 14187 // `display: none` is removed from the element. |
| 14188 this.async(function() { |
| 14189 if (this._focusTarget) { |
| 14190 this._focusTarget.focus(); |
| 14191 } |
| 14192 }); |
| 14193 } |
| 14194 }); |
| 14195 })(); |
| 14196 Polymer({ |
| 14197 |
| 14198 is: 'fade-in-animation', |
| 14199 |
| 14200 behaviors: [ |
| 14201 Polymer.NeonAnimationBehavior |
| 14202 ], |
| 14203 |
| 14204 configure: function(config) { |
| 14205 var node = config.node; |
| 14206 this._effect = new KeyframeEffect(node, [ |
| 14207 {'opacity': '0'}, |
| 14208 {'opacity': '1'} |
| 14209 ], this.timingFromConfig(config)); |
| 14210 return this._effect; |
| 14211 } |
| 14212 |
| 14213 }); |
| 14214 Polymer({ |
| 14215 |
| 14216 is: 'fade-out-animation', |
| 14217 |
| 14218 behaviors: [ |
| 14219 Polymer.NeonAnimationBehavior |
| 14220 ], |
| 14221 |
| 14222 configure: function(config) { |
| 14223 var node = config.node; |
| 14224 this._effect = new KeyframeEffect(node, [ |
| 14225 {'opacity': '1'}, |
| 14226 {'opacity': '0'} |
| 14227 ], this.timingFromConfig(config)); |
| 14228 return this._effect; |
| 14229 } |
| 14230 |
| 14231 }); |
| 14232 Polymer({ |
| 14233 is: 'paper-menu-grow-height-animation', |
| 14234 |
| 14235 behaviors: [ |
| 14236 Polymer.NeonAnimationBehavior |
| 14237 ], |
| 14238 |
| 14239 configure: function(config) { |
| 14240 var node = config.node; |
| 14241 var rect = node.getBoundingClientRect(); |
| 14242 var height = rect.height; |
| 14243 |
| 14244 this._effect = new KeyframeEffect(node, [{ |
| 14245 height: (height / 2) + 'px' |
| 14246 }, { |
| 14247 height: height + 'px' |
| 14248 }], this.timingFromConfig(config)); |
| 14249 |
| 14250 return this._effect; |
| 14251 } |
| 14252 }); |
| 14253 |
| 14254 Polymer({ |
| 14255 is: 'paper-menu-grow-width-animation', |
| 14256 |
| 14257 behaviors: [ |
| 14258 Polymer.NeonAnimationBehavior |
| 14259 ], |
| 14260 |
| 14261 configure: function(config) { |
| 14262 var node = config.node; |
| 14263 var rect = node.getBoundingClientRect(); |
| 14264 var width = rect.width; |
| 14265 |
| 14266 this._effect = new KeyframeEffect(node, [{ |
| 14267 width: (width / 2) + 'px' |
| 14268 }, { |
| 14269 width: width + 'px' |
| 14270 }], this.timingFromConfig(config)); |
| 14271 |
| 14272 return this._effect; |
| 14273 } |
| 14274 }); |
| 14275 |
| 14276 Polymer({ |
| 14277 is: 'paper-menu-shrink-width-animation', |
| 14278 |
| 14279 behaviors: [ |
| 14280 Polymer.NeonAnimationBehavior |
| 14281 ], |
| 14282 |
| 14283 configure: function(config) { |
| 14284 var node = config.node; |
| 14285 var rect = node.getBoundingClientRect(); |
| 14286 var width = rect.width; |
| 14287 |
| 14288 this._effect = new KeyframeEffect(node, [{ |
| 14289 width: width + 'px' |
| 14290 }, { |
| 14291 width: width - (width / 20) + 'px' |
| 14292 }], this.timingFromConfig(config)); |
| 14293 |
| 14294 return this._effect; |
| 14295 } |
| 14296 }); |
| 14297 |
| 14298 Polymer({ |
| 14299 is: 'paper-menu-shrink-height-animation', |
| 14300 |
| 14301 behaviors: [ |
| 14302 Polymer.NeonAnimationBehavior |
| 14303 ], |
| 14304 |
| 14305 configure: function(config) { |
| 14306 var node = config.node; |
| 14307 var rect = node.getBoundingClientRect(); |
| 14308 var height = rect.height; |
| 14309 var top = rect.top; |
| 14310 |
| 14311 this.setPrefixedProperty(node, 'transformOrigin', '0 0'); |
| 14312 |
| 14313 this._effect = new KeyframeEffect(node, [{ |
| 14314 height: height + 'px', |
| 14315 transform: 'translateY(0)' |
| 14316 }, { |
| 14317 height: height / 2 + 'px', |
| 14318 transform: 'translateY(-20px)' |
| 14319 }], this.timingFromConfig(config)); |
| 14320 |
| 14321 return this._effect; |
| 14322 } |
| 14323 }); |
| 14324 (function() { |
| 14325 'use strict'; |
| 14326 |
| 14327 var PaperMenuButton = Polymer({ |
| 14328 is: 'paper-menu-button', |
| 14329 |
| 14330 /** |
| 14331 * Fired when the dropdown opens. |
| 14332 * |
| 14333 * @event paper-dropdown-open |
| 14334 */ |
| 14335 |
| 14336 /** |
| 14337 * Fired when the dropdown closes. |
| 14338 * |
| 14339 * @event paper-dropdown-close |
| 14340 */ |
| 14341 |
| 14342 behaviors: [ |
| 14343 Polymer.IronA11yKeysBehavior, |
| 14344 Polymer.IronControlState |
| 14345 ], |
| 14346 |
| 14347 properties: { |
| 14348 |
| 14349 /** |
| 14350 * True if the content is currently displayed. |
| 14351 */ |
| 14352 opened: { |
| 14353 type: Boolean, |
| 14354 value: false, |
| 14355 notify: true, |
| 14356 observer: '_openedChanged' |
| 14357 }, |
| 14358 |
| 14359 /** |
| 14360 * The orientation against which to align the menu dropdown |
| 14361 * horizontally relative to the dropdown trigger. |
| 14362 */ |
| 14363 horizontalAlign: { |
| 14364 type: String, |
| 14365 value: 'left', |
| 14366 reflectToAttribute: true |
| 14367 }, |
| 14368 |
| 14369 /** |
| 14370 * The orientation against which to align the menu dropdown |
| 14371 * vertically relative to the dropdown trigger. |
| 14372 */ |
| 14373 verticalAlign: { |
| 14374 type: String, |
| 14375 value: 'top', |
| 14376 reflectToAttribute: true |
| 14377 }, |
| 14378 |
| 14379 /** |
| 14380 * A pixel value that will be added to the position calculated for the |
| 14381 * given `horizontalAlign`. Use a negative value to offset to the |
| 14382 * left, or a positive value to offset to the right. |
| 14383 */ |
| 14384 horizontalOffset: { |
| 14385 type: Number, |
| 14386 value: 0, |
| 14387 notify: true |
| 14388 }, |
| 14389 |
| 14390 /** |
| 14391 * A pixel value that will be added to the position calculated for the |
| 14392 * given `verticalAlign`. Use a negative value to offset towards the |
| 14393 * top, or a positive value to offset towards the bottom. |
| 14394 */ |
| 14395 verticalOffset: { |
| 14396 type: Number, |
| 14397 value: 0, |
| 14398 notify: true |
| 14399 }, |
| 14400 |
| 14401 /** |
| 14402 * Set to true to disable animations when opening and closing the |
| 14403 * dropdown. |
| 14404 */ |
| 14405 noAnimations: { |
| 14406 type: Boolean, |
| 14407 value: false |
| 14408 }, |
| 14409 |
| 14410 /** |
| 14411 * Set to true to disable automatically closing the dropdown after |
| 14412 * a selection has been made. |
| 14413 */ |
| 14414 ignoreSelect: { |
| 14415 type: Boolean, |
| 14416 value: false |
| 14417 }, |
| 14418 |
| 14419 /** |
| 14420 * An animation config. If provided, this will be used to animate the |
| 14421 * opening of the dropdown. |
| 14422 */ |
| 14423 openAnimationConfig: { |
| 14424 type: Object, |
| 14425 value: function() { |
| 14426 return [{ |
| 14427 name: 'fade-in-animation', |
| 14428 timing: { |
| 14429 delay: 100, |
| 14430 duration: 200 |
| 14431 } |
| 14432 }, { |
| 14433 name: 'paper-menu-grow-width-animation', |
| 14434 timing: { |
| 14435 delay: 100, |
| 14436 duration: 150, |
| 14437 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER |
| 14438 } |
| 14439 }, { |
| 14440 name: 'paper-menu-grow-height-animation', |
| 14441 timing: { |
| 14442 delay: 100, |
| 14443 duration: 275, |
| 14444 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER |
| 14445 } |
| 14446 }]; |
| 14447 } |
| 14448 }, |
| 14449 |
| 14450 /** |
| 14451 * An animation config. If provided, this will be used to animate the |
| 14452 * closing of the dropdown. |
| 14453 */ |
| 14454 closeAnimationConfig: { |
| 14455 type: Object, |
| 14456 value: function() { |
| 14457 return [{ |
| 14458 name: 'fade-out-animation', |
| 14459 timing: { |
| 14460 duration: 150 |
| 14461 } |
| 14462 }, { |
| 14463 name: 'paper-menu-shrink-width-animation', |
| 14464 timing: { |
| 14465 delay: 100, |
| 14466 duration: 50, |
| 14467 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER |
| 14468 } |
| 14469 }, { |
| 14470 name: 'paper-menu-shrink-height-animation', |
| 14471 timing: { |
| 14472 duration: 200, |
| 14473 easing: 'ease-in' |
| 14474 } |
| 14475 }]; |
| 14476 } |
| 14477 }, |
| 14478 |
| 14479 /** |
| 14480 * This is the element intended to be bound as the focus target |
| 14481 * for the `iron-dropdown` contained by `paper-menu-button`. |
| 14482 */ |
| 14483 _dropdownContent: { |
| 14484 type: Object |
| 14485 } |
| 14486 }, |
| 14487 |
| 14488 hostAttributes: { |
| 14489 role: 'group', |
| 14490 'aria-haspopup': 'true' |
| 14491 }, |
| 14492 |
| 14493 listeners: { |
| 14494 'iron-select': '_onIronSelect' |
| 14495 }, |
| 14496 |
| 14497 /** |
| 14498 * The content element that is contained by the menu button, if any. |
| 14499 */ |
| 14500 get contentElement() { |
| 14501 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 14502 }, |
| 14503 |
| 14504 /** |
| 14505 * Make the dropdown content appear as an overlay positioned relative |
| 14506 * to the dropdown trigger. |
| 14507 */ |
| 14508 open: function() { |
| 14509 if (this.disabled) { |
| 14510 return; |
| 14511 } |
| 14512 |
| 14513 this.$.dropdown.open(); |
| 14514 }, |
| 14515 |
| 14516 /** |
| 14517 * Hide the dropdown content. |
| 14518 */ |
| 14519 close: function() { |
| 14520 this.$.dropdown.close(); |
| 14521 }, |
| 14522 |
| 14523 /** |
| 14524 * When an `iron-select` event is received, the dropdown should |
| 14525 * automatically close on the assumption that a value has been chosen. |
| 14526 * |
| 14527 * @param {CustomEvent} event A CustomEvent instance with type |
| 14528 * set to `"iron-select"`. |
| 14529 */ |
| 14530 _onIronSelect: function(event) { |
| 14531 if (!this.ignoreSelect) { |
| 14532 this.close(); |
| 14533 } |
| 14534 }, |
| 14535 |
| 14536 /** |
| 14537 * When the dropdown opens, the `paper-menu-button` fires `paper-open`. |
| 14538 * When the dropdown closes, the `paper-menu-button` fires `paper-close`. |
| 14539 * |
| 14540 * @param {boolean} opened True if the dropdown is opened, otherwise false
. |
| 14541 * @param {boolean} oldOpened The previous value of `opened`. |
| 14542 */ |
| 14543 _openedChanged: function(opened, oldOpened) { |
| 14544 if (opened) { |
| 14545 // TODO(cdata): Update this when we can measure changes in distributed |
| 14546 // children in an idiomatic way. |
| 14547 // We poke this property in case the element has changed. This will |
| 14548 // cause the focus target for the `iron-dropdown` to be updated as |
| 14549 // necessary: |
| 14550 this._dropdownContent = this.contentElement; |
| 14551 this.fire('paper-dropdown-open'); |
| 14552 } else if (oldOpened != null) { |
| 14553 this.fire('paper-dropdown-close'); |
| 14554 } |
| 14555 }, |
| 14556 |
| 14557 /** |
| 14558 * If the dropdown is open when disabled becomes true, close the |
| 14559 * dropdown. |
| 14560 * |
| 14561 * @param {boolean} disabled True if disabled, otherwise false. |
| 14562 */ |
| 14563 _disabledChanged: function(disabled) { |
| 14564 Polymer.IronControlState._disabledChanged.apply(this, arguments); |
| 14565 if (disabled && this.opened) { |
| 14566 this.close(); |
| 14567 } |
| 14568 } |
| 14569 }); |
| 14570 |
| 14571 PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)'; |
| 14572 PaperMenuButton.MAX_ANIMATION_TIME_MS = 400; |
| 14573 |
| 14574 Polymer.PaperMenuButton = PaperMenuButton; |
| 14575 })(); |
| 14576 /** |
| 14577 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k
eyboard focus. |
| 14578 * |
| 14579 * @polymerBehavior Polymer.PaperInkyFocusBehavior |
| 14580 */ |
| 14581 Polymer.PaperInkyFocusBehaviorImpl = { |
| 14582 |
| 14583 observers: [ |
| 14584 '_focusedChanged(receivedFocusFromKeyboard)' |
| 14585 ], |
| 14586 |
| 14587 _focusedChanged: function(receivedFocusFromKeyboard) { |
| 14588 if (!this.$.ink) { |
| 14589 return; |
| 14590 } |
| 14591 |
| 14592 this.$.ink.holdDown = receivedFocusFromKeyboard; |
| 14593 } |
| 14594 |
| 14595 }; |
| 14596 |
| 14597 /** @polymerBehavior Polymer.PaperInkyFocusBehavior */ |
| 14598 Polymer.PaperInkyFocusBehavior = [ |
| 14599 Polymer.IronButtonState, |
| 14600 Polymer.IronControlState, |
| 14601 Polymer.PaperInkyFocusBehaviorImpl |
| 14602 ]; |
| 14603 Polymer({ |
| 14604 is: 'paper-icon-button', |
| 14605 |
| 14606 hostAttributes: { |
| 14607 role: 'button', |
| 14608 tabindex: '0' |
| 14609 }, |
| 14610 |
| 14611 behaviors: [ |
| 14612 Polymer.PaperInkyFocusBehavior |
| 14613 ], |
| 14614 |
| 14615 properties: { |
| 14616 /** |
| 14617 * The URL of an image for the icon. If the src property is specified, |
| 14618 * the icon property should not be. |
| 14619 */ |
| 14620 src: { |
| 14621 type: String |
| 14622 }, |
| 14623 |
| 14624 /** |
| 14625 * Specifies the icon name or index in the set of icons available in |
| 14626 * the icon's icon set. If the icon property is specified, |
| 14627 * the src property should not be. |
| 14628 */ |
| 14629 icon: { |
| 14630 type: String |
| 14631 }, |
| 14632 |
| 14633 /** |
| 14634 * Specifies the alternate text for the button, for accessibility. |
| 14635 */ |
| 14636 alt: { |
| 14637 type: String, |
| 14638 observer: "_altChanged" |
| 14639 } |
| 14640 }, |
| 14641 |
| 14642 _altChanged: function(newValue, oldValue) { |
| 14643 var label = this.getAttribute('aria-label'); |
| 14644 |
| 14645 // Don't stomp over a user-set aria-label. |
| 14646 if (!label || oldValue == label) { |
| 14647 this.setAttribute('aria-label', newValue); |
| 14648 } |
| 14649 } |
| 14650 }); |
| 14651 /** |
| 14652 * Use `Polymer.IronValidatableBehavior` to implement an element that validate
s user input. |
| 14653 * |
| 14654 * ### Accessibility |
| 14655 * |
| 14656 * Changing the `invalid` property, either manually or by calling `validate()`
will update the |
| 14657 * `aria-invalid` attribute. |
| 14658 * |
| 14659 * @demo demo/index.html |
| 14660 * @polymerBehavior |
| 14661 */ |
| 14662 Polymer.IronValidatableBehavior = { |
| 14663 |
| 14664 properties: { |
| 14665 |
| 14666 /** |
| 14667 * Namespace for this validator. |
| 14668 */ |
| 14669 validatorType: { |
| 14670 type: String, |
| 14671 value: 'validator' |
| 14672 }, |
| 14673 |
| 14674 /** |
| 14675 * Name of the validator to use. |
| 14676 */ |
| 14677 validator: { |
| 14678 type: String |
| 14679 }, |
| 14680 |
| 14681 /** |
| 14682 * True if the last call to `validate` is invalid. |
| 14683 */ |
| 14684 invalid: { |
| 14685 notify: true, |
| 14686 reflectToAttribute: true, |
| 14687 type: Boolean, |
| 14688 value: false |
| 14689 }, |
| 14690 |
| 14691 _validatorMeta: { |
| 14692 type: Object |
| 14693 } |
| 14694 |
| 14695 }, |
| 14696 |
| 14697 observers: [ |
| 14698 '_invalidChanged(invalid)' |
| 14699 ], |
| 14700 |
| 14701 get _validator() { |
| 14702 return this._validatorMeta && this._validatorMeta.byKey(this.validator); |
| 14703 }, |
| 14704 |
| 14705 ready: function() { |
| 14706 this._validatorMeta = new Polymer.IronMeta({type: this.validatorType}); |
| 14707 }, |
| 14708 |
| 14709 _invalidChanged: function() { |
| 14710 if (this.invalid) { |
| 14711 this.setAttribute('aria-invalid', 'true'); |
| 14712 } else { |
| 14713 this.removeAttribute('aria-invalid'); |
| 14714 } |
| 14715 }, |
| 14716 |
| 14717 /** |
| 14718 * @return {boolean} True if the validator `validator` exists. |
| 14719 */ |
| 14720 hasValidator: function() { |
| 14721 return this._validator != null; |
| 14722 }, |
| 14723 |
| 14724 /** |
| 14725 * Returns true if the `value` is valid, and updates `invalid`. If you want |
| 14726 * your element to have custom validation logic, do not override this method
; |
| 14727 * override `_getValidity(value)` instead. |
| 14728 |
| 14729 * @param {Object} value The value to be validated. By default, it is passed |
| 14730 * to the validator's `validate()` function, if a validator is set. |
| 14731 * @return {boolean} True if `value` is valid. |
| 14732 */ |
| 14733 validate: function(value) { |
| 14734 this.invalid = !this._getValidity(value); |
| 14735 return !this.invalid; |
| 14736 }, |
| 14737 |
| 14738 /** |
| 14739 * Returns true if `value` is valid. By default, it is passed |
| 14740 * to the validator's `validate()` function, if a validator is set. You |
| 14741 * should override this method if you want to implement custom validity |
| 14742 * logic for your element. |
| 14743 * |
| 14744 * @param {Object} value The value to be validated. |
| 14745 * @return {boolean} True if `value` is valid. |
| 14746 */ |
| 14747 |
| 14748 _getValidity: function(value) { |
| 14749 if (this.hasValidator()) { |
| 14750 return this._validator.validate(value); |
| 14751 } |
| 14752 return true; |
| 14753 } |
| 14754 }; |
| 14755 /* |
| 14756 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal
idatorBehavior` |
| 14757 to `<input>`. |
| 14758 |
| 14759 ### Two-way binding |
| 14760 |
| 14761 By default you can only get notified of changes to an `input`'s `value` due to u
ser input: |
| 14762 |
| 14763 <input value="{{myValue::input}}"> |
| 14764 |
| 14765 `iron-input` adds the `bind-value` property that mirrors the `value` property, a
nd can be used |
| 14766 for two-way data binding. `bind-value` will notify if it is changed either by us
er input or by script. |
| 14767 |
| 14768 <input is="iron-input" bind-value="{{myValue}}"> |
| 14769 |
| 14770 ### Custom validators |
| 14771 |
| 14772 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit
h `<iron-input>`. |
| 14773 |
| 14774 <input is="iron-input" validator="my-custom-validator"> |
| 14775 |
| 14776 ### Stopping invalid input |
| 14777 |
| 14778 It may be desirable to only allow users to enter certain characters. You can use
the |
| 14779 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish
this. This feature |
| 14780 is separate from validation, and `allowed-pattern` does not affect how the input
is validated. |
| 14781 |
| 14782 <!-- only allow characters that match [0-9] --> |
| 14783 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> |
| 14784 |
| 14785 @hero hero.svg |
| 14786 @demo demo/index.html |
| 14787 */ |
| 14788 |
| 14789 Polymer({ |
| 14790 |
| 14791 is: 'iron-input', |
| 14792 |
| 14793 extends: 'input', |
| 14794 |
| 14795 behaviors: [ |
| 14796 Polymer.IronValidatableBehavior |
| 14797 ], |
| 14798 |
| 14799 properties: { |
| 14800 |
| 14801 /** |
| 14802 * Use this property instead of `value` for two-way data binding. |
| 14803 */ |
| 14804 bindValue: { |
| 14805 observer: '_bindValueChanged', |
| 14806 type: String |
| 14807 }, |
| 14808 |
| 14809 /** |
| 14810 * Set to true to prevent the user from entering invalid input. The new in
put characters are |
| 14811 * matched with `allowedPattern` if it is set, otherwise it will use the `
pattern` attribute if |
| 14812 * set, or the `type` attribute (only supported for `type=number`). |
| 14813 */ |
| 14814 preventInvalidInput: { |
| 14815 type: Boolean |
| 14816 }, |
| 14817 |
| 14818 /** |
| 14819 * Regular expression to match valid input characters. |
| 14820 */ |
| 14821 allowedPattern: { |
| 14822 type: String |
| 14823 }, |
| 14824 |
| 14825 _previousValidInput: { |
| 14826 type: String, |
| 14827 value: '' |
| 14828 }, |
| 14829 |
| 14830 _patternAlreadyChecked: { |
| 14831 type: Boolean, |
| 14832 value: false |
| 14833 } |
| 14834 |
| 14835 }, |
| 14836 |
| 14837 listeners: { |
| 14838 'input': '_onInput', |
| 14839 'keypress': '_onKeypress' |
| 14840 }, |
| 14841 |
| 14842 get _patternRegExp() { |
| 14843 var pattern; |
| 14844 if (this.allowedPattern) { |
| 14845 pattern = new RegExp(this.allowedPattern); |
| 14846 } else if (this.pattern) { |
| 14847 pattern = new RegExp(this.pattern); |
| 14848 } else { |
| 14849 switch (this.type) { |
| 14850 case 'number': |
| 14851 pattern = /[0-9.,e-]/; |
| 14852 break; |
| 14853 } |
| 14854 } |
| 14855 return pattern; |
| 14856 }, |
| 14857 |
| 14858 ready: function() { |
| 14859 this.bindValue = this.value; |
| 14860 }, |
| 14861 |
| 14862 /** |
| 14863 * @suppress {checkTypes} |
| 14864 */ |
| 14865 _bindValueChanged: function() { |
| 14866 if (this.value !== this.bindValue) { |
| 14867 this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindV
alue; |
| 14868 } |
| 14869 // manually notify because we don't want to notify until after setting val
ue |
| 14870 this.fire('bind-value-changed', {value: this.bindValue}); |
| 14871 }, |
| 14872 |
| 14873 _onInput: function() { |
| 14874 // Need to validate each of the characters pasted if they haven't |
| 14875 // been validated inside `_onKeypress` already. |
| 14876 if (this.preventInvalidInput && !this._patternAlreadyChecked) { |
| 14877 var valid = this._checkPatternValidity(); |
| 14878 if (!valid) { |
| 14879 this.value = this._previousValidInput; |
| 14880 } |
| 14881 } |
| 14882 |
| 14883 this.bindValue = this.value; |
| 14884 this._previousValidInput = this.value; |
| 14885 this._patternAlreadyChecked = false; |
| 14886 }, |
| 14887 |
| 14888 _isPrintable: function(event) { |
| 14889 // What a control/printable character is varies wildly based on the browse
r. |
| 14890 // - most control characters (arrows, backspace) do not send a `keypress`
event |
| 14891 // in Chrome, but the *do* on Firefox |
| 14892 // - in Firefox, when they do send a `keypress` event, control chars have |
| 14893 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) |
| 14894 // - printable characters always send a keypress event. |
| 14895 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the
keyCode |
| 14896 // always matches the charCode. |
| 14897 // None of this makes any sense. |
| 14898 |
| 14899 // For these keys, ASCII code == browser keycode. |
| 14900 var anyNonPrintable = |
| 14901 (event.keyCode == 8) || // backspace |
| 14902 (event.keyCode == 9) || // tab |
| 14903 (event.keyCode == 13) || // enter |
| 14904 (event.keyCode == 27); // escape |
| 14905 |
| 14906 // For these keys, make sure it's a browser keycode and not an ASCII code. |
| 14907 var mozNonPrintable = |
| 14908 (event.keyCode == 19) || // pause |
| 14909 (event.keyCode == 20) || // caps lock |
| 14910 (event.keyCode == 45) || // insert |
| 14911 (event.keyCode == 46) || // delete |
| 14912 (event.keyCode == 144) || // num lock |
| 14913 (event.keyCode == 145) || // scroll lock |
| 14914 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho
me, arrows |
| 14915 (event.keyCode > 111 && event.keyCode < 124); // fn keys |
| 14916 |
| 14917 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); |
| 14918 }, |
| 14919 |
| 14920 _onKeypress: function(event) { |
| 14921 if (!this.preventInvalidInput && this.type !== 'number') { |
| 14922 return; |
| 14923 } |
| 14924 var regexp = this._patternRegExp; |
| 14925 if (!regexp) { |
| 14926 return; |
| 14927 } |
| 14928 |
| 14929 // Handle special keys and backspace |
| 14930 if (event.metaKey || event.ctrlKey || event.altKey) |
| 14931 return; |
| 14932 |
| 14933 // Check the pattern either here or in `_onInput`, but not in both. |
| 14934 this._patternAlreadyChecked = true; |
| 14935 |
| 14936 var thisChar = String.fromCharCode(event.charCode); |
| 14937 if (this._isPrintable(event) && !regexp.test(thisChar)) { |
| 14938 event.preventDefault(); |
| 14939 } |
| 14940 }, |
| 14941 |
| 14942 _checkPatternValidity: function() { |
| 14943 var regexp = this._patternRegExp; |
| 14944 if (!regexp) { |
| 14945 return true; |
| 14946 } |
| 14947 for (var i = 0; i < this.value.length; i++) { |
| 14948 if (!regexp.test(this.value[i])) { |
| 14949 return false; |
| 14950 } |
| 14951 } |
| 14952 return true; |
| 14953 }, |
| 14954 |
| 14955 /** |
| 14956 * Returns true if `value` is valid. The validator provided in `validator` w
ill be used first, |
| 14957 * then any constraints. |
| 14958 * @return {boolean} True if the value is valid. |
| 14959 */ |
| 14960 validate: function() { |
| 14961 // Empty, non-required input is valid. |
| 14962 if (!this.required && this.value == '') { |
| 14963 this.invalid = false; |
| 14964 return true; |
| 14965 } |
| 14966 |
| 14967 var valid; |
| 14968 if (this.hasValidator()) { |
| 14969 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); |
| 14970 } else { |
| 14971 this.invalid = !this.validity.valid; |
| 14972 valid = this.validity.valid; |
| 14973 } |
| 14974 this.fire('iron-input-validate'); |
| 14975 return valid; |
| 14976 } |
| 14977 |
| 14978 }); |
| 14979 |
| 14980 /* |
| 14981 The `iron-input-validate` event is fired whenever `validate()` is called. |
| 14982 @event iron-input-validate |
| 14983 */ |
| 14984 Polymer({ |
| 14985 is: 'paper-input-container', |
| 14986 |
| 14987 properties: { |
| 14988 /** |
| 14989 * Set to true to disable the floating label. The label disappears when th
e input value is |
| 14990 * not null. |
| 14991 */ |
| 14992 noLabelFloat: { |
| 14993 type: Boolean, |
| 14994 value: false |
| 14995 }, |
| 14996 |
| 14997 /** |
| 14998 * Set to true to always float the floating label. |
| 14999 */ |
| 15000 alwaysFloatLabel: { |
| 15001 type: Boolean, |
| 15002 value: false |
| 15003 }, |
| 15004 |
| 15005 /** |
| 15006 * The attribute to listen for value changes on. |
| 15007 */ |
| 15008 attrForValue: { |
| 15009 type: String, |
| 15010 value: 'bind-value' |
| 15011 }, |
| 15012 |
| 15013 /** |
| 15014 * Set to true to auto-validate the input value when it changes. |
| 15015 */ |
| 15016 autoValidate: { |
| 15017 type: Boolean, |
| 15018 value: false |
| 15019 }, |
| 15020 |
| 15021 /** |
| 15022 * True if the input is invalid. This property is set automatically when t
he input value |
| 15023 * changes if auto-validating, or when the `iron-input-validate` event is
heard from a child. |
| 15024 */ |
| 15025 invalid: { |
| 15026 observer: '_invalidChanged', |
| 15027 type: Boolean, |
| 15028 value: false |
| 15029 }, |
| 15030 |
| 15031 /** |
| 15032 * True if the input has focus. |
| 15033 */ |
| 15034 focused: { |
| 15035 readOnly: true, |
| 15036 type: Boolean, |
| 15037 value: false, |
| 15038 notify: true |
| 15039 }, |
| 15040 |
| 15041 _addons: { |
| 15042 type: Array |
| 15043 // do not set a default value here intentionally - it will be initialize
d lazily when a |
| 15044 // distributed child is attached, which may occur before configuration f
or this element |
| 15045 // in polyfill. |
| 15046 }, |
| 15047 |
| 15048 _inputHasContent: { |
| 15049 type: Boolean, |
| 15050 value: false |
| 15051 }, |
| 15052 |
| 15053 _inputSelector: { |
| 15054 type: String, |
| 15055 value: 'input,textarea,.paper-input-input' |
| 15056 }, |
| 15057 |
| 15058 _boundOnFocus: { |
| 15059 type: Function, |
| 15060 value: function() { |
| 15061 return this._onFocus.bind(this); |
| 15062 } |
| 15063 }, |
| 15064 |
| 15065 _boundOnBlur: { |
| 15066 type: Function, |
| 15067 value: function() { |
| 15068 return this._onBlur.bind(this); |
| 15069 } |
| 15070 }, |
| 15071 |
| 15072 _boundOnInput: { |
| 15073 type: Function, |
| 15074 value: function() { |
| 15075 return this._onInput.bind(this); |
| 15076 } |
| 15077 }, |
| 15078 |
| 15079 _boundValueChanged: { |
| 15080 type: Function, |
| 15081 value: function() { |
| 15082 return this._onValueChanged.bind(this); |
| 15083 } |
| 15084 } |
| 15085 }, |
| 15086 |
| 15087 listeners: { |
| 15088 'addon-attached': '_onAddonAttached', |
| 15089 'iron-input-validate': '_onIronInputValidate' |
| 15090 }, |
| 15091 |
| 15092 get _valueChangedEvent() { |
| 15093 return this.attrForValue + '-changed'; |
| 15094 }, |
| 15095 |
| 15096 get _propertyForValue() { |
| 15097 return Polymer.CaseMap.dashToCamelCase(this.attrForValue); |
| 15098 }, |
| 15099 |
| 15100 get _inputElement() { |
| 15101 return Polymer.dom(this).querySelector(this._inputSelector); |
| 15102 }, |
| 15103 |
| 15104 get _inputElementValue() { |
| 15105 return this._inputElement[this._propertyForValue] || this._inputElement.va
lue; |
| 15106 }, |
| 15107 |
| 15108 ready: function() { |
| 15109 if (!this._addons) { |
| 15110 this._addons = []; |
| 15111 } |
| 15112 this.addEventListener('focus', this._boundOnFocus, true); |
| 15113 this.addEventListener('blur', this._boundOnBlur, true); |
| 15114 if (this.attrForValue) { |
| 15115 this._inputElement.addEventListener(this._valueChangedEvent, this._bound
ValueChanged); |
| 15116 } else { |
| 15117 this.addEventListener('input', this._onInput); |
| 15118 } |
| 15119 }, |
| 15120 |
| 15121 attached: function() { |
| 15122 // Only validate when attached if the input already has a value. |
| 15123 if (this._inputElementValue != '') { |
| 15124 this._handleValueAndAutoValidate(this._inputElement); |
| 15125 } else { |
| 15126 this._handleValue(this._inputElement); |
| 15127 } |
| 15128 }, |
| 15129 |
| 15130 _onAddonAttached: function(event) { |
| 15131 if (!this._addons) { |
| 15132 this._addons = []; |
| 15133 } |
| 15134 var target = event.target; |
| 15135 if (this._addons.indexOf(target) === -1) { |
| 15136 this._addons.push(target); |
| 15137 if (this.isAttached) { |
| 15138 this._handleValue(this._inputElement); |
| 15139 } |
| 15140 } |
| 15141 }, |
| 15142 |
| 15143 _onFocus: function() { |
| 15144 this._setFocused(true); |
| 15145 }, |
| 15146 |
| 15147 _onBlur: function() { |
| 15148 this._setFocused(false); |
| 15149 this._handleValueAndAutoValidate(this._inputElement); |
| 15150 }, |
| 15151 |
| 15152 _onInput: function(event) { |
| 15153 this._handleValueAndAutoValidate(event.target); |
| 15154 }, |
| 15155 |
| 15156 _onValueChanged: function(event) { |
| 15157 this._handleValueAndAutoValidate(event.target); |
| 15158 }, |
| 15159 |
| 15160 _handleValue: function(inputElement) { |
| 15161 var value = this._inputElementValue; |
| 15162 |
| 15163 // type="number" hack needed because this.value is empty until it's valid |
| 15164 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme
nt.checkValidity())) { |
| 15165 this._inputHasContent = true; |
| 15166 } else { |
| 15167 this._inputHasContent = false; |
| 15168 } |
| 15169 |
| 15170 this.updateAddons({ |
| 15171 inputElement: inputElement, |
| 15172 value: value, |
| 15173 invalid: this.invalid |
| 15174 }); |
| 15175 }, |
| 15176 |
| 15177 _handleValueAndAutoValidate: function(inputElement) { |
| 15178 if (this.autoValidate) { |
| 15179 var valid; |
| 15180 if (inputElement.validate) { |
| 15181 valid = inputElement.validate(this._inputElementValue); |
| 15182 } else { |
| 15183 valid = inputElement.checkValidity(); |
| 15184 } |
| 15185 this.invalid = !valid; |
| 15186 } |
| 15187 |
| 15188 // Call this last to notify the add-ons. |
| 15189 this._handleValue(inputElement); |
| 15190 }, |
| 15191 |
| 15192 _onIronInputValidate: function(event) { |
| 15193 this.invalid = this._inputElement.invalid; |
| 15194 }, |
| 15195 |
| 15196 _invalidChanged: function() { |
| 15197 if (this._addons) { |
| 15198 this.updateAddons({invalid: this.invalid}); |
| 15199 } |
| 15200 }, |
| 15201 |
| 15202 /** |
| 15203 * Call this to update the state of add-ons. |
| 15204 * @param {Object} state Add-on state. |
| 15205 */ |
| 15206 updateAddons: function(state) { |
| 15207 for (var addon, index = 0; addon = this._addons[index]; index++) { |
| 15208 addon.update(state); |
| 15209 } |
| 15210 }, |
| 15211 |
| 15212 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused,
invalid, _inputHasContent) { |
| 15213 var cls = 'input-content'; |
| 15214 if (!noLabelFloat) { |
| 15215 var label = this.querySelector('label'); |
| 15216 |
| 15217 if (alwaysFloatLabel || _inputHasContent) { |
| 15218 cls += ' label-is-floating'; |
| 15219 if (invalid) { |
| 15220 cls += ' is-invalid'; |
| 15221 } else if (focused) { |
| 15222 cls += " label-is-highlighted"; |
| 15223 } |
| 15224 // The label might have a horizontal offset if a prefix element exists |
| 15225 // which needs to be undone when displayed as a floating label. |
| 15226 if (Polymer.dom(this.$.prefix).getDistributedNodes().length > 0 && |
| 15227 label && label.offsetParent) { |
| 15228 label.style.left = -label.offsetParent.offsetLeft + 'px'; |
| 15229 } |
| 15230 } else { |
| 15231 // When the label is not floating, it should overlap the input element
. |
| 15232 if (label) { |
| 15233 label.style.left = 0; |
| 15234 } |
| 15235 } |
| 15236 } else { |
| 15237 if (_inputHasContent) { |
| 15238 cls += ' label-is-hidden'; |
| 15239 } |
| 15240 } |
| 15241 return cls; |
| 15242 }, |
| 15243 |
| 15244 _computeUnderlineClass: function(focused, invalid) { |
| 15245 var cls = 'underline'; |
| 15246 if (invalid) { |
| 15247 cls += ' is-invalid'; |
| 15248 } else if (focused) { |
| 15249 cls += ' is-highlighted' |
| 15250 } |
| 15251 return cls; |
| 15252 }, |
| 15253 |
| 15254 _computeAddOnContentClass: function(focused, invalid) { |
| 15255 var cls = 'add-on-content'; |
| 15256 if (invalid) { |
| 15257 cls += ' is-invalid'; |
| 15258 } else if (focused) { |
| 15259 cls += ' is-highlighted' |
| 15260 } |
| 15261 return cls; |
| 15262 } |
| 15263 }); |
| 15264 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 15265 // Use of this source code is governed by a BSD-style license that can be |
| 15266 // found in the LICENSE file. |
| 15267 |
| 15268 /** @interface */ |
| 15269 var SearchFieldDelegate = function() {}; |
| 15270 |
| 15271 SearchFieldDelegate.prototype = { |
| 15272 /** |
| 15273 * @param {string} value |
| 15274 */ |
| 15275 onSearchTermSearch: assertNotReached, |
| 15276 }; |
| 15277 |
| 15278 var SearchField = Polymer({ |
| 15279 is: 'cr-search-field', |
| 15280 |
| 15281 properties: { |
| 15282 label: { |
| 15283 type: String, |
| 15284 value: '', |
| 15285 }, |
| 15286 |
| 15287 clearLabel: { |
| 15288 type: String, |
| 15289 value: '', |
| 15290 }, |
| 15291 |
| 15292 showingSearch_: { |
| 15293 type: Boolean, |
| 15294 value: false, |
| 15295 }, |
| 15296 }, |
| 15297 |
| 15298 /** @param {SearchFieldDelegate} delegate */ |
| 15299 setDelegate: function(delegate) { |
| 15300 this.delegate_ = delegate; |
| 15301 }, |
| 15302 |
| 15303 /** |
| 15304 * Returns the value of the search field. |
| 15305 * @return {string} |
| 15306 */ |
| 15307 getValue: function() { |
| 15308 var searchInput = this.$$('#search-input'); |
| 15309 return searchInput ? searchInput.value : ''; |
| 15310 }, |
| 15311 |
| 15312 /** @private */ |
| 15313 onSearchTermSearch_: function() { |
| 15314 if (this.delegate_) |
| 15315 this.delegate_.onSearchTermSearch(this.getValue()); |
| 15316 }, |
| 15317 |
| 15318 /** @private */ |
| 15319 onSearchTermKeydown_: function(e) { |
| 15320 assert(this.showingSearch_); |
| 15321 if (e.keyIdentifier == 'U+001B') // Escape. |
| 15322 this.toggleShowingSearch_(); |
| 15323 }, |
| 15324 |
| 15325 /** @private */ |
| 15326 toggleShowingSearch_: function() { |
| 15327 this.showingSearch_ = !this.showingSearch_; |
| 15328 this.async(function() { |
| 15329 var searchInput = this.$$('#search-input'); |
| 15330 if (this.showingSearch_) { |
| 15331 searchInput.focus(); |
| 15332 } else { |
| 15333 searchInput.value = ''; |
| 15334 this.onSearchTermSearch_(); |
| 15335 } |
| 15336 }); |
| 15337 }, |
| 15338 }); |
| 15339 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 15340 // Use of this source code is governed by a BSD-style license that can be |
| 15341 // found in the LICENSE file. |
| 15342 |
| 15343 cr.define('downloads', function() { |
| 15344 var Toolbar = Polymer({ |
| 15345 is: 'downloads-toolbar', |
| 15346 |
| 15347 attached: function() { |
| 15348 /** @private {!SearchFieldDelegate} */ |
| 15349 this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this); |
| 15350 this.$['search-input'].setDelegate(this.searchFieldDelegate_); |
| 15351 }, |
| 15352 |
| 15353 properties: { |
| 15354 downloadsShowing: { |
| 15355 reflectToAttribute: true, |
| 15356 type: Boolean, |
| 15357 value: false, |
| 15358 observer: 'onDownloadsShowingChange_', |
| 15359 }, |
| 15360 }, |
| 15361 |
| 15362 /** @return {boolean} Whether removal can be undone. */ |
| 15363 canUndo: function() { |
| 15364 return this.$['search-input'] != this.shadowRoot.activeElement; |
| 15365 }, |
| 15366 |
| 15367 /** @return {boolean} Whether "Clear all" should be allowed. */ |
| 15368 canClearAll: function() { |
| 15369 return !this.$['search-input'].getValue() && this.downloadsShowing; |
| 15370 }, |
| 15371 |
| 15372 /** @private */ |
| 15373 onClearAllClick_: function() { |
| 15374 assert(this.canClearAll()); |
| 15375 downloads.ActionService.getInstance().clearAll(); |
| 15376 }, |
| 15377 |
| 15378 /** @private */ |
| 15379 onDownloadsShowingChange_: function() { |
| 15380 this.updateClearAll_(); |
| 15381 }, |
| 15382 |
| 15383 /** @param {string} searchTerm */ |
| 15384 onSearchTermSearch: function(searchTerm) { |
| 15385 downloads.ActionService.getInstance().search(searchTerm); |
| 15386 this.updateClearAll_(); |
| 15387 }, |
| 15388 |
| 15389 /** @private */ |
| 15390 onOpenDownloadsFolderClick_: function() { |
| 15391 downloads.ActionService.getInstance().openDownloadsFolder(); |
| 15392 }, |
| 15393 |
| 15394 /** @private */ |
| 15395 updateClearAll_: function() { |
| 15396 this.$$('#actions .clear-all').hidden = !this.canClearAll(); |
| 15397 this.$$('paper-menu .clear-all').hidden = !this.canClearAll(); |
| 15398 }, |
| 15399 }); |
| 15400 |
| 15401 /** |
| 15402 * @constructor |
| 15403 * @implements {SearchFieldDelegate} |
| 15404 */ |
| 15405 // TODO(devlin): This is a bit excessive, and it would be better to just have |
| 15406 // Toolbar implement SearchFieldDelegate. But for now, we don't know how to |
| 15407 // make that happen with closure compiler. |
| 15408 function ToolbarSearchFieldDelegate(toolbar) { |
| 15409 this.toolbar_ = toolbar; |
| 15410 } |
| 15411 |
| 15412 ToolbarSearchFieldDelegate.prototype = { |
| 15413 /** @override */ |
| 15414 onSearchTermSearch: function(searchTerm) { |
| 15415 this.toolbar_.onSearchTermSearch(searchTerm); |
| 15416 } |
| 15417 }; |
| 15418 |
| 15419 return {Toolbar: Toolbar}; |
| 15420 }); |
| 15421 |
| 15422 // TODO(dbeam): https://github.com/PolymerElements/iron-dropdown/pull/16/files |
| 15423 /** @suppress {checkTypes} */ |
| 15424 (function() { |
| 15425 Polymer.IronDropdownScrollManager.pushScrollLock = function() {}; |
| 15426 })(); |
| 15427 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 15428 // Use of this source code is governed by a BSD-style license that can be |
| 15429 // found in the LICENSE file. |
| 15430 |
| 15431 cr.define('downloads', function() { |
| 15432 var Manager = Polymer({ |
| 15433 is: 'downloads-manager', |
| 15434 |
| 15435 properties: { |
| 15436 hasDownloads_: { |
| 15437 type: Boolean, |
| 15438 value: false, |
| 15439 }, |
| 15440 }, |
| 15441 |
| 15442 /** |
| 15443 * @return {number} A guess at how many items could be visible at once. |
| 15444 * @private |
| 15445 */ |
| 15446 guesstimateNumberOfVisibleItems_: function() { |
| 15447 var toolbarHeight = this.$.toolbar.offsetHeight; |
| 15448 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1; |
| 15449 }, |
| 15450 |
| 15451 /** |
| 15452 * @param {Event} e |
| 15453 * @private |
| 15454 */ |
| 15455 onCanExecute_: function(e) { |
| 15456 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
| 15457 switch (e.command.id) { |
| 15458 case 'undo-command': |
| 15459 e.canExecute = this.$.toolbar.canUndo(); |
| 15460 break; |
| 15461 case 'clear-all-command': |
| 15462 e.canExecute = this.$.toolbar.canClearAll(); |
| 15463 break; |
| 15464 } |
| 15465 }, |
| 15466 |
| 15467 /** |
| 15468 * @param {Event} e |
| 15469 * @private |
| 15470 */ |
| 15471 onCommand_: function(e) { |
| 15472 if (e.command.id == 'clear-all-command') |
| 15473 downloads.ActionService.getInstance().clearAll(); |
| 15474 else if (e.command.id == 'undo-command') |
| 15475 downloads.ActionService.getInstance().undo(); |
| 15476 }, |
| 15477 |
| 15478 /** @private */ |
| 15479 onLoad_: function() { |
| 15480 cr.ui.decorate('command', cr.ui.Command); |
| 15481 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
| 15482 document.addEventListener('command', this.onCommand_.bind(this)); |
| 15483 |
| 15484 // Shows all downloads. |
| 15485 downloads.ActionService.getInstance().search(''); |
| 15486 }, |
| 15487 |
| 15488 /** @private */ |
| 15489 rebuildFocusGrid_: function() { |
| 15490 var activeElement = this.shadowRoot.activeElement; |
| 15491 |
| 15492 var activeItem; |
| 15493 if (activeElement && activeElement.tagName == 'downloads-item') |
| 15494 activeItem = activeElement; |
| 15495 |
| 15496 var activeControl = activeItem && activeItem.shadowRoot.activeElement; |
| 15497 |
| 15498 /** @private {!cr.ui.FocusGrid} */ |
| 15499 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid; |
| 15500 this.focusGrid_.destroy(); |
| 15501 |
| 15502 var boundary = this.$['downloads-list']; |
| 15503 |
| 15504 this.items_.forEach(function(item) { |
| 15505 var focusRow = new downloads.FocusRow(item.content, boundary); |
| 15506 this.focusGrid_.addRow(focusRow); |
| 15507 |
| 15508 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl)) |
| 15509 focusRow.getEquivalentElement(activeControl).focus(); |
| 15510 }, this); |
| 15511 |
| 15512 this.focusGrid_.ensureRowActive(); |
| 15513 }, |
| 15514 |
| 15515 /** |
| 15516 * @return {number} The number of downloads shown on the page. |
| 15517 * @private |
| 15518 */ |
| 15519 size_: function() { |
| 15520 return this.items_.length; |
| 15521 }, |
| 15522 |
| 15523 /** |
| 15524 * Called when all items need to be updated. |
| 15525 * @param {!Array<!downloads.Data>} list A list of new download data. |
| 15526 * @private |
| 15527 */ |
| 15528 updateAll_: function(list) { |
| 15529 var oldIdMap = this.idMap_ || {}; |
| 15530 |
| 15531 /** @private {!Object<!downloads.Item>} */ |
| 15532 this.idMap_ = {}; |
| 15533 |
| 15534 /** @private {!Array<!downloads.Item>} */ |
| 15535 this.items_ = []; |
| 15536 |
| 15537 if (!this.iconLoader_) { |
| 15538 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); |
| 15539 /** @private {downloads.ThrottledIconLoader} */ |
| 15540 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); |
| 15541 } |
| 15542 |
| 15543 for (var i = 0; i < list.length; ++i) { |
| 15544 var data = list[i]; |
| 15545 var id = data.id; |
| 15546 |
| 15547 // Re-use old items when possible (saves work, preserves focus). |
| 15548 var item = oldIdMap[id] || new downloads.Item(this.iconLoader_); |
| 15549 |
| 15550 this.idMap_[id] = item; // Associated by ID for fast lookup. |
| 15551 this.items_.push(item); // Add to sorted list for order. |
| 15552 |
| 15553 // Render |item| but don't actually add to the DOM yet. |this.items_| |
| 15554 // must be fully created to be able to find the right spot to insert. |
| 15555 item.update(data); |
| 15556 |
| 15557 // Collapse redundant dates. |
| 15558 var prev = list[i - 1]; |
| 15559 item.hideDate = !!prev && prev.date_string == data.date_string; |
| 15560 |
| 15561 delete oldIdMap[id]; |
| 15562 } |
| 15563 |
| 15564 // Remove stale, previously rendered items from the DOM. |
| 15565 for (var id in oldIdMap) { |
| 15566 if (oldIdMap[id].parentNode) |
| 15567 oldIdMap[id].parentNode.removeChild(oldIdMap[id]); |
| 15568 delete oldIdMap[id]; |
| 15569 } |
| 15570 |
| 15571 for (var i = 0; i < this.items_.length; ++i) { |
| 15572 var item = this.items_[i]; |
| 15573 if (item.parentNode) // Already in the DOM; skip. |
| 15574 continue; |
| 15575 |
| 15576 var before = null; |
| 15577 // Find the next rendered item after this one, and insert before it. |
| 15578 for (var j = i + 1; !before && j < this.items_.length; ++j) { |
| 15579 if (this.items_[j].parentNode) |
| 15580 before = this.items_[j]; |
| 15581 } |
| 15582 // If |before| is null, |item| will just get added at the end. |
| 15583 this.$['downloads-list'].insertBefore(item, before); |
| 15584 } |
| 15585 |
| 15586 var hasDownloads = this.size_() > 0; |
| 15587 if (!hasDownloads) { |
| 15588 var isSearching = downloads.ActionService.getInstance().isSearching(); |
| 15589 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; |
| 15590 this.$['no-downloads'].querySelector('span').textContent = |
| 15591 loadTimeData.getString(messageToShow); |
| 15592 } |
| 15593 this.hasDownloads_ = hasDownloads; |
| 15594 |
| 15595 if (loadTimeData.getBoolean('allowDeletingHistory')) |
| 15596 this.$.toolbar.downloadsShowing = this.hasDownloads_; |
| 15597 |
| 15598 this.$.panel.classList.remove('loading'); |
| 15599 |
| 15600 var allReady = this.items_.map(function(i) { return i.readyPromise; }); |
| 15601 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this)); |
| 15602 }, |
| 15603 |
| 15604 /** |
| 15605 * @param {!downloads.Data} data |
| 15606 * @private |
| 15607 */ |
| 15608 updateItem_: function(data) { |
| 15609 var item = this.idMap_[data.id]; |
| 15610 |
| 15611 var activeControl = this.shadowRoot.activeElement == item ? |
| 15612 item.shadowRoot.activeElement : null; |
| 15613 |
| 15614 item.update(data); |
| 15615 |
| 15616 this.async(function() { |
| 15617 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) { |
| 15618 var focusRow = this.focusGrid_.getRowForRoot(item.content); |
| 15619 focusRow.getEquivalentElement(activeControl).focus(); |
| 15620 } |
| 15621 }.bind(this)); |
| 15622 }, |
| 15623 }); |
| 15624 |
| 15625 Manager.size = function() { |
| 15626 return document.querySelector('downloads-manager').size_(); |
| 15627 }; |
| 15628 |
| 15629 Manager.updateAll = function(list) { |
| 15630 document.querySelector('downloads-manager').updateAll_(list); |
| 15631 }; |
| 15632 |
| 15633 Manager.updateItem = function(item) { |
| 15634 document.querySelector('downloads-manager').updateItem_(item); |
| 15635 }; |
| 15636 |
| 15637 Manager.onLoad = function() { |
| 15638 document.querySelector('downloads-manager').onLoad_(); |
| 15639 }; |
| 15640 |
| 15641 return {Manager: Manager}; |
| 15642 }); |
| 15643 |
| 15644 window.addEventListener('load', downloads.Manager.onLoad); |
OLD | NEW |