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 return {ActionService: ActionService}; |
| 1864 }); |
| 1865 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 1866 // Use of this source code is governed by a BSD-style license that can be |
| 1867 // found in the LICENSE file. |
| 1868 |
| 1869 cr.define('downloads', function() { |
| 1870 /** |
| 1871 * Explains why a download is in DANGEROUS state. |
| 1872 * @enum {string} |
| 1873 */ |
| 1874 var DangerType = { |
| 1875 NOT_DANGEROUS: 'NOT_DANGEROUS', |
| 1876 DANGEROUS_FILE: 'DANGEROUS_FILE', |
| 1877 DANGEROUS_URL: 'DANGEROUS_URL', |
| 1878 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT', |
| 1879 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT', |
| 1880 DANGEROUS_HOST: 'DANGEROUS_HOST', |
| 1881 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED', |
| 1882 }; |
| 1883 |
| 1884 /** |
| 1885 * The states a download can be in. These correspond to states defined in |
| 1886 * DownloadsDOMHandler::CreateDownloadItemValue |
| 1887 * @enum {string} |
| 1888 */ |
| 1889 var States = { |
| 1890 IN_PROGRESS: 'IN_PROGRESS', |
| 1891 CANCELLED: 'CANCELLED', |
| 1892 COMPLETE: 'COMPLETE', |
| 1893 PAUSED: 'PAUSED', |
| 1894 DANGEROUS: 'DANGEROUS', |
| 1895 INTERRUPTED: 'INTERRUPTED', |
| 1896 }; |
| 1897 |
| 1898 return { |
| 1899 DangerType: DangerType, |
| 1900 States: States, |
| 1901 }; |
| 1902 }); |
| 1903 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 1904 // Use of this source code is governed by a BSD-style license that can be |
| 1905 // found in the LICENSE file. |
| 1906 |
| 1907 cr.define('cr.ui', function() { |
| 1908 /** |
| 1909 * A class to manage focus between given horizontally arranged elements. |
| 1910 * |
| 1911 * Pressing left cycles backward and pressing right cycles forward in item |
| 1912 * order. Pressing Home goes to the beginning of the list and End goes to the |
| 1913 * end of the list. |
| 1914 * |
| 1915 * If an item in this row is focused, it'll stay active (accessible via tab). |
| 1916 * If no items in this row are focused, the row can stay active until focus |
| 1917 * changes to a node inside |this.boundary_|. If |boundary| isn't specified, |
| 1918 * any focus change deactivates the row. |
| 1919 * |
| 1920 * @param {!Element} root The root of this focus row. Focus classes are |
| 1921 * applied to |root| and all added elements must live within |root|. |
| 1922 * @param {?Node} boundary Focus events are ignored outside of this node. |
| 1923 * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate. |
| 1924 * @constructor |
| 1925 */ |
| 1926 function FocusRow(root, boundary, opt_delegate) { |
| 1927 /** @type {!Element} */ |
| 1928 this.root = root; |
| 1929 |
| 1930 /** @private {!Node} */ |
| 1931 this.boundary_ = boundary || document; |
| 1932 |
| 1933 /** @type {cr.ui.FocusRow.Delegate|undefined} */ |
| 1934 this.delegate = opt_delegate; |
| 1935 |
| 1936 /** @protected {!EventTracker} */ |
| 1937 this.eventTracker = new EventTracker; |
| 1938 } |
| 1939 |
| 1940 /** @interface */ |
| 1941 FocusRow.Delegate = function() {}; |
| 1942 |
| 1943 FocusRow.Delegate.prototype = { |
| 1944 /** |
| 1945 * Called when a key is pressed while on a FocusRow's item. If true is |
| 1946 * returned, further processing is skipped. |
| 1947 * @param {!cr.ui.FocusRow} row The row that detected a keydown. |
| 1948 * @param {!Event} e |
| 1949 * @return {boolean} Whether the event was handled. |
| 1950 */ |
| 1951 onKeydown: assertNotReached, |
| 1952 |
| 1953 /** |
| 1954 * @param {!cr.ui.FocusRow} row |
| 1955 * @param {!Event} e |
| 1956 */ |
| 1957 onFocus: assertNotReached, |
| 1958 }; |
| 1959 |
| 1960 /** @const {string} */ |
| 1961 FocusRow.ACTIVE_CLASS = 'focus-row-active'; |
| 1962 |
| 1963 /** |
| 1964 * Whether it's possible that |element| can be focused. |
| 1965 * @param {Element} element |
| 1966 * @return {boolean} Whether the item is focusable. |
| 1967 */ |
| 1968 FocusRow.isFocusable = function(element) { |
| 1969 if (!element || element.disabled) |
| 1970 return false; |
| 1971 |
| 1972 // We don't check that element.tabIndex >= 0 here because inactive rows set |
| 1973 // a tabIndex of -1. |
| 1974 |
| 1975 function isVisible(element) { |
| 1976 assertInstanceof(element, Element); |
| 1977 |
| 1978 var style = window.getComputedStyle(element); |
| 1979 if (style.visibility == 'hidden' || style.display == 'none') |
| 1980 return false; |
| 1981 |
| 1982 var parent = element.parentNode; |
| 1983 if (!parent) |
| 1984 return false; |
| 1985 |
| 1986 if (parent == element.ownerDocument || parent instanceof DocumentFragment) |
| 1987 return true; |
| 1988 |
| 1989 return isVisible(parent); |
| 1990 } |
| 1991 |
| 1992 return isVisible(element); |
| 1993 }; |
| 1994 |
| 1995 FocusRow.prototype = { |
| 1996 /** |
| 1997 * Register a new type of focusable element (or add to an existing one). |
| 1998 * |
| 1999 * Example: an (X) button might be 'delete' or 'close'. |
| 2000 * |
| 2001 * When FocusRow is used within a FocusGrid, these types are used to |
| 2002 * determine equivalent controls when Up/Down are pressed to change rows. |
| 2003 * |
| 2004 * Another example: mutually exclusive controls that hide eachother on |
| 2005 * activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause') |
| 2006 * to indicate they're equivalent. |
| 2007 * |
| 2008 * @param {string} type The type of element to track focus of. |
| 2009 * @param {string} query The selector of the element from this row's root. |
| 2010 * @return {boolean} Whether a new item was added. |
| 2011 */ |
| 2012 addItem: function(type, query) { |
| 2013 assert(type); |
| 2014 |
| 2015 var element = this.root.querySelector(query); |
| 2016 if (!element) |
| 2017 return false; |
| 2018 |
| 2019 element.setAttribute('focus-type', type); |
| 2020 element.tabIndex = this.isActive() ? 0 : -1; |
| 2021 |
| 2022 this.eventTracker.add(element, 'blur', this.onBlur_.bind(this)); |
| 2023 this.eventTracker.add(element, 'focus', this.onFocus_.bind(this)); |
| 2024 this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this)); |
| 2025 this.eventTracker.add(element, 'mousedown', |
| 2026 this.onMousedown_.bind(this)); |
| 2027 return true; |
| 2028 }, |
| 2029 |
| 2030 /** Dereferences nodes and removes event handlers. */ |
| 2031 destroy: function() { |
| 2032 this.eventTracker.removeAll(); |
| 2033 }, |
| 2034 |
| 2035 /** |
| 2036 * @param {Element} sampleElement An element for to find an equivalent for. |
| 2037 * @return {!Element} An equivalent element to focus for |sampleElement|. |
| 2038 * @protected |
| 2039 */ |
| 2040 getCustomEquivalent: function(sampleElement) { |
| 2041 return assert(this.getFirstFocusable()); |
| 2042 }, |
| 2043 |
| 2044 /** |
| 2045 * @return {!Array<!Element>} All registered elements (regardless of |
| 2046 * focusability). |
| 2047 */ |
| 2048 getElements: function() { |
| 2049 var elements = this.root.querySelectorAll('[focus-type]'); |
| 2050 return Array.prototype.slice.call(elements); |
| 2051 }, |
| 2052 |
| 2053 /** |
| 2054 * Find the element that best matches |sampleElement|. |
| 2055 * @param {!Element} sampleElement An element from a row of the same type |
| 2056 * which previously held focus. |
| 2057 * @return {!Element} The element that best matches sampleElement. |
| 2058 */ |
| 2059 getEquivalentElement: function(sampleElement) { |
| 2060 if (this.getFocusableElements().indexOf(sampleElement) >= 0) |
| 2061 return sampleElement; |
| 2062 |
| 2063 var sampleFocusType = this.getTypeForElement(sampleElement); |
| 2064 if (sampleFocusType) { |
| 2065 var sameType = this.getFirstFocusable(sampleFocusType); |
| 2066 if (sameType) |
| 2067 return sameType; |
| 2068 } |
| 2069 |
| 2070 return this.getCustomEquivalent(sampleElement); |
| 2071 }, |
| 2072 |
| 2073 /** |
| 2074 * @param {string=} opt_type An optional type to search for. |
| 2075 * @return {?Element} The first focusable element with |type|. |
| 2076 */ |
| 2077 getFirstFocusable: function(opt_type) { |
| 2078 var filter = opt_type ? '="' + opt_type + '"' : ''; |
| 2079 var elements = this.root.querySelectorAll('[focus-type' + filter + ']'); |
| 2080 for (var i = 0; i < elements.length; ++i) { |
| 2081 if (cr.ui.FocusRow.isFocusable(elements[i])) |
| 2082 return elements[i]; |
| 2083 } |
| 2084 return null; |
| 2085 }, |
| 2086 |
| 2087 /** @return {!Array<!Element>} Registered, focusable elements. */ |
| 2088 getFocusableElements: function() { |
| 2089 return this.getElements().filter(cr.ui.FocusRow.isFocusable); |
| 2090 }, |
| 2091 |
| 2092 /** |
| 2093 * @param {!Element} element An element to determine a focus type for. |
| 2094 * @return {string} The focus type for |element| or '' if none. |
| 2095 */ |
| 2096 getTypeForElement: function(element) { |
| 2097 return element.getAttribute('focus-type') || ''; |
| 2098 }, |
| 2099 |
| 2100 /** @return {boolean} Whether this row is currently active. */ |
| 2101 isActive: function() { |
| 2102 return this.root.classList.contains(FocusRow.ACTIVE_CLASS); |
| 2103 }, |
| 2104 |
| 2105 /** |
| 2106 * Enables/disables the tabIndex of the focusable elements in the FocusRow. |
| 2107 * tabIndex can be set properly. |
| 2108 * @param {boolean} active True if tab is allowed for this row. |
| 2109 */ |
| 2110 makeActive: function(active) { |
| 2111 if (active == this.isActive()) |
| 2112 return; |
| 2113 |
| 2114 this.getElements().forEach(function(element) { |
| 2115 element.tabIndex = active ? 0 : -1; |
| 2116 }); |
| 2117 |
| 2118 this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active); |
| 2119 }, |
| 2120 |
| 2121 /** |
| 2122 * @param {!Event} e |
| 2123 * @private |
| 2124 */ |
| 2125 onBlur_: function(e) { |
| 2126 if (!this.boundary_.contains(/** @type {Node} */(e.relatedTarget))) |
| 2127 return; |
| 2128 |
| 2129 if (this.getFocusableElements().indexOf(e.currentTarget) >= 0) |
| 2130 this.makeActive(false); |
| 2131 }, |
| 2132 |
| 2133 /** |
| 2134 * @param {!Event} e |
| 2135 * @private |
| 2136 */ |
| 2137 onFocus_: function(e) { |
| 2138 if (this.delegate) |
| 2139 this.delegate.onFocus(this, e); |
| 2140 }, |
| 2141 |
| 2142 /** |
| 2143 * @param {!Event} e A mousedown event. |
| 2144 * @private |
| 2145 */ |
| 2146 onMousedown_: function(e) { |
| 2147 // Only accept left mouse clicks. |
| 2148 if (e.button) |
| 2149 return; |
| 2150 |
| 2151 // Allow the element under the mouse cursor to be focusable. |
| 2152 if (!e.currentTarget.disabled) |
| 2153 e.currentTarget.tabIndex = 0; |
| 2154 }, |
| 2155 |
| 2156 /** |
| 2157 * @param {Event} e The keydown event. |
| 2158 * @private |
| 2159 */ |
| 2160 onKeydown_: function(e) { |
| 2161 var elements = this.getFocusableElements(); |
| 2162 var elementIndex = elements.indexOf(e.currentTarget); |
| 2163 assert(elementIndex >= 0); |
| 2164 |
| 2165 if (this.delegate && this.delegate.onKeydown(this, e)) |
| 2166 return; |
| 2167 |
| 2168 if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) |
| 2169 return; |
| 2170 |
| 2171 var index = -1; |
| 2172 |
| 2173 if (e.keyIdentifier == 'Left') |
| 2174 index = elementIndex + (isRTL() ? 1 : -1); |
| 2175 else if (e.keyIdentifier == 'Right') |
| 2176 index = elementIndex + (isRTL() ? -1 : 1); |
| 2177 else if (e.keyIdentifier == 'Home') |
| 2178 index = 0; |
| 2179 else if (e.keyIdentifier == 'End') |
| 2180 index = elements.length - 1; |
| 2181 |
| 2182 var elementToFocus = elements[index]; |
| 2183 if (elementToFocus) { |
| 2184 this.getEquivalentElement(elementToFocus).focus(); |
| 2185 e.preventDefault(); |
| 2186 } |
| 2187 }, |
| 2188 }; |
| 2189 |
| 2190 return { |
| 2191 FocusRow: FocusRow, |
| 2192 }; |
| 2193 }); |
| 2194 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2195 // Use of this source code is governed by a BSD-style license that can be |
| 2196 // found in the LICENSE file. |
| 2197 |
| 2198 cr.define('downloads', function() { |
| 2199 /** |
| 2200 * @param {!Element} root |
| 2201 * @param {?Node} boundary |
| 2202 * @constructor |
| 2203 * @extends {cr.ui.FocusRow} |
| 2204 */ |
| 2205 function FocusRow(root, boundary) { |
| 2206 cr.ui.FocusRow.call(this, root, boundary); |
| 2207 this.addItems(); |
| 2208 } |
| 2209 |
| 2210 FocusRow.prototype = { |
| 2211 __proto__: cr.ui.FocusRow.prototype, |
| 2212 |
| 2213 addItems: function() { |
| 2214 this.destroy(); |
| 2215 |
| 2216 this.addItem('name-file-link', |
| 2217 'content.is-active:not(.show-progress):not(.dangerous) #name'); |
| 2218 assert(this.addItem('name-file-link', '#file-link')); |
| 2219 assert(this.addItem('url', '#url')); |
| 2220 this.addItem('show-retry', '#show'); |
| 2221 this.addItem('show-retry', '#retry'); |
| 2222 this.addItem('pause-resume', '#pause'); |
| 2223 this.addItem('pause-resume', '#resume'); |
| 2224 this.addItem('cancel', '#cancel'); |
| 2225 this.addItem('controlled-by', '#controlled-by a'); |
| 2226 this.addItem('danger-remove-discard', '#discard'); |
| 2227 this.addItem('restore-save', '#save'); |
| 2228 this.addItem('danger-remove-discard', '#danger-remove'); |
| 2229 this.addItem('restore-save', '#restore'); |
| 2230 assert(this.addItem('remove', '#remove')); |
| 2231 |
| 2232 // TODO(dbeam): it would be nice to do this asynchronously (so if multiple |
| 2233 // templates get rendered we only re-add once), but Manager#updateItem_() |
| 2234 // relies on the DOM being re-rendered synchronously. |
| 2235 this.eventTracker.add(this.root, 'dom-change', this.addItems.bind(this)); |
| 2236 }, |
| 2237 }; |
| 2238 |
| 2239 return {FocusRow: FocusRow}; |
| 2240 }); |
| 2241 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2242 // Use of this source code is governed by a BSD-style license that can be |
| 2243 // found in the LICENSE file. |
| 2244 |
| 2245 // Action links are elements that are used to perform an in-page navigation or |
| 2246 // action (e.g. showing a dialog). |
| 2247 // |
| 2248 // They look like normal anchor (<a>) tags as their text color is blue. However, |
| 2249 // they're subtly different as they're not initially underlined (giving users a |
| 2250 // clue that underlined links navigate while action links don't). |
| 2251 // |
| 2252 // Action links look very similar to normal links when hovered (hand cursor, |
| 2253 // underlined). This gives the user an idea that clicking this link will do |
| 2254 // something similar to navigation but in the same page. |
| 2255 // |
| 2256 // They can be created in JavaScript like this: |
| 2257 // |
| 2258 // var link = document.createElement('a', 'action-link'); // Note second arg. |
| 2259 // |
| 2260 // or with a constructor like this: |
| 2261 // |
| 2262 // var link = new ActionLink(); |
| 2263 // |
| 2264 // They can be used easily from HTML as well, like so: |
| 2265 // |
| 2266 // <a is="action-link">Click me!</a> |
| 2267 // |
| 2268 // NOTE: <action-link> and document.createElement('action-link') don't work. |
| 2269 |
| 2270 /** |
| 2271 * @constructor |
| 2272 * @extends {HTMLAnchorElement} |
| 2273 */ |
| 2274 var ActionLink = document.registerElement('action-link', { |
| 2275 prototype: { |
| 2276 __proto__: HTMLAnchorElement.prototype, |
| 2277 |
| 2278 /** @this {ActionLink} */ |
| 2279 createdCallback: function() { |
| 2280 // Action links can start disabled (e.g. <a is="action-link" disabled>). |
| 2281 this.tabIndex = this.disabled ? -1 : 0; |
| 2282 |
| 2283 if (!this.hasAttribute('role')) |
| 2284 this.setAttribute('role', 'link'); |
| 2285 |
| 2286 this.addEventListener('keydown', function(e) { |
| 2287 if (!this.disabled && e.keyIdentifier == 'Enter') { |
| 2288 // Schedule a click asynchronously because other 'keydown' handlers |
| 2289 // may still run later (e.g. document.addEventListener('keydown')). |
| 2290 // Specifically options dialogs break when this timeout isn't here. |
| 2291 // NOTE: this affects the "trusted" state of the ensuing click. I |
| 2292 // haven't found anything that breaks because of this (yet). |
| 2293 window.setTimeout(this.click.bind(this), 0); |
| 2294 } |
| 2295 }); |
| 2296 |
| 2297 function preventDefault(e) { |
| 2298 e.preventDefault(); |
| 2299 } |
| 2300 |
| 2301 function removePreventDefault() { |
| 2302 document.removeEventListener('selectstart', preventDefault); |
| 2303 document.removeEventListener('mouseup', removePreventDefault); |
| 2304 } |
| 2305 |
| 2306 this.addEventListener('mousedown', function() { |
| 2307 // This handlers strives to match the behavior of <a href="...">. |
| 2308 |
| 2309 // While the mouse is down, prevent text selection from dragging. |
| 2310 document.addEventListener('selectstart', preventDefault); |
| 2311 document.addEventListener('mouseup', removePreventDefault); |
| 2312 |
| 2313 // If focus started via mouse press, don't show an outline. |
| 2314 if (document.activeElement != this) |
| 2315 this.classList.add('no-outline'); |
| 2316 }); |
| 2317 |
| 2318 this.addEventListener('blur', function() { |
| 2319 this.classList.remove('no-outline'); |
| 2320 }); |
| 2321 }, |
| 2322 |
| 2323 /** @type {boolean} */ |
| 2324 set disabled(disabled) { |
| 2325 if (disabled) |
| 2326 HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', ''); |
| 2327 else |
| 2328 HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled'); |
| 2329 this.tabIndex = disabled ? -1 : 0; |
| 2330 }, |
| 2331 get disabled() { |
| 2332 return this.hasAttribute('disabled'); |
| 2333 }, |
| 2334 |
| 2335 /** @override */ |
| 2336 setAttribute: function(attr, val) { |
| 2337 if (attr.toLowerCase() == 'disabled') |
| 2338 this.disabled = true; |
| 2339 else |
| 2340 HTMLAnchorElement.prototype.setAttribute.apply(this, arguments); |
| 2341 }, |
| 2342 |
| 2343 /** @override */ |
| 2344 removeAttribute: function(attr) { |
| 2345 if (attr.toLowerCase() == 'disabled') |
| 2346 this.disabled = false; |
| 2347 else |
| 2348 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); |
| 2349 }, |
| 2350 }, |
| 2351 |
| 2352 extends: 'a', |
| 2353 }); |
| 2354 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2355 // Use of this source code is governed by a BSD-style license that can be |
| 2356 // found in the LICENSE file. |
| 2357 |
| 2358 /** @typedef {{img: HTMLImageElement, url: string}} */ |
| 2359 var LoadIconRequest; |
| 2360 |
| 2361 cr.define('downloads', function() { |
| 2362 /** |
| 2363 * @param {number} maxAllowed The maximum number of simultaneous downloads |
| 2364 * allowed. |
| 2365 * @constructor |
| 2366 */ |
| 2367 function ThrottledIconLoader(maxAllowed) { |
| 2368 assert(maxAllowed > 0); |
| 2369 |
| 2370 /** @private {number} */ |
| 2371 this.maxAllowed_ = maxAllowed; |
| 2372 |
| 2373 /** @private {!Array<!LoadIconRequest>} */ |
| 2374 this.requests_ = []; |
| 2375 } |
| 2376 |
| 2377 ThrottledIconLoader.prototype = { |
| 2378 /** @private {number} */ |
| 2379 loading_: 0, |
| 2380 |
| 2381 /** |
| 2382 * Load the provided |url| into |img.src| after appending ?scale=. |
| 2383 * @param {!HTMLImageElement} img An <img> to show the loaded image in. |
| 2384 * @param {string} url A remote image URL to load. |
| 2385 */ |
| 2386 loadScaledIcon: function(img, url) { |
| 2387 var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x'; |
| 2388 if (img.src == scaledUrl) |
| 2389 return; |
| 2390 |
| 2391 this.requests_.push({img: img, url: scaledUrl}); |
| 2392 this.loadNextIcon_(); |
| 2393 }, |
| 2394 |
| 2395 /** @private */ |
| 2396 loadNextIcon_: function() { |
| 2397 if (this.loading_ > this.maxAllowed_ || !this.requests_.length) |
| 2398 return; |
| 2399 |
| 2400 var request = this.requests_.shift(); |
| 2401 var img = request.img; |
| 2402 |
| 2403 img.onabort = img.onerror = img.onload = function() { |
| 2404 this.loading_--; |
| 2405 this.loadNextIcon_(); |
| 2406 }.bind(this); |
| 2407 |
| 2408 this.loading_++; |
| 2409 img.src = request.url; |
| 2410 }, |
| 2411 }; |
| 2412 |
| 2413 return {ThrottledIconLoader: ThrottledIconLoader}; |
| 2414 }); |
| 2415 // Copyright 2014 Google Inc. All rights reserved. |
| 2416 // |
| 2417 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 2418 // you may not use this file except in compliance with the License. |
| 2419 // You may obtain a copy of the License at |
| 2420 // |
| 2421 // http://www.apache.org/licenses/LICENSE-2.0 |
| 2422 // |
| 2423 // Unless required by applicable law or agreed to in writing, software |
| 2424 // distributed under the License is distributed on an "AS IS" BASIS, |
| 2425 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 2426 // See the License for the specific language governing permissions and |
| 2427 // limitations under the License. |
| 2428 |
| 2429 !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() |
| 2430 },_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}()); |
| 2431 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2432 // Use of this source code is governed by a BSD-style license that can be |
| 2433 // found in the LICENSE file. |
| 2434 |
| 2435 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js"> |
| 2436 |
| 2437 i18nTemplate.process(document, loadTimeData); |
| 2438 (function () { |
| 2439 function resolve() { |
| 2440 document.body.removeAttribute('unresolved'); |
| 2441 } |
| 2442 if (window.WebComponents) { |
| 2443 addEventListener('WebComponentsReady', resolve); |
| 2444 } else { |
| 2445 if (document.readyState === 'interactive' || document.readyState === 'complete')
{ |
| 2446 resolve(); |
| 2447 } else { |
| 2448 addEventListener('DOMContentLoaded', resolve); |
| 2449 } |
| 2450 } |
| 2451 }()); |
| 2452 window.Polymer = { |
| 2453 Settings: function () { |
| 2454 var user = window.Polymer || {}; |
| 2455 location.search.slice(1).split('&').forEach(function (o) { |
| 2456 o = o.split('='); |
| 2457 o[0] && (user[o[0]] = o[1] || true); |
| 2458 }); |
| 2459 var wantShadow = user.dom === 'shadow'; |
| 2460 var hasShadow = Boolean(Element.prototype.createShadowRoot); |
| 2461 var nativeShadow = hasShadow && !window.ShadowDOMPolyfill; |
| 2462 var useShadow = wantShadow && hasShadow; |
| 2463 var hasNativeImports = Boolean('import' in document.createElement('link')); |
| 2464 var useNativeImports = hasNativeImports; |
| 2465 var useNativeCustomElements = !window.CustomElements || window.CustomElements.us
eNative; |
| 2466 return { |
| 2467 wantShadow: wantShadow, |
| 2468 hasShadow: hasShadow, |
| 2469 nativeShadow: nativeShadow, |
| 2470 useShadow: useShadow, |
| 2471 useNativeShadow: useShadow && nativeShadow, |
| 2472 useNativeImports: useNativeImports, |
| 2473 useNativeCustomElements: useNativeCustomElements |
| 2474 }; |
| 2475 }() |
| 2476 }; |
| 2477 (function () { |
| 2478 var userPolymer = window.Polymer; |
| 2479 window.Polymer = function (prototype) { |
| 2480 if (typeof prototype === 'function') { |
| 2481 prototype = prototype.prototype; |
| 2482 } |
| 2483 if (!prototype) { |
| 2484 prototype = {}; |
| 2485 } |
| 2486 var factory = desugar(prototype); |
| 2487 prototype = factory.prototype; |
| 2488 var options = { prototype: prototype }; |
| 2489 if (prototype.extends) { |
| 2490 options.extends = prototype.extends; |
| 2491 } |
| 2492 Polymer.telemetry._registrate(prototype); |
| 2493 document.registerElement(prototype.is, options); |
| 2494 return factory; |
| 2495 }; |
| 2496 var desugar = function (prototype) { |
| 2497 var base = Polymer.Base; |
| 2498 if (prototype.extends) { |
| 2499 base = Polymer.Base._getExtendedPrototype(prototype.extends); |
| 2500 } |
| 2501 prototype = Polymer.Base.chainObject(prototype, base); |
| 2502 prototype.registerCallback(); |
| 2503 return prototype.constructor; |
| 2504 }; |
| 2505 window.Polymer = Polymer; |
| 2506 if (userPolymer) { |
| 2507 for (var i in userPolymer) { |
| 2508 Polymer[i] = userPolymer[i]; |
| 2509 } |
| 2510 } |
| 2511 Polymer.Class = desugar; |
| 2512 }()); |
| 2513 Polymer.telemetry = { |
| 2514 registrations: [], |
| 2515 _regLog: function (prototype) { |
| 2516 console.log('[' + prototype.is + ']: registered'); |
| 2517 }, |
| 2518 _registrate: function (prototype) { |
| 2519 this.registrations.push(prototype); |
| 2520 Polymer.log && this._regLog(prototype); |
| 2521 }, |
| 2522 dumpRegistrations: function () { |
| 2523 this.registrations.forEach(this._regLog); |
| 2524 } |
| 2525 }; |
| 2526 Object.defineProperty(window, 'currentImport', { |
| 2527 enumerable: true, |
| 2528 configurable: true, |
| 2529 get: function () { |
| 2530 return (document._currentScript || document.currentScript).ownerDocument; |
| 2531 } |
| 2532 }); |
| 2533 Polymer.RenderStatus = { |
| 2534 _ready: false, |
| 2535 _callbacks: [], |
| 2536 whenReady: function (cb) { |
| 2537 if (this._ready) { |
| 2538 cb(); |
| 2539 } else { |
| 2540 this._callbacks.push(cb); |
| 2541 } |
| 2542 }, |
| 2543 _makeReady: function () { |
| 2544 this._ready = true; |
| 2545 this._callbacks.forEach(function (cb) { |
| 2546 cb(); |
| 2547 }); |
| 2548 this._callbacks = []; |
| 2549 }, |
| 2550 _catchFirstRender: function () { |
| 2551 requestAnimationFrame(function () { |
| 2552 Polymer.RenderStatus._makeReady(); |
| 2553 }); |
| 2554 } |
| 2555 }; |
| 2556 if (window.HTMLImports) { |
| 2557 HTMLImports.whenReady(function () { |
| 2558 Polymer.RenderStatus._catchFirstRender(); |
| 2559 }); |
| 2560 } else { |
| 2561 Polymer.RenderStatus._catchFirstRender(); |
| 2562 } |
| 2563 Polymer.ImportStatus = Polymer.RenderStatus; |
| 2564 Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady; |
| 2565 Polymer.Base = { |
| 2566 __isPolymerInstance__: true, |
| 2567 _addFeature: function (feature) { |
| 2568 this.extend(this, feature); |
| 2569 }, |
| 2570 registerCallback: function () { |
| 2571 this._desugarBehaviors(); |
| 2572 this._doBehavior('beforeRegister'); |
| 2573 this._registerFeatures(); |
| 2574 this._doBehavior('registered'); |
| 2575 }, |
| 2576 createdCallback: function () { |
| 2577 Polymer.telemetry.instanceCount++; |
| 2578 this.root = this; |
| 2579 this._doBehavior('created'); |
| 2580 this._initFeatures(); |
| 2581 }, |
| 2582 attachedCallback: function () { |
| 2583 Polymer.RenderStatus.whenReady(function () { |
| 2584 this.isAttached = true; |
| 2585 this._doBehavior('attached'); |
| 2586 }.bind(this)); |
| 2587 }, |
| 2588 detachedCallback: function () { |
| 2589 this.isAttached = false; |
| 2590 this._doBehavior('detached'); |
| 2591 }, |
| 2592 attributeChangedCallback: function (name) { |
| 2593 this._attributeChangedImpl(name); |
| 2594 this._doBehavior('attributeChanged', arguments); |
| 2595 }, |
| 2596 _attributeChangedImpl: function (name) { |
| 2597 this._setAttributeToProperty(this, name); |
| 2598 }, |
| 2599 extend: function (prototype, api) { |
| 2600 if (prototype && api) { |
| 2601 Object.getOwnPropertyNames(api).forEach(function (n) { |
| 2602 this.copyOwnProperty(n, api, prototype); |
| 2603 }, this); |
| 2604 } |
| 2605 return prototype || api; |
| 2606 }, |
| 2607 mixin: function (target, source) { |
| 2608 for (var i in source) { |
| 2609 target[i] = source[i]; |
| 2610 } |
| 2611 return target; |
| 2612 }, |
| 2613 copyOwnProperty: function (name, source, target) { |
| 2614 var pd = Object.getOwnPropertyDescriptor(source, name); |
| 2615 if (pd) { |
| 2616 Object.defineProperty(target, name, pd); |
| 2617 } |
| 2618 }, |
| 2619 _log: console.log.apply.bind(console.log, console), |
| 2620 _warn: console.warn.apply.bind(console.warn, console), |
| 2621 _error: console.error.apply.bind(console.error, console), |
| 2622 _logf: function () { |
| 2623 return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(argum
ents, 0)); |
| 2624 } |
| 2625 }; |
| 2626 Polymer.Base._logPrefix = function () { |
| 2627 var color = window.chrome || /firefox/i.test(navigator.userAgent); |
| 2628 return color ? [ |
| 2629 '%c[%s::%s]:', |
| 2630 'font-weight: bold; background-color:#EEEE00;' |
| 2631 ] : ['[%s::%s]:']; |
| 2632 }(); |
| 2633 Polymer.Base.chainObject = function (object, inherited) { |
| 2634 if (object && inherited && object !== inherited) { |
| 2635 if (!Object.__proto__) { |
| 2636 object = Polymer.Base.extend(Object.create(inherited), object); |
| 2637 } |
| 2638 object.__proto__ = inherited; |
| 2639 } |
| 2640 return object; |
| 2641 }; |
| 2642 Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype); |
| 2643 if (window.CustomElements) { |
| 2644 Polymer.instanceof = CustomElements.instanceof; |
| 2645 } else { |
| 2646 Polymer.instanceof = function (obj, ctor) { |
| 2647 return obj instanceof ctor; |
| 2648 }; |
| 2649 } |
| 2650 Polymer.isInstance = function (obj) { |
| 2651 return Boolean(obj && obj.__isPolymerInstance__); |
| 2652 }; |
| 2653 Polymer.telemetry.instanceCount = 0; |
| 2654 (function () { |
| 2655 var modules = {}; |
| 2656 var lcModules = {}; |
| 2657 var findModule = function (id) { |
| 2658 return modules[id] || lcModules[id.toLowerCase()]; |
| 2659 }; |
| 2660 var DomModule = function () { |
| 2661 return document.createElement('dom-module'); |
| 2662 }; |
| 2663 DomModule.prototype = Object.create(HTMLElement.prototype); |
| 2664 Polymer.Base.extend(DomModule.prototype, { |
| 2665 constructor: DomModule, |
| 2666 createdCallback: function () { |
| 2667 this.register(); |
| 2668 }, |
| 2669 register: function (id) { |
| 2670 var id = id || this.id || this.getAttribute('name') || this.getAttribute('is'); |
| 2671 if (id) { |
| 2672 this.id = id; |
| 2673 modules[id] = this; |
| 2674 lcModules[id.toLowerCase()] = this; |
| 2675 } |
| 2676 }, |
| 2677 import: function (id, selector) { |
| 2678 if (id) { |
| 2679 var m = findModule(id); |
| 2680 if (!m) { |
| 2681 forceDocumentUpgrade(); |
| 2682 m = findModule(id); |
| 2683 } |
| 2684 if (m && selector) { |
| 2685 m = m.querySelector(selector); |
| 2686 } |
| 2687 return m; |
| 2688 } |
| 2689 } |
| 2690 }); |
| 2691 var cePolyfill = window.CustomElements && !CustomElements.useNative; |
| 2692 document.registerElement('dom-module', DomModule); |
| 2693 function forceDocumentUpgrade() { |
| 2694 if (cePolyfill) { |
| 2695 var script = document._currentScript || document.currentScript; |
| 2696 var doc = script && script.ownerDocument; |
| 2697 if (doc) { |
| 2698 CustomElements.upgradeAll(doc); |
| 2699 } |
| 2700 } |
| 2701 } |
| 2702 }()); |
| 2703 Polymer.Base._addFeature({ |
| 2704 _prepIs: function () { |
| 2705 if (!this.is) { |
| 2706 var module = (document._currentScript || document.currentScript).parentNode; |
| 2707 if (module.localName === 'dom-module') { |
| 2708 var id = module.id || module.getAttribute('name') || module.getAttribute('is'); |
| 2709 this.is = id; |
| 2710 } |
| 2711 } |
| 2712 if (this.is) { |
| 2713 this.is = this.is.toLowerCase(); |
| 2714 } |
| 2715 } |
| 2716 }); |
| 2717 Polymer.Base._addFeature({ |
| 2718 behaviors: [], |
| 2719 _desugarBehaviors: function () { |
| 2720 if (this.behaviors.length) { |
| 2721 this.behaviors = this._desugarSomeBehaviors(this.behaviors); |
| 2722 } |
| 2723 }, |
| 2724 _desugarSomeBehaviors: function (behaviors) { |
| 2725 behaviors = this._flattenBehaviorsList(behaviors); |
| 2726 for (var i = behaviors.length - 1; i >= 0; i--) { |
| 2727 this._mixinBehavior(behaviors[i]); |
| 2728 } |
| 2729 return behaviors; |
| 2730 }, |
| 2731 _flattenBehaviorsList: function (behaviors) { |
| 2732 var flat = []; |
| 2733 behaviors.forEach(function (b) { |
| 2734 if (b instanceof Array) { |
| 2735 flat = flat.concat(this._flattenBehaviorsList(b)); |
| 2736 } else if (b) { |
| 2737 flat.push(b); |
| 2738 } else { |
| 2739 this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for miss
ing or 404 import')); |
| 2740 } |
| 2741 }, this); |
| 2742 return flat; |
| 2743 }, |
| 2744 _mixinBehavior: function (b) { |
| 2745 Object.getOwnPropertyNames(b).forEach(function (n) { |
| 2746 switch (n) { |
| 2747 case 'hostAttributes': |
| 2748 case 'registered': |
| 2749 case 'properties': |
| 2750 case 'observers': |
| 2751 case 'listeners': |
| 2752 case 'created': |
| 2753 case 'attached': |
| 2754 case 'detached': |
| 2755 case 'attributeChanged': |
| 2756 case 'configure': |
| 2757 case 'ready': |
| 2758 break; |
| 2759 default: |
| 2760 if (!this.hasOwnProperty(n)) { |
| 2761 this.copyOwnProperty(n, b, this); |
| 2762 } |
| 2763 break; |
| 2764 } |
| 2765 }, this); |
| 2766 }, |
| 2767 _prepBehaviors: function () { |
| 2768 this._prepFlattenedBehaviors(this.behaviors); |
| 2769 }, |
| 2770 _prepFlattenedBehaviors: function (behaviors) { |
| 2771 for (var i = 0, l = behaviors.length; i < l; i++) { |
| 2772 this._prepBehavior(behaviors[i]); |
| 2773 } |
| 2774 this._prepBehavior(this); |
| 2775 }, |
| 2776 _doBehavior: function (name, args) { |
| 2777 this.behaviors.forEach(function (b) { |
| 2778 this._invokeBehavior(b, name, args); |
| 2779 }, this); |
| 2780 this._invokeBehavior(this, name, args); |
| 2781 }, |
| 2782 _invokeBehavior: function (b, name, args) { |
| 2783 var fn = b[name]; |
| 2784 if (fn) { |
| 2785 fn.apply(this, args || Polymer.nar); |
| 2786 } |
| 2787 }, |
| 2788 _marshalBehaviors: function () { |
| 2789 this.behaviors.forEach(function (b) { |
| 2790 this._marshalBehavior(b); |
| 2791 }, this); |
| 2792 this._marshalBehavior(this); |
| 2793 } |
| 2794 }); |
| 2795 Polymer.Base._addFeature({ |
| 2796 _getExtendedPrototype: function (tag) { |
| 2797 return this._getExtendedNativePrototype(tag); |
| 2798 }, |
| 2799 _nativePrototypes: {}, |
| 2800 _getExtendedNativePrototype: function (tag) { |
| 2801 var p = this._nativePrototypes[tag]; |
| 2802 if (!p) { |
| 2803 var np = this.getNativePrototype(tag); |
| 2804 p = this.extend(Object.create(np), Polymer.Base); |
| 2805 this._nativePrototypes[tag] = p; |
| 2806 } |
| 2807 return p; |
| 2808 }, |
| 2809 getNativePrototype: function (tag) { |
| 2810 return Object.getPrototypeOf(document.createElement(tag)); |
| 2811 } |
| 2812 }); |
| 2813 Polymer.Base._addFeature({ |
| 2814 _prepConstructor: function () { |
| 2815 this._factoryArgs = this.extends ? [ |
| 2816 this.extends, |
| 2817 this.is |
| 2818 ] : [this.is]; |
| 2819 var ctor = function () { |
| 2820 return this._factory(arguments); |
| 2821 }; |
| 2822 if (this.hasOwnProperty('extends')) { |
| 2823 ctor.extends = this.extends; |
| 2824 } |
| 2825 Object.defineProperty(this, 'constructor', { |
| 2826 value: ctor, |
| 2827 writable: true, |
| 2828 configurable: true |
| 2829 }); |
| 2830 ctor.prototype = this; |
| 2831 }, |
| 2832 _factory: function (args) { |
| 2833 var elt = document.createElement.apply(document, this._factoryArgs); |
| 2834 if (this.factoryImpl) { |
| 2835 this.factoryImpl.apply(elt, args); |
| 2836 } |
| 2837 return elt; |
| 2838 } |
| 2839 }); |
| 2840 Polymer.nob = Object.create(null); |
| 2841 Polymer.Base._addFeature({ |
| 2842 properties: {}, |
| 2843 getPropertyInfo: function (property) { |
| 2844 var info = this._getPropertyInfo(property, this.properties); |
| 2845 if (!info) { |
| 2846 this.behaviors.some(function (b) { |
| 2847 return info = this._getPropertyInfo(property, b.properties); |
| 2848 }, this); |
| 2849 } |
| 2850 return info || Polymer.nob; |
| 2851 }, |
| 2852 _getPropertyInfo: function (property, properties) { |
| 2853 var p = properties && properties[property]; |
| 2854 if (typeof p === 'function') { |
| 2855 p = properties[property] = { type: p }; |
| 2856 } |
| 2857 if (p) { |
| 2858 p.defined = true; |
| 2859 } |
| 2860 return p; |
| 2861 } |
| 2862 }); |
| 2863 Polymer.CaseMap = { |
| 2864 _caseMap: {}, |
| 2865 dashToCamelCase: function (dash) { |
| 2866 var mapped = Polymer.CaseMap._caseMap[dash]; |
| 2867 if (mapped) { |
| 2868 return mapped; |
| 2869 } |
| 2870 if (dash.indexOf('-') < 0) { |
| 2871 return Polymer.CaseMap._caseMap[dash] = dash; |
| 2872 } |
| 2873 return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) { |
| 2874 return m[1].toUpperCase(); |
| 2875 }); |
| 2876 }, |
| 2877 camelToDashCase: function (camel) { |
| 2878 var mapped = Polymer.CaseMap._caseMap[camel]; |
| 2879 if (mapped) { |
| 2880 return mapped; |
| 2881 } |
| 2882 return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function
(g) { |
| 2883 return g[0] + '-' + g[1].toLowerCase(); |
| 2884 }); |
| 2885 } |
| 2886 }; |
| 2887 Polymer.Base._addFeature({ |
| 2888 _prepAttributes: function () { |
| 2889 this._aggregatedAttributes = {}; |
| 2890 }, |
| 2891 _addHostAttributes: function (attributes) { |
| 2892 if (attributes) { |
| 2893 this.mixin(this._aggregatedAttributes, attributes); |
| 2894 } |
| 2895 }, |
| 2896 _marshalHostAttributes: function () { |
| 2897 this._applyAttributes(this, this._aggregatedAttributes); |
| 2898 }, |
| 2899 _applyAttributes: function (node, attr$) { |
| 2900 for (var n in attr$) { |
| 2901 if (!this.hasAttribute(n) && n !== 'class') { |
| 2902 this.serializeValueToAttribute(attr$[n], n, this); |
| 2903 } |
| 2904 } |
| 2905 }, |
| 2906 _marshalAttributes: function () { |
| 2907 this._takeAttributesToModel(this); |
| 2908 }, |
| 2909 _takeAttributesToModel: function (model) { |
| 2910 for (var i = 0, l = this.attributes.length; i < l; i++) { |
| 2911 this._setAttributeToProperty(model, this.attributes[i].name); |
| 2912 } |
| 2913 }, |
| 2914 _setAttributeToProperty: function (model, attrName) { |
| 2915 if (!this._serializing) { |
| 2916 var propName = Polymer.CaseMap.dashToCamelCase(attrName); |
| 2917 var info = this.getPropertyInfo(propName); |
| 2918 if (info.defined || this._propertyEffects && this._propertyEffects[propName]) { |
| 2919 var val = this.getAttribute(attrName); |
| 2920 model[propName] = this.deserialize(val, info.type); |
| 2921 } |
| 2922 } |
| 2923 }, |
| 2924 _serializing: false, |
| 2925 reflectPropertyToAttribute: function (name) { |
| 2926 this._serializing = true; |
| 2927 this.serializeValueToAttribute(this[name], Polymer.CaseMap.camelToDashCase(name)
); |
| 2928 this._serializing = false; |
| 2929 }, |
| 2930 serializeValueToAttribute: function (value, attribute, node) { |
| 2931 var str = this.serialize(value); |
| 2932 (node || this)[str === undefined ? 'removeAttribute' : 'setAttribute'](attribute
, str); |
| 2933 }, |
| 2934 deserialize: function (value, type) { |
| 2935 switch (type) { |
| 2936 case Number: |
| 2937 value = Number(value); |
| 2938 break; |
| 2939 case Boolean: |
| 2940 value = value !== null; |
| 2941 break; |
| 2942 case Object: |
| 2943 try { |
| 2944 value = JSON.parse(value); |
| 2945 } catch (x) { |
| 2946 } |
| 2947 break; |
| 2948 case Array: |
| 2949 try { |
| 2950 value = JSON.parse(value); |
| 2951 } catch (x) { |
| 2952 value = null; |
| 2953 console.warn('Polymer::Attributes: couldn`t decode Array as JSON'); |
| 2954 } |
| 2955 break; |
| 2956 case Date: |
| 2957 value = new Date(value); |
| 2958 break; |
| 2959 case String: |
| 2960 default: |
| 2961 break; |
| 2962 } |
| 2963 return value; |
| 2964 }, |
| 2965 serialize: function (value) { |
| 2966 switch (typeof value) { |
| 2967 case 'boolean': |
| 2968 return value ? '' : undefined; |
| 2969 case 'object': |
| 2970 if (value instanceof Date) { |
| 2971 return value; |
| 2972 } else if (value) { |
| 2973 try { |
| 2974 return JSON.stringify(value); |
| 2975 } catch (x) { |
| 2976 return ''; |
| 2977 } |
| 2978 } |
| 2979 default: |
| 2980 return value != null ? value : undefined; |
| 2981 } |
| 2982 } |
| 2983 }); |
| 2984 Polymer.Base._addFeature({ |
| 2985 _setupDebouncers: function () { |
| 2986 this._debouncers = {}; |
| 2987 }, |
| 2988 debounce: function (jobName, callback, wait) { |
| 2989 return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[
jobName], callback, wait); |
| 2990 }, |
| 2991 isDebouncerActive: function (jobName) { |
| 2992 var debouncer = this._debouncers[jobName]; |
| 2993 return debouncer && debouncer.finish; |
| 2994 }, |
| 2995 flushDebouncer: function (jobName) { |
| 2996 var debouncer = this._debouncers[jobName]; |
| 2997 if (debouncer) { |
| 2998 debouncer.complete(); |
| 2999 } |
| 3000 }, |
| 3001 cancelDebouncer: function (jobName) { |
| 3002 var debouncer = this._debouncers[jobName]; |
| 3003 if (debouncer) { |
| 3004 debouncer.stop(); |
| 3005 } |
| 3006 } |
| 3007 }); |
| 3008 Polymer.version = '1.1.4'; |
| 3009 Polymer.Base._addFeature({ |
| 3010 _registerFeatures: function () { |
| 3011 this._prepIs(); |
| 3012 this._prepAttributes(); |
| 3013 this._prepBehaviors(); |
| 3014 this._prepConstructor(); |
| 3015 }, |
| 3016 _prepBehavior: function (b) { |
| 3017 this._addHostAttributes(b.hostAttributes); |
| 3018 }, |
| 3019 _marshalBehavior: function (b) { |
| 3020 }, |
| 3021 _initFeatures: function () { |
| 3022 this._marshalHostAttributes(); |
| 3023 this._setupDebouncers(); |
| 3024 this._marshalBehaviors(); |
| 3025 } |
| 3026 }); |
| 3027 Polymer.Base._addFeature({ |
| 3028 _prepTemplate: function () { |
| 3029 this._template = this._template || Polymer.DomModule.import(this.is, 'template')
; |
| 3030 if (this._template && this._template.hasAttribute('is')) { |
| 3031 this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not
be a type-extension, found', this._template, 'Move inside simple <template>.'))
; |
| 3032 } |
| 3033 if (this._template && !this._template.content && HTMLTemplateElement.bootstrap)
{ |
| 3034 HTMLTemplateElement.decorate(this._template); |
| 3035 HTMLTemplateElement.bootstrap(this._template.content); |
| 3036 } |
| 3037 }, |
| 3038 _stampTemplate: function () { |
| 3039 if (this._template) { |
| 3040 this.root = this.instanceTemplate(this._template); |
| 3041 } |
| 3042 }, |
| 3043 instanceTemplate: function (template) { |
| 3044 var dom = document.importNode(template._content || template.content, true); |
| 3045 return dom; |
| 3046 } |
| 3047 }); |
| 3048 (function () { |
| 3049 var baseAttachedCallback = Polymer.Base.attachedCallback; |
| 3050 Polymer.Base._addFeature({ |
| 3051 _hostStack: [], |
| 3052 ready: function () { |
| 3053 }, |
| 3054 _pushHost: function (host) { |
| 3055 this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.l
ength - 1]; |
| 3056 if (host && host._clients) { |
| 3057 host._clients.push(this); |
| 3058 } |
| 3059 this._beginHost(); |
| 3060 }, |
| 3061 _beginHost: function () { |
| 3062 Polymer.Base._hostStack.push(this); |
| 3063 if (!this._clients) { |
| 3064 this._clients = []; |
| 3065 } |
| 3066 }, |
| 3067 _popHost: function () { |
| 3068 Polymer.Base._hostStack.pop(); |
| 3069 }, |
| 3070 _tryReady: function () { |
| 3071 if (this._canReady()) { |
| 3072 this._ready(); |
| 3073 } |
| 3074 }, |
| 3075 _canReady: function () { |
| 3076 return !this.dataHost || this.dataHost._clientsReadied; |
| 3077 }, |
| 3078 _ready: function () { |
| 3079 this._beforeClientsReady(); |
| 3080 this._setupRoot(); |
| 3081 this._readyClients(); |
| 3082 this._afterClientsReady(); |
| 3083 this._readySelf(); |
| 3084 }, |
| 3085 _readyClients: function () { |
| 3086 this._beginDistribute(); |
| 3087 var c$ = this._clients; |
| 3088 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 3089 c._ready(); |
| 3090 } |
| 3091 this._finishDistribute(); |
| 3092 this._clientsReadied = true; |
| 3093 this._clients = null; |
| 3094 }, |
| 3095 _readySelf: function () { |
| 3096 this._doBehavior('ready'); |
| 3097 this._readied = true; |
| 3098 if (this._attachedPending) { |
| 3099 this._attachedPending = false; |
| 3100 this.attachedCallback(); |
| 3101 } |
| 3102 }, |
| 3103 _beforeClientsReady: function () { |
| 3104 }, |
| 3105 _afterClientsReady: function () { |
| 3106 }, |
| 3107 _beforeAttached: function () { |
| 3108 }, |
| 3109 attachedCallback: function () { |
| 3110 if (this._readied) { |
| 3111 this._beforeAttached(); |
| 3112 baseAttachedCallback.call(this); |
| 3113 } else { |
| 3114 this._attachedPending = true; |
| 3115 } |
| 3116 } |
| 3117 }); |
| 3118 }()); |
| 3119 Polymer.ArraySplice = function () { |
| 3120 function newSplice(index, removed, addedCount) { |
| 3121 return { |
| 3122 index: index, |
| 3123 removed: removed, |
| 3124 addedCount: addedCount |
| 3125 }; |
| 3126 } |
| 3127 var EDIT_LEAVE = 0; |
| 3128 var EDIT_UPDATE = 1; |
| 3129 var EDIT_ADD = 2; |
| 3130 var EDIT_DELETE = 3; |
| 3131 function ArraySplice() { |
| 3132 } |
| 3133 ArraySplice.prototype = { |
| 3134 calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, o
ldEnd) { |
| 3135 var rowCount = oldEnd - oldStart + 1; |
| 3136 var columnCount = currentEnd - currentStart + 1; |
| 3137 var distances = new Array(rowCount); |
| 3138 for (var i = 0; i < rowCount; i++) { |
| 3139 distances[i] = new Array(columnCount); |
| 3140 distances[i][0] = i; |
| 3141 } |
| 3142 for (var j = 0; j < columnCount; j++) |
| 3143 distances[0][j] = j; |
| 3144 for (var i = 1; i < rowCount; i++) { |
| 3145 for (var j = 1; j < columnCount; j++) { |
| 3146 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| 3147 distances[i][j] = distances[i - 1][j - 1]; |
| 3148 else { |
| 3149 var north = distances[i - 1][j] + 1; |
| 3150 var west = distances[i][j - 1] + 1; |
| 3151 distances[i][j] = north < west ? north : west; |
| 3152 } |
| 3153 } |
| 3154 } |
| 3155 return distances; |
| 3156 }, |
| 3157 spliceOperationsFromEditDistances: function (distances) { |
| 3158 var i = distances.length - 1; |
| 3159 var j = distances[0].length - 1; |
| 3160 var current = distances[i][j]; |
| 3161 var edits = []; |
| 3162 while (i > 0 || j > 0) { |
| 3163 if (i == 0) { |
| 3164 edits.push(EDIT_ADD); |
| 3165 j--; |
| 3166 continue; |
| 3167 } |
| 3168 if (j == 0) { |
| 3169 edits.push(EDIT_DELETE); |
| 3170 i--; |
| 3171 continue; |
| 3172 } |
| 3173 var northWest = distances[i - 1][j - 1]; |
| 3174 var west = distances[i - 1][j]; |
| 3175 var north = distances[i][j - 1]; |
| 3176 var min; |
| 3177 if (west < north) |
| 3178 min = west < northWest ? west : northWest; |
| 3179 else |
| 3180 min = north < northWest ? north : northWest; |
| 3181 if (min == northWest) { |
| 3182 if (northWest == current) { |
| 3183 edits.push(EDIT_LEAVE); |
| 3184 } else { |
| 3185 edits.push(EDIT_UPDATE); |
| 3186 current = northWest; |
| 3187 } |
| 3188 i--; |
| 3189 j--; |
| 3190 } else if (min == west) { |
| 3191 edits.push(EDIT_DELETE); |
| 3192 i--; |
| 3193 current = west; |
| 3194 } else { |
| 3195 edits.push(EDIT_ADD); |
| 3196 j--; |
| 3197 current = north; |
| 3198 } |
| 3199 } |
| 3200 edits.reverse(); |
| 3201 return edits; |
| 3202 }, |
| 3203 calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd)
{ |
| 3204 var prefixCount = 0; |
| 3205 var suffixCount = 0; |
| 3206 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| 3207 if (currentStart == 0 && oldStart == 0) |
| 3208 prefixCount = this.sharedPrefix(current, old, minLength); |
| 3209 if (currentEnd == current.length && oldEnd == old.length) |
| 3210 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| 3211 currentStart += prefixCount; |
| 3212 oldStart += prefixCount; |
| 3213 currentEnd -= suffixCount; |
| 3214 oldEnd -= suffixCount; |
| 3215 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| 3216 return []; |
| 3217 if (currentStart == currentEnd) { |
| 3218 var splice = newSplice(currentStart, [], 0); |
| 3219 while (oldStart < oldEnd) |
| 3220 splice.removed.push(old[oldStart++]); |
| 3221 return [splice]; |
| 3222 } else if (oldStart == oldEnd) |
| 3223 return [newSplice(currentStart, [], currentEnd - currentStart)]; |
| 3224 var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current,
currentStart, currentEnd, old, oldStart, oldEnd)); |
| 3225 var splice = undefined; |
| 3226 var splices = []; |
| 3227 var index = currentStart; |
| 3228 var oldIndex = oldStart; |
| 3229 for (var i = 0; i < ops.length; i++) { |
| 3230 switch (ops[i]) { |
| 3231 case EDIT_LEAVE: |
| 3232 if (splice) { |
| 3233 splices.push(splice); |
| 3234 splice = undefined; |
| 3235 } |
| 3236 index++; |
| 3237 oldIndex++; |
| 3238 break; |
| 3239 case EDIT_UPDATE: |
| 3240 if (!splice) |
| 3241 splice = newSplice(index, [], 0); |
| 3242 splice.addedCount++; |
| 3243 index++; |
| 3244 splice.removed.push(old[oldIndex]); |
| 3245 oldIndex++; |
| 3246 break; |
| 3247 case EDIT_ADD: |
| 3248 if (!splice) |
| 3249 splice = newSplice(index, [], 0); |
| 3250 splice.addedCount++; |
| 3251 index++; |
| 3252 break; |
| 3253 case EDIT_DELETE: |
| 3254 if (!splice) |
| 3255 splice = newSplice(index, [], 0); |
| 3256 splice.removed.push(old[oldIndex]); |
| 3257 oldIndex++; |
| 3258 break; |
| 3259 } |
| 3260 } |
| 3261 if (splice) { |
| 3262 splices.push(splice); |
| 3263 } |
| 3264 return splices; |
| 3265 }, |
| 3266 sharedPrefix: function (current, old, searchLength) { |
| 3267 for (var i = 0; i < searchLength; i++) |
| 3268 if (!this.equals(current[i], old[i])) |
| 3269 return i; |
| 3270 return searchLength; |
| 3271 }, |
| 3272 sharedSuffix: function (current, old, searchLength) { |
| 3273 var index1 = current.length; |
| 3274 var index2 = old.length; |
| 3275 var count = 0; |
| 3276 while (count < searchLength && this.equals(current[--index1], old[--index2])) |
| 3277 count++; |
| 3278 return count; |
| 3279 }, |
| 3280 calculateSplices: function (current, previous) { |
| 3281 return this.calcSplices(current, 0, current.length, previous, 0, previous.length
); |
| 3282 }, |
| 3283 equals: function (currentValue, previousValue) { |
| 3284 return currentValue === previousValue; |
| 3285 } |
| 3286 }; |
| 3287 return new ArraySplice(); |
| 3288 }(); |
| 3289 Polymer.EventApi = function () { |
| 3290 var Settings = Polymer.Settings; |
| 3291 var EventApi = function (event) { |
| 3292 this.event = event; |
| 3293 }; |
| 3294 if (Settings.useShadow) { |
| 3295 EventApi.prototype = { |
| 3296 get rootTarget() { |
| 3297 return this.event.path[0]; |
| 3298 }, |
| 3299 get localTarget() { |
| 3300 return this.event.target; |
| 3301 }, |
| 3302 get path() { |
| 3303 return this.event.path; |
| 3304 } |
| 3305 }; |
| 3306 } else { |
| 3307 EventApi.prototype = { |
| 3308 get rootTarget() { |
| 3309 return this.event.target; |
| 3310 }, |
| 3311 get localTarget() { |
| 3312 var current = this.event.currentTarget; |
| 3313 var currentRoot = current && Polymer.dom(current).getOwnerRoot(); |
| 3314 var p$ = this.path; |
| 3315 for (var i = 0; i < p$.length; i++) { |
| 3316 if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) { |
| 3317 return p$[i]; |
| 3318 } |
| 3319 } |
| 3320 }, |
| 3321 get path() { |
| 3322 if (!this.event._path) { |
| 3323 var path = []; |
| 3324 var o = this.rootTarget; |
| 3325 while (o) { |
| 3326 path.push(o); |
| 3327 o = Polymer.dom(o).parentNode || o.host; |
| 3328 } |
| 3329 path.push(window); |
| 3330 this.event._path = path; |
| 3331 } |
| 3332 return this.event._path; |
| 3333 } |
| 3334 }; |
| 3335 } |
| 3336 var factory = function (event) { |
| 3337 if (!event.__eventApi) { |
| 3338 event.__eventApi = new EventApi(event); |
| 3339 } |
| 3340 return event.__eventApi; |
| 3341 }; |
| 3342 return { factory: factory }; |
| 3343 }(); |
| 3344 Polymer.domInnerHTML = function () { |
| 3345 var escapeAttrRegExp = /[&\u00A0"]/g; |
| 3346 var escapeDataRegExp = /[&\u00A0<>]/g; |
| 3347 function escapeReplace(c) { |
| 3348 switch (c) { |
| 3349 case '&': |
| 3350 return '&'; |
| 3351 case '<': |
| 3352 return '<'; |
| 3353 case '>': |
| 3354 return '>'; |
| 3355 case '"': |
| 3356 return '"'; |
| 3357 case '\xA0': |
| 3358 return ' '; |
| 3359 } |
| 3360 } |
| 3361 function escapeAttr(s) { |
| 3362 return s.replace(escapeAttrRegExp, escapeReplace); |
| 3363 } |
| 3364 function escapeData(s) { |
| 3365 return s.replace(escapeDataRegExp, escapeReplace); |
| 3366 } |
| 3367 function makeSet(arr) { |
| 3368 var set = {}; |
| 3369 for (var i = 0; i < arr.length; i++) { |
| 3370 set[arr[i]] = true; |
| 3371 } |
| 3372 return set; |
| 3373 } |
| 3374 var voidElements = makeSet([ |
| 3375 'area', |
| 3376 'base', |
| 3377 'br', |
| 3378 'col', |
| 3379 'command', |
| 3380 'embed', |
| 3381 'hr', |
| 3382 'img', |
| 3383 'input', |
| 3384 'keygen', |
| 3385 'link', |
| 3386 'meta', |
| 3387 'param', |
| 3388 'source', |
| 3389 'track', |
| 3390 'wbr' |
| 3391 ]); |
| 3392 var plaintextParents = makeSet([ |
| 3393 'style', |
| 3394 'script', |
| 3395 'xmp', |
| 3396 'iframe', |
| 3397 'noembed', |
| 3398 'noframes', |
| 3399 'plaintext', |
| 3400 'noscript' |
| 3401 ]); |
| 3402 function getOuterHTML(node, parentNode, composed) { |
| 3403 switch (node.nodeType) { |
| 3404 case Node.ELEMENT_NODE: |
| 3405 var tagName = node.localName; |
| 3406 var s = '<' + tagName; |
| 3407 var attrs = node.attributes; |
| 3408 for (var i = 0, attr; attr = attrs[i]; i++) { |
| 3409 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; |
| 3410 } |
| 3411 s += '>'; |
| 3412 if (voidElements[tagName]) { |
| 3413 return s; |
| 3414 } |
| 3415 return s + getInnerHTML(node, composed) + '</' + tagName + '>'; |
| 3416 case Node.TEXT_NODE: |
| 3417 var data = node.data; |
| 3418 if (parentNode && plaintextParents[parentNode.localName]) { |
| 3419 return data; |
| 3420 } |
| 3421 return escapeData(data); |
| 3422 case Node.COMMENT_NODE: |
| 3423 return '<!--' + node.data + '-->'; |
| 3424 default: |
| 3425 console.error(node); |
| 3426 throw new Error('not implemented'); |
| 3427 } |
| 3428 } |
| 3429 function getInnerHTML(node, composed) { |
| 3430 if (node instanceof HTMLTemplateElement) |
| 3431 node = node.content; |
| 3432 var s = ''; |
| 3433 var c$ = Polymer.dom(node).childNodes; |
| 3434 c$ = composed ? node._composedChildren : c$; |
| 3435 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { |
| 3436 s += getOuterHTML(child, node, composed); |
| 3437 } |
| 3438 return s; |
| 3439 } |
| 3440 return { getInnerHTML: getInnerHTML }; |
| 3441 }(); |
| 3442 Polymer.DomApi = function () { |
| 3443 'use strict'; |
| 3444 var Settings = Polymer.Settings; |
| 3445 var getInnerHTML = Polymer.domInnerHTML.getInnerHTML; |
| 3446 var nativeInsertBefore = Element.prototype.insertBefore; |
| 3447 var nativeRemoveChild = Element.prototype.removeChild; |
| 3448 var nativeAppendChild = Element.prototype.appendChild; |
| 3449 var nativeCloneNode = Element.prototype.cloneNode; |
| 3450 var nativeImportNode = Document.prototype.importNode; |
| 3451 var DomApi = function (node) { |
| 3452 this.node = node; |
| 3453 if (this.patch) { |
| 3454 this.patch(); |
| 3455 } |
| 3456 }; |
| 3457 if (window.wrap && Settings.useShadow && !Settings.useNativeShadow) { |
| 3458 DomApi = function (node) { |
| 3459 this.node = wrap(node); |
| 3460 if (this.patch) { |
| 3461 this.patch(); |
| 3462 } |
| 3463 }; |
| 3464 } |
| 3465 DomApi.prototype = { |
| 3466 flush: function () { |
| 3467 Polymer.dom.flush(); |
| 3468 }, |
| 3469 _lazyDistribute: function (host) { |
| 3470 if (host.shadyRoot && host.shadyRoot._distributionClean) { |
| 3471 host.shadyRoot._distributionClean = false; |
| 3472 Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent)); |
| 3473 } |
| 3474 }, |
| 3475 appendChild: function (node) { |
| 3476 return this._addNode(node); |
| 3477 }, |
| 3478 insertBefore: function (node, ref_node) { |
| 3479 return this._addNode(node, ref_node); |
| 3480 }, |
| 3481 _addNode: function (node, ref_node) { |
| 3482 this._removeNodeFromHost(node, true); |
| 3483 var addedInsertionPoint; |
| 3484 var root = this.getOwnerRoot(); |
| 3485 if (root) { |
| 3486 addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node); |
| 3487 } |
| 3488 if (this._nodeHasLogicalChildren(this.node)) { |
| 3489 if (ref_node) { |
| 3490 var children = this.childNodes; |
| 3491 var index = children.indexOf(ref_node); |
| 3492 if (index < 0) { |
| 3493 throw Error('The ref_node to be inserted before is not a child ' + 'of this node
'); |
| 3494 } |
| 3495 } |
| 3496 this._addLogicalInfo(node, this.node, index); |
| 3497 } |
| 3498 this._addNodeToHost(node); |
| 3499 if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode
(node)) { |
| 3500 if (ref_node) { |
| 3501 ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) :
ref_node; |
| 3502 } |
| 3503 var container = this.node._isShadyRoot ? this.node.host : this.node; |
| 3504 addToComposedParent(container, node, ref_node); |
| 3505 if (ref_node) { |
| 3506 nativeInsertBefore.call(container, node, ref_node); |
| 3507 } else { |
| 3508 nativeAppendChild.call(container, node); |
| 3509 } |
| 3510 } |
| 3511 if (addedInsertionPoint) { |
| 3512 this._updateInsertionPoints(root.host); |
| 3513 } |
| 3514 return node; |
| 3515 }, |
| 3516 removeChild: function (node) { |
| 3517 if (factory(node).parentNode !== this.node) { |
| 3518 console.warn('The node to be removed is not a child of this node', node); |
| 3519 } |
| 3520 this._removeNodeFromHost(node); |
| 3521 if (!this._maybeDistribute(node, this.node)) { |
| 3522 var container = this.node._isShadyRoot ? this.node.host : this.node; |
| 3523 if (container === node.parentNode) { |
| 3524 removeFromComposedParent(container, node); |
| 3525 nativeRemoveChild.call(container, node); |
| 3526 } |
| 3527 } |
| 3528 return node; |
| 3529 }, |
| 3530 replaceChild: function (node, ref_node) { |
| 3531 this.insertBefore(node, ref_node); |
| 3532 this.removeChild(ref_node); |
| 3533 return node; |
| 3534 }, |
| 3535 _hasCachedOwnerRoot: function (node) { |
| 3536 return Boolean(node._ownerShadyRoot !== undefined); |
| 3537 }, |
| 3538 getOwnerRoot: function () { |
| 3539 return this._ownerShadyRootForNode(this.node); |
| 3540 }, |
| 3541 _ownerShadyRootForNode: function (node) { |
| 3542 if (!node) { |
| 3543 return; |
| 3544 } |
| 3545 if (node._ownerShadyRoot === undefined) { |
| 3546 var root; |
| 3547 if (node._isShadyRoot) { |
| 3548 root = node; |
| 3549 } else { |
| 3550 var parent = Polymer.dom(node).parentNode; |
| 3551 if (parent) { |
| 3552 root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent); |
| 3553 } else { |
| 3554 root = null; |
| 3555 } |
| 3556 } |
| 3557 node._ownerShadyRoot = root; |
| 3558 } |
| 3559 return node._ownerShadyRoot; |
| 3560 }, |
| 3561 _maybeDistribute: function (node, parent) { |
| 3562 var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noCon
tent && Polymer.dom(node).querySelector(CONTENT); |
| 3563 var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType
!== Node.DOCUMENT_FRAGMENT_NODE; |
| 3564 var hasContent = fragContent || node.localName === CONTENT; |
| 3565 if (hasContent) { |
| 3566 var root = this._ownerShadyRootForNode(parent); |
| 3567 if (root) { |
| 3568 var host = root.host; |
| 3569 this._lazyDistribute(host); |
| 3570 } |
| 3571 } |
| 3572 var parentNeedsDist = this._parentNeedsDistribution(parent); |
| 3573 if (parentNeedsDist) { |
| 3574 this._lazyDistribute(parent); |
| 3575 } |
| 3576 return parentNeedsDist || hasContent && !wrappedContent; |
| 3577 }, |
| 3578 _maybeAddInsertionPoint: function (node, parent) { |
| 3579 var added; |
| 3580 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) { |
| 3581 var c$ = factory(node).querySelectorAll(CONTENT); |
| 3582 for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) { |
| 3583 np = factory(n).parentNode; |
| 3584 if (np === node) { |
| 3585 np = parent; |
| 3586 } |
| 3587 na = this._maybeAddInsertionPoint(n, np); |
| 3588 added = added || na; |
| 3589 } |
| 3590 } else if (node.localName === CONTENT) { |
| 3591 saveLightChildrenIfNeeded(parent); |
| 3592 saveLightChildrenIfNeeded(node); |
| 3593 added = true; |
| 3594 } |
| 3595 return added; |
| 3596 }, |
| 3597 _tryRemoveUndistributedNode: function (node) { |
| 3598 if (this.node.shadyRoot) { |
| 3599 var parent = getComposedParent(node); |
| 3600 if (parent) { |
| 3601 nativeRemoveChild.call(parent, node); |
| 3602 } |
| 3603 return true; |
| 3604 } |
| 3605 }, |
| 3606 _updateInsertionPoints: function (host) { |
| 3607 var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelector
All(CONTENT); |
| 3608 for (var i = 0, c; i < i$.length; i++) { |
| 3609 c = i$[i]; |
| 3610 saveLightChildrenIfNeeded(c); |
| 3611 saveLightChildrenIfNeeded(factory(c).parentNode); |
| 3612 } |
| 3613 }, |
| 3614 _nodeHasLogicalChildren: function (node) { |
| 3615 return Boolean(node._lightChildren !== undefined); |
| 3616 }, |
| 3617 _parentNeedsDistribution: function (parent) { |
| 3618 return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot); |
| 3619 }, |
| 3620 _removeNodeFromHost: function (node, ensureComposedRemoval) { |
| 3621 var hostNeedsDist; |
| 3622 var root; |
| 3623 var parent = node._lightParent; |
| 3624 if (parent) { |
| 3625 factory(node)._distributeParent(); |
| 3626 root = this._ownerShadyRootForNode(node); |
| 3627 if (root) { |
| 3628 root.host._elementRemove(node); |
| 3629 hostNeedsDist = this._removeDistributedChildren(root, node); |
| 3630 } |
| 3631 this._removeLogicalInfo(node, node._lightParent); |
| 3632 } |
| 3633 this._removeOwnerShadyRoot(node); |
| 3634 if (root && hostNeedsDist) { |
| 3635 this._updateInsertionPoints(root.host); |
| 3636 this._lazyDistribute(root.host); |
| 3637 } else if (ensureComposedRemoval) { |
| 3638 removeFromComposedParent(getComposedParent(node), node); |
| 3639 } |
| 3640 }, |
| 3641 _removeDistributedChildren: function (root, container) { |
| 3642 var hostNeedsDist; |
| 3643 var ip$ = root._insertionPoints; |
| 3644 for (var i = 0; i < ip$.length; i++) { |
| 3645 var content = ip$[i]; |
| 3646 if (this._contains(container, content)) { |
| 3647 var dc$ = factory(content).getDistributedNodes(); |
| 3648 for (var j = 0; j < dc$.length; j++) { |
| 3649 hostNeedsDist = true; |
| 3650 var node = dc$[j]; |
| 3651 var parent = node.parentNode; |
| 3652 if (parent) { |
| 3653 removeFromComposedParent(parent, node); |
| 3654 nativeRemoveChild.call(parent, node); |
| 3655 } |
| 3656 } |
| 3657 } |
| 3658 } |
| 3659 return hostNeedsDist; |
| 3660 }, |
| 3661 _contains: function (container, node) { |
| 3662 while (node) { |
| 3663 if (node == container) { |
| 3664 return true; |
| 3665 } |
| 3666 node = factory(node).parentNode; |
| 3667 } |
| 3668 }, |
| 3669 _addNodeToHost: function (node) { |
| 3670 var root = this.getOwnerRoot(); |
| 3671 if (root) { |
| 3672 root.host._elementAdd(node); |
| 3673 } |
| 3674 }, |
| 3675 _addLogicalInfo: function (node, container, index) { |
| 3676 var children = factory(container).childNodes; |
| 3677 index = index === undefined ? children.length : index; |
| 3678 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 3679 var c$ = Array.prototype.slice.call(node.childNodes); |
| 3680 for (var i = 0, n; i < c$.length && (n = c$[i]); i++) { |
| 3681 children.splice(index++, 0, n); |
| 3682 n._lightParent = container; |
| 3683 } |
| 3684 } else { |
| 3685 children.splice(index, 0, node); |
| 3686 node._lightParent = container; |
| 3687 } |
| 3688 }, |
| 3689 _removeLogicalInfo: function (node, container) { |
| 3690 var children = factory(container).childNodes; |
| 3691 var index = children.indexOf(node); |
| 3692 if (index < 0 || container !== node._lightParent) { |
| 3693 throw Error('The node to be removed is not a child of this node'); |
| 3694 } |
| 3695 children.splice(index, 1); |
| 3696 node._lightParent = null; |
| 3697 }, |
| 3698 _removeOwnerShadyRoot: function (node) { |
| 3699 if (this._hasCachedOwnerRoot(node)) { |
| 3700 var c$ = factory(node).childNodes; |
| 3701 for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) { |
| 3702 this._removeOwnerShadyRoot(n); |
| 3703 } |
| 3704 } |
| 3705 node._ownerShadyRoot = undefined; |
| 3706 }, |
| 3707 _firstComposedNode: function (content) { |
| 3708 var n$ = factory(content).getDistributedNodes(); |
| 3709 for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) { |
| 3710 p$ = factory(n).getDestinationInsertionPoints(); |
| 3711 if (p$[p$.length - 1] === content) { |
| 3712 return n; |
| 3713 } |
| 3714 } |
| 3715 }, |
| 3716 querySelector: function (selector) { |
| 3717 return this.querySelectorAll(selector)[0]; |
| 3718 }, |
| 3719 querySelectorAll: function (selector) { |
| 3720 return this._query(function (n) { |
| 3721 return matchesSelector.call(n, selector); |
| 3722 }, this.node); |
| 3723 }, |
| 3724 _query: function (matcher, node) { |
| 3725 node = node || this.node; |
| 3726 var list = []; |
| 3727 this._queryElements(factory(node).childNodes, matcher, list); |
| 3728 return list; |
| 3729 }, |
| 3730 _queryElements: function (elements, matcher, list) { |
| 3731 for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) { |
| 3732 if (c.nodeType === Node.ELEMENT_NODE) { |
| 3733 this._queryElement(c, matcher, list); |
| 3734 } |
| 3735 } |
| 3736 }, |
| 3737 _queryElement: function (node, matcher, list) { |
| 3738 if (matcher(node)) { |
| 3739 list.push(node); |
| 3740 } |
| 3741 this._queryElements(factory(node).childNodes, matcher, list); |
| 3742 }, |
| 3743 getDestinationInsertionPoints: function () { |
| 3744 return this.node._destinationInsertionPoints || []; |
| 3745 }, |
| 3746 getDistributedNodes: function () { |
| 3747 return this.node._distributedNodes || []; |
| 3748 }, |
| 3749 queryDistributedElements: function (selector) { |
| 3750 var c$ = this.childNodes; |
| 3751 var list = []; |
| 3752 this._distributedFilter(selector, c$, list); |
| 3753 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 3754 if (c.localName === CONTENT) { |
| 3755 this._distributedFilter(selector, factory(c).getDistributedNodes(), list); |
| 3756 } |
| 3757 } |
| 3758 return list; |
| 3759 }, |
| 3760 _distributedFilter: function (selector, list, results) { |
| 3761 results = results || []; |
| 3762 for (var i = 0, l = list.length, d; i < l && (d = list[i]); i++) { |
| 3763 if (d.nodeType === Node.ELEMENT_NODE && d.localName !== CONTENT && matchesSelect
or.call(d, selector)) { |
| 3764 results.push(d); |
| 3765 } |
| 3766 } |
| 3767 return results; |
| 3768 }, |
| 3769 _clear: function () { |
| 3770 while (this.childNodes.length) { |
| 3771 this.removeChild(this.childNodes[0]); |
| 3772 } |
| 3773 }, |
| 3774 setAttribute: function (name, value) { |
| 3775 this.node.setAttribute(name, value); |
| 3776 this._distributeParent(); |
| 3777 }, |
| 3778 removeAttribute: function (name) { |
| 3779 this.node.removeAttribute(name); |
| 3780 this._distributeParent(); |
| 3781 }, |
| 3782 _distributeParent: function () { |
| 3783 if (this._parentNeedsDistribution(this.parentNode)) { |
| 3784 this._lazyDistribute(this.parentNode); |
| 3785 } |
| 3786 }, |
| 3787 cloneNode: function (deep) { |
| 3788 var n = nativeCloneNode.call(this.node, false); |
| 3789 if (deep) { |
| 3790 var c$ = this.childNodes; |
| 3791 var d = factory(n); |
| 3792 for (var i = 0, nc; i < c$.length; i++) { |
| 3793 nc = factory(c$[i]).cloneNode(true); |
| 3794 d.appendChild(nc); |
| 3795 } |
| 3796 } |
| 3797 return n; |
| 3798 }, |
| 3799 importNode: function (externalNode, deep) { |
| 3800 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; |
| 3801 var n = nativeImportNode.call(doc, externalNode, false); |
| 3802 if (deep) { |
| 3803 var c$ = factory(externalNode).childNodes; |
| 3804 var d = factory(n); |
| 3805 for (var i = 0, nc; i < c$.length; i++) { |
| 3806 nc = factory(doc).importNode(c$[i], true); |
| 3807 d.appendChild(nc); |
| 3808 } |
| 3809 } |
| 3810 return n; |
| 3811 } |
| 3812 }; |
| 3813 Object.defineProperty(DomApi.prototype, 'classList', { |
| 3814 get: function () { |
| 3815 if (!this._classList) { |
| 3816 this._classList = new DomApi.ClassList(this); |
| 3817 } |
| 3818 return this._classList; |
| 3819 }, |
| 3820 configurable: true |
| 3821 }); |
| 3822 DomApi.ClassList = function (host) { |
| 3823 this.domApi = host; |
| 3824 this.node = host.node; |
| 3825 }; |
| 3826 DomApi.ClassList.prototype = { |
| 3827 add: function () { |
| 3828 this.node.classList.add.apply(this.node.classList, arguments); |
| 3829 this.domApi._distributeParent(); |
| 3830 }, |
| 3831 remove: function () { |
| 3832 this.node.classList.remove.apply(this.node.classList, arguments); |
| 3833 this.domApi._distributeParent(); |
| 3834 }, |
| 3835 toggle: function () { |
| 3836 this.node.classList.toggle.apply(this.node.classList, arguments); |
| 3837 this.domApi._distributeParent(); |
| 3838 }, |
| 3839 contains: function () { |
| 3840 return this.node.classList.contains.apply(this.node.classList, arguments); |
| 3841 } |
| 3842 }; |
| 3843 if (!Settings.useShadow) { |
| 3844 Object.defineProperties(DomApi.prototype, { |
| 3845 childNodes: { |
| 3846 get: function () { |
| 3847 var c$ = getLightChildren(this.node); |
| 3848 return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$); |
| 3849 }, |
| 3850 configurable: true |
| 3851 }, |
| 3852 children: { |
| 3853 get: function () { |
| 3854 return Array.prototype.filter.call(this.childNodes, function (n) { |
| 3855 return n.nodeType === Node.ELEMENT_NODE; |
| 3856 }); |
| 3857 }, |
| 3858 configurable: true |
| 3859 }, |
| 3860 parentNode: { |
| 3861 get: function () { |
| 3862 return this.node._lightParent || getComposedParent(this.node); |
| 3863 }, |
| 3864 configurable: true |
| 3865 }, |
| 3866 firstChild: { |
| 3867 get: function () { |
| 3868 return this.childNodes[0]; |
| 3869 }, |
| 3870 configurable: true |
| 3871 }, |
| 3872 lastChild: { |
| 3873 get: function () { |
| 3874 var c$ = this.childNodes; |
| 3875 return c$[c$.length - 1]; |
| 3876 }, |
| 3877 configurable: true |
| 3878 }, |
| 3879 nextSibling: { |
| 3880 get: function () { |
| 3881 var c$ = this.parentNode && factory(this.parentNode).childNodes; |
| 3882 if (c$) { |
| 3883 return c$[Array.prototype.indexOf.call(c$, this.node) + 1]; |
| 3884 } |
| 3885 }, |
| 3886 configurable: true |
| 3887 }, |
| 3888 previousSibling: { |
| 3889 get: function () { |
| 3890 var c$ = this.parentNode && factory(this.parentNode).childNodes; |
| 3891 if (c$) { |
| 3892 return c$[Array.prototype.indexOf.call(c$, this.node) - 1]; |
| 3893 } |
| 3894 }, |
| 3895 configurable: true |
| 3896 }, |
| 3897 firstElementChild: { |
| 3898 get: function () { |
| 3899 return this.children[0]; |
| 3900 }, |
| 3901 configurable: true |
| 3902 }, |
| 3903 lastElementChild: { |
| 3904 get: function () { |
| 3905 var c$ = this.children; |
| 3906 return c$[c$.length - 1]; |
| 3907 }, |
| 3908 configurable: true |
| 3909 }, |
| 3910 nextElementSibling: { |
| 3911 get: function () { |
| 3912 var c$ = this.parentNode && factory(this.parentNode).children; |
| 3913 if (c$) { |
| 3914 return c$[Array.prototype.indexOf.call(c$, this.node) + 1]; |
| 3915 } |
| 3916 }, |
| 3917 configurable: true |
| 3918 }, |
| 3919 previousElementSibling: { |
| 3920 get: function () { |
| 3921 var c$ = this.parentNode && factory(this.parentNode).children; |
| 3922 if (c$) { |
| 3923 return c$[Array.prototype.indexOf.call(c$, this.node) - 1]; |
| 3924 } |
| 3925 }, |
| 3926 configurable: true |
| 3927 }, |
| 3928 textContent: { |
| 3929 get: function () { |
| 3930 var nt = this.node.nodeType; |
| 3931 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { |
| 3932 return this.node.textContent; |
| 3933 } else { |
| 3934 var tc = []; |
| 3935 for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) { |
| 3936 if (c.nodeType !== Node.COMMENT_NODE) { |
| 3937 tc.push(c.textContent); |
| 3938 } |
| 3939 } |
| 3940 return tc.join(''); |
| 3941 } |
| 3942 }, |
| 3943 set: function (text) { |
| 3944 var nt = this.node.nodeType; |
| 3945 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { |
| 3946 this.node.textContent = text; |
| 3947 } else { |
| 3948 this._clear(); |
| 3949 if (text) { |
| 3950 this.appendChild(document.createTextNode(text)); |
| 3951 } |
| 3952 } |
| 3953 }, |
| 3954 configurable: true |
| 3955 }, |
| 3956 innerHTML: { |
| 3957 get: function () { |
| 3958 var nt = this.node.nodeType; |
| 3959 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { |
| 3960 return null; |
| 3961 } else { |
| 3962 return getInnerHTML(this.node); |
| 3963 } |
| 3964 }, |
| 3965 set: function (text) { |
| 3966 var nt = this.node.nodeType; |
| 3967 if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) { |
| 3968 this._clear(); |
| 3969 var d = document.createElement('div'); |
| 3970 d.innerHTML = text; |
| 3971 var c$ = Array.prototype.slice.call(d.childNodes); |
| 3972 for (var i = 0; i < c$.length; i++) { |
| 3973 this.appendChild(c$[i]); |
| 3974 } |
| 3975 } |
| 3976 }, |
| 3977 configurable: true |
| 3978 } |
| 3979 }); |
| 3980 DomApi.prototype._getComposedInnerHTML = function () { |
| 3981 return getInnerHTML(this.node, true); |
| 3982 }; |
| 3983 } else { |
| 3984 var forwardMethods = [ |
| 3985 'cloneNode', |
| 3986 'appendChild', |
| 3987 'insertBefore', |
| 3988 'removeChild', |
| 3989 'replaceChild' |
| 3990 ]; |
| 3991 forwardMethods.forEach(function (name) { |
| 3992 DomApi.prototype[name] = function () { |
| 3993 return this.node[name].apply(this.node, arguments); |
| 3994 }; |
| 3995 }); |
| 3996 DomApi.prototype.querySelectorAll = function (selector) { |
| 3997 return Array.prototype.slice.call(this.node.querySelectorAll(selector)); |
| 3998 }; |
| 3999 DomApi.prototype.getOwnerRoot = function () { |
| 4000 var n = this.node; |
| 4001 while (n) { |
| 4002 if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) { |
| 4003 return n; |
| 4004 } |
| 4005 n = n.parentNode; |
| 4006 } |
| 4007 }; |
| 4008 DomApi.prototype.importNode = function (externalNode, deep) { |
| 4009 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; |
| 4010 return doc.importNode(externalNode, deep); |
| 4011 }; |
| 4012 DomApi.prototype.getDestinationInsertionPoints = function () { |
| 4013 var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInse
rtionPoints(); |
| 4014 return n$ ? Array.prototype.slice.call(n$) : []; |
| 4015 }; |
| 4016 DomApi.prototype.getDistributedNodes = function () { |
| 4017 var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes(); |
| 4018 return n$ ? Array.prototype.slice.call(n$) : []; |
| 4019 }; |
| 4020 DomApi.prototype._distributeParent = function () { |
| 4021 }; |
| 4022 Object.defineProperties(DomApi.prototype, { |
| 4023 childNodes: { |
| 4024 get: function () { |
| 4025 return Array.prototype.slice.call(this.node.childNodes); |
| 4026 }, |
| 4027 configurable: true |
| 4028 }, |
| 4029 children: { |
| 4030 get: function () { |
| 4031 return Array.prototype.slice.call(this.node.children); |
| 4032 }, |
| 4033 configurable: true |
| 4034 }, |
| 4035 textContent: { |
| 4036 get: function () { |
| 4037 return this.node.textContent; |
| 4038 }, |
| 4039 set: function (value) { |
| 4040 return this.node.textContent = value; |
| 4041 }, |
| 4042 configurable: true |
| 4043 }, |
| 4044 innerHTML: { |
| 4045 get: function () { |
| 4046 return this.node.innerHTML; |
| 4047 }, |
| 4048 set: function (value) { |
| 4049 return this.node.innerHTML = value; |
| 4050 }, |
| 4051 configurable: true |
| 4052 } |
| 4053 }); |
| 4054 var forwardProperties = [ |
| 4055 'parentNode', |
| 4056 'firstChild', |
| 4057 'lastChild', |
| 4058 'nextSibling', |
| 4059 'previousSibling', |
| 4060 'firstElementChild', |
| 4061 'lastElementChild', |
| 4062 'nextElementSibling', |
| 4063 'previousElementSibling' |
| 4064 ]; |
| 4065 forwardProperties.forEach(function (name) { |
| 4066 Object.defineProperty(DomApi.prototype, name, { |
| 4067 get: function () { |
| 4068 return this.node[name]; |
| 4069 }, |
| 4070 configurable: true |
| 4071 }); |
| 4072 }); |
| 4073 } |
| 4074 var CONTENT = 'content'; |
| 4075 var factory = function (node, patch) { |
| 4076 node = node || document; |
| 4077 if (!node.__domApi) { |
| 4078 node.__domApi = new DomApi(node, patch); |
| 4079 } |
| 4080 return node.__domApi; |
| 4081 }; |
| 4082 Polymer.dom = function (obj, patch) { |
| 4083 if (obj instanceof Event) { |
| 4084 return Polymer.EventApi.factory(obj); |
| 4085 } else { |
| 4086 return factory(obj, patch); |
| 4087 } |
| 4088 }; |
| 4089 Polymer.Base.extend(Polymer.dom, { |
| 4090 _flushGuard: 0, |
| 4091 _FLUSH_MAX: 100, |
| 4092 _needsTakeRecords: !Polymer.Settings.useNativeCustomElements, |
| 4093 _debouncers: [], |
| 4094 _finishDebouncer: null, |
| 4095 flush: function () { |
| 4096 for (var i = 0; i < this._debouncers.length; i++) { |
| 4097 this._debouncers[i].complete(); |
| 4098 } |
| 4099 if (this._finishDebouncer) { |
| 4100 this._finishDebouncer.complete(); |
| 4101 } |
| 4102 this._flushPolyfills(); |
| 4103 if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { |
| 4104 this._flushGuard++; |
| 4105 this.flush(); |
| 4106 } else { |
| 4107 if (this._flushGuard >= this._FLUSH_MAX) { |
| 4108 console.warn('Polymer.dom.flush aborted. Flush may not be complete.'); |
| 4109 } |
| 4110 this._flushGuard = 0; |
| 4111 } |
| 4112 }, |
| 4113 _flushPolyfills: function () { |
| 4114 if (this._needsTakeRecords) { |
| 4115 CustomElements.takeRecords(); |
| 4116 } |
| 4117 }, |
| 4118 addDebouncer: function (debouncer) { |
| 4119 this._debouncers.push(debouncer); |
| 4120 this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlus
h); |
| 4121 }, |
| 4122 _finishFlush: function () { |
| 4123 Polymer.dom._debouncers = []; |
| 4124 } |
| 4125 }); |
| 4126 function getLightChildren(node) { |
| 4127 var children = node._lightChildren; |
| 4128 return children ? children : node.childNodes; |
| 4129 } |
| 4130 function getComposedChildren(node) { |
| 4131 if (!node._composedChildren) { |
| 4132 node._composedChildren = Array.prototype.slice.call(node.childNodes); |
| 4133 } |
| 4134 return node._composedChildren; |
| 4135 } |
| 4136 function addToComposedParent(parent, node, ref_node) { |
| 4137 var children = getComposedChildren(parent); |
| 4138 var i = ref_node ? children.indexOf(ref_node) : -1; |
| 4139 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 4140 var fragChildren = getComposedChildren(node); |
| 4141 for (var j = 0; j < fragChildren.length; j++) { |
| 4142 addNodeToComposedChildren(fragChildren[j], parent, children, i + j); |
| 4143 } |
| 4144 node._composedChildren = null; |
| 4145 } else { |
| 4146 addNodeToComposedChildren(node, parent, children, i); |
| 4147 } |
| 4148 } |
| 4149 function getComposedParent(node) { |
| 4150 return node.__patched ? node._composedParent : node.parentNode; |
| 4151 } |
| 4152 function addNodeToComposedChildren(node, parent, children, i) { |
| 4153 node._composedParent = parent; |
| 4154 children.splice(i >= 0 ? i : children.length, 0, node); |
| 4155 } |
| 4156 function removeFromComposedParent(parent, node) { |
| 4157 node._composedParent = null; |
| 4158 if (parent) { |
| 4159 var children = getComposedChildren(parent); |
| 4160 var i = children.indexOf(node); |
| 4161 if (i >= 0) { |
| 4162 children.splice(i, 1); |
| 4163 } |
| 4164 } |
| 4165 } |
| 4166 function saveLightChildrenIfNeeded(node) { |
| 4167 if (!node._lightChildren) { |
| 4168 var c$ = Array.prototype.slice.call(node.childNodes); |
| 4169 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { |
| 4170 child._lightParent = child._lightParent || node; |
| 4171 } |
| 4172 node._lightChildren = c$; |
| 4173 } |
| 4174 } |
| 4175 function hasInsertionPoint(root) { |
| 4176 return Boolean(root && root._insertionPoints.length); |
| 4177 } |
| 4178 var p = Element.prototype; |
| 4179 var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector ||
p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector; |
| 4180 return { |
| 4181 getLightChildren: getLightChildren, |
| 4182 getComposedParent: getComposedParent, |
| 4183 getComposedChildren: getComposedChildren, |
| 4184 removeFromComposedParent: removeFromComposedParent, |
| 4185 saveLightChildrenIfNeeded: saveLightChildrenIfNeeded, |
| 4186 matchesSelector: matchesSelector, |
| 4187 hasInsertionPoint: hasInsertionPoint, |
| 4188 ctor: DomApi, |
| 4189 factory: factory |
| 4190 }; |
| 4191 }(); |
| 4192 (function () { |
| 4193 Polymer.Base._addFeature({ |
| 4194 _prepShady: function () { |
| 4195 this._useContent = this._useContent || Boolean(this._template); |
| 4196 }, |
| 4197 _poolContent: function () { |
| 4198 if (this._useContent) { |
| 4199 saveLightChildrenIfNeeded(this); |
| 4200 } |
| 4201 }, |
| 4202 _setupRoot: function () { |
| 4203 if (this._useContent) { |
| 4204 this._createLocalRoot(); |
| 4205 if (!this.dataHost) { |
| 4206 upgradeLightChildren(this._lightChildren); |
| 4207 } |
| 4208 } |
| 4209 }, |
| 4210 _createLocalRoot: function () { |
| 4211 this.shadyRoot = this.root; |
| 4212 this.shadyRoot._distributionClean = false; |
| 4213 this.shadyRoot._isShadyRoot = true; |
| 4214 this.shadyRoot._dirtyRoots = []; |
| 4215 var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasConte
nt ? this.shadyRoot.querySelectorAll('content') : []; |
| 4216 saveLightChildrenIfNeeded(this.shadyRoot); |
| 4217 for (var i = 0, c; i < i$.length; i++) { |
| 4218 c = i$[i]; |
| 4219 saveLightChildrenIfNeeded(c); |
| 4220 saveLightChildrenIfNeeded(c.parentNode); |
| 4221 } |
| 4222 this.shadyRoot.host = this; |
| 4223 }, |
| 4224 get domHost() { |
| 4225 var root = Polymer.dom(this).getOwnerRoot(); |
| 4226 return root && root.host; |
| 4227 }, |
| 4228 distributeContent: function (updateInsertionPoints) { |
| 4229 if (this.shadyRoot) { |
| 4230 var dom = Polymer.dom(this); |
| 4231 if (updateInsertionPoints) { |
| 4232 dom._updateInsertionPoints(this); |
| 4233 } |
| 4234 var host = getTopDistributingHost(this); |
| 4235 dom._lazyDistribute(host); |
| 4236 } |
| 4237 }, |
| 4238 _distributeContent: function () { |
| 4239 if (this._useContent && !this.shadyRoot._distributionClean) { |
| 4240 this._beginDistribute(); |
| 4241 this._distributeDirtyRoots(); |
| 4242 this._finishDistribute(); |
| 4243 } |
| 4244 }, |
| 4245 _beginDistribute: function () { |
| 4246 if (this._useContent && hasInsertionPoint(this.shadyRoot)) { |
| 4247 this._resetDistribution(); |
| 4248 this._distributePool(this.shadyRoot, this._collectPool()); |
| 4249 } |
| 4250 }, |
| 4251 _distributeDirtyRoots: function () { |
| 4252 var c$ = this.shadyRoot._dirtyRoots; |
| 4253 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 4254 c._distributeContent(); |
| 4255 } |
| 4256 this.shadyRoot._dirtyRoots = []; |
| 4257 }, |
| 4258 _finishDistribute: function () { |
| 4259 if (this._useContent) { |
| 4260 this.shadyRoot._distributionClean = true; |
| 4261 if (hasInsertionPoint(this.shadyRoot)) { |
| 4262 this._composeTree(); |
| 4263 } else { |
| 4264 if (!this.shadyRoot._hasDistributed) { |
| 4265 this.textContent = ''; |
| 4266 this._composedChildren = null; |
| 4267 this.appendChild(this.shadyRoot); |
| 4268 } else { |
| 4269 var children = this._composeNode(this); |
| 4270 this._updateChildNodes(this, children); |
| 4271 } |
| 4272 } |
| 4273 this.shadyRoot._hasDistributed = true; |
| 4274 } |
| 4275 }, |
| 4276 elementMatches: function (selector, node) { |
| 4277 node = node || this; |
| 4278 return matchesSelector.call(node, selector); |
| 4279 }, |
| 4280 _resetDistribution: function () { |
| 4281 var children = getLightChildren(this); |
| 4282 for (var i = 0; i < children.length; i++) { |
| 4283 var child = children[i]; |
| 4284 if (child._destinationInsertionPoints) { |
| 4285 child._destinationInsertionPoints = undefined; |
| 4286 } |
| 4287 if (isInsertionPoint(child)) { |
| 4288 clearDistributedDestinationInsertionPoints(child); |
| 4289 } |
| 4290 } |
| 4291 var root = this.shadyRoot; |
| 4292 var p$ = root._insertionPoints; |
| 4293 for (var j = 0; j < p$.length; j++) { |
| 4294 p$[j]._distributedNodes = []; |
| 4295 } |
| 4296 }, |
| 4297 _collectPool: function () { |
| 4298 var pool = []; |
| 4299 var children = getLightChildren(this); |
| 4300 for (var i = 0; i < children.length; i++) { |
| 4301 var child = children[i]; |
| 4302 if (isInsertionPoint(child)) { |
| 4303 pool.push.apply(pool, child._distributedNodes); |
| 4304 } else { |
| 4305 pool.push(child); |
| 4306 } |
| 4307 } |
| 4308 return pool; |
| 4309 }, |
| 4310 _distributePool: function (node, pool) { |
| 4311 var p$ = node._insertionPoints; |
| 4312 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) { |
| 4313 this._distributeInsertionPoint(p, pool); |
| 4314 maybeRedistributeParent(p, this); |
| 4315 } |
| 4316 }, |
| 4317 _distributeInsertionPoint: function (content, pool) { |
| 4318 var anyDistributed = false; |
| 4319 for (var i = 0, l = pool.length, node; i < l; i++) { |
| 4320 node = pool[i]; |
| 4321 if (!node) { |
| 4322 continue; |
| 4323 } |
| 4324 if (this._matchesContentSelect(node, content)) { |
| 4325 distributeNodeInto(node, content); |
| 4326 pool[i] = undefined; |
| 4327 anyDistributed = true; |
| 4328 } |
| 4329 } |
| 4330 if (!anyDistributed) { |
| 4331 var children = getLightChildren(content); |
| 4332 for (var j = 0; j < children.length; j++) { |
| 4333 distributeNodeInto(children[j], content); |
| 4334 } |
| 4335 } |
| 4336 }, |
| 4337 _composeTree: function () { |
| 4338 this._updateChildNodes(this, this._composeNode(this)); |
| 4339 var p$ = this.shadyRoot._insertionPoints; |
| 4340 for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) { |
| 4341 parent = p._lightParent || p.parentNode; |
| 4342 if (!parent._useContent && parent !== this && parent !== this.shadyRoot) { |
| 4343 this._updateChildNodes(parent, this._composeNode(parent)); |
| 4344 } |
| 4345 } |
| 4346 }, |
| 4347 _composeNode: function (node) { |
| 4348 var children = []; |
| 4349 var c$ = getLightChildren(node.shadyRoot || node); |
| 4350 for (var i = 0; i < c$.length; i++) { |
| 4351 var child = c$[i]; |
| 4352 if (isInsertionPoint(child)) { |
| 4353 var distributedNodes = child._distributedNodes; |
| 4354 for (var j = 0; j < distributedNodes.length; j++) { |
| 4355 var distributedNode = distributedNodes[j]; |
| 4356 if (isFinalDestination(child, distributedNode)) { |
| 4357 children.push(distributedNode); |
| 4358 } |
| 4359 } |
| 4360 } else { |
| 4361 children.push(child); |
| 4362 } |
| 4363 } |
| 4364 return children; |
| 4365 }, |
| 4366 _updateChildNodes: function (container, children) { |
| 4367 var composed = getComposedChildren(container); |
| 4368 var splices = Polymer.ArraySplice.calculateSplices(children, composed); |
| 4369 for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) { |
| 4370 for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) { |
| 4371 if (getComposedParent(n) === container) { |
| 4372 remove(n); |
| 4373 } |
| 4374 composed.splice(s.index + d, 1); |
| 4375 } |
| 4376 d -= s.addedCount; |
| 4377 } |
| 4378 for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) { |
| 4379 next = composed[s.index]; |
| 4380 for (var j = s.index, n; j < s.index + s.addedCount; j++) { |
| 4381 n = children[j]; |
| 4382 insertBefore(container, n, next); |
| 4383 composed.splice(j, 0, n); |
| 4384 } |
| 4385 } |
| 4386 ensureComposedParent(container, children); |
| 4387 }, |
| 4388 _matchesContentSelect: function (node, contentElement) { |
| 4389 var select = contentElement.getAttribute('select'); |
| 4390 if (!select) { |
| 4391 return true; |
| 4392 } |
| 4393 select = select.trim(); |
| 4394 if (!select) { |
| 4395 return true; |
| 4396 } |
| 4397 if (!(node instanceof Element)) { |
| 4398 return false; |
| 4399 } |
| 4400 var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/; |
| 4401 if (!validSelectors.test(select)) { |
| 4402 return false; |
| 4403 } |
| 4404 return this.elementMatches(select, node); |
| 4405 }, |
| 4406 _elementAdd: function () { |
| 4407 }, |
| 4408 _elementRemove: function () { |
| 4409 } |
| 4410 }); |
| 4411 var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded; |
| 4412 var getLightChildren = Polymer.DomApi.getLightChildren; |
| 4413 var matchesSelector = Polymer.DomApi.matchesSelector; |
| 4414 var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint; |
| 4415 var getComposedChildren = Polymer.DomApi.getComposedChildren; |
| 4416 var getComposedParent = Polymer.DomApi.getComposedParent; |
| 4417 var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent; |
| 4418 function distributeNodeInto(child, insertionPoint) { |
| 4419 insertionPoint._distributedNodes.push(child); |
| 4420 var points = child._destinationInsertionPoints; |
| 4421 if (!points) { |
| 4422 child._destinationInsertionPoints = [insertionPoint]; |
| 4423 } else { |
| 4424 points.push(insertionPoint); |
| 4425 } |
| 4426 } |
| 4427 function clearDistributedDestinationInsertionPoints(content) { |
| 4428 var e$ = content._distributedNodes; |
| 4429 if (e$) { |
| 4430 for (var i = 0; i < e$.length; i++) { |
| 4431 var d = e$[i]._destinationInsertionPoints; |
| 4432 if (d) { |
| 4433 d.splice(d.indexOf(content) + 1, d.length); |
| 4434 } |
| 4435 } |
| 4436 } |
| 4437 } |
| 4438 function maybeRedistributeParent(content, host) { |
| 4439 var parent = content._lightParent; |
| 4440 if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent.
shadyRoot._distributionClean) { |
| 4441 parent.shadyRoot._distributionClean = false; |
| 4442 host.shadyRoot._dirtyRoots.push(parent); |
| 4443 } |
| 4444 } |
| 4445 function isFinalDestination(insertionPoint, node) { |
| 4446 var points = node._destinationInsertionPoints; |
| 4447 return points && points[points.length - 1] === insertionPoint; |
| 4448 } |
| 4449 function isInsertionPoint(node) { |
| 4450 return node.localName == 'content'; |
| 4451 } |
| 4452 var nativeInsertBefore = Element.prototype.insertBefore; |
| 4453 var nativeRemoveChild = Element.prototype.removeChild; |
| 4454 function insertBefore(parentNode, newChild, refChild) { |
| 4455 var newChildParent = getComposedParent(newChild); |
| 4456 if (newChildParent !== parentNode) { |
| 4457 removeFromComposedParent(newChildParent, newChild); |
| 4458 } |
| 4459 remove(newChild); |
| 4460 nativeInsertBefore.call(parentNode, newChild, refChild || null); |
| 4461 newChild._composedParent = parentNode; |
| 4462 } |
| 4463 function remove(node) { |
| 4464 var parentNode = getComposedParent(node); |
| 4465 if (parentNode) { |
| 4466 node._composedParent = null; |
| 4467 nativeRemoveChild.call(parentNode, node); |
| 4468 } |
| 4469 } |
| 4470 function ensureComposedParent(parent, children) { |
| 4471 for (var i = 0, n; i < children.length; i++) { |
| 4472 children[i]._composedParent = parent; |
| 4473 } |
| 4474 } |
| 4475 function getTopDistributingHost(host) { |
| 4476 while (host && hostNeedsRedistribution(host)) { |
| 4477 host = host.domHost; |
| 4478 } |
| 4479 return host; |
| 4480 } |
| 4481 function hostNeedsRedistribution(host) { |
| 4482 var c$ = Polymer.dom(host).children; |
| 4483 for (var i = 0, c; i < c$.length; i++) { |
| 4484 c = c$[i]; |
| 4485 if (c.localName === 'content') { |
| 4486 return host.domHost; |
| 4487 } |
| 4488 } |
| 4489 } |
| 4490 var needsUpgrade = window.CustomElements && !CustomElements.useNative; |
| 4491 function upgradeLightChildren(children) { |
| 4492 if (needsUpgrade && children) { |
| 4493 for (var i = 0; i < children.length; i++) { |
| 4494 CustomElements.upgrade(children[i]); |
| 4495 } |
| 4496 } |
| 4497 } |
| 4498 }()); |
| 4499 if (Polymer.Settings.useShadow) { |
| 4500 Polymer.Base._addFeature({ |
| 4501 _poolContent: function () { |
| 4502 }, |
| 4503 _beginDistribute: function () { |
| 4504 }, |
| 4505 distributeContent: function () { |
| 4506 }, |
| 4507 _distributeContent: function () { |
| 4508 }, |
| 4509 _finishDistribute: function () { |
| 4510 }, |
| 4511 _createLocalRoot: function () { |
| 4512 this.createShadowRoot(); |
| 4513 this.shadowRoot.appendChild(this.root); |
| 4514 this.root = this.shadowRoot; |
| 4515 } |
| 4516 }); |
| 4517 } |
| 4518 Polymer.DomModule = document.createElement('dom-module'); |
| 4519 Polymer.Base._addFeature({ |
| 4520 _registerFeatures: function () { |
| 4521 this._prepIs(); |
| 4522 this._prepAttributes(); |
| 4523 this._prepBehaviors(); |
| 4524 this._prepConstructor(); |
| 4525 this._prepTemplate(); |
| 4526 this._prepShady(); |
| 4527 }, |
| 4528 _prepBehavior: function (b) { |
| 4529 this._addHostAttributes(b.hostAttributes); |
| 4530 }, |
| 4531 _initFeatures: function () { |
| 4532 this._poolContent(); |
| 4533 this._pushHost(); |
| 4534 this._stampTemplate(); |
| 4535 this._popHost(); |
| 4536 this._marshalHostAttributes(); |
| 4537 this._setupDebouncers(); |
| 4538 this._marshalBehaviors(); |
| 4539 this._tryReady(); |
| 4540 }, |
| 4541 _marshalBehavior: function (b) { |
| 4542 } |
| 4543 }); |
| 4544 Polymer.nar = []; |
| 4545 Polymer.Annotations = { |
| 4546 parseAnnotations: function (template) { |
| 4547 var list = []; |
| 4548 var content = template._content || template.content; |
| 4549 this._parseNodeAnnotations(content, list); |
| 4550 return list; |
| 4551 }, |
| 4552 _parseNodeAnnotations: function (node, list) { |
| 4553 return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, li
st) : this._parseElementAnnotations(node, list); |
| 4554 }, |
| 4555 _testEscape: function (value) { |
| 4556 var escape = value.slice(0, 2); |
| 4557 if (escape === '{{' || escape === '[[') { |
| 4558 return escape; |
| 4559 } |
| 4560 }, |
| 4561 _parseTextNodeAnnotation: function (node, list) { |
| 4562 var v = node.textContent; |
| 4563 var escape = this._testEscape(v); |
| 4564 if (escape) { |
| 4565 node.textContent = ' '; |
| 4566 var annote = { |
| 4567 bindings: [{ |
| 4568 kind: 'text', |
| 4569 mode: escape[0], |
| 4570 value: v.slice(2, -2).trim() |
| 4571 }] |
| 4572 }; |
| 4573 list.push(annote); |
| 4574 return annote; |
| 4575 } |
| 4576 }, |
| 4577 _parseElementAnnotations: function (element, list) { |
| 4578 var annote = { |
| 4579 bindings: [], |
| 4580 events: [] |
| 4581 }; |
| 4582 if (element.localName === 'content') { |
| 4583 list._hasContent = true; |
| 4584 } |
| 4585 this._parseChildNodesAnnotations(element, annote, list); |
| 4586 if (element.attributes) { |
| 4587 this._parseNodeAttributeAnnotations(element, annote, list); |
| 4588 if (this.prepElement) { |
| 4589 this.prepElement(element); |
| 4590 } |
| 4591 } |
| 4592 if (annote.bindings.length || annote.events.length || annote.id) { |
| 4593 list.push(annote); |
| 4594 } |
| 4595 return annote; |
| 4596 }, |
| 4597 _parseChildNodesAnnotations: function (root, annote, list, callback) { |
| 4598 if (root.firstChild) { |
| 4599 for (var i = 0, node = root.firstChild; node; node = node.nextSibling, i++) { |
| 4600 if (node.localName === 'template' && !node.hasAttribute('preserve-content')) { |
| 4601 this._parseTemplate(node, i, list, annote); |
| 4602 } |
| 4603 if (node.nodeType === Node.TEXT_NODE) { |
| 4604 var n = node.nextSibling; |
| 4605 while (n && n.nodeType === Node.TEXT_NODE) { |
| 4606 node.textContent += n.textContent; |
| 4607 root.removeChild(n); |
| 4608 n = n.nextSibling; |
| 4609 } |
| 4610 } |
| 4611 var childAnnotation = this._parseNodeAnnotations(node, list, callback); |
| 4612 if (childAnnotation) { |
| 4613 childAnnotation.parent = annote; |
| 4614 childAnnotation.index = i; |
| 4615 } |
| 4616 } |
| 4617 } |
| 4618 }, |
| 4619 _parseTemplate: function (node, index, list, parent) { |
| 4620 var content = document.createDocumentFragment(); |
| 4621 content._notes = this.parseAnnotations(node); |
| 4622 content.appendChild(node.content); |
| 4623 list.push({ |
| 4624 bindings: Polymer.nar, |
| 4625 events: Polymer.nar, |
| 4626 templateContent: content, |
| 4627 parent: parent, |
| 4628 index: index |
| 4629 }); |
| 4630 }, |
| 4631 _parseNodeAttributeAnnotations: function (node, annotation) { |
| 4632 for (var i = node.attributes.length - 1, a; a = node.attributes[i]; i--) { |
| 4633 var n = a.name, v = a.value; |
| 4634 if (n === 'id' && !this._testEscape(v)) { |
| 4635 annotation.id = v; |
| 4636 } else if (n.slice(0, 3) === 'on-') { |
| 4637 node.removeAttribute(n); |
| 4638 annotation.events.push({ |
| 4639 name: n.slice(3), |
| 4640 value: v |
| 4641 }); |
| 4642 } else { |
| 4643 var b = this._parseNodeAttributeAnnotation(node, n, v); |
| 4644 if (b) { |
| 4645 annotation.bindings.push(b); |
| 4646 } |
| 4647 } |
| 4648 } |
| 4649 }, |
| 4650 _parseNodeAttributeAnnotation: function (node, n, v) { |
| 4651 var escape = this._testEscape(v); |
| 4652 if (escape) { |
| 4653 var customEvent; |
| 4654 var name = n; |
| 4655 var mode = escape[0]; |
| 4656 v = v.slice(2, -2).trim(); |
| 4657 var not = false; |
| 4658 if (v[0] == '!') { |
| 4659 v = v.substring(1); |
| 4660 not = true; |
| 4661 } |
| 4662 var kind = 'property'; |
| 4663 if (n[n.length - 1] == '$') { |
| 4664 name = n.slice(0, -1); |
| 4665 kind = 'attribute'; |
| 4666 } |
| 4667 var notifyEvent, colon; |
| 4668 if (mode == '{' && (colon = v.indexOf('::')) > 0) { |
| 4669 notifyEvent = v.substring(colon + 2); |
| 4670 v = v.substring(0, colon); |
| 4671 customEvent = true; |
| 4672 } |
| 4673 if (node.localName == 'input' && n == 'value') { |
| 4674 node.setAttribute(n, ''); |
| 4675 } |
| 4676 node.removeAttribute(n); |
| 4677 if (kind === 'property') { |
| 4678 name = Polymer.CaseMap.dashToCamelCase(name); |
| 4679 } |
| 4680 return { |
| 4681 kind: kind, |
| 4682 mode: mode, |
| 4683 name: name, |
| 4684 value: v, |
| 4685 negate: not, |
| 4686 event: notifyEvent, |
| 4687 customEvent: customEvent |
| 4688 }; |
| 4689 } |
| 4690 }, |
| 4691 _localSubTree: function (node, host) { |
| 4692 return node === host ? node.childNodes : node._lightChildren || node.childNodes; |
| 4693 }, |
| 4694 findAnnotatedNode: function (root, annote) { |
| 4695 var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote
.parent); |
| 4696 return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.i
ndex]; |
| 4697 } |
| 4698 }; |
| 4699 (function () { |
| 4700 function resolveCss(cssText, ownerDocument) { |
| 4701 return cssText.replace(CSS_URL_RX, function (m, pre, url, post) { |
| 4702 return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + po
st; |
| 4703 }); |
| 4704 } |
| 4705 function resolveAttrs(element, ownerDocument) { |
| 4706 for (var name in URL_ATTRS) { |
| 4707 var a$ = URL_ATTRS[name]; |
| 4708 for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) { |
| 4709 if (name === '*' || element.localName === name) { |
| 4710 at = element.attributes[a]; |
| 4711 v = at && at.value; |
| 4712 if (v && v.search(BINDING_RX) < 0) { |
| 4713 at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocume
nt); |
| 4714 } |
| 4715 } |
| 4716 } |
| 4717 } |
| 4718 } |
| 4719 function resolve(url, ownerDocument) { |
| 4720 if (url && url[0] === '#') { |
| 4721 return url; |
| 4722 } |
| 4723 var resolver = getUrlResolver(ownerDocument); |
| 4724 resolver.href = url; |
| 4725 return resolver.href || url; |
| 4726 } |
| 4727 var tempDoc; |
| 4728 var tempDocBase; |
| 4729 function resolveUrl(url, baseUri) { |
| 4730 if (!tempDoc) { |
| 4731 tempDoc = document.implementation.createHTMLDocument('temp'); |
| 4732 tempDocBase = tempDoc.createElement('base'); |
| 4733 tempDoc.head.appendChild(tempDocBase); |
| 4734 } |
| 4735 tempDocBase.href = baseUri; |
| 4736 return resolve(url, tempDoc); |
| 4737 } |
| 4738 function getUrlResolver(ownerDocument) { |
| 4739 return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocume
nt.createElement('a')); |
| 4740 } |
| 4741 var CSS_URL_RX = /(url\()([^)]*)(\))/g; |
| 4742 var URL_ATTRS = { |
| 4743 '*': [ |
| 4744 'href', |
| 4745 'src', |
| 4746 'style', |
| 4747 'url' |
| 4748 ], |
| 4749 form: ['action'] |
| 4750 }; |
| 4751 var BINDING_RX = /\{\{|\[\[/; |
| 4752 Polymer.ResolveUrl = { |
| 4753 resolveCss: resolveCss, |
| 4754 resolveAttrs: resolveAttrs, |
| 4755 resolveUrl: resolveUrl |
| 4756 }; |
| 4757 }()); |
| 4758 Polymer.Base._addFeature({ |
| 4759 _prepAnnotations: function () { |
| 4760 if (!this._template) { |
| 4761 this._notes = []; |
| 4762 } else { |
| 4763 Polymer.Annotations.prepElement = this._prepElement.bind(this); |
| 4764 if (this._template._content && this._template._content._notes) { |
| 4765 this._notes = this._template._content._notes; |
| 4766 } else { |
| 4767 this._notes = Polymer.Annotations.parseAnnotations(this._template); |
| 4768 } |
| 4769 this._processAnnotations(this._notes); |
| 4770 Polymer.Annotations.prepElement = null; |
| 4771 } |
| 4772 }, |
| 4773 _processAnnotations: function (notes) { |
| 4774 for (var i = 0; i < notes.length; i++) { |
| 4775 var note = notes[i]; |
| 4776 for (var j = 0; j < note.bindings.length; j++) { |
| 4777 var b = note.bindings[j]; |
| 4778 b.signature = this._parseMethod(b.value); |
| 4779 if (!b.signature) { |
| 4780 b.model = this._modelForPath(b.value); |
| 4781 } |
| 4782 } |
| 4783 if (note.templateContent) { |
| 4784 this._processAnnotations(note.templateContent._notes); |
| 4785 var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(n
ote.templateContent._notes); |
| 4786 var bindings = []; |
| 4787 for (var prop in pp) { |
| 4788 bindings.push({ |
| 4789 index: note.index, |
| 4790 kind: 'property', |
| 4791 mode: '{', |
| 4792 name: '_parent_' + prop, |
| 4793 model: prop, |
| 4794 value: prop |
| 4795 }); |
| 4796 } |
| 4797 note.bindings = note.bindings.concat(bindings); |
| 4798 } |
| 4799 } |
| 4800 }, |
| 4801 _discoverTemplateParentProps: function (notes) { |
| 4802 var pp = {}; |
| 4803 notes.forEach(function (n) { |
| 4804 n.bindings.forEach(function (b) { |
| 4805 if (b.signature) { |
| 4806 var args = b.signature.args; |
| 4807 for (var k = 0; k < args.length; k++) { |
| 4808 pp[args[k].model] = true; |
| 4809 } |
| 4810 } else { |
| 4811 pp[b.model] = true; |
| 4812 } |
| 4813 }); |
| 4814 if (n.templateContent) { |
| 4815 var tpp = n.templateContent._parentProps; |
| 4816 Polymer.Base.mixin(pp, tpp); |
| 4817 } |
| 4818 }); |
| 4819 return pp; |
| 4820 }, |
| 4821 _prepElement: function (element) { |
| 4822 Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument); |
| 4823 }, |
| 4824 _findAnnotatedNode: Polymer.Annotations.findAnnotatedNode, |
| 4825 _marshalAnnotationReferences: function () { |
| 4826 if (this._template) { |
| 4827 this._marshalIdNodes(); |
| 4828 this._marshalAnnotatedNodes(); |
| 4829 this._marshalAnnotatedListeners(); |
| 4830 } |
| 4831 }, |
| 4832 _configureAnnotationReferences: function () { |
| 4833 this._configureTemplateContent(); |
| 4834 }, |
| 4835 _configureTemplateContent: function () { |
| 4836 this._notes.forEach(function (note, i) { |
| 4837 if (note.templateContent) { |
| 4838 this._nodes[i]._content = note.templateContent; |
| 4839 } |
| 4840 }, this); |
| 4841 }, |
| 4842 _marshalIdNodes: function () { |
| 4843 this.$ = {}; |
| 4844 this._notes.forEach(function (a) { |
| 4845 if (a.id) { |
| 4846 this.$[a.id] = this._findAnnotatedNode(this.root, a); |
| 4847 } |
| 4848 }, this); |
| 4849 }, |
| 4850 _marshalAnnotatedNodes: function () { |
| 4851 if (this._nodes) { |
| 4852 this._nodes = this._nodes.map(function (a) { |
| 4853 return this._findAnnotatedNode(this.root, a); |
| 4854 }, this); |
| 4855 } |
| 4856 }, |
| 4857 _marshalAnnotatedListeners: function () { |
| 4858 this._notes.forEach(function (a) { |
| 4859 if (a.events && a.events.length) { |
| 4860 var node = this._findAnnotatedNode(this.root, a); |
| 4861 a.events.forEach(function (e) { |
| 4862 this.listen(node, e.name, e.value); |
| 4863 }, this); |
| 4864 } |
| 4865 }, this); |
| 4866 } |
| 4867 }); |
| 4868 Polymer.Base._addFeature({ |
| 4869 listeners: {}, |
| 4870 _listenListeners: function (listeners) { |
| 4871 var node, name, key; |
| 4872 for (key in listeners) { |
| 4873 if (key.indexOf('.') < 0) { |
| 4874 node = this; |
| 4875 name = key; |
| 4876 } else { |
| 4877 name = key.split('.'); |
| 4878 node = this.$[name[0]]; |
| 4879 name = name[1]; |
| 4880 } |
| 4881 this.listen(node, name, listeners[key]); |
| 4882 } |
| 4883 }, |
| 4884 listen: function (node, eventName, methodName) { |
| 4885 this._listen(node, eventName, this._createEventHandler(node, eventName, methodNa
me)); |
| 4886 }, |
| 4887 _boundListenerKey: function (eventName, methodName) { |
| 4888 return eventName + ':' + methodName; |
| 4889 }, |
| 4890 _recordEventHandler: function (host, eventName, target, methodName, handler) { |
| 4891 var hbl = host.__boundListeners; |
| 4892 if (!hbl) { |
| 4893 hbl = host.__boundListeners = new WeakMap(); |
| 4894 } |
| 4895 var bl = hbl.get(target); |
| 4896 if (!bl) { |
| 4897 bl = {}; |
| 4898 hbl.set(target, bl); |
| 4899 } |
| 4900 var key = this._boundListenerKey(eventName, methodName); |
| 4901 bl[key] = handler; |
| 4902 }, |
| 4903 _recallEventHandler: function (host, eventName, target, methodName) { |
| 4904 var hbl = host.__boundListeners; |
| 4905 if (!hbl) { |
| 4906 return; |
| 4907 } |
| 4908 var bl = hbl.get(target); |
| 4909 if (!bl) { |
| 4910 return; |
| 4911 } |
| 4912 var key = this._boundListenerKey(eventName, methodName); |
| 4913 return bl[key]; |
| 4914 }, |
| 4915 _createEventHandler: function (node, eventName, methodName) { |
| 4916 var host = this; |
| 4917 var handler = function (e) { |
| 4918 if (host[methodName]) { |
| 4919 host[methodName](e, e.detail); |
| 4920 } else { |
| 4921 host._warn(host._logf('_createEventHandler', 'listener method `' + methodName +
'` not defined')); |
| 4922 } |
| 4923 }; |
| 4924 this._recordEventHandler(host, eventName, node, methodName, handler); |
| 4925 return handler; |
| 4926 }, |
| 4927 unlisten: function (node, eventName, methodName) { |
| 4928 var handler = this._recallEventHandler(this, eventName, node, methodName); |
| 4929 if (handler) { |
| 4930 this._unlisten(node, eventName, handler); |
| 4931 } |
| 4932 }, |
| 4933 _listen: function (node, eventName, handler) { |
| 4934 node.addEventListener(eventName, handler); |
| 4935 }, |
| 4936 _unlisten: function (node, eventName, handler) { |
| 4937 node.removeEventListener(eventName, handler); |
| 4938 } |
| 4939 }); |
| 4940 (function () { |
| 4941 'use strict'; |
| 4942 var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string'; |
| 4943 var GESTURE_KEY = '__polymerGestures'; |
| 4944 var HANDLED_OBJ = '__polymerGesturesHandled'; |
| 4945 var TOUCH_ACTION = '__polymerGesturesTouchAction'; |
| 4946 var TAP_DISTANCE = 25; |
| 4947 var TRACK_DISTANCE = 5; |
| 4948 var TRACK_LENGTH = 2; |
| 4949 var MOUSE_TIMEOUT = 2500; |
| 4950 var MOUSE_EVENTS = [ |
| 4951 'mousedown', |
| 4952 'mousemove', |
| 4953 'mouseup', |
| 4954 'click' |
| 4955 ]; |
| 4956 var MOUSE_WHICH_TO_BUTTONS = [ |
| 4957 0, |
| 4958 1, |
| 4959 4, |
| 4960 2 |
| 4961 ]; |
| 4962 var MOUSE_HAS_BUTTONS = function () { |
| 4963 try { |
| 4964 return new MouseEvent('test', { buttons: 1 }).buttons === 1; |
| 4965 } catch (e) { |
| 4966 return false; |
| 4967 } |
| 4968 }(); |
| 4969 var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/); |
| 4970 var mouseCanceller = function (mouseEvent) { |
| 4971 mouseEvent[HANDLED_OBJ] = { skip: true }; |
| 4972 if (mouseEvent.type === 'click') { |
| 4973 var path = Polymer.dom(mouseEvent).path; |
| 4974 for (var i = 0; i < path.length; i++) { |
| 4975 if (path[i] === POINTERSTATE.mouse.target) { |
| 4976 return; |
| 4977 } |
| 4978 } |
| 4979 mouseEvent.preventDefault(); |
| 4980 mouseEvent.stopPropagation(); |
| 4981 } |
| 4982 }; |
| 4983 function setupTeardownMouseCanceller(setup) { |
| 4984 for (var i = 0, en; i < MOUSE_EVENTS.length; i++) { |
| 4985 en = MOUSE_EVENTS[i]; |
| 4986 if (setup) { |
| 4987 document.addEventListener(en, mouseCanceller, true); |
| 4988 } else { |
| 4989 document.removeEventListener(en, mouseCanceller, true); |
| 4990 } |
| 4991 } |
| 4992 } |
| 4993 function ignoreMouse() { |
| 4994 if (IS_TOUCH_ONLY) { |
| 4995 return; |
| 4996 } |
| 4997 if (!POINTERSTATE.mouse.mouseIgnoreJob) { |
| 4998 setupTeardownMouseCanceller(true); |
| 4999 } |
| 5000 var unset = function () { |
| 5001 setupTeardownMouseCanceller(); |
| 5002 POINTERSTATE.mouse.target = null; |
| 5003 POINTERSTATE.mouse.mouseIgnoreJob = null; |
| 5004 }; |
| 5005 POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgn
oreJob, unset, MOUSE_TIMEOUT); |
| 5006 } |
| 5007 function hasLeftMouseButton(ev) { |
| 5008 var type = ev.type; |
| 5009 if (MOUSE_EVENTS.indexOf(type) === -1) { |
| 5010 return false; |
| 5011 } |
| 5012 if (type === 'mousemove') { |
| 5013 var buttons = ev.buttons === undefined ? 1 : ev.buttons; |
| 5014 if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) { |
| 5015 buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0; |
| 5016 } |
| 5017 return Boolean(buttons & 1); |
| 5018 } else { |
| 5019 var button = ev.button === undefined ? 0 : ev.button; |
| 5020 return button === 0; |
| 5021 } |
| 5022 } |
| 5023 function isSyntheticClick(ev) { |
| 5024 if (ev.type === 'click') { |
| 5025 if (ev.detail === 0) { |
| 5026 return true; |
| 5027 } |
| 5028 var t = Gestures.findOriginalTarget(ev); |
| 5029 var bcr = t.getBoundingClientRect(); |
| 5030 var x = ev.pageX, y = ev.pageY; |
| 5031 return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom)); |
| 5032 } |
| 5033 return false; |
| 5034 } |
| 5035 var POINTERSTATE = { |
| 5036 mouse: { |
| 5037 target: null, |
| 5038 mouseIgnoreJob: null |
| 5039 }, |
| 5040 touch: { |
| 5041 x: 0, |
| 5042 y: 0, |
| 5043 id: -1, |
| 5044 scrollDecided: false |
| 5045 } |
| 5046 }; |
| 5047 function firstTouchAction(ev) { |
| 5048 var path = Polymer.dom(ev).path; |
| 5049 var ta = 'auto'; |
| 5050 for (var i = 0, n; i < path.length; i++) { |
| 5051 n = path[i]; |
| 5052 if (n[TOUCH_ACTION]) { |
| 5053 ta = n[TOUCH_ACTION]; |
| 5054 break; |
| 5055 } |
| 5056 } |
| 5057 return ta; |
| 5058 } |
| 5059 function trackDocument(stateObj, movefn, upfn) { |
| 5060 stateObj.movefn = movefn; |
| 5061 stateObj.upfn = upfn; |
| 5062 document.addEventListener('mousemove', movefn); |
| 5063 document.addEventListener('mouseup', upfn); |
| 5064 } |
| 5065 function untrackDocument(stateObj) { |
| 5066 document.removeEventListener('mousemove', stateObj.movefn); |
| 5067 document.removeEventListener('mouseup', stateObj.upfn); |
| 5068 } |
| 5069 var Gestures = { |
| 5070 gestures: {}, |
| 5071 recognizers: [], |
| 5072 deepTargetFind: function (x, y) { |
| 5073 var node = document.elementFromPoint(x, y); |
| 5074 var next = node; |
| 5075 while (next && next.shadowRoot) { |
| 5076 next = next.shadowRoot.elementFromPoint(x, y); |
| 5077 if (next) { |
| 5078 node = next; |
| 5079 } |
| 5080 } |
| 5081 return node; |
| 5082 }, |
| 5083 findOriginalTarget: function (ev) { |
| 5084 if (ev.path) { |
| 5085 return ev.path[0]; |
| 5086 } |
| 5087 return ev.target; |
| 5088 }, |
| 5089 handleNative: function (ev) { |
| 5090 var handled; |
| 5091 var type = ev.type; |
| 5092 var node = ev.currentTarget; |
| 5093 var gobj = node[GESTURE_KEY]; |
| 5094 var gs = gobj[type]; |
| 5095 if (!gs) { |
| 5096 return; |
| 5097 } |
| 5098 if (!ev[HANDLED_OBJ]) { |
| 5099 ev[HANDLED_OBJ] = {}; |
| 5100 if (type.slice(0, 5) === 'touch') { |
| 5101 var t = ev.changedTouches[0]; |
| 5102 if (type === 'touchstart') { |
| 5103 if (ev.touches.length === 1) { |
| 5104 POINTERSTATE.touch.id = t.identifier; |
| 5105 } |
| 5106 } |
| 5107 if (POINTERSTATE.touch.id !== t.identifier) { |
| 5108 return; |
| 5109 } |
| 5110 if (!HAS_NATIVE_TA) { |
| 5111 if (type === 'touchstart' || type === 'touchmove') { |
| 5112 Gestures.handleTouchAction(ev); |
| 5113 } |
| 5114 } |
| 5115 if (type === 'touchend') { |
| 5116 POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget; |
| 5117 ignoreMouse(true); |
| 5118 } |
| 5119 } |
| 5120 } |
| 5121 handled = ev[HANDLED_OBJ]; |
| 5122 if (handled.skip) { |
| 5123 return; |
| 5124 } |
| 5125 var recognizers = Gestures.recognizers; |
| 5126 for (var i = 0, r; i < recognizers.length; i++) { |
| 5127 r = recognizers[i]; |
| 5128 if (gs[r.name] && !handled[r.name]) { |
| 5129 if (r.flow && r.flow.start.indexOf(ev.type) > -1) { |
| 5130 if (r.reset) { |
| 5131 r.reset(); |
| 5132 } |
| 5133 } |
| 5134 } |
| 5135 } |
| 5136 for (var i = 0, r; i < recognizers.length; i++) { |
| 5137 r = recognizers[i]; |
| 5138 if (gs[r.name] && !handled[r.name]) { |
| 5139 handled[r.name] = true; |
| 5140 r[type](ev); |
| 5141 } |
| 5142 } |
| 5143 }, |
| 5144 handleTouchAction: function (ev) { |
| 5145 var t = ev.changedTouches[0]; |
| 5146 var type = ev.type; |
| 5147 if (type === 'touchstart') { |
| 5148 POINTERSTATE.touch.x = t.clientX; |
| 5149 POINTERSTATE.touch.y = t.clientY; |
| 5150 POINTERSTATE.touch.scrollDecided = false; |
| 5151 } else if (type === 'touchmove') { |
| 5152 if (POINTERSTATE.touch.scrollDecided) { |
| 5153 return; |
| 5154 } |
| 5155 POINTERSTATE.touch.scrollDecided = true; |
| 5156 var ta = firstTouchAction(ev); |
| 5157 var prevent = false; |
| 5158 var dx = Math.abs(POINTERSTATE.touch.x - t.clientX); |
| 5159 var dy = Math.abs(POINTERSTATE.touch.y - t.clientY); |
| 5160 if (!ev.cancelable) { |
| 5161 } else if (ta === 'none') { |
| 5162 prevent = true; |
| 5163 } else if (ta === 'pan-x') { |
| 5164 prevent = dy > dx; |
| 5165 } else if (ta === 'pan-y') { |
| 5166 prevent = dx > dy; |
| 5167 } |
| 5168 if (prevent) { |
| 5169 ev.preventDefault(); |
| 5170 } else { |
| 5171 Gestures.prevent('track'); |
| 5172 } |
| 5173 } |
| 5174 }, |
| 5175 add: function (node, evType, handler) { |
| 5176 var recognizer = this.gestures[evType]; |
| 5177 var deps = recognizer.deps; |
| 5178 var name = recognizer.name; |
| 5179 var gobj = node[GESTURE_KEY]; |
| 5180 if (!gobj) { |
| 5181 node[GESTURE_KEY] = gobj = {}; |
| 5182 } |
| 5183 for (var i = 0, dep, gd; i < deps.length; i++) { |
| 5184 dep = deps[i]; |
| 5185 if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) { |
| 5186 continue; |
| 5187 } |
| 5188 gd = gobj[dep]; |
| 5189 if (!gd) { |
| 5190 gobj[dep] = gd = { _count: 0 }; |
| 5191 } |
| 5192 if (gd._count === 0) { |
| 5193 node.addEventListener(dep, this.handleNative); |
| 5194 } |
| 5195 gd[name] = (gd[name] || 0) + 1; |
| 5196 gd._count = (gd._count || 0) + 1; |
| 5197 } |
| 5198 node.addEventListener(evType, handler); |
| 5199 if (recognizer.touchAction) { |
| 5200 this.setTouchAction(node, recognizer.touchAction); |
| 5201 } |
| 5202 }, |
| 5203 remove: function (node, evType, handler) { |
| 5204 var recognizer = this.gestures[evType]; |
| 5205 var deps = recognizer.deps; |
| 5206 var name = recognizer.name; |
| 5207 var gobj = node[GESTURE_KEY]; |
| 5208 if (gobj) { |
| 5209 for (var i = 0, dep, gd; i < deps.length; i++) { |
| 5210 dep = deps[i]; |
| 5211 gd = gobj[dep]; |
| 5212 if (gd && gd[name]) { |
| 5213 gd[name] = (gd[name] || 1) - 1; |
| 5214 gd._count = (gd._count || 1) - 1; |
| 5215 if (gd._count === 0) { |
| 5216 node.removeEventListener(dep, this.handleNative); |
| 5217 } |
| 5218 } |
| 5219 } |
| 5220 } |
| 5221 node.removeEventListener(evType, handler); |
| 5222 }, |
| 5223 register: function (recog) { |
| 5224 this.recognizers.push(recog); |
| 5225 for (var i = 0; i < recog.emits.length; i++) { |
| 5226 this.gestures[recog.emits[i]] = recog; |
| 5227 } |
| 5228 }, |
| 5229 findRecognizerByEvent: function (evName) { |
| 5230 for (var i = 0, r; i < this.recognizers.length; i++) { |
| 5231 r = this.recognizers[i]; |
| 5232 for (var j = 0, n; j < r.emits.length; j++) { |
| 5233 n = r.emits[j]; |
| 5234 if (n === evName) { |
| 5235 return r; |
| 5236 } |
| 5237 } |
| 5238 } |
| 5239 return null; |
| 5240 }, |
| 5241 setTouchAction: function (node, value) { |
| 5242 if (HAS_NATIVE_TA) { |
| 5243 node.style.touchAction = value; |
| 5244 } |
| 5245 node[TOUCH_ACTION] = value; |
| 5246 }, |
| 5247 fire: function (target, type, detail) { |
| 5248 var ev = Polymer.Base.fire(type, detail, { |
| 5249 node: target, |
| 5250 bubbles: true, |
| 5251 cancelable: true |
| 5252 }); |
| 5253 if (ev.defaultPrevented) { |
| 5254 var se = detail.sourceEvent; |
| 5255 if (se && se.preventDefault) { |
| 5256 se.preventDefault(); |
| 5257 } |
| 5258 } |
| 5259 }, |
| 5260 prevent: function (evName) { |
| 5261 var recognizer = this.findRecognizerByEvent(evName); |
| 5262 if (recognizer.info) { |
| 5263 recognizer.info.prevent = true; |
| 5264 } |
| 5265 } |
| 5266 }; |
| 5267 Gestures.register({ |
| 5268 name: 'downup', |
| 5269 deps: [ |
| 5270 'mousedown', |
| 5271 'touchstart', |
| 5272 'touchend' |
| 5273 ], |
| 5274 flow: { |
| 5275 start: [ |
| 5276 'mousedown', |
| 5277 'touchstart' |
| 5278 ], |
| 5279 end: [ |
| 5280 'mouseup', |
| 5281 'touchend' |
| 5282 ] |
| 5283 }, |
| 5284 emits: [ |
| 5285 'down', |
| 5286 'up' |
| 5287 ], |
| 5288 info: { |
| 5289 movefn: function () { |
| 5290 }, |
| 5291 upfn: function () { |
| 5292 } |
| 5293 }, |
| 5294 reset: function () { |
| 5295 untrackDocument(this.info); |
| 5296 }, |
| 5297 mousedown: function (e) { |
| 5298 if (!hasLeftMouseButton(e)) { |
| 5299 return; |
| 5300 } |
| 5301 var t = Gestures.findOriginalTarget(e); |
| 5302 var self = this; |
| 5303 var movefn = function movefn(e) { |
| 5304 if (!hasLeftMouseButton(e)) { |
| 5305 self.fire('up', t, e); |
| 5306 untrackDocument(self.info); |
| 5307 } |
| 5308 }; |
| 5309 var upfn = function upfn(e) { |
| 5310 if (hasLeftMouseButton(e)) { |
| 5311 self.fire('up', t, e); |
| 5312 } |
| 5313 untrackDocument(self.info); |
| 5314 }; |
| 5315 trackDocument(this.info, movefn, upfn); |
| 5316 this.fire('down', t, e); |
| 5317 }, |
| 5318 touchstart: function (e) { |
| 5319 this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]); |
| 5320 }, |
| 5321 touchend: function (e) { |
| 5322 this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]); |
| 5323 }, |
| 5324 fire: function (type, target, event) { |
| 5325 var self = this; |
| 5326 Gestures.fire(target, type, { |
| 5327 x: event.clientX, |
| 5328 y: event.clientY, |
| 5329 sourceEvent: event, |
| 5330 prevent: Gestures.prevent.bind(Gestures) |
| 5331 }); |
| 5332 } |
| 5333 }); |
| 5334 Gestures.register({ |
| 5335 name: 'track', |
| 5336 touchAction: 'none', |
| 5337 deps: [ |
| 5338 'mousedown', |
| 5339 'touchstart', |
| 5340 'touchmove', |
| 5341 'touchend' |
| 5342 ], |
| 5343 flow: { |
| 5344 start: [ |
| 5345 'mousedown', |
| 5346 'touchstart' |
| 5347 ], |
| 5348 end: [ |
| 5349 'mouseup', |
| 5350 'touchend' |
| 5351 ] |
| 5352 }, |
| 5353 emits: ['track'], |
| 5354 info: { |
| 5355 x: 0, |
| 5356 y: 0, |
| 5357 state: 'start', |
| 5358 started: false, |
| 5359 moves: [], |
| 5360 addMove: function (move) { |
| 5361 if (this.moves.length > TRACK_LENGTH) { |
| 5362 this.moves.shift(); |
| 5363 } |
| 5364 this.moves.push(move); |
| 5365 }, |
| 5366 movefn: function () { |
| 5367 }, |
| 5368 upfn: function () { |
| 5369 }, |
| 5370 prevent: false |
| 5371 }, |
| 5372 reset: function () { |
| 5373 this.info.state = 'start'; |
| 5374 this.info.started = false; |
| 5375 this.info.moves = []; |
| 5376 this.info.x = 0; |
| 5377 this.info.y = 0; |
| 5378 this.info.prevent = false; |
| 5379 untrackDocument(this.info); |
| 5380 }, |
| 5381 hasMovedEnough: function (x, y) { |
| 5382 if (this.info.prevent) { |
| 5383 return false; |
| 5384 } |
| 5385 if (this.info.started) { |
| 5386 return true; |
| 5387 } |
| 5388 var dx = Math.abs(this.info.x - x); |
| 5389 var dy = Math.abs(this.info.y - y); |
| 5390 return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE; |
| 5391 }, |
| 5392 mousedown: function (e) { |
| 5393 if (!hasLeftMouseButton(e)) { |
| 5394 return; |
| 5395 } |
| 5396 var t = Gestures.findOriginalTarget(e); |
| 5397 var self = this; |
| 5398 var movefn = function movefn(e) { |
| 5399 var x = e.clientX, y = e.clientY; |
| 5400 if (self.hasMovedEnough(x, y)) { |
| 5401 self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : '
start'; |
| 5402 self.info.addMove({ |
| 5403 x: x, |
| 5404 y: y |
| 5405 }); |
| 5406 if (!hasLeftMouseButton(e)) { |
| 5407 self.info.state = 'end'; |
| 5408 untrackDocument(self.info); |
| 5409 } |
| 5410 self.fire(t, e); |
| 5411 self.info.started = true; |
| 5412 } |
| 5413 }; |
| 5414 var upfn = function upfn(e) { |
| 5415 if (self.info.started) { |
| 5416 Gestures.prevent('tap'); |
| 5417 movefn(e); |
| 5418 } |
| 5419 untrackDocument(self.info); |
| 5420 }; |
| 5421 trackDocument(this.info, movefn, upfn); |
| 5422 this.info.x = e.clientX; |
| 5423 this.info.y = e.clientY; |
| 5424 }, |
| 5425 touchstart: function (e) { |
| 5426 var ct = e.changedTouches[0]; |
| 5427 this.info.x = ct.clientX; |
| 5428 this.info.y = ct.clientY; |
| 5429 }, |
| 5430 touchmove: function (e) { |
| 5431 var t = Gestures.findOriginalTarget(e); |
| 5432 var ct = e.changedTouches[0]; |
| 5433 var x = ct.clientX, y = ct.clientY; |
| 5434 if (this.hasMovedEnough(x, y)) { |
| 5435 this.info.addMove({ |
| 5436 x: x, |
| 5437 y: y |
| 5438 }); |
| 5439 this.fire(t, ct); |
| 5440 this.info.state = 'track'; |
| 5441 this.info.started = true; |
| 5442 } |
| 5443 }, |
| 5444 touchend: function (e) { |
| 5445 var t = Gestures.findOriginalTarget(e); |
| 5446 var ct = e.changedTouches[0]; |
| 5447 if (this.info.started) { |
| 5448 Gestures.prevent('tap'); |
| 5449 this.info.state = 'end'; |
| 5450 this.info.addMove({ |
| 5451 x: ct.clientX, |
| 5452 y: ct.clientY |
| 5453 }); |
| 5454 this.fire(t, ct); |
| 5455 } |
| 5456 }, |
| 5457 fire: function (target, touch) { |
| 5458 var secondlast = this.info.moves[this.info.moves.length - 2]; |
| 5459 var lastmove = this.info.moves[this.info.moves.length - 1]; |
| 5460 var dx = lastmove.x - this.info.x; |
| 5461 var dy = lastmove.y - this.info.y; |
| 5462 var ddx, ddy = 0; |
| 5463 if (secondlast) { |
| 5464 ddx = lastmove.x - secondlast.x; |
| 5465 ddy = lastmove.y - secondlast.y; |
| 5466 } |
| 5467 return Gestures.fire(target, 'track', { |
| 5468 state: this.info.state, |
| 5469 x: touch.clientX, |
| 5470 y: touch.clientY, |
| 5471 dx: dx, |
| 5472 dy: dy, |
| 5473 ddx: ddx, |
| 5474 ddy: ddy, |
| 5475 sourceEvent: touch, |
| 5476 hover: function () { |
| 5477 return Gestures.deepTargetFind(touch.clientX, touch.clientY); |
| 5478 } |
| 5479 }); |
| 5480 } |
| 5481 }); |
| 5482 Gestures.register({ |
| 5483 name: 'tap', |
| 5484 deps: [ |
| 5485 'mousedown', |
| 5486 'click', |
| 5487 'touchstart', |
| 5488 'touchend' |
| 5489 ], |
| 5490 flow: { |
| 5491 start: [ |
| 5492 'mousedown', |
| 5493 'touchstart' |
| 5494 ], |
| 5495 end: [ |
| 5496 'click', |
| 5497 'touchend' |
| 5498 ] |
| 5499 }, |
| 5500 emits: ['tap'], |
| 5501 info: { |
| 5502 x: NaN, |
| 5503 y: NaN, |
| 5504 prevent: false |
| 5505 }, |
| 5506 reset: function () { |
| 5507 this.info.x = NaN; |
| 5508 this.info.y = NaN; |
| 5509 this.info.prevent = false; |
| 5510 }, |
| 5511 save: function (e) { |
| 5512 this.info.x = e.clientX; |
| 5513 this.info.y = e.clientY; |
| 5514 }, |
| 5515 mousedown: function (e) { |
| 5516 if (hasLeftMouseButton(e)) { |
| 5517 this.save(e); |
| 5518 } |
| 5519 }, |
| 5520 click: function (e) { |
| 5521 if (hasLeftMouseButton(e)) { |
| 5522 this.forward(e); |
| 5523 } |
| 5524 }, |
| 5525 touchstart: function (e) { |
| 5526 this.save(e.changedTouches[0]); |
| 5527 }, |
| 5528 touchend: function (e) { |
| 5529 this.forward(e.changedTouches[0]); |
| 5530 }, |
| 5531 forward: function (e) { |
| 5532 var dx = Math.abs(e.clientX - this.info.x); |
| 5533 var dy = Math.abs(e.clientY - this.info.y); |
| 5534 var t = Gestures.findOriginalTarget(e); |
| 5535 if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSynt
heticClick(e)) { |
| 5536 if (!this.info.prevent) { |
| 5537 Gestures.fire(t, 'tap', { |
| 5538 x: e.clientX, |
| 5539 y: e.clientY, |
| 5540 sourceEvent: e |
| 5541 }); |
| 5542 } |
| 5543 } |
| 5544 } |
| 5545 }); |
| 5546 var DIRECTION_MAP = { |
| 5547 x: 'pan-x', |
| 5548 y: 'pan-y', |
| 5549 none: 'none', |
| 5550 all: 'auto' |
| 5551 }; |
| 5552 Polymer.Base._addFeature({ |
| 5553 _listen: function (node, eventName, handler) { |
| 5554 if (Gestures.gestures[eventName]) { |
| 5555 Gestures.add(node, eventName, handler); |
| 5556 } else { |
| 5557 node.addEventListener(eventName, handler); |
| 5558 } |
| 5559 }, |
| 5560 _unlisten: function (node, eventName, handler) { |
| 5561 if (Gestures.gestures[eventName]) { |
| 5562 Gestures.remove(node, eventName, handler); |
| 5563 } else { |
| 5564 node.removeEventListener(eventName, handler); |
| 5565 } |
| 5566 }, |
| 5567 setScrollDirection: function (direction, node) { |
| 5568 node = node || this; |
| 5569 Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto'); |
| 5570 } |
| 5571 }); |
| 5572 Polymer.Gestures = Gestures; |
| 5573 }()); |
| 5574 Polymer.Async = { |
| 5575 _currVal: 0, |
| 5576 _lastVal: 0, |
| 5577 _callbacks: [], |
| 5578 _twiddleContent: 0, |
| 5579 _twiddle: document.createTextNode(''), |
| 5580 run: function (callback, waitTime) { |
| 5581 if (waitTime > 0) { |
| 5582 return ~setTimeout(callback, waitTime); |
| 5583 } else { |
| 5584 this._twiddle.textContent = this._twiddleContent++; |
| 5585 this._callbacks.push(callback); |
| 5586 return this._currVal++; |
| 5587 } |
| 5588 }, |
| 5589 cancel: function (handle) { |
| 5590 if (handle < 0) { |
| 5591 clearTimeout(~handle); |
| 5592 } else { |
| 5593 var idx = handle - this._lastVal; |
| 5594 if (idx >= 0) { |
| 5595 if (!this._callbacks[idx]) { |
| 5596 throw 'invalid async handle: ' + handle; |
| 5597 } |
| 5598 this._callbacks[idx] = null; |
| 5599 } |
| 5600 } |
| 5601 }, |
| 5602 _atEndOfMicrotask: function () { |
| 5603 var len = this._callbacks.length; |
| 5604 for (var i = 0; i < len; i++) { |
| 5605 var cb = this._callbacks[i]; |
| 5606 if (cb) { |
| 5607 try { |
| 5608 cb(); |
| 5609 } catch (e) { |
| 5610 i++; |
| 5611 this._callbacks.splice(0, i); |
| 5612 this._lastVal += i; |
| 5613 this._twiddle.textContent = this._twiddleContent++; |
| 5614 throw e; |
| 5615 } |
| 5616 } |
| 5617 } |
| 5618 this._callbacks.splice(0, len); |
| 5619 this._lastVal += len; |
| 5620 } |
| 5621 }; |
| 5622 new (window.MutationObserver || JsMutationObserver)(Polymer.Async._atEndOfMicrot
ask.bind(Polymer.Async)).observe(Polymer.Async._twiddle, { characterData: true }
); |
| 5623 Polymer.Debounce = function () { |
| 5624 var Async = Polymer.Async; |
| 5625 var Debouncer = function (context) { |
| 5626 this.context = context; |
| 5627 this.boundComplete = this.complete.bind(this); |
| 5628 }; |
| 5629 Debouncer.prototype = { |
| 5630 go: function (callback, wait) { |
| 5631 var h; |
| 5632 this.finish = function () { |
| 5633 Async.cancel(h); |
| 5634 }; |
| 5635 h = Async.run(this.boundComplete, wait); |
| 5636 this.callback = callback; |
| 5637 }, |
| 5638 stop: function () { |
| 5639 if (this.finish) { |
| 5640 this.finish(); |
| 5641 this.finish = null; |
| 5642 } |
| 5643 }, |
| 5644 complete: function () { |
| 5645 if (this.finish) { |
| 5646 this.stop(); |
| 5647 this.callback.call(this.context); |
| 5648 } |
| 5649 } |
| 5650 }; |
| 5651 function debounce(debouncer, callback, wait) { |
| 5652 if (debouncer) { |
| 5653 debouncer.stop(); |
| 5654 } else { |
| 5655 debouncer = new Debouncer(this); |
| 5656 } |
| 5657 debouncer.go(callback, wait); |
| 5658 return debouncer; |
| 5659 } |
| 5660 return debounce; |
| 5661 }(); |
| 5662 Polymer.Base._addFeature({ |
| 5663 $$: function (slctr) { |
| 5664 return Polymer.dom(this.root).querySelector(slctr); |
| 5665 }, |
| 5666 toggleClass: function (name, bool, node) { |
| 5667 node = node || this; |
| 5668 if (arguments.length == 1) { |
| 5669 bool = !node.classList.contains(name); |
| 5670 } |
| 5671 if (bool) { |
| 5672 Polymer.dom(node).classList.add(name); |
| 5673 } else { |
| 5674 Polymer.dom(node).classList.remove(name); |
| 5675 } |
| 5676 }, |
| 5677 toggleAttribute: function (name, bool, node) { |
| 5678 node = node || this; |
| 5679 if (arguments.length == 1) { |
| 5680 bool = !node.hasAttribute(name); |
| 5681 } |
| 5682 if (bool) { |
| 5683 Polymer.dom(node).setAttribute(name, ''); |
| 5684 } else { |
| 5685 Polymer.dom(node).removeAttribute(name); |
| 5686 } |
| 5687 }, |
| 5688 classFollows: function (name, toElement, fromElement) { |
| 5689 if (fromElement) { |
| 5690 Polymer.dom(fromElement).classList.remove(name); |
| 5691 } |
| 5692 if (toElement) { |
| 5693 Polymer.dom(toElement).classList.add(name); |
| 5694 } |
| 5695 }, |
| 5696 attributeFollows: function (name, toElement, fromElement) { |
| 5697 if (fromElement) { |
| 5698 Polymer.dom(fromElement).removeAttribute(name); |
| 5699 } |
| 5700 if (toElement) { |
| 5701 Polymer.dom(toElement).setAttribute(name, ''); |
| 5702 } |
| 5703 }, |
| 5704 getContentChildNodes: function (slctr) { |
| 5705 var content = Polymer.dom(this.root).querySelector(slctr || 'content'); |
| 5706 return content ? Polymer.dom(content).getDistributedNodes() : []; |
| 5707 }, |
| 5708 getContentChildren: function (slctr) { |
| 5709 return this.getContentChildNodes(slctr).filter(function (n) { |
| 5710 return n.nodeType === Node.ELEMENT_NODE; |
| 5711 }); |
| 5712 }, |
| 5713 fire: function (type, detail, options) { |
| 5714 options = options || Polymer.nob; |
| 5715 var node = options.node || this; |
| 5716 var detail = detail === null || detail === undefined ? Polymer.nob : detail; |
| 5717 var bubbles = options.bubbles === undefined ? true : options.bubbles; |
| 5718 var cancelable = Boolean(options.cancelable); |
| 5719 var event = new CustomEvent(type, { |
| 5720 bubbles: Boolean(bubbles), |
| 5721 cancelable: cancelable, |
| 5722 detail: detail |
| 5723 }); |
| 5724 node.dispatchEvent(event); |
| 5725 return event; |
| 5726 }, |
| 5727 async: function (callback, waitTime) { |
| 5728 return Polymer.Async.run(callback.bind(this), waitTime); |
| 5729 }, |
| 5730 cancelAsync: function (handle) { |
| 5731 Polymer.Async.cancel(handle); |
| 5732 }, |
| 5733 arrayDelete: function (path, item) { |
| 5734 var index; |
| 5735 if (Array.isArray(path)) { |
| 5736 index = path.indexOf(item); |
| 5737 if (index >= 0) { |
| 5738 return path.splice(index, 1); |
| 5739 } |
| 5740 } else { |
| 5741 var arr = this.get(path); |
| 5742 index = arr.indexOf(item); |
| 5743 if (index >= 0) { |
| 5744 return this.splice(path, index, 1); |
| 5745 } |
| 5746 } |
| 5747 }, |
| 5748 transform: function (transform, node) { |
| 5749 node = node || this; |
| 5750 node.style.webkitTransform = transform; |
| 5751 node.style.transform = transform; |
| 5752 }, |
| 5753 translate3d: function (x, y, z, node) { |
| 5754 node = node || this; |
| 5755 this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node); |
| 5756 }, |
| 5757 importHref: function (href, onload, onerror) { |
| 5758 var l = document.createElement('link'); |
| 5759 l.rel = 'import'; |
| 5760 l.href = href; |
| 5761 if (onload) { |
| 5762 l.onload = onload.bind(this); |
| 5763 } |
| 5764 if (onerror) { |
| 5765 l.onerror = onerror.bind(this); |
| 5766 } |
| 5767 document.head.appendChild(l); |
| 5768 return l; |
| 5769 }, |
| 5770 create: function (tag, props) { |
| 5771 var elt = document.createElement(tag); |
| 5772 if (props) { |
| 5773 for (var n in props) { |
| 5774 elt[n] = props[n]; |
| 5775 } |
| 5776 } |
| 5777 return elt; |
| 5778 } |
| 5779 }); |
| 5780 Polymer.Bind = { |
| 5781 prepareModel: function (model) { |
| 5782 model._propertyEffects = {}; |
| 5783 model._bindListeners = []; |
| 5784 Polymer.Base.mixin(model, this._modelApi); |
| 5785 }, |
| 5786 _modelApi: { |
| 5787 _notifyChange: function (property) { |
| 5788 var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed'; |
| 5789 Polymer.Base.fire(eventName, { value: this[property] }, { |
| 5790 bubbles: false, |
| 5791 node: this |
| 5792 }); |
| 5793 }, |
| 5794 _propertySetter: function (property, value, effects, fromAbove) { |
| 5795 var old = this.__data__[property]; |
| 5796 if (old !== value && (old === old || value === value)) { |
| 5797 this.__data__[property] = value; |
| 5798 if (typeof value == 'object') { |
| 5799 this._clearPath(property); |
| 5800 } |
| 5801 if (this._propertyChanged) { |
| 5802 this._propertyChanged(property, value, old); |
| 5803 } |
| 5804 if (effects) { |
| 5805 this._effectEffects(property, value, effects, old, fromAbove); |
| 5806 } |
| 5807 } |
| 5808 return old; |
| 5809 }, |
| 5810 __setProperty: function (property, value, quiet, node) { |
| 5811 node = node || this; |
| 5812 var effects = node._propertyEffects && node._propertyEffects[property]; |
| 5813 if (effects) { |
| 5814 node._propertySetter(property, value, effects, quiet); |
| 5815 } else { |
| 5816 node[property] = value; |
| 5817 } |
| 5818 }, |
| 5819 _effectEffects: function (property, value, effects, old, fromAbove) { |
| 5820 effects.forEach(function (fx) { |
| 5821 var fn = Polymer.Bind['_' + fx.kind + 'Effect']; |
| 5822 if (fn) { |
| 5823 fn.call(this, property, value, fx.effect, old, fromAbove); |
| 5824 } |
| 5825 }, this); |
| 5826 }, |
| 5827 _clearPath: function (path) { |
| 5828 for (var prop in this.__data__) { |
| 5829 if (prop.indexOf(path + '.') === 0) { |
| 5830 this.__data__[prop] = undefined; |
| 5831 } |
| 5832 } |
| 5833 } |
| 5834 }, |
| 5835 ensurePropertyEffects: function (model, property) { |
| 5836 var fx = model._propertyEffects[property]; |
| 5837 if (!fx) { |
| 5838 fx = model._propertyEffects[property] = []; |
| 5839 } |
| 5840 return fx; |
| 5841 }, |
| 5842 addPropertyEffect: function (model, property, kind, effect) { |
| 5843 var fx = this.ensurePropertyEffects(model, property); |
| 5844 fx.push({ |
| 5845 kind: kind, |
| 5846 effect: effect |
| 5847 }); |
| 5848 }, |
| 5849 createBindings: function (model) { |
| 5850 var fx$ = model._propertyEffects; |
| 5851 if (fx$) { |
| 5852 for (var n in fx$) { |
| 5853 var fx = fx$[n]; |
| 5854 fx.sort(this._sortPropertyEffects); |
| 5855 this._createAccessors(model, n, fx); |
| 5856 } |
| 5857 } |
| 5858 }, |
| 5859 _sortPropertyEffects: function () { |
| 5860 var EFFECT_ORDER = { |
| 5861 'compute': 0, |
| 5862 'annotation': 1, |
| 5863 'computedAnnotation': 2, |
| 5864 'reflect': 3, |
| 5865 'notify': 4, |
| 5866 'observer': 5, |
| 5867 'complexObserver': 6, |
| 5868 'function': 7 |
| 5869 }; |
| 5870 return function (a, b) { |
| 5871 return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind]; |
| 5872 }; |
| 5873 }(), |
| 5874 _createAccessors: function (model, property, effects) { |
| 5875 var defun = { |
| 5876 get: function () { |
| 5877 return this.__data__[property]; |
| 5878 } |
| 5879 }; |
| 5880 var setter = function (value) { |
| 5881 this._propertySetter(property, value, effects); |
| 5882 }; |
| 5883 var info = model.getPropertyInfo && model.getPropertyInfo(property); |
| 5884 if (info && info.readOnly) { |
| 5885 if (!info.computed) { |
| 5886 model['_set' + this.upper(property)] = setter; |
| 5887 } |
| 5888 } else { |
| 5889 defun.set = setter; |
| 5890 } |
| 5891 Object.defineProperty(model, property, defun); |
| 5892 }, |
| 5893 upper: function (name) { |
| 5894 return name[0].toUpperCase() + name.substring(1); |
| 5895 }, |
| 5896 _addAnnotatedListener: function (model, index, property, path, event) { |
| 5897 var fn = this._notedListenerFactory(property, path, this._isStructured(path), th
is._isEventBogus); |
| 5898 var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed'; |
| 5899 model._bindListeners.push({ |
| 5900 index: index, |
| 5901 property: property, |
| 5902 path: path, |
| 5903 changedFn: fn, |
| 5904 event: eventName |
| 5905 }); |
| 5906 }, |
| 5907 _isStructured: function (path) { |
| 5908 return path.indexOf('.') > 0; |
| 5909 }, |
| 5910 _isEventBogus: function (e, target) { |
| 5911 return e.path && e.path[0] !== target; |
| 5912 }, |
| 5913 _notedListenerFactory: function (property, path, isStructured, bogusTest) { |
| 5914 return function (e, target) { |
| 5915 if (!bogusTest(e, target)) { |
| 5916 if (e.detail && e.detail.path) { |
| 5917 this.notifyPath(this._fixPath(path, property, e.detail.path), e.detail.value); |
| 5918 } else { |
| 5919 var value = target[property]; |
| 5920 if (!isStructured) { |
| 5921 this[path] = target[property]; |
| 5922 } else { |
| 5923 if (this.__data__[path] != value) { |
| 5924 this.set(path, value); |
| 5925 } |
| 5926 } |
| 5927 } |
| 5928 } |
| 5929 }; |
| 5930 }, |
| 5931 prepareInstance: function (inst) { |
| 5932 inst.__data__ = Object.create(null); |
| 5933 }, |
| 5934 setupBindListeners: function (inst) { |
| 5935 inst._bindListeners.forEach(function (info) { |
| 5936 var node = inst._nodes[info.index]; |
| 5937 node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn
)); |
| 5938 }); |
| 5939 } |
| 5940 }; |
| 5941 Polymer.Base.extend(Polymer.Bind, { |
| 5942 _shouldAddListener: function (effect) { |
| 5943 return effect.name && effect.mode === '{' && !effect.negate && effect.kind != 'a
ttribute'; |
| 5944 }, |
| 5945 _annotationEffect: function (source, value, effect) { |
| 5946 if (source != effect.value) { |
| 5947 value = this.get(effect.value); |
| 5948 this.__data__[effect.value] = value; |
| 5949 } |
| 5950 var calc = effect.negate ? !value : value; |
| 5951 if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) { |
| 5952 return this._applyEffectValue(calc, effect); |
| 5953 } |
| 5954 }, |
| 5955 _reflectEffect: function (source) { |
| 5956 this.reflectPropertyToAttribute(source); |
| 5957 }, |
| 5958 _notifyEffect: function (source, value, effect, old, fromAbove) { |
| 5959 if (!fromAbove) { |
| 5960 this._notifyChange(source); |
| 5961 } |
| 5962 }, |
| 5963 _functionEffect: function (source, value, fn, old, fromAbove) { |
| 5964 fn.call(this, source, value, old, fromAbove); |
| 5965 }, |
| 5966 _observerEffect: function (source, value, effect, old) { |
| 5967 var fn = this[effect.method]; |
| 5968 if (fn) { |
| 5969 fn.call(this, value, old); |
| 5970 } else { |
| 5971 this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + '
` not defined')); |
| 5972 } |
| 5973 }, |
| 5974 _complexObserverEffect: function (source, value, effect) { |
| 5975 var fn = this[effect.method]; |
| 5976 if (fn) { |
| 5977 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); |
| 5978 if (args) { |
| 5979 fn.apply(this, args); |
| 5980 } |
| 5981 } else { |
| 5982 this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.met
hod + '` not defined')); |
| 5983 } |
| 5984 }, |
| 5985 _computeEffect: function (source, value, effect) { |
| 5986 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); |
| 5987 if (args) { |
| 5988 var fn = this[effect.method]; |
| 5989 if (fn) { |
| 5990 this.__setProperty(effect.property, fn.apply(this, args)); |
| 5991 } else { |
| 5992 this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '`
not defined')); |
| 5993 } |
| 5994 } |
| 5995 }, |
| 5996 _annotatedComputationEffect: function (source, value, effect) { |
| 5997 var computedHost = this._rootDataHost || this; |
| 5998 var fn = computedHost[effect.method]; |
| 5999 if (fn) { |
| 6000 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); |
| 6001 if (args) { |
| 6002 var computedvalue = fn.apply(computedHost, args); |
| 6003 if (effect.negate) { |
| 6004 computedvalue = !computedvalue; |
| 6005 } |
| 6006 this._applyEffectValue(computedvalue, effect); |
| 6007 } |
| 6008 } else { |
| 6009 computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute me
thod `' + effect.method + '` not defined')); |
| 6010 } |
| 6011 }, |
| 6012 _marshalArgs: function (model, effect, path, value) { |
| 6013 var values = []; |
| 6014 var args = effect.args; |
| 6015 for (var i = 0, l = args.length; i < l; i++) { |
| 6016 var arg = args[i]; |
| 6017 var name = arg.name; |
| 6018 var v; |
| 6019 if (arg.literal) { |
| 6020 v = arg.value; |
| 6021 } else if (arg.structured) { |
| 6022 v = Polymer.Base.get(name, model); |
| 6023 } else { |
| 6024 v = model[name]; |
| 6025 } |
| 6026 if (args.length > 1 && v === undefined) { |
| 6027 return; |
| 6028 } |
| 6029 if (arg.wildcard) { |
| 6030 var baseChanged = name.indexOf(path + '.') === 0; |
| 6031 var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged; |
| 6032 values[i] = { |
| 6033 path: matches ? path : name, |
| 6034 value: matches ? value : v, |
| 6035 base: v |
| 6036 }; |
| 6037 } else { |
| 6038 values[i] = v; |
| 6039 } |
| 6040 } |
| 6041 return values; |
| 6042 } |
| 6043 }); |
| 6044 Polymer.Base._addFeature({ |
| 6045 _addPropertyEffect: function (property, kind, effect) { |
| 6046 Polymer.Bind.addPropertyEffect(this, property, kind, effect); |
| 6047 }, |
| 6048 _prepEffects: function () { |
| 6049 Polymer.Bind.prepareModel(this); |
| 6050 this._addAnnotationEffects(this._notes); |
| 6051 }, |
| 6052 _prepBindings: function () { |
| 6053 Polymer.Bind.createBindings(this); |
| 6054 }, |
| 6055 _addPropertyEffects: function (properties) { |
| 6056 if (properties) { |
| 6057 for (var p in properties) { |
| 6058 var prop = properties[p]; |
| 6059 if (prop.observer) { |
| 6060 this._addObserverEffect(p, prop.observer); |
| 6061 } |
| 6062 if (prop.computed) { |
| 6063 prop.readOnly = true; |
| 6064 this._addComputedEffect(p, prop.computed); |
| 6065 } |
| 6066 if (prop.notify) { |
| 6067 this._addPropertyEffect(p, 'notify'); |
| 6068 } |
| 6069 if (prop.reflectToAttribute) { |
| 6070 this._addPropertyEffect(p, 'reflect'); |
| 6071 } |
| 6072 if (prop.readOnly) { |
| 6073 Polymer.Bind.ensurePropertyEffects(this, p); |
| 6074 } |
| 6075 } |
| 6076 } |
| 6077 }, |
| 6078 _addComputedEffect: function (name, expression) { |
| 6079 var sig = this._parseMethod(expression); |
| 6080 sig.args.forEach(function (arg) { |
| 6081 this._addPropertyEffect(arg.model, 'compute', { |
| 6082 method: sig.method, |
| 6083 args: sig.args, |
| 6084 trigger: arg, |
| 6085 property: name |
| 6086 }); |
| 6087 }, this); |
| 6088 }, |
| 6089 _addObserverEffect: function (property, observer) { |
| 6090 this._addPropertyEffect(property, 'observer', { |
| 6091 method: observer, |
| 6092 property: property |
| 6093 }); |
| 6094 }, |
| 6095 _addComplexObserverEffects: function (observers) { |
| 6096 if (observers) { |
| 6097 observers.forEach(function (observer) { |
| 6098 this._addComplexObserverEffect(observer); |
| 6099 }, this); |
| 6100 } |
| 6101 }, |
| 6102 _addComplexObserverEffect: function (observer) { |
| 6103 var sig = this._parseMethod(observer); |
| 6104 sig.args.forEach(function (arg) { |
| 6105 this._addPropertyEffect(arg.model, 'complexObserver', { |
| 6106 method: sig.method, |
| 6107 args: sig.args, |
| 6108 trigger: arg |
| 6109 }); |
| 6110 }, this); |
| 6111 }, |
| 6112 _addAnnotationEffects: function (notes) { |
| 6113 this._nodes = []; |
| 6114 notes.forEach(function (note) { |
| 6115 var index = this._nodes.push(note) - 1; |
| 6116 note.bindings.forEach(function (binding) { |
| 6117 this._addAnnotationEffect(binding, index); |
| 6118 }, this); |
| 6119 }, this); |
| 6120 }, |
| 6121 _addAnnotationEffect: function (note, index) { |
| 6122 if (Polymer.Bind._shouldAddListener(note)) { |
| 6123 Polymer.Bind._addAnnotatedListener(this, index, note.name, note.value, note.even
t); |
| 6124 } |
| 6125 if (note.signature) { |
| 6126 this._addAnnotatedComputationEffect(note, index); |
| 6127 } else { |
| 6128 note.index = index; |
| 6129 this._addPropertyEffect(note.model, 'annotation', note); |
| 6130 } |
| 6131 }, |
| 6132 _addAnnotatedComputationEffect: function (note, index) { |
| 6133 var sig = note.signature; |
| 6134 if (sig.static) { |
| 6135 this.__addAnnotatedComputationEffect('__static__', index, note, sig, null); |
| 6136 } else { |
| 6137 sig.args.forEach(function (arg) { |
| 6138 if (!arg.literal) { |
| 6139 this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg); |
| 6140 } |
| 6141 }, this); |
| 6142 } |
| 6143 }, |
| 6144 __addAnnotatedComputationEffect: function (property, index, note, sig, trigger)
{ |
| 6145 this._addPropertyEffect(property, 'annotatedComputation', { |
| 6146 index: index, |
| 6147 kind: note.kind, |
| 6148 property: note.name, |
| 6149 negate: note.negate, |
| 6150 method: sig.method, |
| 6151 args: sig.args, |
| 6152 trigger: trigger |
| 6153 }); |
| 6154 }, |
| 6155 _parseMethod: function (expression) { |
| 6156 var m = expression.match(/([^\s]+)\((.*)\)/); |
| 6157 if (m) { |
| 6158 var sig = { |
| 6159 method: m[1], |
| 6160 static: true |
| 6161 }; |
| 6162 if (m[2].trim()) { |
| 6163 var args = m[2].replace(/\\,/g, ',').split(','); |
| 6164 return this._parseArgs(args, sig); |
| 6165 } else { |
| 6166 sig.args = Polymer.nar; |
| 6167 return sig; |
| 6168 } |
| 6169 } |
| 6170 }, |
| 6171 _parseArgs: function (argList, sig) { |
| 6172 sig.args = argList.map(function (rawArg) { |
| 6173 var arg = this._parseArg(rawArg); |
| 6174 if (!arg.literal) { |
| 6175 sig.static = false; |
| 6176 } |
| 6177 return arg; |
| 6178 }, this); |
| 6179 return sig; |
| 6180 }, |
| 6181 _parseArg: function (rawArg) { |
| 6182 var arg = rawArg.trim().replace(/,/g, ',').replace(/\\(.)/g, '$1'); |
| 6183 var a = { |
| 6184 name: arg, |
| 6185 model: this._modelForPath(arg) |
| 6186 }; |
| 6187 var fc = arg[0]; |
| 6188 if (fc === '-') { |
| 6189 fc = arg[1]; |
| 6190 } |
| 6191 if (fc >= '0' && fc <= '9') { |
| 6192 fc = '#'; |
| 6193 } |
| 6194 switch (fc) { |
| 6195 case '\'': |
| 6196 case '"': |
| 6197 a.value = arg.slice(1, -1); |
| 6198 a.literal = true; |
| 6199 break; |
| 6200 case '#': |
| 6201 a.value = Number(arg); |
| 6202 a.literal = true; |
| 6203 break; |
| 6204 } |
| 6205 if (!a.literal) { |
| 6206 a.structured = arg.indexOf('.') > 0; |
| 6207 if (a.structured) { |
| 6208 a.wildcard = arg.slice(-2) == '.*'; |
| 6209 if (a.wildcard) { |
| 6210 a.name = arg.slice(0, -2); |
| 6211 } |
| 6212 } |
| 6213 } |
| 6214 return a; |
| 6215 }, |
| 6216 _marshalInstanceEffects: function () { |
| 6217 Polymer.Bind.prepareInstance(this); |
| 6218 Polymer.Bind.setupBindListeners(this); |
| 6219 }, |
| 6220 _applyEffectValue: function (value, info) { |
| 6221 var node = this._nodes[info.index]; |
| 6222 var property = info.property || info.name || 'textContent'; |
| 6223 if (info.kind == 'attribute') { |
| 6224 this.serializeValueToAttribute(value, property, node); |
| 6225 } else { |
| 6226 if (property === 'className') { |
| 6227 value = this._scopeElementClass(node, value); |
| 6228 } |
| 6229 if (property === 'textContent' || node.localName == 'input' && property == 'valu
e') { |
| 6230 value = value == undefined ? '' : value; |
| 6231 } |
| 6232 return node[property] = value; |
| 6233 } |
| 6234 }, |
| 6235 _executeStaticEffects: function () { |
| 6236 if (this._propertyEffects.__static__) { |
| 6237 this._effectEffects('__static__', null, this._propertyEffects.__static__); |
| 6238 } |
| 6239 } |
| 6240 }); |
| 6241 Polymer.Base._addFeature({ |
| 6242 _setupConfigure: function (initialConfig) { |
| 6243 this._config = {}; |
| 6244 for (var i in initialConfig) { |
| 6245 if (initialConfig[i] !== undefined) { |
| 6246 this._config[i] = initialConfig[i]; |
| 6247 } |
| 6248 } |
| 6249 this._handlers = []; |
| 6250 }, |
| 6251 _marshalAttributes: function () { |
| 6252 this._takeAttributesToModel(this._config); |
| 6253 }, |
| 6254 _attributeChangedImpl: function (name) { |
| 6255 var model = this._clientsReadied ? this : this._config; |
| 6256 this._setAttributeToProperty(model, name); |
| 6257 }, |
| 6258 _configValue: function (name, value) { |
| 6259 this._config[name] = value; |
| 6260 }, |
| 6261 _beforeClientsReady: function () { |
| 6262 this._configure(); |
| 6263 }, |
| 6264 _configure: function () { |
| 6265 this._configureAnnotationReferences(); |
| 6266 this._aboveConfig = this.mixin({}, this._config); |
| 6267 var config = {}; |
| 6268 this.behaviors.forEach(function (b) { |
| 6269 this._configureProperties(b.properties, config); |
| 6270 }, this); |
| 6271 this._configureProperties(this.properties, config); |
| 6272 this._mixinConfigure(config, this._aboveConfig); |
| 6273 this._config = config; |
| 6274 this._distributeConfig(this._config); |
| 6275 }, |
| 6276 _configureProperties: function (properties, config) { |
| 6277 for (var i in properties) { |
| 6278 var c = properties[i]; |
| 6279 if (c.value !== undefined) { |
| 6280 var value = c.value; |
| 6281 if (typeof value == 'function') { |
| 6282 value = value.call(this, this._config); |
| 6283 } |
| 6284 config[i] = value; |
| 6285 } |
| 6286 } |
| 6287 }, |
| 6288 _mixinConfigure: function (a, b) { |
| 6289 for (var prop in b) { |
| 6290 if (!this.getPropertyInfo(prop).readOnly) { |
| 6291 a[prop] = b[prop]; |
| 6292 } |
| 6293 } |
| 6294 }, |
| 6295 _distributeConfig: function (config) { |
| 6296 var fx$ = this._propertyEffects; |
| 6297 if (fx$) { |
| 6298 for (var p in config) { |
| 6299 var fx = fx$[p]; |
| 6300 if (fx) { |
| 6301 for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) { |
| 6302 if (x.kind === 'annotation') { |
| 6303 var node = this._nodes[x.effect.index]; |
| 6304 if (node._configValue) { |
| 6305 var value = p === x.effect.value ? config[p] : this.get(x.effect.value, config); |
| 6306 node._configValue(x.effect.name, value); |
| 6307 } |
| 6308 } |
| 6309 } |
| 6310 } |
| 6311 } |
| 6312 } |
| 6313 }, |
| 6314 _afterClientsReady: function () { |
| 6315 this._executeStaticEffects(); |
| 6316 this._applyConfig(this._config, this._aboveConfig); |
| 6317 this._flushHandlers(); |
| 6318 }, |
| 6319 _applyConfig: function (config, aboveConfig) { |
| 6320 for (var n in config) { |
| 6321 if (this[n] === undefined) { |
| 6322 this.__setProperty(n, config[n], n in aboveConfig); |
| 6323 } |
| 6324 } |
| 6325 }, |
| 6326 _notifyListener: function (fn, e) { |
| 6327 if (!this._clientsReadied) { |
| 6328 this._queueHandler([ |
| 6329 fn, |
| 6330 e, |
| 6331 e.target |
| 6332 ]); |
| 6333 } else { |
| 6334 return fn.call(this, e, e.target); |
| 6335 } |
| 6336 }, |
| 6337 _queueHandler: function (args) { |
| 6338 this._handlers.push(args); |
| 6339 }, |
| 6340 _flushHandlers: function () { |
| 6341 var h$ = this._handlers; |
| 6342 for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) { |
| 6343 h[0].call(this, h[1], h[2]); |
| 6344 } |
| 6345 this._handlers = []; |
| 6346 } |
| 6347 }); |
| 6348 (function () { |
| 6349 'use strict'; |
| 6350 Polymer.Base._addFeature({ |
| 6351 notifyPath: function (path, value, fromAbove) { |
| 6352 var old = this._propertySetter(path, value); |
| 6353 if (old !== value && (old === old || value === value)) { |
| 6354 this._pathEffector(path, value); |
| 6355 if (!fromAbove) { |
| 6356 this._notifyPath(path, value); |
| 6357 } |
| 6358 return true; |
| 6359 } |
| 6360 }, |
| 6361 _getPathParts: function (path) { |
| 6362 if (Array.isArray(path)) { |
| 6363 var parts = []; |
| 6364 for (var i = 0; i < path.length; i++) { |
| 6365 var args = path[i].toString().split('.'); |
| 6366 for (var j = 0; j < args.length; j++) { |
| 6367 parts.push(args[j]); |
| 6368 } |
| 6369 } |
| 6370 return parts; |
| 6371 } else { |
| 6372 return path.toString().split('.'); |
| 6373 } |
| 6374 }, |
| 6375 set: function (path, value, root) { |
| 6376 var prop = root || this; |
| 6377 var parts = this._getPathParts(path); |
| 6378 var array; |
| 6379 var last = parts[parts.length - 1]; |
| 6380 if (parts.length > 1) { |
| 6381 for (var i = 0; i < parts.length - 1; i++) { |
| 6382 var part = parts[i]; |
| 6383 prop = prop[part]; |
| 6384 if (array && parseInt(part) == part) { |
| 6385 parts[i] = Polymer.Collection.get(array).getKey(prop); |
| 6386 } |
| 6387 if (!prop) { |
| 6388 return; |
| 6389 } |
| 6390 array = Array.isArray(prop) ? prop : null; |
| 6391 } |
| 6392 if (array && parseInt(last) == last) { |
| 6393 var coll = Polymer.Collection.get(array); |
| 6394 var old = prop[last]; |
| 6395 var key = coll.getKey(old); |
| 6396 parts[i] = key; |
| 6397 coll.setItem(key, value); |
| 6398 } |
| 6399 prop[last] = value; |
| 6400 if (!root) { |
| 6401 this.notifyPath(parts.join('.'), value); |
| 6402 } |
| 6403 } else { |
| 6404 prop[path] = value; |
| 6405 } |
| 6406 }, |
| 6407 get: function (path, root) { |
| 6408 var prop = root || this; |
| 6409 var parts = this._getPathParts(path); |
| 6410 var last = parts.pop(); |
| 6411 while (parts.length) { |
| 6412 prop = prop[parts.shift()]; |
| 6413 if (!prop) { |
| 6414 return; |
| 6415 } |
| 6416 } |
| 6417 return prop[last]; |
| 6418 }, |
| 6419 _pathEffector: function (path, value) { |
| 6420 var model = this._modelForPath(path); |
| 6421 var fx$ = this._propertyEffects[model]; |
| 6422 if (fx$) { |
| 6423 fx$.forEach(function (fx) { |
| 6424 var fxFn = this['_' + fx.kind + 'PathEffect']; |
| 6425 if (fxFn) { |
| 6426 fxFn.call(this, path, value, fx.effect); |
| 6427 } |
| 6428 }, this); |
| 6429 } |
| 6430 if (this._boundPaths) { |
| 6431 this._notifyBoundPaths(path, value); |
| 6432 } |
| 6433 }, |
| 6434 _annotationPathEffect: function (path, value, effect) { |
| 6435 if (effect.value === path || effect.value.indexOf(path + '.') === 0) { |
| 6436 Polymer.Bind._annotationEffect.call(this, path, value, effect); |
| 6437 } else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) { |
| 6438 var node = this._nodes[effect.index]; |
| 6439 if (node && node.notifyPath) { |
| 6440 var p = this._fixPath(effect.name, effect.value, path); |
| 6441 node.notifyPath(p, value, true); |
| 6442 } |
| 6443 } |
| 6444 }, |
| 6445 _complexObserverPathEffect: function (path, value, effect) { |
| 6446 if (this._pathMatchesEffect(path, effect)) { |
| 6447 Polymer.Bind._complexObserverEffect.call(this, path, value, effect); |
| 6448 } |
| 6449 }, |
| 6450 _computePathEffect: function (path, value, effect) { |
| 6451 if (this._pathMatchesEffect(path, effect)) { |
| 6452 Polymer.Bind._computeEffect.call(this, path, value, effect); |
| 6453 } |
| 6454 }, |
| 6455 _annotatedComputationPathEffect: function (path, value, effect) { |
| 6456 if (this._pathMatchesEffect(path, effect)) { |
| 6457 Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect); |
| 6458 } |
| 6459 }, |
| 6460 _pathMatchesEffect: function (path, effect) { |
| 6461 var effectArg = effect.trigger.name; |
| 6462 return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigge
r.wildcard && path.indexOf(effectArg) === 0; |
| 6463 }, |
| 6464 linkPaths: function (to, from) { |
| 6465 this._boundPaths = this._boundPaths || {}; |
| 6466 if (from) { |
| 6467 this._boundPaths[to] = from; |
| 6468 } else { |
| 6469 this.unlinkPaths(to); |
| 6470 } |
| 6471 }, |
| 6472 unlinkPaths: function (path) { |
| 6473 if (this._boundPaths) { |
| 6474 delete this._boundPaths[path]; |
| 6475 } |
| 6476 }, |
| 6477 _notifyBoundPaths: function (path, value) { |
| 6478 for (var a in this._boundPaths) { |
| 6479 var b = this._boundPaths[a]; |
| 6480 if (path.indexOf(a + '.') == 0) { |
| 6481 this.notifyPath(this._fixPath(b, a, path), value); |
| 6482 } else if (path.indexOf(b + '.') == 0) { |
| 6483 this.notifyPath(this._fixPath(a, b, path), value); |
| 6484 } |
| 6485 } |
| 6486 }, |
| 6487 _fixPath: function (property, root, path) { |
| 6488 return property + path.slice(root.length); |
| 6489 }, |
| 6490 _notifyPath: function (path, value) { |
| 6491 var rootName = this._modelForPath(path); |
| 6492 var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName); |
| 6493 var eventName = dashCaseName + this._EVENT_CHANGED; |
| 6494 this.fire(eventName, { |
| 6495 path: path, |
| 6496 value: value |
| 6497 }, { bubbles: false }); |
| 6498 }, |
| 6499 _modelForPath: function (path) { |
| 6500 var dot = path.indexOf('.'); |
| 6501 return dot < 0 ? path : path.slice(0, dot); |
| 6502 }, |
| 6503 _EVENT_CHANGED: '-changed', |
| 6504 _notifySplice: function (array, path, index, added, removed) { |
| 6505 var splices = [{ |
| 6506 index: index, |
| 6507 addedCount: added, |
| 6508 removed: removed, |
| 6509 object: array, |
| 6510 type: 'splice' |
| 6511 }]; |
| 6512 var change = { |
| 6513 keySplices: Polymer.Collection.applySplices(array, splices), |
| 6514 indexSplices: splices |
| 6515 }; |
| 6516 this.set(path + '.splices', change); |
| 6517 if (added != removed.length) { |
| 6518 this.notifyPath(path + '.length', array.length); |
| 6519 } |
| 6520 change.keySplices = null; |
| 6521 change.indexSplices = null; |
| 6522 }, |
| 6523 push: function (path) { |
| 6524 var array = this.get(path); |
| 6525 var args = Array.prototype.slice.call(arguments, 1); |
| 6526 var len = array.length; |
| 6527 var ret = array.push.apply(array, args); |
| 6528 if (args.length) { |
| 6529 this._notifySplice(array, path, len, args.length, []); |
| 6530 } |
| 6531 return ret; |
| 6532 }, |
| 6533 pop: function (path) { |
| 6534 var array = this.get(path); |
| 6535 var hadLength = Boolean(array.length); |
| 6536 var args = Array.prototype.slice.call(arguments, 1); |
| 6537 var ret = array.pop.apply(array, args); |
| 6538 if (hadLength) { |
| 6539 this._notifySplice(array, path, array.length, 0, [ret]); |
| 6540 } |
| 6541 return ret; |
| 6542 }, |
| 6543 splice: function (path, start, deleteCount) { |
| 6544 var array = this.get(path); |
| 6545 if (start < 0) { |
| 6546 start = array.length - Math.floor(-start); |
| 6547 } else { |
| 6548 start = Math.floor(start); |
| 6549 } |
| 6550 if (!start) { |
| 6551 start = 0; |
| 6552 } |
| 6553 var args = Array.prototype.slice.call(arguments, 1); |
| 6554 var ret = array.splice.apply(array, args); |
| 6555 var addedCount = Math.max(args.length - 2, 0); |
| 6556 if (addedCount || ret.length) { |
| 6557 this._notifySplice(array, path, start, addedCount, ret); |
| 6558 } |
| 6559 return ret; |
| 6560 }, |
| 6561 shift: function (path) { |
| 6562 var array = this.get(path); |
| 6563 var hadLength = Boolean(array.length); |
| 6564 var args = Array.prototype.slice.call(arguments, 1); |
| 6565 var ret = array.shift.apply(array, args); |
| 6566 if (hadLength) { |
| 6567 this._notifySplice(array, path, 0, 0, [ret]); |
| 6568 } |
| 6569 return ret; |
| 6570 }, |
| 6571 unshift: function (path) { |
| 6572 var array = this.get(path); |
| 6573 var args = Array.prototype.slice.call(arguments, 1); |
| 6574 var ret = array.unshift.apply(array, args); |
| 6575 if (args.length) { |
| 6576 this._notifySplice(array, path, 0, args.length, []); |
| 6577 } |
| 6578 return ret; |
| 6579 } |
| 6580 }); |
| 6581 }()); |
| 6582 Polymer.Base._addFeature({ |
| 6583 resolveUrl: function (url) { |
| 6584 var module = Polymer.DomModule.import(this.is); |
| 6585 var root = ''; |
| 6586 if (module) { |
| 6587 var assetPath = module.getAttribute('assetpath') || ''; |
| 6588 root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI); |
| 6589 } |
| 6590 return Polymer.ResolveUrl.resolveUrl(url, root); |
| 6591 } |
| 6592 }); |
| 6593 Polymer.CssParse = function () { |
| 6594 var api = { |
| 6595 parse: function (text) { |
| 6596 text = this._clean(text); |
| 6597 return this._parseCss(this._lex(text), text); |
| 6598 }, |
| 6599 _clean: function (cssText) { |
| 6600 return cssText.replace(this._rx.comments, '').replace(this._rx.port, ''); |
| 6601 }, |
| 6602 _lex: function (text) { |
| 6603 var root = { |
| 6604 start: 0, |
| 6605 end: text.length |
| 6606 }; |
| 6607 var n = root; |
| 6608 for (var i = 0, s = 0, l = text.length; i < l; i++) { |
| 6609 switch (text[i]) { |
| 6610 case this.OPEN_BRACE: |
| 6611 if (!n.rules) { |
| 6612 n.rules = []; |
| 6613 } |
| 6614 var p = n; |
| 6615 var previous = p.rules[p.rules.length - 1]; |
| 6616 n = { |
| 6617 start: i + 1, |
| 6618 parent: p, |
| 6619 previous: previous |
| 6620 }; |
| 6621 p.rules.push(n); |
| 6622 break; |
| 6623 case this.CLOSE_BRACE: |
| 6624 n.end = i + 1; |
| 6625 n = n.parent || root; |
| 6626 break; |
| 6627 } |
| 6628 } |
| 6629 return root; |
| 6630 }, |
| 6631 _parseCss: function (node, text) { |
| 6632 var t = text.substring(node.start, node.end - 1); |
| 6633 node.parsedCssText = node.cssText = t.trim(); |
| 6634 if (node.parent) { |
| 6635 var ss = node.previous ? node.previous.end : node.parent.start; |
| 6636 t = text.substring(ss, node.start - 1); |
| 6637 t = t.substring(t.lastIndexOf(';') + 1); |
| 6638 var s = node.parsedSelector = node.selector = t.trim(); |
| 6639 node.atRule = s.indexOf(this.AT_START) === 0; |
| 6640 if (node.atRule) { |
| 6641 if (s.indexOf(this.MEDIA_START) === 0) { |
| 6642 node.type = this.types.MEDIA_RULE; |
| 6643 } else if (s.match(this._rx.keyframesRule)) { |
| 6644 node.type = this.types.KEYFRAMES_RULE; |
| 6645 } |
| 6646 } else { |
| 6647 if (s.indexOf(this.VAR_START) === 0) { |
| 6648 node.type = this.types.MIXIN_RULE; |
| 6649 } else { |
| 6650 node.type = this.types.STYLE_RULE; |
| 6651 } |
| 6652 } |
| 6653 } |
| 6654 var r$ = node.rules; |
| 6655 if (r$) { |
| 6656 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { |
| 6657 this._parseCss(r, text); |
| 6658 } |
| 6659 } |
| 6660 return node; |
| 6661 }, |
| 6662 stringify: function (node, preserveProperties, text) { |
| 6663 text = text || ''; |
| 6664 var cssText = ''; |
| 6665 if (node.cssText || node.rules) { |
| 6666 var r$ = node.rules; |
| 6667 if (r$ && (preserveProperties || !this._hasMixinRules(r$))) { |
| 6668 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { |
| 6669 cssText = this.stringify(r, preserveProperties, cssText); |
| 6670 } |
| 6671 } else { |
| 6672 cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssTex
t); |
| 6673 cssText = cssText.trim(); |
| 6674 if (cssText) { |
| 6675 cssText = ' ' + cssText + '\n'; |
| 6676 } |
| 6677 } |
| 6678 } |
| 6679 if (cssText) { |
| 6680 if (node.selector) { |
| 6681 text += node.selector + ' ' + this.OPEN_BRACE + '\n'; |
| 6682 } |
| 6683 text += cssText; |
| 6684 if (node.selector) { |
| 6685 text += this.CLOSE_BRACE + '\n\n'; |
| 6686 } |
| 6687 } |
| 6688 return text; |
| 6689 }, |
| 6690 _hasMixinRules: function (rules) { |
| 6691 return rules[0].selector.indexOf(this.VAR_START) >= 0; |
| 6692 }, |
| 6693 removeCustomProps: function (cssText) { |
| 6694 return cssText; |
| 6695 }, |
| 6696 removeCustomPropAssignment: function (cssText) { |
| 6697 return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, ''); |
| 6698 }, |
| 6699 removeCustomPropApply: function (cssText) { |
| 6700 return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, ''); |
| 6701 }, |
| 6702 types: { |
| 6703 STYLE_RULE: 1, |
| 6704 KEYFRAMES_RULE: 7, |
| 6705 MEDIA_RULE: 4, |
| 6706 MIXIN_RULE: 1000 |
| 6707 }, |
| 6708 OPEN_BRACE: '{', |
| 6709 CLOSE_BRACE: '}', |
| 6710 _rx: { |
| 6711 comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim, |
| 6712 port: /@import[^;]*;/gim, |
| 6713 customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim, |
| 6714 mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim, |
| 6715 mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim, |
| 6716 varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim, |
| 6717 keyframesRule: /^@[^\s]*keyframes/ |
| 6718 }, |
| 6719 VAR_START: '--', |
| 6720 MEDIA_START: '@media', |
| 6721 AT_START: '@' |
| 6722 }; |
| 6723 return api; |
| 6724 }(); |
| 6725 Polymer.StyleUtil = function () { |
| 6726 return { |
| 6727 MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template', |
| 6728 INCLUDE_ATTR: 'include', |
| 6729 toCssText: function (rules, callback, preserveProperties) { |
| 6730 if (typeof rules === 'string') { |
| 6731 rules = this.parser.parse(rules); |
| 6732 } |
| 6733 if (callback) { |
| 6734 this.forEachStyleRule(rules, callback); |
| 6735 } |
| 6736 return this.parser.stringify(rules, preserveProperties); |
| 6737 }, |
| 6738 forRulesInStyles: function (styles, callback) { |
| 6739 if (styles) { |
| 6740 for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) { |
| 6741 this.forEachStyleRule(this.rulesForStyle(s), callback); |
| 6742 } |
| 6743 } |
| 6744 }, |
| 6745 rulesForStyle: function (style) { |
| 6746 if (!style.__cssRules && style.textContent) { |
| 6747 style.__cssRules = this.parser.parse(style.textContent); |
| 6748 } |
| 6749 return style.__cssRules; |
| 6750 }, |
| 6751 clearStyleRules: function (style) { |
| 6752 style.__cssRules = null; |
| 6753 }, |
| 6754 forEachStyleRule: function (node, callback) { |
| 6755 if (!node) { |
| 6756 return; |
| 6757 } |
| 6758 var s = node.parsedSelector; |
| 6759 var skipRules = false; |
| 6760 if (node.type === this.ruleTypes.STYLE_RULE) { |
| 6761 callback(node); |
| 6762 } else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.rul
eTypes.MIXIN_RULE) { |
| 6763 skipRules = true; |
| 6764 } |
| 6765 var r$ = node.rules; |
| 6766 if (r$ && !skipRules) { |
| 6767 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { |
| 6768 this.forEachStyleRule(r, callback); |
| 6769 } |
| 6770 } |
| 6771 }, |
| 6772 applyCss: function (cssText, moniker, target, afterNode) { |
| 6773 var style = document.createElement('style'); |
| 6774 if (moniker) { |
| 6775 style.setAttribute('scope', moniker); |
| 6776 } |
| 6777 style.textContent = cssText; |
| 6778 target = target || document.head; |
| 6779 if (!afterNode) { |
| 6780 var n$ = target.querySelectorAll('style[scope]'); |
| 6781 afterNode = n$[n$.length - 1]; |
| 6782 } |
| 6783 target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChi
ld); |
| 6784 return style; |
| 6785 }, |
| 6786 cssFromModules: function (moduleIds, warnIfNotFound) { |
| 6787 var modules = moduleIds.trim().split(' '); |
| 6788 var cssText = ''; |
| 6789 for (var i = 0; i < modules.length; i++) { |
| 6790 cssText += this.cssFromModule(modules[i], warnIfNotFound); |
| 6791 } |
| 6792 return cssText; |
| 6793 }, |
| 6794 cssFromModule: function (moduleId, warnIfNotFound) { |
| 6795 var m = Polymer.DomModule.import(moduleId); |
| 6796 if (m && !m._cssText) { |
| 6797 m._cssText = this._cssFromElement(m); |
| 6798 } |
| 6799 if (!m && warnIfNotFound) { |
| 6800 console.warn('Could not find style data in module named', moduleId); |
| 6801 } |
| 6802 return m && m._cssText || ''; |
| 6803 }, |
| 6804 _cssFromElement: function (element) { |
| 6805 var cssText = ''; |
| 6806 var content = element.content || element; |
| 6807 var e$ = Array.prototype.slice.call(content.querySelectorAll(this.MODULE_STYLES_
SELECTOR)); |
| 6808 for (var i = 0, e; i < e$.length; i++) { |
| 6809 e = e$[i]; |
| 6810 if (e.localName === 'template') { |
| 6811 cssText += this._cssFromElement(e); |
| 6812 } else { |
| 6813 if (e.localName === 'style') { |
| 6814 var include = e.getAttribute(this.INCLUDE_ATTR); |
| 6815 if (include) { |
| 6816 cssText += this.cssFromModules(include, true); |
| 6817 } |
| 6818 e = e.__appliedElement || e; |
| 6819 e.parentNode.removeChild(e); |
| 6820 cssText += this.resolveCss(e.textContent, element.ownerDocument); |
| 6821 } else if (e.import && e.import.body) { |
| 6822 cssText += this.resolveCss(e.import.body.textContent, e.import); |
| 6823 } |
| 6824 } |
| 6825 } |
| 6826 return cssText; |
| 6827 }, |
| 6828 resolveCss: Polymer.ResolveUrl.resolveCss, |
| 6829 parser: Polymer.CssParse, |
| 6830 ruleTypes: Polymer.CssParse.types |
| 6831 }; |
| 6832 }(); |
| 6833 Polymer.StyleTransformer = function () { |
| 6834 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 6835 var styleUtil = Polymer.StyleUtil; |
| 6836 var api = { |
| 6837 dom: function (node, scope, useAttr, shouldRemoveScope) { |
| 6838 this._transformDom(node, scope || '', useAttr, shouldRemoveScope); |
| 6839 }, |
| 6840 _transformDom: function (node, selector, useAttr, shouldRemoveScope) { |
| 6841 if (node.setAttribute) { |
| 6842 this.element(node, selector, useAttr, shouldRemoveScope); |
| 6843 } |
| 6844 var c$ = Polymer.dom(node).childNodes; |
| 6845 for (var i = 0; i < c$.length; i++) { |
| 6846 this._transformDom(c$[i], selector, useAttr, shouldRemoveScope); |
| 6847 } |
| 6848 }, |
| 6849 element: function (element, scope, useAttr, shouldRemoveScope) { |
| 6850 if (useAttr) { |
| 6851 if (shouldRemoveScope) { |
| 6852 element.removeAttribute(SCOPE_NAME); |
| 6853 } else { |
| 6854 element.setAttribute(SCOPE_NAME, scope); |
| 6855 } |
| 6856 } else { |
| 6857 if (scope) { |
| 6858 if (element.classList) { |
| 6859 if (shouldRemoveScope) { |
| 6860 element.classList.remove(SCOPE_NAME); |
| 6861 element.classList.remove(scope); |
| 6862 } else { |
| 6863 element.classList.add(SCOPE_NAME); |
| 6864 element.classList.add(scope); |
| 6865 } |
| 6866 } else if (element.getAttribute) { |
| 6867 var c = element.getAttribute(CLASS); |
| 6868 if (shouldRemoveScope) { |
| 6869 if (c) { |
| 6870 element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, '')); |
| 6871 } |
| 6872 } else { |
| 6873 element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope); |
| 6874 } |
| 6875 } |
| 6876 } |
| 6877 } |
| 6878 }, |
| 6879 elementStyles: function (element, callback) { |
| 6880 var styles = element._styles; |
| 6881 var cssText = ''; |
| 6882 for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) { |
| 6883 var rules = styleUtil.rulesForStyle(s); |
| 6884 cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules,
element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n'; |
| 6885 } |
| 6886 return cssText.trim(); |
| 6887 }, |
| 6888 css: function (rules, scope, ext, callback, useAttr) { |
| 6889 var hostScope = this._calcHostScope(scope, ext); |
| 6890 scope = this._calcElementScope(scope, useAttr); |
| 6891 var self = this; |
| 6892 return styleUtil.toCssText(rules, function (rule) { |
| 6893 if (!rule.isScoped) { |
| 6894 self.rule(rule, scope, hostScope); |
| 6895 rule.isScoped = true; |
| 6896 } |
| 6897 if (callback) { |
| 6898 callback(rule, scope, hostScope); |
| 6899 } |
| 6900 }); |
| 6901 }, |
| 6902 _calcElementScope: function (scope, useAttr) { |
| 6903 if (scope) { |
| 6904 return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX +
scope; |
| 6905 } else { |
| 6906 return ''; |
| 6907 } |
| 6908 }, |
| 6909 _calcHostScope: function (scope, ext) { |
| 6910 return ext ? '[is=' + scope + ']' : scope; |
| 6911 }, |
| 6912 rule: function (rule, scope, hostScope) { |
| 6913 this._transformRule(rule, this._transformComplexSelector, scope, hostScope); |
| 6914 }, |
| 6915 _transformRule: function (rule, transformer, scope, hostScope) { |
| 6916 var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP); |
| 6917 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) { |
| 6918 p$[i] = transformer.call(this, p, scope, hostScope); |
| 6919 } |
| 6920 rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP); |
| 6921 }, |
| 6922 _transformComplexSelector: function (selector, scope, hostScope) { |
| 6923 var stop = false; |
| 6924 var hostContext = false; |
| 6925 var self = this; |
| 6926 selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) { |
| 6927 if (!stop) { |
| 6928 var info = self._transformCompoundSelector(s, c, scope, hostScope); |
| 6929 stop = stop || info.stop; |
| 6930 hostContext = hostContext || info.hostContext; |
| 6931 c = info.combinator; |
| 6932 s = info.value; |
| 6933 } else { |
| 6934 s = s.replace(SCOPE_JUMP, ' '); |
| 6935 } |
| 6936 return c + s; |
| 6937 }); |
| 6938 if (hostContext) { |
| 6939 selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) { |
| 6940 return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre +
hostScope + paren + post; |
| 6941 }); |
| 6942 } |
| 6943 return selector; |
| 6944 }, |
| 6945 _transformCompoundSelector: function (selector, combinator, scope, hostScope) { |
| 6946 var jumpIndex = selector.search(SCOPE_JUMP); |
| 6947 var hostContext = false; |
| 6948 if (selector.indexOf(HOST_CONTEXT) >= 0) { |
| 6949 hostContext = true; |
| 6950 } else if (selector.indexOf(HOST) >= 0) { |
| 6951 selector = selector.replace(HOST_PAREN, function (m, host, paren) { |
| 6952 return hostScope + paren; |
| 6953 }); |
| 6954 selector = selector.replace(HOST, hostScope); |
| 6955 } else if (jumpIndex !== 0) { |
| 6956 selector = scope ? this._transformSimpleSelector(selector, scope) : selector; |
| 6957 } |
| 6958 if (selector.indexOf(CONTENT) >= 0) { |
| 6959 combinator = ''; |
| 6960 } |
| 6961 var stop; |
| 6962 if (jumpIndex >= 0) { |
| 6963 selector = selector.replace(SCOPE_JUMP, ' '); |
| 6964 stop = true; |
| 6965 } |
| 6966 return { |
| 6967 value: selector, |
| 6968 combinator: combinator, |
| 6969 stop: stop, |
| 6970 hostContext: hostContext |
| 6971 }; |
| 6972 }, |
| 6973 _transformSimpleSelector: function (selector, scope) { |
| 6974 var p$ = selector.split(PSEUDO_PREFIX); |
| 6975 p$[0] += scope; |
| 6976 return p$.join(PSEUDO_PREFIX); |
| 6977 }, |
| 6978 documentRule: function (rule) { |
| 6979 rule.selector = rule.parsedSelector; |
| 6980 this.normalizeRootSelector(rule); |
| 6981 if (!nativeShadow) { |
| 6982 this._transformRule(rule, this._transformDocumentSelector); |
| 6983 } |
| 6984 }, |
| 6985 normalizeRootSelector: function (rule) { |
| 6986 if (rule.selector === ROOT) { |
| 6987 rule.selector = 'body'; |
| 6988 } |
| 6989 }, |
| 6990 _transformDocumentSelector: function (selector) { |
| 6991 return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCO
PE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELE
CTOR); |
| 6992 }, |
| 6993 SCOPE_NAME: 'style-scope' |
| 6994 }; |
| 6995 var SCOPE_NAME = api.SCOPE_NAME; |
| 6996 var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME +
')'; |
| 6997 var COMPLEX_SELECTOR_SEP = ','; |
| 6998 var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g; |
| 6999 var HOST = ':host'; |
| 7000 var ROOT = ':root'; |
| 7001 var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g; |
| 7002 var HOST_CONTEXT = ':host-context'; |
| 7003 var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\)
)(.*)/; |
| 7004 var CONTENT = '::content'; |
| 7005 var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//; |
| 7006 var CSS_CLASS_PREFIX = '.'; |
| 7007 var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~='; |
| 7008 var CSS_ATTR_SUFFIX = ']'; |
| 7009 var PSEUDO_PREFIX = ':'; |
| 7010 var CLASS = 'class'; |
| 7011 return api; |
| 7012 }(); |
| 7013 Polymer.StyleExtends = function () { |
| 7014 var styleUtil = Polymer.StyleUtil; |
| 7015 return { |
| 7016 hasExtends: function (cssText) { |
| 7017 return Boolean(cssText.match(this.rx.EXTEND)); |
| 7018 }, |
| 7019 transform: function (style) { |
| 7020 var rules = styleUtil.rulesForStyle(style); |
| 7021 var self = this; |
| 7022 styleUtil.forEachStyleRule(rules, function (rule) { |
| 7023 var map = self._mapRule(rule); |
| 7024 if (rule.parent) { |
| 7025 var m; |
| 7026 while (m = self.rx.EXTEND.exec(rule.cssText)) { |
| 7027 var extend = m[1]; |
| 7028 var extendor = self._findExtendor(extend, rule); |
| 7029 if (extendor) { |
| 7030 self._extendRule(rule, extendor); |
| 7031 } |
| 7032 } |
| 7033 } |
| 7034 rule.cssText = rule.cssText.replace(self.rx.EXTEND, ''); |
| 7035 }); |
| 7036 return styleUtil.toCssText(rules, function (rule) { |
| 7037 if (rule.selector.match(self.rx.STRIP)) { |
| 7038 rule.cssText = ''; |
| 7039 } |
| 7040 }, true); |
| 7041 }, |
| 7042 _mapRule: function (rule) { |
| 7043 if (rule.parent) { |
| 7044 var map = rule.parent.map || (rule.parent.map = {}); |
| 7045 var parts = rule.selector.split(','); |
| 7046 for (var i = 0, p; i < parts.length; i++) { |
| 7047 p = parts[i]; |
| 7048 map[p.trim()] = rule; |
| 7049 } |
| 7050 return map; |
| 7051 } |
| 7052 }, |
| 7053 _findExtendor: function (extend, rule) { |
| 7054 return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findEx
tendor(extend, rule.parent); |
| 7055 }, |
| 7056 _extendRule: function (target, source) { |
| 7057 if (target.parent !== source.parent) { |
| 7058 this._cloneAndAddRuleToParent(source, target.parent); |
| 7059 } |
| 7060 target.extends = target.extends || (target.extends = []); |
| 7061 target.extends.push(source); |
| 7062 source.selector = source.selector.replace(this.rx.STRIP, ''); |
| 7063 source.selector = (source.selector && source.selector + ',\n') + target.selector
; |
| 7064 if (source.extends) { |
| 7065 source.extends.forEach(function (e) { |
| 7066 this._extendRule(target, e); |
| 7067 }, this); |
| 7068 } |
| 7069 }, |
| 7070 _cloneAndAddRuleToParent: function (rule, parent) { |
| 7071 rule = Object.create(rule); |
| 7072 rule.parent = parent; |
| 7073 if (rule.extends) { |
| 7074 rule.extends = rule.extends.slice(); |
| 7075 } |
| 7076 parent.rules.push(rule); |
| 7077 }, |
| 7078 rx: { |
| 7079 EXTEND: /@extends\(([^)]*)\)\s*?;/gim, |
| 7080 STRIP: /%[^,]*$/ |
| 7081 } |
| 7082 }; |
| 7083 }(); |
| 7084 (function () { |
| 7085 var prepElement = Polymer.Base._prepElement; |
| 7086 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7087 var styleUtil = Polymer.StyleUtil; |
| 7088 var styleTransformer = Polymer.StyleTransformer; |
| 7089 var styleExtends = Polymer.StyleExtends; |
| 7090 Polymer.Base._addFeature({ |
| 7091 _prepElement: function (element) { |
| 7092 if (this._encapsulateStyle) { |
| 7093 styleTransformer.element(element, this.is, this._scopeCssViaAttr); |
| 7094 } |
| 7095 prepElement.call(this, element); |
| 7096 }, |
| 7097 _prepStyles: function () { |
| 7098 if (this._encapsulateStyle === undefined) { |
| 7099 this._encapsulateStyle = !nativeShadow && Boolean(this._template); |
| 7100 } |
| 7101 this._styles = this._collectStyles(); |
| 7102 var cssText = styleTransformer.elementStyles(this); |
| 7103 if (cssText && this._template) { |
| 7104 var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.c
ontent : null); |
| 7105 if (!nativeShadow) { |
| 7106 this._scopeStyle = style; |
| 7107 } |
| 7108 } |
| 7109 }, |
| 7110 _collectStyles: function () { |
| 7111 var styles = []; |
| 7112 var cssText = '', m$ = this.styleModules; |
| 7113 if (m$) { |
| 7114 for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) { |
| 7115 cssText += styleUtil.cssFromModule(m); |
| 7116 } |
| 7117 } |
| 7118 cssText += styleUtil.cssFromModule(this.is); |
| 7119 if (cssText) { |
| 7120 var style = document.createElement('style'); |
| 7121 style.textContent = cssText; |
| 7122 if (styleExtends.hasExtends(style.textContent)) { |
| 7123 cssText = styleExtends.transform(style); |
| 7124 } |
| 7125 styles.push(style); |
| 7126 } |
| 7127 return styles; |
| 7128 }, |
| 7129 _elementAdd: function (node) { |
| 7130 if (this._encapsulateStyle) { |
| 7131 if (node.__styleScoped) { |
| 7132 node.__styleScoped = false; |
| 7133 } else { |
| 7134 styleTransformer.dom(node, this.is, this._scopeCssViaAttr); |
| 7135 } |
| 7136 } |
| 7137 }, |
| 7138 _elementRemove: function (node) { |
| 7139 if (this._encapsulateStyle) { |
| 7140 styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true); |
| 7141 } |
| 7142 }, |
| 7143 scopeSubtree: function (container, shouldObserve) { |
| 7144 if (nativeShadow) { |
| 7145 return; |
| 7146 } |
| 7147 var self = this; |
| 7148 var scopify = function (node) { |
| 7149 if (node.nodeType === Node.ELEMENT_NODE) { |
| 7150 node.className = self._scopeElementClass(node, node.className); |
| 7151 var n$ = node.querySelectorAll('*'); |
| 7152 Array.prototype.forEach.call(n$, function (n) { |
| 7153 n.className = self._scopeElementClass(n, n.className); |
| 7154 }); |
| 7155 } |
| 7156 }; |
| 7157 scopify(container); |
| 7158 if (shouldObserve) { |
| 7159 var mo = new MutationObserver(function (mxns) { |
| 7160 mxns.forEach(function (m) { |
| 7161 if (m.addedNodes) { |
| 7162 for (var i = 0; i < m.addedNodes.length; i++) { |
| 7163 scopify(m.addedNodes[i]); |
| 7164 } |
| 7165 } |
| 7166 }); |
| 7167 }); |
| 7168 mo.observe(container, { |
| 7169 childList: true, |
| 7170 subtree: true |
| 7171 }); |
| 7172 return mo; |
| 7173 } |
| 7174 } |
| 7175 }); |
| 7176 }()); |
| 7177 Polymer.StyleProperties = function () { |
| 7178 'use strict'; |
| 7179 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7180 var matchesSelector = Polymer.DomApi.matchesSelector; |
| 7181 var styleUtil = Polymer.StyleUtil; |
| 7182 var styleTransformer = Polymer.StyleTransformer; |
| 7183 return { |
| 7184 decorateStyles: function (styles) { |
| 7185 var self = this, props = {}; |
| 7186 styleUtil.forRulesInStyles(styles, function (rule) { |
| 7187 self.decorateRule(rule); |
| 7188 self.collectPropertiesInCssText(rule.propertyInfo.cssText, props); |
| 7189 }); |
| 7190 var names = []; |
| 7191 for (var i in props) { |
| 7192 names.push(i); |
| 7193 } |
| 7194 return names; |
| 7195 }, |
| 7196 decorateRule: function (rule) { |
| 7197 if (rule.propertyInfo) { |
| 7198 return rule.propertyInfo; |
| 7199 } |
| 7200 var info = {}, properties = {}; |
| 7201 var hasProperties = this.collectProperties(rule, properties); |
| 7202 if (hasProperties) { |
| 7203 info.properties = properties; |
| 7204 rule.rules = null; |
| 7205 } |
| 7206 info.cssText = this.collectCssText(rule); |
| 7207 rule.propertyInfo = info; |
| 7208 return info; |
| 7209 }, |
| 7210 collectProperties: function (rule, properties) { |
| 7211 var info = rule.propertyInfo; |
| 7212 if (info) { |
| 7213 if (info.properties) { |
| 7214 Polymer.Base.mixin(properties, info.properties); |
| 7215 return true; |
| 7216 } |
| 7217 } else { |
| 7218 var m, rx = this.rx.VAR_ASSIGN; |
| 7219 var cssText = rule.parsedCssText; |
| 7220 var any; |
| 7221 while (m = rx.exec(cssText)) { |
| 7222 properties[m[1]] = (m[2] || m[3]).trim(); |
| 7223 any = true; |
| 7224 } |
| 7225 return any; |
| 7226 } |
| 7227 }, |
| 7228 collectCssText: function (rule) { |
| 7229 var customCssText = ''; |
| 7230 var cssText = rule.parsedCssText; |
| 7231 cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, '')
; |
| 7232 var parts = cssText.split(';'); |
| 7233 for (var i = 0, p; i < parts.length; i++) { |
| 7234 p = parts[i]; |
| 7235 if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) { |
| 7236 customCssText += p + ';\n'; |
| 7237 } |
| 7238 } |
| 7239 return customCssText; |
| 7240 }, |
| 7241 collectPropertiesInCssText: function (cssText, props) { |
| 7242 var m; |
| 7243 while (m = this.rx.VAR_CAPTURE.exec(cssText)) { |
| 7244 props[m[1]] = true; |
| 7245 var def = m[2]; |
| 7246 if (def && def.match(this.rx.IS_VAR)) { |
| 7247 props[def] = true; |
| 7248 } |
| 7249 } |
| 7250 }, |
| 7251 reify: function (props) { |
| 7252 var names = Object.getOwnPropertyNames(props); |
| 7253 for (var i = 0, n; i < names.length; i++) { |
| 7254 n = names[i]; |
| 7255 props[n] = this.valueForProperty(props[n], props); |
| 7256 } |
| 7257 }, |
| 7258 valueForProperty: function (property, props) { |
| 7259 if (property) { |
| 7260 if (property.indexOf(';') >= 0) { |
| 7261 property = this.valueForProperties(property, props); |
| 7262 } else { |
| 7263 var self = this; |
| 7264 var fn = function (all, prefix, value, fallback) { |
| 7265 var propertyValue = self.valueForProperty(props[value], props) || (props[fallbac
k] ? self.valueForProperty(props[fallback], props) : fallback); |
| 7266 return prefix + (propertyValue || ''); |
| 7267 }; |
| 7268 property = property.replace(this.rx.VAR_MATCH, fn); |
| 7269 } |
| 7270 } |
| 7271 return property && property.trim() || ''; |
| 7272 }, |
| 7273 valueForProperties: function (property, props) { |
| 7274 var parts = property.split(';'); |
| 7275 for (var i = 0, p, m; i < parts.length; i++) { |
| 7276 if (p = parts[i]) { |
| 7277 m = p.match(this.rx.MIXIN_MATCH); |
| 7278 if (m) { |
| 7279 p = this.valueForProperty(props[m[1]], props); |
| 7280 } else { |
| 7281 var pp = p.split(':'); |
| 7282 if (pp[1]) { |
| 7283 pp[1] = pp[1].trim(); |
| 7284 pp[1] = this.valueForProperty(pp[1], props) || pp[1]; |
| 7285 } |
| 7286 p = pp.join(':'); |
| 7287 } |
| 7288 parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || ''; |
| 7289 } |
| 7290 } |
| 7291 return parts.join(';'); |
| 7292 }, |
| 7293 applyProperties: function (rule, props) { |
| 7294 var output = ''; |
| 7295 if (!rule.propertyInfo) { |
| 7296 this.decorateRule(rule); |
| 7297 } |
| 7298 if (rule.propertyInfo.cssText) { |
| 7299 output = this.valueForProperties(rule.propertyInfo.cssText, props); |
| 7300 } |
| 7301 rule.cssText = output; |
| 7302 }, |
| 7303 propertyDataFromStyles: function (styles, element) { |
| 7304 var props = {}, self = this; |
| 7305 var o = [], i = 0; |
| 7306 styleUtil.forRulesInStyles(styles, function (rule) { |
| 7307 if (!rule.propertyInfo) { |
| 7308 self.decorateRule(rule); |
| 7309 } |
| 7310 if (element && rule.propertyInfo.properties && matchesSelector.call(element, rul
e.transformedSelector || rule.parsedSelector)) { |
| 7311 self.collectProperties(rule, props); |
| 7312 addToBitMask(i, o); |
| 7313 } |
| 7314 i++; |
| 7315 }); |
| 7316 return { |
| 7317 properties: props, |
| 7318 key: o |
| 7319 }; |
| 7320 }, |
| 7321 scopePropertiesFromStyles: function (styles) { |
| 7322 if (!styles._scopeStyleProperties) { |
| 7323 styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SC
OPE_SELECTORS); |
| 7324 } |
| 7325 return styles._scopeStyleProperties; |
| 7326 }, |
| 7327 hostPropertiesFromStyles: function (styles) { |
| 7328 if (!styles._hostStyleProperties) { |
| 7329 styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOS
T_SELECTORS); |
| 7330 } |
| 7331 return styles._hostStyleProperties; |
| 7332 }, |
| 7333 selectedPropertiesFromStyles: function (styles, selectors) { |
| 7334 var props = {}, self = this; |
| 7335 styleUtil.forRulesInStyles(styles, function (rule) { |
| 7336 if (!rule.propertyInfo) { |
| 7337 self.decorateRule(rule); |
| 7338 } |
| 7339 for (var i = 0; i < selectors.length; i++) { |
| 7340 if (rule.parsedSelector === selectors[i]) { |
| 7341 self.collectProperties(rule, props); |
| 7342 return; |
| 7343 } |
| 7344 } |
| 7345 }); |
| 7346 return props; |
| 7347 }, |
| 7348 transformStyles: function (element, properties, scopeSelector) { |
| 7349 var self = this; |
| 7350 var hostSelector = styleTransformer._calcHostScope(element.is, element.extends); |
| 7351 var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]'
: hostSelector; |
| 7352 var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFF
IX); |
| 7353 return styleTransformer.elementStyles(element, function (rule) { |
| 7354 self.applyProperties(rule, properties); |
| 7355 if (rule.cssText && !nativeShadow) { |
| 7356 self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeS
elector); |
| 7357 } |
| 7358 }); |
| 7359 }, |
| 7360 _scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) { |
| 7361 rule.transformedSelector = rule.transformedSelector || rule.selector; |
| 7362 var selector = rule.transformedSelector; |
| 7363 var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' :
'.' + scopeId; |
| 7364 var parts = selector.split(','); |
| 7365 for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) { |
| 7366 parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : sco
pe + ' ' + p; |
| 7367 } |
| 7368 rule.selector = parts.join(','); |
| 7369 }, |
| 7370 applyElementScopeSelector: function (element, selector, old, viaAttr) { |
| 7371 var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.cl
assName; |
| 7372 var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME +
' ' + selector; |
| 7373 if (c !== v) { |
| 7374 if (viaAttr) { |
| 7375 element.setAttribute(styleTransformer.SCOPE_NAME, v); |
| 7376 } else { |
| 7377 element.className = v; |
| 7378 } |
| 7379 } |
| 7380 }, |
| 7381 applyElementStyle: function (element, properties, selector, style) { |
| 7382 var cssText = style ? style.textContent || '' : this.transformStyles(element, pr
operties, selector); |
| 7383 var s = element._customStyle; |
| 7384 if (s && !nativeShadow && s !== style) { |
| 7385 s._useCount--; |
| 7386 if (s._useCount <= 0 && s.parentNode) { |
| 7387 s.parentNode.removeChild(s); |
| 7388 } |
| 7389 } |
| 7390 if (nativeShadow || (!style || !style.parentNode)) { |
| 7391 if (nativeShadow && element._customStyle) { |
| 7392 element._customStyle.textContent = cssText; |
| 7393 style = element._customStyle; |
| 7394 } else if (cssText) { |
| 7395 style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null
, element._scopeStyle); |
| 7396 } |
| 7397 } |
| 7398 if (style) { |
| 7399 style._useCount = style._useCount || 0; |
| 7400 if (element._customStyle != style) { |
| 7401 style._useCount++; |
| 7402 } |
| 7403 element._customStyle = style; |
| 7404 } |
| 7405 return style; |
| 7406 }, |
| 7407 mixinCustomStyle: function (props, customStyle) { |
| 7408 var v; |
| 7409 for (var i in customStyle) { |
| 7410 v = customStyle[i]; |
| 7411 if (v || v === 0) { |
| 7412 props[i] = v; |
| 7413 } |
| 7414 } |
| 7415 }, |
| 7416 rx: { |
| 7417 VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/
gi, |
| 7418 MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i, |
| 7419 VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\))
)[\s]*?\)/gi, |
| 7420 VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi, |
| 7421 IS_VAR: /^--/, |
| 7422 BRACKETED: /\{[^}]*\}/g, |
| 7423 HOST_PREFIX: '(?:^|[^.#[:])', |
| 7424 HOST_SUFFIX: '($|[.:[\\s>+~])' |
| 7425 }, |
| 7426 HOST_SELECTORS: [':host'], |
| 7427 SCOPE_SELECTORS: [':root'], |
| 7428 XSCOPE_NAME: 'x-scope' |
| 7429 }; |
| 7430 function addToBitMask(n, bits) { |
| 7431 var o = parseInt(n / 32); |
| 7432 var v = 1 << n % 32; |
| 7433 bits[o] = (bits[o] || 0) | v; |
| 7434 } |
| 7435 }(); |
| 7436 (function () { |
| 7437 Polymer.StyleCache = function () { |
| 7438 this.cache = {}; |
| 7439 }; |
| 7440 Polymer.StyleCache.prototype = { |
| 7441 MAX: 100, |
| 7442 store: function (is, data, keyValues, keyStyles) { |
| 7443 data.keyValues = keyValues; |
| 7444 data.styles = keyStyles; |
| 7445 var s$ = this.cache[is] = this.cache[is] || []; |
| 7446 s$.push(data); |
| 7447 if (s$.length > this.MAX) { |
| 7448 s$.shift(); |
| 7449 } |
| 7450 }, |
| 7451 retrieve: function (is, keyValues, keyStyles) { |
| 7452 var cache = this.cache[is]; |
| 7453 if (cache) { |
| 7454 for (var i = cache.length - 1, data; i >= 0; i--) { |
| 7455 data = cache[i]; |
| 7456 if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues))
{ |
| 7457 return data; |
| 7458 } |
| 7459 } |
| 7460 } |
| 7461 }, |
| 7462 clear: function () { |
| 7463 this.cache = {}; |
| 7464 }, |
| 7465 _objectsEqual: function (target, source) { |
| 7466 var t, s; |
| 7467 for (var i in target) { |
| 7468 t = target[i], s = source[i]; |
| 7469 if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s))
{ |
| 7470 return false; |
| 7471 } |
| 7472 } |
| 7473 if (Array.isArray(target)) { |
| 7474 return target.length === source.length; |
| 7475 } |
| 7476 return true; |
| 7477 }, |
| 7478 _objectsStrictlyEqual: function (target, source) { |
| 7479 return this._objectsEqual(target, source) && this._objectsEqual(source, target); |
| 7480 } |
| 7481 }; |
| 7482 }()); |
| 7483 Polymer.StyleDefaults = function () { |
| 7484 var styleProperties = Polymer.StyleProperties; |
| 7485 var styleUtil = Polymer.StyleUtil; |
| 7486 var StyleCache = Polymer.StyleCache; |
| 7487 var api = { |
| 7488 _styles: [], |
| 7489 _properties: null, |
| 7490 customStyle: {}, |
| 7491 _styleCache: new StyleCache(), |
| 7492 addStyle: function (style) { |
| 7493 this._styles.push(style); |
| 7494 this._properties = null; |
| 7495 }, |
| 7496 get _styleProperties() { |
| 7497 if (!this._properties) { |
| 7498 styleProperties.decorateStyles(this._styles); |
| 7499 this._styles._scopeStyleProperties = null; |
| 7500 this._properties = styleProperties.scopePropertiesFromStyles(this._styles); |
| 7501 styleProperties.mixinCustomStyle(this._properties, this.customStyle); |
| 7502 styleProperties.reify(this._properties); |
| 7503 } |
| 7504 return this._properties; |
| 7505 }, |
| 7506 _needsStyleProperties: function () { |
| 7507 }, |
| 7508 _computeStyleProperties: function () { |
| 7509 return this._styleProperties; |
| 7510 }, |
| 7511 updateStyles: function (properties) { |
| 7512 this._properties = null; |
| 7513 if (properties) { |
| 7514 Polymer.Base.mixin(this.customStyle, properties); |
| 7515 } |
| 7516 this._styleCache.clear(); |
| 7517 for (var i = 0, s; i < this._styles.length; i++) { |
| 7518 s = this._styles[i]; |
| 7519 s = s.__importElement || s; |
| 7520 s._apply(); |
| 7521 } |
| 7522 } |
| 7523 }; |
| 7524 return api; |
| 7525 }(); |
| 7526 (function () { |
| 7527 'use strict'; |
| 7528 var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute; |
| 7529 var propertyUtils = Polymer.StyleProperties; |
| 7530 var styleTransformer = Polymer.StyleTransformer; |
| 7531 var styleUtil = Polymer.StyleUtil; |
| 7532 var styleDefaults = Polymer.StyleDefaults; |
| 7533 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7534 Polymer.Base._addFeature({ |
| 7535 _prepStyleProperties: function () { |
| 7536 this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._
styles) : []; |
| 7537 }, |
| 7538 customStyle: {}, |
| 7539 _setupStyleProperties: function () { |
| 7540 this.customStyle = {}; |
| 7541 }, |
| 7542 _needsStyleProperties: function () { |
| 7543 return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length
); |
| 7544 }, |
| 7545 _beforeAttached: function () { |
| 7546 if (!this._scopeSelector && this._needsStyleProperties()) { |
| 7547 this._updateStyleProperties(); |
| 7548 } |
| 7549 }, |
| 7550 _findStyleHost: function () { |
| 7551 var e = this, root; |
| 7552 while (root = Polymer.dom(e).getOwnerRoot()) { |
| 7553 if (Polymer.isInstance(root.host)) { |
| 7554 return root.host; |
| 7555 } |
| 7556 e = root.host; |
| 7557 } |
| 7558 return styleDefaults; |
| 7559 }, |
| 7560 _updateStyleProperties: function () { |
| 7561 var info, scope = this._findStyleHost(); |
| 7562 if (!scope._styleCache) { |
| 7563 scope._styleCache = new Polymer.StyleCache(); |
| 7564 } |
| 7565 var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this); |
| 7566 scopeData.key.customStyle = this.customStyle; |
| 7567 info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles); |
| 7568 var scopeCached = Boolean(info); |
| 7569 if (scopeCached) { |
| 7570 this._styleProperties = info._styleProperties; |
| 7571 } else { |
| 7572 this._computeStyleProperties(scopeData.properties); |
| 7573 } |
| 7574 this._computeOwnStyleProperties(); |
| 7575 if (!scopeCached) { |
| 7576 info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles); |
| 7577 } |
| 7578 var globalCached = Boolean(info) && !scopeCached; |
| 7579 var style = this._applyStyleProperties(info); |
| 7580 if (!scopeCached) { |
| 7581 style = style && nativeShadow ? style.cloneNode(true) : style; |
| 7582 info = { |
| 7583 style: style, |
| 7584 _scopeSelector: this._scopeSelector, |
| 7585 _styleProperties: this._styleProperties |
| 7586 }; |
| 7587 scopeData.key.customStyle = {}; |
| 7588 this.mixin(scopeData.key.customStyle, this.customStyle); |
| 7589 scope._styleCache.store(this.is, info, scopeData.key, this._styles); |
| 7590 if (!globalCached) { |
| 7591 styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._s
tyles); |
| 7592 } |
| 7593 } |
| 7594 }, |
| 7595 _computeStyleProperties: function (scopeProps) { |
| 7596 var scope = this._findStyleHost(); |
| 7597 if (!scope._styleProperties) { |
| 7598 scope._computeStyleProperties(); |
| 7599 } |
| 7600 var props = Object.create(scope._styleProperties); |
| 7601 this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles)); |
| 7602 scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, t
his).properties; |
| 7603 this.mixin(props, scopeProps); |
| 7604 this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles)); |
| 7605 propertyUtils.mixinCustomStyle(props, this.customStyle); |
| 7606 propertyUtils.reify(props); |
| 7607 this._styleProperties = props; |
| 7608 }, |
| 7609 _computeOwnStyleProperties: function () { |
| 7610 var props = {}; |
| 7611 for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) { |
| 7612 n = this._ownStylePropertyNames[i]; |
| 7613 props[n] = this._styleProperties[n]; |
| 7614 } |
| 7615 this._ownStyleProperties = props; |
| 7616 }, |
| 7617 _scopeCount: 0, |
| 7618 _applyStyleProperties: function (info) { |
| 7619 var oldScopeSelector = this._scopeSelector; |
| 7620 this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto_
_._scopeCount++; |
| 7621 var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._s
copeSelector, info && info.style); |
| 7622 if (!nativeShadow) { |
| 7623 propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelec
tor, this._scopeCssViaAttr); |
| 7624 } |
| 7625 return style; |
| 7626 }, |
| 7627 serializeValueToAttribute: function (value, attribute, node) { |
| 7628 node = node || this; |
| 7629 if (attribute === 'class' && !nativeShadow) { |
| 7630 var host = node === this ? this.domHost || this.dataHost : this; |
| 7631 if (host) { |
| 7632 value = host._scopeElementClass(node, value); |
| 7633 } |
| 7634 } |
| 7635 node = Polymer.dom(node); |
| 7636 serializeValueToAttribute.call(this, value, attribute, node); |
| 7637 }, |
| 7638 _scopeElementClass: function (element, selector) { |
| 7639 if (!nativeShadow && !this._scopeCssViaAttr) { |
| 7640 selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scope
Selector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : ''); |
| 7641 } |
| 7642 return selector; |
| 7643 }, |
| 7644 updateStyles: function (properties) { |
| 7645 if (this.isAttached) { |
| 7646 if (properties) { |
| 7647 this.mixin(this.customStyle, properties); |
| 7648 } |
| 7649 if (this._needsStyleProperties()) { |
| 7650 this._updateStyleProperties(); |
| 7651 } else { |
| 7652 this._styleProperties = null; |
| 7653 } |
| 7654 if (this._styleCache) { |
| 7655 this._styleCache.clear(); |
| 7656 } |
| 7657 this._updateRootStyles(); |
| 7658 } |
| 7659 }, |
| 7660 _updateRootStyles: function (root) { |
| 7661 root = root || this.root; |
| 7662 var c$ = Polymer.dom(root)._query(function (e) { |
| 7663 return e.shadyRoot || e.shadowRoot; |
| 7664 }); |
| 7665 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { |
| 7666 if (c.updateStyles) { |
| 7667 c.updateStyles(); |
| 7668 } |
| 7669 } |
| 7670 } |
| 7671 }); |
| 7672 Polymer.updateStyles = function (properties) { |
| 7673 styleDefaults.updateStyles(properties); |
| 7674 Polymer.Base._updateRootStyles(document); |
| 7675 }; |
| 7676 var styleCache = new Polymer.StyleCache(); |
| 7677 Polymer.customStyleCache = styleCache; |
| 7678 var SCOPE_NAME = styleTransformer.SCOPE_NAME; |
| 7679 var XSCOPE_NAME = propertyUtils.XSCOPE_NAME; |
| 7680 }()); |
| 7681 Polymer.Base._addFeature({ |
| 7682 _registerFeatures: function () { |
| 7683 this._prepIs(); |
| 7684 this._prepAttributes(); |
| 7685 this._prepConstructor(); |
| 7686 this._prepTemplate(); |
| 7687 this._prepStyles(); |
| 7688 this._prepStyleProperties(); |
| 7689 this._prepAnnotations(); |
| 7690 this._prepEffects(); |
| 7691 this._prepBehaviors(); |
| 7692 this._prepBindings(); |
| 7693 this._prepShady(); |
| 7694 }, |
| 7695 _prepBehavior: function (b) { |
| 7696 this._addPropertyEffects(b.properties); |
| 7697 this._addComplexObserverEffects(b.observers); |
| 7698 this._addHostAttributes(b.hostAttributes); |
| 7699 }, |
| 7700 _initFeatures: function () { |
| 7701 this._poolContent(); |
| 7702 this._setupConfigure(); |
| 7703 this._setupStyleProperties(); |
| 7704 this._pushHost(); |
| 7705 this._stampTemplate(); |
| 7706 this._popHost(); |
| 7707 this._marshalAnnotationReferences(); |
| 7708 this._setupDebouncers(); |
| 7709 this._marshalInstanceEffects(); |
| 7710 this._marshalHostAttributes(); |
| 7711 this._marshalBehaviors(); |
| 7712 this._marshalAttributes(); |
| 7713 this._tryReady(); |
| 7714 }, |
| 7715 _marshalBehavior: function (b) { |
| 7716 this._listenListeners(b.listeners); |
| 7717 } |
| 7718 }); |
| 7719 (function () { |
| 7720 var nativeShadow = Polymer.Settings.useNativeShadow; |
| 7721 var propertyUtils = Polymer.StyleProperties; |
| 7722 var styleUtil = Polymer.StyleUtil; |
| 7723 var cssParse = Polymer.CssParse; |
| 7724 var styleDefaults = Polymer.StyleDefaults; |
| 7725 var styleTransformer = Polymer.StyleTransformer; |
| 7726 Polymer({ |
| 7727 is: 'custom-style', |
| 7728 extends: 'style', |
| 7729 properties: { include: String }, |
| 7730 ready: function () { |
| 7731 this._tryApply(); |
| 7732 }, |
| 7733 attached: function () { |
| 7734 this._tryApply(); |
| 7735 }, |
| 7736 _tryApply: function () { |
| 7737 if (!this._appliesToDocument) { |
| 7738 if (this.parentNode && this.parentNode.localName !== 'dom-module') { |
| 7739 this._appliesToDocument = true; |
| 7740 var e = this.__appliedElement || this; |
| 7741 styleDefaults.addStyle(e); |
| 7742 if (e.textContent || this.include) { |
| 7743 this._apply(); |
| 7744 } else { |
| 7745 var observer = new MutationObserver(function () { |
| 7746 observer.disconnect(); |
| 7747 this._apply(); |
| 7748 }.bind(this)); |
| 7749 observer.observe(e, { childList: true }); |
| 7750 } |
| 7751 } |
| 7752 } |
| 7753 }, |
| 7754 _apply: function () { |
| 7755 var e = this.__appliedElement || this; |
| 7756 if (this.include) { |
| 7757 e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent; |
| 7758 } |
| 7759 if (e.textContent) { |
| 7760 styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) { |
| 7761 styleTransformer.documentRule(rule); |
| 7762 }); |
| 7763 this._applyCustomProperties(e); |
| 7764 } |
| 7765 }, |
| 7766 _applyCustomProperties: function (element) { |
| 7767 this._computeStyleProperties(); |
| 7768 var props = this._styleProperties; |
| 7769 var rules = styleUtil.rulesForStyle(element); |
| 7770 element.textContent = styleUtil.toCssText(rules, function (rule) { |
| 7771 var css = rule.cssText = rule.parsedCssText; |
| 7772 if (rule.propertyInfo && rule.propertyInfo.cssText) { |
| 7773 css = cssParse.removeCustomPropAssignment(css); |
| 7774 rule.cssText = propertyUtils.valueForProperties(css, props); |
| 7775 } |
| 7776 }); |
| 7777 } |
| 7778 }); |
| 7779 }()); |
| 7780 Polymer.Templatizer = { |
| 7781 properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } }, |
| 7782 _instanceProps: Polymer.nob, |
| 7783 _parentPropPrefix: '_parent_', |
| 7784 templatize: function (template) { |
| 7785 if (!template._content) { |
| 7786 template._content = template.content; |
| 7787 } |
| 7788 if (template._content._ctor) { |
| 7789 this.ctor = template._content._ctor; |
| 7790 this._prepParentProperties(this.ctor.prototype, template); |
| 7791 return; |
| 7792 } |
| 7793 var archetype = Object.create(Polymer.Base); |
| 7794 this._customPrepAnnotations(archetype, template); |
| 7795 archetype._prepEffects(); |
| 7796 this._customPrepEffects(archetype); |
| 7797 archetype._prepBehaviors(); |
| 7798 archetype._prepBindings(); |
| 7799 this._prepParentProperties(archetype, template); |
| 7800 archetype._notifyPath = this._notifyPathImpl; |
| 7801 archetype._scopeElementClass = this._scopeElementClassImpl; |
| 7802 archetype.listen = this._listenImpl; |
| 7803 archetype._showHideChildren = this._showHideChildrenImpl; |
| 7804 var _constructor = this._constructorImpl; |
| 7805 var ctor = function TemplateInstance(model, host) { |
| 7806 _constructor.call(this, model, host); |
| 7807 }; |
| 7808 ctor.prototype = archetype; |
| 7809 archetype.constructor = ctor; |
| 7810 template._content._ctor = ctor; |
| 7811 this.ctor = ctor; |
| 7812 }, |
| 7813 _getRootDataHost: function () { |
| 7814 return this.dataHost && this.dataHost._rootDataHost || this.dataHost; |
| 7815 }, |
| 7816 _showHideChildrenImpl: function (hide) { |
| 7817 var c = this._children; |
| 7818 for (var i = 0; i < c.length; i++) { |
| 7819 var n = c[i]; |
| 7820 if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) { |
| 7821 if (n.nodeType === Node.TEXT_NODE) { |
| 7822 if (hide) { |
| 7823 n.__polymerTextContent__ = n.textContent; |
| 7824 n.textContent = ''; |
| 7825 } else { |
| 7826 n.textContent = n.__polymerTextContent__; |
| 7827 } |
| 7828 } else if (n.style) { |
| 7829 if (hide) { |
| 7830 n.__polymerDisplay__ = n.style.display; |
| 7831 n.style.display = 'none'; |
| 7832 } else { |
| 7833 n.style.display = n.__polymerDisplay__; |
| 7834 } |
| 7835 } |
| 7836 } |
| 7837 n.__hideTemplateChildren__ = hide; |
| 7838 } |
| 7839 }, |
| 7840 _debounceTemplate: function (fn) { |
| 7841 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn)); |
| 7842 }, |
| 7843 _flushTemplates: function (debouncerExpired) { |
| 7844 Polymer.dom.flush(); |
| 7845 }, |
| 7846 _customPrepEffects: function (archetype) { |
| 7847 var parentProps = archetype._parentProps; |
| 7848 for (var prop in parentProps) { |
| 7849 archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop
)); |
| 7850 } |
| 7851 for (var prop in this._instanceProps) { |
| 7852 archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector(
prop)); |
| 7853 } |
| 7854 }, |
| 7855 _customPrepAnnotations: function (archetype, template) { |
| 7856 archetype._template = template; |
| 7857 var c = template._content; |
| 7858 if (!c._notes) { |
| 7859 var rootDataHost = archetype._rootDataHost; |
| 7860 if (rootDataHost) { |
| 7861 Polymer.Annotations.prepElement = rootDataHost._prepElement.bind(rootDataHost); |
| 7862 } |
| 7863 c._notes = Polymer.Annotations.parseAnnotations(template); |
| 7864 Polymer.Annotations.prepElement = null; |
| 7865 this._processAnnotations(c._notes); |
| 7866 } |
| 7867 archetype._notes = c._notes; |
| 7868 archetype._parentProps = c._parentProps; |
| 7869 }, |
| 7870 _prepParentProperties: function (archetype, template) { |
| 7871 var parentProps = this._parentProps = archetype._parentProps; |
| 7872 if (this._forwardParentProp && parentProps) { |
| 7873 var proto = archetype._parentPropProto; |
| 7874 var prop; |
| 7875 if (!proto) { |
| 7876 for (prop in this._instanceProps) { |
| 7877 delete parentProps[prop]; |
| 7878 } |
| 7879 proto = archetype._parentPropProto = Object.create(null); |
| 7880 if (template != this) { |
| 7881 Polymer.Bind.prepareModel(proto); |
| 7882 } |
| 7883 for (prop in parentProps) { |
| 7884 var parentProp = this._parentPropPrefix + prop; |
| 7885 var effects = [ |
| 7886 { |
| 7887 kind: 'function', |
| 7888 effect: this._createForwardPropEffector(prop) |
| 7889 }, |
| 7890 { kind: 'notify' } |
| 7891 ]; |
| 7892 Polymer.Bind._createAccessors(proto, parentProp, effects); |
| 7893 } |
| 7894 } |
| 7895 if (template != this) { |
| 7896 Polymer.Bind.prepareInstance(template); |
| 7897 template._forwardParentProp = this._forwardParentProp.bind(this); |
| 7898 } |
| 7899 this._extendTemplate(template, proto); |
| 7900 } |
| 7901 }, |
| 7902 _createForwardPropEffector: function (prop) { |
| 7903 return function (source, value) { |
| 7904 this._forwardParentProp(prop, value); |
| 7905 }; |
| 7906 }, |
| 7907 _createHostPropEffector: function (prop) { |
| 7908 var prefix = this._parentPropPrefix; |
| 7909 return function (source, value) { |
| 7910 this.dataHost[prefix + prop] = value; |
| 7911 }; |
| 7912 }, |
| 7913 _createInstancePropEffector: function (prop) { |
| 7914 return function (source, value, old, fromAbove) { |
| 7915 if (!fromAbove) { |
| 7916 this.dataHost._forwardInstanceProp(this, prop, value); |
| 7917 } |
| 7918 }; |
| 7919 }, |
| 7920 _extendTemplate: function (template, proto) { |
| 7921 Object.getOwnPropertyNames(proto).forEach(function (n) { |
| 7922 var val = template[n]; |
| 7923 var pd = Object.getOwnPropertyDescriptor(proto, n); |
| 7924 Object.defineProperty(template, n, pd); |
| 7925 if (val !== undefined) { |
| 7926 template._propertySetter(n, val); |
| 7927 } |
| 7928 }); |
| 7929 }, |
| 7930 _showHideChildren: function (hidden) { |
| 7931 }, |
| 7932 _forwardInstancePath: function (inst, path, value) { |
| 7933 }, |
| 7934 _forwardInstanceProp: function (inst, prop, value) { |
| 7935 }, |
| 7936 _notifyPathImpl: function (path, value) { |
| 7937 var dataHost = this.dataHost; |
| 7938 var dot = path.indexOf('.'); |
| 7939 var root = dot < 0 ? path : path.slice(0, dot); |
| 7940 dataHost._forwardInstancePath.call(dataHost, this, path, value); |
| 7941 if (root in dataHost._parentProps) { |
| 7942 dataHost.notifyPath(dataHost._parentPropPrefix + path, value); |
| 7943 } |
| 7944 }, |
| 7945 _pathEffector: function (path, value, fromAbove) { |
| 7946 if (this._forwardParentPath) { |
| 7947 if (path.indexOf(this._parentPropPrefix) === 0) { |
| 7948 this._forwardParentPath(path.substring(8), value); |
| 7949 } |
| 7950 } |
| 7951 Polymer.Base._pathEffector.apply(this, arguments); |
| 7952 }, |
| 7953 _constructorImpl: function (model, host) { |
| 7954 this._rootDataHost = host._getRootDataHost(); |
| 7955 this._setupConfigure(model); |
| 7956 this._pushHost(host); |
| 7957 this.root = this.instanceTemplate(this._template); |
| 7958 this.root.__noContent = !this._notes._hasContent; |
| 7959 this.root.__styleScoped = true; |
| 7960 this._popHost(); |
| 7961 this._marshalAnnotatedNodes(); |
| 7962 this._marshalInstanceEffects(); |
| 7963 this._marshalAnnotatedListeners(); |
| 7964 var children = []; |
| 7965 for (var n = this.root.firstChild; n; n = n.nextSibling) { |
| 7966 children.push(n); |
| 7967 n._templateInstance = this; |
| 7968 } |
| 7969 this._children = children; |
| 7970 if (host.__hideTemplateChildren__) { |
| 7971 this._showHideChildren(true); |
| 7972 } |
| 7973 this._tryReady(); |
| 7974 }, |
| 7975 _listenImpl: function (node, eventName, methodName) { |
| 7976 var model = this; |
| 7977 var host = this._rootDataHost; |
| 7978 var handler = host._createEventHandler(node, eventName, methodName); |
| 7979 var decorated = function (e) { |
| 7980 e.model = model; |
| 7981 handler(e); |
| 7982 }; |
| 7983 host._listen(node, eventName, decorated); |
| 7984 }, |
| 7985 _scopeElementClassImpl: function (node, value) { |
| 7986 var host = this._rootDataHost; |
| 7987 if (host) { |
| 7988 return host._scopeElementClass(node, value); |
| 7989 } |
| 7990 }, |
| 7991 stamp: function (model) { |
| 7992 model = model || {}; |
| 7993 if (this._parentProps) { |
| 7994 for (var prop in this._parentProps) { |
| 7995 model[prop] = this[this._parentPropPrefix + prop]; |
| 7996 } |
| 7997 } |
| 7998 return new this.ctor(model, this); |
| 7999 }, |
| 8000 modelForElement: function (el) { |
| 8001 var model; |
| 8002 while (el) { |
| 8003 if (model = el._templateInstance) { |
| 8004 if (model.dataHost != this) { |
| 8005 el = model.dataHost; |
| 8006 } else { |
| 8007 return model; |
| 8008 } |
| 8009 } else { |
| 8010 el = el.parentNode; |
| 8011 } |
| 8012 } |
| 8013 } |
| 8014 }; |
| 8015 Polymer({ |
| 8016 is: 'dom-template', |
| 8017 extends: 'template', |
| 8018 behaviors: [Polymer.Templatizer], |
| 8019 ready: function () { |
| 8020 this.templatize(this); |
| 8021 } |
| 8022 }); |
| 8023 Polymer._collections = new WeakMap(); |
| 8024 Polymer.Collection = function (userArray) { |
| 8025 Polymer._collections.set(userArray, this); |
| 8026 this.userArray = userArray; |
| 8027 this.store = userArray.slice(); |
| 8028 this.initMap(); |
| 8029 }; |
| 8030 Polymer.Collection.prototype = { |
| 8031 constructor: Polymer.Collection, |
| 8032 initMap: function () { |
| 8033 var omap = this.omap = new WeakMap(); |
| 8034 var pmap = this.pmap = {}; |
| 8035 var s = this.store; |
| 8036 for (var i = 0; i < s.length; i++) { |
| 8037 var item = s[i]; |
| 8038 if (item && typeof item == 'object') { |
| 8039 omap.set(item, i); |
| 8040 } else { |
| 8041 pmap[item] = i; |
| 8042 } |
| 8043 } |
| 8044 }, |
| 8045 add: function (item) { |
| 8046 var key = this.store.push(item) - 1; |
| 8047 if (item && typeof item == 'object') { |
| 8048 this.omap.set(item, key); |
| 8049 } else { |
| 8050 this.pmap[item] = key; |
| 8051 } |
| 8052 return key; |
| 8053 }, |
| 8054 removeKey: function (key) { |
| 8055 this._removeFromMap(this.store[key]); |
| 8056 delete this.store[key]; |
| 8057 }, |
| 8058 _removeFromMap: function (item) { |
| 8059 if (item && typeof item == 'object') { |
| 8060 this.omap.delete(item); |
| 8061 } else { |
| 8062 delete this.pmap[item]; |
| 8063 } |
| 8064 }, |
| 8065 remove: function (item) { |
| 8066 var key = this.getKey(item); |
| 8067 this.removeKey(key); |
| 8068 return key; |
| 8069 }, |
| 8070 getKey: function (item) { |
| 8071 if (item && typeof item == 'object') { |
| 8072 return this.omap.get(item); |
| 8073 } else { |
| 8074 return this.pmap[item]; |
| 8075 } |
| 8076 }, |
| 8077 getKeys: function () { |
| 8078 return Object.keys(this.store); |
| 8079 }, |
| 8080 setItem: function (key, item) { |
| 8081 var old = this.store[key]; |
| 8082 if (old) { |
| 8083 this._removeFromMap(old); |
| 8084 } |
| 8085 if (item && typeof item == 'object') { |
| 8086 this.omap.set(item, key); |
| 8087 } else { |
| 8088 this.pmap[item] = key; |
| 8089 } |
| 8090 this.store[key] = item; |
| 8091 }, |
| 8092 getItem: function (key) { |
| 8093 return this.store[key]; |
| 8094 }, |
| 8095 getItems: function () { |
| 8096 var items = [], store = this.store; |
| 8097 for (var key in store) { |
| 8098 items.push(store[key]); |
| 8099 } |
| 8100 return items; |
| 8101 }, |
| 8102 _applySplices: function (splices) { |
| 8103 var keyMap = {}, key, i; |
| 8104 splices.forEach(function (s) { |
| 8105 s.addedKeys = []; |
| 8106 for (i = 0; i < s.removed.length; i++) { |
| 8107 key = this.getKey(s.removed[i]); |
| 8108 keyMap[key] = keyMap[key] ? null : -1; |
| 8109 } |
| 8110 for (i = 0; i < s.addedCount; i++) { |
| 8111 var item = this.userArray[s.index + i]; |
| 8112 key = this.getKey(item); |
| 8113 key = key === undefined ? this.add(item) : key; |
| 8114 keyMap[key] = keyMap[key] ? null : 1; |
| 8115 s.addedKeys.push(key); |
| 8116 } |
| 8117 }, this); |
| 8118 var removed = []; |
| 8119 var added = []; |
| 8120 for (var key in keyMap) { |
| 8121 if (keyMap[key] < 0) { |
| 8122 this.removeKey(key); |
| 8123 removed.push(key); |
| 8124 } |
| 8125 if (keyMap[key] > 0) { |
| 8126 added.push(key); |
| 8127 } |
| 8128 } |
| 8129 return [{ |
| 8130 removed: removed, |
| 8131 added: added |
| 8132 }]; |
| 8133 } |
| 8134 }; |
| 8135 Polymer.Collection.get = function (userArray) { |
| 8136 return Polymer._collections.get(userArray) || new Polymer.Collection(userArray); |
| 8137 }; |
| 8138 Polymer.Collection.applySplices = function (userArray, splices) { |
| 8139 var coll = Polymer._collections.get(userArray); |
| 8140 return coll ? coll._applySplices(splices) : null; |
| 8141 }; |
| 8142 Polymer({ |
| 8143 is: 'dom-repeat', |
| 8144 extends: 'template', |
| 8145 properties: { |
| 8146 items: { type: Array }, |
| 8147 as: { |
| 8148 type: String, |
| 8149 value: 'item' |
| 8150 }, |
| 8151 indexAs: { |
| 8152 type: String, |
| 8153 value: 'index' |
| 8154 }, |
| 8155 sort: { |
| 8156 type: Function, |
| 8157 observer: '_sortChanged' |
| 8158 }, |
| 8159 filter: { |
| 8160 type: Function, |
| 8161 observer: '_filterChanged' |
| 8162 }, |
| 8163 observe: { |
| 8164 type: String, |
| 8165 observer: '_observeChanged' |
| 8166 }, |
| 8167 delay: Number |
| 8168 }, |
| 8169 behaviors: [Polymer.Templatizer], |
| 8170 observers: ['_itemsChanged(items.*)'], |
| 8171 created: function () { |
| 8172 this._instances = []; |
| 8173 }, |
| 8174 detached: function () { |
| 8175 for (var i = 0; i < this._instances.length; i++) { |
| 8176 this._detachRow(i); |
| 8177 } |
| 8178 }, |
| 8179 attached: function () { |
| 8180 var parentNode = Polymer.dom(this).parentNode; |
| 8181 for (var i = 0; i < this._instances.length; i++) { |
| 8182 Polymer.dom(parentNode).insertBefore(this._instances[i].root, this); |
| 8183 } |
| 8184 }, |
| 8185 ready: function () { |
| 8186 this._instanceProps = { __key__: true }; |
| 8187 this._instanceProps[this.as] = true; |
| 8188 this._instanceProps[this.indexAs] = true; |
| 8189 if (!this.ctor) { |
| 8190 this.templatize(this); |
| 8191 } |
| 8192 }, |
| 8193 _sortChanged: function () { |
| 8194 var dataHost = this._getRootDataHost(); |
| 8195 var sort = this.sort; |
| 8196 this._sortFn = sort && (typeof sort == 'function' ? sort : function () { |
| 8197 return dataHost[sort].apply(dataHost, arguments); |
| 8198 }); |
| 8199 this._needFullRefresh = true; |
| 8200 if (this.items) { |
| 8201 this._debounceTemplate(this._render); |
| 8202 } |
| 8203 }, |
| 8204 _filterChanged: function () { |
| 8205 var dataHost = this._getRootDataHost(); |
| 8206 var filter = this.filter; |
| 8207 this._filterFn = filter && (typeof filter == 'function' ? filter : function () { |
| 8208 return dataHost[filter].apply(dataHost, arguments); |
| 8209 }); |
| 8210 this._needFullRefresh = true; |
| 8211 if (this.items) { |
| 8212 this._debounceTemplate(this._render); |
| 8213 } |
| 8214 }, |
| 8215 _observeChanged: function () { |
| 8216 this._observePaths = this.observe && this.observe.replace('.*', '.').split(' '); |
| 8217 }, |
| 8218 _itemsChanged: function (change) { |
| 8219 if (change.path == 'items') { |
| 8220 if (Array.isArray(this.items)) { |
| 8221 this.collection = Polymer.Collection.get(this.items); |
| 8222 } else if (!this.items) { |
| 8223 this.collection = null; |
| 8224 } else { |
| 8225 this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', t
his.items)); |
| 8226 } |
| 8227 this._keySplices = []; |
| 8228 this._indexSplices = []; |
| 8229 this._needFullRefresh = true; |
| 8230 this._debounceTemplate(this._render); |
| 8231 } else if (change.path == 'items.splices') { |
| 8232 this._keySplices = this._keySplices.concat(change.value.keySplices); |
| 8233 this._indexSplices = this._indexSplices.concat(change.value.indexSplices); |
| 8234 this._debounceTemplate(this._render); |
| 8235 } else { |
| 8236 var subpath = change.path.slice(6); |
| 8237 this._forwardItemPath(subpath, change.value); |
| 8238 this._checkObservedPaths(subpath); |
| 8239 } |
| 8240 }, |
| 8241 _checkObservedPaths: function (path) { |
| 8242 if (this._observePaths) { |
| 8243 path = path.substring(path.indexOf('.') + 1); |
| 8244 var paths = this._observePaths; |
| 8245 for (var i = 0; i < paths.length; i++) { |
| 8246 if (path.indexOf(paths[i]) === 0) { |
| 8247 this._needFullRefresh = true; |
| 8248 if (this.delay) { |
| 8249 this.debounce('render', this._render, this.delay); |
| 8250 } else { |
| 8251 this._debounceTemplate(this._render); |
| 8252 } |
| 8253 return; |
| 8254 } |
| 8255 } |
| 8256 } |
| 8257 }, |
| 8258 render: function () { |
| 8259 this._needFullRefresh = true; |
| 8260 this._debounceTemplate(this._render); |
| 8261 this._flushTemplates(); |
| 8262 }, |
| 8263 _render: function () { |
| 8264 var c = this.collection; |
| 8265 if (this._needFullRefresh) { |
| 8266 this._applyFullRefresh(); |
| 8267 this._needFullRefresh = false; |
| 8268 } else { |
| 8269 if (this._sortFn) { |
| 8270 this._applySplicesUserSort(this._keySplices); |
| 8271 } else { |
| 8272 if (this._filterFn) { |
| 8273 this._applyFullRefresh(); |
| 8274 } else { |
| 8275 this._applySplicesArrayOrder(this._indexSplices); |
| 8276 } |
| 8277 } |
| 8278 } |
| 8279 this._keySplices = []; |
| 8280 this._indexSplices = []; |
| 8281 var keyToIdx = this._keyToInstIdx = {}; |
| 8282 for (var i = 0; i < this._instances.length; i++) { |
| 8283 var inst = this._instances[i]; |
| 8284 keyToIdx[inst.__key__] = i; |
| 8285 inst.__setProperty(this.indexAs, i, true); |
| 8286 } |
| 8287 this.fire('dom-change'); |
| 8288 }, |
| 8289 _applyFullRefresh: function () { |
| 8290 var c = this.collection; |
| 8291 var keys; |
| 8292 if (this._sortFn) { |
| 8293 keys = c ? c.getKeys() : []; |
| 8294 } else { |
| 8295 keys = []; |
| 8296 var items = this.items; |
| 8297 if (items) { |
| 8298 for (var i = 0; i < items.length; i++) { |
| 8299 keys.push(c.getKey(items[i])); |
| 8300 } |
| 8301 } |
| 8302 } |
| 8303 if (this._filterFn) { |
| 8304 keys = keys.filter(function (a) { |
| 8305 return this._filterFn(c.getItem(a)); |
| 8306 }, this); |
| 8307 } |
| 8308 if (this._sortFn) { |
| 8309 keys.sort(function (a, b) { |
| 8310 return this._sortFn(c.getItem(a), c.getItem(b)); |
| 8311 }.bind(this)); |
| 8312 } |
| 8313 for (var i = 0; i < keys.length; i++) { |
| 8314 var key = keys[i]; |
| 8315 var inst = this._instances[i]; |
| 8316 if (inst) { |
| 8317 inst.__setProperty('__key__', key, true); |
| 8318 inst.__setProperty(this.as, c.getItem(key), true); |
| 8319 } else { |
| 8320 this._instances.push(this._insertRow(i, key)); |
| 8321 } |
| 8322 } |
| 8323 for (; i < this._instances.length; i++) { |
| 8324 this._detachRow(i); |
| 8325 } |
| 8326 this._instances.splice(keys.length, this._instances.length - keys.length); |
| 8327 }, |
| 8328 _keySort: function (a, b) { |
| 8329 return this.collection.getKey(a) - this.collection.getKey(b); |
| 8330 }, |
| 8331 _numericSort: function (a, b) { |
| 8332 return a - b; |
| 8333 }, |
| 8334 _applySplicesUserSort: function (splices) { |
| 8335 var c = this.collection; |
| 8336 var instances = this._instances; |
| 8337 var keyMap = {}; |
| 8338 var pool = []; |
| 8339 var sortFn = this._sortFn || this._keySort.bind(this); |
| 8340 splices.forEach(function (s) { |
| 8341 for (var i = 0; i < s.removed.length; i++) { |
| 8342 var key = s.removed[i]; |
| 8343 keyMap[key] = keyMap[key] ? null : -1; |
| 8344 } |
| 8345 for (var i = 0; i < s.added.length; i++) { |
| 8346 var key = s.added[i]; |
| 8347 keyMap[key] = keyMap[key] ? null : 1; |
| 8348 } |
| 8349 }, this); |
| 8350 var removedIdxs = []; |
| 8351 var addedKeys = []; |
| 8352 for (var key in keyMap) { |
| 8353 if (keyMap[key] === -1) { |
| 8354 removedIdxs.push(this._keyToInstIdx[key]); |
| 8355 } |
| 8356 if (keyMap[key] === 1) { |
| 8357 addedKeys.push(key); |
| 8358 } |
| 8359 } |
| 8360 if (removedIdxs.length) { |
| 8361 removedIdxs.sort(this._numericSort); |
| 8362 for (var i = removedIdxs.length - 1; i >= 0; i--) { |
| 8363 var idx = removedIdxs[i]; |
| 8364 if (idx !== undefined) { |
| 8365 pool.push(this._detachRow(idx)); |
| 8366 instances.splice(idx, 1); |
| 8367 } |
| 8368 } |
| 8369 } |
| 8370 if (addedKeys.length) { |
| 8371 if (this._filterFn) { |
| 8372 addedKeys = addedKeys.filter(function (a) { |
| 8373 return this._filterFn(c.getItem(a)); |
| 8374 }, this); |
| 8375 } |
| 8376 addedKeys.sort(function (a, b) { |
| 8377 return this._sortFn(c.getItem(a), c.getItem(b)); |
| 8378 }.bind(this)); |
| 8379 var start = 0; |
| 8380 for (var i = 0; i < addedKeys.length; i++) { |
| 8381 start = this._insertRowUserSort(start, addedKeys[i], pool); |
| 8382 } |
| 8383 } |
| 8384 }, |
| 8385 _insertRowUserSort: function (start, key, pool) { |
| 8386 var c = this.collection; |
| 8387 var item = c.getItem(key); |
| 8388 var end = this._instances.length - 1; |
| 8389 var idx = -1; |
| 8390 var sortFn = this._sortFn || this._keySort.bind(this); |
| 8391 while (start <= end) { |
| 8392 var mid = start + end >> 1; |
| 8393 var midKey = this._instances[mid].__key__; |
| 8394 var cmp = sortFn(c.getItem(midKey), item); |
| 8395 if (cmp < 0) { |
| 8396 start = mid + 1; |
| 8397 } else if (cmp > 0) { |
| 8398 end = mid - 1; |
| 8399 } else { |
| 8400 idx = mid; |
| 8401 break; |
| 8402 } |
| 8403 } |
| 8404 if (idx < 0) { |
| 8405 idx = end + 1; |
| 8406 } |
| 8407 this._instances.splice(idx, 0, this._insertRow(idx, key, pool)); |
| 8408 return idx; |
| 8409 }, |
| 8410 _applySplicesArrayOrder: function (splices) { |
| 8411 var pool = []; |
| 8412 var c = this.collection; |
| 8413 splices.forEach(function (s) { |
| 8414 for (var i = 0; i < s.removed.length; i++) { |
| 8415 var inst = this._detachRow(s.index + i); |
| 8416 if (!inst.isPlaceholder) { |
| 8417 pool.push(inst); |
| 8418 } |
| 8419 } |
| 8420 this._instances.splice(s.index, s.removed.length); |
| 8421 for (var i = 0; i < s.addedKeys.length; i++) { |
| 8422 var inst = { |
| 8423 isPlaceholder: true, |
| 8424 key: s.addedKeys[i] |
| 8425 }; |
| 8426 this._instances.splice(s.index + i, 0, inst); |
| 8427 } |
| 8428 }, this); |
| 8429 for (var i = this._instances.length - 1; i >= 0; i--) { |
| 8430 var inst = this._instances[i]; |
| 8431 if (inst.isPlaceholder) { |
| 8432 this._instances[i] = this._insertRow(i, inst.key, pool, true); |
| 8433 } |
| 8434 } |
| 8435 }, |
| 8436 _detachRow: function (idx) { |
| 8437 var inst = this._instances[idx]; |
| 8438 if (!inst.isPlaceholder) { |
| 8439 var parentNode = Polymer.dom(this).parentNode; |
| 8440 for (var i = 0; i < inst._children.length; i++) { |
| 8441 var el = inst._children[i]; |
| 8442 Polymer.dom(inst.root).appendChild(el); |
| 8443 } |
| 8444 } |
| 8445 return inst; |
| 8446 }, |
| 8447 _insertRow: function (idx, key, pool, replace) { |
| 8448 var inst; |
| 8449 if (inst = pool && pool.pop()) { |
| 8450 inst.__setProperty(this.as, this.collection.getItem(key), true); |
| 8451 inst.__setProperty('__key__', key, true); |
| 8452 } else { |
| 8453 inst = this._generateRow(idx, key); |
| 8454 } |
| 8455 var beforeRow = this._instances[replace ? idx + 1 : idx]; |
| 8456 var beforeNode = beforeRow ? beforeRow._children[0] : this; |
| 8457 var parentNode = Polymer.dom(this).parentNode; |
| 8458 Polymer.dom(parentNode).insertBefore(inst.root, beforeNode); |
| 8459 return inst; |
| 8460 }, |
| 8461 _generateRow: function (idx, key) { |
| 8462 var model = { __key__: key }; |
| 8463 model[this.as] = this.collection.getItem(key); |
| 8464 model[this.indexAs] = idx; |
| 8465 var inst = this.stamp(model); |
| 8466 return inst; |
| 8467 }, |
| 8468 _showHideChildren: function (hidden) { |
| 8469 for (var i = 0; i < this._instances.length; i++) { |
| 8470 this._instances[i]._showHideChildren(hidden); |
| 8471 } |
| 8472 }, |
| 8473 _forwardInstanceProp: function (inst, prop, value) { |
| 8474 if (prop == this.as) { |
| 8475 var idx; |
| 8476 if (this._sortFn || this._filterFn) { |
| 8477 idx = this.items.indexOf(this.collection.getItem(inst.__key__)); |
| 8478 } else { |
| 8479 idx = inst[this.indexAs]; |
| 8480 } |
| 8481 this.set('items.' + idx, value); |
| 8482 } |
| 8483 }, |
| 8484 _forwardInstancePath: function (inst, path, value) { |
| 8485 if (path.indexOf(this.as + '.') === 0) { |
| 8486 this.notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1),
value); |
| 8487 } |
| 8488 }, |
| 8489 _forwardParentProp: function (prop, value) { |
| 8490 this._instances.forEach(function (inst) { |
| 8491 inst.__setProperty(prop, value, true); |
| 8492 }, this); |
| 8493 }, |
| 8494 _forwardParentPath: function (path, value) { |
| 8495 this._instances.forEach(function (inst) { |
| 8496 inst.notifyPath(path, value, true); |
| 8497 }, this); |
| 8498 }, |
| 8499 _forwardItemPath: function (path, value) { |
| 8500 if (this._keyToInstIdx) { |
| 8501 var dot = path.indexOf('.'); |
| 8502 var key = path.substring(0, dot < 0 ? path.length : dot); |
| 8503 var idx = this._keyToInstIdx[key]; |
| 8504 var inst = this._instances[idx]; |
| 8505 if (inst) { |
| 8506 if (dot >= 0) { |
| 8507 path = this.as + '.' + path.substring(dot + 1); |
| 8508 inst.notifyPath(path, value, true); |
| 8509 } else { |
| 8510 inst.__setProperty(this.as, value, true); |
| 8511 } |
| 8512 } |
| 8513 } |
| 8514 }, |
| 8515 itemForElement: function (el) { |
| 8516 var instance = this.modelForElement(el); |
| 8517 return instance && instance[this.as]; |
| 8518 }, |
| 8519 keyForElement: function (el) { |
| 8520 var instance = this.modelForElement(el); |
| 8521 return instance && instance.__key__; |
| 8522 }, |
| 8523 indexForElement: function (el) { |
| 8524 var instance = this.modelForElement(el); |
| 8525 return instance && instance[this.indexAs]; |
| 8526 } |
| 8527 }); |
| 8528 Polymer({ |
| 8529 is: 'array-selector', |
| 8530 properties: { |
| 8531 items: { |
| 8532 type: Array, |
| 8533 observer: 'clearSelection' |
| 8534 }, |
| 8535 multi: { |
| 8536 type: Boolean, |
| 8537 value: false, |
| 8538 observer: 'clearSelection' |
| 8539 }, |
| 8540 selected: { |
| 8541 type: Object, |
| 8542 notify: true |
| 8543 }, |
| 8544 selectedItem: { |
| 8545 type: Object, |
| 8546 notify: true |
| 8547 }, |
| 8548 toggle: { |
| 8549 type: Boolean, |
| 8550 value: false |
| 8551 } |
| 8552 }, |
| 8553 clearSelection: function () { |
| 8554 if (Array.isArray(this.selected)) { |
| 8555 for (var i = 0; i < this.selected.length; i++) { |
| 8556 this.unlinkPaths('selected.' + i); |
| 8557 } |
| 8558 } else { |
| 8559 this.unlinkPaths('selected'); |
| 8560 } |
| 8561 if (this.multi) { |
| 8562 if (!this.selected || this.selected.length) { |
| 8563 this.selected = []; |
| 8564 this._selectedColl = Polymer.Collection.get(this.selected); |
| 8565 } |
| 8566 } else { |
| 8567 this.selected = null; |
| 8568 this._selectedColl = null; |
| 8569 } |
| 8570 this.selectedItem = null; |
| 8571 }, |
| 8572 isSelected: function (item) { |
| 8573 if (this.multi) { |
| 8574 return this._selectedColl.getKey(item) !== undefined; |
| 8575 } else { |
| 8576 return this.selected == item; |
| 8577 } |
| 8578 }, |
| 8579 deselect: function (item) { |
| 8580 if (this.multi) { |
| 8581 if (this.isSelected(item)) { |
| 8582 var skey = this._selectedColl.getKey(item); |
| 8583 this.arrayDelete('selected', item); |
| 8584 this.unlinkPaths('selected.' + skey); |
| 8585 } |
| 8586 } else { |
| 8587 this.selected = null; |
| 8588 this.selectedItem = null; |
| 8589 this.unlinkPaths('selected'); |
| 8590 this.unlinkPaths('selectedItem'); |
| 8591 } |
| 8592 }, |
| 8593 select: function (item) { |
| 8594 var icol = Polymer.Collection.get(this.items); |
| 8595 var key = icol.getKey(item); |
| 8596 if (this.multi) { |
| 8597 if (this.isSelected(item)) { |
| 8598 if (this.toggle) { |
| 8599 this.deselect(item); |
| 8600 } |
| 8601 } else { |
| 8602 this.push('selected', item); |
| 8603 skey = this._selectedColl.getKey(item); |
| 8604 this.linkPaths('selected.' + skey, 'items.' + key); |
| 8605 } |
| 8606 } else { |
| 8607 if (this.toggle && item == this.selected) { |
| 8608 this.deselect(); |
| 8609 } else { |
| 8610 this.selected = item; |
| 8611 this.selectedItem = item; |
| 8612 this.linkPaths('selected', 'items.' + key); |
| 8613 this.linkPaths('selectedItem', 'items.' + key); |
| 8614 } |
| 8615 } |
| 8616 } |
| 8617 }); |
| 8618 Polymer({ |
| 8619 is: 'dom-if', |
| 8620 extends: 'template', |
| 8621 properties: { |
| 8622 'if': { |
| 8623 type: Boolean, |
| 8624 value: false, |
| 8625 observer: '_queueRender' |
| 8626 }, |
| 8627 restamp: { |
| 8628 type: Boolean, |
| 8629 value: false, |
| 8630 observer: '_queueRender' |
| 8631 } |
| 8632 }, |
| 8633 behaviors: [Polymer.Templatizer], |
| 8634 _queueRender: function () { |
| 8635 this._debounceTemplate(this._render); |
| 8636 }, |
| 8637 detached: function () { |
| 8638 this._teardownInstance(); |
| 8639 }, |
| 8640 attached: function () { |
| 8641 if (this.if && this.ctor) { |
| 8642 this.async(this._ensureInstance); |
| 8643 } |
| 8644 }, |
| 8645 render: function () { |
| 8646 this._flushTemplates(); |
| 8647 }, |
| 8648 _render: function () { |
| 8649 if (this.if) { |
| 8650 if (!this.ctor) { |
| 8651 this.templatize(this); |
| 8652 } |
| 8653 this._ensureInstance(); |
| 8654 this._showHideChildren(); |
| 8655 } else if (this.restamp) { |
| 8656 this._teardownInstance(); |
| 8657 } |
| 8658 if (!this.restamp && this._instance) { |
| 8659 this._showHideChildren(); |
| 8660 } |
| 8661 if (this.if != this._lastIf) { |
| 8662 this.fire('dom-change'); |
| 8663 this._lastIf = this.if; |
| 8664 } |
| 8665 }, |
| 8666 _ensureInstance: function () { |
| 8667 if (!this._instance) { |
| 8668 this._instance = this.stamp(); |
| 8669 var root = this._instance.root; |
| 8670 var parent = Polymer.dom(Polymer.dom(this).parentNode); |
| 8671 parent.insertBefore(root, this); |
| 8672 } |
| 8673 }, |
| 8674 _teardownInstance: function () { |
| 8675 if (this._instance) { |
| 8676 var c = this._instance._children; |
| 8677 if (c) { |
| 8678 var parent = Polymer.dom(Polymer.dom(c[0]).parentNode); |
| 8679 c.forEach(function (n) { |
| 8680 parent.removeChild(n); |
| 8681 }); |
| 8682 } |
| 8683 this._instance = null; |
| 8684 } |
| 8685 }, |
| 8686 _showHideChildren: function () { |
| 8687 var hidden = this.__hideTemplateChildren__ || !this.if; |
| 8688 if (this._instance) { |
| 8689 this._instance._showHideChildren(hidden); |
| 8690 } |
| 8691 }, |
| 8692 _forwardParentProp: function (prop, value) { |
| 8693 if (this._instance) { |
| 8694 this._instance[prop] = value; |
| 8695 } |
| 8696 }, |
| 8697 _forwardParentPath: function (path, value) { |
| 8698 if (this._instance) { |
| 8699 this._instance.notifyPath(path, value, true); |
| 8700 } |
| 8701 } |
| 8702 }); |
| 8703 Polymer({ |
| 8704 is: 'dom-bind', |
| 8705 extends: 'template', |
| 8706 created: function () { |
| 8707 Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this)); |
| 8708 }, |
| 8709 _ensureReady: function () { |
| 8710 if (!this._readied) { |
| 8711 this._readySelf(); |
| 8712 } |
| 8713 }, |
| 8714 _markImportsReady: function () { |
| 8715 this._importsReady = true; |
| 8716 this._ensureReady(); |
| 8717 }, |
| 8718 _registerFeatures: function () { |
| 8719 this._prepConstructor(); |
| 8720 }, |
| 8721 _insertChildren: function () { |
| 8722 var parentDom = Polymer.dom(Polymer.dom(this).parentNode); |
| 8723 parentDom.insertBefore(this.root, this); |
| 8724 }, |
| 8725 _removeChildren: function () { |
| 8726 if (this._children) { |
| 8727 for (var i = 0; i < this._children.length; i++) { |
| 8728 this.root.appendChild(this._children[i]); |
| 8729 } |
| 8730 } |
| 8731 }, |
| 8732 _initFeatures: function () { |
| 8733 }, |
| 8734 _scopeElementClass: function (element, selector) { |
| 8735 if (this.dataHost) { |
| 8736 return this.dataHost._scopeElementClass(element, selector); |
| 8737 } else { |
| 8738 return selector; |
| 8739 } |
| 8740 }, |
| 8741 _prepConfigure: function () { |
| 8742 var config = {}; |
| 8743 for (var prop in this._propertyEffects) { |
| 8744 config[prop] = this[prop]; |
| 8745 } |
| 8746 this._setupConfigure = this._setupConfigure.bind(this, config); |
| 8747 }, |
| 8748 attached: function () { |
| 8749 if (this._importsReady) { |
| 8750 this.render(); |
| 8751 } |
| 8752 }, |
| 8753 detached: function () { |
| 8754 this._removeChildren(); |
| 8755 }, |
| 8756 render: function () { |
| 8757 this._ensureReady(); |
| 8758 if (!this._children) { |
| 8759 this._template = this; |
| 8760 this._prepAnnotations(); |
| 8761 this._prepEffects(); |
| 8762 this._prepBehaviors(); |
| 8763 this._prepConfigure(); |
| 8764 this._prepBindings(); |
| 8765 Polymer.Base._initFeatures.call(this); |
| 8766 this._children = Array.prototype.slice.call(this.root.childNodes); |
| 8767 } |
| 8768 this._insertChildren(); |
| 8769 this.fire('dom-change'); |
| 8770 } |
| 8771 }); |
| 8772 (function() { |
| 8773 |
| 8774 'use strict'; |
| 8775 |
| 8776 var SHADOW_WHEN_SCROLLING = 1; |
| 8777 var SHADOW_ALWAYS = 2; |
| 8778 |
| 8779 |
| 8780 var MODE_CONFIGS = { |
| 8781 |
| 8782 outerScroll: { |
| 8783 'scroll': true |
| 8784 }, |
| 8785 |
| 8786 shadowMode: { |
| 8787 'standard': SHADOW_ALWAYS, |
| 8788 'waterfall': SHADOW_WHEN_SCROLLING, |
| 8789 'waterfall-tall': SHADOW_WHEN_SCROLLING |
| 8790 }, |
| 8791 |
| 8792 tallMode: { |
| 8793 'waterfall-tall': true |
| 8794 } |
| 8795 }; |
| 8796 |
| 8797 Polymer({ |
| 8798 |
| 8799 is: 'paper-header-panel', |
| 8800 |
| 8801 /** |
| 8802 * Fired when the content has been scrolled. `event.detail.target` return
s |
| 8803 * the scrollable element which you can use to access scroll info such as |
| 8804 * `scrollTop`. |
| 8805 * |
| 8806 * <paper-header-panel on-content-scroll="scrollHandler"> |
| 8807 * ... |
| 8808 * </paper-header-panel> |
| 8809 * |
| 8810 * |
| 8811 * scrollHandler: function(event) { |
| 8812 * var scroller = event.detail.target; |
| 8813 * console.log(scroller.scrollTop); |
| 8814 * } |
| 8815 * |
| 8816 * @event content-scroll |
| 8817 */ |
| 8818 |
| 8819 properties: { |
| 8820 |
| 8821 /** |
| 8822 * Controls header and scrolling behavior. Options are |
| 8823 * `standard`, `seamed`, `waterfall`, `waterfall-tall`, `scroll` and |
| 8824 * `cover`. Default is `standard`. |
| 8825 * |
| 8826 * `standard`: The header is a step above the panel. The header will con
sume the |
| 8827 * panel at the point of entry, preventing it from passing through to th
e |
| 8828 * opposite side. |
| 8829 * |
| 8830 * `seamed`: The header is presented as seamed with the panel. |
| 8831 * |
| 8832 * `waterfall`: Similar to standard mode, but header is initially presen
ted as |
| 8833 * seamed with panel, but then separates to form the step. |
| 8834 * |
| 8835 * `waterfall-tall`: The header is initially taller (`tall` class is add
ed to |
| 8836 * the header). As the user scrolls, the header separates (forming an e
dge) |
| 8837 * while condensing (`tall` class is removed from the header). |
| 8838 * |
| 8839 * `scroll`: The header keeps its seam with the panel, and is pushed off
screen. |
| 8840 * |
| 8841 * `cover`: The panel covers the whole `paper-header-panel` including th
e |
| 8842 * header. This allows user to style the panel in such a way that the pa
nel is |
| 8843 * partially covering the header. |
| 8844 * |
| 8845 * <paper-header-panel mode="cover"> |
| 8846 * <paper-toolbar class="tall"> |
| 8847 * <core-icon-button icon="menu"></core-icon-button> |
| 8848 * </paper-toolbar> |
| 8849 * <div class="content"></div> |
| 8850 * </paper-header-panel> |
| 8851 */ |
| 8852 mode: { |
| 8853 type: String, |
| 8854 value: 'standard', |
| 8855 observer: '_modeChanged', |
| 8856 reflectToAttribute: true |
| 8857 }, |
| 8858 |
| 8859 /** |
| 8860 * If true, the drop-shadow is always shown no matter what mode is set t
o. |
| 8861 */ |
| 8862 shadow: { |
| 8863 type: Boolean, |
| 8864 value: false |
| 8865 }, |
| 8866 |
| 8867 /** |
| 8868 * The class used in waterfall-tall mode. Change this if the header |
| 8869 * accepts a different class for toggling height, e.g. "medium-tall" |
| 8870 */ |
| 8871 tallClass: { |
| 8872 type: String, |
| 8873 value: 'tall' |
| 8874 }, |
| 8875 |
| 8876 /** |
| 8877 * If true, the scroller is at the top |
| 8878 */ |
| 8879 atTop: { |
| 8880 type: Boolean, |
| 8881 value: true, |
| 8882 readOnly: true |
| 8883 } |
| 8884 }, |
| 8885 |
| 8886 observers: [ |
| 8887 '_computeDropShadowHidden(atTop, mode, shadow)' |
| 8888 ], |
| 8889 |
| 8890 ready: function() { |
| 8891 this.scrollHandler = this._scroll.bind(this); |
| 8892 this._addListener(); |
| 8893 |
| 8894 // Run `scroll` logic once to initialze class names, etc. |
| 8895 this._keepScrollingState(); |
| 8896 }, |
| 8897 |
| 8898 detached: function() { |
| 8899 this._removeListener(); |
| 8900 }, |
| 8901 |
| 8902 /** |
| 8903 * Returns the header element |
| 8904 * |
| 8905 * @property header |
| 8906 * @type Object |
| 8907 */ |
| 8908 get header() { |
| 8909 return Polymer.dom(this.$.headerContent).getDistributedNodes()[0]; |
| 8910 }, |
| 8911 |
| 8912 /** |
| 8913 * Returns the scrollable element. |
| 8914 * |
| 8915 * @property scroller |
| 8916 * @type Object |
| 8917 */ |
| 8918 get scroller() { |
| 8919 return this._getScrollerForMode(this.mode); |
| 8920 }, |
| 8921 |
| 8922 /** |
| 8923 * Returns true if the scroller has a visible shadow. |
| 8924 * |
| 8925 * @property visibleShadow |
| 8926 * @type Boolean |
| 8927 */ |
| 8928 get visibleShadow() { |
| 8929 return this.$.dropShadow.classList.contains('has-shadow'); |
| 8930 }, |
| 8931 |
| 8932 _computeDropShadowHidden: function(atTop, mode, shadow) { |
| 8933 |
| 8934 var shadowMode = MODE_CONFIGS.shadowMode[mode]; |
| 8935 |
| 8936 if (this.shadow) { |
| 8937 this.toggleClass('has-shadow', true, this.$.dropShadow); |
| 8938 |
| 8939 } else if (shadowMode === SHADOW_ALWAYS) { |
| 8940 this.toggleClass('has-shadow', true, this.$.dropShadow); |
| 8941 |
| 8942 } else if (shadowMode === SHADOW_WHEN_SCROLLING && !atTop) { |
| 8943 this.toggleClass('has-shadow', true, this.$.dropShadow); |
| 8944 |
| 8945 } else { |
| 8946 this.toggleClass('has-shadow', false, this.$.dropShadow); |
| 8947 |
| 8948 } |
| 8949 }, |
| 8950 |
| 8951 _computeMainContainerClass: function(mode) { |
| 8952 // TODO: It will be useful to have a utility for classes |
| 8953 // e.g. Polymer.Utils.classes({ foo: true }); |
| 8954 |
| 8955 var classes = {}; |
| 8956 |
| 8957 classes['flex'] = mode !== 'cover'; |
| 8958 |
| 8959 return Object.keys(classes).filter( |
| 8960 function(className) { |
| 8961 return classes[className]; |
| 8962 }).join(' '); |
| 8963 }, |
| 8964 |
| 8965 _addListener: function() { |
| 8966 this.scroller.addEventListener('scroll', this.scrollHandler, false); |
| 8967 }, |
| 8968 |
| 8969 _removeListener: function() { |
| 8970 this.scroller.removeEventListener('scroll', this.scrollHandler); |
| 8971 }, |
| 8972 |
| 8973 _modeChanged: function(newMode, oldMode) { |
| 8974 var configs = MODE_CONFIGS; |
| 8975 var header = this.header; |
| 8976 var animateDuration = 200; |
| 8977 |
| 8978 if (header) { |
| 8979 // in tallMode it may add tallClass to the header; so do the cleanup |
| 8980 // when mode is changed from tallMode to not tallMode |
| 8981 if (configs.tallMode[oldMode] && !configs.tallMode[newMode]) { |
| 8982 header.classList.remove(this.tallClass); |
| 8983 this.async(function() { |
| 8984 header.classList.remove('animate'); |
| 8985 }, animateDuration); |
| 8986 } else { |
| 8987 header.classList.toggle('animate', configs.tallMode[newMode]); |
| 8988 } |
| 8989 } |
| 8990 this._keepScrollingState(); |
| 8991 }, |
| 8992 |
| 8993 _keepScrollingState: function() { |
| 8994 var main = this.scroller; |
| 8995 var header = this.header; |
| 8996 |
| 8997 this._setAtTop(main.scrollTop === 0); |
| 8998 |
| 8999 if (header && this.tallClass && MODE_CONFIGS.tallMode[this.mode]) { |
| 9000 this.toggleClass(this.tallClass, this.atTop || |
| 9001 header.classList.contains(this.tallClass) && |
| 9002 main.scrollHeight < this.offsetHeight, header); |
| 9003 } |
| 9004 }, |
| 9005 |
| 9006 _scroll: function() { |
| 9007 this._keepScrollingState(); |
| 9008 this.fire('content-scroll', {target: this.scroller}, {bubbles: false}); |
| 9009 }, |
| 9010 |
| 9011 _getScrollerForMode: function(mode) { |
| 9012 return MODE_CONFIGS.outerScroll[mode] ? |
| 9013 this : this.$.mainContainer; |
| 9014 } |
| 9015 |
| 9016 }); |
| 9017 |
| 9018 })(); |
| 9019 Polymer({ |
| 9020 is: 'paper-material', |
| 9021 |
| 9022 properties: { |
| 9023 |
| 9024 /** |
| 9025 * The z-depth of this element, from 0-5. Setting to 0 will remove the |
| 9026 * shadow, and each increasing number greater than 0 will be "deeper" |
| 9027 * than the last. |
| 9028 * |
| 9029 * @attribute elevation |
| 9030 * @type number |
| 9031 * @default 1 |
| 9032 */ |
| 9033 elevation: { |
| 9034 type: Number, |
| 9035 reflectToAttribute: true, |
| 9036 value: 1 |
| 9037 }, |
| 9038 |
| 9039 /** |
| 9040 * Set this to true to animate the shadow when setting a new |
| 9041 * `elevation` value. |
| 9042 * |
| 9043 * @attribute animated |
| 9044 * @type boolean |
| 9045 * @default false |
| 9046 */ |
| 9047 animated: { |
| 9048 type: Boolean, |
| 9049 reflectToAttribute: true, |
| 9050 value: false |
| 9051 } |
| 9052 } |
| 9053 }); |
| 9054 (function() { |
| 9055 'use strict'; |
| 9056 |
| 9057 /** |
| 9058 * Chrome uses an older version of DOM Level 3 Keyboard Events |
| 9059 * |
| 9060 * Most keys are labeled as text, but some are Unicode codepoints. |
| 9061 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set |
| 9062 */ |
| 9063 var KEY_IDENTIFIER = { |
| 9064 'U+0009': 'tab', |
| 9065 'U+001B': 'esc', |
| 9066 'U+0020': 'space', |
| 9067 'U+002A': '*', |
| 9068 'U+0030': '0', |
| 9069 'U+0031': '1', |
| 9070 'U+0032': '2', |
| 9071 'U+0033': '3', |
| 9072 'U+0034': '4', |
| 9073 'U+0035': '5', |
| 9074 'U+0036': '6', |
| 9075 'U+0037': '7', |
| 9076 'U+0038': '8', |
| 9077 'U+0039': '9', |
| 9078 'U+0041': 'a', |
| 9079 'U+0042': 'b', |
| 9080 'U+0043': 'c', |
| 9081 'U+0044': 'd', |
| 9082 'U+0045': 'e', |
| 9083 'U+0046': 'f', |
| 9084 'U+0047': 'g', |
| 9085 'U+0048': 'h', |
| 9086 'U+0049': 'i', |
| 9087 'U+004A': 'j', |
| 9088 'U+004B': 'k', |
| 9089 'U+004C': 'l', |
| 9090 'U+004D': 'm', |
| 9091 'U+004E': 'n', |
| 9092 'U+004F': 'o', |
| 9093 'U+0050': 'p', |
| 9094 'U+0051': 'q', |
| 9095 'U+0052': 'r', |
| 9096 'U+0053': 's', |
| 9097 'U+0054': 't', |
| 9098 'U+0055': 'u', |
| 9099 'U+0056': 'v', |
| 9100 'U+0057': 'w', |
| 9101 'U+0058': 'x', |
| 9102 'U+0059': 'y', |
| 9103 'U+005A': 'z', |
| 9104 'U+007F': 'del' |
| 9105 }; |
| 9106 |
| 9107 /** |
| 9108 * Special table for KeyboardEvent.keyCode. |
| 9109 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er |
| 9110 * than that. |
| 9111 * |
| 9112 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode |
| 9113 */ |
| 9114 var KEY_CODE = { |
| 9115 9: 'tab', |
| 9116 13: 'enter', |
| 9117 27: 'esc', |
| 9118 33: 'pageup', |
| 9119 34: 'pagedown', |
| 9120 35: 'end', |
| 9121 36: 'home', |
| 9122 32: 'space', |
| 9123 37: 'left', |
| 9124 38: 'up', |
| 9125 39: 'right', |
| 9126 40: 'down', |
| 9127 46: 'del', |
| 9128 106: '*' |
| 9129 }; |
| 9130 |
| 9131 /** |
| 9132 * MODIFIER_KEYS maps the short name for modifier keys used in a key |
| 9133 * combo string to the property name that references those same keys |
| 9134 * in a KeyboardEvent instance. |
| 9135 */ |
| 9136 var MODIFIER_KEYS = { |
| 9137 'shift': 'shiftKey', |
| 9138 'ctrl': 'ctrlKey', |
| 9139 'alt': 'altKey', |
| 9140 'meta': 'metaKey' |
| 9141 }; |
| 9142 |
| 9143 /** |
| 9144 * KeyboardEvent.key is mostly represented by printable character made by |
| 9145 * the keyboard, with unprintable keys labeled nicely. |
| 9146 * |
| 9147 * However, on OS X, Alt+char can make a Unicode character that follows an |
| 9148 * Apple-specific mapping. In this case, we |
| 9149 * fall back to .keyCode. |
| 9150 */ |
| 9151 var KEY_CHAR = /[a-z0-9*]/; |
| 9152 |
| 9153 /** |
| 9154 * Matches a keyIdentifier string. |
| 9155 */ |
| 9156 var IDENT_CHAR = /U\+/; |
| 9157 |
| 9158 /** |
| 9159 * Matches arrow keys in Gecko 27.0+ |
| 9160 */ |
| 9161 var ARROW_KEY = /^arrow/; |
| 9162 |
| 9163 /** |
| 9164 * Matches space keys everywhere (notably including IE10's exceptional name |
| 9165 * `spacebar`). |
| 9166 */ |
| 9167 var SPACE_KEY = /^space(bar)?/; |
| 9168 |
| 9169 function transformKey(key) { |
| 9170 var validKey = ''; |
| 9171 if (key) { |
| 9172 var lKey = key.toLowerCase(); |
| 9173 if (lKey.length == 1) { |
| 9174 if (KEY_CHAR.test(lKey)) { |
| 9175 validKey = lKey; |
| 9176 } |
| 9177 } else if (ARROW_KEY.test(lKey)) { |
| 9178 validKey = lKey.replace('arrow', ''); |
| 9179 } else if (SPACE_KEY.test(lKey)) { |
| 9180 validKey = 'space'; |
| 9181 } else if (lKey == 'multiply') { |
| 9182 // numpad '*' can map to Multiply on IE/Windows |
| 9183 validKey = '*'; |
| 9184 } else { |
| 9185 validKey = lKey; |
| 9186 } |
| 9187 } |
| 9188 return validKey; |
| 9189 } |
| 9190 |
| 9191 function transformKeyIdentifier(keyIdent) { |
| 9192 var validKey = ''; |
| 9193 if (keyIdent) { |
| 9194 if (IDENT_CHAR.test(keyIdent)) { |
| 9195 validKey = KEY_IDENTIFIER[keyIdent]; |
| 9196 } else { |
| 9197 validKey = keyIdent.toLowerCase(); |
| 9198 } |
| 9199 } |
| 9200 return validKey; |
| 9201 } |
| 9202 |
| 9203 function transformKeyCode(keyCode) { |
| 9204 var validKey = ''; |
| 9205 if (Number(keyCode)) { |
| 9206 if (keyCode >= 65 && keyCode <= 90) { |
| 9207 // ascii a-z |
| 9208 // lowercase is 32 offset from uppercase |
| 9209 validKey = String.fromCharCode(32 + keyCode); |
| 9210 } else if (keyCode >= 112 && keyCode <= 123) { |
| 9211 // function keys f1-f12 |
| 9212 validKey = 'f' + (keyCode - 112); |
| 9213 } else if (keyCode >= 48 && keyCode <= 57) { |
| 9214 // top 0-9 keys |
| 9215 validKey = String(48 - keyCode); |
| 9216 } else if (keyCode >= 96 && keyCode <= 105) { |
| 9217 // num pad 0-9 |
| 9218 validKey = String(96 - keyCode); |
| 9219 } else { |
| 9220 validKey = KEY_CODE[keyCode]; |
| 9221 } |
| 9222 } |
| 9223 return validKey; |
| 9224 } |
| 9225 |
| 9226 function normalizedKeyForEvent(keyEvent) { |
| 9227 // fall back from .key, to .keyIdentifier, to .keyCode, and then to |
| 9228 // .detail.key to support artificial keyboard events |
| 9229 return transformKey(keyEvent.key) || |
| 9230 transformKeyIdentifier(keyEvent.keyIdentifier) || |
| 9231 transformKeyCode(keyEvent.keyCode) || |
| 9232 transformKey(keyEvent.detail.key) || ''; |
| 9233 } |
| 9234 |
| 9235 function keyComboMatchesEvent(keyCombo, keyEvent) { |
| 9236 return normalizedKeyForEvent(keyEvent) === keyCombo.key && |
| 9237 !!keyEvent.shiftKey === !!keyCombo.shiftKey && |
| 9238 !!keyEvent.ctrlKey === !!keyCombo.ctrlKey && |
| 9239 !!keyEvent.altKey === !!keyCombo.altKey && |
| 9240 !!keyEvent.metaKey === !!keyCombo.metaKey; |
| 9241 } |
| 9242 |
| 9243 function parseKeyComboString(keyComboString) { |
| 9244 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP
art) { |
| 9245 var eventParts = keyComboPart.split(':'); |
| 9246 var keyName = eventParts[0]; |
| 9247 var event = eventParts[1]; |
| 9248 |
| 9249 if (keyName in MODIFIER_KEYS) { |
| 9250 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true; |
| 9251 } else { |
| 9252 parsedKeyCombo.key = keyName; |
| 9253 parsedKeyCombo.event = event || 'keydown'; |
| 9254 } |
| 9255 |
| 9256 return parsedKeyCombo; |
| 9257 }, { |
| 9258 combo: keyComboString.split(':').shift() |
| 9259 }); |
| 9260 } |
| 9261 |
| 9262 function parseEventString(eventString) { |
| 9263 return eventString.split(' ').map(function(keyComboString) { |
| 9264 return parseKeyComboString(keyComboString); |
| 9265 }); |
| 9266 } |
| 9267 |
| 9268 |
| 9269 /** |
| 9270 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces
sing |
| 9271 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3
.org/TR/wai-aria-practices/#kbd_general_binding). |
| 9272 * The element takes care of browser differences with respect to Keyboard ev
ents |
| 9273 * and uses an expressive syntax to filter key presses. |
| 9274 * |
| 9275 * Use the `keyBindings` prototype property to express what combination of k
eys |
| 9276 * will trigger the event to fire. |
| 9277 * |
| 9278 * Use the `key-event-target` attribute to set up event handlers on a specif
ic |
| 9279 * node. |
| 9280 * The `keys-pressed` event will fire when one of the key combinations set w
ith the |
| 9281 * `keys` property is pressed. |
| 9282 * |
| 9283 * @demo demo/index.html |
| 9284 * @polymerBehavior |
| 9285 */ |
| 9286 Polymer.IronA11yKeysBehavior = { |
| 9287 properties: { |
| 9288 /** |
| 9289 * The HTMLElement that will be firing relevant KeyboardEvents. |
| 9290 */ |
| 9291 keyEventTarget: { |
| 9292 type: Object, |
| 9293 value: function() { |
| 9294 return this; |
| 9295 } |
| 9296 }, |
| 9297 |
| 9298 _boundKeyHandlers: { |
| 9299 type: Array, |
| 9300 value: function() { |
| 9301 return []; |
| 9302 } |
| 9303 }, |
| 9304 |
| 9305 // We use this due to a limitation in IE10 where instances will have |
| 9306 // own properties of everything on the "prototype". |
| 9307 _imperativeKeyBindings: { |
| 9308 type: Object, |
| 9309 value: function() { |
| 9310 return {}; |
| 9311 } |
| 9312 } |
| 9313 }, |
| 9314 |
| 9315 observers: [ |
| 9316 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' |
| 9317 ], |
| 9318 |
| 9319 keyBindings: {}, |
| 9320 |
| 9321 registered: function() { |
| 9322 this._prepKeyBindings(); |
| 9323 }, |
| 9324 |
| 9325 attached: function() { |
| 9326 this._listenKeyEventListeners(); |
| 9327 }, |
| 9328 |
| 9329 detached: function() { |
| 9330 this._unlistenKeyEventListeners(); |
| 9331 }, |
| 9332 |
| 9333 /** |
| 9334 * Can be used to imperatively add a key binding to the implementing |
| 9335 * element. This is the imperative equivalent of declaring a keybinding |
| 9336 * in the `keyBindings` prototype property. |
| 9337 */ |
| 9338 addOwnKeyBinding: function(eventString, handlerName) { |
| 9339 this._imperativeKeyBindings[eventString] = handlerName; |
| 9340 this._prepKeyBindings(); |
| 9341 this._resetKeyEventListeners(); |
| 9342 }, |
| 9343 |
| 9344 /** |
| 9345 * When called, will remove all imperatively-added key bindings. |
| 9346 */ |
| 9347 removeOwnKeyBindings: function() { |
| 9348 this._imperativeKeyBindings = {}; |
| 9349 this._prepKeyBindings(); |
| 9350 this._resetKeyEventListeners(); |
| 9351 }, |
| 9352 |
| 9353 keyboardEventMatchesKeys: function(event, eventString) { |
| 9354 var keyCombos = parseEventString(eventString); |
| 9355 var index; |
| 9356 |
| 9357 for (index = 0; index < keyCombos.length; ++index) { |
| 9358 if (keyComboMatchesEvent(keyCombos[index], event)) { |
| 9359 return true; |
| 9360 } |
| 9361 } |
| 9362 |
| 9363 return false; |
| 9364 }, |
| 9365 |
| 9366 _collectKeyBindings: function() { |
| 9367 var keyBindings = this.behaviors.map(function(behavior) { |
| 9368 return behavior.keyBindings; |
| 9369 }); |
| 9370 |
| 9371 if (keyBindings.indexOf(this.keyBindings) === -1) { |
| 9372 keyBindings.push(this.keyBindings); |
| 9373 } |
| 9374 |
| 9375 return keyBindings; |
| 9376 }, |
| 9377 |
| 9378 _prepKeyBindings: function() { |
| 9379 this._keyBindings = {}; |
| 9380 |
| 9381 this._collectKeyBindings().forEach(function(keyBindings) { |
| 9382 for (var eventString in keyBindings) { |
| 9383 this._addKeyBinding(eventString, keyBindings[eventString]); |
| 9384 } |
| 9385 }, this); |
| 9386 |
| 9387 for (var eventString in this._imperativeKeyBindings) { |
| 9388 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); |
| 9389 } |
| 9390 }, |
| 9391 |
| 9392 _addKeyBinding: function(eventString, handlerName) { |
| 9393 parseEventString(eventString).forEach(function(keyCombo) { |
| 9394 this._keyBindings[keyCombo.event] = |
| 9395 this._keyBindings[keyCombo.event] || []; |
| 9396 |
| 9397 this._keyBindings[keyCombo.event].push([ |
| 9398 keyCombo, |
| 9399 handlerName |
| 9400 ]); |
| 9401 }, this); |
| 9402 }, |
| 9403 |
| 9404 _resetKeyEventListeners: function() { |
| 9405 this._unlistenKeyEventListeners(); |
| 9406 |
| 9407 if (this.isAttached) { |
| 9408 this._listenKeyEventListeners(); |
| 9409 } |
| 9410 }, |
| 9411 |
| 9412 _listenKeyEventListeners: function() { |
| 9413 Object.keys(this._keyBindings).forEach(function(eventName) { |
| 9414 var keyBindings = this._keyBindings[eventName]; |
| 9415 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings); |
| 9416 |
| 9417 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH
andler]); |
| 9418 |
| 9419 this.keyEventTarget.addEventListener(eventName, boundKeyHandler); |
| 9420 }, this); |
| 9421 }, |
| 9422 |
| 9423 _unlistenKeyEventListeners: function() { |
| 9424 var keyHandlerTuple; |
| 9425 var keyEventTarget; |
| 9426 var eventName; |
| 9427 var boundKeyHandler; |
| 9428 |
| 9429 while (this._boundKeyHandlers.length) { |
| 9430 // My kingdom for block-scope binding and destructuring assignment.. |
| 9431 keyHandlerTuple = this._boundKeyHandlers.pop(); |
| 9432 keyEventTarget = keyHandlerTuple[0]; |
| 9433 eventName = keyHandlerTuple[1]; |
| 9434 boundKeyHandler = keyHandlerTuple[2]; |
| 9435 |
| 9436 keyEventTarget.removeEventListener(eventName, boundKeyHandler); |
| 9437 } |
| 9438 }, |
| 9439 |
| 9440 _onKeyBindingEvent: function(keyBindings, event) { |
| 9441 keyBindings.forEach(function(keyBinding) { |
| 9442 var keyCombo = keyBinding[0]; |
| 9443 var handlerName = keyBinding[1]; |
| 9444 |
| 9445 if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event))
{ |
| 9446 this._triggerKeyHandler(keyCombo, handlerName, event); |
| 9447 } |
| 9448 }, this); |
| 9449 }, |
| 9450 |
| 9451 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { |
| 9452 var detail = Object.create(keyCombo); |
| 9453 detail.keyboardEvent = keyboardEvent; |
| 9454 |
| 9455 this[handlerName].call(this, new CustomEvent(keyCombo.event, { |
| 9456 detail: detail |
| 9457 })); |
| 9458 } |
| 9459 }; |
| 9460 })(); |
| 9461 (function() { |
| 9462 var Utility = { |
| 9463 distance: function(x1, y1, x2, y2) { |
| 9464 var xDelta = (x1 - x2); |
| 9465 var yDelta = (y1 - y2); |
| 9466 |
| 9467 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); |
| 9468 }, |
| 9469 |
| 9470 now: window.performance && window.performance.now ? |
| 9471 window.performance.now.bind(window.performance) : Date.now |
| 9472 }; |
| 9473 |
| 9474 /** |
| 9475 * @param {HTMLElement} element |
| 9476 * @constructor |
| 9477 */ |
| 9478 function ElementMetrics(element) { |
| 9479 this.element = element; |
| 9480 this.width = this.boundingRect.width; |
| 9481 this.height = this.boundingRect.height; |
| 9482 |
| 9483 this.size = Math.max(this.width, this.height); |
| 9484 } |
| 9485 |
| 9486 ElementMetrics.prototype = { |
| 9487 get boundingRect () { |
| 9488 return this.element.getBoundingClientRect(); |
| 9489 }, |
| 9490 |
| 9491 furthestCornerDistanceFrom: function(x, y) { |
| 9492 var topLeft = Utility.distance(x, y, 0, 0); |
| 9493 var topRight = Utility.distance(x, y, this.width, 0); |
| 9494 var bottomLeft = Utility.distance(x, y, 0, this.height); |
| 9495 var bottomRight = Utility.distance(x, y, this.width, this.height); |
| 9496 |
| 9497 return Math.max(topLeft, topRight, bottomLeft, bottomRight); |
| 9498 } |
| 9499 }; |
| 9500 |
| 9501 /** |
| 9502 * @param {HTMLElement} element |
| 9503 * @constructor |
| 9504 */ |
| 9505 function Ripple(element) { |
| 9506 this.element = element; |
| 9507 this.color = window.getComputedStyle(element).color; |
| 9508 |
| 9509 this.wave = document.createElement('div'); |
| 9510 this.waveContainer = document.createElement('div'); |
| 9511 this.wave.style.backgroundColor = this.color; |
| 9512 this.wave.classList.add('wave'); |
| 9513 this.waveContainer.classList.add('wave-container'); |
| 9514 Polymer.dom(this.waveContainer).appendChild(this.wave); |
| 9515 |
| 9516 this.resetInteractionState(); |
| 9517 } |
| 9518 |
| 9519 Ripple.MAX_RADIUS = 300; |
| 9520 |
| 9521 Ripple.prototype = { |
| 9522 get recenters() { |
| 9523 return this.element.recenters; |
| 9524 }, |
| 9525 |
| 9526 get center() { |
| 9527 return this.element.center; |
| 9528 }, |
| 9529 |
| 9530 get mouseDownElapsed() { |
| 9531 var elapsed; |
| 9532 |
| 9533 if (!this.mouseDownStart) { |
| 9534 return 0; |
| 9535 } |
| 9536 |
| 9537 elapsed = Utility.now() - this.mouseDownStart; |
| 9538 |
| 9539 if (this.mouseUpStart) { |
| 9540 elapsed -= this.mouseUpElapsed; |
| 9541 } |
| 9542 |
| 9543 return elapsed; |
| 9544 }, |
| 9545 |
| 9546 get mouseUpElapsed() { |
| 9547 return this.mouseUpStart ? |
| 9548 Utility.now () - this.mouseUpStart : 0; |
| 9549 }, |
| 9550 |
| 9551 get mouseDownElapsedSeconds() { |
| 9552 return this.mouseDownElapsed / 1000; |
| 9553 }, |
| 9554 |
| 9555 get mouseUpElapsedSeconds() { |
| 9556 return this.mouseUpElapsed / 1000; |
| 9557 }, |
| 9558 |
| 9559 get mouseInteractionSeconds() { |
| 9560 return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds; |
| 9561 }, |
| 9562 |
| 9563 get initialOpacity() { |
| 9564 return this.element.initialOpacity; |
| 9565 }, |
| 9566 |
| 9567 get opacityDecayVelocity() { |
| 9568 return this.element.opacityDecayVelocity; |
| 9569 }, |
| 9570 |
| 9571 get radius() { |
| 9572 var width2 = this.containerMetrics.width * this.containerMetrics.width; |
| 9573 var height2 = this.containerMetrics.height * this.containerMetrics.heigh
t; |
| 9574 var waveRadius = Math.min( |
| 9575 Math.sqrt(width2 + height2), |
| 9576 Ripple.MAX_RADIUS |
| 9577 ) * 1.1 + 5; |
| 9578 |
| 9579 var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS); |
| 9580 var timeNow = this.mouseInteractionSeconds / duration; |
| 9581 var size = waveRadius * (1 - Math.pow(80, -timeNow)); |
| 9582 |
| 9583 return Math.abs(size); |
| 9584 }, |
| 9585 |
| 9586 get opacity() { |
| 9587 if (!this.mouseUpStart) { |
| 9588 return this.initialOpacity; |
| 9589 } |
| 9590 |
| 9591 return Math.max( |
| 9592 0, |
| 9593 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe
locity |
| 9594 ); |
| 9595 }, |
| 9596 |
| 9597 get outerOpacity() { |
| 9598 // Linear increase in background opacity, capped at the opacity |
| 9599 // of the wavefront (waveOpacity). |
| 9600 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; |
| 9601 var waveOpacity = this.opacity; |
| 9602 |
| 9603 return Math.max( |
| 9604 0, |
| 9605 Math.min(outerOpacity, waveOpacity) |
| 9606 ); |
| 9607 }, |
| 9608 |
| 9609 get isOpacityFullyDecayed() { |
| 9610 return this.opacity < 0.01 && |
| 9611 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); |
| 9612 }, |
| 9613 |
| 9614 get isRestingAtMaxRadius() { |
| 9615 return this.opacity >= this.initialOpacity && |
| 9616 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); |
| 9617 }, |
| 9618 |
| 9619 get isAnimationComplete() { |
| 9620 return this.mouseUpStart ? |
| 9621 this.isOpacityFullyDecayed : this.isRestingAtMaxRadius; |
| 9622 }, |
| 9623 |
| 9624 get translationFraction() { |
| 9625 return Math.min( |
| 9626 1, |
| 9627 this.radius / this.containerMetrics.size * 2 / Math.sqrt(2) |
| 9628 ); |
| 9629 }, |
| 9630 |
| 9631 get xNow() { |
| 9632 if (this.xEnd) { |
| 9633 return this.xStart + this.translationFraction * (this.xEnd - this.xSta
rt); |
| 9634 } |
| 9635 |
| 9636 return this.xStart; |
| 9637 }, |
| 9638 |
| 9639 get yNow() { |
| 9640 if (this.yEnd) { |
| 9641 return this.yStart + this.translationFraction * (this.yEnd - this.ySta
rt); |
| 9642 } |
| 9643 |
| 9644 return this.yStart; |
| 9645 }, |
| 9646 |
| 9647 get isMouseDown() { |
| 9648 return this.mouseDownStart && !this.mouseUpStart; |
| 9649 }, |
| 9650 |
| 9651 resetInteractionState: function() { |
| 9652 this.maxRadius = 0; |
| 9653 this.mouseDownStart = 0; |
| 9654 this.mouseUpStart = 0; |
| 9655 |
| 9656 this.xStart = 0; |
| 9657 this.yStart = 0; |
| 9658 this.xEnd = 0; |
| 9659 this.yEnd = 0; |
| 9660 this.slideDistance = 0; |
| 9661 |
| 9662 this.containerMetrics = new ElementMetrics(this.element); |
| 9663 }, |
| 9664 |
| 9665 draw: function() { |
| 9666 var scale; |
| 9667 var translateString; |
| 9668 var dx; |
| 9669 var dy; |
| 9670 |
| 9671 this.wave.style.opacity = this.opacity; |
| 9672 |
| 9673 scale = this.radius / (this.containerMetrics.size / 2); |
| 9674 dx = this.xNow - (this.containerMetrics.width / 2); |
| 9675 dy = this.yNow - (this.containerMetrics.height / 2); |
| 9676 |
| 9677 |
| 9678 // 2d transform for safari because of border-radius and overflow:hidden
clipping bug. |
| 9679 // https://bugs.webkit.org/show_bug.cgi?id=98538 |
| 9680 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' +
dy + 'px)'; |
| 9681 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy +
'px, 0)'; |
| 9682 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; |
| 9683 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; |
| 9684 }, |
| 9685 |
| 9686 /** @param {Event=} event */ |
| 9687 downAction: function(event) { |
| 9688 var xCenter = this.containerMetrics.width / 2; |
| 9689 var yCenter = this.containerMetrics.height / 2; |
| 9690 |
| 9691 this.resetInteractionState(); |
| 9692 this.mouseDownStart = Utility.now(); |
| 9693 |
| 9694 if (this.center) { |
| 9695 this.xStart = xCenter; |
| 9696 this.yStart = yCenter; |
| 9697 this.slideDistance = Utility.distance( |
| 9698 this.xStart, this.yStart, this.xEnd, this.yEnd |
| 9699 ); |
| 9700 } else { |
| 9701 this.xStart = event ? |
| 9702 event.detail.x - this.containerMetrics.boundingRect.left : |
| 9703 this.containerMetrics.width / 2; |
| 9704 this.yStart = event ? |
| 9705 event.detail.y - this.containerMetrics.boundingRect.top : |
| 9706 this.containerMetrics.height / 2; |
| 9707 } |
| 9708 |
| 9709 if (this.recenters) { |
| 9710 this.xEnd = xCenter; |
| 9711 this.yEnd = yCenter; |
| 9712 this.slideDistance = Utility.distance( |
| 9713 this.xStart, this.yStart, this.xEnd, this.yEnd |
| 9714 ); |
| 9715 } |
| 9716 |
| 9717 this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom( |
| 9718 this.xStart, |
| 9719 this.yStart |
| 9720 ); |
| 9721 |
| 9722 this.waveContainer.style.top = |
| 9723 (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px'
; |
| 9724 this.waveContainer.style.left = |
| 9725 (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px'; |
| 9726 |
| 9727 this.waveContainer.style.width = this.containerMetrics.size + 'px'; |
| 9728 this.waveContainer.style.height = this.containerMetrics.size + 'px'; |
| 9729 }, |
| 9730 |
| 9731 /** @param {Event=} event */ |
| 9732 upAction: function(event) { |
| 9733 if (!this.isMouseDown) { |
| 9734 return; |
| 9735 } |
| 9736 |
| 9737 this.mouseUpStart = Utility.now(); |
| 9738 }, |
| 9739 |
| 9740 remove: function() { |
| 9741 Polymer.dom(this.waveContainer.parentNode).removeChild( |
| 9742 this.waveContainer |
| 9743 ); |
| 9744 } |
| 9745 }; |
| 9746 |
| 9747 Polymer({ |
| 9748 is: 'paper-ripple', |
| 9749 |
| 9750 behaviors: [ |
| 9751 Polymer.IronA11yKeysBehavior |
| 9752 ], |
| 9753 |
| 9754 properties: { |
| 9755 /** |
| 9756 * The initial opacity set on the wave. |
| 9757 * |
| 9758 * @attribute initialOpacity |
| 9759 * @type number |
| 9760 * @default 0.25 |
| 9761 */ |
| 9762 initialOpacity: { |
| 9763 type: Number, |
| 9764 value: 0.25 |
| 9765 }, |
| 9766 |
| 9767 /** |
| 9768 * How fast (opacity per second) the wave fades out. |
| 9769 * |
| 9770 * @attribute opacityDecayVelocity |
| 9771 * @type number |
| 9772 * @default 0.8 |
| 9773 */ |
| 9774 opacityDecayVelocity: { |
| 9775 type: Number, |
| 9776 value: 0.8 |
| 9777 }, |
| 9778 |
| 9779 /** |
| 9780 * If true, ripples will exhibit a gravitational pull towards |
| 9781 * the center of their container as they fade away. |
| 9782 * |
| 9783 * @attribute recenters |
| 9784 * @type boolean |
| 9785 * @default false |
| 9786 */ |
| 9787 recenters: { |
| 9788 type: Boolean, |
| 9789 value: false |
| 9790 }, |
| 9791 |
| 9792 /** |
| 9793 * If true, ripples will center inside its container |
| 9794 * |
| 9795 * @attribute recenters |
| 9796 * @type boolean |
| 9797 * @default false |
| 9798 */ |
| 9799 center: { |
| 9800 type: Boolean, |
| 9801 value: false |
| 9802 }, |
| 9803 |
| 9804 /** |
| 9805 * A list of the visual ripples. |
| 9806 * |
| 9807 * @attribute ripples |
| 9808 * @type Array |
| 9809 * @default [] |
| 9810 */ |
| 9811 ripples: { |
| 9812 type: Array, |
| 9813 value: function() { |
| 9814 return []; |
| 9815 } |
| 9816 }, |
| 9817 |
| 9818 /** |
| 9819 * True when there are visible ripples animating within the |
| 9820 * element. |
| 9821 */ |
| 9822 animating: { |
| 9823 type: Boolean, |
| 9824 readOnly: true, |
| 9825 reflectToAttribute: true, |
| 9826 value: false |
| 9827 }, |
| 9828 |
| 9829 /** |
| 9830 * If true, the ripple will remain in the "down" state until `holdDown` |
| 9831 * is set to false again. |
| 9832 */ |
| 9833 holdDown: { |
| 9834 type: Boolean, |
| 9835 value: false, |
| 9836 observer: '_holdDownChanged' |
| 9837 }, |
| 9838 |
| 9839 _animating: { |
| 9840 type: Boolean |
| 9841 }, |
| 9842 |
| 9843 _boundAnimate: { |
| 9844 type: Function, |
| 9845 value: function() { |
| 9846 return this.animate.bind(this); |
| 9847 } |
| 9848 } |
| 9849 }, |
| 9850 |
| 9851 get target () { |
| 9852 var ownerRoot = Polymer.dom(this).getOwnerRoot(); |
| 9853 var target; |
| 9854 |
| 9855 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE |
| 9856 target = ownerRoot.host; |
| 9857 } else { |
| 9858 target = this.parentNode; |
| 9859 } |
| 9860 |
| 9861 return target; |
| 9862 }, |
| 9863 |
| 9864 keyBindings: { |
| 9865 'enter:keydown': '_onEnterKeydown', |
| 9866 'space:keydown': '_onSpaceKeydown', |
| 9867 'space:keyup': '_onSpaceKeyup' |
| 9868 }, |
| 9869 |
| 9870 attached: function() { |
| 9871 this.listen(this.target, 'up', 'upAction'); |
| 9872 this.listen(this.target, 'down', 'downAction'); |
| 9873 |
| 9874 if (!this.target.hasAttribute('noink')) { |
| 9875 this.keyEventTarget = this.target; |
| 9876 } |
| 9877 }, |
| 9878 |
| 9879 get shouldKeepAnimating () { |
| 9880 for (var index = 0; index < this.ripples.length; ++index) { |
| 9881 if (!this.ripples[index].isAnimationComplete) { |
| 9882 return true; |
| 9883 } |
| 9884 } |
| 9885 |
| 9886 return false; |
| 9887 }, |
| 9888 |
| 9889 simulatedRipple: function() { |
| 9890 this.downAction(null); |
| 9891 |
| 9892 // Please see polymer/polymer#1305 |
| 9893 this.async(function() { |
| 9894 this.upAction(); |
| 9895 }, 1); |
| 9896 }, |
| 9897 |
| 9898 /** @param {Event=} event */ |
| 9899 downAction: function(event) { |
| 9900 if (this.holdDown && this.ripples.length > 0) { |
| 9901 return; |
| 9902 } |
| 9903 |
| 9904 var ripple = this.addRipple(); |
| 9905 |
| 9906 ripple.downAction(event); |
| 9907 |
| 9908 if (!this._animating) { |
| 9909 this.animate(); |
| 9910 } |
| 9911 }, |
| 9912 |
| 9913 /** @param {Event=} event */ |
| 9914 upAction: function(event) { |
| 9915 if (this.holdDown) { |
| 9916 return; |
| 9917 } |
| 9918 |
| 9919 this.ripples.forEach(function(ripple) { |
| 9920 ripple.upAction(event); |
| 9921 }); |
| 9922 |
| 9923 this.animate(); |
| 9924 }, |
| 9925 |
| 9926 onAnimationComplete: function() { |
| 9927 this._animating = false; |
| 9928 this.$.background.style.backgroundColor = null; |
| 9929 this.fire('transitionend'); |
| 9930 }, |
| 9931 |
| 9932 addRipple: function() { |
| 9933 var ripple = new Ripple(this); |
| 9934 |
| 9935 Polymer.dom(this.$.waves).appendChild(ripple.waveContainer); |
| 9936 this.$.background.style.backgroundColor = ripple.color; |
| 9937 this.ripples.push(ripple); |
| 9938 |
| 9939 this._setAnimating(true); |
| 9940 |
| 9941 return ripple; |
| 9942 }, |
| 9943 |
| 9944 removeRipple: function(ripple) { |
| 9945 var rippleIndex = this.ripples.indexOf(ripple); |
| 9946 |
| 9947 if (rippleIndex < 0) { |
| 9948 return; |
| 9949 } |
| 9950 |
| 9951 this.ripples.splice(rippleIndex, 1); |
| 9952 |
| 9953 ripple.remove(); |
| 9954 |
| 9955 if (!this.ripples.length) { |
| 9956 this._setAnimating(false); |
| 9957 } |
| 9958 }, |
| 9959 |
| 9960 animate: function() { |
| 9961 var index; |
| 9962 var ripple; |
| 9963 |
| 9964 this._animating = true; |
| 9965 |
| 9966 for (index = 0; index < this.ripples.length; ++index) { |
| 9967 ripple = this.ripples[index]; |
| 9968 |
| 9969 ripple.draw(); |
| 9970 |
| 9971 this.$.background.style.opacity = ripple.outerOpacity; |
| 9972 |
| 9973 if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) { |
| 9974 this.removeRipple(ripple); |
| 9975 } |
| 9976 } |
| 9977 |
| 9978 if (!this.shouldKeepAnimating && this.ripples.length === 0) { |
| 9979 this.onAnimationComplete(); |
| 9980 } else { |
| 9981 window.requestAnimationFrame(this._boundAnimate); |
| 9982 } |
| 9983 }, |
| 9984 |
| 9985 _onEnterKeydown: function() { |
| 9986 this.downAction(); |
| 9987 this.async(this.upAction, 1); |
| 9988 }, |
| 9989 |
| 9990 _onSpaceKeydown: function() { |
| 9991 this.downAction(); |
| 9992 }, |
| 9993 |
| 9994 _onSpaceKeyup: function() { |
| 9995 this.upAction(); |
| 9996 }, |
| 9997 |
| 9998 _holdDownChanged: function(holdDown) { |
| 9999 if (holdDown) { |
| 10000 this.downAction(); |
| 10001 } else { |
| 10002 this.upAction(); |
| 10003 } |
| 10004 } |
| 10005 }); |
| 10006 })(); |
| 10007 /** |
| 10008 * @demo demo/index.html |
| 10009 * @polymerBehavior |
| 10010 */ |
| 10011 Polymer.IronControlState = { |
| 10012 |
| 10013 properties: { |
| 10014 |
| 10015 /** |
| 10016 * If true, the element currently has focus. |
| 10017 */ |
| 10018 focused: { |
| 10019 type: Boolean, |
| 10020 value: false, |
| 10021 notify: true, |
| 10022 readOnly: true, |
| 10023 reflectToAttribute: true |
| 10024 }, |
| 10025 |
| 10026 /** |
| 10027 * If true, the user cannot interact with this element. |
| 10028 */ |
| 10029 disabled: { |
| 10030 type: Boolean, |
| 10031 value: false, |
| 10032 notify: true, |
| 10033 observer: '_disabledChanged', |
| 10034 reflectToAttribute: true |
| 10035 }, |
| 10036 |
| 10037 _oldTabIndex: { |
| 10038 type: Number |
| 10039 }, |
| 10040 |
| 10041 _boundFocusBlurHandler: { |
| 10042 type: Function, |
| 10043 value: function() { |
| 10044 return this._focusBlurHandler.bind(this); |
| 10045 } |
| 10046 } |
| 10047 |
| 10048 }, |
| 10049 |
| 10050 observers: [ |
| 10051 '_changedControlState(focused, disabled)' |
| 10052 ], |
| 10053 |
| 10054 ready: function() { |
| 10055 this.addEventListener('focus', this._boundFocusBlurHandler, true); |
| 10056 this.addEventListener('blur', this._boundFocusBlurHandler, true); |
| 10057 }, |
| 10058 |
| 10059 _focusBlurHandler: function(event) { |
| 10060 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will |
| 10061 // eventually become `this` due to retargeting; if we are not in |
| 10062 // ShadowDOM land, `event.target` will eventually become `this` due |
| 10063 // to the second conditional which fires a synthetic event (that is also |
| 10064 // handled). In either case, we can disregard `event.path`. |
| 10065 |
| 10066 if (event.target === this) { |
| 10067 var focused = event.type === 'focus'; |
| 10068 this._setFocused(focused); |
| 10069 } else if (!this.shadowRoot) { |
| 10070 this.fire(event.type, {sourceEvent: event}, { |
| 10071 node: this, |
| 10072 bubbles: event.bubbles, |
| 10073 cancelable: event.cancelable |
| 10074 }); |
| 10075 } |
| 10076 }, |
| 10077 |
| 10078 _disabledChanged: function(disabled, old) { |
| 10079 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
| 10080 this.style.pointerEvents = disabled ? 'none' : ''; |
| 10081 if (disabled) { |
| 10082 this._oldTabIndex = this.tabIndex; |
| 10083 this.focused = false; |
| 10084 this.tabIndex = -1; |
| 10085 } else if (this._oldTabIndex !== undefined) { |
| 10086 this.tabIndex = this._oldTabIndex; |
| 10087 } |
| 10088 }, |
| 10089 |
| 10090 _changedControlState: function() { |
| 10091 // _controlStateChanged is abstract, follow-on behaviors may implement it |
| 10092 if (this._controlStateChanged) { |
| 10093 this._controlStateChanged(); |
| 10094 } |
| 10095 } |
| 10096 |
| 10097 }; |
| 10098 /** |
| 10099 * @demo demo/index.html |
| 10100 * @polymerBehavior Polymer.IronButtonState |
| 10101 */ |
| 10102 Polymer.IronButtonStateImpl = { |
| 10103 |
| 10104 properties: { |
| 10105 |
| 10106 /** |
| 10107 * If true, the user is currently holding down the button. |
| 10108 */ |
| 10109 pressed: { |
| 10110 type: Boolean, |
| 10111 readOnly: true, |
| 10112 value: false, |
| 10113 reflectToAttribute: true, |
| 10114 observer: '_pressedChanged' |
| 10115 }, |
| 10116 |
| 10117 /** |
| 10118 * If true, the button toggles the active state with each tap or press |
| 10119 * of the spacebar. |
| 10120 */ |
| 10121 toggles: { |
| 10122 type: Boolean, |
| 10123 value: false, |
| 10124 reflectToAttribute: true |
| 10125 }, |
| 10126 |
| 10127 /** |
| 10128 * If true, the button is a toggle and is currently in the active state. |
| 10129 */ |
| 10130 active: { |
| 10131 type: Boolean, |
| 10132 value: false, |
| 10133 notify: true, |
| 10134 reflectToAttribute: true |
| 10135 }, |
| 10136 |
| 10137 /** |
| 10138 * True if the element is currently being pressed by a "pointer," which |
| 10139 * is loosely defined as mouse or touch input (but specifically excluding |
| 10140 * keyboard input). |
| 10141 */ |
| 10142 pointerDown: { |
| 10143 type: Boolean, |
| 10144 readOnly: true, |
| 10145 value: false |
| 10146 }, |
| 10147 |
| 10148 /** |
| 10149 * True if the input device that caused the element to receive focus |
| 10150 * was a keyboard. |
| 10151 */ |
| 10152 receivedFocusFromKeyboard: { |
| 10153 type: Boolean, |
| 10154 readOnly: true |
| 10155 }, |
| 10156 |
| 10157 /** |
| 10158 * The aria attribute to be set if the button is a toggle and in the |
| 10159 * active state. |
| 10160 */ |
| 10161 ariaActiveAttribute: { |
| 10162 type: String, |
| 10163 value: 'aria-pressed', |
| 10164 observer: '_ariaActiveAttributeChanged' |
| 10165 } |
| 10166 }, |
| 10167 |
| 10168 listeners: { |
| 10169 down: '_downHandler', |
| 10170 up: '_upHandler', |
| 10171 tap: '_tapHandler' |
| 10172 }, |
| 10173 |
| 10174 observers: [ |
| 10175 '_detectKeyboardFocus(focused)', |
| 10176 '_activeChanged(active, ariaActiveAttribute)' |
| 10177 ], |
| 10178 |
| 10179 keyBindings: { |
| 10180 'enter:keydown': '_asyncClick', |
| 10181 'space:keydown': '_spaceKeyDownHandler', |
| 10182 'space:keyup': '_spaceKeyUpHandler', |
| 10183 }, |
| 10184 |
| 10185 _mouseEventRe: /^mouse/, |
| 10186 |
| 10187 _tapHandler: function() { |
| 10188 if (this.toggles) { |
| 10189 // a tap is needed to toggle the active state |
| 10190 this._userActivate(!this.active); |
| 10191 } else { |
| 10192 this.active = false; |
| 10193 } |
| 10194 }, |
| 10195 |
| 10196 _detectKeyboardFocus: function(focused) { |
| 10197 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); |
| 10198 }, |
| 10199 |
| 10200 // to emulate native checkbox, (de-)activations from a user interaction fire |
| 10201 // 'change' events |
| 10202 _userActivate: function(active) { |
| 10203 if (this.active !== active) { |
| 10204 this.active = active; |
| 10205 this.fire('change'); |
| 10206 } |
| 10207 }, |
| 10208 |
| 10209 _eventSourceIsPrimaryInput: function(event) { |
| 10210 event = event.detail.sourceEvent || event; |
| 10211 |
| 10212 // Always true for non-mouse events.... |
| 10213 if (!this._mouseEventRe.test(event.type)) { |
| 10214 return true; |
| 10215 } |
| 10216 |
| 10217 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons |
| 10218 if ('buttons' in event) { |
| 10219 return event.buttons === 1; |
| 10220 } |
| 10221 |
| 10222 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which |
| 10223 if (typeof event.which === 'number') { |
| 10224 return event.which < 2; |
| 10225 } |
| 10226 |
| 10227 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button |
| 10228 return event.button < 1; |
| 10229 }, |
| 10230 |
| 10231 _downHandler: function(event) { |
| 10232 if (!this._eventSourceIsPrimaryInput(event)) { |
| 10233 return; |
| 10234 } |
| 10235 |
| 10236 this._setPointerDown(true); |
| 10237 this._setPressed(true); |
| 10238 this._setReceivedFocusFromKeyboard(false); |
| 10239 }, |
| 10240 |
| 10241 _upHandler: function() { |
| 10242 this._setPointerDown(false); |
| 10243 this._setPressed(false); |
| 10244 }, |
| 10245 |
| 10246 _spaceKeyDownHandler: function(event) { |
| 10247 var keyboardEvent = event.detail.keyboardEvent; |
| 10248 keyboardEvent.preventDefault(); |
| 10249 keyboardEvent.stopImmediatePropagation(); |
| 10250 this._setPressed(true); |
| 10251 }, |
| 10252 |
| 10253 _spaceKeyUpHandler: function() { |
| 10254 if (this.pressed) { |
| 10255 this._asyncClick(); |
| 10256 } |
| 10257 this._setPressed(false); |
| 10258 }, |
| 10259 |
| 10260 // trigger click asynchronously, the asynchrony is useful to allow one |
| 10261 // event handler to unwind before triggering another event |
| 10262 _asyncClick: function() { |
| 10263 this.async(function() { |
| 10264 this.click(); |
| 10265 }, 1); |
| 10266 }, |
| 10267 |
| 10268 // any of these changes are considered a change to button state |
| 10269 |
| 10270 _pressedChanged: function(pressed) { |
| 10271 this._changedButtonState(); |
| 10272 }, |
| 10273 |
| 10274 _ariaActiveAttributeChanged: function(value, oldValue) { |
| 10275 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { |
| 10276 this.removeAttribute(oldValue); |
| 10277 } |
| 10278 }, |
| 10279 |
| 10280 _activeChanged: function(active, ariaActiveAttribute) { |
| 10281 if (this.toggles) { |
| 10282 this.setAttribute(this.ariaActiveAttribute, |
| 10283 active ? 'true' : 'false'); |
| 10284 } else { |
| 10285 this.removeAttribute(this.ariaActiveAttribute); |
| 10286 } |
| 10287 this._changedButtonState(); |
| 10288 }, |
| 10289 |
| 10290 _controlStateChanged: function() { |
| 10291 if (this.disabled) { |
| 10292 this._setPressed(false); |
| 10293 } else { |
| 10294 this._changedButtonState(); |
| 10295 } |
| 10296 }, |
| 10297 |
| 10298 // provide hook for follow-on behaviors to react to button-state |
| 10299 |
| 10300 _changedButtonState: function() { |
| 10301 if (this._buttonStateChanged) { |
| 10302 this._buttonStateChanged(); // abstract |
| 10303 } |
| 10304 } |
| 10305 |
| 10306 }; |
| 10307 |
| 10308 /** @polymerBehavior */ |
| 10309 Polymer.IronButtonState = [ |
| 10310 Polymer.IronA11yKeysBehavior, |
| 10311 Polymer.IronButtonStateImpl |
| 10312 ]; |
| 10313 /** @polymerBehavior */ |
| 10314 Polymer.PaperButtonBehaviorImpl = { |
| 10315 |
| 10316 properties: { |
| 10317 |
| 10318 _elevation: { |
| 10319 type: Number |
| 10320 } |
| 10321 |
| 10322 }, |
| 10323 |
| 10324 observers: [ |
| 10325 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom
Keyboard)' |
| 10326 ], |
| 10327 |
| 10328 hostAttributes: { |
| 10329 role: 'button', |
| 10330 tabindex: '0' |
| 10331 }, |
| 10332 |
| 10333 _calculateElevation: function() { |
| 10334 var e = 1; |
| 10335 if (this.disabled) { |
| 10336 e = 0; |
| 10337 } else if (this.active || this.pressed) { |
| 10338 e = 4; |
| 10339 } else if (this.receivedFocusFromKeyboard) { |
| 10340 e = 3; |
| 10341 } |
| 10342 this._elevation = e; |
| 10343 } |
| 10344 }; |
| 10345 |
| 10346 /** @polymerBehavior */ |
| 10347 Polymer.PaperButtonBehavior = [ |
| 10348 Polymer.IronButtonState, |
| 10349 Polymer.IronControlState, |
| 10350 Polymer.PaperButtonBehaviorImpl |
| 10351 ]; |
| 10352 Polymer({ |
| 10353 is: 'paper-button', |
| 10354 |
| 10355 behaviors: [ |
| 10356 Polymer.PaperButtonBehavior |
| 10357 ], |
| 10358 |
| 10359 properties: { |
| 10360 /** |
| 10361 * If true, the button should be styled with a shadow. |
| 10362 */ |
| 10363 raised: { |
| 10364 type: Boolean, |
| 10365 reflectToAttribute: true, |
| 10366 value: false, |
| 10367 observer: '_calculateElevation' |
| 10368 } |
| 10369 }, |
| 10370 |
| 10371 _calculateElevation: function() { |
| 10372 if (!this.raised) { |
| 10373 this._elevation = 0; |
| 10374 } else { |
| 10375 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); |
| 10376 } |
| 10377 }, |
| 10378 |
| 10379 _computeContentClass: function(receivedFocusFromKeyboard) { |
| 10380 var className = 'content '; |
| 10381 if (receivedFocusFromKeyboard) { |
| 10382 className += ' keyboard-focus'; |
| 10383 } |
| 10384 return className; |
| 10385 } |
| 10386 }); |
| 10387 /** |
| 10388 * `iron-range-behavior` provides the behavior for something with a minimum to m
aximum range. |
| 10389 * |
| 10390 * @demo demo/index.html |
| 10391 * @polymerBehavior |
| 10392 */ |
| 10393 Polymer.IronRangeBehavior = { |
| 10394 |
| 10395 properties: { |
| 10396 |
| 10397 /** |
| 10398 * The number that represents the current value. |
| 10399 */ |
| 10400 value: { |
| 10401 type: Number, |
| 10402 value: 0, |
| 10403 notify: true, |
| 10404 reflectToAttribute: true |
| 10405 }, |
| 10406 |
| 10407 /** |
| 10408 * The number that indicates the minimum value of the range. |
| 10409 */ |
| 10410 min: { |
| 10411 type: Number, |
| 10412 value: 0, |
| 10413 notify: true |
| 10414 }, |
| 10415 |
| 10416 /** |
| 10417 * The number that indicates the maximum value of the range. |
| 10418 */ |
| 10419 max: { |
| 10420 type: Number, |
| 10421 value: 100, |
| 10422 notify: true |
| 10423 }, |
| 10424 |
| 10425 /** |
| 10426 * Specifies the value granularity of the range's value. |
| 10427 */ |
| 10428 step: { |
| 10429 type: Number, |
| 10430 value: 1, |
| 10431 notify: true |
| 10432 }, |
| 10433 |
| 10434 /** |
| 10435 * Returns the ratio of the value. |
| 10436 */ |
| 10437 ratio: { |
| 10438 type: Number, |
| 10439 value: 0, |
| 10440 readOnly: true, |
| 10441 notify: true |
| 10442 }, |
| 10443 }, |
| 10444 |
| 10445 observers: [ |
| 10446 '_update(value, min, max, step)' |
| 10447 ], |
| 10448 |
| 10449 _calcRatio: function(value) { |
| 10450 return (this._clampValue(value) - this.min) / (this.max - this.min); |
| 10451 }, |
| 10452 |
| 10453 _clampValue: function(value) { |
| 10454 return Math.min(this.max, Math.max(this.min, this._calcStep(value))); |
| 10455 }, |
| 10456 |
| 10457 _calcStep: function(value) { |
| 10458 /** |
| 10459 * if we calculate the step using |
| 10460 * `Math.round(value / step) * step` we may hit a precision point issue |
| 10461 * eg. 0.1 * 0.2 = 0.020000000000000004 |
| 10462 * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html |
| 10463 * |
| 10464 * as a work around we can divide by the reciprocal of `step` |
| 10465 */ |
| 10466 // polymer/issues/2493 |
| 10467 value = parseFloat(value); |
| 10468 return this.step ? (Math.round((value + this.min) / this.step) / (1 / this.s
tep)) - this.min : value; |
| 10469 }, |
| 10470 |
| 10471 _validateValue: function() { |
| 10472 var v = this._clampValue(this.value); |
| 10473 this.value = this.oldValue = isNaN(v) ? this.oldValue : v; |
| 10474 return this.value !== v; |
| 10475 }, |
| 10476 |
| 10477 _update: function() { |
| 10478 this._validateValue(); |
| 10479 this._setRatio(this._calcRatio(this.value) * 100); |
| 10480 } |
| 10481 |
| 10482 }; |
| 10483 Polymer({ |
| 10484 |
| 10485 is: 'paper-progress', |
| 10486 |
| 10487 behaviors: [ |
| 10488 Polymer.IronRangeBehavior |
| 10489 ], |
| 10490 |
| 10491 properties: { |
| 10492 |
| 10493 /** |
| 10494 * The number that represents the current secondary progress. |
| 10495 */ |
| 10496 secondaryProgress: { |
| 10497 type: Number, |
| 10498 value: 0 |
| 10499 }, |
| 10500 |
| 10501 /** |
| 10502 * The secondary ratio |
| 10503 */ |
| 10504 secondaryRatio: { |
| 10505 type: Number, |
| 10506 value: 0, |
| 10507 readOnly: true |
| 10508 }, |
| 10509 |
| 10510 /** |
| 10511 * Use an indeterminate progress indicator. |
| 10512 */ |
| 10513 indeterminate: { |
| 10514 type: Boolean, |
| 10515 value: false, |
| 10516 observer: '_toggleIndeterminate' |
| 10517 }, |
| 10518 |
| 10519 /** |
| 10520 * True if the progress is disabled. |
| 10521 */ |
| 10522 disabled: { |
| 10523 type: Boolean, |
| 10524 value: false, |
| 10525 reflectToAttribute: true, |
| 10526 observer: '_disabledChanged' |
| 10527 } |
| 10528 }, |
| 10529 |
| 10530 observers: [ |
| 10531 '_progressChanged(secondaryProgress, value, min, max)' |
| 10532 ], |
| 10533 |
| 10534 hostAttributes: { |
| 10535 role: 'progressbar' |
| 10536 }, |
| 10537 |
| 10538 _toggleIndeterminate: function(indeterminate) { |
| 10539 // If we use attribute/class binding, the animation sometimes doesn't tran
slate properly |
| 10540 // on Safari 7.1. So instead, we toggle the class here in the update metho
d. |
| 10541 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress); |
| 10542 }, |
| 10543 |
| 10544 _transformProgress: function(progress, ratio) { |
| 10545 var transform = 'scaleX(' + (ratio / 100) + ')'; |
| 10546 progress.style.transform = progress.style.webkitTransform = transform; |
| 10547 }, |
| 10548 |
| 10549 _mainRatioChanged: function(ratio) { |
| 10550 this._transformProgress(this.$.primaryProgress, ratio); |
| 10551 }, |
| 10552 |
| 10553 _progressChanged: function(secondaryProgress, value, min, max) { |
| 10554 secondaryProgress = this._clampValue(secondaryProgress); |
| 10555 value = this._clampValue(value); |
| 10556 |
| 10557 var secondaryRatio = this._calcRatio(secondaryProgress) * 100; |
| 10558 var mainRatio = this._calcRatio(value) * 100; |
| 10559 |
| 10560 this._setSecondaryRatio(secondaryRatio); |
| 10561 this._transformProgress(this.$.secondaryProgress, secondaryRatio); |
| 10562 this._transformProgress(this.$.primaryProgress, mainRatio); |
| 10563 |
| 10564 this.secondaryProgress = secondaryProgress; |
| 10565 |
| 10566 this.setAttribute('aria-valuenow', value); |
| 10567 this.setAttribute('aria-valuemin', min); |
| 10568 this.setAttribute('aria-valuemax', max); |
| 10569 }, |
| 10570 |
| 10571 _disabledChanged: function(disabled) { |
| 10572 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
| 10573 }, |
| 10574 |
| 10575 _hideSecondaryProgress: function(secondaryRatio) { |
| 10576 return secondaryRatio === 0; |
| 10577 } |
| 10578 |
| 10579 }); |
| 10580 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 10581 // Use of this source code is governed by a BSD-style license that can be |
| 10582 // found in the LICENSE file. |
| 10583 |
| 10584 cr.define('downloads', function() { |
| 10585 var Item = Polymer({ |
| 10586 is: 'downloads-item', |
| 10587 |
| 10588 /** |
| 10589 * @param {!downloads.ThrottledIconLoader} iconLoader |
| 10590 * @param {!downloads.ActionService} actionService |
| 10591 */ |
| 10592 factoryImpl: function(iconLoader, actionService) { |
| 10593 /** @private {!downloads.ThrottledIconLoader} */ |
| 10594 this.iconLoader_ = iconLoader; |
| 10595 |
| 10596 /** @private {!downloads.ActionService} */ |
| 10597 this.actionService_ = actionService; |
| 10598 }, |
| 10599 |
| 10600 properties: { |
| 10601 hideDate: { |
| 10602 type: Boolean, |
| 10603 value: true, |
| 10604 }, |
| 10605 |
| 10606 readyPromise: { |
| 10607 type: Object, |
| 10608 value: function() { |
| 10609 return new Promise(function(resolve, reject) { |
| 10610 this.resolveReadyPromise_ = resolve; |
| 10611 }.bind(this)); |
| 10612 }, |
| 10613 }, |
| 10614 |
| 10615 completelyOnDisk_: { |
| 10616 computed: 'computeCompletelyOnDisk_(' + |
| 10617 'data_.state, data_.file_externally_removed)', |
| 10618 type: Boolean, |
| 10619 value: true, |
| 10620 }, |
| 10621 |
| 10622 controlledBy_: { |
| 10623 computed: 'computeControlledBy_(data_.by_ext_id, data_.by_ext_name)', |
| 10624 type: String, |
| 10625 value: '', |
| 10626 }, |
| 10627 |
| 10628 i18n_: { |
| 10629 readOnly: true, |
| 10630 type: Object, |
| 10631 value: function() { |
| 10632 return { |
| 10633 cancel: loadTimeData.getString('controlCancel'), |
| 10634 discard: loadTimeData.getString('dangerDiscard'), |
| 10635 pause: loadTimeData.getString('controlPause'), |
| 10636 remove: loadTimeData.getString('controlRemoveFromList'), |
| 10637 resume: loadTimeData.getString('controlResume'), |
| 10638 restore: loadTimeData.getString('dangerRestore'), |
| 10639 retry: loadTimeData.getString('controlRetry'), |
| 10640 save: loadTimeData.getString('dangerSave'), |
| 10641 }; |
| 10642 }, |
| 10643 }, |
| 10644 |
| 10645 isActive_: { |
| 10646 computed: 'computeIsActive_(' + |
| 10647 'data_.state, data_.file_externally_removed)', |
| 10648 type: Boolean, |
| 10649 value: true, |
| 10650 }, |
| 10651 |
| 10652 isDangerous_: { |
| 10653 computed: 'computeIsDangerous_(data_.state)', |
| 10654 type: Boolean, |
| 10655 value: false, |
| 10656 }, |
| 10657 |
| 10658 isInProgress_: { |
| 10659 computed: 'computeIsInProgress_(data_.state)', |
| 10660 type: Boolean, |
| 10661 value: false, |
| 10662 }, |
| 10663 |
| 10664 showCancel_: { |
| 10665 computed: 'computeShowCancel_(data_.state)', |
| 10666 type: Boolean, |
| 10667 value: false, |
| 10668 }, |
| 10669 |
| 10670 showProgress_: { |
| 10671 computed: 'computeShowProgress_(showCancel_, data_.percent)', |
| 10672 type: Boolean, |
| 10673 value: false, |
| 10674 }, |
| 10675 |
| 10676 isMalware_: { |
| 10677 computed: 'computeIsMalware_(isDangerous_, data_.danger_type)', |
| 10678 type: Boolean, |
| 10679 value: false, |
| 10680 }, |
| 10681 |
| 10682 data_: { |
| 10683 type: Object, |
| 10684 }, |
| 10685 }, |
| 10686 |
| 10687 observers: [ |
| 10688 // TODO(dbeam): this gets called way more when I observe data_.by_ext_id |
| 10689 // and data_.by_ext_name directly. Why? |
| 10690 'observeControlledBy_(controlledBy_)', |
| 10691 ], |
| 10692 |
| 10693 ready: function() { |
| 10694 this.content = this.$.content; |
| 10695 this.resolveReadyPromise_(); |
| 10696 }, |
| 10697 |
| 10698 /** @param {!downloads.Data} data */ |
| 10699 update: function(data) { |
| 10700 this.data_ = data; |
| 10701 |
| 10702 if (!this.isDangerous_) { |
| 10703 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path); |
| 10704 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon); |
| 10705 } |
| 10706 }, |
| 10707 |
| 10708 /** @private */ |
| 10709 computeClass_: function() { |
| 10710 var classes = []; |
| 10711 |
| 10712 if (this.isActive_) |
| 10713 classes.push('is-active'); |
| 10714 |
| 10715 if (this.isDangerous_) |
| 10716 classes.push('dangerous'); |
| 10717 |
| 10718 if (this.showProgress_) |
| 10719 classes.push('show-progress'); |
| 10720 |
| 10721 return classes.join(' '); |
| 10722 }, |
| 10723 |
| 10724 /** @private */ |
| 10725 computeCompletelyOnDisk_: function() { |
| 10726 return this.data_.state == downloads.States.COMPLETE && |
| 10727 !this.data_.file_externally_removed; |
| 10728 }, |
| 10729 |
| 10730 /** @private */ |
| 10731 computeControlledBy_: function() { |
| 10732 if (!this.data_.by_ext_id || !this.data_.by_ext_name) |
| 10733 return ''; |
| 10734 |
| 10735 var url = 'chrome://extensions#' + this.data_.by_ext_id; |
| 10736 var name = this.data_.by_ext_name; |
| 10737 return loadTimeData.getStringF('controlledByUrl', url, name); |
| 10738 }, |
| 10739 |
| 10740 /** @private */ |
| 10741 computeDate_: function() { |
| 10742 if (this.hideDate) |
| 10743 return ''; |
| 10744 return assert(this.data_.since_string || this.data_.date_string); |
| 10745 }, |
| 10746 |
| 10747 /** @private */ |
| 10748 computeDescription_: function() { |
| 10749 var data = this.data_; |
| 10750 |
| 10751 switch (data.state) { |
| 10752 case downloads.States.DANGEROUS: |
| 10753 var fileName = data.file_name; |
| 10754 switch (data.danger_type) { |
| 10755 case downloads.DangerType.DANGEROUS_FILE: |
| 10756 return loadTimeData.getStringF('dangerFileDesc', fileName); |
| 10757 case downloads.DangerType.DANGEROUS_URL: |
| 10758 return loadTimeData.getString('dangerUrlDesc'); |
| 10759 case downloads.DangerType.DANGEROUS_CONTENT: // Fall through. |
| 10760 case downloads.DangerType.DANGEROUS_HOST: |
| 10761 return loadTimeData.getStringF('dangerContentDesc', fileName); |
| 10762 case downloads.DangerType.UNCOMMON_CONTENT: |
| 10763 return loadTimeData.getStringF('dangerUncommonDesc', fileName); |
| 10764 case downloads.DangerType.POTENTIALLY_UNWANTED: |
| 10765 return loadTimeData.getStringF('dangerSettingsDesc', fileName); |
| 10766 } |
| 10767 break; |
| 10768 |
| 10769 case downloads.States.IN_PROGRESS: |
| 10770 case downloads.States.PAUSED: // Fallthrough. |
| 10771 return data.progress_status_text; |
| 10772 } |
| 10773 |
| 10774 return ''; |
| 10775 }, |
| 10776 |
| 10777 /** @private */ |
| 10778 computeIsActive_: function() { |
| 10779 return this.data_.state != downloads.States.CANCELLED && |
| 10780 this.data_.state != downloads.States.INTERRUPTED && |
| 10781 !this.data_.file_externally_removed; |
| 10782 }, |
| 10783 |
| 10784 /** @private */ |
| 10785 computeIsDangerous_: function() { |
| 10786 return this.data_.state == downloads.States.DANGEROUS; |
| 10787 }, |
| 10788 |
| 10789 /** @private */ |
| 10790 computeIsInProgress_: function() { |
| 10791 return this.data_.state == downloads.States.IN_PROGRESS; |
| 10792 }, |
| 10793 |
| 10794 /** @private */ |
| 10795 computeIsMalware_: function() { |
| 10796 return this.isDangerous_ && |
| 10797 (this.data_.danger_type == downloads.DangerType.DANGEROUS_CONTENT || |
| 10798 this.data_.danger_type == downloads.DangerType.DANGEROUS_HOST || |
| 10799 this.data_.danger_type == downloads.DangerType.DANGEROUS_URL || |
| 10800 this.data_.danger_type == downloads.DangerType.POTENTIALLY_UNWANTED); |
| 10801 }, |
| 10802 |
| 10803 /** @private */ |
| 10804 computeRemoveStyle_: function() { |
| 10805 var canDelete = loadTimeData.getBoolean('allowDeletingHistory'); |
| 10806 var hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete; |
| 10807 return hideRemove ? 'visibility: hidden' : ''; |
| 10808 }, |
| 10809 |
| 10810 /** @private */ |
| 10811 computeShowCancel_: function() { |
| 10812 return this.data_.state == downloads.States.IN_PROGRESS || |
| 10813 this.data_.state == downloads.States.PAUSED; |
| 10814 }, |
| 10815 |
| 10816 /** @private */ |
| 10817 computeShowProgress_: function() { |
| 10818 return this.showCancel_ && this.data_.percent >= -1; |
| 10819 }, |
| 10820 |
| 10821 /** @private */ |
| 10822 computeTag_: function() { |
| 10823 switch (this.data_.state) { |
| 10824 case downloads.States.CANCELLED: |
| 10825 return loadTimeData.getString('statusCancelled'); |
| 10826 |
| 10827 case downloads.States.INTERRUPTED: |
| 10828 return this.data_.last_reason_text; |
| 10829 |
| 10830 case downloads.States.COMPLETE: |
| 10831 return this.data_.file_externally_removed ? |
| 10832 loadTimeData.getString('statusRemoved') : ''; |
| 10833 } |
| 10834 |
| 10835 return ''; |
| 10836 }, |
| 10837 |
| 10838 /** @private */ |
| 10839 isIndeterminate_: function() { |
| 10840 return this.data_.percent == -1; |
| 10841 }, |
| 10842 |
| 10843 /** @private */ |
| 10844 observeControlledBy_: function() { |
| 10845 this.$['controlled-by'].innerHTML = this.controlledBy_; |
| 10846 }, |
| 10847 |
| 10848 /** @private */ |
| 10849 onCancelClick_: function() { |
| 10850 this.actionService_.cancel(this.data_.id); |
| 10851 }, |
| 10852 |
| 10853 /** @private */ |
| 10854 onDiscardDangerous_: function() { |
| 10855 this.actionService_.discardDangerous(this.data_.id); |
| 10856 }, |
| 10857 |
| 10858 /** |
| 10859 * @private |
| 10860 * @param {Event} e |
| 10861 */ |
| 10862 onDragStart_: function(e) { |
| 10863 e.preventDefault(); |
| 10864 this.actionService_.drag(this.data_.id); |
| 10865 }, |
| 10866 |
| 10867 /** |
| 10868 * @param {Event} e |
| 10869 * @private |
| 10870 */ |
| 10871 onFileLinkClick_: function(e) { |
| 10872 e.preventDefault(); |
| 10873 this.actionService_.openFile(this.data_.id); |
| 10874 }, |
| 10875 |
| 10876 /** @private */ |
| 10877 onPauseClick_: function() { |
| 10878 this.actionService_.pause(this.data_.id); |
| 10879 }, |
| 10880 |
| 10881 /** @private */ |
| 10882 onRemoveClick_: function() { |
| 10883 this.actionService_.remove(this.data_.id); |
| 10884 }, |
| 10885 |
| 10886 /** @private */ |
| 10887 onResumeClick_: function() { |
| 10888 this.actionService_.resume(this.data_.id); |
| 10889 }, |
| 10890 |
| 10891 /** @private */ |
| 10892 onRetryClick_: function() { |
| 10893 this.actionService_.download(this.$['file-link'].href); |
| 10894 }, |
| 10895 |
| 10896 /** @private */ |
| 10897 onSaveDangerous_: function() { |
| 10898 this.actionService_.saveDangerous(this.data_.id); |
| 10899 }, |
| 10900 |
| 10901 /** @private */ |
| 10902 onShowClick_: function() { |
| 10903 this.actionService_.show(this.data_.id); |
| 10904 }, |
| 10905 }); |
| 10906 |
| 10907 return {Item: Item}; |
| 10908 }); |
| 10909 (function() { |
| 10910 |
| 10911 // monostate data |
| 10912 var metaDatas = {}; |
| 10913 var metaArrays = {}; |
| 10914 |
| 10915 Polymer.IronMeta = Polymer({ |
| 10916 |
| 10917 is: 'iron-meta', |
| 10918 |
| 10919 properties: { |
| 10920 |
| 10921 /** |
| 10922 * The type of meta-data. All meta-data of the same type is stored |
| 10923 * together. |
| 10924 */ |
| 10925 type: { |
| 10926 type: String, |
| 10927 value: 'default', |
| 10928 observer: '_typeChanged' |
| 10929 }, |
| 10930 |
| 10931 /** |
| 10932 * The key used to store `value` under the `type` namespace. |
| 10933 */ |
| 10934 key: { |
| 10935 type: String, |
| 10936 observer: '_keyChanged' |
| 10937 }, |
| 10938 |
| 10939 /** |
| 10940 * The meta-data to store or retrieve. |
| 10941 */ |
| 10942 value: { |
| 10943 type: Object, |
| 10944 notify: true, |
| 10945 observer: '_valueChanged' |
| 10946 }, |
| 10947 |
| 10948 /** |
| 10949 * If true, `value` is set to the iron-meta instance itself. |
| 10950 */ |
| 10951 self: { |
| 10952 type: Boolean, |
| 10953 observer: '_selfChanged' |
| 10954 }, |
| 10955 |
| 10956 /** |
| 10957 * Array of all meta-data values for the given type. |
| 10958 */ |
| 10959 list: { |
| 10960 type: Array, |
| 10961 notify: true |
| 10962 } |
| 10963 |
| 10964 }, |
| 10965 |
| 10966 /** |
| 10967 * Only runs if someone invokes the factory/constructor directly |
| 10968 * e.g. `new Polymer.IronMeta()` |
| 10969 */ |
| 10970 factoryImpl: function(config) { |
| 10971 if (config) { |
| 10972 for (var n in config) { |
| 10973 switch(n) { |
| 10974 case 'type': |
| 10975 case 'key': |
| 10976 case 'value': |
| 10977 this[n] = config[n]; |
| 10978 break; |
| 10979 } |
| 10980 } |
| 10981 } |
| 10982 }, |
| 10983 |
| 10984 created: function() { |
| 10985 // TODO(sjmiles): good for debugging? |
| 10986 this._metaDatas = metaDatas; |
| 10987 this._metaArrays = metaArrays; |
| 10988 }, |
| 10989 |
| 10990 _keyChanged: function(key, old) { |
| 10991 this._resetRegistration(old); |
| 10992 }, |
| 10993 |
| 10994 _valueChanged: function(value) { |
| 10995 this._resetRegistration(this.key); |
| 10996 }, |
| 10997 |
| 10998 _selfChanged: function(self) { |
| 10999 if (self) { |
| 11000 this.value = this; |
| 11001 } |
| 11002 }, |
| 11003 |
| 11004 _typeChanged: function(type) { |
| 11005 this._unregisterKey(this.key); |
| 11006 if (!metaDatas[type]) { |
| 11007 metaDatas[type] = {}; |
| 11008 } |
| 11009 this._metaData = metaDatas[type]; |
| 11010 if (!metaArrays[type]) { |
| 11011 metaArrays[type] = []; |
| 11012 } |
| 11013 this.list = metaArrays[type]; |
| 11014 this._registerKeyValue(this.key, this.value); |
| 11015 }, |
| 11016 |
| 11017 /** |
| 11018 * Retrieves meta data value by key. |
| 11019 * |
| 11020 * @method byKey |
| 11021 * @param {string} key The key of the meta-data to be returned. |
| 11022 * @return {*} |
| 11023 */ |
| 11024 byKey: function(key) { |
| 11025 return this._metaData && this._metaData[key]; |
| 11026 }, |
| 11027 |
| 11028 _resetRegistration: function(oldKey) { |
| 11029 this._unregisterKey(oldKey); |
| 11030 this._registerKeyValue(this.key, this.value); |
| 11031 }, |
| 11032 |
| 11033 _unregisterKey: function(key) { |
| 11034 this._unregister(key, this._metaData, this.list); |
| 11035 }, |
| 11036 |
| 11037 _registerKeyValue: function(key, value) { |
| 11038 this._register(key, value, this._metaData, this.list); |
| 11039 }, |
| 11040 |
| 11041 _register: function(key, value, data, list) { |
| 11042 if (key && data && value !== undefined) { |
| 11043 data[key] = value; |
| 11044 list.push(value); |
| 11045 } |
| 11046 }, |
| 11047 |
| 11048 _unregister: function(key, data, list) { |
| 11049 if (key && data) { |
| 11050 if (key in data) { |
| 11051 var value = data[key]; |
| 11052 delete data[key]; |
| 11053 this.arrayDelete(list, value); |
| 11054 } |
| 11055 } |
| 11056 } |
| 11057 |
| 11058 }); |
| 11059 |
| 11060 /** |
| 11061 `iron-meta-query` can be used to access infomation stored in `iron-meta`. |
| 11062 |
| 11063 Examples: |
| 11064 |
| 11065 If I create an instance like this: |
| 11066 |
| 11067 <iron-meta key="info" value="foo/bar"></iron-meta> |
| 11068 |
| 11069 Note that value="foo/bar" is the metadata I've defined. I could define more |
| 11070 attributes or use child nodes to define additional metadata. |
| 11071 |
| 11072 Now I can access that element (and it's metadata) from any `iron-meta-query`
instance: |
| 11073 |
| 11074 var value = new Polymer.IronMetaQuery({key: 'info'}).value; |
| 11075 |
| 11076 @group Polymer Iron Elements |
| 11077 @element iron-meta-query |
| 11078 */ |
| 11079 Polymer.IronMetaQuery = Polymer({ |
| 11080 |
| 11081 is: 'iron-meta-query', |
| 11082 |
| 11083 properties: { |
| 11084 |
| 11085 /** |
| 11086 * The type of meta-data. All meta-data of the same type is stored |
| 11087 * together. |
| 11088 */ |
| 11089 type: { |
| 11090 type: String, |
| 11091 value: 'default', |
| 11092 observer: '_typeChanged' |
| 11093 }, |
| 11094 |
| 11095 /** |
| 11096 * Specifies a key to use for retrieving `value` from the `type` |
| 11097 * namespace. |
| 11098 */ |
| 11099 key: { |
| 11100 type: String, |
| 11101 observer: '_keyChanged' |
| 11102 }, |
| 11103 |
| 11104 /** |
| 11105 * The meta-data to store or retrieve. |
| 11106 */ |
| 11107 value: { |
| 11108 type: Object, |
| 11109 notify: true, |
| 11110 readOnly: true |
| 11111 }, |
| 11112 |
| 11113 /** |
| 11114 * Array of all meta-data values for the given type. |
| 11115 */ |
| 11116 list: { |
| 11117 type: Array, |
| 11118 notify: true |
| 11119 } |
| 11120 |
| 11121 }, |
| 11122 |
| 11123 /** |
| 11124 * Actually a factory method, not a true constructor. Only runs if |
| 11125 * someone invokes it directly (via `new Polymer.IronMeta()`); |
| 11126 */ |
| 11127 factoryImpl: function(config) { |
| 11128 if (config) { |
| 11129 for (var n in config) { |
| 11130 switch(n) { |
| 11131 case 'type': |
| 11132 case 'key': |
| 11133 this[n] = config[n]; |
| 11134 break; |
| 11135 } |
| 11136 } |
| 11137 } |
| 11138 }, |
| 11139 |
| 11140 created: function() { |
| 11141 // TODO(sjmiles): good for debugging? |
| 11142 this._metaDatas = metaDatas; |
| 11143 this._metaArrays = metaArrays; |
| 11144 }, |
| 11145 |
| 11146 _keyChanged: function(key) { |
| 11147 this._setValue(this._metaData && this._metaData[key]); |
| 11148 }, |
| 11149 |
| 11150 _typeChanged: function(type) { |
| 11151 this._metaData = metaDatas[type]; |
| 11152 this.list = metaArrays[type]; |
| 11153 if (this.key) { |
| 11154 this._keyChanged(this.key); |
| 11155 } |
| 11156 }, |
| 11157 |
| 11158 /** |
| 11159 * Retrieves meta data value by key. |
| 11160 * @param {string} key The key of the meta-data to be returned. |
| 11161 * @return {*} |
| 11162 */ |
| 11163 byKey: function(key) { |
| 11164 return this._metaData && this._metaData[key]; |
| 11165 } |
| 11166 |
| 11167 }); |
| 11168 |
| 11169 })(); |
| 11170 Polymer({ |
| 11171 |
| 11172 is: 'iron-icon', |
| 11173 |
| 11174 properties: { |
| 11175 |
| 11176 /** |
| 11177 * The name of the icon to use. The name should be of the form: |
| 11178 * `iconset_name:icon_name`. |
| 11179 */ |
| 11180 icon: { |
| 11181 type: String, |
| 11182 observer: '_iconChanged' |
| 11183 }, |
| 11184 |
| 11185 /** |
| 11186 * The name of the theme to used, if one is specified by the |
| 11187 * iconset. |
| 11188 */ |
| 11189 theme: { |
| 11190 type: String, |
| 11191 observer: '_updateIcon' |
| 11192 }, |
| 11193 |
| 11194 /** |
| 11195 * If using iron-icon without an iconset, you can set the src to be |
| 11196 * the URL of an individual icon image file. Note that this will take |
| 11197 * precedence over a given icon attribute. |
| 11198 */ |
| 11199 src: { |
| 11200 type: String, |
| 11201 observer: '_srcChanged' |
| 11202 }, |
| 11203 |
| 11204 /** |
| 11205 * @type {!Polymer.IronMeta} |
| 11206 */ |
| 11207 _meta: { |
| 11208 value: Polymer.Base.create('iron-meta', {type: 'iconset'}) |
| 11209 } |
| 11210 |
| 11211 }, |
| 11212 |
| 11213 _DEFAULT_ICONSET: 'icons', |
| 11214 |
| 11215 _iconChanged: function(icon) { |
| 11216 var parts = (icon || '').split(':'); |
| 11217 this._iconName = parts.pop(); |
| 11218 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET; |
| 11219 this._updateIcon(); |
| 11220 }, |
| 11221 |
| 11222 _srcChanged: function(src) { |
| 11223 this._updateIcon(); |
| 11224 }, |
| 11225 |
| 11226 _usesIconset: function() { |
| 11227 return this.icon || !this.src; |
| 11228 }, |
| 11229 |
| 11230 /** @suppress {visibility} */ |
| 11231 _updateIcon: function() { |
| 11232 if (this._usesIconset()) { |
| 11233 if (this._iconsetName) { |
| 11234 this._iconset = /** @type {?Polymer.Iconset} */ ( |
| 11235 this._meta.byKey(this._iconsetName)); |
| 11236 if (this._iconset) { |
| 11237 this._iconset.applyIcon(this, this._iconName, this.theme); |
| 11238 this.unlisten(window, 'iron-iconset-added', '_updateIcon'); |
| 11239 } else { |
| 11240 this.listen(window, 'iron-iconset-added', '_updateIcon'); |
| 11241 } |
| 11242 } |
| 11243 } else { |
| 11244 if (!this._img) { |
| 11245 this._img = document.createElement('img'); |
| 11246 this._img.style.width = '100%'; |
| 11247 this._img.style.height = '100%'; |
| 11248 this._img.draggable = false; |
| 11249 } |
| 11250 this._img.src = this.src; |
| 11251 Polymer.dom(this.root).appendChild(this._img); |
| 11252 } |
| 11253 } |
| 11254 |
| 11255 }); |
| 11256 /** |
| 11257 * The `iron-iconset-svg` element allows users to define their own icon sets |
| 11258 * that contain svg icons. The svg icon elements should be children of the |
| 11259 * `iron-iconset-svg` element. Multiple icons should be given distinct id's. |
| 11260 * |
| 11261 * Using svg elements to create icons has a few advantages over traditional |
| 11262 * bitmap graphics like jpg or png. Icons that use svg are vector based so the
y |
| 11263 * are resolution independent and should look good on any device. They are |
| 11264 * stylable via css. Icons can be themed, colorized, and even animated. |
| 11265 * |
| 11266 * Example: |
| 11267 * |
| 11268 * <iron-iconset-svg name="my-svg-icons" size="24"> |
| 11269 * <svg> |
| 11270 * <defs> |
| 11271 * <g id="shape"> |
| 11272 * <rect x="50" y="50" width="50" height="50" /> |
| 11273 * <circle cx="50" cy="50" r="50" /> |
| 11274 * </g> |
| 11275 * </defs> |
| 11276 * </svg> |
| 11277 * </iron-iconset-svg> |
| 11278 * |
| 11279 * This will automatically register the icon set "my-svg-icons" to the iconset |
| 11280 * database. To use these icons from within another element, make a |
| 11281 * `iron-iconset` element and call the `byId` method |
| 11282 * to retrieve a given iconset. To apply a particular icon inside an |
| 11283 * element use the `applyIcon` method. For example: |
| 11284 * |
| 11285 * iconset.applyIcon(iconNode, 'car'); |
| 11286 * |
| 11287 * @element iron-iconset-svg |
| 11288 * @demo demo/index.html |
| 11289 */ |
| 11290 Polymer({ |
| 11291 |
| 11292 is: 'iron-iconset-svg', |
| 11293 |
| 11294 properties: { |
| 11295 |
| 11296 /** |
| 11297 * The name of the iconset. |
| 11298 * |
| 11299 * @attribute name |
| 11300 * @type string |
| 11301 */ |
| 11302 name: { |
| 11303 type: String, |
| 11304 observer: '_nameChanged' |
| 11305 }, |
| 11306 |
| 11307 /** |
| 11308 * The size of an individual icon. Note that icons must be square. |
| 11309 * |
| 11310 * @attribute iconSize |
| 11311 * @type number |
| 11312 * @default 24 |
| 11313 */ |
| 11314 size: { |
| 11315 type: Number, |
| 11316 value: 24 |
| 11317 } |
| 11318 |
| 11319 }, |
| 11320 |
| 11321 /** |
| 11322 * Construct an array of all icon names in this iconset. |
| 11323 * |
| 11324 * @return {!Array} Array of icon names. |
| 11325 */ |
| 11326 getIconNames: function() { |
| 11327 this._icons = this._createIconMap(); |
| 11328 return Object.keys(this._icons).map(function(n) { |
| 11329 return this.name + ':' + n; |
| 11330 }, this); |
| 11331 }, |
| 11332 |
| 11333 /** |
| 11334 * Applies an icon to the given element. |
| 11335 * |
| 11336 * An svg icon is prepended to the element's shadowRoot if it exists, |
| 11337 * otherwise to the element itself. |
| 11338 * |
| 11339 * @method applyIcon |
| 11340 * @param {Element} element Element to which the icon is applied. |
| 11341 * @param {string} iconName Name of the icon to apply. |
| 11342 * @return {Element} The svg element which renders the icon. |
| 11343 */ |
| 11344 applyIcon: function(element, iconName) { |
| 11345 // insert svg element into shadow root, if it exists |
| 11346 element = element.root || element; |
| 11347 // Remove old svg element |
| 11348 this.removeIcon(element); |
| 11349 // install new svg element |
| 11350 var svg = this._cloneIcon(iconName); |
| 11351 if (svg) { |
| 11352 var pde = Polymer.dom(element); |
| 11353 pde.insertBefore(svg, pde.childNodes[0]); |
| 11354 return element._svgIcon = svg; |
| 11355 } |
| 11356 return null; |
| 11357 }, |
| 11358 |
| 11359 /** |
| 11360 * Remove an icon from the given element by undoing the changes effected |
| 11361 * by `applyIcon`. |
| 11362 * |
| 11363 * @param {Element} element The element from which the icon is removed. |
| 11364 */ |
| 11365 removeIcon: function(element) { |
| 11366 // Remove old svg element |
| 11367 if (element._svgIcon) { |
| 11368 Polymer.dom(element).removeChild(element._svgIcon); |
| 11369 element._svgIcon = null; |
| 11370 } |
| 11371 }, |
| 11372 |
| 11373 /** |
| 11374 * |
| 11375 * When name is changed, register iconset metadata |
| 11376 * |
| 11377 */ |
| 11378 _nameChanged: function() { |
| 11379 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); |
| 11380 this.async(function() { |
| 11381 this.fire('iron-iconset-added', this, {node: window}); |
| 11382 }); |
| 11383 }, |
| 11384 |
| 11385 /** |
| 11386 * Create a map of child SVG elements by id. |
| 11387 * |
| 11388 * @return {!Object} Map of id's to SVG elements. |
| 11389 */ |
| 11390 _createIconMap: function() { |
| 11391 // Objects chained to Object.prototype (`{}`) have members. Specifically, |
| 11392 // on FF there is a `watch` method that confuses the icon map, so we |
| 11393 // need to use a null-based object here. |
| 11394 var icons = Object.create(null); |
| 11395 Polymer.dom(this).querySelectorAll('[id]') |
| 11396 .forEach(function(icon) { |
| 11397 icons[icon.id] = icon; |
| 11398 }); |
| 11399 return icons; |
| 11400 }, |
| 11401 |
| 11402 /** |
| 11403 * Produce installable clone of the SVG element matching `id` in this |
| 11404 * iconset, or `undefined` if there is no matching element. |
| 11405 * |
| 11406 * @return {Element} Returns an installable clone of the SVG element |
| 11407 * matching `id`. |
| 11408 */ |
| 11409 _cloneIcon: function(id) { |
| 11410 // create the icon map on-demand, since the iconset itself has no discrete |
| 11411 // signal to know when it's children are fully parsed |
| 11412 this._icons = this._icons || this._createIconMap(); |
| 11413 return this._prepareSvgClone(this._icons[id], this.size); |
| 11414 }, |
| 11415 |
| 11416 /** |
| 11417 * @param {Element} sourceSvg |
| 11418 * @param {number} size |
| 11419 * @return {Element} |
| 11420 */ |
| 11421 _prepareSvgClone: function(sourceSvg, size) { |
| 11422 if (sourceSvg) { |
| 11423 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
| 11424 svg.setAttribute('viewBox', ['0', '0', size, size].join(' ')); |
| 11425 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
| 11426 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/
370136 |
| 11427 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a
shadow-root |
| 11428 svg.style.cssText = 'pointer-events: none; display: block; width: 100%;
height: 100%;'; |
| 11429 svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id'); |
| 11430 return svg; |
| 11431 } |
| 11432 return null; |
| 11433 } |
| 11434 |
| 11435 }); |
| 11436 Polymer({ |
| 11437 is: 'paper-item', |
| 11438 |
| 11439 hostAttributes: { |
| 11440 role: 'listitem', |
| 11441 tabindex: '0' |
| 11442 }, |
| 11443 |
| 11444 behaviors: [ |
| 11445 Polymer.IronControlState, |
| 11446 Polymer.IronButtonState |
| 11447 ] |
| 11448 }); |
| 11449 /** |
| 11450 * @param {!Function} selectCallback |
| 11451 * @constructor |
| 11452 */ |
| 11453 Polymer.IronSelection = function(selectCallback) { |
| 11454 this.selection = []; |
| 11455 this.selectCallback = selectCallback; |
| 11456 }; |
| 11457 |
| 11458 Polymer.IronSelection.prototype = { |
| 11459 |
| 11460 /** |
| 11461 * Retrieves the selected item(s). |
| 11462 * |
| 11463 * @method get |
| 11464 * @returns Returns the selected item(s). If the multi property is true, |
| 11465 * `get` will return an array, otherwise it will return |
| 11466 * the selected item or undefined if there is no selection. |
| 11467 */ |
| 11468 get: function() { |
| 11469 return this.multi ? this.selection.slice() : this.selection[0]; |
| 11470 }, |
| 11471 |
| 11472 /** |
| 11473 * Clears all the selection except the ones indicated. |
| 11474 * |
| 11475 * @method clear |
| 11476 * @param {Array} excludes items to be excluded. |
| 11477 */ |
| 11478 clear: function(excludes) { |
| 11479 this.selection.slice().forEach(function(item) { |
| 11480 if (!excludes || excludes.indexOf(item) < 0) { |
| 11481 this.setItemSelected(item, false); |
| 11482 } |
| 11483 }, this); |
| 11484 }, |
| 11485 |
| 11486 /** |
| 11487 * Indicates if a given item is selected. |
| 11488 * |
| 11489 * @method isSelected |
| 11490 * @param {*} item The item whose selection state should be checked. |
| 11491 * @returns Returns true if `item` is selected. |
| 11492 */ |
| 11493 isSelected: function(item) { |
| 11494 return this.selection.indexOf(item) >= 0; |
| 11495 }, |
| 11496 |
| 11497 /** |
| 11498 * Sets the selection state for a given item to either selected or deselecte
d. |
| 11499 * |
| 11500 * @method setItemSelected |
| 11501 * @param {*} item The item to select. |
| 11502 * @param {boolean} isSelected True for selected, false for deselected. |
| 11503 */ |
| 11504 setItemSelected: function(item, isSelected) { |
| 11505 if (item != null) { |
| 11506 if (isSelected) { |
| 11507 this.selection.push(item); |
| 11508 } else { |
| 11509 var i = this.selection.indexOf(item); |
| 11510 if (i >= 0) { |
| 11511 this.selection.splice(i, 1); |
| 11512 } |
| 11513 } |
| 11514 if (this.selectCallback) { |
| 11515 this.selectCallback(item, isSelected); |
| 11516 } |
| 11517 } |
| 11518 }, |
| 11519 |
| 11520 /** |
| 11521 * Sets the selection state for a given item. If the `multi` property |
| 11522 * is true, then the selected state of `item` will be toggled; otherwise |
| 11523 * the `item` will be selected. |
| 11524 * |
| 11525 * @method select |
| 11526 * @param {*} item The item to select. |
| 11527 */ |
| 11528 select: function(item) { |
| 11529 if (this.multi) { |
| 11530 this.toggle(item); |
| 11531 } else if (this.get() !== item) { |
| 11532 this.setItemSelected(this.get(), false); |
| 11533 this.setItemSelected(item, true); |
| 11534 } |
| 11535 }, |
| 11536 |
| 11537 /** |
| 11538 * Toggles the selection state for `item`. |
| 11539 * |
| 11540 * @method toggle |
| 11541 * @param {*} item The item to toggle. |
| 11542 */ |
| 11543 toggle: function(item) { |
| 11544 this.setItemSelected(item, !this.isSelected(item)); |
| 11545 } |
| 11546 |
| 11547 }; |
| 11548 /** @polymerBehavior */ |
| 11549 Polymer.IronSelectableBehavior = { |
| 11550 |
| 11551 /** |
| 11552 * Fired when iron-selector is activated (selected or deselected). |
| 11553 * It is fired before the selected items are changed. |
| 11554 * Cancel the event to abort selection. |
| 11555 * |
| 11556 * @event iron-activate |
| 11557 */ |
| 11558 |
| 11559 /** |
| 11560 * Fired when an item is selected |
| 11561 * |
| 11562 * @event iron-select |
| 11563 */ |
| 11564 |
| 11565 /** |
| 11566 * Fired when an item is deselected |
| 11567 * |
| 11568 * @event iron-deselect |
| 11569 */ |
| 11570 |
| 11571 /** |
| 11572 * Fired when the list of selectable items changes (e.g., items are |
| 11573 * added or removed). The detail of the event is a list of mutation |
| 11574 * records that describe what changed. |
| 11575 * |
| 11576 * @event iron-items-changed |
| 11577 */ |
| 11578 |
| 11579 properties: { |
| 11580 |
| 11581 /** |
| 11582 * If you want to use the attribute value of an element for `selected` ins
tead of the index, |
| 11583 * set this to the name of the attribute. |
| 11584 */ |
| 11585 attrForSelected: { |
| 11586 type: String, |
| 11587 value: null |
| 11588 }, |
| 11589 |
| 11590 /** |
| 11591 * Gets or sets the selected element. The default is to use the index of t
he item. |
| 11592 */ |
| 11593 selected: { |
| 11594 type: String, |
| 11595 notify: true |
| 11596 }, |
| 11597 |
| 11598 /** |
| 11599 * Returns the currently selected item. |
| 11600 */ |
| 11601 selectedItem: { |
| 11602 type: Object, |
| 11603 readOnly: true, |
| 11604 notify: true |
| 11605 }, |
| 11606 |
| 11607 /** |
| 11608 * The event that fires from items when they are selected. Selectable |
| 11609 * will listen for this event from items and update the selection state. |
| 11610 * Set to empty string to listen to no events. |
| 11611 */ |
| 11612 activateEvent: { |
| 11613 type: String, |
| 11614 value: 'tap', |
| 11615 observer: '_activateEventChanged' |
| 11616 }, |
| 11617 |
| 11618 /** |
| 11619 * This is a CSS selector string. If this is set, only items that match t
he CSS selector |
| 11620 * are selectable. |
| 11621 */ |
| 11622 selectable: String, |
| 11623 |
| 11624 /** |
| 11625 * The class to set on elements when selected. |
| 11626 */ |
| 11627 selectedClass: { |
| 11628 type: String, |
| 11629 value: 'iron-selected' |
| 11630 }, |
| 11631 |
| 11632 /** |
| 11633 * The attribute to set on elements when selected. |
| 11634 */ |
| 11635 selectedAttribute: { |
| 11636 type: String, |
| 11637 value: null |
| 11638 }, |
| 11639 |
| 11640 /** |
| 11641 * The set of excluded elements where the key is the `localName` |
| 11642 * of the element that will be ignored from the item list. |
| 11643 * |
| 11644 * @type {object} |
| 11645 * @default {template: 1} |
| 11646 */ |
| 11647 excludedLocalNames: { |
| 11648 type: Object, |
| 11649 value: function() { |
| 11650 return { |
| 11651 'template': 1 |
| 11652 }; |
| 11653 } |
| 11654 } |
| 11655 }, |
| 11656 |
| 11657 observers: [ |
| 11658 '_updateSelected(attrForSelected, selected)' |
| 11659 ], |
| 11660 |
| 11661 created: function() { |
| 11662 this._bindFilterItem = this._filterItem.bind(this); |
| 11663 this._selection = new Polymer.IronSelection(this._applySelection.bind(this
)); |
| 11664 }, |
| 11665 |
| 11666 attached: function() { |
| 11667 this._observer = this._observeItems(this); |
| 11668 this._contentObserver = this._observeContent(this); |
| 11669 if (!this.selectedItem && this.selected) { |
| 11670 this._updateSelected(this.attrForSelected,this.selected) |
| 11671 } |
| 11672 }, |
| 11673 |
| 11674 detached: function() { |
| 11675 if (this._observer) { |
| 11676 this._observer.disconnect(); |
| 11677 } |
| 11678 if (this._contentObserver) { |
| 11679 this._contentObserver.disconnect(); |
| 11680 } |
| 11681 this._removeListener(this.activateEvent); |
| 11682 }, |
| 11683 |
| 11684 /** |
| 11685 * Returns an array of selectable items. |
| 11686 * |
| 11687 * @property items |
| 11688 * @type Array |
| 11689 */ |
| 11690 get items() { |
| 11691 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable ||
'*'); |
| 11692 return Array.prototype.filter.call(nodes, this._bindFilterItem); |
| 11693 }, |
| 11694 |
| 11695 /** |
| 11696 * Returns the index of the given item. |
| 11697 * |
| 11698 * @method indexOf |
| 11699 * @param {Object} item |
| 11700 * @returns Returns the index of the item |
| 11701 */ |
| 11702 indexOf: function(item) { |
| 11703 return this.items.indexOf(item); |
| 11704 }, |
| 11705 |
| 11706 /** |
| 11707 * Selects the given value. |
| 11708 * |
| 11709 * @method select |
| 11710 * @param {string} value the value to select. |
| 11711 */ |
| 11712 select: function(value) { |
| 11713 this.selected = value; |
| 11714 }, |
| 11715 |
| 11716 /** |
| 11717 * Selects the previous item. |
| 11718 * |
| 11719 * @method selectPrevious |
| 11720 */ |
| 11721 selectPrevious: function() { |
| 11722 var length = this.items.length; |
| 11723 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len
gth; |
| 11724 this.selected = this._indexToValue(index); |
| 11725 }, |
| 11726 |
| 11727 /** |
| 11728 * Selects the next item. |
| 11729 * |
| 11730 * @method selectNext |
| 11731 */ |
| 11732 selectNext: function() { |
| 11733 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l
ength; |
| 11734 this.selected = this._indexToValue(index); |
| 11735 }, |
| 11736 |
| 11737 _addListener: function(eventName) { |
| 11738 this.listen(this, eventName, '_activateHandler'); |
| 11739 }, |
| 11740 |
| 11741 _removeListener: function(eventName) { |
| 11742 this.unlisten(this, eventName, '_activateHandler'); |
| 11743 }, |
| 11744 |
| 11745 _activateEventChanged: function(eventName, old) { |
| 11746 this._removeListener(old); |
| 11747 this._addListener(eventName); |
| 11748 }, |
| 11749 |
| 11750 _updateSelected: function() { |
| 11751 this._selectSelected(this.selected); |
| 11752 }, |
| 11753 |
| 11754 _selectSelected: function(selected) { |
| 11755 this._selection.select(this._valueToItem(this.selected)); |
| 11756 }, |
| 11757 |
| 11758 _filterItem: function(node) { |
| 11759 return !this.excludedLocalNames[node.localName]; |
| 11760 }, |
| 11761 |
| 11762 _valueToItem: function(value) { |
| 11763 return (value == null) ? null : this.items[this._valueToIndex(value)]; |
| 11764 }, |
| 11765 |
| 11766 _valueToIndex: function(value) { |
| 11767 if (this.attrForSelected) { |
| 11768 for (var i = 0, item; item = this.items[i]; i++) { |
| 11769 if (this._valueForItem(item) == value) { |
| 11770 return i; |
| 11771 } |
| 11772 } |
| 11773 } else { |
| 11774 return Number(value); |
| 11775 } |
| 11776 }, |
| 11777 |
| 11778 _indexToValue: function(index) { |
| 11779 if (this.attrForSelected) { |
| 11780 var item = this.items[index]; |
| 11781 if (item) { |
| 11782 return this._valueForItem(item); |
| 11783 } |
| 11784 } else { |
| 11785 return index; |
| 11786 } |
| 11787 }, |
| 11788 |
| 11789 _valueForItem: function(item) { |
| 11790 return item[this.attrForSelected] || item.getAttribute(this.attrForSelecte
d); |
| 11791 }, |
| 11792 |
| 11793 _applySelection: function(item, isSelected) { |
| 11794 if (this.selectedClass) { |
| 11795 this.toggleClass(this.selectedClass, isSelected, item); |
| 11796 } |
| 11797 if (this.selectedAttribute) { |
| 11798 this.toggleAttribute(this.selectedAttribute, isSelected, item); |
| 11799 } |
| 11800 this._selectionChange(); |
| 11801 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); |
| 11802 }, |
| 11803 |
| 11804 _selectionChange: function() { |
| 11805 this._setSelectedItem(this._selection.get()); |
| 11806 }, |
| 11807 |
| 11808 // observe content changes under the given node. |
| 11809 _observeContent: function(node) { |
| 11810 var content = node.querySelector('content'); |
| 11811 if (content && content.parentElement === node) { |
| 11812 return this._observeItems(node.domHost); |
| 11813 } |
| 11814 }, |
| 11815 |
| 11816 // observe items change under the given node. |
| 11817 _observeItems: function(node) { |
| 11818 // TODO(cdata): Update this when we get distributed children changed. |
| 11819 var observer = new MutationObserver(function(mutations) { |
| 11820 // Let other interested parties know about the change so that |
| 11821 // we don't have to recreate mutation observers everywher. |
| 11822 this.fire('iron-items-changed', mutations, { |
| 11823 bubbles: false, |
| 11824 cancelable: false |
| 11825 }); |
| 11826 |
| 11827 if (this.selected != null) { |
| 11828 this._updateSelected(); |
| 11829 } |
| 11830 }.bind(this)); |
| 11831 observer.observe(node, { |
| 11832 childList: true, |
| 11833 subtree: true |
| 11834 }); |
| 11835 return observer; |
| 11836 }, |
| 11837 |
| 11838 _activateHandler: function(e) { |
| 11839 var t = e.target; |
| 11840 var items = this.items; |
| 11841 while (t && t != this) { |
| 11842 var i = items.indexOf(t); |
| 11843 if (i >= 0) { |
| 11844 var value = this._indexToValue(i); |
| 11845 this._itemActivate(value, t); |
| 11846 return; |
| 11847 } |
| 11848 t = t.parentNode; |
| 11849 } |
| 11850 }, |
| 11851 |
| 11852 _itemActivate: function(value, item) { |
| 11853 if (!this.fire('iron-activate', |
| 11854 {selected: value, item: item}, {cancelable: true}).defaultPrevented) { |
| 11855 this.select(value); |
| 11856 } |
| 11857 } |
| 11858 |
| 11859 }; |
| 11860 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ |
| 11861 Polymer.IronMultiSelectableBehaviorImpl = { |
| 11862 properties: { |
| 11863 |
| 11864 /** |
| 11865 * If true, multiple selections are allowed. |
| 11866 */ |
| 11867 multi: { |
| 11868 type: Boolean, |
| 11869 value: false, |
| 11870 observer: 'multiChanged' |
| 11871 }, |
| 11872 |
| 11873 /** |
| 11874 * Gets or sets the selected elements. This is used instead of `selected`
when `multi` |
| 11875 * is true. |
| 11876 */ |
| 11877 selectedValues: { |
| 11878 type: Array, |
| 11879 notify: true |
| 11880 }, |
| 11881 |
| 11882 /** |
| 11883 * Returns an array of currently selected items. |
| 11884 */ |
| 11885 selectedItems: { |
| 11886 type: Array, |
| 11887 readOnly: true, |
| 11888 notify: true |
| 11889 }, |
| 11890 |
| 11891 }, |
| 11892 |
| 11893 observers: [ |
| 11894 '_updateSelected(attrForSelected, selectedValues)' |
| 11895 ], |
| 11896 |
| 11897 /** |
| 11898 * Selects the given value. If the `multi` property is true, then the select
ed state of the |
| 11899 * `value` will be toggled; otherwise the `value` will be selected. |
| 11900 * |
| 11901 * @method select |
| 11902 * @param {string} value the value to select. |
| 11903 */ |
| 11904 select: function(value) { |
| 11905 if (this.multi) { |
| 11906 if (this.selectedValues) { |
| 11907 this._toggleSelected(value); |
| 11908 } else { |
| 11909 this.selectedValues = [value]; |
| 11910 } |
| 11911 } else { |
| 11912 this.selected = value; |
| 11913 } |
| 11914 }, |
| 11915 |
| 11916 multiChanged: function(multi) { |
| 11917 this._selection.multi = multi; |
| 11918 }, |
| 11919 |
| 11920 _updateSelected: function() { |
| 11921 if (this.multi) { |
| 11922 this._selectMulti(this.selectedValues); |
| 11923 } else { |
| 11924 this._selectSelected(this.selected); |
| 11925 } |
| 11926 }, |
| 11927 |
| 11928 _selectMulti: function(values) { |
| 11929 this._selection.clear(); |
| 11930 if (values) { |
| 11931 for (var i = 0; i < values.length; i++) { |
| 11932 this._selection.setItemSelected(this._valueToItem(values[i]), true); |
| 11933 } |
| 11934 } |
| 11935 }, |
| 11936 |
| 11937 _selectionChange: function() { |
| 11938 var s = this._selection.get(); |
| 11939 if (this.multi) { |
| 11940 this._setSelectedItems(s); |
| 11941 } else { |
| 11942 this._setSelectedItems([s]); |
| 11943 this._setSelectedItem(s); |
| 11944 } |
| 11945 }, |
| 11946 |
| 11947 _toggleSelected: function(value) { |
| 11948 var i = this.selectedValues.indexOf(value); |
| 11949 var unselected = i < 0; |
| 11950 if (unselected) { |
| 11951 this.push('selectedValues',value); |
| 11952 } else { |
| 11953 this.splice('selectedValues',i,1); |
| 11954 } |
| 11955 this._selection.setItemSelected(this._valueToItem(value), unselected); |
| 11956 } |
| 11957 }; |
| 11958 |
| 11959 /** @polymerBehavior */ |
| 11960 Polymer.IronMultiSelectableBehavior = [ |
| 11961 Polymer.IronSelectableBehavior, |
| 11962 Polymer.IronMultiSelectableBehaviorImpl |
| 11963 ]; |
| 11964 /** |
| 11965 * `Polymer.IronMenuBehavior` implements accessible menu behavior. |
| 11966 * |
| 11967 * @demo demo/index.html |
| 11968 * @polymerBehavior Polymer.IronMenuBehavior |
| 11969 */ |
| 11970 Polymer.IronMenuBehaviorImpl = { |
| 11971 |
| 11972 properties: { |
| 11973 |
| 11974 /** |
| 11975 * Returns the currently focused item. |
| 11976 * @type {?Object} |
| 11977 */ |
| 11978 focusedItem: { |
| 11979 observer: '_focusedItemChanged', |
| 11980 readOnly: true, |
| 11981 type: Object |
| 11982 }, |
| 11983 |
| 11984 /** |
| 11985 * The attribute to use on menu items to look up the item title. Typing th
e first |
| 11986 * letter of an item when the menu is open focuses that item. If unset, `t
extContent` |
| 11987 * will be used. |
| 11988 */ |
| 11989 attrForItemTitle: { |
| 11990 type: String |
| 11991 } |
| 11992 }, |
| 11993 |
| 11994 hostAttributes: { |
| 11995 'role': 'menu', |
| 11996 'tabindex': '0' |
| 11997 }, |
| 11998 |
| 11999 observers: [ |
| 12000 '_updateMultiselectable(multi)' |
| 12001 ], |
| 12002 |
| 12003 listeners: { |
| 12004 'focus': '_onFocus', |
| 12005 'keydown': '_onKeydown', |
| 12006 'iron-items-changed': '_onIronItemsChanged' |
| 12007 }, |
| 12008 |
| 12009 keyBindings: { |
| 12010 'up': '_onUpKey', |
| 12011 'down': '_onDownKey', |
| 12012 'esc': '_onEscKey', |
| 12013 'shift+tab:keydown': '_onShiftTabDown' |
| 12014 }, |
| 12015 |
| 12016 attached: function() { |
| 12017 this._resetTabindices(); |
| 12018 }, |
| 12019 |
| 12020 /** |
| 12021 * Selects the given value. If the `multi` property is true, then the select
ed state of the |
| 12022 * `value` will be toggled; otherwise the `value` will be selected. |
| 12023 * |
| 12024 * @param {string} value the value to select. |
| 12025 */ |
| 12026 select: function(value) { |
| 12027 if (this._defaultFocusAsync) { |
| 12028 this.cancelAsync(this._defaultFocusAsync); |
| 12029 this._defaultFocusAsync = null; |
| 12030 } |
| 12031 var item = this._valueToItem(value); |
| 12032 if (item && item.hasAttribute('disabled')) return; |
| 12033 this._setFocusedItem(item); |
| 12034 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); |
| 12035 }, |
| 12036 |
| 12037 /** |
| 12038 * Resets all tabindex attributes to the appropriate value based on the |
| 12039 * current selection state. The appropriate value is `0` (focusable) for |
| 12040 * the default selected item, and `-1` (not keyboard focusable) for all |
| 12041 * other items. |
| 12042 */ |
| 12043 _resetTabindices: function() { |
| 12044 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[
0]) : this.selectedItem; |
| 12045 |
| 12046 this.items.forEach(function(item) { |
| 12047 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); |
| 12048 }, this); |
| 12049 }, |
| 12050 |
| 12051 /** |
| 12052 * Sets appropriate ARIA based on whether or not the menu is meant to be |
| 12053 * multi-selectable. |
| 12054 * |
| 12055 * @param {boolean} multi True if the menu should be multi-selectable. |
| 12056 */ |
| 12057 _updateMultiselectable: function(multi) { |
| 12058 if (multi) { |
| 12059 this.setAttribute('aria-multiselectable', 'true'); |
| 12060 } else { |
| 12061 this.removeAttribute('aria-multiselectable'); |
| 12062 } |
| 12063 }, |
| 12064 |
| 12065 /** |
| 12066 * Given a KeyboardEvent, this method will focus the appropriate item in the |
| 12067 * menu (if there is a relevant item, and it is possible to focus it). |
| 12068 * |
| 12069 * @param {KeyboardEvent} event A KeyboardEvent. |
| 12070 */ |
| 12071 _focusWithKeyboardEvent: function(event) { |
| 12072 for (var i = 0, item; item = this.items[i]; i++) { |
| 12073 var attr = this.attrForItemTitle || 'textContent'; |
| 12074 var title = item[attr] || item.getAttribute(attr); |
| 12075 if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCod
e(event.keyCode).toLowerCase()) { |
| 12076 this._setFocusedItem(item); |
| 12077 break; |
| 12078 } |
| 12079 } |
| 12080 }, |
| 12081 |
| 12082 /** |
| 12083 * Focuses the previous item (relative to the currently focused item) in the |
| 12084 * menu. |
| 12085 */ |
| 12086 _focusPrevious: function() { |
| 12087 var length = this.items.length; |
| 12088 var index = (Number(this.indexOf(this.focusedItem)) - 1 + length) % length
; |
| 12089 this._setFocusedItem(this.items[index]); |
| 12090 }, |
| 12091 |
| 12092 /** |
| 12093 * Focuses the next item (relative to the currently focused item) in the |
| 12094 * menu. |
| 12095 */ |
| 12096 _focusNext: function() { |
| 12097 var index = (Number(this.indexOf(this.focusedItem)) + 1) % this.items.leng
th; |
| 12098 this._setFocusedItem(this.items[index]); |
| 12099 }, |
| 12100 |
| 12101 /** |
| 12102 * Mutates items in the menu based on provided selection details, so that |
| 12103 * all items correctly reflect selection state. |
| 12104 * |
| 12105 * @param {Element} item An item in the menu. |
| 12106 * @param {boolean} isSelected True if the item should be shown in a |
| 12107 * selected state, otherwise false. |
| 12108 */ |
| 12109 _applySelection: function(item, isSelected) { |
| 12110 if (isSelected) { |
| 12111 item.setAttribute('aria-selected', 'true'); |
| 12112 } else { |
| 12113 item.removeAttribute('aria-selected'); |
| 12114 } |
| 12115 |
| 12116 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); |
| 12117 }, |
| 12118 |
| 12119 /** |
| 12120 * Discretely updates tabindex values among menu items as the focused item |
| 12121 * changes. |
| 12122 * |
| 12123 * @param {Element} focusedItem The element that is currently focused. |
| 12124 * @param {?Element} old The last element that was considered focused, if |
| 12125 * applicable. |
| 12126 */ |
| 12127 _focusedItemChanged: function(focusedItem, old) { |
| 12128 old && old.setAttribute('tabindex', '-1'); |
| 12129 if (focusedItem) { |
| 12130 focusedItem.setAttribute('tabindex', '0'); |
| 12131 focusedItem.focus(); |
| 12132 } |
| 12133 }, |
| 12134 |
| 12135 /** |
| 12136 * A handler that responds to mutation changes related to the list of items |
| 12137 * in the menu. |
| 12138 * |
| 12139 * @param {CustomEvent} event An event containing mutation records as its |
| 12140 * detail. |
| 12141 */ |
| 12142 _onIronItemsChanged: function(event) { |
| 12143 var mutations = event.detail; |
| 12144 var mutation; |
| 12145 var index; |
| 12146 |
| 12147 for (index = 0; index < mutations.length; ++index) { |
| 12148 mutation = mutations[index]; |
| 12149 |
| 12150 if (mutation.addedNodes.length) { |
| 12151 this._resetTabindices(); |
| 12152 break; |
| 12153 } |
| 12154 } |
| 12155 }, |
| 12156 |
| 12157 /** |
| 12158 * Handler that is called when a shift+tab keypress is detected by the menu. |
| 12159 * |
| 12160 * @param {CustomEvent} event A key combination event. |
| 12161 */ |
| 12162 _onShiftTabDown: function(event) { |
| 12163 var oldTabIndex; |
| 12164 |
| 12165 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; |
| 12166 |
| 12167 oldTabIndex = this.getAttribute('tabindex'); |
| 12168 |
| 12169 this.setAttribute('tabindex', '-1'); |
| 12170 |
| 12171 this.async(function() { |
| 12172 this.setAttribute('tabindex', oldTabIndex); |
| 12173 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
| 12174 // NOTE(cdata): polymer/polymer#1305 |
| 12175 }, 1); |
| 12176 }, |
| 12177 |
| 12178 /** |
| 12179 * Handler that is called when the menu receives focus. |
| 12180 * |
| 12181 * @param {FocusEvent} event A focus event. |
| 12182 */ |
| 12183 _onFocus: function(event) { |
| 12184 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { |
| 12185 return; |
| 12186 } |
| 12187 // do not focus the menu itself |
| 12188 this.blur(); |
| 12189 // clear the cached focus item |
| 12190 this._setFocusedItem(null); |
| 12191 this._defaultFocusAsync = this.async(function() { |
| 12192 // focus the selected item when the menu receives focus, or the first it
em |
| 12193 // if no item is selected |
| 12194 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem
s[0]) : this.selectedItem; |
| 12195 if (selectedItem) { |
| 12196 this._setFocusedItem(selectedItem); |
| 12197 } else { |
| 12198 this._setFocusedItem(this.items[0]); |
| 12199 } |
| 12200 // async 100ms to wait for `select` to get called from `_itemActivate` |
| 12201 }, 100); |
| 12202 }, |
| 12203 |
| 12204 /** |
| 12205 * Handler that is called when the up key is pressed. |
| 12206 * |
| 12207 * @param {CustomEvent} event A key combination event. |
| 12208 */ |
| 12209 _onUpKey: function(event) { |
| 12210 // up and down arrows moves the focus |
| 12211 this._focusPrevious(); |
| 12212 }, |
| 12213 |
| 12214 /** |
| 12215 * Handler that is called when the down key is pressed. |
| 12216 * |
| 12217 * @param {CustomEvent} event A key combination event. |
| 12218 */ |
| 12219 _onDownKey: function(event) { |
| 12220 this._focusNext(); |
| 12221 }, |
| 12222 |
| 12223 /** |
| 12224 * Handler that is called when the esc key is pressed. |
| 12225 * |
| 12226 * @param {CustomEvent} event A key combination event. |
| 12227 */ |
| 12228 _onEscKey: function(event) { |
| 12229 // esc blurs the control |
| 12230 this.focusedItem.blur(); |
| 12231 }, |
| 12232 |
| 12233 /** |
| 12234 * Handler that is called when a keydown event is detected. |
| 12235 * |
| 12236 * @param {KeyboardEvent} event A keyboard event. |
| 12237 */ |
| 12238 _onKeydown: function(event) { |
| 12239 if (this.keyboardEventMatchesKeys(event, 'up down esc')) { |
| 12240 return; |
| 12241 } |
| 12242 |
| 12243 // all other keys focus the menu item starting with that character |
| 12244 this._focusWithKeyboardEvent(event); |
| 12245 } |
| 12246 }; |
| 12247 |
| 12248 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
| 12249 |
| 12250 /** @polymerBehavior Polymer.IronMenuBehavior */ |
| 12251 Polymer.IronMenuBehavior = [ |
| 12252 Polymer.IronMultiSelectableBehavior, |
| 12253 Polymer.IronA11yKeysBehavior, |
| 12254 Polymer.IronMenuBehaviorImpl |
| 12255 ]; |
| 12256 (function() { |
| 12257 |
| 12258 Polymer({ |
| 12259 |
| 12260 is: 'paper-menu', |
| 12261 |
| 12262 behaviors: [ |
| 12263 Polymer.IronMenuBehavior |
| 12264 ] |
| 12265 |
| 12266 }); |
| 12267 |
| 12268 })(); |
| 12269 /** |
| 12270 * `IronResizableBehavior` is a behavior that can be used in Polymer elements
to |
| 12271 * coordinate the flow of resize events between "resizers" (elements that cont
rol the |
| 12272 * size or hidden state of their children) and "resizables" (elements that nee
d to be |
| 12273 * notified when they are resized or un-hidden by their parents in order to ta
ke |
| 12274 * action on their new measurements). |
| 12275 * Elements that perform measurement should add the `IronResizableBehavior` be
havior to |
| 12276 * their element definition and listen for the `iron-resize` event on themselv
es. |
| 12277 * This event will be fired when they become showing after having been hidden, |
| 12278 * when they are resized explicitly by another resizable, or when the window h
as been |
| 12279 * resized. |
| 12280 * Note, the `iron-resize` event is non-bubbling. |
| 12281 * |
| 12282 * @polymerBehavior Polymer.IronResizableBehavior |
| 12283 * @demo demo/index.html |
| 12284 **/ |
| 12285 Polymer.IronResizableBehavior = { |
| 12286 properties: { |
| 12287 /** |
| 12288 * The closest ancestor element that implements `IronResizableBehavior`. |
| 12289 */ |
| 12290 _parentResizable: { |
| 12291 type: Object, |
| 12292 observer: '_parentResizableChanged' |
| 12293 }, |
| 12294 |
| 12295 /** |
| 12296 * True if this element is currently notifying its descedant elements of |
| 12297 * resize. |
| 12298 */ |
| 12299 _notifyingDescendant: { |
| 12300 type: Boolean, |
| 12301 value: false |
| 12302 } |
| 12303 }, |
| 12304 |
| 12305 listeners: { |
| 12306 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' |
| 12307 }, |
| 12308 |
| 12309 created: function() { |
| 12310 // We don't really need property effects on these, and also we want them |
| 12311 // to be created before the `_parentResizable` observer fires: |
| 12312 this._interestedResizables = []; |
| 12313 this._boundNotifyResize = this.notifyResize.bind(this); |
| 12314 }, |
| 12315 |
| 12316 attached: function() { |
| 12317 this.fire('iron-request-resize-notifications', null, { |
| 12318 node: this, |
| 12319 bubbles: true, |
| 12320 cancelable: true |
| 12321 }); |
| 12322 |
| 12323 if (!this._parentResizable) { |
| 12324 window.addEventListener('resize', this._boundNotifyResize); |
| 12325 this.notifyResize(); |
| 12326 } |
| 12327 }, |
| 12328 |
| 12329 detached: function() { |
| 12330 if (this._parentResizable) { |
| 12331 this._parentResizable.stopResizeNotificationsFor(this); |
| 12332 } else { |
| 12333 window.removeEventListener('resize', this._boundNotifyResize); |
| 12334 } |
| 12335 |
| 12336 this._parentResizable = null; |
| 12337 }, |
| 12338 |
| 12339 /** |
| 12340 * Can be called to manually notify a resizable and its descendant |
| 12341 * resizables of a resize change. |
| 12342 */ |
| 12343 notifyResize: function() { |
| 12344 if (!this.isAttached) { |
| 12345 return; |
| 12346 } |
| 12347 |
| 12348 this._interestedResizables.forEach(function(resizable) { |
| 12349 if (this.resizerShouldNotify(resizable)) { |
| 12350 this._notifyDescendant(resizable); |
| 12351 } |
| 12352 }, this); |
| 12353 |
| 12354 this._fireResize(); |
| 12355 }, |
| 12356 |
| 12357 /** |
| 12358 * Used to assign the closest resizable ancestor to this resizable |
| 12359 * if the ancestor detects a request for notifications. |
| 12360 */ |
| 12361 assignParentResizable: function(parentResizable) { |
| 12362 this._parentResizable = parentResizable; |
| 12363 }, |
| 12364 |
| 12365 /** |
| 12366 * Used to remove a resizable descendant from the list of descendants |
| 12367 * that should be notified of a resize change. |
| 12368 */ |
| 12369 stopResizeNotificationsFor: function(target) { |
| 12370 var index = this._interestedResizables.indexOf(target); |
| 12371 |
| 12372 if (index > -1) { |
| 12373 this._interestedResizables.splice(index, 1); |
| 12374 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); |
| 12375 } |
| 12376 }, |
| 12377 |
| 12378 /** |
| 12379 * This method can be overridden to filter nested elements that should or |
| 12380 * should not be notified by the current element. Return true if an element |
| 12381 * should be notified, or false if it should not be notified. |
| 12382 * |
| 12383 * @param {HTMLElement} element A candidate descendant element that |
| 12384 * implements `IronResizableBehavior`. |
| 12385 * @return {boolean} True if the `element` should be notified of resize. |
| 12386 */ |
| 12387 resizerShouldNotify: function(element) { return true; }, |
| 12388 |
| 12389 _onDescendantIronResize: function(event) { |
| 12390 if (this._notifyingDescendant) { |
| 12391 event.stopPropagation(); |
| 12392 return; |
| 12393 } |
| 12394 |
| 12395 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the |
| 12396 // otherwise non-bubbling event "just work." We do it manually here for |
| 12397 // the case where Polymer is not using shadow roots for whatever reason: |
| 12398 if (!Polymer.Settings.useShadow) { |
| 12399 this._fireResize(); |
| 12400 } |
| 12401 }, |
| 12402 |
| 12403 _fireResize: function() { |
| 12404 this.fire('iron-resize', null, { |
| 12405 node: this, |
| 12406 bubbles: false |
| 12407 }); |
| 12408 }, |
| 12409 |
| 12410 _onIronRequestResizeNotifications: function(event) { |
| 12411 var target = event.path ? event.path[0] : event.target; |
| 12412 |
| 12413 if (target === this) { |
| 12414 return; |
| 12415 } |
| 12416 |
| 12417 if (this._interestedResizables.indexOf(target) === -1) { |
| 12418 this._interestedResizables.push(target); |
| 12419 this.listen(target, 'iron-resize', '_onDescendantIronResize'); |
| 12420 } |
| 12421 |
| 12422 target.assignParentResizable(this); |
| 12423 this._notifyDescendant(target); |
| 12424 |
| 12425 event.stopPropagation(); |
| 12426 }, |
| 12427 |
| 12428 _parentResizableChanged: function(parentResizable) { |
| 12429 if (parentResizable) { |
| 12430 window.removeEventListener('resize', this._boundNotifyResize); |
| 12431 } |
| 12432 }, |
| 12433 |
| 12434 _notifyDescendant: function(descendant) { |
| 12435 // NOTE(cdata): In IE10, attached is fired on children first, so it's |
| 12436 // important not to notify them if the parent is not attached yet (or |
| 12437 // else they will get redundantly notified when the parent attaches). |
| 12438 if (!this.isAttached) { |
| 12439 return; |
| 12440 } |
| 12441 |
| 12442 this._notifyingDescendant = true; |
| 12443 descendant.notifyResize(); |
| 12444 this._notifyingDescendant = false; |
| 12445 } |
| 12446 }; |
| 12447 /** |
| 12448 Polymer.IronFitBehavior fits an element in another element using `max-height` an
d `max-width`, and |
| 12449 optionally centers it in the window or another element. |
| 12450 |
| 12451 The element will only be sized and/or positioned if it has not already been size
d and/or positioned |
| 12452 by CSS. |
| 12453 |
| 12454 CSS properties | Action |
| 12455 -----------------------------|------------------------------------------- |
| 12456 `position` set | Element is not centered horizontally or verticall
y |
| 12457 `top` or `bottom` set | Element is not vertically centered |
| 12458 `left` or `right` set | Element is not horizontally centered |
| 12459 `max-height` or `height` set | Element respects `max-height` or `height` |
| 12460 `max-width` or `width` set | Element respects `max-width` or `width` |
| 12461 |
| 12462 @demo demo/index.html |
| 12463 @polymerBehavior |
| 12464 */ |
| 12465 |
| 12466 Polymer.IronFitBehavior = { |
| 12467 |
| 12468 properties: { |
| 12469 |
| 12470 /** |
| 12471 * The element that will receive a `max-height`/`width`. By default it is
the same as `this`, |
| 12472 * but it can be set to a child element. This is useful, for example, for
implementing a |
| 12473 * scrolling region inside the element. |
| 12474 * @type {!Element} |
| 12475 */ |
| 12476 sizingTarget: { |
| 12477 type: Object, |
| 12478 value: function() { |
| 12479 return this; |
| 12480 } |
| 12481 }, |
| 12482 |
| 12483 /** |
| 12484 * The element to fit `this` into. |
| 12485 */ |
| 12486 fitInto: { |
| 12487 type: Object, |
| 12488 value: window |
| 12489 }, |
| 12490 |
| 12491 /** |
| 12492 * Set to true to auto-fit on attach. |
| 12493 */ |
| 12494 autoFitOnAttach: { |
| 12495 type: Boolean, |
| 12496 value: false |
| 12497 }, |
| 12498 |
| 12499 /** @type {?Object} */ |
| 12500 _fitInfo: { |
| 12501 type: Object |
| 12502 } |
| 12503 |
| 12504 }, |
| 12505 |
| 12506 get _fitWidth() { |
| 12507 var fitWidth; |
| 12508 if (this.fitInto === window) { |
| 12509 fitWidth = this.fitInto.innerWidth; |
| 12510 } else { |
| 12511 fitWidth = this.fitInto.getBoundingClientRect().width; |
| 12512 } |
| 12513 return fitWidth; |
| 12514 }, |
| 12515 |
| 12516 get _fitHeight() { |
| 12517 var fitHeight; |
| 12518 if (this.fitInto === window) { |
| 12519 fitHeight = this.fitInto.innerHeight; |
| 12520 } else { |
| 12521 fitHeight = this.fitInto.getBoundingClientRect().height; |
| 12522 } |
| 12523 return fitHeight; |
| 12524 }, |
| 12525 |
| 12526 get _fitLeft() { |
| 12527 var fitLeft; |
| 12528 if (this.fitInto === window) { |
| 12529 fitLeft = 0; |
| 12530 } else { |
| 12531 fitLeft = this.fitInto.getBoundingClientRect().left; |
| 12532 } |
| 12533 return fitLeft; |
| 12534 }, |
| 12535 |
| 12536 get _fitTop() { |
| 12537 var fitTop; |
| 12538 if (this.fitInto === window) { |
| 12539 fitTop = 0; |
| 12540 } else { |
| 12541 fitTop = this.fitInto.getBoundingClientRect().top; |
| 12542 } |
| 12543 return fitTop; |
| 12544 }, |
| 12545 |
| 12546 attached: function() { |
| 12547 if (this.autoFitOnAttach) { |
| 12548 if (window.getComputedStyle(this).display === 'none') { |
| 12549 setTimeout(function() { |
| 12550 this.fit(); |
| 12551 }.bind(this)); |
| 12552 } else { |
| 12553 this.fit(); |
| 12554 } |
| 12555 } |
| 12556 }, |
| 12557 |
| 12558 /** |
| 12559 * Fits and optionally centers the element into the window, or `fitInfo` if
specified. |
| 12560 */ |
| 12561 fit: function() { |
| 12562 this._discoverInfo(); |
| 12563 this.constrain(); |
| 12564 this.center(); |
| 12565 }, |
| 12566 |
| 12567 /** |
| 12568 * Memoize information needed to position and size the target element. |
| 12569 */ |
| 12570 _discoverInfo: function() { |
| 12571 if (this._fitInfo) { |
| 12572 return; |
| 12573 } |
| 12574 var target = window.getComputedStyle(this); |
| 12575 var sizer = window.getComputedStyle(this.sizingTarget); |
| 12576 this._fitInfo = { |
| 12577 inlineStyle: { |
| 12578 top: this.style.top || '', |
| 12579 left: this.style.left || '' |
| 12580 }, |
| 12581 positionedBy: { |
| 12582 vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto'
? |
| 12583 'bottom' : null), |
| 12584 horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'aut
o' ? |
| 12585 'right' : null), |
| 12586 css: target.position |
| 12587 }, |
| 12588 sizedBy: { |
| 12589 height: sizer.maxHeight !== 'none', |
| 12590 width: sizer.maxWidth !== 'none' |
| 12591 }, |
| 12592 margin: { |
| 12593 top: parseInt(target.marginTop, 10) || 0, |
| 12594 right: parseInt(target.marginRight, 10) || 0, |
| 12595 bottom: parseInt(target.marginBottom, 10) || 0, |
| 12596 left: parseInt(target.marginLeft, 10) || 0 |
| 12597 } |
| 12598 }; |
| 12599 }, |
| 12600 |
| 12601 /** |
| 12602 * Resets the target element's position and size constraints, and clear |
| 12603 * the memoized data. |
| 12604 */ |
| 12605 resetFit: function() { |
| 12606 if (!this._fitInfo || !this._fitInfo.sizedBy.height) { |
| 12607 this.sizingTarget.style.maxHeight = ''; |
| 12608 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : ''; |
| 12609 } |
| 12610 if (!this._fitInfo || !this._fitInfo.sizedBy.width) { |
| 12611 this.sizingTarget.style.maxWidth = ''; |
| 12612 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : ''; |
| 12613 } |
| 12614 if (this._fitInfo) { |
| 12615 this.style.position = this._fitInfo.positionedBy.css; |
| 12616 } |
| 12617 this._fitInfo = null; |
| 12618 }, |
| 12619 |
| 12620 /** |
| 12621 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
the element, |
| 12622 * the window, or the `fitInfo` element has been resized. |
| 12623 */ |
| 12624 refit: function() { |
| 12625 this.resetFit(); |
| 12626 this.fit(); |
| 12627 }, |
| 12628 |
| 12629 /** |
| 12630 * Constrains the size of the element to the window or `fitInfo` by setting
`max-height` |
| 12631 * and/or `max-width`. |
| 12632 */ |
| 12633 constrain: function() { |
| 12634 var info = this._fitInfo; |
| 12635 // position at (0px, 0px) if not already positioned, so we can measure the
natural size. |
| 12636 if (!this._fitInfo.positionedBy.vertically) { |
| 12637 this.style.top = '0px'; |
| 12638 } |
| 12639 if (!this._fitInfo.positionedBy.horizontally) { |
| 12640 this.style.left = '0px'; |
| 12641 } |
| 12642 // need border-box for margin/padding |
| 12643 this.sizingTarget.style.boxSizing = 'border-box'; |
| 12644 // constrain the width and height if not already set |
| 12645 var rect = this.getBoundingClientRect(); |
| 12646 if (!info.sizedBy.height) { |
| 12647 this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom',
'Height'); |
| 12648 } |
| 12649 if (!info.sizedBy.width) { |
| 12650 this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right
', 'Width'); |
| 12651 } |
| 12652 }, |
| 12653 |
| 12654 _sizeDimension: function(rect, positionedBy, start, end, extent) { |
| 12655 var info = this._fitInfo; |
| 12656 var max = extent === 'Width' ? this._fitWidth : this._fitHeight; |
| 12657 var flip = (positionedBy === end); |
| 12658 var offset = flip ? max - rect[end] : rect[start]; |
| 12659 var margin = info.margin[flip ? start : end]; |
| 12660 var offsetExtent = 'offset' + extent; |
| 12661 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; |
| 12662 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; |
| 12663 }, |
| 12664 |
| 12665 /** |
| 12666 * Centers horizontally and vertically if not already positioned. This also
sets |
| 12667 * `position:fixed`. |
| 12668 */ |
| 12669 center: function() { |
| 12670 if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.
horizontally) { |
| 12671 // need position:fixed to center |
| 12672 this.style.position = 'fixed'; |
| 12673 } |
| 12674 if (!this._fitInfo.positionedBy.vertically) { |
| 12675 var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop; |
| 12676 top -= this._fitInfo.margin.top; |
| 12677 this.style.top = top + 'px'; |
| 12678 } |
| 12679 if (!this._fitInfo.positionedBy.horizontally) { |
| 12680 var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft; |
| 12681 left -= this._fitInfo.margin.left; |
| 12682 this.style.left = left + 'px'; |
| 12683 } |
| 12684 } |
| 12685 |
| 12686 }; |
| 12687 Polymer.IronOverlayManager = (function() { |
| 12688 |
| 12689 var overlays = []; |
| 12690 var DEFAULT_Z = 10; |
| 12691 var backdrops = []; |
| 12692 |
| 12693 // track overlays for z-index and focus managemant |
| 12694 function addOverlay(overlay) { |
| 12695 var z0 = currentOverlayZ(); |
| 12696 overlays.push(overlay); |
| 12697 var z1 = currentOverlayZ(); |
| 12698 if (z1 <= z0) { |
| 12699 applyOverlayZ(overlay, z0); |
| 12700 } |
| 12701 } |
| 12702 |
| 12703 function removeOverlay(overlay) { |
| 12704 var i = overlays.indexOf(overlay); |
| 12705 if (i >= 0) { |
| 12706 overlays.splice(i, 1); |
| 12707 setZ(overlay, ''); |
| 12708 } |
| 12709 } |
| 12710 |
| 12711 function applyOverlayZ(overlay, aboveZ) { |
| 12712 setZ(overlay, aboveZ + 2); |
| 12713 } |
| 12714 |
| 12715 function setZ(element, z) { |
| 12716 element.style.zIndex = z; |
| 12717 } |
| 12718 |
| 12719 function currentOverlay() { |
| 12720 var i = overlays.length - 1; |
| 12721 while (overlays[i] && !overlays[i].opened) { |
| 12722 --i; |
| 12723 } |
| 12724 return overlays[i]; |
| 12725 } |
| 12726 |
| 12727 function currentOverlayZ() { |
| 12728 var z; |
| 12729 var current = currentOverlay(); |
| 12730 if (current) { |
| 12731 var z1 = window.getComputedStyle(current).zIndex; |
| 12732 if (!isNaN(z1)) { |
| 12733 z = Number(z1); |
| 12734 } |
| 12735 } |
| 12736 return z || DEFAULT_Z; |
| 12737 } |
| 12738 |
| 12739 function focusOverlay() { |
| 12740 var current = currentOverlay(); |
| 12741 // We have to be careful to focus the next overlay _after_ any current |
| 12742 // transitions are complete (due to the state being toggled prior to the |
| 12743 // transition). Otherwise, we risk infinite recursion when a transitioning |
| 12744 // (closed) overlay becomes the current overlay. |
| 12745 // |
| 12746 // NOTE: We make the assumption that any overlay that completes a transiti
on |
| 12747 // will call into focusOverlay to kick the process back off. Currently: |
| 12748 // transitionend -> _applyFocus -> focusOverlay. |
| 12749 if (current && !current.transitioning) { |
| 12750 current._applyFocus(); |
| 12751 } |
| 12752 } |
| 12753 |
| 12754 function trackBackdrop(element) { |
| 12755 // backdrops contains the overlays with a backdrop that are currently |
| 12756 // visible |
| 12757 if (element.opened) { |
| 12758 backdrops.push(element); |
| 12759 } else { |
| 12760 var index = backdrops.indexOf(element); |
| 12761 if (index >= 0) { |
| 12762 backdrops.splice(index, 1); |
| 12763 } |
| 12764 } |
| 12765 } |
| 12766 |
| 12767 function getBackdrops() { |
| 12768 return backdrops; |
| 12769 } |
| 12770 |
| 12771 return { |
| 12772 addOverlay: addOverlay, |
| 12773 removeOverlay: removeOverlay, |
| 12774 currentOverlay: currentOverlay, |
| 12775 currentOverlayZ: currentOverlayZ, |
| 12776 focusOverlay: focusOverlay, |
| 12777 trackBackdrop: trackBackdrop, |
| 12778 getBackdrops: getBackdrops |
| 12779 }; |
| 12780 |
| 12781 })(); |
| 12782 (function() { |
| 12783 |
| 12784 Polymer({ |
| 12785 |
| 12786 is: 'iron-overlay-backdrop', |
| 12787 |
| 12788 properties: { |
| 12789 |
| 12790 /** |
| 12791 * Returns true if the backdrop is opened. |
| 12792 */ |
| 12793 opened: { |
| 12794 readOnly: true, |
| 12795 reflectToAttribute: true, |
| 12796 type: Boolean, |
| 12797 value: false |
| 12798 }, |
| 12799 |
| 12800 _manager: { |
| 12801 type: Object, |
| 12802 value: Polymer.IronOverlayManager |
| 12803 } |
| 12804 |
| 12805 }, |
| 12806 |
| 12807 /** |
| 12808 * Appends the backdrop to document body and sets its `z-index` to be below
the latest overlay. |
| 12809 */ |
| 12810 prepare: function() { |
| 12811 if (!this.parentNode) { |
| 12812 Polymer.dom(document.body).appendChild(this); |
| 12813 this.style.zIndex = this._manager.currentOverlayZ() - 1; |
| 12814 } |
| 12815 }, |
| 12816 |
| 12817 /** |
| 12818 * Shows the backdrop if needed. |
| 12819 */ |
| 12820 open: function() { |
| 12821 // only need to make the backdrop visible if this is called by the first o
verlay with a backdrop |
| 12822 if (this._manager.getBackdrops().length < 2) { |
| 12823 this._setOpened(true); |
| 12824 } |
| 12825 }, |
| 12826 |
| 12827 /** |
| 12828 * Hides the backdrop if needed. |
| 12829 */ |
| 12830 close: function() { |
| 12831 // only need to make the backdrop invisible if this is called by the last
overlay with a backdrop |
| 12832 if (this._manager.getBackdrops().length < 2) { |
| 12833 this._setOpened(false); |
| 12834 } |
| 12835 }, |
| 12836 |
| 12837 /** |
| 12838 * Removes the backdrop from document body if needed. |
| 12839 */ |
| 12840 complete: function() { |
| 12841 // only remove the backdrop if there are no more overlays with backdrops |
| 12842 if (this._manager.getBackdrops().length === 0 && this.parentNode) { |
| 12843 Polymer.dom(this.parentNode).removeChild(this); |
| 12844 } |
| 12845 } |
| 12846 |
| 12847 }); |
| 12848 |
| 12849 })(); |
| 12850 /** |
| 12851 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays |
| 12852 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety |
| 12853 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. |
| 12854 |
| 12855 ### Closing and canceling |
| 12856 |
| 12857 A dialog may be hidden by closing or canceling. The difference between close and
cancel is user |
| 12858 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, |
| 12859 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is |
| 12860 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. |
| 12861 `close()` should be called explicitly by the implementer when the user interacts
with a control |
| 12862 in the overlay element. |
| 12863 |
| 12864 ### Positioning |
| 12865 |
| 12866 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can |
| 12867 position and size it manually using CSS. See `Polymer.IronFitBehavior`. |
| 12868 |
| 12869 ### Backdrop |
| 12870 |
| 12871 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is |
| 12872 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling |
| 12873 options. |
| 12874 |
| 12875 ### Limitations |
| 12876 |
| 12877 The element is styled to appear on top of other content by setting its `z-index`
property. You |
| 12878 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking |
| 12879 context. You should place this element as a child of `<body>` whenever possible. |
| 12880 |
| 12881 @demo demo/index.html |
| 12882 @polymerBehavior Polymer.IronOverlayBehavior |
| 12883 */ |
| 12884 |
| 12885 Polymer.IronOverlayBehaviorImpl = { |
| 12886 |
| 12887 properties: { |
| 12888 |
| 12889 /** |
| 12890 * True if the overlay is currently displayed. |
| 12891 */ |
| 12892 opened: { |
| 12893 observer: '_openedChanged', |
| 12894 type: Boolean, |
| 12895 value: false, |
| 12896 notify: true |
| 12897 }, |
| 12898 |
| 12899 /** |
| 12900 * True if the overlay was canceled when it was last closed. |
| 12901 */ |
| 12902 canceled: { |
| 12903 observer: '_canceledChanged', |
| 12904 readOnly: true, |
| 12905 type: Boolean, |
| 12906 value: false |
| 12907 }, |
| 12908 |
| 12909 /** |
| 12910 * Set to true to display a backdrop behind the overlay. |
| 12911 */ |
| 12912 withBackdrop: { |
| 12913 type: Boolean, |
| 12914 value: false |
| 12915 }, |
| 12916 |
| 12917 /** |
| 12918 * Set to true to disable auto-focusing the overlay or child nodes with |
| 12919 * the `autofocus` attribute` when the overlay is opened. |
| 12920 */ |
| 12921 noAutoFocus: { |
| 12922 type: Boolean, |
| 12923 value: false |
| 12924 }, |
| 12925 |
| 12926 /** |
| 12927 * Set to true to disable canceling the overlay with the ESC key. |
| 12928 */ |
| 12929 noCancelOnEscKey: { |
| 12930 type: Boolean, |
| 12931 value: false |
| 12932 }, |
| 12933 |
| 12934 /** |
| 12935 * Set to true to disable canceling the overlay by clicking outside it. |
| 12936 */ |
| 12937 noCancelOnOutsideClick: { |
| 12938 type: Boolean, |
| 12939 value: false |
| 12940 }, |
| 12941 |
| 12942 /** |
| 12943 * Returns the reason this dialog was last closed. |
| 12944 */ |
| 12945 closingReason: { |
| 12946 // was a getter before, but needs to be a property so other |
| 12947 // behaviors can override this. |
| 12948 type: Object |
| 12949 }, |
| 12950 |
| 12951 _manager: { |
| 12952 type: Object, |
| 12953 value: Polymer.IronOverlayManager |
| 12954 }, |
| 12955 |
| 12956 _boundOnCaptureClick: { |
| 12957 type: Function, |
| 12958 value: function() { |
| 12959 return this._onCaptureClick.bind(this); |
| 12960 } |
| 12961 }, |
| 12962 |
| 12963 _boundOnCaptureKeydown: { |
| 12964 type: Function, |
| 12965 value: function() { |
| 12966 return this._onCaptureKeydown.bind(this); |
| 12967 } |
| 12968 } |
| 12969 |
| 12970 }, |
| 12971 |
| 12972 listeners: { |
| 12973 'tap': '_onClick', |
| 12974 'iron-resize': '_onIronResize' |
| 12975 }, |
| 12976 |
| 12977 /** |
| 12978 * The backdrop element. |
| 12979 * @type Node |
| 12980 */ |
| 12981 get backdropElement() { |
| 12982 return this._backdrop; |
| 12983 }, |
| 12984 |
| 12985 get _focusNode() { |
| 12986 return Polymer.dom(this).querySelector('[autofocus]') || this; |
| 12987 }, |
| 12988 |
| 12989 registered: function() { |
| 12990 this._backdrop = document.createElement('iron-overlay-backdrop'); |
| 12991 }, |
| 12992 |
| 12993 ready: function() { |
| 12994 this._ensureSetup(); |
| 12995 if (this._callOpenedWhenReady) { |
| 12996 this._openedChanged(); |
| 12997 } |
| 12998 }, |
| 12999 |
| 13000 detached: function() { |
| 13001 this.opened = false; |
| 13002 this._completeBackdrop(); |
| 13003 this._manager.removeOverlay(this); |
| 13004 }, |
| 13005 |
| 13006 /** |
| 13007 * Toggle the opened state of the overlay. |
| 13008 */ |
| 13009 toggle: function() { |
| 13010 this.opened = !this.opened; |
| 13011 }, |
| 13012 |
| 13013 /** |
| 13014 * Open the overlay. |
| 13015 */ |
| 13016 open: function() { |
| 13017 this.opened = true; |
| 13018 this.closingReason = {canceled: false}; |
| 13019 }, |
| 13020 |
| 13021 /** |
| 13022 * Close the overlay. |
| 13023 */ |
| 13024 close: function() { |
| 13025 this.opened = false; |
| 13026 this._setCanceled(false); |
| 13027 }, |
| 13028 |
| 13029 /** |
| 13030 * Cancels the overlay. |
| 13031 */ |
| 13032 cancel: function() { |
| 13033 this.opened = false; |
| 13034 this._setCanceled(true); |
| 13035 }, |
| 13036 |
| 13037 _ensureSetup: function() { |
| 13038 if (this._overlaySetup) { |
| 13039 return; |
| 13040 } |
| 13041 this._overlaySetup = true; |
| 13042 this.style.outline = 'none'; |
| 13043 this.style.display = 'none'; |
| 13044 }, |
| 13045 |
| 13046 _openedChanged: function() { |
| 13047 if (this.opened) { |
| 13048 this.removeAttribute('aria-hidden'); |
| 13049 } else { |
| 13050 this.setAttribute('aria-hidden', 'true'); |
| 13051 } |
| 13052 |
| 13053 // wait to call after ready only if we're initially open |
| 13054 if (!this._overlaySetup) { |
| 13055 this._callOpenedWhenReady = this.opened; |
| 13056 return; |
| 13057 } |
| 13058 if (this._openChangedAsync) { |
| 13059 this.cancelAsync(this._openChangedAsync); |
| 13060 } |
| 13061 |
| 13062 this._toggleListeners(); |
| 13063 |
| 13064 if (this.opened) { |
| 13065 this._prepareRenderOpened(); |
| 13066 } |
| 13067 |
| 13068 // async here to allow overlay layer to become visible. |
| 13069 this._openChangedAsync = this.async(function() { |
| 13070 // overlay becomes visible here |
| 13071 this.style.display = ''; |
| 13072 // force layout to ensure transitions will go |
| 13073 /** @suppress {suspiciousCode} */ this.offsetWidth; |
| 13074 if (this.opened) { |
| 13075 this._renderOpened(); |
| 13076 } else { |
| 13077 this._renderClosed(); |
| 13078 } |
| 13079 this._openChangedAsync = null; |
| 13080 }); |
| 13081 |
| 13082 }, |
| 13083 |
| 13084 _canceledChanged: function() { |
| 13085 this.closingReason = this.closingReason || {}; |
| 13086 this.closingReason.canceled = this.canceled; |
| 13087 }, |
| 13088 |
| 13089 _toggleListener: function(enable, node, event, boundListener, capture) { |
| 13090 if (enable) { |
| 13091 // enable document-wide tap recognizer |
| 13092 if (event === 'tap') { |
| 13093 Polymer.Gestures.add(document, 'tap', null); |
| 13094 } |
| 13095 node.addEventListener(event, boundListener, capture); |
| 13096 } else { |
| 13097 // disable document-wide tap recognizer |
| 13098 if (event === 'tap') { |
| 13099 Polymer.Gestures.remove(document, 'tap', null); |
| 13100 } |
| 13101 node.removeEventListener(event, boundListener, capture); |
| 13102 } |
| 13103 }, |
| 13104 |
| 13105 _toggleListeners: function() { |
| 13106 if (this._toggleListenersAsync) { |
| 13107 this.cancelAsync(this._toggleListenersAsync); |
| 13108 } |
| 13109 // async so we don't auto-close immediately via a click. |
| 13110 this._toggleListenersAsync = this.async(function() { |
| 13111 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureC
lick, true); |
| 13112 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt
ureKeydown, true); |
| 13113 this._toggleListenersAsync = null; |
| 13114 }, 1); |
| 13115 }, |
| 13116 |
| 13117 // tasks which must occur before opening; e.g. making the element visible |
| 13118 _prepareRenderOpened: function() { |
| 13119 this._manager.addOverlay(this); |
| 13120 |
| 13121 if (this.withBackdrop) { |
| 13122 this.backdropElement.prepare(); |
| 13123 this._manager.trackBackdrop(this); |
| 13124 } |
| 13125 |
| 13126 this._preparePositioning(); |
| 13127 this.fit(); |
| 13128 this._finishPositioning(); |
| 13129 }, |
| 13130 |
| 13131 // tasks which cause the overlay to actually open; typically play an |
| 13132 // animation |
| 13133 _renderOpened: function() { |
| 13134 if (this.withBackdrop) { |
| 13135 this.backdropElement.open(); |
| 13136 } |
| 13137 this._finishRenderOpened(); |
| 13138 }, |
| 13139 |
| 13140 _renderClosed: function() { |
| 13141 if (this.withBackdrop) { |
| 13142 this.backdropElement.close(); |
| 13143 } |
| 13144 this._finishRenderClosed(); |
| 13145 }, |
| 13146 |
| 13147 _onTransitionend: function(event) { |
| 13148 // make sure this is our transition event. |
| 13149 if (event && event.target !== this) { |
| 13150 return; |
| 13151 } |
| 13152 if (this.opened) { |
| 13153 this._finishRenderOpened(); |
| 13154 } else { |
| 13155 this._finishRenderClosed(); |
| 13156 } |
| 13157 }, |
| 13158 |
| 13159 _finishRenderOpened: function() { |
| 13160 // focus the child node with [autofocus] |
| 13161 if (!this.noAutoFocus) { |
| 13162 this._focusNode.focus(); |
| 13163 } |
| 13164 |
| 13165 this.fire('iron-overlay-opened'); |
| 13166 |
| 13167 this._squelchNextResize = true; |
| 13168 this.async(this.notifyResize); |
| 13169 }, |
| 13170 |
| 13171 _finishRenderClosed: function() { |
| 13172 // hide the overlay and remove the backdrop |
| 13173 this.resetFit(); |
| 13174 this.style.display = 'none'; |
| 13175 this._completeBackdrop(); |
| 13176 this._manager.removeOverlay(this); |
| 13177 |
| 13178 this._focusNode.blur(); |
| 13179 // focus the next overlay, if there is one |
| 13180 this._manager.focusOverlay(); |
| 13181 |
| 13182 this.fire('iron-overlay-closed', this.closingReason); |
| 13183 |
| 13184 this._squelchNextResize = true; |
| 13185 this.async(this.notifyResize); |
| 13186 }, |
| 13187 |
| 13188 _completeBackdrop: function() { |
| 13189 if (this.withBackdrop) { |
| 13190 this._manager.trackBackdrop(this); |
| 13191 this.backdropElement.complete(); |
| 13192 } |
| 13193 }, |
| 13194 |
| 13195 _preparePositioning: function() { |
| 13196 this.style.transition = this.style.webkitTransition = 'none'; |
| 13197 this.style.transform = this.style.webkitTransform = 'none'; |
| 13198 this.style.display = ''; |
| 13199 }, |
| 13200 |
| 13201 _finishPositioning: function() { |
| 13202 this.style.display = 'none'; |
| 13203 this.style.transform = this.style.webkitTransform = ''; |
| 13204 // force layout to avoid application of transform |
| 13205 /** @suppress {suspiciousCode} */ this.offsetWidth; |
| 13206 this.style.transition = this.style.webkitTransition = ''; |
| 13207 }, |
| 13208 |
| 13209 _applyFocus: function() { |
| 13210 if (this.opened) { |
| 13211 if (!this.noAutoFocus) { |
| 13212 this._focusNode.focus(); |
| 13213 } |
| 13214 } else { |
| 13215 this._focusNode.blur(); |
| 13216 this._manager.focusOverlay(); |
| 13217 } |
| 13218 }, |
| 13219 |
| 13220 _onCaptureClick: function(event) { |
| 13221 // attempt to close asynchronously and prevent the close of a tap event is
immediately heard |
| 13222 // on target. This is because in shadow dom due to event retargetting even
t.target is not |
| 13223 // useful. |
| 13224 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi
s)) { |
| 13225 this._cancelJob = this.async(function() { |
| 13226 this.cancel(); |
| 13227 }, 10); |
| 13228 } |
| 13229 }, |
| 13230 |
| 13231 _onClick: function(event) { |
| 13232 if (this._cancelJob) { |
| 13233 this.cancelAsync(this._cancelJob); |
| 13234 this._cancelJob = null; |
| 13235 } |
| 13236 }, |
| 13237 |
| 13238 _onCaptureKeydown: function(event) { |
| 13239 var ESC = 27; |
| 13240 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { |
| 13241 this.cancel(); |
| 13242 event.stopPropagation(); |
| 13243 } |
| 13244 }, |
| 13245 |
| 13246 _onIronResize: function() { |
| 13247 if (this._squelchNextResize) { |
| 13248 this._squelchNextResize = false; |
| 13249 return; |
| 13250 } |
| 13251 if (this.opened) { |
| 13252 this.refit(); |
| 13253 } |
| 13254 } |
| 13255 |
| 13256 /** |
| 13257 * Fired after the `iron-overlay` opens. |
| 13258 * @event iron-overlay-opened |
| 13259 */ |
| 13260 |
| 13261 /** |
| 13262 * Fired after the `iron-overlay` closes. |
| 13263 * @event iron-overlay-closed |
| 13264 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute |
| 13265 */ |
| 13266 }; |
| 13267 |
| 13268 /** @polymerBehavior */ |
| 13269 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; |
| 13270 /** |
| 13271 * Use `Polymer.NeonAnimationBehavior` to implement an animation. |
| 13272 * @polymerBehavior |
| 13273 */ |
| 13274 Polymer.NeonAnimationBehavior = { |
| 13275 |
| 13276 properties: { |
| 13277 |
| 13278 /** |
| 13279 * Defines the animation timing. |
| 13280 */ |
| 13281 animationTiming: { |
| 13282 type: Object, |
| 13283 value: function() { |
| 13284 return { |
| 13285 duration: 500, |
| 13286 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', |
| 13287 fill: 'both' |
| 13288 } |
| 13289 } |
| 13290 } |
| 13291 |
| 13292 }, |
| 13293 |
| 13294 registered: function() { |
| 13295 new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constru
ctor}); |
| 13296 }, |
| 13297 |
| 13298 /** |
| 13299 * Do any animation configuration here. |
| 13300 */ |
| 13301 // configure: function(config) { |
| 13302 // }, |
| 13303 |
| 13304 /** |
| 13305 * Returns the animation timing by mixing in properties from `config` to the
defaults defined |
| 13306 * by the animation. |
| 13307 */ |
| 13308 timingFromConfig: function(config) { |
| 13309 if (config.timing) { |
| 13310 for (var property in config.timing) { |
| 13311 this.animationTiming[property] = config.timing[property]; |
| 13312 } |
| 13313 } |
| 13314 return this.animationTiming; |
| 13315 }, |
| 13316 |
| 13317 /** |
| 13318 * Sets `transform` and `transformOrigin` properties along with the prefixed
versions. |
| 13319 */ |
| 13320 setPrefixedProperty: function(node, property, value) { |
| 13321 var map = { |
| 13322 'transform': ['webkitTransform'], |
| 13323 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] |
| 13324 }; |
| 13325 var prefixes = map[property]; |
| 13326 for (var prefix, index = 0; prefix = prefixes[index]; index++) { |
| 13327 node.style[prefix] = value; |
| 13328 } |
| 13329 node.style[property] = value; |
| 13330 }, |
| 13331 |
| 13332 /** |
| 13333 * Called when the animation finishes. |
| 13334 */ |
| 13335 complete: function() {} |
| 13336 |
| 13337 }; |
| 13338 Polymer({ |
| 13339 |
| 13340 is: 'opaque-animation', |
| 13341 |
| 13342 behaviors: [ |
| 13343 Polymer.NeonAnimationBehavior |
| 13344 ], |
| 13345 |
| 13346 configure: function(config) { |
| 13347 var node = config.node; |
| 13348 node.style.opacity = '0'; |
| 13349 this._effect = new KeyframeEffect(node, [ |
| 13350 {'opacity': '1'}, |
| 13351 {'opacity': '1'} |
| 13352 ], this.timingFromConfig(config)); |
| 13353 return this._effect; |
| 13354 }, |
| 13355 |
| 13356 complete: function(config) { |
| 13357 config.node.style.opacity = ''; |
| 13358 } |
| 13359 |
| 13360 }); |
| 13361 /** |
| 13362 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim
ations for use with |
| 13363 * elements implementing `Polymer.NeonAnimationRunnerBehavior`. |
| 13364 * @polymerBehavior |
| 13365 */ |
| 13366 Polymer.NeonAnimatableBehavior = { |
| 13367 |
| 13368 properties: { |
| 13369 |
| 13370 /** |
| 13371 * Animation configuration. See README for more info. |
| 13372 */ |
| 13373 animationConfig: { |
| 13374 type: Object |
| 13375 }, |
| 13376 |
| 13377 /** |
| 13378 * Convenience property for setting an 'entry' animation. Do not set `anim
ationConfig.entry` |
| 13379 * manually if using this. The animated node is set to `this` if using thi
s property. |
| 13380 */ |
| 13381 entryAnimation: { |
| 13382 observer: '_entryAnimationChanged', |
| 13383 type: String |
| 13384 }, |
| 13385 |
| 13386 /** |
| 13387 * Convenience property for setting an 'exit' animation. Do not set `anima
tionConfig.exit` |
| 13388 * manually if using this. The animated node is set to `this` if using thi
s property. |
| 13389 */ |
| 13390 exitAnimation: { |
| 13391 observer: '_exitAnimationChanged', |
| 13392 type: String |
| 13393 } |
| 13394 |
| 13395 }, |
| 13396 |
| 13397 _entryAnimationChanged: function() { |
| 13398 this.animationConfig = this.animationConfig || {}; |
| 13399 if (this.entryAnimation !== 'fade-in-animation') { |
| 13400 // insert polyfill hack |
| 13401 this.animationConfig['entry'] = [{ |
| 13402 name: 'opaque-animation', |
| 13403 node: this |
| 13404 }, { |
| 13405 name: this.entryAnimation, |
| 13406 node: this |
| 13407 }]; |
| 13408 } else { |
| 13409 this.animationConfig['entry'] = [{ |
| 13410 name: this.entryAnimation, |
| 13411 node: this |
| 13412 }]; |
| 13413 } |
| 13414 }, |
| 13415 |
| 13416 _exitAnimationChanged: function() { |
| 13417 this.animationConfig = this.animationConfig || {}; |
| 13418 this.animationConfig['exit'] = [{ |
| 13419 name: this.exitAnimation, |
| 13420 node: this |
| 13421 }]; |
| 13422 }, |
| 13423 |
| 13424 _copyProperties: function(config1, config2) { |
| 13425 // shallowly copy properties from config2 to config1 |
| 13426 for (var property in config2) { |
| 13427 config1[property] = config2[property]; |
| 13428 } |
| 13429 }, |
| 13430 |
| 13431 _cloneConfig: function(config) { |
| 13432 var clone = { |
| 13433 isClone: true |
| 13434 }; |
| 13435 this._copyProperties(clone, config); |
| 13436 return clone; |
| 13437 }, |
| 13438 |
| 13439 _getAnimationConfigRecursive: function(type, map, allConfigs) { |
| 13440 if (!this.animationConfig) { |
| 13441 return; |
| 13442 } |
| 13443 |
| 13444 // type is optional |
| 13445 var thisConfig; |
| 13446 if (type) { |
| 13447 thisConfig = this.animationConfig[type]; |
| 13448 } else { |
| 13449 thisConfig = this.animationConfig; |
| 13450 } |
| 13451 |
| 13452 if (!Array.isArray(thisConfig)) { |
| 13453 thisConfig = [thisConfig]; |
| 13454 } |
| 13455 |
| 13456 // iterate animations and recurse to process configurations from child nod
es |
| 13457 if (thisConfig) { |
| 13458 for (var config, index = 0; config = thisConfig[index]; index++) { |
| 13459 if (config.animatable) { |
| 13460 config.animatable._getAnimationConfigRecursive(config.type || type,
map, allConfigs); |
| 13461 } else { |
| 13462 if (config.id) { |
| 13463 var cachedConfig = map[config.id]; |
| 13464 if (cachedConfig) { |
| 13465 // merge configurations with the same id, making a clone lazily |
| 13466 if (!cachedConfig.isClone) { |
| 13467 map[config.id] = this._cloneConfig(cachedConfig) |
| 13468 cachedConfig = map[config.id]; |
| 13469 } |
| 13470 this._copyProperties(cachedConfig, config); |
| 13471 } else { |
| 13472 // put any configs with an id into a map |
| 13473 map[config.id] = config; |
| 13474 } |
| 13475 } else { |
| 13476 allConfigs.push(config); |
| 13477 } |
| 13478 } |
| 13479 } |
| 13480 } |
| 13481 }, |
| 13482 |
| 13483 /** |
| 13484 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this
method to configure |
| 13485 * an animation with an optional type. Elements implementing `Polymer.NeonAn
imatableBehavior` |
| 13486 * should define the property `animationConfig`, which is either a configura
tion object |
| 13487 * or a map of animation type to array of configuration objects. |
| 13488 */ |
| 13489 getAnimationConfig: function(type) { |
| 13490 var map = []; |
| 13491 var allConfigs = []; |
| 13492 this._getAnimationConfigRecursive(type, map, allConfigs); |
| 13493 // append the configurations saved in the map to the array |
| 13494 for (var key in map) { |
| 13495 allConfigs.push(map[key]); |
| 13496 } |
| 13497 return allConfigs; |
| 13498 } |
| 13499 |
| 13500 }; |
| 13501 /** |
| 13502 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations. |
| 13503 * |
| 13504 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior |
| 13505 */ |
| 13506 Polymer.NeonAnimationRunnerBehaviorImpl = { |
| 13507 |
| 13508 properties: { |
| 13509 |
| 13510 _animationMeta: { |
| 13511 type: Object, |
| 13512 value: function() { |
| 13513 return new Polymer.IronMeta({type: 'animation'}); |
| 13514 } |
| 13515 }, |
| 13516 |
| 13517 /** @type {?Object} */ |
| 13518 _player: { |
| 13519 type: Object |
| 13520 } |
| 13521 |
| 13522 }, |
| 13523 |
| 13524 _configureAnimationEffects: function(allConfigs) { |
| 13525 var allAnimations = []; |
| 13526 if (allConfigs.length > 0) { |
| 13527 for (var config, index = 0; config = allConfigs[index]; index++) { |
| 13528 var animationConstructor = this._animationMeta.byKey(config.name); |
| 13529 if (animationConstructor) { |
| 13530 var animation = animationConstructor && new animationConstructor(); |
| 13531 var effect = animation.configure(config); |
| 13532 if (effect) { |
| 13533 allAnimations.push({ |
| 13534 animation: animation, |
| 13535 config: config, |
| 13536 effect: effect |
| 13537 }); |
| 13538 } |
| 13539 } else { |
| 13540 console.warn(this.is + ':', config.name, 'not found!'); |
| 13541 } |
| 13542 } |
| 13543 } |
| 13544 return allAnimations; |
| 13545 }, |
| 13546 |
| 13547 _runAnimationEffects: function(allEffects) { |
| 13548 return document.timeline.play(new GroupEffect(allEffects)); |
| 13549 }, |
| 13550 |
| 13551 _completeAnimations: function(allAnimations) { |
| 13552 for (var animation, index = 0; animation = allAnimations[index]; index++)
{ |
| 13553 animation.animation.complete(animation.config); |
| 13554 } |
| 13555 }, |
| 13556 |
| 13557 /** |
| 13558 * Plays an animation with an optional `type`. |
| 13559 * @param {string=} type |
| 13560 * @param {!Object=} cookie |
| 13561 */ |
| 13562 playAnimation: function(type, cookie) { |
| 13563 var allConfigs = this.getAnimationConfig(type); |
| 13564 if (!allConfigs) { |
| 13565 return; |
| 13566 } |
| 13567 var allAnimations = this._configureAnimationEffects(allConfigs); |
| 13568 var allEffects = allAnimations.map(function(animation) { |
| 13569 return animation.effect; |
| 13570 }); |
| 13571 |
| 13572 if (allEffects.length > 0) { |
| 13573 this._player = this._runAnimationEffects(allEffects); |
| 13574 this._player.onfinish = function() { |
| 13575 this._completeAnimations(allAnimations); |
| 13576 |
| 13577 if (this._player) { |
| 13578 this._player.cancel(); |
| 13579 this._player = null; |
| 13580 } |
| 13581 |
| 13582 this.fire('neon-animation-finish', cookie, {bubbles: false}); |
| 13583 }.bind(this); |
| 13584 |
| 13585 } else { |
| 13586 this.fire('neon-animation-finish', cookie, {bubbles: false}); |
| 13587 } |
| 13588 }, |
| 13589 |
| 13590 /** |
| 13591 * Cancels the currently running animation. |
| 13592 */ |
| 13593 cancelAnimation: function() { |
| 13594 if (this._player) { |
| 13595 this._player.cancel(); |
| 13596 } |
| 13597 } |
| 13598 }; |
| 13599 |
| 13600 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ |
| 13601 Polymer.NeonAnimationRunnerBehavior = [ |
| 13602 Polymer.NeonAnimatableBehavior, |
| 13603 Polymer.NeonAnimationRunnerBehaviorImpl |
| 13604 ]; |
| 13605 (function() { |
| 13606 'use strict'; |
| 13607 |
| 13608 /** |
| 13609 * The IronDropdownScrollManager is intended to provide a central source |
| 13610 * of authority and control over which elements in a document are currently |
| 13611 * allowed to scroll. |
| 13612 */ |
| 13613 |
| 13614 Polymer.IronDropdownScrollManager = { |
| 13615 |
| 13616 /** |
| 13617 * The current element that defines the DOM boundaries of the |
| 13618 * scroll lock. This is always the most recently locking element. |
| 13619 */ |
| 13620 get currentLockingElement() { |
| 13621 return this._lockingElements[this._lockingElements.length - 1]; |
| 13622 }, |
| 13623 |
| 13624 |
| 13625 /** |
| 13626 * Returns true if the provided element is "scroll locked," which is to |
| 13627 * say that it cannot be scrolled via pointer or keyboard interactions. |
| 13628 * |
| 13629 * @param {HTMLElement} element An HTML element instance which may or may |
| 13630 * not be scroll locked. |
| 13631 */ |
| 13632 elementIsScrollLocked: function(element) { |
| 13633 var currentLockingElement = this.currentLockingElement; |
| 13634 var scrollLocked; |
| 13635 |
| 13636 if (this._hasCachedLockedElement(element)) { |
| 13637 return true; |
| 13638 } |
| 13639 |
| 13640 if (this._hasCachedUnlockedElement(element)) { |
| 13641 return false; |
| 13642 } |
| 13643 |
| 13644 scrollLocked = !!currentLockingElement && |
| 13645 currentLockingElement !== element && |
| 13646 !this._composedTreeContains(currentLockingElement, element); |
| 13647 |
| 13648 if (scrollLocked) { |
| 13649 this._lockedElementCache.push(element); |
| 13650 } else { |
| 13651 this._unlockedElementCache.push(element); |
| 13652 } |
| 13653 |
| 13654 return scrollLocked; |
| 13655 }, |
| 13656 |
| 13657 /** |
| 13658 * Push an element onto the current scroll lock stack. The most recently |
| 13659 * pushed element and its children will be considered scrollable. All |
| 13660 * other elements will not be scrollable. |
| 13661 * |
| 13662 * Scroll locking is implemented as a stack so that cases such as |
| 13663 * dropdowns within dropdowns are handled well. |
| 13664 * |
| 13665 * @param {HTMLElement} element The element that should lock scroll. |
| 13666 */ |
| 13667 pushScrollLock: function(element) { |
| 13668 if (this._lockingElements.length === 0) { |
| 13669 this._lockScrollInteractions(); |
| 13670 } |
| 13671 |
| 13672 this._lockingElements.push(element); |
| 13673 |
| 13674 this._lockedElementCache = []; |
| 13675 this._unlockedElementCache = []; |
| 13676 }, |
| 13677 |
| 13678 /** |
| 13679 * Remove an element from the scroll lock stack. The element being |
| 13680 * removed does not need to be the most recently pushed element. However, |
| 13681 * the scroll lock constraints only change when the most recently pushed |
| 13682 * element is removed. |
| 13683 * |
| 13684 * @param {HTMLElement} element The element to remove from the scroll |
| 13685 * lock stack. |
| 13686 */ |
| 13687 removeScrollLock: function(element) { |
| 13688 var index = this._lockingElements.indexOf(element); |
| 13689 |
| 13690 if (index === -1) { |
| 13691 return; |
| 13692 } |
| 13693 |
| 13694 this._lockingElements.splice(index, 1); |
| 13695 |
| 13696 this._lockedElementCache = []; |
| 13697 this._unlockedElementCache = []; |
| 13698 |
| 13699 if (this._lockingElements.length === 0) { |
| 13700 this._unlockScrollInteractions(); |
| 13701 } |
| 13702 }, |
| 13703 |
| 13704 _lockingElements: [], |
| 13705 |
| 13706 _lockedElementCache: null, |
| 13707 |
| 13708 _unlockedElementCache: null, |
| 13709 |
| 13710 _originalBodyStyles: {}, |
| 13711 |
| 13712 _isScrollingKeypress: function(event) { |
| 13713 return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys( |
| 13714 event, 'pageup pagedown home end up left down right'); |
| 13715 }, |
| 13716 |
| 13717 _hasCachedLockedElement: function(element) { |
| 13718 return this._lockedElementCache.indexOf(element) > -1; |
| 13719 }, |
| 13720 |
| 13721 _hasCachedUnlockedElement: function(element) { |
| 13722 return this._unlockedElementCache.indexOf(element) > -1; |
| 13723 }, |
| 13724 |
| 13725 _composedTreeContains: function(element, child) { |
| 13726 // NOTE(cdata): This method iterates over content elements and their |
| 13727 // corresponding distributed nodes to implement a contains-like method |
| 13728 // that pierces through the composed tree of the ShadowDOM. Results of |
| 13729 // this operation are cached (elsewhere) on a per-scroll-lock basis, to |
| 13730 // guard against potentially expensive lookups happening repeatedly as |
| 13731 // a user scrolls / touchmoves. |
| 13732 var contentElements; |
| 13733 var distributedNodes; |
| 13734 var contentIndex; |
| 13735 var nodeIndex; |
| 13736 |
| 13737 if (element.contains(child)) { |
| 13738 return true; |
| 13739 } |
| 13740 |
| 13741 contentElements = Polymer.dom(element).querySelectorAll('content'); |
| 13742 |
| 13743 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { |
| 13744 |
| 13745 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); |
| 13746 |
| 13747 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ |
| 13748 |
| 13749 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ |
| 13750 return true; |
| 13751 } |
| 13752 } |
| 13753 } |
| 13754 |
| 13755 return false; |
| 13756 }, |
| 13757 |
| 13758 _scrollInteractionHandler: function(event) { |
| 13759 if (Polymer |
| 13760 .IronDropdownScrollManager |
| 13761 .elementIsScrollLocked(event.target)) { |
| 13762 if (event.type === 'keydown' && |
| 13763 !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) { |
| 13764 return; |
| 13765 } |
| 13766 |
| 13767 event.preventDefault(); |
| 13768 } |
| 13769 }, |
| 13770 |
| 13771 _lockScrollInteractions: function() { |
| 13772 // Memoize body inline styles: |
| 13773 this._originalBodyStyles.overflow = document.body.style.overflow; |
| 13774 this._originalBodyStyles.overflowX = document.body.style.overflowX; |
| 13775 this._originalBodyStyles.overflowY = document.body.style.overflowY; |
| 13776 |
| 13777 // Disable overflow scrolling on body: |
| 13778 // TODO(cdata): It is technically not sufficient to hide overflow on |
| 13779 // body alone. A better solution might be to traverse all ancestors of |
| 13780 // the current scroll locking element and hide overflow on them. This |
| 13781 // becomes expensive, though, as it would have to be redone every time |
| 13782 // a new scroll locking element is added. |
| 13783 document.body.style.overflow = 'hidden'; |
| 13784 document.body.style.overflowX = 'hidden'; |
| 13785 document.body.style.overflowY = 'hidden'; |
| 13786 |
| 13787 // Modern `wheel` event for mouse wheel scrolling: |
| 13788 window.addEventListener('wheel', this._scrollInteractionHandler, true); |
| 13789 // Older, non-standard `mousewheel` event for some FF: |
| 13790 window.addEventListener('mousewheel', this._scrollInteractionHandler, tr
ue); |
| 13791 // IE: |
| 13792 window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler
, true); |
| 13793 // Mobile devices can scroll on touch move: |
| 13794 window.addEventListener('touchmove', this._scrollInteractionHandler, tru
e); |
| 13795 // Capture keydown to prevent scrolling keys (pageup, pagedown etc.) |
| 13796 document.addEventListener('keydown', this._scrollInteractionHandler, tru
e); |
| 13797 }, |
| 13798 |
| 13799 _unlockScrollInteractions: function() { |
| 13800 document.body.style.overflow = this._originalBodyStyles.overflow; |
| 13801 document.body.style.overflowX = this._originalBodyStyles.overflowX; |
| 13802 document.body.style.overflowY = this._originalBodyStyles.overflowY; |
| 13803 |
| 13804 window.removeEventListener('wheel', this._scrollInteractionHandler, true
); |
| 13805 window.removeEventListener('mousewheel', this._scrollInteractionHandler,
true); |
| 13806 window.removeEventListener('DOMMouseScroll', this._scrollInteractionHand
ler, true); |
| 13807 window.removeEventListener('touchmove', this._scrollInteractionHandler,
true); |
| 13808 document.removeEventListener('keydown', this._scrollInteractionHandler,
true); |
| 13809 } |
| 13810 }; |
| 13811 })(); |
| 13812 (function() { |
| 13813 'use strict'; |
| 13814 |
| 13815 Polymer({ |
| 13816 is: 'iron-dropdown', |
| 13817 |
| 13818 behaviors: [ |
| 13819 Polymer.IronControlState, |
| 13820 Polymer.IronA11yKeysBehavior, |
| 13821 Polymer.IronOverlayBehavior, |
| 13822 Polymer.NeonAnimationRunnerBehavior |
| 13823 ], |
| 13824 |
| 13825 properties: { |
| 13826 /** |
| 13827 * The orientation against which to align the dropdown content |
| 13828 * horizontally relative to the dropdown trigger. |
| 13829 */ |
| 13830 horizontalAlign: { |
| 13831 type: String, |
| 13832 value: 'left', |
| 13833 reflectToAttribute: true |
| 13834 }, |
| 13835 |
| 13836 /** |
| 13837 * The orientation against which to align the dropdown content |
| 13838 * vertically relative to the dropdown trigger. |
| 13839 */ |
| 13840 verticalAlign: { |
| 13841 type: String, |
| 13842 value: 'top', |
| 13843 reflectToAttribute: true |
| 13844 }, |
| 13845 |
| 13846 /** |
| 13847 * A pixel value that will be added to the position calculated for the |
| 13848 * given `horizontalAlign`. Use a negative value to offset to the |
| 13849 * left, or a positive value to offset to the right. |
| 13850 */ |
| 13851 horizontalOffset: { |
| 13852 type: Number, |
| 13853 value: 0, |
| 13854 notify: true |
| 13855 }, |
| 13856 |
| 13857 /** |
| 13858 * A pixel value that will be added to the position calculated for the |
| 13859 * given `verticalAlign`. Use a negative value to offset towards the |
| 13860 * top, or a positive value to offset towards the bottom. |
| 13861 */ |
| 13862 verticalOffset: { |
| 13863 type: Number, |
| 13864 value: 0, |
| 13865 notify: true |
| 13866 }, |
| 13867 |
| 13868 /** |
| 13869 * The element that should be used to position the dropdown when |
| 13870 * it is opened. |
| 13871 */ |
| 13872 positionTarget: { |
| 13873 type: Object, |
| 13874 observer: '_positionTargetChanged' |
| 13875 }, |
| 13876 |
| 13877 /** |
| 13878 * An animation config. If provided, this will be used to animate the |
| 13879 * opening of the dropdown. |
| 13880 */ |
| 13881 openAnimationConfig: { |
| 13882 type: Object |
| 13883 }, |
| 13884 |
| 13885 /** |
| 13886 * An animation config. If provided, this will be used to animate the |
| 13887 * closing of the dropdown. |
| 13888 */ |
| 13889 closeAnimationConfig: { |
| 13890 type: Object |
| 13891 }, |
| 13892 |
| 13893 /** |
| 13894 * If provided, this will be the element that will be focused when |
| 13895 * the dropdown opens. |
| 13896 */ |
| 13897 focusTarget: { |
| 13898 type: Object |
| 13899 }, |
| 13900 |
| 13901 /** |
| 13902 * Set to true to disable animations when opening and closing the |
| 13903 * dropdown. |
| 13904 */ |
| 13905 noAnimations: { |
| 13906 type: Boolean, |
| 13907 value: false |
| 13908 }, |
| 13909 |
| 13910 /** |
| 13911 * By default, the dropdown will constrain scrolling on the page |
| 13912 * to itself when opened. |
| 13913 * Set to true in order to prevent scroll from being constrained |
| 13914 * to the dropdown when it opens. |
| 13915 */ |
| 13916 allowOutsideScroll: { |
| 13917 type: Boolean, |
| 13918 value: false |
| 13919 }, |
| 13920 |
| 13921 /** |
| 13922 * We memoize the positionTarget bounding rectangle so that we can |
| 13923 * limit the number of times it is queried per resize / relayout. |
| 13924 * @type {?Object} |
| 13925 */ |
| 13926 _positionRectMemo: { |
| 13927 type: Object |
| 13928 } |
| 13929 }, |
| 13930 |
| 13931 listeners: { |
| 13932 'neon-animation-finish': '_onNeonAnimationFinish' |
| 13933 }, |
| 13934 |
| 13935 observers: [ |
| 13936 '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset
, horizontalOffset)' |
| 13937 ], |
| 13938 |
| 13939 attached: function() { |
| 13940 if (this.positionTarget === undefined) { |
| 13941 this.positionTarget = this._defaultPositionTarget; |
| 13942 } |
| 13943 }, |
| 13944 |
| 13945 /** |
| 13946 * The element that is contained by the dropdown, if any. |
| 13947 */ |
| 13948 get containedElement() { |
| 13949 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 13950 }, |
| 13951 |
| 13952 /** |
| 13953 * The element that should be focused when the dropdown opens. |
| 13954 */ |
| 13955 get _focusTarget() { |
| 13956 return this.focusTarget || this.containedElement; |
| 13957 }, |
| 13958 |
| 13959 /** |
| 13960 * The element that should be used to position the dropdown when |
| 13961 * it opens, if no position target is configured. |
| 13962 */ |
| 13963 get _defaultPositionTarget() { |
| 13964 var parent = Polymer.dom(this).parentNode; |
| 13965 |
| 13966 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 13967 parent = parent.host; |
| 13968 } |
| 13969 |
| 13970 return parent; |
| 13971 }, |
| 13972 |
| 13973 /** |
| 13974 * The bounding rect of the position target. |
| 13975 */ |
| 13976 get _positionRect() { |
| 13977 if (!this._positionRectMemo && this.positionTarget) { |
| 13978 this._positionRectMemo = this.positionTarget.getBoundingClientRect()
; |
| 13979 } |
| 13980 |
| 13981 return this._positionRectMemo; |
| 13982 }, |
| 13983 |
| 13984 /** |
| 13985 * The horizontal offset value used to position the dropdown. |
| 13986 */ |
| 13987 get _horizontalAlignTargetValue() { |
| 13988 var target; |
| 13989 |
| 13990 if (this.horizontalAlign === 'right') { |
| 13991 target = document.documentElement.clientWidth - this._positionRect.r
ight; |
| 13992 } else { |
| 13993 target = this._positionRect.left; |
| 13994 } |
| 13995 |
| 13996 target += this.horizontalOffset; |
| 13997 |
| 13998 return Math.max(target, 0); |
| 13999 }, |
| 14000 |
| 14001 /** |
| 14002 * The vertical offset value used to position the dropdown. |
| 14003 */ |
| 14004 get _verticalAlignTargetValue() { |
| 14005 var target; |
| 14006 |
| 14007 if (this.verticalAlign === 'bottom') { |
| 14008 target = document.documentElement.clientHeight - this._positionRect.
bottom; |
| 14009 } else { |
| 14010 target = this._positionRect.top; |
| 14011 } |
| 14012 |
| 14013 target += this.verticalOffset; |
| 14014 |
| 14015 return Math.max(target, 0); |
| 14016 }, |
| 14017 |
| 14018 /** |
| 14019 * Called when the value of `opened` changes. |
| 14020 * |
| 14021 * @param {boolean} opened True if the dropdown is opened. |
| 14022 */ |
| 14023 _openedChanged: function(opened) { |
| 14024 if (opened && this.disabled) { |
| 14025 this.cancel(); |
| 14026 } else { |
| 14027 this.cancelAnimation(); |
| 14028 this._prepareDropdown(); |
| 14029 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); |
| 14030 } |
| 14031 |
| 14032 if (this.opened) { |
| 14033 this._focusContent(); |
| 14034 } |
| 14035 }, |
| 14036 |
| 14037 /** |
| 14038 * Overridden from `IronOverlayBehavior`. |
| 14039 */ |
| 14040 _renderOpened: function() { |
| 14041 if (!this.allowOutsideScroll) { |
| 14042 Polymer.IronDropdownScrollManager.pushScrollLock(this); |
| 14043 } |
| 14044 |
| 14045 if (!this.noAnimations && this.animationConfig && this.animationConfig
.open) { |
| 14046 this.$.contentWrapper.classList.add('animating'); |
| 14047 this.playAnimation('open'); |
| 14048 } else { |
| 14049 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; |
| 14050 } |
| 14051 }, |
| 14052 |
| 14053 /** |
| 14054 * Overridden from `IronOverlayBehavior`. |
| 14055 */ |
| 14056 _renderClosed: function() { |
| 14057 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
| 14058 if (!this.noAnimations && this.animationConfig && this.animationConfig
.close) { |
| 14059 this.$.contentWrapper.classList.add('animating'); |
| 14060 this.playAnimation('close'); |
| 14061 } else { |
| 14062 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; |
| 14063 } |
| 14064 }, |
| 14065 |
| 14066 /** |
| 14067 * Called when animation finishes on the dropdown (when opening or |
| 14068 * closing). Responsible for "completing" the process of opening or |
| 14069 * closing the dropdown by positioning it or setting its display to |
| 14070 * none. |
| 14071 */ |
| 14072 _onNeonAnimationFinish: function() { |
| 14073 this.$.contentWrapper.classList.remove('animating'); |
| 14074 if (this.opened) { |
| 14075 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this); |
| 14076 } else { |
| 14077 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this); |
| 14078 } |
| 14079 }, |
| 14080 |
| 14081 /** |
| 14082 * Called when an `iron-resize` event fires. |
| 14083 */ |
| 14084 _onIronResize: function() { |
| 14085 var containedElement = this.containedElement; |
| 14086 var scrollTop; |
| 14087 var scrollLeft; |
| 14088 |
| 14089 if (containedElement) { |
| 14090 scrollTop = containedElement.scrollTop; |
| 14091 scrollLeft = containedElement.scrollLeft; |
| 14092 } |
| 14093 |
| 14094 if (this.opened) { |
| 14095 this._updateOverlayPosition(); |
| 14096 } |
| 14097 |
| 14098 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments); |
| 14099 |
| 14100 if (containedElement) { |
| 14101 containedElement.scrollTop = scrollTop; |
| 14102 containedElement.scrollLeft = scrollLeft; |
| 14103 } |
| 14104 }, |
| 14105 |
| 14106 /** |
| 14107 * Called when the `positionTarget` property changes. |
| 14108 */ |
| 14109 _positionTargetChanged: function() { |
| 14110 this._updateOverlayPosition(); |
| 14111 }, |
| 14112 |
| 14113 /** |
| 14114 * Constructs the final animation config from different properties used |
| 14115 * to configure specific parts of the opening and closing animations. |
| 14116 */ |
| 14117 _updateAnimationConfig: function() { |
| 14118 var animationConfig = {}; |
| 14119 var animations = []; |
| 14120 |
| 14121 if (this.openAnimationConfig) { |
| 14122 // NOTE(cdata): When making `display:none` elements visible in Safar
i, |
| 14123 // the element will paint once in a fully visible state, causing the |
| 14124 // dropdown to flash before it fades in. We prepend an |
| 14125 // `opaque-animation` to fix this problem: |
| 14126 animationConfig.open = [{ |
| 14127 name: 'opaque-animation', |
| 14128 }].concat(this.openAnimationConfig); |
| 14129 animations = animations.concat(animationConfig.open); |
| 14130 } |
| 14131 |
| 14132 if (this.closeAnimationConfig) { |
| 14133 animationConfig.close = this.closeAnimationConfig; |
| 14134 animations = animations.concat(animationConfig.close); |
| 14135 } |
| 14136 |
| 14137 animations.forEach(function(animation) { |
| 14138 animation.node = this.containedElement; |
| 14139 }, this); |
| 14140 |
| 14141 this.animationConfig = animationConfig; |
| 14142 }, |
| 14143 |
| 14144 /** |
| 14145 * Prepares the dropdown for opening by updating measured layout |
| 14146 * values. |
| 14147 */ |
| 14148 _prepareDropdown: function() { |
| 14149 this.sizingTarget = this.containedElement || this.sizingTarget; |
| 14150 this._updateAnimationConfig(); |
| 14151 this._updateOverlayPosition(); |
| 14152 }, |
| 14153 |
| 14154 /** |
| 14155 * Updates the overlay position based on configured horizontal |
| 14156 * and vertical alignment, and re-memoizes these values for the sake |
| 14157 * of behavior in `IronFitBehavior`. |
| 14158 */ |
| 14159 _updateOverlayPosition: function() { |
| 14160 this._positionRectMemo = null; |
| 14161 |
| 14162 if (!this.positionTarget) { |
| 14163 return; |
| 14164 } |
| 14165 |
| 14166 this.style[this.horizontalAlign] = |
| 14167 this._horizontalAlignTargetValue + 'px'; |
| 14168 |
| 14169 this.style[this.verticalAlign] = |
| 14170 this._verticalAlignTargetValue + 'px'; |
| 14171 |
| 14172 // NOTE(cdata): We re-memoize inline styles here, otherwise |
| 14173 // calling `refit` from `IronFitBehavior` will reset inline styles |
| 14174 // to whatever they were when the dropdown first opened. |
| 14175 if (this._fitInfo) { |
| 14176 this._fitInfo.inlineStyle[this.horizontalAlign] = |
| 14177 this.style[this.horizontalAlign]; |
| 14178 |
| 14179 this._fitInfo.inlineStyle[this.verticalAlign] = |
| 14180 this.style[this.verticalAlign]; |
| 14181 } |
| 14182 }, |
| 14183 |
| 14184 /** |
| 14185 * Focuses the configured focus target. |
| 14186 */ |
| 14187 _focusContent: function() { |
| 14188 // NOTE(cdata): This is async so that it can attempt the focus after |
| 14189 // `display: none` is removed from the element. |
| 14190 this.async(function() { |
| 14191 if (this._focusTarget) { |
| 14192 this._focusTarget.focus(); |
| 14193 } |
| 14194 }); |
| 14195 } |
| 14196 }); |
| 14197 })(); |
| 14198 Polymer({ |
| 14199 |
| 14200 is: 'fade-in-animation', |
| 14201 |
| 14202 behaviors: [ |
| 14203 Polymer.NeonAnimationBehavior |
| 14204 ], |
| 14205 |
| 14206 configure: function(config) { |
| 14207 var node = config.node; |
| 14208 this._effect = new KeyframeEffect(node, [ |
| 14209 {'opacity': '0'}, |
| 14210 {'opacity': '1'} |
| 14211 ], this.timingFromConfig(config)); |
| 14212 return this._effect; |
| 14213 } |
| 14214 |
| 14215 }); |
| 14216 Polymer({ |
| 14217 |
| 14218 is: 'fade-out-animation', |
| 14219 |
| 14220 behaviors: [ |
| 14221 Polymer.NeonAnimationBehavior |
| 14222 ], |
| 14223 |
| 14224 configure: function(config) { |
| 14225 var node = config.node; |
| 14226 this._effect = new KeyframeEffect(node, [ |
| 14227 {'opacity': '1'}, |
| 14228 {'opacity': '0'} |
| 14229 ], this.timingFromConfig(config)); |
| 14230 return this._effect; |
| 14231 } |
| 14232 |
| 14233 }); |
| 14234 Polymer({ |
| 14235 is: 'paper-menu-grow-height-animation', |
| 14236 |
| 14237 behaviors: [ |
| 14238 Polymer.NeonAnimationBehavior |
| 14239 ], |
| 14240 |
| 14241 configure: function(config) { |
| 14242 var node = config.node; |
| 14243 var rect = node.getBoundingClientRect(); |
| 14244 var height = rect.height; |
| 14245 |
| 14246 this._effect = new KeyframeEffect(node, [{ |
| 14247 height: (height / 2) + 'px' |
| 14248 }, { |
| 14249 height: height + 'px' |
| 14250 }], this.timingFromConfig(config)); |
| 14251 |
| 14252 return this._effect; |
| 14253 } |
| 14254 }); |
| 14255 |
| 14256 Polymer({ |
| 14257 is: 'paper-menu-grow-width-animation', |
| 14258 |
| 14259 behaviors: [ |
| 14260 Polymer.NeonAnimationBehavior |
| 14261 ], |
| 14262 |
| 14263 configure: function(config) { |
| 14264 var node = config.node; |
| 14265 var rect = node.getBoundingClientRect(); |
| 14266 var width = rect.width; |
| 14267 |
| 14268 this._effect = new KeyframeEffect(node, [{ |
| 14269 width: (width / 2) + 'px' |
| 14270 }, { |
| 14271 width: width + 'px' |
| 14272 }], this.timingFromConfig(config)); |
| 14273 |
| 14274 return this._effect; |
| 14275 } |
| 14276 }); |
| 14277 |
| 14278 Polymer({ |
| 14279 is: 'paper-menu-shrink-width-animation', |
| 14280 |
| 14281 behaviors: [ |
| 14282 Polymer.NeonAnimationBehavior |
| 14283 ], |
| 14284 |
| 14285 configure: function(config) { |
| 14286 var node = config.node; |
| 14287 var rect = node.getBoundingClientRect(); |
| 14288 var width = rect.width; |
| 14289 |
| 14290 this._effect = new KeyframeEffect(node, [{ |
| 14291 width: width + 'px' |
| 14292 }, { |
| 14293 width: width - (width / 20) + 'px' |
| 14294 }], this.timingFromConfig(config)); |
| 14295 |
| 14296 return this._effect; |
| 14297 } |
| 14298 }); |
| 14299 |
| 14300 Polymer({ |
| 14301 is: 'paper-menu-shrink-height-animation', |
| 14302 |
| 14303 behaviors: [ |
| 14304 Polymer.NeonAnimationBehavior |
| 14305 ], |
| 14306 |
| 14307 configure: function(config) { |
| 14308 var node = config.node; |
| 14309 var rect = node.getBoundingClientRect(); |
| 14310 var height = rect.height; |
| 14311 var top = rect.top; |
| 14312 |
| 14313 this.setPrefixedProperty(node, 'transformOrigin', '0 0'); |
| 14314 |
| 14315 this._effect = new KeyframeEffect(node, [{ |
| 14316 height: height + 'px', |
| 14317 transform: 'translateY(0)' |
| 14318 }, { |
| 14319 height: height / 2 + 'px', |
| 14320 transform: 'translateY(-20px)' |
| 14321 }], this.timingFromConfig(config)); |
| 14322 |
| 14323 return this._effect; |
| 14324 } |
| 14325 }); |
| 14326 (function() { |
| 14327 'use strict'; |
| 14328 |
| 14329 var PaperMenuButton = Polymer({ |
| 14330 is: 'paper-menu-button', |
| 14331 |
| 14332 /** |
| 14333 * Fired when the dropdown opens. |
| 14334 * |
| 14335 * @event paper-dropdown-open |
| 14336 */ |
| 14337 |
| 14338 /** |
| 14339 * Fired when the dropdown closes. |
| 14340 * |
| 14341 * @event paper-dropdown-close |
| 14342 */ |
| 14343 |
| 14344 behaviors: [ |
| 14345 Polymer.IronA11yKeysBehavior, |
| 14346 Polymer.IronControlState |
| 14347 ], |
| 14348 |
| 14349 properties: { |
| 14350 |
| 14351 /** |
| 14352 * True if the content is currently displayed. |
| 14353 */ |
| 14354 opened: { |
| 14355 type: Boolean, |
| 14356 value: false, |
| 14357 notify: true, |
| 14358 observer: '_openedChanged' |
| 14359 }, |
| 14360 |
| 14361 /** |
| 14362 * The orientation against which to align the menu dropdown |
| 14363 * horizontally relative to the dropdown trigger. |
| 14364 */ |
| 14365 horizontalAlign: { |
| 14366 type: String, |
| 14367 value: 'left', |
| 14368 reflectToAttribute: true |
| 14369 }, |
| 14370 |
| 14371 /** |
| 14372 * The orientation against which to align the menu dropdown |
| 14373 * vertically relative to the dropdown trigger. |
| 14374 */ |
| 14375 verticalAlign: { |
| 14376 type: String, |
| 14377 value: 'top', |
| 14378 reflectToAttribute: true |
| 14379 }, |
| 14380 |
| 14381 /** |
| 14382 * A pixel value that will be added to the position calculated for the |
| 14383 * given `horizontalAlign`. Use a negative value to offset to the |
| 14384 * left, or a positive value to offset to the right. |
| 14385 */ |
| 14386 horizontalOffset: { |
| 14387 type: Number, |
| 14388 value: 0, |
| 14389 notify: true |
| 14390 }, |
| 14391 |
| 14392 /** |
| 14393 * A pixel value that will be added to the position calculated for the |
| 14394 * given `verticalAlign`. Use a negative value to offset towards the |
| 14395 * top, or a positive value to offset towards the bottom. |
| 14396 */ |
| 14397 verticalOffset: { |
| 14398 type: Number, |
| 14399 value: 0, |
| 14400 notify: true |
| 14401 }, |
| 14402 |
| 14403 /** |
| 14404 * Set to true to disable animations when opening and closing the |
| 14405 * dropdown. |
| 14406 */ |
| 14407 noAnimations: { |
| 14408 type: Boolean, |
| 14409 value: false |
| 14410 }, |
| 14411 |
| 14412 /** |
| 14413 * Set to true to disable automatically closing the dropdown after |
| 14414 * a selection has been made. |
| 14415 */ |
| 14416 ignoreSelect: { |
| 14417 type: Boolean, |
| 14418 value: false |
| 14419 }, |
| 14420 |
| 14421 /** |
| 14422 * An animation config. If provided, this will be used to animate the |
| 14423 * opening of the dropdown. |
| 14424 */ |
| 14425 openAnimationConfig: { |
| 14426 type: Object, |
| 14427 value: function() { |
| 14428 return [{ |
| 14429 name: 'fade-in-animation', |
| 14430 timing: { |
| 14431 delay: 100, |
| 14432 duration: 200 |
| 14433 } |
| 14434 }, { |
| 14435 name: 'paper-menu-grow-width-animation', |
| 14436 timing: { |
| 14437 delay: 100, |
| 14438 duration: 150, |
| 14439 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER |
| 14440 } |
| 14441 }, { |
| 14442 name: 'paper-menu-grow-height-animation', |
| 14443 timing: { |
| 14444 delay: 100, |
| 14445 duration: 275, |
| 14446 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER |
| 14447 } |
| 14448 }]; |
| 14449 } |
| 14450 }, |
| 14451 |
| 14452 /** |
| 14453 * An animation config. If provided, this will be used to animate the |
| 14454 * closing of the dropdown. |
| 14455 */ |
| 14456 closeAnimationConfig: { |
| 14457 type: Object, |
| 14458 value: function() { |
| 14459 return [{ |
| 14460 name: 'fade-out-animation', |
| 14461 timing: { |
| 14462 duration: 150 |
| 14463 } |
| 14464 }, { |
| 14465 name: 'paper-menu-shrink-width-animation', |
| 14466 timing: { |
| 14467 delay: 100, |
| 14468 duration: 50, |
| 14469 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER |
| 14470 } |
| 14471 }, { |
| 14472 name: 'paper-menu-shrink-height-animation', |
| 14473 timing: { |
| 14474 duration: 200, |
| 14475 easing: 'ease-in' |
| 14476 } |
| 14477 }]; |
| 14478 } |
| 14479 }, |
| 14480 |
| 14481 /** |
| 14482 * This is the element intended to be bound as the focus target |
| 14483 * for the `iron-dropdown` contained by `paper-menu-button`. |
| 14484 */ |
| 14485 _dropdownContent: { |
| 14486 type: Object |
| 14487 } |
| 14488 }, |
| 14489 |
| 14490 hostAttributes: { |
| 14491 role: 'group', |
| 14492 'aria-haspopup': 'true' |
| 14493 }, |
| 14494 |
| 14495 listeners: { |
| 14496 'iron-select': '_onIronSelect' |
| 14497 }, |
| 14498 |
| 14499 /** |
| 14500 * The content element that is contained by the menu button, if any. |
| 14501 */ |
| 14502 get contentElement() { |
| 14503 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 14504 }, |
| 14505 |
| 14506 /** |
| 14507 * Make the dropdown content appear as an overlay positioned relative |
| 14508 * to the dropdown trigger. |
| 14509 */ |
| 14510 open: function() { |
| 14511 if (this.disabled) { |
| 14512 return; |
| 14513 } |
| 14514 |
| 14515 this.$.dropdown.open(); |
| 14516 }, |
| 14517 |
| 14518 /** |
| 14519 * Hide the dropdown content. |
| 14520 */ |
| 14521 close: function() { |
| 14522 this.$.dropdown.close(); |
| 14523 }, |
| 14524 |
| 14525 /** |
| 14526 * When an `iron-select` event is received, the dropdown should |
| 14527 * automatically close on the assumption that a value has been chosen. |
| 14528 * |
| 14529 * @param {CustomEvent} event A CustomEvent instance with type |
| 14530 * set to `"iron-select"`. |
| 14531 */ |
| 14532 _onIronSelect: function(event) { |
| 14533 if (!this.ignoreSelect) { |
| 14534 this.close(); |
| 14535 } |
| 14536 }, |
| 14537 |
| 14538 /** |
| 14539 * When the dropdown opens, the `paper-menu-button` fires `paper-open`. |
| 14540 * When the dropdown closes, the `paper-menu-button` fires `paper-close`. |
| 14541 * |
| 14542 * @param {boolean} opened True if the dropdown is opened, otherwise false
. |
| 14543 * @param {boolean} oldOpened The previous value of `opened`. |
| 14544 */ |
| 14545 _openedChanged: function(opened, oldOpened) { |
| 14546 if (opened) { |
| 14547 // TODO(cdata): Update this when we can measure changes in distributed |
| 14548 // children in an idiomatic way. |
| 14549 // We poke this property in case the element has changed. This will |
| 14550 // cause the focus target for the `iron-dropdown` to be updated as |
| 14551 // necessary: |
| 14552 this._dropdownContent = this.contentElement; |
| 14553 this.fire('paper-dropdown-open'); |
| 14554 } else if (oldOpened != null) { |
| 14555 this.fire('paper-dropdown-close'); |
| 14556 } |
| 14557 }, |
| 14558 |
| 14559 /** |
| 14560 * If the dropdown is open when disabled becomes true, close the |
| 14561 * dropdown. |
| 14562 * |
| 14563 * @param {boolean} disabled True if disabled, otherwise false. |
| 14564 */ |
| 14565 _disabledChanged: function(disabled) { |
| 14566 Polymer.IronControlState._disabledChanged.apply(this, arguments); |
| 14567 if (disabled && this.opened) { |
| 14568 this.close(); |
| 14569 } |
| 14570 } |
| 14571 }); |
| 14572 |
| 14573 PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)'; |
| 14574 PaperMenuButton.MAX_ANIMATION_TIME_MS = 400; |
| 14575 |
| 14576 Polymer.PaperMenuButton = PaperMenuButton; |
| 14577 })(); |
| 14578 /** |
| 14579 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k
eyboard focus. |
| 14580 * |
| 14581 * @polymerBehavior Polymer.PaperInkyFocusBehavior |
| 14582 */ |
| 14583 Polymer.PaperInkyFocusBehaviorImpl = { |
| 14584 |
| 14585 observers: [ |
| 14586 '_focusedChanged(receivedFocusFromKeyboard)' |
| 14587 ], |
| 14588 |
| 14589 _focusedChanged: function(receivedFocusFromKeyboard) { |
| 14590 if (!this.$.ink) { |
| 14591 return; |
| 14592 } |
| 14593 |
| 14594 this.$.ink.holdDown = receivedFocusFromKeyboard; |
| 14595 } |
| 14596 |
| 14597 }; |
| 14598 |
| 14599 /** @polymerBehavior Polymer.PaperInkyFocusBehavior */ |
| 14600 Polymer.PaperInkyFocusBehavior = [ |
| 14601 Polymer.IronButtonState, |
| 14602 Polymer.IronControlState, |
| 14603 Polymer.PaperInkyFocusBehaviorImpl |
| 14604 ]; |
| 14605 Polymer({ |
| 14606 is: 'paper-icon-button', |
| 14607 |
| 14608 hostAttributes: { |
| 14609 role: 'button', |
| 14610 tabindex: '0' |
| 14611 }, |
| 14612 |
| 14613 behaviors: [ |
| 14614 Polymer.PaperInkyFocusBehavior |
| 14615 ], |
| 14616 |
| 14617 properties: { |
| 14618 /** |
| 14619 * The URL of an image for the icon. If the src property is specified, |
| 14620 * the icon property should not be. |
| 14621 */ |
| 14622 src: { |
| 14623 type: String |
| 14624 }, |
| 14625 |
| 14626 /** |
| 14627 * Specifies the icon name or index in the set of icons available in |
| 14628 * the icon's icon set. If the icon property is specified, |
| 14629 * the src property should not be. |
| 14630 */ |
| 14631 icon: { |
| 14632 type: String |
| 14633 }, |
| 14634 |
| 14635 /** |
| 14636 * Specifies the alternate text for the button, for accessibility. |
| 14637 */ |
| 14638 alt: { |
| 14639 type: String, |
| 14640 observer: "_altChanged" |
| 14641 } |
| 14642 }, |
| 14643 |
| 14644 _altChanged: function(newValue, oldValue) { |
| 14645 var label = this.getAttribute('aria-label'); |
| 14646 |
| 14647 // Don't stomp over a user-set aria-label. |
| 14648 if (!label || oldValue == label) { |
| 14649 this.setAttribute('aria-label', newValue); |
| 14650 } |
| 14651 } |
| 14652 }); |
| 14653 /** |
| 14654 * Use `Polymer.IronValidatableBehavior` to implement an element that validate
s user input. |
| 14655 * |
| 14656 * ### Accessibility |
| 14657 * |
| 14658 * Changing the `invalid` property, either manually or by calling `validate()`
will update the |
| 14659 * `aria-invalid` attribute. |
| 14660 * |
| 14661 * @demo demo/index.html |
| 14662 * @polymerBehavior |
| 14663 */ |
| 14664 Polymer.IronValidatableBehavior = { |
| 14665 |
| 14666 properties: { |
| 14667 |
| 14668 /** |
| 14669 * Namespace for this validator. |
| 14670 */ |
| 14671 validatorType: { |
| 14672 type: String, |
| 14673 value: 'validator' |
| 14674 }, |
| 14675 |
| 14676 /** |
| 14677 * Name of the validator to use. |
| 14678 */ |
| 14679 validator: { |
| 14680 type: String |
| 14681 }, |
| 14682 |
| 14683 /** |
| 14684 * True if the last call to `validate` is invalid. |
| 14685 */ |
| 14686 invalid: { |
| 14687 notify: true, |
| 14688 reflectToAttribute: true, |
| 14689 type: Boolean, |
| 14690 value: false |
| 14691 }, |
| 14692 |
| 14693 _validatorMeta: { |
| 14694 type: Object |
| 14695 } |
| 14696 |
| 14697 }, |
| 14698 |
| 14699 observers: [ |
| 14700 '_invalidChanged(invalid)' |
| 14701 ], |
| 14702 |
| 14703 get _validator() { |
| 14704 return this._validatorMeta && this._validatorMeta.byKey(this.validator); |
| 14705 }, |
| 14706 |
| 14707 ready: function() { |
| 14708 this._validatorMeta = new Polymer.IronMeta({type: this.validatorType}); |
| 14709 }, |
| 14710 |
| 14711 _invalidChanged: function() { |
| 14712 if (this.invalid) { |
| 14713 this.setAttribute('aria-invalid', 'true'); |
| 14714 } else { |
| 14715 this.removeAttribute('aria-invalid'); |
| 14716 } |
| 14717 }, |
| 14718 |
| 14719 /** |
| 14720 * @return {boolean} True if the validator `validator` exists. |
| 14721 */ |
| 14722 hasValidator: function() { |
| 14723 return this._validator != null; |
| 14724 }, |
| 14725 |
| 14726 /** |
| 14727 * Returns true if the `value` is valid, and updates `invalid`. If you want |
| 14728 * your element to have custom validation logic, do not override this method
; |
| 14729 * override `_getValidity(value)` instead. |
| 14730 |
| 14731 * @param {Object} value The value to be validated. By default, it is passed |
| 14732 * to the validator's `validate()` function, if a validator is set. |
| 14733 * @return {boolean} True if `value` is valid. |
| 14734 */ |
| 14735 validate: function(value) { |
| 14736 this.invalid = !this._getValidity(value); |
| 14737 return !this.invalid; |
| 14738 }, |
| 14739 |
| 14740 /** |
| 14741 * Returns true if `value` is valid. By default, it is passed |
| 14742 * to the validator's `validate()` function, if a validator is set. You |
| 14743 * should override this method if you want to implement custom validity |
| 14744 * logic for your element. |
| 14745 * |
| 14746 * @param {Object} value The value to be validated. |
| 14747 * @return {boolean} True if `value` is valid. |
| 14748 */ |
| 14749 |
| 14750 _getValidity: function(value) { |
| 14751 if (this.hasValidator()) { |
| 14752 return this._validator.validate(value); |
| 14753 } |
| 14754 return true; |
| 14755 } |
| 14756 }; |
| 14757 /* |
| 14758 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal
idatorBehavior` |
| 14759 to `<input>`. |
| 14760 |
| 14761 ### Two-way binding |
| 14762 |
| 14763 By default you can only get notified of changes to an `input`'s `value` due to u
ser input: |
| 14764 |
| 14765 <input value="{{myValue::input}}"> |
| 14766 |
| 14767 `iron-input` adds the `bind-value` property that mirrors the `value` property, a
nd can be used |
| 14768 for two-way data binding. `bind-value` will notify if it is changed either by us
er input or by script. |
| 14769 |
| 14770 <input is="iron-input" bind-value="{{myValue}}"> |
| 14771 |
| 14772 ### Custom validators |
| 14773 |
| 14774 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit
h `<iron-input>`. |
| 14775 |
| 14776 <input is="iron-input" validator="my-custom-validator"> |
| 14777 |
| 14778 ### Stopping invalid input |
| 14779 |
| 14780 It may be desirable to only allow users to enter certain characters. You can use
the |
| 14781 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish
this. This feature |
| 14782 is separate from validation, and `allowed-pattern` does not affect how the input
is validated. |
| 14783 |
| 14784 <!-- only allow characters that match [0-9] --> |
| 14785 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> |
| 14786 |
| 14787 @hero hero.svg |
| 14788 @demo demo/index.html |
| 14789 */ |
| 14790 |
| 14791 Polymer({ |
| 14792 |
| 14793 is: 'iron-input', |
| 14794 |
| 14795 extends: 'input', |
| 14796 |
| 14797 behaviors: [ |
| 14798 Polymer.IronValidatableBehavior |
| 14799 ], |
| 14800 |
| 14801 properties: { |
| 14802 |
| 14803 /** |
| 14804 * Use this property instead of `value` for two-way data binding. |
| 14805 */ |
| 14806 bindValue: { |
| 14807 observer: '_bindValueChanged', |
| 14808 type: String |
| 14809 }, |
| 14810 |
| 14811 /** |
| 14812 * Set to true to prevent the user from entering invalid input. The new in
put characters are |
| 14813 * matched with `allowedPattern` if it is set, otherwise it will use the `
pattern` attribute if |
| 14814 * set, or the `type` attribute (only supported for `type=number`). |
| 14815 */ |
| 14816 preventInvalidInput: { |
| 14817 type: Boolean |
| 14818 }, |
| 14819 |
| 14820 /** |
| 14821 * Regular expression to match valid input characters. |
| 14822 */ |
| 14823 allowedPattern: { |
| 14824 type: String |
| 14825 }, |
| 14826 |
| 14827 _previousValidInput: { |
| 14828 type: String, |
| 14829 value: '' |
| 14830 }, |
| 14831 |
| 14832 _patternAlreadyChecked: { |
| 14833 type: Boolean, |
| 14834 value: false |
| 14835 } |
| 14836 |
| 14837 }, |
| 14838 |
| 14839 listeners: { |
| 14840 'input': '_onInput', |
| 14841 'keypress': '_onKeypress' |
| 14842 }, |
| 14843 |
| 14844 get _patternRegExp() { |
| 14845 var pattern; |
| 14846 if (this.allowedPattern) { |
| 14847 pattern = new RegExp(this.allowedPattern); |
| 14848 } else if (this.pattern) { |
| 14849 pattern = new RegExp(this.pattern); |
| 14850 } else { |
| 14851 switch (this.type) { |
| 14852 case 'number': |
| 14853 pattern = /[0-9.,e-]/; |
| 14854 break; |
| 14855 } |
| 14856 } |
| 14857 return pattern; |
| 14858 }, |
| 14859 |
| 14860 ready: function() { |
| 14861 this.bindValue = this.value; |
| 14862 }, |
| 14863 |
| 14864 /** |
| 14865 * @suppress {checkTypes} |
| 14866 */ |
| 14867 _bindValueChanged: function() { |
| 14868 if (this.value !== this.bindValue) { |
| 14869 this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindV
alue; |
| 14870 } |
| 14871 // manually notify because we don't want to notify until after setting val
ue |
| 14872 this.fire('bind-value-changed', {value: this.bindValue}); |
| 14873 }, |
| 14874 |
| 14875 _onInput: function() { |
| 14876 // Need to validate each of the characters pasted if they haven't |
| 14877 // been validated inside `_onKeypress` already. |
| 14878 if (this.preventInvalidInput && !this._patternAlreadyChecked) { |
| 14879 var valid = this._checkPatternValidity(); |
| 14880 if (!valid) { |
| 14881 this.value = this._previousValidInput; |
| 14882 } |
| 14883 } |
| 14884 |
| 14885 this.bindValue = this.value; |
| 14886 this._previousValidInput = this.value; |
| 14887 this._patternAlreadyChecked = false; |
| 14888 }, |
| 14889 |
| 14890 _isPrintable: function(event) { |
| 14891 // What a control/printable character is varies wildly based on the browse
r. |
| 14892 // - most control characters (arrows, backspace) do not send a `keypress`
event |
| 14893 // in Chrome, but the *do* on Firefox |
| 14894 // - in Firefox, when they do send a `keypress` event, control chars have |
| 14895 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) |
| 14896 // - printable characters always send a keypress event. |
| 14897 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the
keyCode |
| 14898 // always matches the charCode. |
| 14899 // None of this makes any sense. |
| 14900 |
| 14901 // For these keys, ASCII code == browser keycode. |
| 14902 var anyNonPrintable = |
| 14903 (event.keyCode == 8) || // backspace |
| 14904 (event.keyCode == 9) || // tab |
| 14905 (event.keyCode == 13) || // enter |
| 14906 (event.keyCode == 27); // escape |
| 14907 |
| 14908 // For these keys, make sure it's a browser keycode and not an ASCII code. |
| 14909 var mozNonPrintable = |
| 14910 (event.keyCode == 19) || // pause |
| 14911 (event.keyCode == 20) || // caps lock |
| 14912 (event.keyCode == 45) || // insert |
| 14913 (event.keyCode == 46) || // delete |
| 14914 (event.keyCode == 144) || // num lock |
| 14915 (event.keyCode == 145) || // scroll lock |
| 14916 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho
me, arrows |
| 14917 (event.keyCode > 111 && event.keyCode < 124); // fn keys |
| 14918 |
| 14919 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); |
| 14920 }, |
| 14921 |
| 14922 _onKeypress: function(event) { |
| 14923 if (!this.preventInvalidInput && this.type !== 'number') { |
| 14924 return; |
| 14925 } |
| 14926 var regexp = this._patternRegExp; |
| 14927 if (!regexp) { |
| 14928 return; |
| 14929 } |
| 14930 |
| 14931 // Handle special keys and backspace |
| 14932 if (event.metaKey || event.ctrlKey || event.altKey) |
| 14933 return; |
| 14934 |
| 14935 // Check the pattern either here or in `_onInput`, but not in both. |
| 14936 this._patternAlreadyChecked = true; |
| 14937 |
| 14938 var thisChar = String.fromCharCode(event.charCode); |
| 14939 if (this._isPrintable(event) && !regexp.test(thisChar)) { |
| 14940 event.preventDefault(); |
| 14941 } |
| 14942 }, |
| 14943 |
| 14944 _checkPatternValidity: function() { |
| 14945 var regexp = this._patternRegExp; |
| 14946 if (!regexp) { |
| 14947 return true; |
| 14948 } |
| 14949 for (var i = 0; i < this.value.length; i++) { |
| 14950 if (!regexp.test(this.value[i])) { |
| 14951 return false; |
| 14952 } |
| 14953 } |
| 14954 return true; |
| 14955 }, |
| 14956 |
| 14957 /** |
| 14958 * Returns true if `value` is valid. The validator provided in `validator` w
ill be used first, |
| 14959 * then any constraints. |
| 14960 * @return {boolean} True if the value is valid. |
| 14961 */ |
| 14962 validate: function() { |
| 14963 // Empty, non-required input is valid. |
| 14964 if (!this.required && this.value == '') { |
| 14965 this.invalid = false; |
| 14966 return true; |
| 14967 } |
| 14968 |
| 14969 var valid; |
| 14970 if (this.hasValidator()) { |
| 14971 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); |
| 14972 } else { |
| 14973 this.invalid = !this.validity.valid; |
| 14974 valid = this.validity.valid; |
| 14975 } |
| 14976 this.fire('iron-input-validate'); |
| 14977 return valid; |
| 14978 } |
| 14979 |
| 14980 }); |
| 14981 |
| 14982 /* |
| 14983 The `iron-input-validate` event is fired whenever `validate()` is called. |
| 14984 @event iron-input-validate |
| 14985 */ |
| 14986 Polymer({ |
| 14987 is: 'paper-input-container', |
| 14988 |
| 14989 properties: { |
| 14990 /** |
| 14991 * Set to true to disable the floating label. The label disappears when th
e input value is |
| 14992 * not null. |
| 14993 */ |
| 14994 noLabelFloat: { |
| 14995 type: Boolean, |
| 14996 value: false |
| 14997 }, |
| 14998 |
| 14999 /** |
| 15000 * Set to true to always float the floating label. |
| 15001 */ |
| 15002 alwaysFloatLabel: { |
| 15003 type: Boolean, |
| 15004 value: false |
| 15005 }, |
| 15006 |
| 15007 /** |
| 15008 * The attribute to listen for value changes on. |
| 15009 */ |
| 15010 attrForValue: { |
| 15011 type: String, |
| 15012 value: 'bind-value' |
| 15013 }, |
| 15014 |
| 15015 /** |
| 15016 * Set to true to auto-validate the input value when it changes. |
| 15017 */ |
| 15018 autoValidate: { |
| 15019 type: Boolean, |
| 15020 value: false |
| 15021 }, |
| 15022 |
| 15023 /** |
| 15024 * True if the input is invalid. This property is set automatically when t
he input value |
| 15025 * changes if auto-validating, or when the `iron-input-validate` event is
heard from a child. |
| 15026 */ |
| 15027 invalid: { |
| 15028 observer: '_invalidChanged', |
| 15029 type: Boolean, |
| 15030 value: false |
| 15031 }, |
| 15032 |
| 15033 /** |
| 15034 * True if the input has focus. |
| 15035 */ |
| 15036 focused: { |
| 15037 readOnly: true, |
| 15038 type: Boolean, |
| 15039 value: false, |
| 15040 notify: true |
| 15041 }, |
| 15042 |
| 15043 _addons: { |
| 15044 type: Array |
| 15045 // do not set a default value here intentionally - it will be initialize
d lazily when a |
| 15046 // distributed child is attached, which may occur before configuration f
or this element |
| 15047 // in polyfill. |
| 15048 }, |
| 15049 |
| 15050 _inputHasContent: { |
| 15051 type: Boolean, |
| 15052 value: false |
| 15053 }, |
| 15054 |
| 15055 _inputSelector: { |
| 15056 type: String, |
| 15057 value: 'input,textarea,.paper-input-input' |
| 15058 }, |
| 15059 |
| 15060 _boundOnFocus: { |
| 15061 type: Function, |
| 15062 value: function() { |
| 15063 return this._onFocus.bind(this); |
| 15064 } |
| 15065 }, |
| 15066 |
| 15067 _boundOnBlur: { |
| 15068 type: Function, |
| 15069 value: function() { |
| 15070 return this._onBlur.bind(this); |
| 15071 } |
| 15072 }, |
| 15073 |
| 15074 _boundOnInput: { |
| 15075 type: Function, |
| 15076 value: function() { |
| 15077 return this._onInput.bind(this); |
| 15078 } |
| 15079 }, |
| 15080 |
| 15081 _boundValueChanged: { |
| 15082 type: Function, |
| 15083 value: function() { |
| 15084 return this._onValueChanged.bind(this); |
| 15085 } |
| 15086 } |
| 15087 }, |
| 15088 |
| 15089 listeners: { |
| 15090 'addon-attached': '_onAddonAttached', |
| 15091 'iron-input-validate': '_onIronInputValidate' |
| 15092 }, |
| 15093 |
| 15094 get _valueChangedEvent() { |
| 15095 return this.attrForValue + '-changed'; |
| 15096 }, |
| 15097 |
| 15098 get _propertyForValue() { |
| 15099 return Polymer.CaseMap.dashToCamelCase(this.attrForValue); |
| 15100 }, |
| 15101 |
| 15102 get _inputElement() { |
| 15103 return Polymer.dom(this).querySelector(this._inputSelector); |
| 15104 }, |
| 15105 |
| 15106 get _inputElementValue() { |
| 15107 return this._inputElement[this._propertyForValue] || this._inputElement.va
lue; |
| 15108 }, |
| 15109 |
| 15110 ready: function() { |
| 15111 if (!this._addons) { |
| 15112 this._addons = []; |
| 15113 } |
| 15114 this.addEventListener('focus', this._boundOnFocus, true); |
| 15115 this.addEventListener('blur', this._boundOnBlur, true); |
| 15116 if (this.attrForValue) { |
| 15117 this._inputElement.addEventListener(this._valueChangedEvent, this._bound
ValueChanged); |
| 15118 } else { |
| 15119 this.addEventListener('input', this._onInput); |
| 15120 } |
| 15121 }, |
| 15122 |
| 15123 attached: function() { |
| 15124 // Only validate when attached if the input already has a value. |
| 15125 if (this._inputElementValue != '') { |
| 15126 this._handleValueAndAutoValidate(this._inputElement); |
| 15127 } else { |
| 15128 this._handleValue(this._inputElement); |
| 15129 } |
| 15130 }, |
| 15131 |
| 15132 _onAddonAttached: function(event) { |
| 15133 if (!this._addons) { |
| 15134 this._addons = []; |
| 15135 } |
| 15136 var target = event.target; |
| 15137 if (this._addons.indexOf(target) === -1) { |
| 15138 this._addons.push(target); |
| 15139 if (this.isAttached) { |
| 15140 this._handleValue(this._inputElement); |
| 15141 } |
| 15142 } |
| 15143 }, |
| 15144 |
| 15145 _onFocus: function() { |
| 15146 this._setFocused(true); |
| 15147 }, |
| 15148 |
| 15149 _onBlur: function() { |
| 15150 this._setFocused(false); |
| 15151 this._handleValueAndAutoValidate(this._inputElement); |
| 15152 }, |
| 15153 |
| 15154 _onInput: function(event) { |
| 15155 this._handleValueAndAutoValidate(event.target); |
| 15156 }, |
| 15157 |
| 15158 _onValueChanged: function(event) { |
| 15159 this._handleValueAndAutoValidate(event.target); |
| 15160 }, |
| 15161 |
| 15162 _handleValue: function(inputElement) { |
| 15163 var value = this._inputElementValue; |
| 15164 |
| 15165 // type="number" hack needed because this.value is empty until it's valid |
| 15166 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme
nt.checkValidity())) { |
| 15167 this._inputHasContent = true; |
| 15168 } else { |
| 15169 this._inputHasContent = false; |
| 15170 } |
| 15171 |
| 15172 this.updateAddons({ |
| 15173 inputElement: inputElement, |
| 15174 value: value, |
| 15175 invalid: this.invalid |
| 15176 }); |
| 15177 }, |
| 15178 |
| 15179 _handleValueAndAutoValidate: function(inputElement) { |
| 15180 if (this.autoValidate) { |
| 15181 var valid; |
| 15182 if (inputElement.validate) { |
| 15183 valid = inputElement.validate(this._inputElementValue); |
| 15184 } else { |
| 15185 valid = inputElement.checkValidity(); |
| 15186 } |
| 15187 this.invalid = !valid; |
| 15188 } |
| 15189 |
| 15190 // Call this last to notify the add-ons. |
| 15191 this._handleValue(inputElement); |
| 15192 }, |
| 15193 |
| 15194 _onIronInputValidate: function(event) { |
| 15195 this.invalid = this._inputElement.invalid; |
| 15196 }, |
| 15197 |
| 15198 _invalidChanged: function() { |
| 15199 if (this._addons) { |
| 15200 this.updateAddons({invalid: this.invalid}); |
| 15201 } |
| 15202 }, |
| 15203 |
| 15204 /** |
| 15205 * Call this to update the state of add-ons. |
| 15206 * @param {Object} state Add-on state. |
| 15207 */ |
| 15208 updateAddons: function(state) { |
| 15209 for (var addon, index = 0; addon = this._addons[index]; index++) { |
| 15210 addon.update(state); |
| 15211 } |
| 15212 }, |
| 15213 |
| 15214 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused,
invalid, _inputHasContent) { |
| 15215 var cls = 'input-content'; |
| 15216 if (!noLabelFloat) { |
| 15217 var label = this.querySelector('label'); |
| 15218 |
| 15219 if (alwaysFloatLabel || _inputHasContent) { |
| 15220 cls += ' label-is-floating'; |
| 15221 if (invalid) { |
| 15222 cls += ' is-invalid'; |
| 15223 } else if (focused) { |
| 15224 cls += " label-is-highlighted"; |
| 15225 } |
| 15226 // The label might have a horizontal offset if a prefix element exists |
| 15227 // which needs to be undone when displayed as a floating label. |
| 15228 if (Polymer.dom(this.$.prefix).getDistributedNodes().length > 0 && |
| 15229 label && label.offsetParent) { |
| 15230 label.style.left = -label.offsetParent.offsetLeft + 'px'; |
| 15231 } |
| 15232 } else { |
| 15233 // When the label is not floating, it should overlap the input element
. |
| 15234 if (label) { |
| 15235 label.style.left = 0; |
| 15236 } |
| 15237 } |
| 15238 } else { |
| 15239 if (_inputHasContent) { |
| 15240 cls += ' label-is-hidden'; |
| 15241 } |
| 15242 } |
| 15243 return cls; |
| 15244 }, |
| 15245 |
| 15246 _computeUnderlineClass: function(focused, invalid) { |
| 15247 var cls = 'underline'; |
| 15248 if (invalid) { |
| 15249 cls += ' is-invalid'; |
| 15250 } else if (focused) { |
| 15251 cls += ' is-highlighted' |
| 15252 } |
| 15253 return cls; |
| 15254 }, |
| 15255 |
| 15256 _computeAddOnContentClass: function(focused, invalid) { |
| 15257 var cls = 'add-on-content'; |
| 15258 if (invalid) { |
| 15259 cls += ' is-invalid'; |
| 15260 } else if (focused) { |
| 15261 cls += ' is-highlighted' |
| 15262 } |
| 15263 return cls; |
| 15264 } |
| 15265 }); |
| 15266 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 15267 // Use of this source code is governed by a BSD-style license that can be |
| 15268 // found in the LICENSE file. |
| 15269 |
| 15270 /** @interface */ |
| 15271 var SearchFieldDelegate = function() {}; |
| 15272 |
| 15273 SearchFieldDelegate.prototype = { |
| 15274 /** |
| 15275 * @param {string} value |
| 15276 */ |
| 15277 onSearchTermSearch: assertNotReached, |
| 15278 }; |
| 15279 |
| 15280 var SearchField = Polymer({ |
| 15281 is: 'cr-search-field', |
| 15282 |
| 15283 properties: { |
| 15284 label: { |
| 15285 type: String, |
| 15286 value: '', |
| 15287 }, |
| 15288 |
| 15289 clearLabel: { |
| 15290 type: String, |
| 15291 value: '', |
| 15292 }, |
| 15293 |
| 15294 showingSearch_: { |
| 15295 type: Boolean, |
| 15296 value: false, |
| 15297 }, |
| 15298 }, |
| 15299 |
| 15300 /** @param {SearchFieldDelegate} delegate */ |
| 15301 setDelegate: function(delegate) { |
| 15302 this.delegate_ = delegate; |
| 15303 }, |
| 15304 |
| 15305 /** |
| 15306 * Returns the value of the search field. |
| 15307 * @return {string} |
| 15308 */ |
| 15309 getValue: function() { |
| 15310 var searchInput = this.$$('#search-input'); |
| 15311 return searchInput ? searchInput.value : ''; |
| 15312 }, |
| 15313 |
| 15314 /** @private */ |
| 15315 onSearchTermSearch_: function() { |
| 15316 if (this.delegate_) |
| 15317 this.delegate_.onSearchTermSearch(this.getValue()); |
| 15318 }, |
| 15319 |
| 15320 /** @private */ |
| 15321 onSearchTermKeydown_: function(e) { |
| 15322 assert(this.showingSearch_); |
| 15323 if (e.keyIdentifier == 'U+001B') // Escape. |
| 15324 this.toggleShowingSearch_(); |
| 15325 }, |
| 15326 |
| 15327 /** @private */ |
| 15328 toggleShowingSearch_: function() { |
| 15329 this.showingSearch_ = !this.showingSearch_; |
| 15330 this.async(function() { |
| 15331 var searchInput = this.$$('#search-input'); |
| 15332 if (this.showingSearch_) { |
| 15333 searchInput.focus(); |
| 15334 } else { |
| 15335 searchInput.value = ''; |
| 15336 this.onSearchTermSearch_(); |
| 15337 } |
| 15338 }); |
| 15339 }, |
| 15340 }); |
| 15341 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 15342 // Use of this source code is governed by a BSD-style license that can be |
| 15343 // found in the LICENSE file. |
| 15344 |
| 15345 cr.define('downloads', function() { |
| 15346 var Toolbar = Polymer({ |
| 15347 is: 'downloads-toolbar', |
| 15348 |
| 15349 /** @param {!downloads.ActionService} actionService */ |
| 15350 setActionService: function(actionService) { |
| 15351 /** @private {!downloads.ActionService} */ |
| 15352 this.actionService_ = actionService; |
| 15353 }, |
| 15354 |
| 15355 attached: function() { |
| 15356 /** @private {!SearchFieldDelegate} */ |
| 15357 this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this); |
| 15358 this.$['search-input'].setDelegate(this.searchFieldDelegate_); |
| 15359 }, |
| 15360 |
| 15361 properties: { |
| 15362 downloadsShowing: { |
| 15363 reflectToAttribute: true, |
| 15364 type: Boolean, |
| 15365 value: false, |
| 15366 observer: 'onDownloadsShowingChange_', |
| 15367 }, |
| 15368 }, |
| 15369 |
| 15370 /** @return {boolean} Whether removal can be undone. */ |
| 15371 canUndo: function() { |
| 15372 return this.$['search-input'] != this.shadowRoot.activeElement; |
| 15373 }, |
| 15374 |
| 15375 /** @return {boolean} Whether "Clear all" should be allowed. */ |
| 15376 canClearAll: function() { |
| 15377 return !this.$['search-input'].getValue() && this.downloadsShowing; |
| 15378 }, |
| 15379 |
| 15380 /** @private */ |
| 15381 onClearAllClick_: function() { |
| 15382 assert(this.canClearAll()); |
| 15383 this.actionService_.clearAll(); |
| 15384 }, |
| 15385 |
| 15386 /** @private */ |
| 15387 onDownloadsShowingChange_: function() { |
| 15388 this.updateClearAll_(); |
| 15389 }, |
| 15390 |
| 15391 /** @param {string} searchTerm */ |
| 15392 onSearchTermSearch: function(searchTerm) { |
| 15393 this.actionService_.search(searchTerm); |
| 15394 this.updateClearAll_(); |
| 15395 }, |
| 15396 |
| 15397 /** @private */ |
| 15398 onOpenDownloadsFolderClick_: function() { |
| 15399 this.actionService_.openDownloadsFolder(); |
| 15400 }, |
| 15401 |
| 15402 /** @private */ |
| 15403 updateClearAll_: function() { |
| 15404 this.$$('#actions .clear-all').hidden = !this.canClearAll(); |
| 15405 this.$$('paper-menu .clear-all').hidden = !this.canClearAll(); |
| 15406 }, |
| 15407 }); |
| 15408 |
| 15409 /** |
| 15410 * @constructor |
| 15411 * @implements {SearchFieldDelegate} |
| 15412 */ |
| 15413 // TODO(devlin): This is a bit excessive, and it would be better to just have |
| 15414 // Toolbar implement SearchFieldDelegate. But for now, we don't know how to |
| 15415 // make that happen with closure compiler. |
| 15416 function ToolbarSearchFieldDelegate(toolbar) { |
| 15417 this.toolbar_ = toolbar; |
| 15418 } |
| 15419 |
| 15420 ToolbarSearchFieldDelegate.prototype = { |
| 15421 /** @override */ |
| 15422 onSearchTermSearch: function(searchTerm) { |
| 15423 this.toolbar_.onSearchTermSearch(searchTerm); |
| 15424 } |
| 15425 }; |
| 15426 |
| 15427 return {Toolbar: Toolbar}; |
| 15428 }); |
| 15429 |
| 15430 // TODO(dbeam): https://github.com/PolymerElements/iron-dropdown/pull/16/files |
| 15431 /** @suppress {checkTypes} */ |
| 15432 (function() { |
| 15433 Polymer.IronDropdownScrollManager.pushScrollLock = function() {}; |
| 15434 })(); |
| 15435 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 15436 // Use of this source code is governed by a BSD-style license that can be |
| 15437 // found in the LICENSE file. |
| 15438 |
| 15439 cr.define('downloads', function() { |
| 15440 var Manager = Polymer({ |
| 15441 is: 'downloads-manager', |
| 15442 |
| 15443 created: function() { |
| 15444 /** @private {!downloads.ActionService} */ |
| 15445 this.actionService_ = new downloads.ActionService; |
| 15446 }, |
| 15447 |
| 15448 properties: { |
| 15449 hasDownloads_: { |
| 15450 type: Boolean, |
| 15451 value: false, |
| 15452 }, |
| 15453 }, |
| 15454 |
| 15455 /** |
| 15456 * @return {number} A guess at how many items could be visible at once. |
| 15457 * @private |
| 15458 */ |
| 15459 guesstimateNumberOfVisibleItems_: function() { |
| 15460 var toolbarHeight = this.$.toolbar.offsetHeight; |
| 15461 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1; |
| 15462 }, |
| 15463 |
| 15464 /** |
| 15465 * @param {Event} e |
| 15466 * @private |
| 15467 */ |
| 15468 onCanExecute_: function(e) { |
| 15469 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
| 15470 switch (e.command.id) { |
| 15471 case 'undo-command': |
| 15472 e.canExecute = this.$.toolbar.canUndo(); |
| 15473 break; |
| 15474 case 'clear-all-command': |
| 15475 e.canExecute = this.$.toolbar.canClearAll(); |
| 15476 break; |
| 15477 } |
| 15478 }, |
| 15479 |
| 15480 /** |
| 15481 * @param {Event} e |
| 15482 * @private |
| 15483 */ |
| 15484 onCommand_: function(e) { |
| 15485 if (e.command.id == 'clear-all-command') |
| 15486 this.actionService_.clearAll(); |
| 15487 else if (e.command.id == 'undo-command') |
| 15488 this.actionService_.undo(); |
| 15489 }, |
| 15490 |
| 15491 /** @private */ |
| 15492 onLoad_: function() { |
| 15493 this.$.toolbar.setActionService(this.actionService_); |
| 15494 |
| 15495 cr.ui.decorate('command', cr.ui.Command); |
| 15496 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
| 15497 document.addEventListener('command', this.onCommand_.bind(this)); |
| 15498 |
| 15499 // Shows all downloads. |
| 15500 this.actionService_.search(''); |
| 15501 }, |
| 15502 |
| 15503 /** @private */ |
| 15504 rebuildFocusGrid_: function() { |
| 15505 var activeElement = this.shadowRoot.activeElement; |
| 15506 |
| 15507 var activeItem; |
| 15508 if (activeElement && activeElement.tagName == 'downloads-item') |
| 15509 activeItem = activeElement; |
| 15510 |
| 15511 var activeControl = activeItem && activeItem.shadowRoot.activeElement; |
| 15512 |
| 15513 /** @private {!cr.ui.FocusGrid} */ |
| 15514 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid; |
| 15515 this.focusGrid_.destroy(); |
| 15516 |
| 15517 var boundary = this.$['downloads-list']; |
| 15518 |
| 15519 this.items_.forEach(function(item) { |
| 15520 var focusRow = new downloads.FocusRow(item.content, boundary); |
| 15521 this.focusGrid_.addRow(focusRow); |
| 15522 |
| 15523 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl)) |
| 15524 focusRow.getEquivalentElement(activeControl).focus(); |
| 15525 }, this); |
| 15526 |
| 15527 this.focusGrid_.ensureRowActive(); |
| 15528 }, |
| 15529 |
| 15530 /** |
| 15531 * @return {number} The number of downloads shown on the page. |
| 15532 * @private |
| 15533 */ |
| 15534 size_: function() { |
| 15535 return this.items_.length; |
| 15536 }, |
| 15537 |
| 15538 /** |
| 15539 * Called when all items need to be updated. |
| 15540 * @param {!Array<!downloads.Data>} list A list of new download data. |
| 15541 * @private |
| 15542 */ |
| 15543 updateAll_: function(list) { |
| 15544 var oldIdMap = this.idMap_ || {}; |
| 15545 |
| 15546 /** @private {!Object<!downloads.Item>} */ |
| 15547 this.idMap_ = {}; |
| 15548 |
| 15549 /** @private {!Array<!downloads.Item>} */ |
| 15550 this.items_ = []; |
| 15551 |
| 15552 if (!this.iconLoader_) { |
| 15553 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); |
| 15554 /** @private {downloads.ThrottledIconLoader} */ |
| 15555 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); |
| 15556 } |
| 15557 |
| 15558 for (var i = 0; i < list.length; ++i) { |
| 15559 var data = list[i]; |
| 15560 var id = data.id; |
| 15561 |
| 15562 // Re-use old items when possible (saves work, preserves focus). |
| 15563 var item = oldIdMap[id] || |
| 15564 new downloads.Item(this.iconLoader_, this.actionService_); |
| 15565 |
| 15566 this.idMap_[id] = item; // Associated by ID for fast lookup. |
| 15567 this.items_.push(item); // Add to sorted list for order. |
| 15568 |
| 15569 // Render |item| but don't actually add to the DOM yet. |this.items_| |
| 15570 // must be fully created to be able to find the right spot to insert. |
| 15571 item.update(data); |
| 15572 |
| 15573 // Collapse redundant dates. |
| 15574 var prev = list[i - 1]; |
| 15575 item.hideDate = !!prev && prev.date_string == data.date_string; |
| 15576 |
| 15577 delete oldIdMap[id]; |
| 15578 } |
| 15579 |
| 15580 // Remove stale, previously rendered items from the DOM. |
| 15581 for (var id in oldIdMap) { |
| 15582 if (oldIdMap[id].parentNode) |
| 15583 oldIdMap[id].parentNode.removeChild(oldIdMap[id]); |
| 15584 delete oldIdMap[id]; |
| 15585 } |
| 15586 |
| 15587 for (var i = 0; i < this.items_.length; ++i) { |
| 15588 var item = this.items_[i]; |
| 15589 if (item.parentNode) // Already in the DOM; skip. |
| 15590 continue; |
| 15591 |
| 15592 var before = null; |
| 15593 // Find the next rendered item after this one, and insert before it. |
| 15594 for (var j = i + 1; !before && j < this.items_.length; ++j) { |
| 15595 if (this.items_[j].parentNode) |
| 15596 before = this.items_[j]; |
| 15597 } |
| 15598 // If |before| is null, |item| will just get added at the end. |
| 15599 this.$['downloads-list'].insertBefore(item, before); |
| 15600 } |
| 15601 |
| 15602 var hasDownloads = this.size_() > 0; |
| 15603 if (!hasDownloads) { |
| 15604 var isSearching = this.actionService_.isSearching(); |
| 15605 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; |
| 15606 this.$['no-downloads'].querySelector('span').textContent = |
| 15607 loadTimeData.getString(messageToShow); |
| 15608 } |
| 15609 this.hasDownloads_ = hasDownloads; |
| 15610 |
| 15611 if (loadTimeData.getBoolean('allowDeletingHistory')) |
| 15612 this.$.toolbar.downloadsShowing = this.hasDownloads_; |
| 15613 |
| 15614 this.$.panel.classList.remove('loading'); |
| 15615 |
| 15616 var allReady = this.items_.map(function(i) { return i.readyPromise; }); |
| 15617 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this)); |
| 15618 }, |
| 15619 |
| 15620 /** |
| 15621 * @param {!downloads.Data} data |
| 15622 * @private |
| 15623 */ |
| 15624 updateItem_: function(data) { |
| 15625 var item = this.idMap_[data.id]; |
| 15626 |
| 15627 var activeControl = this.shadowRoot.activeElement == item ? |
| 15628 item.shadowRoot.activeElement : null; |
| 15629 |
| 15630 item.update(data); |
| 15631 |
| 15632 this.async(function() { |
| 15633 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) { |
| 15634 var focusRow = this.focusGrid_.getRowForRoot(item.content); |
| 15635 focusRow.getEquivalentElement(activeControl).focus(); |
| 15636 } |
| 15637 }.bind(this)); |
| 15638 }, |
| 15639 }); |
| 15640 |
| 15641 Manager.size = function() { |
| 15642 return document.querySelector('downloads-manager').size_(); |
| 15643 }; |
| 15644 |
| 15645 Manager.updateAll = function(list) { |
| 15646 document.querySelector('downloads-manager').updateAll_(list); |
| 15647 }; |
| 15648 |
| 15649 Manager.updateItem = function(item) { |
| 15650 document.querySelector('downloads-manager').updateItem_(item); |
| 15651 }; |
| 15652 |
| 15653 Manager.onLoad = function() { |
| 15654 document.querySelector('downloads-manager').onLoad_(); |
| 15655 }; |
| 15656 |
| 15657 return {Manager: Manager}; |
| 15658 }); |
| 15659 |
| 15660 window.addEventListener('load', downloads.Manager.onLoad); |
OLD | NEW |