Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(115)

Side by Side Diff: chrome/browser/resources/md_downloads/crisper.js

Issue 1378993003: Vulcanize new Material Design downloads UI (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@vulcanize2
Patch Set: -crisper.html Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/browser_resources.grd ('k') | chrome/browser/resources/md_downloads/item.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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, '&amp;')
1748 .replace(/</g, '&lt;')
1749 .replace(/>/g, '&gt;')
1750 .replace(/"/g, '&quot;')
1751 .replace(/'/g, '&#39;');
1752 }
1753
1754 /**
1755 * Shortens the provided string (if necessary) to a string of length at most
1756 * |maxLength|.
1757 * @param {string} original The original string.
1758 * @param {number} maxLength The maximum length allowed for the string.
1759 * @return {string} The original string if its length does not exceed
1760 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
1761 * appended.
1762 */
1763 function elide(original, maxLength) {
1764 if (original.length <= maxLength)
1765 return original;
1766 return original.substring(0, maxLength - 1) + '\u2026';
1767 };
1768 // Copyright 2015 The Chromium Authors. All rights reserved.
1769 // Use of this source code is governed by a BSD-style license that can be
1770 // found in the LICENSE file.
1771
1772 cr.define('downloads', function() {
1773 /**
1774 * @param {string} chromeSendName
1775 * @return {function(string):void} A chrome.send() callback with curried name.
1776 */
1777 function chromeSendWithId(chromeSendName) {
1778 return function(id) { chrome.send(chromeSendName, [id]); };
1779 }
1780
1781 /** @constructor */
1782 function ActionService() {}
1783
1784 ActionService.prototype = {
1785 /** @param {string} id ID of the download to cancel. */
1786 cancel: chromeSendWithId('cancel'),
1787
1788 /** Instructs the browser to clear all finished downloads. */
1789 clearAll: function() {
1790 if (loadTimeData.getBoolean('allowDeletingHistory')) {
1791 chrome.send('clearAll');
1792 this.search('');
1793 }
1794 },
1795
1796 /** @param {string} id ID of the dangerous download to discard. */
1797 discardDangerous: chromeSendWithId('discardDangerous'),
1798
1799 /** @param {string} url URL of a file to download. */
1800 download: function(url) {
1801 var a = document.createElement('a');
1802 a.href = url;
1803 a.setAttribute('download', '');
1804 a.click();
1805 },
1806
1807 /** @param {string} id ID of the download that the user started dragging. */
1808 drag: chromeSendWithId('drag'),
1809
1810 /**
1811 * @return {boolean} Whether the user is currently searching for downloads
1812 * (i.e. has a non-empty search term).
1813 */
1814 isSearching: function() {
1815 return this.searchText_.length > 0;
1816 },
1817
1818 /** Opens the current local destination for downloads. */
1819 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'),
1820
1821 /**
1822 * @param {string} id ID of the download to run locally on the user's box.
1823 */
1824 openFile: chromeSendWithId('openFile'),
1825
1826 /** @param {string} id ID the of the progressing download to pause. */
1827 pause: chromeSendWithId('pause'),
1828
1829 /** @param {string} id ID of the finished download to remove. */
1830 remove: chromeSendWithId('remove'),
1831
1832 /** @param {string} id ID of the paused download to resume. */
1833 resume: chromeSendWithId('resume'),
1834
1835 /**
1836 * @param {string} id ID of the dangerous download to save despite
1837 * warnings.
1838 */
1839 saveDangerous: chromeSendWithId('saveDangerous'),
1840
1841 /** @param {string} searchText What to search for. */
1842 search: function(searchText) {
1843 if (this.searchText_ == searchText)
1844 return;
1845
1846 this.searchText_ = searchText;
1847
1848 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
1849 function trim(s) { return s.trim(); }
1850 chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim));
1851 },
1852
1853 /**
1854 * Shows the local folder a finished download resides in.
1855 * @param {string} id ID of the download to show.
1856 */
1857 show: chromeSendWithId('show'),
1858
1859 /** Undo download removal. */
1860 undo: chrome.send.bind(chrome, 'undo'),
1861 };
1862
1863 cr.addSingletonGetter(ActionService);
1864
1865 return {ActionService: ActionService};
1866 });
1867 // Copyright 2015 The Chromium Authors. All rights reserved.
1868 // Use of this source code is governed by a BSD-style license that can be
1869 // found in the LICENSE file.
1870
1871 cr.define('downloads', function() {
1872 /**
1873 * Explains why a download is in DANGEROUS state.
1874 * @enum {string}
1875 */
1876 var DangerType = {
1877 NOT_DANGEROUS: 'NOT_DANGEROUS',
1878 DANGEROUS_FILE: 'DANGEROUS_FILE',
1879 DANGEROUS_URL: 'DANGEROUS_URL',
1880 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
1881 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
1882 DANGEROUS_HOST: 'DANGEROUS_HOST',
1883 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
1884 };
1885
1886 /**
1887 * The states a download can be in. These correspond to states defined in
1888 * DownloadsDOMHandler::CreateDownloadItemValue
1889 * @enum {string}
1890 */
1891 var States = {
1892 IN_PROGRESS: 'IN_PROGRESS',
1893 CANCELLED: 'CANCELLED',
1894 COMPLETE: 'COMPLETE',
1895 PAUSED: 'PAUSED',
1896 DANGEROUS: 'DANGEROUS',
1897 INTERRUPTED: 'INTERRUPTED',
1898 };
1899
1900 return {
1901 DangerType: DangerType,
1902 States: States,
1903 };
1904 });
1905 // Copyright 2014 The Chromium Authors. All rights reserved.
1906 // Use of this source code is governed by a BSD-style license that can be
1907 // found in the LICENSE file.
1908
1909 cr.define('cr.ui', function() {
1910 /**
1911 * A class to manage focus between given horizontally arranged elements.
1912 *
1913 * Pressing left cycles backward and pressing right cycles forward in item
1914 * order. Pressing Home goes to the beginning of the list and End goes to the
1915 * end of the list.
1916 *
1917 * If an item in this row is focused, it'll stay active (accessible via tab).
1918 * If no items in this row are focused, the row can stay active until focus
1919 * changes to a node inside |this.boundary_|. If |boundary| isn't specified,
1920 * any focus change deactivates the row.
1921 *
1922 * @param {!Element} root The root of this focus row. Focus classes are
1923 * applied to |root| and all added elements must live within |root|.
1924 * @param {?Node} boundary Focus events are ignored outside of this node.
1925 * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate.
1926 * @constructor
1927 */
1928 function FocusRow(root, boundary, opt_delegate) {
1929 /** @type {!Element} */
1930 this.root = root;
1931
1932 /** @private {!Node} */
1933 this.boundary_ = boundary || document;
1934
1935 /** @type {cr.ui.FocusRow.Delegate|undefined} */
1936 this.delegate = opt_delegate;
1937
1938 /** @protected {!EventTracker} */
1939 this.eventTracker = new EventTracker;
1940 }
1941
1942 /** @interface */
1943 FocusRow.Delegate = function() {};
1944
1945 FocusRow.Delegate.prototype = {
1946 /**
1947 * Called when a key is pressed while on a FocusRow's item. If true is
1948 * returned, further processing is skipped.
1949 * @param {!cr.ui.FocusRow} row The row that detected a keydown.
1950 * @param {!Event} e
1951 * @return {boolean} Whether the event was handled.
1952 */
1953 onKeydown: assertNotReached,
1954
1955 /**
1956 * @param {!cr.ui.FocusRow} row
1957 * @param {!Event} e
1958 */
1959 onFocus: assertNotReached,
1960 };
1961
1962 /** @const {string} */
1963 FocusRow.ACTIVE_CLASS = 'focus-row-active';
1964
1965 /**
1966 * Whether it's possible that |element| can be focused.
1967 * @param {Element} element
1968 * @return {boolean} Whether the item is focusable.
1969 */
1970 FocusRow.isFocusable = function(element) {
1971 if (!element || element.disabled)
1972 return false;
1973
1974 // We don't check that element.tabIndex >= 0 here because inactive rows set
1975 // a tabIndex of -1.
1976
1977 function isVisible(element) {
1978 assertInstanceof(element, Element);
1979
1980 var style = window.getComputedStyle(element);
1981 if (style.visibility == 'hidden' || style.display == 'none')
1982 return false;
1983
1984 var parent = element.parentNode;
1985 if (!parent)
1986 return false;
1987
1988 if (parent == element.ownerDocument || parent instanceof DocumentFragment)
1989 return true;
1990
1991 return isVisible(parent);
1992 }
1993
1994 return isVisible(element);
1995 };
1996
1997 FocusRow.prototype = {
1998 /**
1999 * Register a new type of focusable element (or add to an existing one).
2000 *
2001 * Example: an (X) button might be 'delete' or 'close'.
2002 *
2003 * When FocusRow is used within a FocusGrid, these types are used to
2004 * determine equivalent controls when Up/Down are pressed to change rows.
2005 *
2006 * Another example: mutually exclusive controls that hide eachother on
2007 * activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause')
2008 * to indicate they're equivalent.
2009 *
2010 * @param {string} type The type of element to track focus of.
2011 * @param {string} query The selector of the element from this row's root.
2012 * @return {boolean} Whether a new item was added.
2013 */
2014 addItem: function(type, query) {
2015 assert(type);
2016
2017 var element = this.root.querySelector(query);
2018 if (!element)
2019 return false;
2020
2021 element.setAttribute('focus-type', type);
2022 element.tabIndex = this.isActive() ? 0 : -1;
2023
2024 this.eventTracker.add(element, 'blur', this.onBlur_.bind(this));
2025 this.eventTracker.add(element, 'focus', this.onFocus_.bind(this));
2026 this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this));
2027 this.eventTracker.add(element, 'mousedown',
2028 this.onMousedown_.bind(this));
2029 return true;
2030 },
2031
2032 /** Dereferences nodes and removes event handlers. */
2033 destroy: function() {
2034 this.eventTracker.removeAll();
2035 },
2036
2037 /**
2038 * @param {Element} sampleElement An element for to find an equivalent for.
2039 * @return {!Element} An equivalent element to focus for |sampleElement|.
2040 * @protected
2041 */
2042 getCustomEquivalent: function(sampleElement) {
2043 return assert(this.getFirstFocusable());
2044 },
2045
2046 /**
2047 * @return {!Array<!Element>} All registered elements (regardless of
2048 * focusability).
2049 */
2050 getElements: function() {
2051 var elements = this.root.querySelectorAll('[focus-type]');
2052 return Array.prototype.slice.call(elements);
2053 },
2054
2055 /**
2056 * Find the element that best matches |sampleElement|.
2057 * @param {!Element} sampleElement An element from a row of the same type
2058 * which previously held focus.
2059 * @return {!Element} The element that best matches sampleElement.
2060 */
2061 getEquivalentElement: function(sampleElement) {
2062 if (this.getFocusableElements().indexOf(sampleElement) >= 0)
2063 return sampleElement;
2064
2065 var sampleFocusType = this.getTypeForElement(sampleElement);
2066 if (sampleFocusType) {
2067 var sameType = this.getFirstFocusable(sampleFocusType);
2068 if (sameType)
2069 return sameType;
2070 }
2071
2072 return this.getCustomEquivalent(sampleElement);
2073 },
2074
2075 /**
2076 * @param {string=} opt_type An optional type to search for.
2077 * @return {?Element} The first focusable element with |type|.
2078 */
2079 getFirstFocusable: function(opt_type) {
2080 var filter = opt_type ? '="' + opt_type + '"' : '';
2081 var elements = this.root.querySelectorAll('[focus-type' + filter + ']');
2082 for (var i = 0; i < elements.length; ++i) {
2083 if (cr.ui.FocusRow.isFocusable(elements[i]))
2084 return elements[i];
2085 }
2086 return null;
2087 },
2088
2089 /** @return {!Array<!Element>} Registered, focusable elements. */
2090 getFocusableElements: function() {
2091 return this.getElements().filter(cr.ui.FocusRow.isFocusable);
2092 },
2093
2094 /**
2095 * @param {!Element} element An element to determine a focus type for.
2096 * @return {string} The focus type for |element| or '' if none.
2097 */
2098 getTypeForElement: function(element) {
2099 return element.getAttribute('focus-type') || '';
2100 },
2101
2102 /** @return {boolean} Whether this row is currently active. */
2103 isActive: function() {
2104 return this.root.classList.contains(FocusRow.ACTIVE_CLASS);
2105 },
2106
2107 /**
2108 * Enables/disables the tabIndex of the focusable elements in the FocusRow.
2109 * tabIndex can be set properly.
2110 * @param {boolean} active True if tab is allowed for this row.
2111 */
2112 makeActive: function(active) {
2113 if (active == this.isActive())
2114 return;
2115
2116 this.getElements().forEach(function(element) {
2117 element.tabIndex = active ? 0 : -1;
2118 });
2119
2120 this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active);
2121 },
2122
2123 /**
2124 * @param {!Event} e
2125 * @private
2126 */
2127 onBlur_: function(e) {
2128 if (!this.boundary_.contains(/** @type {Node} */(e.relatedTarget)))
2129 return;
2130
2131 if (this.getFocusableElements().indexOf(e.currentTarget) >= 0)
2132 this.makeActive(false);
2133 },
2134
2135 /**
2136 * @param {!Event} e
2137 * @private
2138 */
2139 onFocus_: function(e) {
2140 if (this.delegate)
2141 this.delegate.onFocus(this, e);
2142 },
2143
2144 /**
2145 * @param {!Event} e A mousedown event.
2146 * @private
2147 */
2148 onMousedown_: function(e) {
2149 // Only accept left mouse clicks.
2150 if (e.button)
2151 return;
2152
2153 // Allow the element under the mouse cursor to be focusable.
2154 if (!e.currentTarget.disabled)
2155 e.currentTarget.tabIndex = 0;
2156 },
2157
2158 /**
2159 * @param {Event} e The keydown event.
2160 * @private
2161 */
2162 onKeydown_: function(e) {
2163 var elements = this.getFocusableElements();
2164 var elementIndex = elements.indexOf(e.currentTarget);
2165 assert(elementIndex >= 0);
2166
2167 if (this.delegate && this.delegate.onKeydown(this, e))
2168 return;
2169
2170 if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
2171 return;
2172
2173 var index = -1;
2174
2175 if (e.keyIdentifier == 'Left')
2176 index = elementIndex + (isRTL() ? 1 : -1);
2177 else if (e.keyIdentifier == 'Right')
2178 index = elementIndex + (isRTL() ? -1 : 1);
2179 else if (e.keyIdentifier == 'Home')
2180 index = 0;
2181 else if (e.keyIdentifier == 'End')
2182 index = elements.length - 1;
2183
2184 var elementToFocus = elements[index];
2185 if (elementToFocus) {
2186 this.getEquivalentElement(elementToFocus).focus();
2187 e.preventDefault();
2188 }
2189 },
2190 };
2191
2192 return {
2193 FocusRow: FocusRow,
2194 };
2195 });
2196 // Copyright 2015 The Chromium Authors. All rights reserved.
2197 // Use of this source code is governed by a BSD-style license that can be
2198 // found in the LICENSE file.
2199
2200 cr.define('downloads', function() {
2201 /**
2202 * @param {!Element} root
2203 * @param {?Node} boundary
2204 * @constructor
2205 * @extends {cr.ui.FocusRow}
2206 */
2207 function FocusRow(root, boundary) {
2208 cr.ui.FocusRow.call(this, root, boundary);
2209 this.addItems();
2210 }
2211
2212 FocusRow.prototype = {
2213 __proto__: cr.ui.FocusRow.prototype,
2214
2215 addItems: function() {
2216 this.destroy();
2217
2218 this.addItem('name-file-link',
2219 'content.is-active:not(.show-progress):not(.dangerous) #name');
2220 assert(this.addItem('name-file-link', '#file-link'));
2221 assert(this.addItem('url', '#url'));
2222 this.addItem('show-retry', '#show');
2223 this.addItem('show-retry', '#retry');
2224 this.addItem('pause-resume', '#pause');
2225 this.addItem('pause-resume', '#resume');
2226 this.addItem('cancel', '#cancel');
2227 this.addItem('controlled-by', '#controlled-by a');
2228 this.addItem('danger-remove-discard', '#discard');
2229 this.addItem('restore-save', '#save');
2230 this.addItem('danger-remove-discard', '#danger-remove');
2231 this.addItem('restore-save', '#restore');
2232 assert(this.addItem('remove', '#remove'));
2233
2234 // TODO(dbeam): it would be nice to do this asynchronously (so if multiple
2235 // templates get rendered we only re-add once), but Manager#updateItem_()
2236 // relies on the DOM being re-rendered synchronously.
2237 this.eventTracker.add(this.root, 'dom-change', this.addItems.bind(this));
2238 },
2239 };
2240
2241 return {FocusRow: FocusRow};
2242 });
2243 // Copyright 2014 The Chromium Authors. All rights reserved.
2244 // Use of this source code is governed by a BSD-style license that can be
2245 // found in the LICENSE file.
2246
2247 // Action links are elements that are used to perform an in-page navigation or
2248 // action (e.g. showing a dialog).
2249 //
2250 // They look like normal anchor (<a>) tags as their text color is blue. However,
2251 // they're subtly different as they're not initially underlined (giving users a
2252 // clue that underlined links navigate while action links don't).
2253 //
2254 // Action links look very similar to normal links when hovered (hand cursor,
2255 // underlined). This gives the user an idea that clicking this link will do
2256 // something similar to navigation but in the same page.
2257 //
2258 // They can be created in JavaScript like this:
2259 //
2260 // var link = document.createElement('a', 'action-link'); // Note second arg.
2261 //
2262 // or with a constructor like this:
2263 //
2264 // var link = new ActionLink();
2265 //
2266 // They can be used easily from HTML as well, like so:
2267 //
2268 // <a is="action-link">Click me!</a>
2269 //
2270 // NOTE: <action-link> and document.createElement('action-link') don't work.
2271
2272 /**
2273 * @constructor
2274 * @extends {HTMLAnchorElement}
2275 */
2276 var ActionLink = document.registerElement('action-link', {
2277 prototype: {
2278 __proto__: HTMLAnchorElement.prototype,
2279
2280 /** @this {ActionLink} */
2281 createdCallback: function() {
2282 // Action links can start disabled (e.g. <a is="action-link" disabled>).
2283 this.tabIndex = this.disabled ? -1 : 0;
2284
2285 if (!this.hasAttribute('role'))
2286 this.setAttribute('role', 'link');
2287
2288 this.addEventListener('keydown', function(e) {
2289 if (!this.disabled && e.keyIdentifier == 'Enter') {
2290 // Schedule a click asynchronously because other 'keydown' handlers
2291 // may still run later (e.g. document.addEventListener('keydown')).
2292 // Specifically options dialogs break when this timeout isn't here.
2293 // NOTE: this affects the "trusted" state of the ensuing click. I
2294 // haven't found anything that breaks because of this (yet).
2295 window.setTimeout(this.click.bind(this), 0);
2296 }
2297 });
2298
2299 function preventDefault(e) {
2300 e.preventDefault();
2301 }
2302
2303 function removePreventDefault() {
2304 document.removeEventListener('selectstart', preventDefault);
2305 document.removeEventListener('mouseup', removePreventDefault);
2306 }
2307
2308 this.addEventListener('mousedown', function() {
2309 // This handlers strives to match the behavior of <a href="...">.
2310
2311 // While the mouse is down, prevent text selection from dragging.
2312 document.addEventListener('selectstart', preventDefault);
2313 document.addEventListener('mouseup', removePreventDefault);
2314
2315 // If focus started via mouse press, don't show an outline.
2316 if (document.activeElement != this)
2317 this.classList.add('no-outline');
2318 });
2319
2320 this.addEventListener('blur', function() {
2321 this.classList.remove('no-outline');
2322 });
2323 },
2324
2325 /** @type {boolean} */
2326 set disabled(disabled) {
2327 if (disabled)
2328 HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', '');
2329 else
2330 HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled');
2331 this.tabIndex = disabled ? -1 : 0;
2332 },
2333 get disabled() {
2334 return this.hasAttribute('disabled');
2335 },
2336
2337 /** @override */
2338 setAttribute: function(attr, val) {
2339 if (attr.toLowerCase() == 'disabled')
2340 this.disabled = true;
2341 else
2342 HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
2343 },
2344
2345 /** @override */
2346 removeAttribute: function(attr) {
2347 if (attr.toLowerCase() == 'disabled')
2348 this.disabled = false;
2349 else
2350 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
2351 },
2352 },
2353
2354 extends: 'a',
2355 });
2356 // Copyright 2015 The Chromium Authors. All rights reserved.
2357 // Use of this source code is governed by a BSD-style license that can be
2358 // found in the LICENSE file.
2359
2360 /** @typedef {{img: HTMLImageElement, url: string}} */
2361 var LoadIconRequest;
2362
2363 cr.define('downloads', function() {
2364 /**
2365 * @param {number} maxAllowed The maximum number of simultaneous downloads
2366 * allowed.
2367 * @constructor
2368 */
2369 function ThrottledIconLoader(maxAllowed) {
2370 assert(maxAllowed > 0);
2371
2372 /** @private {number} */
2373 this.maxAllowed_ = maxAllowed;
2374
2375 /** @private {!Array<!LoadIconRequest>} */
2376 this.requests_ = [];
2377 }
2378
2379 ThrottledIconLoader.prototype = {
2380 /** @private {number} */
2381 loading_: 0,
2382
2383 /**
2384 * Load the provided |url| into |img.src| after appending ?scale=.
2385 * @param {!HTMLImageElement} img An <img> to show the loaded image in.
2386 * @param {string} url A remote image URL to load.
2387 */
2388 loadScaledIcon: function(img, url) {
2389 var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
2390 if (img.src == scaledUrl)
2391 return;
2392
2393 this.requests_.push({img: img, url: scaledUrl});
2394 this.loadNextIcon_();
2395 },
2396
2397 /** @private */
2398 loadNextIcon_: function() {
2399 if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
2400 return;
2401
2402 var request = this.requests_.shift();
2403 var img = request.img;
2404
2405 img.onabort = img.onerror = img.onload = function() {
2406 this.loading_--;
2407 this.loadNextIcon_();
2408 }.bind(this);
2409
2410 this.loading_++;
2411 img.src = request.url;
2412 },
2413 };
2414
2415 return {ThrottledIconLoader: ThrottledIconLoader};
2416 });
2417 // Copyright 2014 Google Inc. All rights reserved.
2418 //
2419 // Licensed under the Apache License, Version 2.0 (the "License");
2420 // you may not use this file except in compliance with the License.
2421 // You may obtain a copy of the License at
2422 //
2423 // http://www.apache.org/licenses/LICENSE-2.0
2424 //
2425 // Unless required by applicable law or agreed to in writing, software
2426 // distributed under the License is distributed on an "AS IS" BASIS,
2427 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2428 // See the License for the specific language governing permissions and
2429 // limitations under the License.
2430
2431 !function(a,b){b["true"]=a;var c={},d={},e={},f=null;!function(a){function b(a){ if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}functi on c(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,t his._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal" ,this._easing="linear"}function d(b,d){var e=new c;return d&&(e.fill="both",e.du ration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNam es(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof e[c]||"duration" ==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==s.indexOf(b[ c]))return;if("direction"==c&&-1==t.indexOf(b[c]))return;if("playbackRate"==c&&1 !==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use A nimation.playbackRate instead."))return;e[c]=b[c]}}):e.duration=b,e}function e(a ){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function f( b,c){b=a.numericTimingToObject(b);var e=d(b,c);return e._easing=i(e.easing),e}fu nction g(a,b,c,d){return 0>a||a>1||0>c||c>1?B:function(e){function f(a,b,c){retu rn 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(0==e||1==e)return e;for(var g=0,h=1; ;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}} function h(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c% d}}function i(a){var b=z.exec(a);if(b)return g.apply(this,b.slice(1).map(Number) );var c=A.exec(a);if(c)return h(Number(c[1]),{start:u,middle:v,end:w}[c[2]]);var d=x[a];return d?d:B}function j(a){return Math.abs(k(a)/a.playbackRate)}function k(a){return a.duration*a.iterations}function l(a,b,c){return null==b?C:b<c.dela y?D:b>=c.delay+a?E:F}function m(a,b,c,d,e){switch(d){case D:return"backwards"==b ||"both"==b?0:null;case F:return c-e;case E:return"forwards"==b||"both"==b?a:nul l;case C:return null}}function n(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playb ackRate+c}function o(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&& (e.iterations+e.iterationStart)%1==0?a:c%a}function p(a,b,c,d){return 0===c?0:b= =a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function q(a,b,c,d){var e=a%2 >=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g= f?c:b-c,h=g/b;return b*d.easing(h)}function r(a,b,c){var d=l(a,b,c),e=m(a,c.fill ,b,d,c.delay);if(null===e)return null;if(0===a)return d===D?0:1;var f=c.iteratio nStart*c.duration,g=n(a,e,f,c),h=o(c.duration,k(c),g,f,c),i=p(c.duration,h,g,c); return q(i,c.duration,h,c)/c.duration}var s="backwards|forwards|both|none".split ("|"),t="reverse|alternate|alternate-reverse".split("|");c.prototype={_setMember :function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this. _effect._timing=a.normalizeTimingInput(a.normalizeTimingInput(this._effect._timi ngInput)),this._effect.activeDuration=a.calculateActiveDuration(this._effect._ti ming),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimati on())},get playbackRate(){return this._playbackRate},set delay(a){this._setMembe r("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("e ndDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember(" fill",a)},get fill(){return this._fill},set iterationStart(a){this._setMember("i terationStart",a)},get iterationStart(){return this._iterationStart},set duratio n(a){this._setMember("duration",a)},get duration(){return this._duration},set di rection(a){this._setMember("direction",a)},get direction(){return this._directio n},set easing(a){this._setMember("easing",a)},get easing(){return this._easing}, set iterations(a){this._setMember("iterations",a)},get iterations(){return this. _iterations}};var u=1,v=.5,w=0,x={ease:g(.25,.1,.25,1),"ease-in":g(.42,0,1,1),"e ase-out":g(0,0,.58,1),"ease-in-out":g(.42,0,.58,1),"step-start":h(1,u),"step-mid dle":h(1,v),"step-end":h(1,w)},y="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",z=new RegE xp("cubic-bezier\\("+y+","+y+","+y+","+y+"\\)"),A=/steps\(\s*(\d+)\s*,\s*(start| middle|end)\s*\)/,B=function(a){return a},C=0,D=1,E=2,F=3;a.cloneTimingInput=b,a .makeTiming=d,a.numericTimingToObject=e,a.normalizeTimingInput=f,a.calculateActi veDuration=j,a.calculateTimeFraction=r,a.calculatePhase=l,a.toTimingFunction=i}( c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){va r g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)} }else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset& &(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].of fset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].off set=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError ("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null! =f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be num bers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"N otSupportedError",message:"add compositing is not supported"};f="easing"==e?a.to TimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0= =d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;h<e.lengt h;h++){var i=e[h].offset;if(null!=i){if(g>i)throw{code:DOMException.INVALID_MODI FICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely sorted by offset. Sort or specify offsets."};g=i}else f=!1}return e=e.filter(fun ction(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={background:["backgrou ndImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAtt achment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderT opColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle" ,"borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth", "borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBott omWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor", "borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLef tWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius" ,"borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],bord erRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["b orderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth", "borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flex Shrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fo ntWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","margin Left"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingT op","paddingRight","paddingBottom","paddingLeft"]},f=document.createElementNS("h ttp://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={b orderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,fontSiz e:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-la rge":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth :g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.normalizeKeyframes=d}(c,f),function(a){var b={};a.isDeprecated= function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth( h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecate d and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecat ed=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Err or(b+" "+f+" no longer supported. "+d)}}(c),function(){if(document.documentEleme nt.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play| currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split( "|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}!function(a,b){funct ion c(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easi ng"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][ d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!= g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupporte dError",message:"Partial keyframes are not supported"}}return b}function d(a){va r c=[];for(var d in a)for(var e=a[d],f=0;f<e.length-1;f++){var g=e[f].offset,h=e [f+1].offset,i=e[f].value,j=e[f+1].value;g==h&&(1==h?i=j:j=i),c.push({startTime: g,endTime:h,easing:e[f].easing,property:d,interpolation:b.propertyInterpolation( d,i,j)})}return c.sort(function(a,b){return a.startTime-b.startTime}),c}b.conver tEffectInput=function(e){var f=a.normalizeKeyframes(e),g=c(f),h=d(g);return func tion(a,c){if(null!=c)h.filter(function(a){return 0>=c&&0==a.startTime||c>=1&&1== a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTim e,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpol ation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clea r(a,d)}}}(c,d,f),function(a){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}fu nction c(a,c,d){for(var e=0;e<d.length;e++){var f=d[e];b(a,c,f),/-/.test(f)&&b(a ,c,f.replace(/-(.)/g,function(a,b){return b.toUpperCase()}))}}function d(b,c,d){ if("initial"==c||"initial"==d){var g=b.replace(/-(.)/g,function(a,b){return b.to UpperCase()});"initial"==c&&(c=f[g]),"initial"==d&&(d=f[g])}for(var h=c==d?[]:e[ b],i=0;h&&i<h.length;i++){var j=h[i][0](c),k=h[i][0](d);if(void 0!==j&&void 0!== k){var l=h[i][1](j,k);if(l){var m=a.Interpolation.apply(null,l);return function( a){return 0==a?c:1==a?d:m(a)}}}}return a.Interpolation(!1,!0,function(a){return a?d:c})}var e={};a.addPropertiesHandler=c;var f={backgroundColor:"transparent",b ackgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadiu s:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"c urrentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWi dth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius: "0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0 px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto" ,left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginL eft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",min Height:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0 px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px", paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transpare nt",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto ",wordSpacing:"normal",zIndex:"auto"};a.propertyInterpolation=d}(d,f),function(a ,b){function c(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calc ulateTimeFraction(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d._isCurr ent=function(d){var e=a.calculatePhase(c,d,b);return e===PhaseActive||e===PhaseB efore},d}b.KeyframeEffect=function(d,e,f){var g,h=c(a.normalizeTimingInput(f)),i =b.convertEffectInput(e),j=function(){i(d,g)};return j._update=function(a){retur n g=h(a),null!==g},j._clear=function(){i(d,null)},j._hasSameTarget=function(a){r eturn d===a},j._isCurrent=h._isCurrent,j._totalDuration=h._totalDuration,j},b.Nu llEffect=function(a){var b=function(){a&&(a(),a=null)};return b._update=function (){return null},b._totalDuration=0,b._isCurrent=function(){return!1},b._hasSameT arget=function(){return!1},b}}(c,d,f),function(a){a.apply=function(b,c,d){b.styl e[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(d, f),function(a){window.Element.prototype.animate=function(b,c){return a.timeline. _play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number" ==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boole an"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;f<a.length; f++)e.push(b(a[f],c[f],d));return e}throw"Mismatched interpolation arguments "+a +":"+c}a.Interpolation=function(a,c,d){return function(e){return d(b(a,c,e))}}}( d,f),function(a,b){a.sequenceNumber=0;var c=function(a,b,c){this.target=a,this.c urrentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancela ble=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TA RGET,this.timeStamp=Date.now()};b.Animation=function(b){this._sequenceNumber=a.s equenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._p laybackRate=1,this._inTimeline=!0,this._finishedFlag=!1,this.onfinish=null,this. _finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._i dle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function (){this._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime ?-1:this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(th is._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function( a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._cur rentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get c urrentTime(){return this._idle||this._currentTimePending?null:this._currentTime} ,set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startT ime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._cur rentTimePending=!1,this._currentTime!=a&&(this._tickCurrentTime(a,!0),b.invalida teEffects()))},get startTime(){return this._startTime},set startTime(a){a=+a,isN aN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this. _timeline.currentTime-this._startTime)*this.playbackRate),b.invalidateEffects()) },get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._ playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null," paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.curr entTime=b)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._c urrentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},ge t _totalDuration(){return this._effect._totalDuration},get playState(){return th is._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this ._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished" :"running"},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(thi s._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null,b .invalidateEffects()),this._finishedFlag=!1,b.restart(),this._idle=!1,this._ensu reAlive()},pause:function(){this._isFinished||this._paused||this._idle||(this._c urrentTimePending=!0),this._startTime=null,this._paused=!0},finish:function(){th is._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._sta rtTime=this._totalDuration-this.currentTime,this._currentTimePending=!1)},cancel :function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this.currentTime=0, this._startTime=null,this._effect._update(null),b.invalidateEffects(),b.restart( ))},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:funct ion(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},remove EventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b );c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){var b=this._i sFinished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._curren tTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTime out(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag =b},_tick:function(a){return this._idle||this._paused||(null==this._startTime?th is.startTime=a-this._currentTime/this.playbackRate:this._isFinished||this._tickC urrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,t his._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(c,d,f), function(a,b){function c(a){var b=i;i=[],a<s.currentTime&&(a=s.currentTime),g(a) ,b.forEach(function(b){b[1](a)}),o&&g(a),f(),l=void 0}function d(a,b){return a._ sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTi me=window.performance&&performance.now?performance.now():0}function f(){p.forEac h(function(a){a()}),p.length=0}function g(a){n=!1;var c=b.timeline;c.currentTime =a,c._animations.sort(d),m=!1;var e=c._animations;c._animations=[];var f=[],g=[] ;e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._ef fect):f.push(b._effect),b._isFinished||b._paused||b._idle||(m=!0),b._inTimeline} ),p.push.apply(p,f),p.push.apply(p,g),c._animations.push.apply(c._animations,e), o=!1,m&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i =[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h (c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function( b){b[0]==a&&(b[1]=function(){})})},e.prototype={_play:function(c){c._timing=a.no rmalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timel ine=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var k,l= void 0,k=function(){return void 0==l&&(l=performance.now()),l},m=!1,n=!1;b.resta rt=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n};var o =!1;b.invalidateEffects=function(){o=!0};var p=[],q=1e3/60,r=window.getComputedS tyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable :!0,value:function(){if(o){var a=k();a-s.currentTime>0&&(s.currentTime+=q*(Math. floor((a-s.currentTime)/q)+1)),g(s.currentTime)}return f(),r.apply(this,argument s)}});var s=new e;b.timeline=s}(c,d,f),function(a){function b(a,b){var c=a.exec( b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void 0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace (/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e );if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g [1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++) if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,0>=c))break;var e=a(b.substr( 0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c& &d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b) ;return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return d?d:[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrim med(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}return""== c?d:void 0}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k =0;j>k;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.pus h(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b ](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h= 0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0] ),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(functi on(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+ =f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a .consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRep eated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(d),functio n(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);if(c)return d .inset=!0,c;var c=a.consumeLengthOrPercent(b);if(c)return d.lengths.push(c[0]),c ;var c=a.consumeColor(b);return c?(d.color=c[0],c):void 0}var d={inset:!1,length s:[],color:null},e=a.consumeRepeated(c,/^/,b);return e&&e[0].length?[d,e[1]]:voi d 0}function c(c){var d=a.consumeRepeated(b,/^,/,c);return d&&""==d[1]?d[0]:void 0}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.len gth);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.l engths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color ){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDime nsions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[ 2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1 ],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f+ +)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(v ar g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f (d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.b ind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(d),fu nction(a){function b(a){return a.toFixed(3).replace(".000","")}function c(a,b,c) {return Math.min(b,Math.max(a,c))}function d(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/ .test(a)?Number(a):void 0}function e(a,c){return[a,c,b]}function f(a,b){return 0 !=a?h(0,1/0)(a,b):void 0}function g(a,b){return[a,b,function(a){return Math.roun d(c(1,1/0,a))}]}function h(a,d){return function(e,f){return[e,f,function(e){retu rn b(c(a,d,e))}]}}function i(a,b){return[a,b,Math.round]}a.clamp=c,a.addProperti esHandler(d,h(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandle r(d,h(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(d,f,["fle x-grow","flex-shrink"]),a.addPropertiesHandler(d,g,["orphans","widows"]),a.addPr opertiesHandler(d,i,["z-index"]),a.parseNumber=d,a.mergeNumbers=e,a.numberToStri ng=b}(d,f),function(a){function b(a,b){return"visible"==a||"visible"==b?[0,1,fun ction(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b ,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.f illStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle) {e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(b,c){return[b,c,function( b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++) b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"r gba("+b.join(",")+")"}]}var d=document.createElementNS("http://www.w3.org/1999/x html","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandl er(b,c,["background-color","border-bottom-color","border-left-color","border-rig ht-color","border-top-color","color","outline-color","text-decoration-color"]),a .consumeColor=a.consumeParenthesised.bind(null,b),a.mergeColors=c}(d,f),function (a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)retu rn{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.rep lace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.repl ace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\ s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*/]N/g,/(N|D)O\1/g,/\((N|D)\)/g], g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g" ),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(a,b){return e(a,b,! 0)}function e(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&& f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c= Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+ c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,n ew RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|g rad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a. consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.con sumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consum eRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l( a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o= a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPrope rtiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom -width","border-image-width","border-left-width","border-right-width","border-to p-width","flex-basis","font-size","height","line-height","max-height","max-width ","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radi us","border-bottom-right-radius","border-top-left-radius","border-top-right-radi us","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right ","margin-top","min-height","min-width","outline-offset","padding-bottom","paddi ng-left","padding-right","padding-top","perspective","right","shape-margin","tex t-indent","top","vertical-align","word-spacing"])}(d,f),function(a){function b(b ){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){va r d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consum eToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeT oken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(b,c){ return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"au to";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}func tion e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d," , ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"])}(d,f),func tion(a){function b(a){return function(b){var c=0;return a.map(function(a){return a===j?b[c++]:a})}}function c(a){return a}function d(b){if(b=b.toLowerCase().tri m(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);) {if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=m[g];if(!h)return;var i =c[2].split(","),j=h[0];if(j.length<i.length)return;for(var n=[],o=0;o<j.length; o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?l:a.parseAngl e(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()] (q):{a:l,n:n[0],t:k}[r],void 0===p)return;n.push(p)}if(e.push({t:g,d:n}),d.lastI ndex==b.length)return e}}function e(a){return a.toFixed(6).replace(".000000","") }function f(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeM atrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var f=a. makeMatrixDecomposition(c)}return null==d[0]||null==f[0]?[[!1],[!0],function(a){ return a?c[0].d:b[0].d}]:(d[0].push(0),f[0].push(1),[d,f,function(b){var c=a.qua t(d[0][3],f[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(e).join (",");return h}])}function g(a){return a.replace(/[xy]/,"")}function h(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function i(b,c){var d=a.makeMatrixDecomposition& &!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var i=0;i<b.leng th;i++){var j=b[i].t,k=b[i].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(f unction(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return b})})}}var n=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"== a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.l ength){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else fo r(var i=0;i<b.length;i++){var j,s=b[i].t,t=c[i].t,u=b[i].d,v=c[i].d,w=m[s],x=m[t ];if(n(s,t)){if(!d)return;var r=f([b[i]],[c[i]]);o.push(r[0]),p.push(r[1]),q.pus h(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&g(s)==g(t))j=g(s),u=w[ 2](u),v=x[2](v);else{if(!w[1]||!x[1]||h(s)!=h(t)){if(!d)return;var r=f(b,c);o=[r [0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=h(s),u=w[1](u),v=x[1](v)}for(var y=[ ],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mer geDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z) ,q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(functi on(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix "==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).jo in(" ")}]}var j=null,k={px:0},l={deg:0},m={matrix:["NNNNNN",[j,j,0,0,j,j,0,0,0,0 ,1,0,j,j,0,1],c],matrix3d:["NNNNNNNNNNNNNNNN",c],rotate:["A"],rotatex:["A"],rota tey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",b([j,j,1 ]),c],scalex:["N",b([j,1,1]),b([j,1])],scaley:["N",b([1,j,1]),b([1,j])],scalez:[ "N",b([1,1,j])],scale3d:["NNN",c],skew:["Aa",null,c],skewx:["A",null,b([j,l])],s kewy:["A",null,b([l,j])],translate:["Tt",b([j,j,k]),c],translatex:["T",b([j,k,k] ),b([j,k])],translatey:["T",b([k,j,k]),b([k,j])],translatez:["L",b([k,k,j])],tra nslate3d:["TTL",c]};a.addPropertiesHandler(d,i,["transform"])}(d,f),function(a){ function b(a,b){b.concat([a]).forEach(function(b){b in document.documentElement. style&&(c[a]=b)})}var c={};b("transform",["webkitTransform","msTransform"]),b("t ransformOrigin",["webkitTransformOrigin"]),b("perspective",["webkitPerspective"] ),b("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){ return c[a]||a}}(d,f)}(),!function(a,b){function c(a){var b=window.document.time line;b.currentTime=a,b._discardAnimations(),0==b._animations.length?e=!1:request AnimationFrame(c)}var d=window.requestAnimationFrame;window.requestAnimationFram e=function(a){return d(function(b){window.document.timeline._updateAnimationsPro mises(),a(b),window.document.timeline._updateAnimationsPromises()})},b.Animation Timeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTime line.prototype={getAnimations:function(){return this._discardAnimations(),this._ animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromise s=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_dis cardAnimations:function(){this._updateAnimationsPromises(),this._animations=this ._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playSta te})},_play:function(a){var c=new b.Animation(a,this);return this._animations.pu sh(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c ._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var e=!1;b.restartWebAnimationsNextTick=function(){e||(e=!0,requestAnimationFrame(c) )};var f=new b.AnimationTimeline;b.timeline=f;try{Object.defineProperty(window.d ocument,"timeline",{configurable:!0,get:function(){return f}})}catch(g){}try{win dow.document.timeline=f}catch(g){}}(c,e,f),function(a,b){b.animationsWithPromise s=[],b.Animation=function(b,c){if(this.effect=b,b&&(b._animation=this),!c)throw new Error("Animation with null timeline is not supported");this._timeline=c,this ._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGro up=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._ol dPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),th is._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=th is._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?( this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolve ReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise& &b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0): "finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromi se=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finished Promise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d ,e,f=this._animation?!0:!1;f&&(a=this.playbackRate,c=this._paused,d=this.startTi me,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,thi s._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)& &(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindA nimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect|| this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnim ationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effe ct._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate= a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&& (this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises()
2432 },_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this. effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChil dren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration (c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&thi s._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b] ._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChild Animations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.d elay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var d=window.document.timeline._play(c);this._childAnimations.push(d),d.playbackRate =this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,t his._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.gr oupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.s tartTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.s tartTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)}, get timeline(){return this._timeline},get playState(){return this._animation?thi s._animation.playState:"idle"},get finished(){return window.Promise?(this._finis hedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromise s.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinish edPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOM Exception.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState &&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animatio n Promises require JavaScript Promise constructor"),null)},get ready(){return wi ndow.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b .animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){ this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function( ){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==t his.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("A nimation Promises require JavaScript Promise constructor"),null)},get onfinish() {return this._onfinish},set onfinish(a){"function"==typeof a?(this._onfinish=a,t his._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this)):(t his._animation.onfinish=a,this.onfinish=this._animation.onfinish)},get currentTi me(){this._updatePromises();var a=this._animation.currentTime;return this._updat ePromises(),a},set currentTime(a){this._updatePromises(),this._animation.current Time=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachC hild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){r eturn this._animation.startTime},set startTime(a){this._updatePromises(),this._a nimation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(), this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get p laybackRate(){return this._animation.playbackRate},set playbackRate(a){this._upd atePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEac hChild(function(b){b.playbackRate=a}),"paused"!=this.playState&&"idle"!=this.pla yState&&this.play(),null!==b&&(this.currentTime=b),this._updatePromises()},play: function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==thi s._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),thi s._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.curr entTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this ._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._ani mation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this. _paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this ._animation.finish(),this._register(),this._updatePromises()},cancel:function(){ this._updatePromises(),this._animation.cancel(),this._register(),this._removeChi ldAnimations(),this._updatePromises()},reverse:function(){this._updatePromises() ;var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a) {a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventLi stener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this, b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)}, removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wr apper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;) this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this. effect.children&&this._childAnimations.length<this.effect.children.length&&this. _constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(thi s,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration )}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.curre ntTime;null!==e&&(e=a.calculateTimeFraction(a.calculateActiveDuration(d),e,d)),( null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animatio n}(c,e,f),function(a,b){function c(b){this._frames=a.normalizeKeyframes(b)}funct ion d(){for(var a=!1;h.length;){var b=h.shift();b._updateChildren(),a=!0}return a}var e=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)e(a.children[ b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._pa rent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._p arent.children.indexOf(d),1),d._parent=null,e(d)):d._animation&&d._animation.eff ect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d ._animation._callback&&(d._animation._callback._animation=null),d._animation._re buildUnderlyingAnimation(),e(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.Keyfr ameEffect=function(b,d,e){return this.target=b,this._parent=null,e=a.numericTimi ngToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTi mingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"funct ion"==typeof d?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeE ffect.onsample instead."),this._normalizedKeyframes=d):this._normalizedKeyframes =new c(d),this._keyframes=d,this.activeDuration=a.calculateActiveDuration(this._ timing),this},b.KeyframeEffect.prototype={getFrames:function(){return"function"= =typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyf rames._frames},set onsample(a){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this ._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFr ames())throw new Error("Cloning custom effects is not supported.");var b=new Key frameEffect(this.target,[],a.cloneTimingInput(this._timingInput));return b._norm alizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove :function(){b.removeMulti([this])}};var f=Element.prototype.animate;Element.prot otype.animate=function(a,c){return b.timeline._play(new b.KeyframeEffect(this,a, c))};var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newU nderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||g,c=a._key frames;"function"==typeof c&&(c=[]);var d=a._timingInput}else var b=g,c=[],d=0;r eturn f.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&" function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect( a)};var h=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==h. length&&requestAnimationFrame(d),h.push(a))};var i=window.getComputedStyle;Objec t.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value: function(){window.document.timeline._updateAnimationsPromises();var a=i.apply(th is,arguments);return d()&&(a=i.apply(this,arguments)),window.document.timeline._ updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.El ement.prototype.getAnimations=function(){return document.timeline.getAnimations( ).filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))} }(c,e,f),function(a,b){function c(a){a._registered||(a._registered=!0,f.push(a), g||(g=!0,requestAnimationFrame(d)))}function d(){var a=f;f=[],a.sort(function(a, b){return a._sequenceNumber-b._sequenceNumber}),a=a.filter(function(a){a();var b =a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a ._registered=!1),a._registered}),f.push.apply(f,a),f.length?(g=!0,requestAnimati onFrame(d)):g=!1}var e=(document.createElementNS("http://www.w3.org/1999/xhtml", "div"),0);b.bindAnimationForCustomEffect=function(b){var d,f=b.effect.target,g=" function"==typeof b.effect.getFrames();d=g?b.effect.getFrames():b.effect._onsamp le;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var c=j._animation?j._animation.currentTime:null;null!==c&&(c=a.calculateTimeFracti on(a.calculateActiveDuration(h),c,h),isNaN(c)&&(c=null)),c!==i&&(g?d(c,f,b.effec t):d(c,b.effect,b.effect._animation)),i=c};j._animation=b,j._registered=!1,j._se quenceNumber=e++,b._callback=j,c(j)};var f=[],g=!1;b.Animation.prototype._regist er=function(){this._callback&&c(this._callback)}}(c,e,f),function(a,b){function c(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function d(b,c){ this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericT imingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normaliz eTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this, "auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}wind ow.SequenceEffect=function(){d.apply(this,arguments)},window.GroupEffect=functio n(){d.apply(this,arguments)},d.prototype={_isAncestor:function(a){for(var b=this ;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a =this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._ parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent :function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_put Child:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prep end an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type :DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for( var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);th is._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)} ,prepend:function(){this._putChild(arguments,!1)},get parent(){return this._pare nt},get firstChild(){return this.children.length?this.children[0]:null},get last Child(){return this.children.length?this.children[this.children.length-1]:null}, clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this .children.length;d++)c.push(this.children[d].clone());return this instanceof Gro upEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.remov eMulti([this])}},window.SequenceEffect.prototype=Object.create(d.prototype),Obje ct.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function (){var a=0;return this.children.forEach(function(b){a+=c(b)}),Math.max(a,0)}}),w indow.GroupEffect.prototype=Object.create(d.prototype),Object.defineProperty(win dow.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.c hildren.forEach(function(b){a=Math.max(a,c(b))}),a}}),b.newUnderlyingAnimationFo rGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;return c&&"pendin g"!=c.playState&&c.effect?null==b?void c._removeChildAnimations():0==b&&c.playba ckRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateTimeFract ion(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(funct ion(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0:void 0},g=new KeyframeEffect(null,[],c._timing);return g.onsample=f,d=b.timeline._play(g)},b.b indAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitSt artTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChil dDuration=c}(c,e,f)}({},function(){return this}());
2433 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2434 // Use of this source code is governed by a BSD-style license that can be
2435 // found in the LICENSE file.
2436
2437 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
2438
2439 i18nTemplate.process(document, loadTimeData);
2440 (function () {
2441 function resolve() {
2442 document.body.removeAttribute('unresolved');
2443 }
2444 if (window.WebComponents) {
2445 addEventListener('WebComponentsReady', resolve);
2446 } else {
2447 if (document.readyState === 'interactive' || document.readyState === 'complete') {
2448 resolve();
2449 } else {
2450 addEventListener('DOMContentLoaded', resolve);
2451 }
2452 }
2453 }());
2454 window.Polymer = {
2455 Settings: function () {
2456 var user = window.Polymer || {};
2457 location.search.slice(1).split('&').forEach(function (o) {
2458 o = o.split('=');
2459 o[0] && (user[o[0]] = o[1] || true);
2460 });
2461 var wantShadow = user.dom === 'shadow';
2462 var hasShadow = Boolean(Element.prototype.createShadowRoot);
2463 var nativeShadow = hasShadow && !window.ShadowDOMPolyfill;
2464 var useShadow = wantShadow && hasShadow;
2465 var hasNativeImports = Boolean('import' in document.createElement('link'));
2466 var useNativeImports = hasNativeImports;
2467 var useNativeCustomElements = !window.CustomElements || window.CustomElements.us eNative;
2468 return {
2469 wantShadow: wantShadow,
2470 hasShadow: hasShadow,
2471 nativeShadow: nativeShadow,
2472 useShadow: useShadow,
2473 useNativeShadow: useShadow && nativeShadow,
2474 useNativeImports: useNativeImports,
2475 useNativeCustomElements: useNativeCustomElements
2476 };
2477 }()
2478 };
2479 (function () {
2480 var userPolymer = window.Polymer;
2481 window.Polymer = function (prototype) {
2482 if (typeof prototype === 'function') {
2483 prototype = prototype.prototype;
2484 }
2485 if (!prototype) {
2486 prototype = {};
2487 }
2488 var factory = desugar(prototype);
2489 prototype = factory.prototype;
2490 var options = { prototype: prototype };
2491 if (prototype.extends) {
2492 options.extends = prototype.extends;
2493 }
2494 Polymer.telemetry._registrate(prototype);
2495 document.registerElement(prototype.is, options);
2496 return factory;
2497 };
2498 var desugar = function (prototype) {
2499 var base = Polymer.Base;
2500 if (prototype.extends) {
2501 base = Polymer.Base._getExtendedPrototype(prototype.extends);
2502 }
2503 prototype = Polymer.Base.chainObject(prototype, base);
2504 prototype.registerCallback();
2505 return prototype.constructor;
2506 };
2507 window.Polymer = Polymer;
2508 if (userPolymer) {
2509 for (var i in userPolymer) {
2510 Polymer[i] = userPolymer[i];
2511 }
2512 }
2513 Polymer.Class = desugar;
2514 }());
2515 Polymer.telemetry = {
2516 registrations: [],
2517 _regLog: function (prototype) {
2518 console.log('[' + prototype.is + ']: registered');
2519 },
2520 _registrate: function (prototype) {
2521 this.registrations.push(prototype);
2522 Polymer.log && this._regLog(prototype);
2523 },
2524 dumpRegistrations: function () {
2525 this.registrations.forEach(this._regLog);
2526 }
2527 };
2528 Object.defineProperty(window, 'currentImport', {
2529 enumerable: true,
2530 configurable: true,
2531 get: function () {
2532 return (document._currentScript || document.currentScript).ownerDocument;
2533 }
2534 });
2535 Polymer.RenderStatus = {
2536 _ready: false,
2537 _callbacks: [],
2538 whenReady: function (cb) {
2539 if (this._ready) {
2540 cb();
2541 } else {
2542 this._callbacks.push(cb);
2543 }
2544 },
2545 _makeReady: function () {
2546 this._ready = true;
2547 this._callbacks.forEach(function (cb) {
2548 cb();
2549 });
2550 this._callbacks = [];
2551 },
2552 _catchFirstRender: function () {
2553 requestAnimationFrame(function () {
2554 Polymer.RenderStatus._makeReady();
2555 });
2556 }
2557 };
2558 if (window.HTMLImports) {
2559 HTMLImports.whenReady(function () {
2560 Polymer.RenderStatus._catchFirstRender();
2561 });
2562 } else {
2563 Polymer.RenderStatus._catchFirstRender();
2564 }
2565 Polymer.ImportStatus = Polymer.RenderStatus;
2566 Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady;
2567 Polymer.Base = {
2568 __isPolymerInstance__: true,
2569 _addFeature: function (feature) {
2570 this.extend(this, feature);
2571 },
2572 registerCallback: function () {
2573 this._desugarBehaviors();
2574 this._doBehavior('beforeRegister');
2575 this._registerFeatures();
2576 this._doBehavior('registered');
2577 },
2578 createdCallback: function () {
2579 Polymer.telemetry.instanceCount++;
2580 this.root = this;
2581 this._doBehavior('created');
2582 this._initFeatures();
2583 },
2584 attachedCallback: function () {
2585 Polymer.RenderStatus.whenReady(function () {
2586 this.isAttached = true;
2587 this._doBehavior('attached');
2588 }.bind(this));
2589 },
2590 detachedCallback: function () {
2591 this.isAttached = false;
2592 this._doBehavior('detached');
2593 },
2594 attributeChangedCallback: function (name) {
2595 this._attributeChangedImpl(name);
2596 this._doBehavior('attributeChanged', arguments);
2597 },
2598 _attributeChangedImpl: function (name) {
2599 this._setAttributeToProperty(this, name);
2600 },
2601 extend: function (prototype, api) {
2602 if (prototype && api) {
2603 Object.getOwnPropertyNames(api).forEach(function (n) {
2604 this.copyOwnProperty(n, api, prototype);
2605 }, this);
2606 }
2607 return prototype || api;
2608 },
2609 mixin: function (target, source) {
2610 for (var i in source) {
2611 target[i] = source[i];
2612 }
2613 return target;
2614 },
2615 copyOwnProperty: function (name, source, target) {
2616 var pd = Object.getOwnPropertyDescriptor(source, name);
2617 if (pd) {
2618 Object.defineProperty(target, name, pd);
2619 }
2620 },
2621 _log: console.log.apply.bind(console.log, console),
2622 _warn: console.warn.apply.bind(console.warn, console),
2623 _error: console.error.apply.bind(console.error, console),
2624 _logf: function () {
2625 return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(argum ents, 0));
2626 }
2627 };
2628 Polymer.Base._logPrefix = function () {
2629 var color = window.chrome || /firefox/i.test(navigator.userAgent);
2630 return color ? [
2631 '%c[%s::%s]:',
2632 'font-weight: bold; background-color:#EEEE00;'
2633 ] : ['[%s::%s]:'];
2634 }();
2635 Polymer.Base.chainObject = function (object, inherited) {
2636 if (object && inherited && object !== inherited) {
2637 if (!Object.__proto__) {
2638 object = Polymer.Base.extend(Object.create(inherited), object);
2639 }
2640 object.__proto__ = inherited;
2641 }
2642 return object;
2643 };
2644 Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype);
2645 if (window.CustomElements) {
2646 Polymer.instanceof = CustomElements.instanceof;
2647 } else {
2648 Polymer.instanceof = function (obj, ctor) {
2649 return obj instanceof ctor;
2650 };
2651 }
2652 Polymer.isInstance = function (obj) {
2653 return Boolean(obj && obj.__isPolymerInstance__);
2654 };
2655 Polymer.telemetry.instanceCount = 0;
2656 (function () {
2657 var modules = {};
2658 var lcModules = {};
2659 var findModule = function (id) {
2660 return modules[id] || lcModules[id.toLowerCase()];
2661 };
2662 var DomModule = function () {
2663 return document.createElement('dom-module');
2664 };
2665 DomModule.prototype = Object.create(HTMLElement.prototype);
2666 Polymer.Base.extend(DomModule.prototype, {
2667 constructor: DomModule,
2668 createdCallback: function () {
2669 this.register();
2670 },
2671 register: function (id) {
2672 var id = id || this.id || this.getAttribute('name') || this.getAttribute('is');
2673 if (id) {
2674 this.id = id;
2675 modules[id] = this;
2676 lcModules[id.toLowerCase()] = this;
2677 }
2678 },
2679 import: function (id, selector) {
2680 if (id) {
2681 var m = findModule(id);
2682 if (!m) {
2683 forceDocumentUpgrade();
2684 m = findModule(id);
2685 }
2686 if (m && selector) {
2687 m = m.querySelector(selector);
2688 }
2689 return m;
2690 }
2691 }
2692 });
2693 var cePolyfill = window.CustomElements && !CustomElements.useNative;
2694 document.registerElement('dom-module', DomModule);
2695 function forceDocumentUpgrade() {
2696 if (cePolyfill) {
2697 var script = document._currentScript || document.currentScript;
2698 var doc = script && script.ownerDocument;
2699 if (doc) {
2700 CustomElements.upgradeAll(doc);
2701 }
2702 }
2703 }
2704 }());
2705 Polymer.Base._addFeature({
2706 _prepIs: function () {
2707 if (!this.is) {
2708 var module = (document._currentScript || document.currentScript).parentNode;
2709 if (module.localName === 'dom-module') {
2710 var id = module.id || module.getAttribute('name') || module.getAttribute('is');
2711 this.is = id;
2712 }
2713 }
2714 if (this.is) {
2715 this.is = this.is.toLowerCase();
2716 }
2717 }
2718 });
2719 Polymer.Base._addFeature({
2720 behaviors: [],
2721 _desugarBehaviors: function () {
2722 if (this.behaviors.length) {
2723 this.behaviors = this._desugarSomeBehaviors(this.behaviors);
2724 }
2725 },
2726 _desugarSomeBehaviors: function (behaviors) {
2727 behaviors = this._flattenBehaviorsList(behaviors);
2728 for (var i = behaviors.length - 1; i >= 0; i--) {
2729 this._mixinBehavior(behaviors[i]);
2730 }
2731 return behaviors;
2732 },
2733 _flattenBehaviorsList: function (behaviors) {
2734 var flat = [];
2735 behaviors.forEach(function (b) {
2736 if (b instanceof Array) {
2737 flat = flat.concat(this._flattenBehaviorsList(b));
2738 } else if (b) {
2739 flat.push(b);
2740 } else {
2741 this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for miss ing or 404 import'));
2742 }
2743 }, this);
2744 return flat;
2745 },
2746 _mixinBehavior: function (b) {
2747 Object.getOwnPropertyNames(b).forEach(function (n) {
2748 switch (n) {
2749 case 'hostAttributes':
2750 case 'registered':
2751 case 'properties':
2752 case 'observers':
2753 case 'listeners':
2754 case 'created':
2755 case 'attached':
2756 case 'detached':
2757 case 'attributeChanged':
2758 case 'configure':
2759 case 'ready':
2760 break;
2761 default:
2762 if (!this.hasOwnProperty(n)) {
2763 this.copyOwnProperty(n, b, this);
2764 }
2765 break;
2766 }
2767 }, this);
2768 },
2769 _prepBehaviors: function () {
2770 this._prepFlattenedBehaviors(this.behaviors);
2771 },
2772 _prepFlattenedBehaviors: function (behaviors) {
2773 for (var i = 0, l = behaviors.length; i < l; i++) {
2774 this._prepBehavior(behaviors[i]);
2775 }
2776 this._prepBehavior(this);
2777 },
2778 _doBehavior: function (name, args) {
2779 this.behaviors.forEach(function (b) {
2780 this._invokeBehavior(b, name, args);
2781 }, this);
2782 this._invokeBehavior(this, name, args);
2783 },
2784 _invokeBehavior: function (b, name, args) {
2785 var fn = b[name];
2786 if (fn) {
2787 fn.apply(this, args || Polymer.nar);
2788 }
2789 },
2790 _marshalBehaviors: function () {
2791 this.behaviors.forEach(function (b) {
2792 this._marshalBehavior(b);
2793 }, this);
2794 this._marshalBehavior(this);
2795 }
2796 });
2797 Polymer.Base._addFeature({
2798 _getExtendedPrototype: function (tag) {
2799 return this._getExtendedNativePrototype(tag);
2800 },
2801 _nativePrototypes: {},
2802 _getExtendedNativePrototype: function (tag) {
2803 var p = this._nativePrototypes[tag];
2804 if (!p) {
2805 var np = this.getNativePrototype(tag);
2806 p = this.extend(Object.create(np), Polymer.Base);
2807 this._nativePrototypes[tag] = p;
2808 }
2809 return p;
2810 },
2811 getNativePrototype: function (tag) {
2812 return Object.getPrototypeOf(document.createElement(tag));
2813 }
2814 });
2815 Polymer.Base._addFeature({
2816 _prepConstructor: function () {
2817 this._factoryArgs = this.extends ? [
2818 this.extends,
2819 this.is
2820 ] : [this.is];
2821 var ctor = function () {
2822 return this._factory(arguments);
2823 };
2824 if (this.hasOwnProperty('extends')) {
2825 ctor.extends = this.extends;
2826 }
2827 Object.defineProperty(this, 'constructor', {
2828 value: ctor,
2829 writable: true,
2830 configurable: true
2831 });
2832 ctor.prototype = this;
2833 },
2834 _factory: function (args) {
2835 var elt = document.createElement.apply(document, this._factoryArgs);
2836 if (this.factoryImpl) {
2837 this.factoryImpl.apply(elt, args);
2838 }
2839 return elt;
2840 }
2841 });
2842 Polymer.nob = Object.create(null);
2843 Polymer.Base._addFeature({
2844 properties: {},
2845 getPropertyInfo: function (property) {
2846 var info = this._getPropertyInfo(property, this.properties);
2847 if (!info) {
2848 this.behaviors.some(function (b) {
2849 return info = this._getPropertyInfo(property, b.properties);
2850 }, this);
2851 }
2852 return info || Polymer.nob;
2853 },
2854 _getPropertyInfo: function (property, properties) {
2855 var p = properties && properties[property];
2856 if (typeof p === 'function') {
2857 p = properties[property] = { type: p };
2858 }
2859 if (p) {
2860 p.defined = true;
2861 }
2862 return p;
2863 }
2864 });
2865 Polymer.CaseMap = {
2866 _caseMap: {},
2867 dashToCamelCase: function (dash) {
2868 var mapped = Polymer.CaseMap._caseMap[dash];
2869 if (mapped) {
2870 return mapped;
2871 }
2872 if (dash.indexOf('-') < 0) {
2873 return Polymer.CaseMap._caseMap[dash] = dash;
2874 }
2875 return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) {
2876 return m[1].toUpperCase();
2877 });
2878 },
2879 camelToDashCase: function (camel) {
2880 var mapped = Polymer.CaseMap._caseMap[camel];
2881 if (mapped) {
2882 return mapped;
2883 }
2884 return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function (g) {
2885 return g[0] + '-' + g[1].toLowerCase();
2886 });
2887 }
2888 };
2889 Polymer.Base._addFeature({
2890 _prepAttributes: function () {
2891 this._aggregatedAttributes = {};
2892 },
2893 _addHostAttributes: function (attributes) {
2894 if (attributes) {
2895 this.mixin(this._aggregatedAttributes, attributes);
2896 }
2897 },
2898 _marshalHostAttributes: function () {
2899 this._applyAttributes(this, this._aggregatedAttributes);
2900 },
2901 _applyAttributes: function (node, attr$) {
2902 for (var n in attr$) {
2903 if (!this.hasAttribute(n) && n !== 'class') {
2904 this.serializeValueToAttribute(attr$[n], n, this);
2905 }
2906 }
2907 },
2908 _marshalAttributes: function () {
2909 this._takeAttributesToModel(this);
2910 },
2911 _takeAttributesToModel: function (model) {
2912 for (var i = 0, l = this.attributes.length; i < l; i++) {
2913 this._setAttributeToProperty(model, this.attributes[i].name);
2914 }
2915 },
2916 _setAttributeToProperty: function (model, attrName) {
2917 if (!this._serializing) {
2918 var propName = Polymer.CaseMap.dashToCamelCase(attrName);
2919 var info = this.getPropertyInfo(propName);
2920 if (info.defined || this._propertyEffects && this._propertyEffects[propName]) {
2921 var val = this.getAttribute(attrName);
2922 model[propName] = this.deserialize(val, info.type);
2923 }
2924 }
2925 },
2926 _serializing: false,
2927 reflectPropertyToAttribute: function (name) {
2928 this._serializing = true;
2929 this.serializeValueToAttribute(this[name], Polymer.CaseMap.camelToDashCase(name) );
2930 this._serializing = false;
2931 },
2932 serializeValueToAttribute: function (value, attribute, node) {
2933 var str = this.serialize(value);
2934 (node || this)[str === undefined ? 'removeAttribute' : 'setAttribute'](attribute , str);
2935 },
2936 deserialize: function (value, type) {
2937 switch (type) {
2938 case Number:
2939 value = Number(value);
2940 break;
2941 case Boolean:
2942 value = value !== null;
2943 break;
2944 case Object:
2945 try {
2946 value = JSON.parse(value);
2947 } catch (x) {
2948 }
2949 break;
2950 case Array:
2951 try {
2952 value = JSON.parse(value);
2953 } catch (x) {
2954 value = null;
2955 console.warn('Polymer::Attributes: couldn`t decode Array as JSON');
2956 }
2957 break;
2958 case Date:
2959 value = new Date(value);
2960 break;
2961 case String:
2962 default:
2963 break;
2964 }
2965 return value;
2966 },
2967 serialize: function (value) {
2968 switch (typeof value) {
2969 case 'boolean':
2970 return value ? '' : undefined;
2971 case 'object':
2972 if (value instanceof Date) {
2973 return value;
2974 } else if (value) {
2975 try {
2976 return JSON.stringify(value);
2977 } catch (x) {
2978 return '';
2979 }
2980 }
2981 default:
2982 return value != null ? value : undefined;
2983 }
2984 }
2985 });
2986 Polymer.Base._addFeature({
2987 _setupDebouncers: function () {
2988 this._debouncers = {};
2989 },
2990 debounce: function (jobName, callback, wait) {
2991 return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[ jobName], callback, wait);
2992 },
2993 isDebouncerActive: function (jobName) {
2994 var debouncer = this._debouncers[jobName];
2995 return debouncer && debouncer.finish;
2996 },
2997 flushDebouncer: function (jobName) {
2998 var debouncer = this._debouncers[jobName];
2999 if (debouncer) {
3000 debouncer.complete();
3001 }
3002 },
3003 cancelDebouncer: function (jobName) {
3004 var debouncer = this._debouncers[jobName];
3005 if (debouncer) {
3006 debouncer.stop();
3007 }
3008 }
3009 });
3010 Polymer.version = '1.1.4';
3011 Polymer.Base._addFeature({
3012 _registerFeatures: function () {
3013 this._prepIs();
3014 this._prepAttributes();
3015 this._prepBehaviors();
3016 this._prepConstructor();
3017 },
3018 _prepBehavior: function (b) {
3019 this._addHostAttributes(b.hostAttributes);
3020 },
3021 _marshalBehavior: function (b) {
3022 },
3023 _initFeatures: function () {
3024 this._marshalHostAttributes();
3025 this._setupDebouncers();
3026 this._marshalBehaviors();
3027 }
3028 });
3029 Polymer.Base._addFeature({
3030 _prepTemplate: function () {
3031 this._template = this._template || Polymer.DomModule.import(this.is, 'template') ;
3032 if (this._template && this._template.hasAttribute('is')) {
3033 this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not be a type-extension, found', this._template, 'Move inside simple <template>.')) ;
3034 }
3035 if (this._template && !this._template.content && HTMLTemplateElement.bootstrap) {
3036 HTMLTemplateElement.decorate(this._template);
3037 HTMLTemplateElement.bootstrap(this._template.content);
3038 }
3039 },
3040 _stampTemplate: function () {
3041 if (this._template) {
3042 this.root = this.instanceTemplate(this._template);
3043 }
3044 },
3045 instanceTemplate: function (template) {
3046 var dom = document.importNode(template._content || template.content, true);
3047 return dom;
3048 }
3049 });
3050 (function () {
3051 var baseAttachedCallback = Polymer.Base.attachedCallback;
3052 Polymer.Base._addFeature({
3053 _hostStack: [],
3054 ready: function () {
3055 },
3056 _pushHost: function (host) {
3057 this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.l ength - 1];
3058 if (host && host._clients) {
3059 host._clients.push(this);
3060 }
3061 this._beginHost();
3062 },
3063 _beginHost: function () {
3064 Polymer.Base._hostStack.push(this);
3065 if (!this._clients) {
3066 this._clients = [];
3067 }
3068 },
3069 _popHost: function () {
3070 Polymer.Base._hostStack.pop();
3071 },
3072 _tryReady: function () {
3073 if (this._canReady()) {
3074 this._ready();
3075 }
3076 },
3077 _canReady: function () {
3078 return !this.dataHost || this.dataHost._clientsReadied;
3079 },
3080 _ready: function () {
3081 this._beforeClientsReady();
3082 this._setupRoot();
3083 this._readyClients();
3084 this._afterClientsReady();
3085 this._readySelf();
3086 },
3087 _readyClients: function () {
3088 this._beginDistribute();
3089 var c$ = this._clients;
3090 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
3091 c._ready();
3092 }
3093 this._finishDistribute();
3094 this._clientsReadied = true;
3095 this._clients = null;
3096 },
3097 _readySelf: function () {
3098 this._doBehavior('ready');
3099 this._readied = true;
3100 if (this._attachedPending) {
3101 this._attachedPending = false;
3102 this.attachedCallback();
3103 }
3104 },
3105 _beforeClientsReady: function () {
3106 },
3107 _afterClientsReady: function () {
3108 },
3109 _beforeAttached: function () {
3110 },
3111 attachedCallback: function () {
3112 if (this._readied) {
3113 this._beforeAttached();
3114 baseAttachedCallback.call(this);
3115 } else {
3116 this._attachedPending = true;
3117 }
3118 }
3119 });
3120 }());
3121 Polymer.ArraySplice = function () {
3122 function newSplice(index, removed, addedCount) {
3123 return {
3124 index: index,
3125 removed: removed,
3126 addedCount: addedCount
3127 };
3128 }
3129 var EDIT_LEAVE = 0;
3130 var EDIT_UPDATE = 1;
3131 var EDIT_ADD = 2;
3132 var EDIT_DELETE = 3;
3133 function ArraySplice() {
3134 }
3135 ArraySplice.prototype = {
3136 calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, o ldEnd) {
3137 var rowCount = oldEnd - oldStart + 1;
3138 var columnCount = currentEnd - currentStart + 1;
3139 var distances = new Array(rowCount);
3140 for (var i = 0; i < rowCount; i++) {
3141 distances[i] = new Array(columnCount);
3142 distances[i][0] = i;
3143 }
3144 for (var j = 0; j < columnCount; j++)
3145 distances[0][j] = j;
3146 for (var i = 1; i < rowCount; i++) {
3147 for (var j = 1; j < columnCount; j++) {
3148 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
3149 distances[i][j] = distances[i - 1][j - 1];
3150 else {
3151 var north = distances[i - 1][j] + 1;
3152 var west = distances[i][j - 1] + 1;
3153 distances[i][j] = north < west ? north : west;
3154 }
3155 }
3156 }
3157 return distances;
3158 },
3159 spliceOperationsFromEditDistances: function (distances) {
3160 var i = distances.length - 1;
3161 var j = distances[0].length - 1;
3162 var current = distances[i][j];
3163 var edits = [];
3164 while (i > 0 || j > 0) {
3165 if (i == 0) {
3166 edits.push(EDIT_ADD);
3167 j--;
3168 continue;
3169 }
3170 if (j == 0) {
3171 edits.push(EDIT_DELETE);
3172 i--;
3173 continue;
3174 }
3175 var northWest = distances[i - 1][j - 1];
3176 var west = distances[i - 1][j];
3177 var north = distances[i][j - 1];
3178 var min;
3179 if (west < north)
3180 min = west < northWest ? west : northWest;
3181 else
3182 min = north < northWest ? north : northWest;
3183 if (min == northWest) {
3184 if (northWest == current) {
3185 edits.push(EDIT_LEAVE);
3186 } else {
3187 edits.push(EDIT_UPDATE);
3188 current = northWest;
3189 }
3190 i--;
3191 j--;
3192 } else if (min == west) {
3193 edits.push(EDIT_DELETE);
3194 i--;
3195 current = west;
3196 } else {
3197 edits.push(EDIT_ADD);
3198 j--;
3199 current = north;
3200 }
3201 }
3202 edits.reverse();
3203 return edits;
3204 },
3205 calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd) {
3206 var prefixCount = 0;
3207 var suffixCount = 0;
3208 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
3209 if (currentStart == 0 && oldStart == 0)
3210 prefixCount = this.sharedPrefix(current, old, minLength);
3211 if (currentEnd == current.length && oldEnd == old.length)
3212 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
3213 currentStart += prefixCount;
3214 oldStart += prefixCount;
3215 currentEnd -= suffixCount;
3216 oldEnd -= suffixCount;
3217 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
3218 return [];
3219 if (currentStart == currentEnd) {
3220 var splice = newSplice(currentStart, [], 0);
3221 while (oldStart < oldEnd)
3222 splice.removed.push(old[oldStart++]);
3223 return [splice];
3224 } else if (oldStart == oldEnd)
3225 return [newSplice(currentStart, [], currentEnd - currentStart)];
3226 var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
3227 var splice = undefined;
3228 var splices = [];
3229 var index = currentStart;
3230 var oldIndex = oldStart;
3231 for (var i = 0; i < ops.length; i++) {
3232 switch (ops[i]) {
3233 case EDIT_LEAVE:
3234 if (splice) {
3235 splices.push(splice);
3236 splice = undefined;
3237 }
3238 index++;
3239 oldIndex++;
3240 break;
3241 case EDIT_UPDATE:
3242 if (!splice)
3243 splice = newSplice(index, [], 0);
3244 splice.addedCount++;
3245 index++;
3246 splice.removed.push(old[oldIndex]);
3247 oldIndex++;
3248 break;
3249 case EDIT_ADD:
3250 if (!splice)
3251 splice = newSplice(index, [], 0);
3252 splice.addedCount++;
3253 index++;
3254 break;
3255 case EDIT_DELETE:
3256 if (!splice)
3257 splice = newSplice(index, [], 0);
3258 splice.removed.push(old[oldIndex]);
3259 oldIndex++;
3260 break;
3261 }
3262 }
3263 if (splice) {
3264 splices.push(splice);
3265 }
3266 return splices;
3267 },
3268 sharedPrefix: function (current, old, searchLength) {
3269 for (var i = 0; i < searchLength; i++)
3270 if (!this.equals(current[i], old[i]))
3271 return i;
3272 return searchLength;
3273 },
3274 sharedSuffix: function (current, old, searchLength) {
3275 var index1 = current.length;
3276 var index2 = old.length;
3277 var count = 0;
3278 while (count < searchLength && this.equals(current[--index1], old[--index2]))
3279 count++;
3280 return count;
3281 },
3282 calculateSplices: function (current, previous) {
3283 return this.calcSplices(current, 0, current.length, previous, 0, previous.length );
3284 },
3285 equals: function (currentValue, previousValue) {
3286 return currentValue === previousValue;
3287 }
3288 };
3289 return new ArraySplice();
3290 }();
3291 Polymer.EventApi = function () {
3292 var Settings = Polymer.Settings;
3293 var EventApi = function (event) {
3294 this.event = event;
3295 };
3296 if (Settings.useShadow) {
3297 EventApi.prototype = {
3298 get rootTarget() {
3299 return this.event.path[0];
3300 },
3301 get localTarget() {
3302 return this.event.target;
3303 },
3304 get path() {
3305 return this.event.path;
3306 }
3307 };
3308 } else {
3309 EventApi.prototype = {
3310 get rootTarget() {
3311 return this.event.target;
3312 },
3313 get localTarget() {
3314 var current = this.event.currentTarget;
3315 var currentRoot = current && Polymer.dom(current).getOwnerRoot();
3316 var p$ = this.path;
3317 for (var i = 0; i < p$.length; i++) {
3318 if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) {
3319 return p$[i];
3320 }
3321 }
3322 },
3323 get path() {
3324 if (!this.event._path) {
3325 var path = [];
3326 var o = this.rootTarget;
3327 while (o) {
3328 path.push(o);
3329 o = Polymer.dom(o).parentNode || o.host;
3330 }
3331 path.push(window);
3332 this.event._path = path;
3333 }
3334 return this.event._path;
3335 }
3336 };
3337 }
3338 var factory = function (event) {
3339 if (!event.__eventApi) {
3340 event.__eventApi = new EventApi(event);
3341 }
3342 return event.__eventApi;
3343 };
3344 return { factory: factory };
3345 }();
3346 Polymer.domInnerHTML = function () {
3347 var escapeAttrRegExp = /[&\u00A0"]/g;
3348 var escapeDataRegExp = /[&\u00A0<>]/g;
3349 function escapeReplace(c) {
3350 switch (c) {
3351 case '&':
3352 return '&amp;';
3353 case '<':
3354 return '&lt;';
3355 case '>':
3356 return '&gt;';
3357 case '"':
3358 return '&quot;';
3359 case '\xA0':
3360 return '&nbsp;';
3361 }
3362 }
3363 function escapeAttr(s) {
3364 return s.replace(escapeAttrRegExp, escapeReplace);
3365 }
3366 function escapeData(s) {
3367 return s.replace(escapeDataRegExp, escapeReplace);
3368 }
3369 function makeSet(arr) {
3370 var set = {};
3371 for (var i = 0; i < arr.length; i++) {
3372 set[arr[i]] = true;
3373 }
3374 return set;
3375 }
3376 var voidElements = makeSet([
3377 'area',
3378 'base',
3379 'br',
3380 'col',
3381 'command',
3382 'embed',
3383 'hr',
3384 'img',
3385 'input',
3386 'keygen',
3387 'link',
3388 'meta',
3389 'param',
3390 'source',
3391 'track',
3392 'wbr'
3393 ]);
3394 var plaintextParents = makeSet([
3395 'style',
3396 'script',
3397 'xmp',
3398 'iframe',
3399 'noembed',
3400 'noframes',
3401 'plaintext',
3402 'noscript'
3403 ]);
3404 function getOuterHTML(node, parentNode, composed) {
3405 switch (node.nodeType) {
3406 case Node.ELEMENT_NODE:
3407 var tagName = node.localName;
3408 var s = '<' + tagName;
3409 var attrs = node.attributes;
3410 for (var i = 0, attr; attr = attrs[i]; i++) {
3411 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
3412 }
3413 s += '>';
3414 if (voidElements[tagName]) {
3415 return s;
3416 }
3417 return s + getInnerHTML(node, composed) + '</' + tagName + '>';
3418 case Node.TEXT_NODE:
3419 var data = node.data;
3420 if (parentNode && plaintextParents[parentNode.localName]) {
3421 return data;
3422 }
3423 return escapeData(data);
3424 case Node.COMMENT_NODE:
3425 return '<!--' + node.data + '-->';
3426 default:
3427 console.error(node);
3428 throw new Error('not implemented');
3429 }
3430 }
3431 function getInnerHTML(node, composed) {
3432 if (node instanceof HTMLTemplateElement)
3433 node = node.content;
3434 var s = '';
3435 var c$ = Polymer.dom(node).childNodes;
3436 c$ = composed ? node._composedChildren : c$;
3437 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
3438 s += getOuterHTML(child, node, composed);
3439 }
3440 return s;
3441 }
3442 return { getInnerHTML: getInnerHTML };
3443 }();
3444 Polymer.DomApi = function () {
3445 'use strict';
3446 var Settings = Polymer.Settings;
3447 var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
3448 var nativeInsertBefore = Element.prototype.insertBefore;
3449 var nativeRemoveChild = Element.prototype.removeChild;
3450 var nativeAppendChild = Element.prototype.appendChild;
3451 var nativeCloneNode = Element.prototype.cloneNode;
3452 var nativeImportNode = Document.prototype.importNode;
3453 var DomApi = function (node) {
3454 this.node = node;
3455 if (this.patch) {
3456 this.patch();
3457 }
3458 };
3459 if (window.wrap && Settings.useShadow && !Settings.useNativeShadow) {
3460 DomApi = function (node) {
3461 this.node = wrap(node);
3462 if (this.patch) {
3463 this.patch();
3464 }
3465 };
3466 }
3467 DomApi.prototype = {
3468 flush: function () {
3469 Polymer.dom.flush();
3470 },
3471 _lazyDistribute: function (host) {
3472 if (host.shadyRoot && host.shadyRoot._distributionClean) {
3473 host.shadyRoot._distributionClean = false;
3474 Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent));
3475 }
3476 },
3477 appendChild: function (node) {
3478 return this._addNode(node);
3479 },
3480 insertBefore: function (node, ref_node) {
3481 return this._addNode(node, ref_node);
3482 },
3483 _addNode: function (node, ref_node) {
3484 this._removeNodeFromHost(node, true);
3485 var addedInsertionPoint;
3486 var root = this.getOwnerRoot();
3487 if (root) {
3488 addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node);
3489 }
3490 if (this._nodeHasLogicalChildren(this.node)) {
3491 if (ref_node) {
3492 var children = this.childNodes;
3493 var index = children.indexOf(ref_node);
3494 if (index < 0) {
3495 throw Error('The ref_node to be inserted before is not a child ' + 'of this node ');
3496 }
3497 }
3498 this._addLogicalInfo(node, this.node, index);
3499 }
3500 this._addNodeToHost(node);
3501 if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode (node)) {
3502 if (ref_node) {
3503 ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) : ref_node;
3504 }
3505 var container = this.node._isShadyRoot ? this.node.host : this.node;
3506 addToComposedParent(container, node, ref_node);
3507 if (ref_node) {
3508 nativeInsertBefore.call(container, node, ref_node);
3509 } else {
3510 nativeAppendChild.call(container, node);
3511 }
3512 }
3513 if (addedInsertionPoint) {
3514 this._updateInsertionPoints(root.host);
3515 }
3516 return node;
3517 },
3518 removeChild: function (node) {
3519 if (factory(node).parentNode !== this.node) {
3520 console.warn('The node to be removed is not a child of this node', node);
3521 }
3522 this._removeNodeFromHost(node);
3523 if (!this._maybeDistribute(node, this.node)) {
3524 var container = this.node._isShadyRoot ? this.node.host : this.node;
3525 if (container === node.parentNode) {
3526 removeFromComposedParent(container, node);
3527 nativeRemoveChild.call(container, node);
3528 }
3529 }
3530 return node;
3531 },
3532 replaceChild: function (node, ref_node) {
3533 this.insertBefore(node, ref_node);
3534 this.removeChild(ref_node);
3535 return node;
3536 },
3537 _hasCachedOwnerRoot: function (node) {
3538 return Boolean(node._ownerShadyRoot !== undefined);
3539 },
3540 getOwnerRoot: function () {
3541 return this._ownerShadyRootForNode(this.node);
3542 },
3543 _ownerShadyRootForNode: function (node) {
3544 if (!node) {
3545 return;
3546 }
3547 if (node._ownerShadyRoot === undefined) {
3548 var root;
3549 if (node._isShadyRoot) {
3550 root = node;
3551 } else {
3552 var parent = Polymer.dom(node).parentNode;
3553 if (parent) {
3554 root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent);
3555 } else {
3556 root = null;
3557 }
3558 }
3559 node._ownerShadyRoot = root;
3560 }
3561 return node._ownerShadyRoot;
3562 },
3563 _maybeDistribute: function (node, parent) {
3564 var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noCon tent && Polymer.dom(node).querySelector(CONTENT);
3565 var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE;
3566 var hasContent = fragContent || node.localName === CONTENT;
3567 if (hasContent) {
3568 var root = this._ownerShadyRootForNode(parent);
3569 if (root) {
3570 var host = root.host;
3571 this._lazyDistribute(host);
3572 }
3573 }
3574 var parentNeedsDist = this._parentNeedsDistribution(parent);
3575 if (parentNeedsDist) {
3576 this._lazyDistribute(parent);
3577 }
3578 return parentNeedsDist || hasContent && !wrappedContent;
3579 },
3580 _maybeAddInsertionPoint: function (node, parent) {
3581 var added;
3582 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) {
3583 var c$ = factory(node).querySelectorAll(CONTENT);
3584 for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) {
3585 np = factory(n).parentNode;
3586 if (np === node) {
3587 np = parent;
3588 }
3589 na = this._maybeAddInsertionPoint(n, np);
3590 added = added || na;
3591 }
3592 } else if (node.localName === CONTENT) {
3593 saveLightChildrenIfNeeded(parent);
3594 saveLightChildrenIfNeeded(node);
3595 added = true;
3596 }
3597 return added;
3598 },
3599 _tryRemoveUndistributedNode: function (node) {
3600 if (this.node.shadyRoot) {
3601 var parent = getComposedParent(node);
3602 if (parent) {
3603 nativeRemoveChild.call(parent, node);
3604 }
3605 return true;
3606 }
3607 },
3608 _updateInsertionPoints: function (host) {
3609 var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelector All(CONTENT);
3610 for (var i = 0, c; i < i$.length; i++) {
3611 c = i$[i];
3612 saveLightChildrenIfNeeded(c);
3613 saveLightChildrenIfNeeded(factory(c).parentNode);
3614 }
3615 },
3616 _nodeHasLogicalChildren: function (node) {
3617 return Boolean(node._lightChildren !== undefined);
3618 },
3619 _parentNeedsDistribution: function (parent) {
3620 return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
3621 },
3622 _removeNodeFromHost: function (node, ensureComposedRemoval) {
3623 var hostNeedsDist;
3624 var root;
3625 var parent = node._lightParent;
3626 if (parent) {
3627 factory(node)._distributeParent();
3628 root = this._ownerShadyRootForNode(node);
3629 if (root) {
3630 root.host._elementRemove(node);
3631 hostNeedsDist = this._removeDistributedChildren(root, node);
3632 }
3633 this._removeLogicalInfo(node, node._lightParent);
3634 }
3635 this._removeOwnerShadyRoot(node);
3636 if (root && hostNeedsDist) {
3637 this._updateInsertionPoints(root.host);
3638 this._lazyDistribute(root.host);
3639 } else if (ensureComposedRemoval) {
3640 removeFromComposedParent(getComposedParent(node), node);
3641 }
3642 },
3643 _removeDistributedChildren: function (root, container) {
3644 var hostNeedsDist;
3645 var ip$ = root._insertionPoints;
3646 for (var i = 0; i < ip$.length; i++) {
3647 var content = ip$[i];
3648 if (this._contains(container, content)) {
3649 var dc$ = factory(content).getDistributedNodes();
3650 for (var j = 0; j < dc$.length; j++) {
3651 hostNeedsDist = true;
3652 var node = dc$[j];
3653 var parent = node.parentNode;
3654 if (parent) {
3655 removeFromComposedParent(parent, node);
3656 nativeRemoveChild.call(parent, node);
3657 }
3658 }
3659 }
3660 }
3661 return hostNeedsDist;
3662 },
3663 _contains: function (container, node) {
3664 while (node) {
3665 if (node == container) {
3666 return true;
3667 }
3668 node = factory(node).parentNode;
3669 }
3670 },
3671 _addNodeToHost: function (node) {
3672 var root = this.getOwnerRoot();
3673 if (root) {
3674 root.host._elementAdd(node);
3675 }
3676 },
3677 _addLogicalInfo: function (node, container, index) {
3678 var children = factory(container).childNodes;
3679 index = index === undefined ? children.length : index;
3680 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
3681 var c$ = Array.prototype.slice.call(node.childNodes);
3682 for (var i = 0, n; i < c$.length && (n = c$[i]); i++) {
3683 children.splice(index++, 0, n);
3684 n._lightParent = container;
3685 }
3686 } else {
3687 children.splice(index, 0, node);
3688 node._lightParent = container;
3689 }
3690 },
3691 _removeLogicalInfo: function (node, container) {
3692 var children = factory(container).childNodes;
3693 var index = children.indexOf(node);
3694 if (index < 0 || container !== node._lightParent) {
3695 throw Error('The node to be removed is not a child of this node');
3696 }
3697 children.splice(index, 1);
3698 node._lightParent = null;
3699 },
3700 _removeOwnerShadyRoot: function (node) {
3701 if (this._hasCachedOwnerRoot(node)) {
3702 var c$ = factory(node).childNodes;
3703 for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) {
3704 this._removeOwnerShadyRoot(n);
3705 }
3706 }
3707 node._ownerShadyRoot = undefined;
3708 },
3709 _firstComposedNode: function (content) {
3710 var n$ = factory(content).getDistributedNodes();
3711 for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) {
3712 p$ = factory(n).getDestinationInsertionPoints();
3713 if (p$[p$.length - 1] === content) {
3714 return n;
3715 }
3716 }
3717 },
3718 querySelector: function (selector) {
3719 return this.querySelectorAll(selector)[0];
3720 },
3721 querySelectorAll: function (selector) {
3722 return this._query(function (n) {
3723 return matchesSelector.call(n, selector);
3724 }, this.node);
3725 },
3726 _query: function (matcher, node) {
3727 node = node || this.node;
3728 var list = [];
3729 this._queryElements(factory(node).childNodes, matcher, list);
3730 return list;
3731 },
3732 _queryElements: function (elements, matcher, list) {
3733 for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) {
3734 if (c.nodeType === Node.ELEMENT_NODE) {
3735 this._queryElement(c, matcher, list);
3736 }
3737 }
3738 },
3739 _queryElement: function (node, matcher, list) {
3740 if (matcher(node)) {
3741 list.push(node);
3742 }
3743 this._queryElements(factory(node).childNodes, matcher, list);
3744 },
3745 getDestinationInsertionPoints: function () {
3746 return this.node._destinationInsertionPoints || [];
3747 },
3748 getDistributedNodes: function () {
3749 return this.node._distributedNodes || [];
3750 },
3751 queryDistributedElements: function (selector) {
3752 var c$ = this.childNodes;
3753 var list = [];
3754 this._distributedFilter(selector, c$, list);
3755 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
3756 if (c.localName === CONTENT) {
3757 this._distributedFilter(selector, factory(c).getDistributedNodes(), list);
3758 }
3759 }
3760 return list;
3761 },
3762 _distributedFilter: function (selector, list, results) {
3763 results = results || [];
3764 for (var i = 0, l = list.length, d; i < l && (d = list[i]); i++) {
3765 if (d.nodeType === Node.ELEMENT_NODE && d.localName !== CONTENT && matchesSelect or.call(d, selector)) {
3766 results.push(d);
3767 }
3768 }
3769 return results;
3770 },
3771 _clear: function () {
3772 while (this.childNodes.length) {
3773 this.removeChild(this.childNodes[0]);
3774 }
3775 },
3776 setAttribute: function (name, value) {
3777 this.node.setAttribute(name, value);
3778 this._distributeParent();
3779 },
3780 removeAttribute: function (name) {
3781 this.node.removeAttribute(name);
3782 this._distributeParent();
3783 },
3784 _distributeParent: function () {
3785 if (this._parentNeedsDistribution(this.parentNode)) {
3786 this._lazyDistribute(this.parentNode);
3787 }
3788 },
3789 cloneNode: function (deep) {
3790 var n = nativeCloneNode.call(this.node, false);
3791 if (deep) {
3792 var c$ = this.childNodes;
3793 var d = factory(n);
3794 for (var i = 0, nc; i < c$.length; i++) {
3795 nc = factory(c$[i]).cloneNode(true);
3796 d.appendChild(nc);
3797 }
3798 }
3799 return n;
3800 },
3801 importNode: function (externalNode, deep) {
3802 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
3803 var n = nativeImportNode.call(doc, externalNode, false);
3804 if (deep) {
3805 var c$ = factory(externalNode).childNodes;
3806 var d = factory(n);
3807 for (var i = 0, nc; i < c$.length; i++) {
3808 nc = factory(doc).importNode(c$[i], true);
3809 d.appendChild(nc);
3810 }
3811 }
3812 return n;
3813 }
3814 };
3815 Object.defineProperty(DomApi.prototype, 'classList', {
3816 get: function () {
3817 if (!this._classList) {
3818 this._classList = new DomApi.ClassList(this);
3819 }
3820 return this._classList;
3821 },
3822 configurable: true
3823 });
3824 DomApi.ClassList = function (host) {
3825 this.domApi = host;
3826 this.node = host.node;
3827 };
3828 DomApi.ClassList.prototype = {
3829 add: function () {
3830 this.node.classList.add.apply(this.node.classList, arguments);
3831 this.domApi._distributeParent();
3832 },
3833 remove: function () {
3834 this.node.classList.remove.apply(this.node.classList, arguments);
3835 this.domApi._distributeParent();
3836 },
3837 toggle: function () {
3838 this.node.classList.toggle.apply(this.node.classList, arguments);
3839 this.domApi._distributeParent();
3840 },
3841 contains: function () {
3842 return this.node.classList.contains.apply(this.node.classList, arguments);
3843 }
3844 };
3845 if (!Settings.useShadow) {
3846 Object.defineProperties(DomApi.prototype, {
3847 childNodes: {
3848 get: function () {
3849 var c$ = getLightChildren(this.node);
3850 return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$);
3851 },
3852 configurable: true
3853 },
3854 children: {
3855 get: function () {
3856 return Array.prototype.filter.call(this.childNodes, function (n) {
3857 return n.nodeType === Node.ELEMENT_NODE;
3858 });
3859 },
3860 configurable: true
3861 },
3862 parentNode: {
3863 get: function () {
3864 return this.node._lightParent || getComposedParent(this.node);
3865 },
3866 configurable: true
3867 },
3868 firstChild: {
3869 get: function () {
3870 return this.childNodes[0];
3871 },
3872 configurable: true
3873 },
3874 lastChild: {
3875 get: function () {
3876 var c$ = this.childNodes;
3877 return c$[c$.length - 1];
3878 },
3879 configurable: true
3880 },
3881 nextSibling: {
3882 get: function () {
3883 var c$ = this.parentNode && factory(this.parentNode).childNodes;
3884 if (c$) {
3885 return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
3886 }
3887 },
3888 configurable: true
3889 },
3890 previousSibling: {
3891 get: function () {
3892 var c$ = this.parentNode && factory(this.parentNode).childNodes;
3893 if (c$) {
3894 return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
3895 }
3896 },
3897 configurable: true
3898 },
3899 firstElementChild: {
3900 get: function () {
3901 return this.children[0];
3902 },
3903 configurable: true
3904 },
3905 lastElementChild: {
3906 get: function () {
3907 var c$ = this.children;
3908 return c$[c$.length - 1];
3909 },
3910 configurable: true
3911 },
3912 nextElementSibling: {
3913 get: function () {
3914 var c$ = this.parentNode && factory(this.parentNode).children;
3915 if (c$) {
3916 return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
3917 }
3918 },
3919 configurable: true
3920 },
3921 previousElementSibling: {
3922 get: function () {
3923 var c$ = this.parentNode && factory(this.parentNode).children;
3924 if (c$) {
3925 return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
3926 }
3927 },
3928 configurable: true
3929 },
3930 textContent: {
3931 get: function () {
3932 var nt = this.node.nodeType;
3933 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
3934 return this.node.textContent;
3935 } else {
3936 var tc = [];
3937 for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
3938 if (c.nodeType !== Node.COMMENT_NODE) {
3939 tc.push(c.textContent);
3940 }
3941 }
3942 return tc.join('');
3943 }
3944 },
3945 set: function (text) {
3946 var nt = this.node.nodeType;
3947 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
3948 this.node.textContent = text;
3949 } else {
3950 this._clear();
3951 if (text) {
3952 this.appendChild(document.createTextNode(text));
3953 }
3954 }
3955 },
3956 configurable: true
3957 },
3958 innerHTML: {
3959 get: function () {
3960 var nt = this.node.nodeType;
3961 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
3962 return null;
3963 } else {
3964 return getInnerHTML(this.node);
3965 }
3966 },
3967 set: function (text) {
3968 var nt = this.node.nodeType;
3969 if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
3970 this._clear();
3971 var d = document.createElement('div');
3972 d.innerHTML = text;
3973 var c$ = Array.prototype.slice.call(d.childNodes);
3974 for (var i = 0; i < c$.length; i++) {
3975 this.appendChild(c$[i]);
3976 }
3977 }
3978 },
3979 configurable: true
3980 }
3981 });
3982 DomApi.prototype._getComposedInnerHTML = function () {
3983 return getInnerHTML(this.node, true);
3984 };
3985 } else {
3986 var forwardMethods = [
3987 'cloneNode',
3988 'appendChild',
3989 'insertBefore',
3990 'removeChild',
3991 'replaceChild'
3992 ];
3993 forwardMethods.forEach(function (name) {
3994 DomApi.prototype[name] = function () {
3995 return this.node[name].apply(this.node, arguments);
3996 };
3997 });
3998 DomApi.prototype.querySelectorAll = function (selector) {
3999 return Array.prototype.slice.call(this.node.querySelectorAll(selector));
4000 };
4001 DomApi.prototype.getOwnerRoot = function () {
4002 var n = this.node;
4003 while (n) {
4004 if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
4005 return n;
4006 }
4007 n = n.parentNode;
4008 }
4009 };
4010 DomApi.prototype.importNode = function (externalNode, deep) {
4011 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
4012 return doc.importNode(externalNode, deep);
4013 };
4014 DomApi.prototype.getDestinationInsertionPoints = function () {
4015 var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInse rtionPoints();
4016 return n$ ? Array.prototype.slice.call(n$) : [];
4017 };
4018 DomApi.prototype.getDistributedNodes = function () {
4019 var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes();
4020 return n$ ? Array.prototype.slice.call(n$) : [];
4021 };
4022 DomApi.prototype._distributeParent = function () {
4023 };
4024 Object.defineProperties(DomApi.prototype, {
4025 childNodes: {
4026 get: function () {
4027 return Array.prototype.slice.call(this.node.childNodes);
4028 },
4029 configurable: true
4030 },
4031 children: {
4032 get: function () {
4033 return Array.prototype.slice.call(this.node.children);
4034 },
4035 configurable: true
4036 },
4037 textContent: {
4038 get: function () {
4039 return this.node.textContent;
4040 },
4041 set: function (value) {
4042 return this.node.textContent = value;
4043 },
4044 configurable: true
4045 },
4046 innerHTML: {
4047 get: function () {
4048 return this.node.innerHTML;
4049 },
4050 set: function (value) {
4051 return this.node.innerHTML = value;
4052 },
4053 configurable: true
4054 }
4055 });
4056 var forwardProperties = [
4057 'parentNode',
4058 'firstChild',
4059 'lastChild',
4060 'nextSibling',
4061 'previousSibling',
4062 'firstElementChild',
4063 'lastElementChild',
4064 'nextElementSibling',
4065 'previousElementSibling'
4066 ];
4067 forwardProperties.forEach(function (name) {
4068 Object.defineProperty(DomApi.prototype, name, {
4069 get: function () {
4070 return this.node[name];
4071 },
4072 configurable: true
4073 });
4074 });
4075 }
4076 var CONTENT = 'content';
4077 var factory = function (node, patch) {
4078 node = node || document;
4079 if (!node.__domApi) {
4080 node.__domApi = new DomApi(node, patch);
4081 }
4082 return node.__domApi;
4083 };
4084 Polymer.dom = function (obj, patch) {
4085 if (obj instanceof Event) {
4086 return Polymer.EventApi.factory(obj);
4087 } else {
4088 return factory(obj, patch);
4089 }
4090 };
4091 Polymer.Base.extend(Polymer.dom, {
4092 _flushGuard: 0,
4093 _FLUSH_MAX: 100,
4094 _needsTakeRecords: !Polymer.Settings.useNativeCustomElements,
4095 _debouncers: [],
4096 _finishDebouncer: null,
4097 flush: function () {
4098 for (var i = 0; i < this._debouncers.length; i++) {
4099 this._debouncers[i].complete();
4100 }
4101 if (this._finishDebouncer) {
4102 this._finishDebouncer.complete();
4103 }
4104 this._flushPolyfills();
4105 if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) {
4106 this._flushGuard++;
4107 this.flush();
4108 } else {
4109 if (this._flushGuard >= this._FLUSH_MAX) {
4110 console.warn('Polymer.dom.flush aborted. Flush may not be complete.');
4111 }
4112 this._flushGuard = 0;
4113 }
4114 },
4115 _flushPolyfills: function () {
4116 if (this._needsTakeRecords) {
4117 CustomElements.takeRecords();
4118 }
4119 },
4120 addDebouncer: function (debouncer) {
4121 this._debouncers.push(debouncer);
4122 this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlus h);
4123 },
4124 _finishFlush: function () {
4125 Polymer.dom._debouncers = [];
4126 }
4127 });
4128 function getLightChildren(node) {
4129 var children = node._lightChildren;
4130 return children ? children : node.childNodes;
4131 }
4132 function getComposedChildren(node) {
4133 if (!node._composedChildren) {
4134 node._composedChildren = Array.prototype.slice.call(node.childNodes);
4135 }
4136 return node._composedChildren;
4137 }
4138 function addToComposedParent(parent, node, ref_node) {
4139 var children = getComposedChildren(parent);
4140 var i = ref_node ? children.indexOf(ref_node) : -1;
4141 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
4142 var fragChildren = getComposedChildren(node);
4143 for (var j = 0; j < fragChildren.length; j++) {
4144 addNodeToComposedChildren(fragChildren[j], parent, children, i + j);
4145 }
4146 node._composedChildren = null;
4147 } else {
4148 addNodeToComposedChildren(node, parent, children, i);
4149 }
4150 }
4151 function getComposedParent(node) {
4152 return node.__patched ? node._composedParent : node.parentNode;
4153 }
4154 function addNodeToComposedChildren(node, parent, children, i) {
4155 node._composedParent = parent;
4156 children.splice(i >= 0 ? i : children.length, 0, node);
4157 }
4158 function removeFromComposedParent(parent, node) {
4159 node._composedParent = null;
4160 if (parent) {
4161 var children = getComposedChildren(parent);
4162 var i = children.indexOf(node);
4163 if (i >= 0) {
4164 children.splice(i, 1);
4165 }
4166 }
4167 }
4168 function saveLightChildrenIfNeeded(node) {
4169 if (!node._lightChildren) {
4170 var c$ = Array.prototype.slice.call(node.childNodes);
4171 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
4172 child._lightParent = child._lightParent || node;
4173 }
4174 node._lightChildren = c$;
4175 }
4176 }
4177 function hasInsertionPoint(root) {
4178 return Boolean(root && root._insertionPoints.length);
4179 }
4180 var p = Element.prototype;
4181 var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector;
4182 return {
4183 getLightChildren: getLightChildren,
4184 getComposedParent: getComposedParent,
4185 getComposedChildren: getComposedChildren,
4186 removeFromComposedParent: removeFromComposedParent,
4187 saveLightChildrenIfNeeded: saveLightChildrenIfNeeded,
4188 matchesSelector: matchesSelector,
4189 hasInsertionPoint: hasInsertionPoint,
4190 ctor: DomApi,
4191 factory: factory
4192 };
4193 }();
4194 (function () {
4195 Polymer.Base._addFeature({
4196 _prepShady: function () {
4197 this._useContent = this._useContent || Boolean(this._template);
4198 },
4199 _poolContent: function () {
4200 if (this._useContent) {
4201 saveLightChildrenIfNeeded(this);
4202 }
4203 },
4204 _setupRoot: function () {
4205 if (this._useContent) {
4206 this._createLocalRoot();
4207 if (!this.dataHost) {
4208 upgradeLightChildren(this._lightChildren);
4209 }
4210 }
4211 },
4212 _createLocalRoot: function () {
4213 this.shadyRoot = this.root;
4214 this.shadyRoot._distributionClean = false;
4215 this.shadyRoot._isShadyRoot = true;
4216 this.shadyRoot._dirtyRoots = [];
4217 var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasConte nt ? this.shadyRoot.querySelectorAll('content') : [];
4218 saveLightChildrenIfNeeded(this.shadyRoot);
4219 for (var i = 0, c; i < i$.length; i++) {
4220 c = i$[i];
4221 saveLightChildrenIfNeeded(c);
4222 saveLightChildrenIfNeeded(c.parentNode);
4223 }
4224 this.shadyRoot.host = this;
4225 },
4226 get domHost() {
4227 var root = Polymer.dom(this).getOwnerRoot();
4228 return root && root.host;
4229 },
4230 distributeContent: function (updateInsertionPoints) {
4231 if (this.shadyRoot) {
4232 var dom = Polymer.dom(this);
4233 if (updateInsertionPoints) {
4234 dom._updateInsertionPoints(this);
4235 }
4236 var host = getTopDistributingHost(this);
4237 dom._lazyDistribute(host);
4238 }
4239 },
4240 _distributeContent: function () {
4241 if (this._useContent && !this.shadyRoot._distributionClean) {
4242 this._beginDistribute();
4243 this._distributeDirtyRoots();
4244 this._finishDistribute();
4245 }
4246 },
4247 _beginDistribute: function () {
4248 if (this._useContent && hasInsertionPoint(this.shadyRoot)) {
4249 this._resetDistribution();
4250 this._distributePool(this.shadyRoot, this._collectPool());
4251 }
4252 },
4253 _distributeDirtyRoots: function () {
4254 var c$ = this.shadyRoot._dirtyRoots;
4255 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
4256 c._distributeContent();
4257 }
4258 this.shadyRoot._dirtyRoots = [];
4259 },
4260 _finishDistribute: function () {
4261 if (this._useContent) {
4262 this.shadyRoot._distributionClean = true;
4263 if (hasInsertionPoint(this.shadyRoot)) {
4264 this._composeTree();
4265 } else {
4266 if (!this.shadyRoot._hasDistributed) {
4267 this.textContent = '';
4268 this._composedChildren = null;
4269 this.appendChild(this.shadyRoot);
4270 } else {
4271 var children = this._composeNode(this);
4272 this._updateChildNodes(this, children);
4273 }
4274 }
4275 this.shadyRoot._hasDistributed = true;
4276 }
4277 },
4278 elementMatches: function (selector, node) {
4279 node = node || this;
4280 return matchesSelector.call(node, selector);
4281 },
4282 _resetDistribution: function () {
4283 var children = getLightChildren(this);
4284 for (var i = 0; i < children.length; i++) {
4285 var child = children[i];
4286 if (child._destinationInsertionPoints) {
4287 child._destinationInsertionPoints = undefined;
4288 }
4289 if (isInsertionPoint(child)) {
4290 clearDistributedDestinationInsertionPoints(child);
4291 }
4292 }
4293 var root = this.shadyRoot;
4294 var p$ = root._insertionPoints;
4295 for (var j = 0; j < p$.length; j++) {
4296 p$[j]._distributedNodes = [];
4297 }
4298 },
4299 _collectPool: function () {
4300 var pool = [];
4301 var children = getLightChildren(this);
4302 for (var i = 0; i < children.length; i++) {
4303 var child = children[i];
4304 if (isInsertionPoint(child)) {
4305 pool.push.apply(pool, child._distributedNodes);
4306 } else {
4307 pool.push(child);
4308 }
4309 }
4310 return pool;
4311 },
4312 _distributePool: function (node, pool) {
4313 var p$ = node._insertionPoints;
4314 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
4315 this._distributeInsertionPoint(p, pool);
4316 maybeRedistributeParent(p, this);
4317 }
4318 },
4319 _distributeInsertionPoint: function (content, pool) {
4320 var anyDistributed = false;
4321 for (var i = 0, l = pool.length, node; i < l; i++) {
4322 node = pool[i];
4323 if (!node) {
4324 continue;
4325 }
4326 if (this._matchesContentSelect(node, content)) {
4327 distributeNodeInto(node, content);
4328 pool[i] = undefined;
4329 anyDistributed = true;
4330 }
4331 }
4332 if (!anyDistributed) {
4333 var children = getLightChildren(content);
4334 for (var j = 0; j < children.length; j++) {
4335 distributeNodeInto(children[j], content);
4336 }
4337 }
4338 },
4339 _composeTree: function () {
4340 this._updateChildNodes(this, this._composeNode(this));
4341 var p$ = this.shadyRoot._insertionPoints;
4342 for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) {
4343 parent = p._lightParent || p.parentNode;
4344 if (!parent._useContent && parent !== this && parent !== this.shadyRoot) {
4345 this._updateChildNodes(parent, this._composeNode(parent));
4346 }
4347 }
4348 },
4349 _composeNode: function (node) {
4350 var children = [];
4351 var c$ = getLightChildren(node.shadyRoot || node);
4352 for (var i = 0; i < c$.length; i++) {
4353 var child = c$[i];
4354 if (isInsertionPoint(child)) {
4355 var distributedNodes = child._distributedNodes;
4356 for (var j = 0; j < distributedNodes.length; j++) {
4357 var distributedNode = distributedNodes[j];
4358 if (isFinalDestination(child, distributedNode)) {
4359 children.push(distributedNode);
4360 }
4361 }
4362 } else {
4363 children.push(child);
4364 }
4365 }
4366 return children;
4367 },
4368 _updateChildNodes: function (container, children) {
4369 var composed = getComposedChildren(container);
4370 var splices = Polymer.ArraySplice.calculateSplices(children, composed);
4371 for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) {
4372 for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) {
4373 if (getComposedParent(n) === container) {
4374 remove(n);
4375 }
4376 composed.splice(s.index + d, 1);
4377 }
4378 d -= s.addedCount;
4379 }
4380 for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) {
4381 next = composed[s.index];
4382 for (var j = s.index, n; j < s.index + s.addedCount; j++) {
4383 n = children[j];
4384 insertBefore(container, n, next);
4385 composed.splice(j, 0, n);
4386 }
4387 }
4388 ensureComposedParent(container, children);
4389 },
4390 _matchesContentSelect: function (node, contentElement) {
4391 var select = contentElement.getAttribute('select');
4392 if (!select) {
4393 return true;
4394 }
4395 select = select.trim();
4396 if (!select) {
4397 return true;
4398 }
4399 if (!(node instanceof Element)) {
4400 return false;
4401 }
4402 var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/;
4403 if (!validSelectors.test(select)) {
4404 return false;
4405 }
4406 return this.elementMatches(select, node);
4407 },
4408 _elementAdd: function () {
4409 },
4410 _elementRemove: function () {
4411 }
4412 });
4413 var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
4414 var getLightChildren = Polymer.DomApi.getLightChildren;
4415 var matchesSelector = Polymer.DomApi.matchesSelector;
4416 var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint;
4417 var getComposedChildren = Polymer.DomApi.getComposedChildren;
4418 var getComposedParent = Polymer.DomApi.getComposedParent;
4419 var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent;
4420 function distributeNodeInto(child, insertionPoint) {
4421 insertionPoint._distributedNodes.push(child);
4422 var points = child._destinationInsertionPoints;
4423 if (!points) {
4424 child._destinationInsertionPoints = [insertionPoint];
4425 } else {
4426 points.push(insertionPoint);
4427 }
4428 }
4429 function clearDistributedDestinationInsertionPoints(content) {
4430 var e$ = content._distributedNodes;
4431 if (e$) {
4432 for (var i = 0; i < e$.length; i++) {
4433 var d = e$[i]._destinationInsertionPoints;
4434 if (d) {
4435 d.splice(d.indexOf(content) + 1, d.length);
4436 }
4437 }
4438 }
4439 }
4440 function maybeRedistributeParent(content, host) {
4441 var parent = content._lightParent;
4442 if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent. shadyRoot._distributionClean) {
4443 parent.shadyRoot._distributionClean = false;
4444 host.shadyRoot._dirtyRoots.push(parent);
4445 }
4446 }
4447 function isFinalDestination(insertionPoint, node) {
4448 var points = node._destinationInsertionPoints;
4449 return points && points[points.length - 1] === insertionPoint;
4450 }
4451 function isInsertionPoint(node) {
4452 return node.localName == 'content';
4453 }
4454 var nativeInsertBefore = Element.prototype.insertBefore;
4455 var nativeRemoveChild = Element.prototype.removeChild;
4456 function insertBefore(parentNode, newChild, refChild) {
4457 var newChildParent = getComposedParent(newChild);
4458 if (newChildParent !== parentNode) {
4459 removeFromComposedParent(newChildParent, newChild);
4460 }
4461 remove(newChild);
4462 nativeInsertBefore.call(parentNode, newChild, refChild || null);
4463 newChild._composedParent = parentNode;
4464 }
4465 function remove(node) {
4466 var parentNode = getComposedParent(node);
4467 if (parentNode) {
4468 node._composedParent = null;
4469 nativeRemoveChild.call(parentNode, node);
4470 }
4471 }
4472 function ensureComposedParent(parent, children) {
4473 for (var i = 0, n; i < children.length; i++) {
4474 children[i]._composedParent = parent;
4475 }
4476 }
4477 function getTopDistributingHost(host) {
4478 while (host && hostNeedsRedistribution(host)) {
4479 host = host.domHost;
4480 }
4481 return host;
4482 }
4483 function hostNeedsRedistribution(host) {
4484 var c$ = Polymer.dom(host).children;
4485 for (var i = 0, c; i < c$.length; i++) {
4486 c = c$[i];
4487 if (c.localName === 'content') {
4488 return host.domHost;
4489 }
4490 }
4491 }
4492 var needsUpgrade = window.CustomElements && !CustomElements.useNative;
4493 function upgradeLightChildren(children) {
4494 if (needsUpgrade && children) {
4495 for (var i = 0; i < children.length; i++) {
4496 CustomElements.upgrade(children[i]);
4497 }
4498 }
4499 }
4500 }());
4501 if (Polymer.Settings.useShadow) {
4502 Polymer.Base._addFeature({
4503 _poolContent: function () {
4504 },
4505 _beginDistribute: function () {
4506 },
4507 distributeContent: function () {
4508 },
4509 _distributeContent: function () {
4510 },
4511 _finishDistribute: function () {
4512 },
4513 _createLocalRoot: function () {
4514 this.createShadowRoot();
4515 this.shadowRoot.appendChild(this.root);
4516 this.root = this.shadowRoot;
4517 }
4518 });
4519 }
4520 Polymer.DomModule = document.createElement('dom-module');
4521 Polymer.Base._addFeature({
4522 _registerFeatures: function () {
4523 this._prepIs();
4524 this._prepAttributes();
4525 this._prepBehaviors();
4526 this._prepConstructor();
4527 this._prepTemplate();
4528 this._prepShady();
4529 },
4530 _prepBehavior: function (b) {
4531 this._addHostAttributes(b.hostAttributes);
4532 },
4533 _initFeatures: function () {
4534 this._poolContent();
4535 this._pushHost();
4536 this._stampTemplate();
4537 this._popHost();
4538 this._marshalHostAttributes();
4539 this._setupDebouncers();
4540 this._marshalBehaviors();
4541 this._tryReady();
4542 },
4543 _marshalBehavior: function (b) {
4544 }
4545 });
4546 Polymer.nar = [];
4547 Polymer.Annotations = {
4548 parseAnnotations: function (template) {
4549 var list = [];
4550 var content = template._content || template.content;
4551 this._parseNodeAnnotations(content, list);
4552 return list;
4553 },
4554 _parseNodeAnnotations: function (node, list) {
4555 return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, li st) : this._parseElementAnnotations(node, list);
4556 },
4557 _testEscape: function (value) {
4558 var escape = value.slice(0, 2);
4559 if (escape === '{{' || escape === '[[') {
4560 return escape;
4561 }
4562 },
4563 _parseTextNodeAnnotation: function (node, list) {
4564 var v = node.textContent;
4565 var escape = this._testEscape(v);
4566 if (escape) {
4567 node.textContent = ' ';
4568 var annote = {
4569 bindings: [{
4570 kind: 'text',
4571 mode: escape[0],
4572 value: v.slice(2, -2).trim()
4573 }]
4574 };
4575 list.push(annote);
4576 return annote;
4577 }
4578 },
4579 _parseElementAnnotations: function (element, list) {
4580 var annote = {
4581 bindings: [],
4582 events: []
4583 };
4584 if (element.localName === 'content') {
4585 list._hasContent = true;
4586 }
4587 this._parseChildNodesAnnotations(element, annote, list);
4588 if (element.attributes) {
4589 this._parseNodeAttributeAnnotations(element, annote, list);
4590 if (this.prepElement) {
4591 this.prepElement(element);
4592 }
4593 }
4594 if (annote.bindings.length || annote.events.length || annote.id) {
4595 list.push(annote);
4596 }
4597 return annote;
4598 },
4599 _parseChildNodesAnnotations: function (root, annote, list, callback) {
4600 if (root.firstChild) {
4601 for (var i = 0, node = root.firstChild; node; node = node.nextSibling, i++) {
4602 if (node.localName === 'template' && !node.hasAttribute('preserve-content')) {
4603 this._parseTemplate(node, i, list, annote);
4604 }
4605 if (node.nodeType === Node.TEXT_NODE) {
4606 var n = node.nextSibling;
4607 while (n && n.nodeType === Node.TEXT_NODE) {
4608 node.textContent += n.textContent;
4609 root.removeChild(n);
4610 n = n.nextSibling;
4611 }
4612 }
4613 var childAnnotation = this._parseNodeAnnotations(node, list, callback);
4614 if (childAnnotation) {
4615 childAnnotation.parent = annote;
4616 childAnnotation.index = i;
4617 }
4618 }
4619 }
4620 },
4621 _parseTemplate: function (node, index, list, parent) {
4622 var content = document.createDocumentFragment();
4623 content._notes = this.parseAnnotations(node);
4624 content.appendChild(node.content);
4625 list.push({
4626 bindings: Polymer.nar,
4627 events: Polymer.nar,
4628 templateContent: content,
4629 parent: parent,
4630 index: index
4631 });
4632 },
4633 _parseNodeAttributeAnnotations: function (node, annotation) {
4634 for (var i = node.attributes.length - 1, a; a = node.attributes[i]; i--) {
4635 var n = a.name, v = a.value;
4636 if (n === 'id' && !this._testEscape(v)) {
4637 annotation.id = v;
4638 } else if (n.slice(0, 3) === 'on-') {
4639 node.removeAttribute(n);
4640 annotation.events.push({
4641 name: n.slice(3),
4642 value: v
4643 });
4644 } else {
4645 var b = this._parseNodeAttributeAnnotation(node, n, v);
4646 if (b) {
4647 annotation.bindings.push(b);
4648 }
4649 }
4650 }
4651 },
4652 _parseNodeAttributeAnnotation: function (node, n, v) {
4653 var escape = this._testEscape(v);
4654 if (escape) {
4655 var customEvent;
4656 var name = n;
4657 var mode = escape[0];
4658 v = v.slice(2, -2).trim();
4659 var not = false;
4660 if (v[0] == '!') {
4661 v = v.substring(1);
4662 not = true;
4663 }
4664 var kind = 'property';
4665 if (n[n.length - 1] == '$') {
4666 name = n.slice(0, -1);
4667 kind = 'attribute';
4668 }
4669 var notifyEvent, colon;
4670 if (mode == '{' && (colon = v.indexOf('::')) > 0) {
4671 notifyEvent = v.substring(colon + 2);
4672 v = v.substring(0, colon);
4673 customEvent = true;
4674 }
4675 if (node.localName == 'input' && n == 'value') {
4676 node.setAttribute(n, '');
4677 }
4678 node.removeAttribute(n);
4679 if (kind === 'property') {
4680 name = Polymer.CaseMap.dashToCamelCase(name);
4681 }
4682 return {
4683 kind: kind,
4684 mode: mode,
4685 name: name,
4686 value: v,
4687 negate: not,
4688 event: notifyEvent,
4689 customEvent: customEvent
4690 };
4691 }
4692 },
4693 _localSubTree: function (node, host) {
4694 return node === host ? node.childNodes : node._lightChildren || node.childNodes;
4695 },
4696 findAnnotatedNode: function (root, annote) {
4697 var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote .parent);
4698 return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.i ndex];
4699 }
4700 };
4701 (function () {
4702 function resolveCss(cssText, ownerDocument) {
4703 return cssText.replace(CSS_URL_RX, function (m, pre, url, post) {
4704 return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + po st;
4705 });
4706 }
4707 function resolveAttrs(element, ownerDocument) {
4708 for (var name in URL_ATTRS) {
4709 var a$ = URL_ATTRS[name];
4710 for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) {
4711 if (name === '*' || element.localName === name) {
4712 at = element.attributes[a];
4713 v = at && at.value;
4714 if (v && v.search(BINDING_RX) < 0) {
4715 at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocume nt);
4716 }
4717 }
4718 }
4719 }
4720 }
4721 function resolve(url, ownerDocument) {
4722 if (url && url[0] === '#') {
4723 return url;
4724 }
4725 var resolver = getUrlResolver(ownerDocument);
4726 resolver.href = url;
4727 return resolver.href || url;
4728 }
4729 var tempDoc;
4730 var tempDocBase;
4731 function resolveUrl(url, baseUri) {
4732 if (!tempDoc) {
4733 tempDoc = document.implementation.createHTMLDocument('temp');
4734 tempDocBase = tempDoc.createElement('base');
4735 tempDoc.head.appendChild(tempDocBase);
4736 }
4737 tempDocBase.href = baseUri;
4738 return resolve(url, tempDoc);
4739 }
4740 function getUrlResolver(ownerDocument) {
4741 return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocume nt.createElement('a'));
4742 }
4743 var CSS_URL_RX = /(url\()([^)]*)(\))/g;
4744 var URL_ATTRS = {
4745 '*': [
4746 'href',
4747 'src',
4748 'style',
4749 'url'
4750 ],
4751 form: ['action']
4752 };
4753 var BINDING_RX = /\{\{|\[\[/;
4754 Polymer.ResolveUrl = {
4755 resolveCss: resolveCss,
4756 resolveAttrs: resolveAttrs,
4757 resolveUrl: resolveUrl
4758 };
4759 }());
4760 Polymer.Base._addFeature({
4761 _prepAnnotations: function () {
4762 if (!this._template) {
4763 this._notes = [];
4764 } else {
4765 Polymer.Annotations.prepElement = this._prepElement.bind(this);
4766 if (this._template._content && this._template._content._notes) {
4767 this._notes = this._template._content._notes;
4768 } else {
4769 this._notes = Polymer.Annotations.parseAnnotations(this._template);
4770 }
4771 this._processAnnotations(this._notes);
4772 Polymer.Annotations.prepElement = null;
4773 }
4774 },
4775 _processAnnotations: function (notes) {
4776 for (var i = 0; i < notes.length; i++) {
4777 var note = notes[i];
4778 for (var j = 0; j < note.bindings.length; j++) {
4779 var b = note.bindings[j];
4780 b.signature = this._parseMethod(b.value);
4781 if (!b.signature) {
4782 b.model = this._modelForPath(b.value);
4783 }
4784 }
4785 if (note.templateContent) {
4786 this._processAnnotations(note.templateContent._notes);
4787 var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(n ote.templateContent._notes);
4788 var bindings = [];
4789 for (var prop in pp) {
4790 bindings.push({
4791 index: note.index,
4792 kind: 'property',
4793 mode: '{',
4794 name: '_parent_' + prop,
4795 model: prop,
4796 value: prop
4797 });
4798 }
4799 note.bindings = note.bindings.concat(bindings);
4800 }
4801 }
4802 },
4803 _discoverTemplateParentProps: function (notes) {
4804 var pp = {};
4805 notes.forEach(function (n) {
4806 n.bindings.forEach(function (b) {
4807 if (b.signature) {
4808 var args = b.signature.args;
4809 for (var k = 0; k < args.length; k++) {
4810 pp[args[k].model] = true;
4811 }
4812 } else {
4813 pp[b.model] = true;
4814 }
4815 });
4816 if (n.templateContent) {
4817 var tpp = n.templateContent._parentProps;
4818 Polymer.Base.mixin(pp, tpp);
4819 }
4820 });
4821 return pp;
4822 },
4823 _prepElement: function (element) {
4824 Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument);
4825 },
4826 _findAnnotatedNode: Polymer.Annotations.findAnnotatedNode,
4827 _marshalAnnotationReferences: function () {
4828 if (this._template) {
4829 this._marshalIdNodes();
4830 this._marshalAnnotatedNodes();
4831 this._marshalAnnotatedListeners();
4832 }
4833 },
4834 _configureAnnotationReferences: function () {
4835 this._configureTemplateContent();
4836 },
4837 _configureTemplateContent: function () {
4838 this._notes.forEach(function (note, i) {
4839 if (note.templateContent) {
4840 this._nodes[i]._content = note.templateContent;
4841 }
4842 }, this);
4843 },
4844 _marshalIdNodes: function () {
4845 this.$ = {};
4846 this._notes.forEach(function (a) {
4847 if (a.id) {
4848 this.$[a.id] = this._findAnnotatedNode(this.root, a);
4849 }
4850 }, this);
4851 },
4852 _marshalAnnotatedNodes: function () {
4853 if (this._nodes) {
4854 this._nodes = this._nodes.map(function (a) {
4855 return this._findAnnotatedNode(this.root, a);
4856 }, this);
4857 }
4858 },
4859 _marshalAnnotatedListeners: function () {
4860 this._notes.forEach(function (a) {
4861 if (a.events && a.events.length) {
4862 var node = this._findAnnotatedNode(this.root, a);
4863 a.events.forEach(function (e) {
4864 this.listen(node, e.name, e.value);
4865 }, this);
4866 }
4867 }, this);
4868 }
4869 });
4870 Polymer.Base._addFeature({
4871 listeners: {},
4872 _listenListeners: function (listeners) {
4873 var node, name, key;
4874 for (key in listeners) {
4875 if (key.indexOf('.') < 0) {
4876 node = this;
4877 name = key;
4878 } else {
4879 name = key.split('.');
4880 node = this.$[name[0]];
4881 name = name[1];
4882 }
4883 this.listen(node, name, listeners[key]);
4884 }
4885 },
4886 listen: function (node, eventName, methodName) {
4887 this._listen(node, eventName, this._createEventHandler(node, eventName, methodNa me));
4888 },
4889 _boundListenerKey: function (eventName, methodName) {
4890 return eventName + ':' + methodName;
4891 },
4892 _recordEventHandler: function (host, eventName, target, methodName, handler) {
4893 var hbl = host.__boundListeners;
4894 if (!hbl) {
4895 hbl = host.__boundListeners = new WeakMap();
4896 }
4897 var bl = hbl.get(target);
4898 if (!bl) {
4899 bl = {};
4900 hbl.set(target, bl);
4901 }
4902 var key = this._boundListenerKey(eventName, methodName);
4903 bl[key] = handler;
4904 },
4905 _recallEventHandler: function (host, eventName, target, methodName) {
4906 var hbl = host.__boundListeners;
4907 if (!hbl) {
4908 return;
4909 }
4910 var bl = hbl.get(target);
4911 if (!bl) {
4912 return;
4913 }
4914 var key = this._boundListenerKey(eventName, methodName);
4915 return bl[key];
4916 },
4917 _createEventHandler: function (node, eventName, methodName) {
4918 var host = this;
4919 var handler = function (e) {
4920 if (host[methodName]) {
4921 host[methodName](e, e.detail);
4922 } else {
4923 host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined'));
4924 }
4925 };
4926 this._recordEventHandler(host, eventName, node, methodName, handler);
4927 return handler;
4928 },
4929 unlisten: function (node, eventName, methodName) {
4930 var handler = this._recallEventHandler(this, eventName, node, methodName);
4931 if (handler) {
4932 this._unlisten(node, eventName, handler);
4933 }
4934 },
4935 _listen: function (node, eventName, handler) {
4936 node.addEventListener(eventName, handler);
4937 },
4938 _unlisten: function (node, eventName, handler) {
4939 node.removeEventListener(eventName, handler);
4940 }
4941 });
4942 (function () {
4943 'use strict';
4944 var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
4945 var GESTURE_KEY = '__polymerGestures';
4946 var HANDLED_OBJ = '__polymerGesturesHandled';
4947 var TOUCH_ACTION = '__polymerGesturesTouchAction';
4948 var TAP_DISTANCE = 25;
4949 var TRACK_DISTANCE = 5;
4950 var TRACK_LENGTH = 2;
4951 var MOUSE_TIMEOUT = 2500;
4952 var MOUSE_EVENTS = [
4953 'mousedown',
4954 'mousemove',
4955 'mouseup',
4956 'click'
4957 ];
4958 var MOUSE_WHICH_TO_BUTTONS = [
4959 0,
4960 1,
4961 4,
4962 2
4963 ];
4964 var MOUSE_HAS_BUTTONS = function () {
4965 try {
4966 return new MouseEvent('test', { buttons: 1 }).buttons === 1;
4967 } catch (e) {
4968 return false;
4969 }
4970 }();
4971 var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
4972 var mouseCanceller = function (mouseEvent) {
4973 mouseEvent[HANDLED_OBJ] = { skip: true };
4974 if (mouseEvent.type === 'click') {
4975 var path = Polymer.dom(mouseEvent).path;
4976 for (var i = 0; i < path.length; i++) {
4977 if (path[i] === POINTERSTATE.mouse.target) {
4978 return;
4979 }
4980 }
4981 mouseEvent.preventDefault();
4982 mouseEvent.stopPropagation();
4983 }
4984 };
4985 function setupTeardownMouseCanceller(setup) {
4986 for (var i = 0, en; i < MOUSE_EVENTS.length; i++) {
4987 en = MOUSE_EVENTS[i];
4988 if (setup) {
4989 document.addEventListener(en, mouseCanceller, true);
4990 } else {
4991 document.removeEventListener(en, mouseCanceller, true);
4992 }
4993 }
4994 }
4995 function ignoreMouse() {
4996 if (IS_TOUCH_ONLY) {
4997 return;
4998 }
4999 if (!POINTERSTATE.mouse.mouseIgnoreJob) {
5000 setupTeardownMouseCanceller(true);
5001 }
5002 var unset = function () {
5003 setupTeardownMouseCanceller();
5004 POINTERSTATE.mouse.target = null;
5005 POINTERSTATE.mouse.mouseIgnoreJob = null;
5006 };
5007 POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgn oreJob, unset, MOUSE_TIMEOUT);
5008 }
5009 function hasLeftMouseButton(ev) {
5010 var type = ev.type;
5011 if (MOUSE_EVENTS.indexOf(type) === -1) {
5012 return false;
5013 }
5014 if (type === 'mousemove') {
5015 var buttons = ev.buttons === undefined ? 1 : ev.buttons;
5016 if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) {
5017 buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
5018 }
5019 return Boolean(buttons & 1);
5020 } else {
5021 var button = ev.button === undefined ? 0 : ev.button;
5022 return button === 0;
5023 }
5024 }
5025 function isSyntheticClick(ev) {
5026 if (ev.type === 'click') {
5027 if (ev.detail === 0) {
5028 return true;
5029 }
5030 var t = Gestures.findOriginalTarget(ev);
5031 var bcr = t.getBoundingClientRect();
5032 var x = ev.pageX, y = ev.pageY;
5033 return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom));
5034 }
5035 return false;
5036 }
5037 var POINTERSTATE = {
5038 mouse: {
5039 target: null,
5040 mouseIgnoreJob: null
5041 },
5042 touch: {
5043 x: 0,
5044 y: 0,
5045 id: -1,
5046 scrollDecided: false
5047 }
5048 };
5049 function firstTouchAction(ev) {
5050 var path = Polymer.dom(ev).path;
5051 var ta = 'auto';
5052 for (var i = 0, n; i < path.length; i++) {
5053 n = path[i];
5054 if (n[TOUCH_ACTION]) {
5055 ta = n[TOUCH_ACTION];
5056 break;
5057 }
5058 }
5059 return ta;
5060 }
5061 function trackDocument(stateObj, movefn, upfn) {
5062 stateObj.movefn = movefn;
5063 stateObj.upfn = upfn;
5064 document.addEventListener('mousemove', movefn);
5065 document.addEventListener('mouseup', upfn);
5066 }
5067 function untrackDocument(stateObj) {
5068 document.removeEventListener('mousemove', stateObj.movefn);
5069 document.removeEventListener('mouseup', stateObj.upfn);
5070 }
5071 var Gestures = {
5072 gestures: {},
5073 recognizers: [],
5074 deepTargetFind: function (x, y) {
5075 var node = document.elementFromPoint(x, y);
5076 var next = node;
5077 while (next && next.shadowRoot) {
5078 next = next.shadowRoot.elementFromPoint(x, y);
5079 if (next) {
5080 node = next;
5081 }
5082 }
5083 return node;
5084 },
5085 findOriginalTarget: function (ev) {
5086 if (ev.path) {
5087 return ev.path[0];
5088 }
5089 return ev.target;
5090 },
5091 handleNative: function (ev) {
5092 var handled;
5093 var type = ev.type;
5094 var node = ev.currentTarget;
5095 var gobj = node[GESTURE_KEY];
5096 var gs = gobj[type];
5097 if (!gs) {
5098 return;
5099 }
5100 if (!ev[HANDLED_OBJ]) {
5101 ev[HANDLED_OBJ] = {};
5102 if (type.slice(0, 5) === 'touch') {
5103 var t = ev.changedTouches[0];
5104 if (type === 'touchstart') {
5105 if (ev.touches.length === 1) {
5106 POINTERSTATE.touch.id = t.identifier;
5107 }
5108 }
5109 if (POINTERSTATE.touch.id !== t.identifier) {
5110 return;
5111 }
5112 if (!HAS_NATIVE_TA) {
5113 if (type === 'touchstart' || type === 'touchmove') {
5114 Gestures.handleTouchAction(ev);
5115 }
5116 }
5117 if (type === 'touchend') {
5118 POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget;
5119 ignoreMouse(true);
5120 }
5121 }
5122 }
5123 handled = ev[HANDLED_OBJ];
5124 if (handled.skip) {
5125 return;
5126 }
5127 var recognizers = Gestures.recognizers;
5128 for (var i = 0, r; i < recognizers.length; i++) {
5129 r = recognizers[i];
5130 if (gs[r.name] && !handled[r.name]) {
5131 if (r.flow && r.flow.start.indexOf(ev.type) > -1) {
5132 if (r.reset) {
5133 r.reset();
5134 }
5135 }
5136 }
5137 }
5138 for (var i = 0, r; i < recognizers.length; i++) {
5139 r = recognizers[i];
5140 if (gs[r.name] && !handled[r.name]) {
5141 handled[r.name] = true;
5142 r[type](ev);
5143 }
5144 }
5145 },
5146 handleTouchAction: function (ev) {
5147 var t = ev.changedTouches[0];
5148 var type = ev.type;
5149 if (type === 'touchstart') {
5150 POINTERSTATE.touch.x = t.clientX;
5151 POINTERSTATE.touch.y = t.clientY;
5152 POINTERSTATE.touch.scrollDecided = false;
5153 } else if (type === 'touchmove') {
5154 if (POINTERSTATE.touch.scrollDecided) {
5155 return;
5156 }
5157 POINTERSTATE.touch.scrollDecided = true;
5158 var ta = firstTouchAction(ev);
5159 var prevent = false;
5160 var dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
5161 var dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
5162 if (!ev.cancelable) {
5163 } else if (ta === 'none') {
5164 prevent = true;
5165 } else if (ta === 'pan-x') {
5166 prevent = dy > dx;
5167 } else if (ta === 'pan-y') {
5168 prevent = dx > dy;
5169 }
5170 if (prevent) {
5171 ev.preventDefault();
5172 } else {
5173 Gestures.prevent('track');
5174 }
5175 }
5176 },
5177 add: function (node, evType, handler) {
5178 var recognizer = this.gestures[evType];
5179 var deps = recognizer.deps;
5180 var name = recognizer.name;
5181 var gobj = node[GESTURE_KEY];
5182 if (!gobj) {
5183 node[GESTURE_KEY] = gobj = {};
5184 }
5185 for (var i = 0, dep, gd; i < deps.length; i++) {
5186 dep = deps[i];
5187 if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) {
5188 continue;
5189 }
5190 gd = gobj[dep];
5191 if (!gd) {
5192 gobj[dep] = gd = { _count: 0 };
5193 }
5194 if (gd._count === 0) {
5195 node.addEventListener(dep, this.handleNative);
5196 }
5197 gd[name] = (gd[name] || 0) + 1;
5198 gd._count = (gd._count || 0) + 1;
5199 }
5200 node.addEventListener(evType, handler);
5201 if (recognizer.touchAction) {
5202 this.setTouchAction(node, recognizer.touchAction);
5203 }
5204 },
5205 remove: function (node, evType, handler) {
5206 var recognizer = this.gestures[evType];
5207 var deps = recognizer.deps;
5208 var name = recognizer.name;
5209 var gobj = node[GESTURE_KEY];
5210 if (gobj) {
5211 for (var i = 0, dep, gd; i < deps.length; i++) {
5212 dep = deps[i];
5213 gd = gobj[dep];
5214 if (gd && gd[name]) {
5215 gd[name] = (gd[name] || 1) - 1;
5216 gd._count = (gd._count || 1) - 1;
5217 if (gd._count === 0) {
5218 node.removeEventListener(dep, this.handleNative);
5219 }
5220 }
5221 }
5222 }
5223 node.removeEventListener(evType, handler);
5224 },
5225 register: function (recog) {
5226 this.recognizers.push(recog);
5227 for (var i = 0; i < recog.emits.length; i++) {
5228 this.gestures[recog.emits[i]] = recog;
5229 }
5230 },
5231 findRecognizerByEvent: function (evName) {
5232 for (var i = 0, r; i < this.recognizers.length; i++) {
5233 r = this.recognizers[i];
5234 for (var j = 0, n; j < r.emits.length; j++) {
5235 n = r.emits[j];
5236 if (n === evName) {
5237 return r;
5238 }
5239 }
5240 }
5241 return null;
5242 },
5243 setTouchAction: function (node, value) {
5244 if (HAS_NATIVE_TA) {
5245 node.style.touchAction = value;
5246 }
5247 node[TOUCH_ACTION] = value;
5248 },
5249 fire: function (target, type, detail) {
5250 var ev = Polymer.Base.fire(type, detail, {
5251 node: target,
5252 bubbles: true,
5253 cancelable: true
5254 });
5255 if (ev.defaultPrevented) {
5256 var se = detail.sourceEvent;
5257 if (se && se.preventDefault) {
5258 se.preventDefault();
5259 }
5260 }
5261 },
5262 prevent: function (evName) {
5263 var recognizer = this.findRecognizerByEvent(evName);
5264 if (recognizer.info) {
5265 recognizer.info.prevent = true;
5266 }
5267 }
5268 };
5269 Gestures.register({
5270 name: 'downup',
5271 deps: [
5272 'mousedown',
5273 'touchstart',
5274 'touchend'
5275 ],
5276 flow: {
5277 start: [
5278 'mousedown',
5279 'touchstart'
5280 ],
5281 end: [
5282 'mouseup',
5283 'touchend'
5284 ]
5285 },
5286 emits: [
5287 'down',
5288 'up'
5289 ],
5290 info: {
5291 movefn: function () {
5292 },
5293 upfn: function () {
5294 }
5295 },
5296 reset: function () {
5297 untrackDocument(this.info);
5298 },
5299 mousedown: function (e) {
5300 if (!hasLeftMouseButton(e)) {
5301 return;
5302 }
5303 var t = Gestures.findOriginalTarget(e);
5304 var self = this;
5305 var movefn = function movefn(e) {
5306 if (!hasLeftMouseButton(e)) {
5307 self.fire('up', t, e);
5308 untrackDocument(self.info);
5309 }
5310 };
5311 var upfn = function upfn(e) {
5312 if (hasLeftMouseButton(e)) {
5313 self.fire('up', t, e);
5314 }
5315 untrackDocument(self.info);
5316 };
5317 trackDocument(this.info, movefn, upfn);
5318 this.fire('down', t, e);
5319 },
5320 touchstart: function (e) {
5321 this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]);
5322 },
5323 touchend: function (e) {
5324 this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]);
5325 },
5326 fire: function (type, target, event) {
5327 var self = this;
5328 Gestures.fire(target, type, {
5329 x: event.clientX,
5330 y: event.clientY,
5331 sourceEvent: event,
5332 prevent: Gestures.prevent.bind(Gestures)
5333 });
5334 }
5335 });
5336 Gestures.register({
5337 name: 'track',
5338 touchAction: 'none',
5339 deps: [
5340 'mousedown',
5341 'touchstart',
5342 'touchmove',
5343 'touchend'
5344 ],
5345 flow: {
5346 start: [
5347 'mousedown',
5348 'touchstart'
5349 ],
5350 end: [
5351 'mouseup',
5352 'touchend'
5353 ]
5354 },
5355 emits: ['track'],
5356 info: {
5357 x: 0,
5358 y: 0,
5359 state: 'start',
5360 started: false,
5361 moves: [],
5362 addMove: function (move) {
5363 if (this.moves.length > TRACK_LENGTH) {
5364 this.moves.shift();
5365 }
5366 this.moves.push(move);
5367 },
5368 movefn: function () {
5369 },
5370 upfn: function () {
5371 },
5372 prevent: false
5373 },
5374 reset: function () {
5375 this.info.state = 'start';
5376 this.info.started = false;
5377 this.info.moves = [];
5378 this.info.x = 0;
5379 this.info.y = 0;
5380 this.info.prevent = false;
5381 untrackDocument(this.info);
5382 },
5383 hasMovedEnough: function (x, y) {
5384 if (this.info.prevent) {
5385 return false;
5386 }
5387 if (this.info.started) {
5388 return true;
5389 }
5390 var dx = Math.abs(this.info.x - x);
5391 var dy = Math.abs(this.info.y - y);
5392 return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE;
5393 },
5394 mousedown: function (e) {
5395 if (!hasLeftMouseButton(e)) {
5396 return;
5397 }
5398 var t = Gestures.findOriginalTarget(e);
5399 var self = this;
5400 var movefn = function movefn(e) {
5401 var x = e.clientX, y = e.clientY;
5402 if (self.hasMovedEnough(x, y)) {
5403 self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : ' start';
5404 self.info.addMove({
5405 x: x,
5406 y: y
5407 });
5408 if (!hasLeftMouseButton(e)) {
5409 self.info.state = 'end';
5410 untrackDocument(self.info);
5411 }
5412 self.fire(t, e);
5413 self.info.started = true;
5414 }
5415 };
5416 var upfn = function upfn(e) {
5417 if (self.info.started) {
5418 Gestures.prevent('tap');
5419 movefn(e);
5420 }
5421 untrackDocument(self.info);
5422 };
5423 trackDocument(this.info, movefn, upfn);
5424 this.info.x = e.clientX;
5425 this.info.y = e.clientY;
5426 },
5427 touchstart: function (e) {
5428 var ct = e.changedTouches[0];
5429 this.info.x = ct.clientX;
5430 this.info.y = ct.clientY;
5431 },
5432 touchmove: function (e) {
5433 var t = Gestures.findOriginalTarget(e);
5434 var ct = e.changedTouches[0];
5435 var x = ct.clientX, y = ct.clientY;
5436 if (this.hasMovedEnough(x, y)) {
5437 this.info.addMove({
5438 x: x,
5439 y: y
5440 });
5441 this.fire(t, ct);
5442 this.info.state = 'track';
5443 this.info.started = true;
5444 }
5445 },
5446 touchend: function (e) {
5447 var t = Gestures.findOriginalTarget(e);
5448 var ct = e.changedTouches[0];
5449 if (this.info.started) {
5450 Gestures.prevent('tap');
5451 this.info.state = 'end';
5452 this.info.addMove({
5453 x: ct.clientX,
5454 y: ct.clientY
5455 });
5456 this.fire(t, ct);
5457 }
5458 },
5459 fire: function (target, touch) {
5460 var secondlast = this.info.moves[this.info.moves.length - 2];
5461 var lastmove = this.info.moves[this.info.moves.length - 1];
5462 var dx = lastmove.x - this.info.x;
5463 var dy = lastmove.y - this.info.y;
5464 var ddx, ddy = 0;
5465 if (secondlast) {
5466 ddx = lastmove.x - secondlast.x;
5467 ddy = lastmove.y - secondlast.y;
5468 }
5469 return Gestures.fire(target, 'track', {
5470 state: this.info.state,
5471 x: touch.clientX,
5472 y: touch.clientY,
5473 dx: dx,
5474 dy: dy,
5475 ddx: ddx,
5476 ddy: ddy,
5477 sourceEvent: touch,
5478 hover: function () {
5479 return Gestures.deepTargetFind(touch.clientX, touch.clientY);
5480 }
5481 });
5482 }
5483 });
5484 Gestures.register({
5485 name: 'tap',
5486 deps: [
5487 'mousedown',
5488 'click',
5489 'touchstart',
5490 'touchend'
5491 ],
5492 flow: {
5493 start: [
5494 'mousedown',
5495 'touchstart'
5496 ],
5497 end: [
5498 'click',
5499 'touchend'
5500 ]
5501 },
5502 emits: ['tap'],
5503 info: {
5504 x: NaN,
5505 y: NaN,
5506 prevent: false
5507 },
5508 reset: function () {
5509 this.info.x = NaN;
5510 this.info.y = NaN;
5511 this.info.prevent = false;
5512 },
5513 save: function (e) {
5514 this.info.x = e.clientX;
5515 this.info.y = e.clientY;
5516 },
5517 mousedown: function (e) {
5518 if (hasLeftMouseButton(e)) {
5519 this.save(e);
5520 }
5521 },
5522 click: function (e) {
5523 if (hasLeftMouseButton(e)) {
5524 this.forward(e);
5525 }
5526 },
5527 touchstart: function (e) {
5528 this.save(e.changedTouches[0]);
5529 },
5530 touchend: function (e) {
5531 this.forward(e.changedTouches[0]);
5532 },
5533 forward: function (e) {
5534 var dx = Math.abs(e.clientX - this.info.x);
5535 var dy = Math.abs(e.clientY - this.info.y);
5536 var t = Gestures.findOriginalTarget(e);
5537 if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSynt heticClick(e)) {
5538 if (!this.info.prevent) {
5539 Gestures.fire(t, 'tap', {
5540 x: e.clientX,
5541 y: e.clientY,
5542 sourceEvent: e
5543 });
5544 }
5545 }
5546 }
5547 });
5548 var DIRECTION_MAP = {
5549 x: 'pan-x',
5550 y: 'pan-y',
5551 none: 'none',
5552 all: 'auto'
5553 };
5554 Polymer.Base._addFeature({
5555 _listen: function (node, eventName, handler) {
5556 if (Gestures.gestures[eventName]) {
5557 Gestures.add(node, eventName, handler);
5558 } else {
5559 node.addEventListener(eventName, handler);
5560 }
5561 },
5562 _unlisten: function (node, eventName, handler) {
5563 if (Gestures.gestures[eventName]) {
5564 Gestures.remove(node, eventName, handler);
5565 } else {
5566 node.removeEventListener(eventName, handler);
5567 }
5568 },
5569 setScrollDirection: function (direction, node) {
5570 node = node || this;
5571 Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto');
5572 }
5573 });
5574 Polymer.Gestures = Gestures;
5575 }());
5576 Polymer.Async = {
5577 _currVal: 0,
5578 _lastVal: 0,
5579 _callbacks: [],
5580 _twiddleContent: 0,
5581 _twiddle: document.createTextNode(''),
5582 run: function (callback, waitTime) {
5583 if (waitTime > 0) {
5584 return ~setTimeout(callback, waitTime);
5585 } else {
5586 this._twiddle.textContent = this._twiddleContent++;
5587 this._callbacks.push(callback);
5588 return this._currVal++;
5589 }
5590 },
5591 cancel: function (handle) {
5592 if (handle < 0) {
5593 clearTimeout(~handle);
5594 } else {
5595 var idx = handle - this._lastVal;
5596 if (idx >= 0) {
5597 if (!this._callbacks[idx]) {
5598 throw 'invalid async handle: ' + handle;
5599 }
5600 this._callbacks[idx] = null;
5601 }
5602 }
5603 },
5604 _atEndOfMicrotask: function () {
5605 var len = this._callbacks.length;
5606 for (var i = 0; i < len; i++) {
5607 var cb = this._callbacks[i];
5608 if (cb) {
5609 try {
5610 cb();
5611 } catch (e) {
5612 i++;
5613 this._callbacks.splice(0, i);
5614 this._lastVal += i;
5615 this._twiddle.textContent = this._twiddleContent++;
5616 throw e;
5617 }
5618 }
5619 }
5620 this._callbacks.splice(0, len);
5621 this._lastVal += len;
5622 }
5623 };
5624 new (window.MutationObserver || JsMutationObserver)(Polymer.Async._atEndOfMicrot ask.bind(Polymer.Async)).observe(Polymer.Async._twiddle, { characterData: true } );
5625 Polymer.Debounce = function () {
5626 var Async = Polymer.Async;
5627 var Debouncer = function (context) {
5628 this.context = context;
5629 this.boundComplete = this.complete.bind(this);
5630 };
5631 Debouncer.prototype = {
5632 go: function (callback, wait) {
5633 var h;
5634 this.finish = function () {
5635 Async.cancel(h);
5636 };
5637 h = Async.run(this.boundComplete, wait);
5638 this.callback = callback;
5639 },
5640 stop: function () {
5641 if (this.finish) {
5642 this.finish();
5643 this.finish = null;
5644 }
5645 },
5646 complete: function () {
5647 if (this.finish) {
5648 this.stop();
5649 this.callback.call(this.context);
5650 }
5651 }
5652 };
5653 function debounce(debouncer, callback, wait) {
5654 if (debouncer) {
5655 debouncer.stop();
5656 } else {
5657 debouncer = new Debouncer(this);
5658 }
5659 debouncer.go(callback, wait);
5660 return debouncer;
5661 }
5662 return debounce;
5663 }();
5664 Polymer.Base._addFeature({
5665 $$: function (slctr) {
5666 return Polymer.dom(this.root).querySelector(slctr);
5667 },
5668 toggleClass: function (name, bool, node) {
5669 node = node || this;
5670 if (arguments.length == 1) {
5671 bool = !node.classList.contains(name);
5672 }
5673 if (bool) {
5674 Polymer.dom(node).classList.add(name);
5675 } else {
5676 Polymer.dom(node).classList.remove(name);
5677 }
5678 },
5679 toggleAttribute: function (name, bool, node) {
5680 node = node || this;
5681 if (arguments.length == 1) {
5682 bool = !node.hasAttribute(name);
5683 }
5684 if (bool) {
5685 Polymer.dom(node).setAttribute(name, '');
5686 } else {
5687 Polymer.dom(node).removeAttribute(name);
5688 }
5689 },
5690 classFollows: function (name, toElement, fromElement) {
5691 if (fromElement) {
5692 Polymer.dom(fromElement).classList.remove(name);
5693 }
5694 if (toElement) {
5695 Polymer.dom(toElement).classList.add(name);
5696 }
5697 },
5698 attributeFollows: function (name, toElement, fromElement) {
5699 if (fromElement) {
5700 Polymer.dom(fromElement).removeAttribute(name);
5701 }
5702 if (toElement) {
5703 Polymer.dom(toElement).setAttribute(name, '');
5704 }
5705 },
5706 getContentChildNodes: function (slctr) {
5707 var content = Polymer.dom(this.root).querySelector(slctr || 'content');
5708 return content ? Polymer.dom(content).getDistributedNodes() : [];
5709 },
5710 getContentChildren: function (slctr) {
5711 return this.getContentChildNodes(slctr).filter(function (n) {
5712 return n.nodeType === Node.ELEMENT_NODE;
5713 });
5714 },
5715 fire: function (type, detail, options) {
5716 options = options || Polymer.nob;
5717 var node = options.node || this;
5718 var detail = detail === null || detail === undefined ? Polymer.nob : detail;
5719 var bubbles = options.bubbles === undefined ? true : options.bubbles;
5720 var cancelable = Boolean(options.cancelable);
5721 var event = new CustomEvent(type, {
5722 bubbles: Boolean(bubbles),
5723 cancelable: cancelable,
5724 detail: detail
5725 });
5726 node.dispatchEvent(event);
5727 return event;
5728 },
5729 async: function (callback, waitTime) {
5730 return Polymer.Async.run(callback.bind(this), waitTime);
5731 },
5732 cancelAsync: function (handle) {
5733 Polymer.Async.cancel(handle);
5734 },
5735 arrayDelete: function (path, item) {
5736 var index;
5737 if (Array.isArray(path)) {
5738 index = path.indexOf(item);
5739 if (index >= 0) {
5740 return path.splice(index, 1);
5741 }
5742 } else {
5743 var arr = this.get(path);
5744 index = arr.indexOf(item);
5745 if (index >= 0) {
5746 return this.splice(path, index, 1);
5747 }
5748 }
5749 },
5750 transform: function (transform, node) {
5751 node = node || this;
5752 node.style.webkitTransform = transform;
5753 node.style.transform = transform;
5754 },
5755 translate3d: function (x, y, z, node) {
5756 node = node || this;
5757 this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node);
5758 },
5759 importHref: function (href, onload, onerror) {
5760 var l = document.createElement('link');
5761 l.rel = 'import';
5762 l.href = href;
5763 if (onload) {
5764 l.onload = onload.bind(this);
5765 }
5766 if (onerror) {
5767 l.onerror = onerror.bind(this);
5768 }
5769 document.head.appendChild(l);
5770 return l;
5771 },
5772 create: function (tag, props) {
5773 var elt = document.createElement(tag);
5774 if (props) {
5775 for (var n in props) {
5776 elt[n] = props[n];
5777 }
5778 }
5779 return elt;
5780 }
5781 });
5782 Polymer.Bind = {
5783 prepareModel: function (model) {
5784 model._propertyEffects = {};
5785 model._bindListeners = [];
5786 Polymer.Base.mixin(model, this._modelApi);
5787 },
5788 _modelApi: {
5789 _notifyChange: function (property) {
5790 var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed';
5791 Polymer.Base.fire(eventName, { value: this[property] }, {
5792 bubbles: false,
5793 node: this
5794 });
5795 },
5796 _propertySetter: function (property, value, effects, fromAbove) {
5797 var old = this.__data__[property];
5798 if (old !== value && (old === old || value === value)) {
5799 this.__data__[property] = value;
5800 if (typeof value == 'object') {
5801 this._clearPath(property);
5802 }
5803 if (this._propertyChanged) {
5804 this._propertyChanged(property, value, old);
5805 }
5806 if (effects) {
5807 this._effectEffects(property, value, effects, old, fromAbove);
5808 }
5809 }
5810 return old;
5811 },
5812 __setProperty: function (property, value, quiet, node) {
5813 node = node || this;
5814 var effects = node._propertyEffects && node._propertyEffects[property];
5815 if (effects) {
5816 node._propertySetter(property, value, effects, quiet);
5817 } else {
5818 node[property] = value;
5819 }
5820 },
5821 _effectEffects: function (property, value, effects, old, fromAbove) {
5822 effects.forEach(function (fx) {
5823 var fn = Polymer.Bind['_' + fx.kind + 'Effect'];
5824 if (fn) {
5825 fn.call(this, property, value, fx.effect, old, fromAbove);
5826 }
5827 }, this);
5828 },
5829 _clearPath: function (path) {
5830 for (var prop in this.__data__) {
5831 if (prop.indexOf(path + '.') === 0) {
5832 this.__data__[prop] = undefined;
5833 }
5834 }
5835 }
5836 },
5837 ensurePropertyEffects: function (model, property) {
5838 var fx = model._propertyEffects[property];
5839 if (!fx) {
5840 fx = model._propertyEffects[property] = [];
5841 }
5842 return fx;
5843 },
5844 addPropertyEffect: function (model, property, kind, effect) {
5845 var fx = this.ensurePropertyEffects(model, property);
5846 fx.push({
5847 kind: kind,
5848 effect: effect
5849 });
5850 },
5851 createBindings: function (model) {
5852 var fx$ = model._propertyEffects;
5853 if (fx$) {
5854 for (var n in fx$) {
5855 var fx = fx$[n];
5856 fx.sort(this._sortPropertyEffects);
5857 this._createAccessors(model, n, fx);
5858 }
5859 }
5860 },
5861 _sortPropertyEffects: function () {
5862 var EFFECT_ORDER = {
5863 'compute': 0,
5864 'annotation': 1,
5865 'computedAnnotation': 2,
5866 'reflect': 3,
5867 'notify': 4,
5868 'observer': 5,
5869 'complexObserver': 6,
5870 'function': 7
5871 };
5872 return function (a, b) {
5873 return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind];
5874 };
5875 }(),
5876 _createAccessors: function (model, property, effects) {
5877 var defun = {
5878 get: function () {
5879 return this.__data__[property];
5880 }
5881 };
5882 var setter = function (value) {
5883 this._propertySetter(property, value, effects);
5884 };
5885 var info = model.getPropertyInfo && model.getPropertyInfo(property);
5886 if (info && info.readOnly) {
5887 if (!info.computed) {
5888 model['_set' + this.upper(property)] = setter;
5889 }
5890 } else {
5891 defun.set = setter;
5892 }
5893 Object.defineProperty(model, property, defun);
5894 },
5895 upper: function (name) {
5896 return name[0].toUpperCase() + name.substring(1);
5897 },
5898 _addAnnotatedListener: function (model, index, property, path, event) {
5899 var fn = this._notedListenerFactory(property, path, this._isStructured(path), th is._isEventBogus);
5900 var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed';
5901 model._bindListeners.push({
5902 index: index,
5903 property: property,
5904 path: path,
5905 changedFn: fn,
5906 event: eventName
5907 });
5908 },
5909 _isStructured: function (path) {
5910 return path.indexOf('.') > 0;
5911 },
5912 _isEventBogus: function (e, target) {
5913 return e.path && e.path[0] !== target;
5914 },
5915 _notedListenerFactory: function (property, path, isStructured, bogusTest) {
5916 return function (e, target) {
5917 if (!bogusTest(e, target)) {
5918 if (e.detail && e.detail.path) {
5919 this.notifyPath(this._fixPath(path, property, e.detail.path), e.detail.value);
5920 } else {
5921 var value = target[property];
5922 if (!isStructured) {
5923 this[path] = target[property];
5924 } else {
5925 if (this.__data__[path] != value) {
5926 this.set(path, value);
5927 }
5928 }
5929 }
5930 }
5931 };
5932 },
5933 prepareInstance: function (inst) {
5934 inst.__data__ = Object.create(null);
5935 },
5936 setupBindListeners: function (inst) {
5937 inst._bindListeners.forEach(function (info) {
5938 var node = inst._nodes[info.index];
5939 node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn ));
5940 });
5941 }
5942 };
5943 Polymer.Base.extend(Polymer.Bind, {
5944 _shouldAddListener: function (effect) {
5945 return effect.name && effect.mode === '{' && !effect.negate && effect.kind != 'a ttribute';
5946 },
5947 _annotationEffect: function (source, value, effect) {
5948 if (source != effect.value) {
5949 value = this.get(effect.value);
5950 this.__data__[effect.value] = value;
5951 }
5952 var calc = effect.negate ? !value : value;
5953 if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) {
5954 return this._applyEffectValue(calc, effect);
5955 }
5956 },
5957 _reflectEffect: function (source) {
5958 this.reflectPropertyToAttribute(source);
5959 },
5960 _notifyEffect: function (source, value, effect, old, fromAbove) {
5961 if (!fromAbove) {
5962 this._notifyChange(source);
5963 }
5964 },
5965 _functionEffect: function (source, value, fn, old, fromAbove) {
5966 fn.call(this, source, value, old, fromAbove);
5967 },
5968 _observerEffect: function (source, value, effect, old) {
5969 var fn = this[effect.method];
5970 if (fn) {
5971 fn.call(this, value, old);
5972 } else {
5973 this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + ' ` not defined'));
5974 }
5975 },
5976 _complexObserverEffect: function (source, value, effect) {
5977 var fn = this[effect.method];
5978 if (fn) {
5979 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
5980 if (args) {
5981 fn.apply(this, args);
5982 }
5983 } else {
5984 this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.met hod + '` not defined'));
5985 }
5986 },
5987 _computeEffect: function (source, value, effect) {
5988 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
5989 if (args) {
5990 var fn = this[effect.method];
5991 if (fn) {
5992 this.__setProperty(effect.property, fn.apply(this, args));
5993 } else {
5994 this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '` not defined'));
5995 }
5996 }
5997 },
5998 _annotatedComputationEffect: function (source, value, effect) {
5999 var computedHost = this._rootDataHost || this;
6000 var fn = computedHost[effect.method];
6001 if (fn) {
6002 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
6003 if (args) {
6004 var computedvalue = fn.apply(computedHost, args);
6005 if (effect.negate) {
6006 computedvalue = !computedvalue;
6007 }
6008 this._applyEffectValue(computedvalue, effect);
6009 }
6010 } else {
6011 computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute me thod `' + effect.method + '` not defined'));
6012 }
6013 },
6014 _marshalArgs: function (model, effect, path, value) {
6015 var values = [];
6016 var args = effect.args;
6017 for (var i = 0, l = args.length; i < l; i++) {
6018 var arg = args[i];
6019 var name = arg.name;
6020 var v;
6021 if (arg.literal) {
6022 v = arg.value;
6023 } else if (arg.structured) {
6024 v = Polymer.Base.get(name, model);
6025 } else {
6026 v = model[name];
6027 }
6028 if (args.length > 1 && v === undefined) {
6029 return;
6030 }
6031 if (arg.wildcard) {
6032 var baseChanged = name.indexOf(path + '.') === 0;
6033 var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged;
6034 values[i] = {
6035 path: matches ? path : name,
6036 value: matches ? value : v,
6037 base: v
6038 };
6039 } else {
6040 values[i] = v;
6041 }
6042 }
6043 return values;
6044 }
6045 });
6046 Polymer.Base._addFeature({
6047 _addPropertyEffect: function (property, kind, effect) {
6048 Polymer.Bind.addPropertyEffect(this, property, kind, effect);
6049 },
6050 _prepEffects: function () {
6051 Polymer.Bind.prepareModel(this);
6052 this._addAnnotationEffects(this._notes);
6053 },
6054 _prepBindings: function () {
6055 Polymer.Bind.createBindings(this);
6056 },
6057 _addPropertyEffects: function (properties) {
6058 if (properties) {
6059 for (var p in properties) {
6060 var prop = properties[p];
6061 if (prop.observer) {
6062 this._addObserverEffect(p, prop.observer);
6063 }
6064 if (prop.computed) {
6065 prop.readOnly = true;
6066 this._addComputedEffect(p, prop.computed);
6067 }
6068 if (prop.notify) {
6069 this._addPropertyEffect(p, 'notify');
6070 }
6071 if (prop.reflectToAttribute) {
6072 this._addPropertyEffect(p, 'reflect');
6073 }
6074 if (prop.readOnly) {
6075 Polymer.Bind.ensurePropertyEffects(this, p);
6076 }
6077 }
6078 }
6079 },
6080 _addComputedEffect: function (name, expression) {
6081 var sig = this._parseMethod(expression);
6082 sig.args.forEach(function (arg) {
6083 this._addPropertyEffect(arg.model, 'compute', {
6084 method: sig.method,
6085 args: sig.args,
6086 trigger: arg,
6087 property: name
6088 });
6089 }, this);
6090 },
6091 _addObserverEffect: function (property, observer) {
6092 this._addPropertyEffect(property, 'observer', {
6093 method: observer,
6094 property: property
6095 });
6096 },
6097 _addComplexObserverEffects: function (observers) {
6098 if (observers) {
6099 observers.forEach(function (observer) {
6100 this._addComplexObserverEffect(observer);
6101 }, this);
6102 }
6103 },
6104 _addComplexObserverEffect: function (observer) {
6105 var sig = this._parseMethod(observer);
6106 sig.args.forEach(function (arg) {
6107 this._addPropertyEffect(arg.model, 'complexObserver', {
6108 method: sig.method,
6109 args: sig.args,
6110 trigger: arg
6111 });
6112 }, this);
6113 },
6114 _addAnnotationEffects: function (notes) {
6115 this._nodes = [];
6116 notes.forEach(function (note) {
6117 var index = this._nodes.push(note) - 1;
6118 note.bindings.forEach(function (binding) {
6119 this._addAnnotationEffect(binding, index);
6120 }, this);
6121 }, this);
6122 },
6123 _addAnnotationEffect: function (note, index) {
6124 if (Polymer.Bind._shouldAddListener(note)) {
6125 Polymer.Bind._addAnnotatedListener(this, index, note.name, note.value, note.even t);
6126 }
6127 if (note.signature) {
6128 this._addAnnotatedComputationEffect(note, index);
6129 } else {
6130 note.index = index;
6131 this._addPropertyEffect(note.model, 'annotation', note);
6132 }
6133 },
6134 _addAnnotatedComputationEffect: function (note, index) {
6135 var sig = note.signature;
6136 if (sig.static) {
6137 this.__addAnnotatedComputationEffect('__static__', index, note, sig, null);
6138 } else {
6139 sig.args.forEach(function (arg) {
6140 if (!arg.literal) {
6141 this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg);
6142 }
6143 }, this);
6144 }
6145 },
6146 __addAnnotatedComputationEffect: function (property, index, note, sig, trigger) {
6147 this._addPropertyEffect(property, 'annotatedComputation', {
6148 index: index,
6149 kind: note.kind,
6150 property: note.name,
6151 negate: note.negate,
6152 method: sig.method,
6153 args: sig.args,
6154 trigger: trigger
6155 });
6156 },
6157 _parseMethod: function (expression) {
6158 var m = expression.match(/([^\s]+)\((.*)\)/);
6159 if (m) {
6160 var sig = {
6161 method: m[1],
6162 static: true
6163 };
6164 if (m[2].trim()) {
6165 var args = m[2].replace(/\\,/g, '&comma;').split(',');
6166 return this._parseArgs(args, sig);
6167 } else {
6168 sig.args = Polymer.nar;
6169 return sig;
6170 }
6171 }
6172 },
6173 _parseArgs: function (argList, sig) {
6174 sig.args = argList.map(function (rawArg) {
6175 var arg = this._parseArg(rawArg);
6176 if (!arg.literal) {
6177 sig.static = false;
6178 }
6179 return arg;
6180 }, this);
6181 return sig;
6182 },
6183 _parseArg: function (rawArg) {
6184 var arg = rawArg.trim().replace(/&comma;/g, ',').replace(/\\(.)/g, '$1');
6185 var a = {
6186 name: arg,
6187 model: this._modelForPath(arg)
6188 };
6189 var fc = arg[0];
6190 if (fc === '-') {
6191 fc = arg[1];
6192 }
6193 if (fc >= '0' && fc <= '9') {
6194 fc = '#';
6195 }
6196 switch (fc) {
6197 case '\'':
6198 case '"':
6199 a.value = arg.slice(1, -1);
6200 a.literal = true;
6201 break;
6202 case '#':
6203 a.value = Number(arg);
6204 a.literal = true;
6205 break;
6206 }
6207 if (!a.literal) {
6208 a.structured = arg.indexOf('.') > 0;
6209 if (a.structured) {
6210 a.wildcard = arg.slice(-2) == '.*';
6211 if (a.wildcard) {
6212 a.name = arg.slice(0, -2);
6213 }
6214 }
6215 }
6216 return a;
6217 },
6218 _marshalInstanceEffects: function () {
6219 Polymer.Bind.prepareInstance(this);
6220 Polymer.Bind.setupBindListeners(this);
6221 },
6222 _applyEffectValue: function (value, info) {
6223 var node = this._nodes[info.index];
6224 var property = info.property || info.name || 'textContent';
6225 if (info.kind == 'attribute') {
6226 this.serializeValueToAttribute(value, property, node);
6227 } else {
6228 if (property === 'className') {
6229 value = this._scopeElementClass(node, value);
6230 }
6231 if (property === 'textContent' || node.localName == 'input' && property == 'valu e') {
6232 value = value == undefined ? '' : value;
6233 }
6234 return node[property] = value;
6235 }
6236 },
6237 _executeStaticEffects: function () {
6238 if (this._propertyEffects.__static__) {
6239 this._effectEffects('__static__', null, this._propertyEffects.__static__);
6240 }
6241 }
6242 });
6243 Polymer.Base._addFeature({
6244 _setupConfigure: function (initialConfig) {
6245 this._config = {};
6246 for (var i in initialConfig) {
6247 if (initialConfig[i] !== undefined) {
6248 this._config[i] = initialConfig[i];
6249 }
6250 }
6251 this._handlers = [];
6252 },
6253 _marshalAttributes: function () {
6254 this._takeAttributesToModel(this._config);
6255 },
6256 _attributeChangedImpl: function (name) {
6257 var model = this._clientsReadied ? this : this._config;
6258 this._setAttributeToProperty(model, name);
6259 },
6260 _configValue: function (name, value) {
6261 this._config[name] = value;
6262 },
6263 _beforeClientsReady: function () {
6264 this._configure();
6265 },
6266 _configure: function () {
6267 this._configureAnnotationReferences();
6268 this._aboveConfig = this.mixin({}, this._config);
6269 var config = {};
6270 this.behaviors.forEach(function (b) {
6271 this._configureProperties(b.properties, config);
6272 }, this);
6273 this._configureProperties(this.properties, config);
6274 this._mixinConfigure(config, this._aboveConfig);
6275 this._config = config;
6276 this._distributeConfig(this._config);
6277 },
6278 _configureProperties: function (properties, config) {
6279 for (var i in properties) {
6280 var c = properties[i];
6281 if (c.value !== undefined) {
6282 var value = c.value;
6283 if (typeof value == 'function') {
6284 value = value.call(this, this._config);
6285 }
6286 config[i] = value;
6287 }
6288 }
6289 },
6290 _mixinConfigure: function (a, b) {
6291 for (var prop in b) {
6292 if (!this.getPropertyInfo(prop).readOnly) {
6293 a[prop] = b[prop];
6294 }
6295 }
6296 },
6297 _distributeConfig: function (config) {
6298 var fx$ = this._propertyEffects;
6299 if (fx$) {
6300 for (var p in config) {
6301 var fx = fx$[p];
6302 if (fx) {
6303 for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) {
6304 if (x.kind === 'annotation') {
6305 var node = this._nodes[x.effect.index];
6306 if (node._configValue) {
6307 var value = p === x.effect.value ? config[p] : this.get(x.effect.value, config);
6308 node._configValue(x.effect.name, value);
6309 }
6310 }
6311 }
6312 }
6313 }
6314 }
6315 },
6316 _afterClientsReady: function () {
6317 this._executeStaticEffects();
6318 this._applyConfig(this._config, this._aboveConfig);
6319 this._flushHandlers();
6320 },
6321 _applyConfig: function (config, aboveConfig) {
6322 for (var n in config) {
6323 if (this[n] === undefined) {
6324 this.__setProperty(n, config[n], n in aboveConfig);
6325 }
6326 }
6327 },
6328 _notifyListener: function (fn, e) {
6329 if (!this._clientsReadied) {
6330 this._queueHandler([
6331 fn,
6332 e,
6333 e.target
6334 ]);
6335 } else {
6336 return fn.call(this, e, e.target);
6337 }
6338 },
6339 _queueHandler: function (args) {
6340 this._handlers.push(args);
6341 },
6342 _flushHandlers: function () {
6343 var h$ = this._handlers;
6344 for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) {
6345 h[0].call(this, h[1], h[2]);
6346 }
6347 this._handlers = [];
6348 }
6349 });
6350 (function () {
6351 'use strict';
6352 Polymer.Base._addFeature({
6353 notifyPath: function (path, value, fromAbove) {
6354 var old = this._propertySetter(path, value);
6355 if (old !== value && (old === old || value === value)) {
6356 this._pathEffector(path, value);
6357 if (!fromAbove) {
6358 this._notifyPath(path, value);
6359 }
6360 return true;
6361 }
6362 },
6363 _getPathParts: function (path) {
6364 if (Array.isArray(path)) {
6365 var parts = [];
6366 for (var i = 0; i < path.length; i++) {
6367 var args = path[i].toString().split('.');
6368 for (var j = 0; j < args.length; j++) {
6369 parts.push(args[j]);
6370 }
6371 }
6372 return parts;
6373 } else {
6374 return path.toString().split('.');
6375 }
6376 },
6377 set: function (path, value, root) {
6378 var prop = root || this;
6379 var parts = this._getPathParts(path);
6380 var array;
6381 var last = parts[parts.length - 1];
6382 if (parts.length > 1) {
6383 for (var i = 0; i < parts.length - 1; i++) {
6384 var part = parts[i];
6385 prop = prop[part];
6386 if (array && parseInt(part) == part) {
6387 parts[i] = Polymer.Collection.get(array).getKey(prop);
6388 }
6389 if (!prop) {
6390 return;
6391 }
6392 array = Array.isArray(prop) ? prop : null;
6393 }
6394 if (array && parseInt(last) == last) {
6395 var coll = Polymer.Collection.get(array);
6396 var old = prop[last];
6397 var key = coll.getKey(old);
6398 parts[i] = key;
6399 coll.setItem(key, value);
6400 }
6401 prop[last] = value;
6402 if (!root) {
6403 this.notifyPath(parts.join('.'), value);
6404 }
6405 } else {
6406 prop[path] = value;
6407 }
6408 },
6409 get: function (path, root) {
6410 var prop = root || this;
6411 var parts = this._getPathParts(path);
6412 var last = parts.pop();
6413 while (parts.length) {
6414 prop = prop[parts.shift()];
6415 if (!prop) {
6416 return;
6417 }
6418 }
6419 return prop[last];
6420 },
6421 _pathEffector: function (path, value) {
6422 var model = this._modelForPath(path);
6423 var fx$ = this._propertyEffects[model];
6424 if (fx$) {
6425 fx$.forEach(function (fx) {
6426 var fxFn = this['_' + fx.kind + 'PathEffect'];
6427 if (fxFn) {
6428 fxFn.call(this, path, value, fx.effect);
6429 }
6430 }, this);
6431 }
6432 if (this._boundPaths) {
6433 this._notifyBoundPaths(path, value);
6434 }
6435 },
6436 _annotationPathEffect: function (path, value, effect) {
6437 if (effect.value === path || effect.value.indexOf(path + '.') === 0) {
6438 Polymer.Bind._annotationEffect.call(this, path, value, effect);
6439 } else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) {
6440 var node = this._nodes[effect.index];
6441 if (node && node.notifyPath) {
6442 var p = this._fixPath(effect.name, effect.value, path);
6443 node.notifyPath(p, value, true);
6444 }
6445 }
6446 },
6447 _complexObserverPathEffect: function (path, value, effect) {
6448 if (this._pathMatchesEffect(path, effect)) {
6449 Polymer.Bind._complexObserverEffect.call(this, path, value, effect);
6450 }
6451 },
6452 _computePathEffect: function (path, value, effect) {
6453 if (this._pathMatchesEffect(path, effect)) {
6454 Polymer.Bind._computeEffect.call(this, path, value, effect);
6455 }
6456 },
6457 _annotatedComputationPathEffect: function (path, value, effect) {
6458 if (this._pathMatchesEffect(path, effect)) {
6459 Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect);
6460 }
6461 },
6462 _pathMatchesEffect: function (path, effect) {
6463 var effectArg = effect.trigger.name;
6464 return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigge r.wildcard && path.indexOf(effectArg) === 0;
6465 },
6466 linkPaths: function (to, from) {
6467 this._boundPaths = this._boundPaths || {};
6468 if (from) {
6469 this._boundPaths[to] = from;
6470 } else {
6471 this.unlinkPaths(to);
6472 }
6473 },
6474 unlinkPaths: function (path) {
6475 if (this._boundPaths) {
6476 delete this._boundPaths[path];
6477 }
6478 },
6479 _notifyBoundPaths: function (path, value) {
6480 for (var a in this._boundPaths) {
6481 var b = this._boundPaths[a];
6482 if (path.indexOf(a + '.') == 0) {
6483 this.notifyPath(this._fixPath(b, a, path), value);
6484 } else if (path.indexOf(b + '.') == 0) {
6485 this.notifyPath(this._fixPath(a, b, path), value);
6486 }
6487 }
6488 },
6489 _fixPath: function (property, root, path) {
6490 return property + path.slice(root.length);
6491 },
6492 _notifyPath: function (path, value) {
6493 var rootName = this._modelForPath(path);
6494 var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName);
6495 var eventName = dashCaseName + this._EVENT_CHANGED;
6496 this.fire(eventName, {
6497 path: path,
6498 value: value
6499 }, { bubbles: false });
6500 },
6501 _modelForPath: function (path) {
6502 var dot = path.indexOf('.');
6503 return dot < 0 ? path : path.slice(0, dot);
6504 },
6505 _EVENT_CHANGED: '-changed',
6506 _notifySplice: function (array, path, index, added, removed) {
6507 var splices = [{
6508 index: index,
6509 addedCount: added,
6510 removed: removed,
6511 object: array,
6512 type: 'splice'
6513 }];
6514 var change = {
6515 keySplices: Polymer.Collection.applySplices(array, splices),
6516 indexSplices: splices
6517 };
6518 this.set(path + '.splices', change);
6519 if (added != removed.length) {
6520 this.notifyPath(path + '.length', array.length);
6521 }
6522 change.keySplices = null;
6523 change.indexSplices = null;
6524 },
6525 push: function (path) {
6526 var array = this.get(path);
6527 var args = Array.prototype.slice.call(arguments, 1);
6528 var len = array.length;
6529 var ret = array.push.apply(array, args);
6530 if (args.length) {
6531 this._notifySplice(array, path, len, args.length, []);
6532 }
6533 return ret;
6534 },
6535 pop: function (path) {
6536 var array = this.get(path);
6537 var hadLength = Boolean(array.length);
6538 var args = Array.prototype.slice.call(arguments, 1);
6539 var ret = array.pop.apply(array, args);
6540 if (hadLength) {
6541 this._notifySplice(array, path, array.length, 0, [ret]);
6542 }
6543 return ret;
6544 },
6545 splice: function (path, start, deleteCount) {
6546 var array = this.get(path);
6547 if (start < 0) {
6548 start = array.length - Math.floor(-start);
6549 } else {
6550 start = Math.floor(start);
6551 }
6552 if (!start) {
6553 start = 0;
6554 }
6555 var args = Array.prototype.slice.call(arguments, 1);
6556 var ret = array.splice.apply(array, args);
6557 var addedCount = Math.max(args.length - 2, 0);
6558 if (addedCount || ret.length) {
6559 this._notifySplice(array, path, start, addedCount, ret);
6560 }
6561 return ret;
6562 },
6563 shift: function (path) {
6564 var array = this.get(path);
6565 var hadLength = Boolean(array.length);
6566 var args = Array.prototype.slice.call(arguments, 1);
6567 var ret = array.shift.apply(array, args);
6568 if (hadLength) {
6569 this._notifySplice(array, path, 0, 0, [ret]);
6570 }
6571 return ret;
6572 },
6573 unshift: function (path) {
6574 var array = this.get(path);
6575 var args = Array.prototype.slice.call(arguments, 1);
6576 var ret = array.unshift.apply(array, args);
6577 if (args.length) {
6578 this._notifySplice(array, path, 0, args.length, []);
6579 }
6580 return ret;
6581 }
6582 });
6583 }());
6584 Polymer.Base._addFeature({
6585 resolveUrl: function (url) {
6586 var module = Polymer.DomModule.import(this.is);
6587 var root = '';
6588 if (module) {
6589 var assetPath = module.getAttribute('assetpath') || '';
6590 root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI);
6591 }
6592 return Polymer.ResolveUrl.resolveUrl(url, root);
6593 }
6594 });
6595 Polymer.CssParse = function () {
6596 var api = {
6597 parse: function (text) {
6598 text = this._clean(text);
6599 return this._parseCss(this._lex(text), text);
6600 },
6601 _clean: function (cssText) {
6602 return cssText.replace(this._rx.comments, '').replace(this._rx.port, '');
6603 },
6604 _lex: function (text) {
6605 var root = {
6606 start: 0,
6607 end: text.length
6608 };
6609 var n = root;
6610 for (var i = 0, s = 0, l = text.length; i < l; i++) {
6611 switch (text[i]) {
6612 case this.OPEN_BRACE:
6613 if (!n.rules) {
6614 n.rules = [];
6615 }
6616 var p = n;
6617 var previous = p.rules[p.rules.length - 1];
6618 n = {
6619 start: i + 1,
6620 parent: p,
6621 previous: previous
6622 };
6623 p.rules.push(n);
6624 break;
6625 case this.CLOSE_BRACE:
6626 n.end = i + 1;
6627 n = n.parent || root;
6628 break;
6629 }
6630 }
6631 return root;
6632 },
6633 _parseCss: function (node, text) {
6634 var t = text.substring(node.start, node.end - 1);
6635 node.parsedCssText = node.cssText = t.trim();
6636 if (node.parent) {
6637 var ss = node.previous ? node.previous.end : node.parent.start;
6638 t = text.substring(ss, node.start - 1);
6639 t = t.substring(t.lastIndexOf(';') + 1);
6640 var s = node.parsedSelector = node.selector = t.trim();
6641 node.atRule = s.indexOf(this.AT_START) === 0;
6642 if (node.atRule) {
6643 if (s.indexOf(this.MEDIA_START) === 0) {
6644 node.type = this.types.MEDIA_RULE;
6645 } else if (s.match(this._rx.keyframesRule)) {
6646 node.type = this.types.KEYFRAMES_RULE;
6647 }
6648 } else {
6649 if (s.indexOf(this.VAR_START) === 0) {
6650 node.type = this.types.MIXIN_RULE;
6651 } else {
6652 node.type = this.types.STYLE_RULE;
6653 }
6654 }
6655 }
6656 var r$ = node.rules;
6657 if (r$) {
6658 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
6659 this._parseCss(r, text);
6660 }
6661 }
6662 return node;
6663 },
6664 stringify: function (node, preserveProperties, text) {
6665 text = text || '';
6666 var cssText = '';
6667 if (node.cssText || node.rules) {
6668 var r$ = node.rules;
6669 if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
6670 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
6671 cssText = this.stringify(r, preserveProperties, cssText);
6672 }
6673 } else {
6674 cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssTex t);
6675 cssText = cssText.trim();
6676 if (cssText) {
6677 cssText = ' ' + cssText + '\n';
6678 }
6679 }
6680 }
6681 if (cssText) {
6682 if (node.selector) {
6683 text += node.selector + ' ' + this.OPEN_BRACE + '\n';
6684 }
6685 text += cssText;
6686 if (node.selector) {
6687 text += this.CLOSE_BRACE + '\n\n';
6688 }
6689 }
6690 return text;
6691 },
6692 _hasMixinRules: function (rules) {
6693 return rules[0].selector.indexOf(this.VAR_START) >= 0;
6694 },
6695 removeCustomProps: function (cssText) {
6696 return cssText;
6697 },
6698 removeCustomPropAssignment: function (cssText) {
6699 return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, '');
6700 },
6701 removeCustomPropApply: function (cssText) {
6702 return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, '');
6703 },
6704 types: {
6705 STYLE_RULE: 1,
6706 KEYFRAMES_RULE: 7,
6707 MEDIA_RULE: 4,
6708 MIXIN_RULE: 1000
6709 },
6710 OPEN_BRACE: '{',
6711 CLOSE_BRACE: '}',
6712 _rx: {
6713 comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,
6714 port: /@import[^;]*;/gim,
6715 customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim,
6716 mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim,
6717 mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
6718 varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim,
6719 keyframesRule: /^@[^\s]*keyframes/
6720 },
6721 VAR_START: '--',
6722 MEDIA_START: '@media',
6723 AT_START: '@'
6724 };
6725 return api;
6726 }();
6727 Polymer.StyleUtil = function () {
6728 return {
6729 MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template',
6730 INCLUDE_ATTR: 'include',
6731 toCssText: function (rules, callback, preserveProperties) {
6732 if (typeof rules === 'string') {
6733 rules = this.parser.parse(rules);
6734 }
6735 if (callback) {
6736 this.forEachStyleRule(rules, callback);
6737 }
6738 return this.parser.stringify(rules, preserveProperties);
6739 },
6740 forRulesInStyles: function (styles, callback) {
6741 if (styles) {
6742 for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) {
6743 this.forEachStyleRule(this.rulesForStyle(s), callback);
6744 }
6745 }
6746 },
6747 rulesForStyle: function (style) {
6748 if (!style.__cssRules && style.textContent) {
6749 style.__cssRules = this.parser.parse(style.textContent);
6750 }
6751 return style.__cssRules;
6752 },
6753 clearStyleRules: function (style) {
6754 style.__cssRules = null;
6755 },
6756 forEachStyleRule: function (node, callback) {
6757 if (!node) {
6758 return;
6759 }
6760 var s = node.parsedSelector;
6761 var skipRules = false;
6762 if (node.type === this.ruleTypes.STYLE_RULE) {
6763 callback(node);
6764 } else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.rul eTypes.MIXIN_RULE) {
6765 skipRules = true;
6766 }
6767 var r$ = node.rules;
6768 if (r$ && !skipRules) {
6769 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
6770 this.forEachStyleRule(r, callback);
6771 }
6772 }
6773 },
6774 applyCss: function (cssText, moniker, target, afterNode) {
6775 var style = document.createElement('style');
6776 if (moniker) {
6777 style.setAttribute('scope', moniker);
6778 }
6779 style.textContent = cssText;
6780 target = target || document.head;
6781 if (!afterNode) {
6782 var n$ = target.querySelectorAll('style[scope]');
6783 afterNode = n$[n$.length - 1];
6784 }
6785 target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChi ld);
6786 return style;
6787 },
6788 cssFromModules: function (moduleIds, warnIfNotFound) {
6789 var modules = moduleIds.trim().split(' ');
6790 var cssText = '';
6791 for (var i = 0; i < modules.length; i++) {
6792 cssText += this.cssFromModule(modules[i], warnIfNotFound);
6793 }
6794 return cssText;
6795 },
6796 cssFromModule: function (moduleId, warnIfNotFound) {
6797 var m = Polymer.DomModule.import(moduleId);
6798 if (m && !m._cssText) {
6799 m._cssText = this._cssFromElement(m);
6800 }
6801 if (!m && warnIfNotFound) {
6802 console.warn('Could not find style data in module named', moduleId);
6803 }
6804 return m && m._cssText || '';
6805 },
6806 _cssFromElement: function (element) {
6807 var cssText = '';
6808 var content = element.content || element;
6809 var e$ = Array.prototype.slice.call(content.querySelectorAll(this.MODULE_STYLES_ SELECTOR));
6810 for (var i = 0, e; i < e$.length; i++) {
6811 e = e$[i];
6812 if (e.localName === 'template') {
6813 cssText += this._cssFromElement(e);
6814 } else {
6815 if (e.localName === 'style') {
6816 var include = e.getAttribute(this.INCLUDE_ATTR);
6817 if (include) {
6818 cssText += this.cssFromModules(include, true);
6819 }
6820 e = e.__appliedElement || e;
6821 e.parentNode.removeChild(e);
6822 cssText += this.resolveCss(e.textContent, element.ownerDocument);
6823 } else if (e.import && e.import.body) {
6824 cssText += this.resolveCss(e.import.body.textContent, e.import);
6825 }
6826 }
6827 }
6828 return cssText;
6829 },
6830 resolveCss: Polymer.ResolveUrl.resolveCss,
6831 parser: Polymer.CssParse,
6832 ruleTypes: Polymer.CssParse.types
6833 };
6834 }();
6835 Polymer.StyleTransformer = function () {
6836 var nativeShadow = Polymer.Settings.useNativeShadow;
6837 var styleUtil = Polymer.StyleUtil;
6838 var api = {
6839 dom: function (node, scope, useAttr, shouldRemoveScope) {
6840 this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
6841 },
6842 _transformDom: function (node, selector, useAttr, shouldRemoveScope) {
6843 if (node.setAttribute) {
6844 this.element(node, selector, useAttr, shouldRemoveScope);
6845 }
6846 var c$ = Polymer.dom(node).childNodes;
6847 for (var i = 0; i < c$.length; i++) {
6848 this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
6849 }
6850 },
6851 element: function (element, scope, useAttr, shouldRemoveScope) {
6852 if (useAttr) {
6853 if (shouldRemoveScope) {
6854 element.removeAttribute(SCOPE_NAME);
6855 } else {
6856 element.setAttribute(SCOPE_NAME, scope);
6857 }
6858 } else {
6859 if (scope) {
6860 if (element.classList) {
6861 if (shouldRemoveScope) {
6862 element.classList.remove(SCOPE_NAME);
6863 element.classList.remove(scope);
6864 } else {
6865 element.classList.add(SCOPE_NAME);
6866 element.classList.add(scope);
6867 }
6868 } else if (element.getAttribute) {
6869 var c = element.getAttribute(CLASS);
6870 if (shouldRemoveScope) {
6871 if (c) {
6872 element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, ''));
6873 }
6874 } else {
6875 element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope);
6876 }
6877 }
6878 }
6879 }
6880 },
6881 elementStyles: function (element, callback) {
6882 var styles = element._styles;
6883 var cssText = '';
6884 for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) {
6885 var rules = styleUtil.rulesForStyle(s);
6886 cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules, element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n';
6887 }
6888 return cssText.trim();
6889 },
6890 css: function (rules, scope, ext, callback, useAttr) {
6891 var hostScope = this._calcHostScope(scope, ext);
6892 scope = this._calcElementScope(scope, useAttr);
6893 var self = this;
6894 return styleUtil.toCssText(rules, function (rule) {
6895 if (!rule.isScoped) {
6896 self.rule(rule, scope, hostScope);
6897 rule.isScoped = true;
6898 }
6899 if (callback) {
6900 callback(rule, scope, hostScope);
6901 }
6902 });
6903 },
6904 _calcElementScope: function (scope, useAttr) {
6905 if (scope) {
6906 return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX + scope;
6907 } else {
6908 return '';
6909 }
6910 },
6911 _calcHostScope: function (scope, ext) {
6912 return ext ? '[is=' + scope + ']' : scope;
6913 },
6914 rule: function (rule, scope, hostScope) {
6915 this._transformRule(rule, this._transformComplexSelector, scope, hostScope);
6916 },
6917 _transformRule: function (rule, transformer, scope, hostScope) {
6918 var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
6919 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
6920 p$[i] = transformer.call(this, p, scope, hostScope);
6921 }
6922 rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP);
6923 },
6924 _transformComplexSelector: function (selector, scope, hostScope) {
6925 var stop = false;
6926 var hostContext = false;
6927 var self = this;
6928 selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) {
6929 if (!stop) {
6930 var info = self._transformCompoundSelector(s, c, scope, hostScope);
6931 stop = stop || info.stop;
6932 hostContext = hostContext || info.hostContext;
6933 c = info.combinator;
6934 s = info.value;
6935 } else {
6936 s = s.replace(SCOPE_JUMP, ' ');
6937 }
6938 return c + s;
6939 });
6940 if (hostContext) {
6941 selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) {
6942 return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
6943 });
6944 }
6945 return selector;
6946 },
6947 _transformCompoundSelector: function (selector, combinator, scope, hostScope) {
6948 var jumpIndex = selector.search(SCOPE_JUMP);
6949 var hostContext = false;
6950 if (selector.indexOf(HOST_CONTEXT) >= 0) {
6951 hostContext = true;
6952 } else if (selector.indexOf(HOST) >= 0) {
6953 selector = selector.replace(HOST_PAREN, function (m, host, paren) {
6954 return hostScope + paren;
6955 });
6956 selector = selector.replace(HOST, hostScope);
6957 } else if (jumpIndex !== 0) {
6958 selector = scope ? this._transformSimpleSelector(selector, scope) : selector;
6959 }
6960 if (selector.indexOf(CONTENT) >= 0) {
6961 combinator = '';
6962 }
6963 var stop;
6964 if (jumpIndex >= 0) {
6965 selector = selector.replace(SCOPE_JUMP, ' ');
6966 stop = true;
6967 }
6968 return {
6969 value: selector,
6970 combinator: combinator,
6971 stop: stop,
6972 hostContext: hostContext
6973 };
6974 },
6975 _transformSimpleSelector: function (selector, scope) {
6976 var p$ = selector.split(PSEUDO_PREFIX);
6977 p$[0] += scope;
6978 return p$.join(PSEUDO_PREFIX);
6979 },
6980 documentRule: function (rule) {
6981 rule.selector = rule.parsedSelector;
6982 this.normalizeRootSelector(rule);
6983 if (!nativeShadow) {
6984 this._transformRule(rule, this._transformDocumentSelector);
6985 }
6986 },
6987 normalizeRootSelector: function (rule) {
6988 if (rule.selector === ROOT) {
6989 rule.selector = 'body';
6990 }
6991 },
6992 _transformDocumentSelector: function (selector) {
6993 return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCO PE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELE CTOR);
6994 },
6995 SCOPE_NAME: 'style-scope'
6996 };
6997 var SCOPE_NAME = api.SCOPE_NAME;
6998 var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME + ')';
6999 var COMPLEX_SELECTOR_SEP = ',';
7000 var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
7001 var HOST = ':host';
7002 var ROOT = ':root';
7003 var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
7004 var HOST_CONTEXT = ':host-context';
7005 var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\) )(.*)/;
7006 var CONTENT = '::content';
7007 var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//;
7008 var CSS_CLASS_PREFIX = '.';
7009 var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
7010 var CSS_ATTR_SUFFIX = ']';
7011 var PSEUDO_PREFIX = ':';
7012 var CLASS = 'class';
7013 return api;
7014 }();
7015 Polymer.StyleExtends = function () {
7016 var styleUtil = Polymer.StyleUtil;
7017 return {
7018 hasExtends: function (cssText) {
7019 return Boolean(cssText.match(this.rx.EXTEND));
7020 },
7021 transform: function (style) {
7022 var rules = styleUtil.rulesForStyle(style);
7023 var self = this;
7024 styleUtil.forEachStyleRule(rules, function (rule) {
7025 var map = self._mapRule(rule);
7026 if (rule.parent) {
7027 var m;
7028 while (m = self.rx.EXTEND.exec(rule.cssText)) {
7029 var extend = m[1];
7030 var extendor = self._findExtendor(extend, rule);
7031 if (extendor) {
7032 self._extendRule(rule, extendor);
7033 }
7034 }
7035 }
7036 rule.cssText = rule.cssText.replace(self.rx.EXTEND, '');
7037 });
7038 return styleUtil.toCssText(rules, function (rule) {
7039 if (rule.selector.match(self.rx.STRIP)) {
7040 rule.cssText = '';
7041 }
7042 }, true);
7043 },
7044 _mapRule: function (rule) {
7045 if (rule.parent) {
7046 var map = rule.parent.map || (rule.parent.map = {});
7047 var parts = rule.selector.split(',');
7048 for (var i = 0, p; i < parts.length; i++) {
7049 p = parts[i];
7050 map[p.trim()] = rule;
7051 }
7052 return map;
7053 }
7054 },
7055 _findExtendor: function (extend, rule) {
7056 return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findEx tendor(extend, rule.parent);
7057 },
7058 _extendRule: function (target, source) {
7059 if (target.parent !== source.parent) {
7060 this._cloneAndAddRuleToParent(source, target.parent);
7061 }
7062 target.extends = target.extends || (target.extends = []);
7063 target.extends.push(source);
7064 source.selector = source.selector.replace(this.rx.STRIP, '');
7065 source.selector = (source.selector && source.selector + ',\n') + target.selector ;
7066 if (source.extends) {
7067 source.extends.forEach(function (e) {
7068 this._extendRule(target, e);
7069 }, this);
7070 }
7071 },
7072 _cloneAndAddRuleToParent: function (rule, parent) {
7073 rule = Object.create(rule);
7074 rule.parent = parent;
7075 if (rule.extends) {
7076 rule.extends = rule.extends.slice();
7077 }
7078 parent.rules.push(rule);
7079 },
7080 rx: {
7081 EXTEND: /@extends\(([^)]*)\)\s*?;/gim,
7082 STRIP: /%[^,]*$/
7083 }
7084 };
7085 }();
7086 (function () {
7087 var prepElement = Polymer.Base._prepElement;
7088 var nativeShadow = Polymer.Settings.useNativeShadow;
7089 var styleUtil = Polymer.StyleUtil;
7090 var styleTransformer = Polymer.StyleTransformer;
7091 var styleExtends = Polymer.StyleExtends;
7092 Polymer.Base._addFeature({
7093 _prepElement: function (element) {
7094 if (this._encapsulateStyle) {
7095 styleTransformer.element(element, this.is, this._scopeCssViaAttr);
7096 }
7097 prepElement.call(this, element);
7098 },
7099 _prepStyles: function () {
7100 if (this._encapsulateStyle === undefined) {
7101 this._encapsulateStyle = !nativeShadow && Boolean(this._template);
7102 }
7103 this._styles = this._collectStyles();
7104 var cssText = styleTransformer.elementStyles(this);
7105 if (cssText && this._template) {
7106 var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.c ontent : null);
7107 if (!nativeShadow) {
7108 this._scopeStyle = style;
7109 }
7110 }
7111 },
7112 _collectStyles: function () {
7113 var styles = [];
7114 var cssText = '', m$ = this.styleModules;
7115 if (m$) {
7116 for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) {
7117 cssText += styleUtil.cssFromModule(m);
7118 }
7119 }
7120 cssText += styleUtil.cssFromModule(this.is);
7121 if (cssText) {
7122 var style = document.createElement('style');
7123 style.textContent = cssText;
7124 if (styleExtends.hasExtends(style.textContent)) {
7125 cssText = styleExtends.transform(style);
7126 }
7127 styles.push(style);
7128 }
7129 return styles;
7130 },
7131 _elementAdd: function (node) {
7132 if (this._encapsulateStyle) {
7133 if (node.__styleScoped) {
7134 node.__styleScoped = false;
7135 } else {
7136 styleTransformer.dom(node, this.is, this._scopeCssViaAttr);
7137 }
7138 }
7139 },
7140 _elementRemove: function (node) {
7141 if (this._encapsulateStyle) {
7142 styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true);
7143 }
7144 },
7145 scopeSubtree: function (container, shouldObserve) {
7146 if (nativeShadow) {
7147 return;
7148 }
7149 var self = this;
7150 var scopify = function (node) {
7151 if (node.nodeType === Node.ELEMENT_NODE) {
7152 node.className = self._scopeElementClass(node, node.className);
7153 var n$ = node.querySelectorAll('*');
7154 Array.prototype.forEach.call(n$, function (n) {
7155 n.className = self._scopeElementClass(n, n.className);
7156 });
7157 }
7158 };
7159 scopify(container);
7160 if (shouldObserve) {
7161 var mo = new MutationObserver(function (mxns) {
7162 mxns.forEach(function (m) {
7163 if (m.addedNodes) {
7164 for (var i = 0; i < m.addedNodes.length; i++) {
7165 scopify(m.addedNodes[i]);
7166 }
7167 }
7168 });
7169 });
7170 mo.observe(container, {
7171 childList: true,
7172 subtree: true
7173 });
7174 return mo;
7175 }
7176 }
7177 });
7178 }());
7179 Polymer.StyleProperties = function () {
7180 'use strict';
7181 var nativeShadow = Polymer.Settings.useNativeShadow;
7182 var matchesSelector = Polymer.DomApi.matchesSelector;
7183 var styleUtil = Polymer.StyleUtil;
7184 var styleTransformer = Polymer.StyleTransformer;
7185 return {
7186 decorateStyles: function (styles) {
7187 var self = this, props = {};
7188 styleUtil.forRulesInStyles(styles, function (rule) {
7189 self.decorateRule(rule);
7190 self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
7191 });
7192 var names = [];
7193 for (var i in props) {
7194 names.push(i);
7195 }
7196 return names;
7197 },
7198 decorateRule: function (rule) {
7199 if (rule.propertyInfo) {
7200 return rule.propertyInfo;
7201 }
7202 var info = {}, properties = {};
7203 var hasProperties = this.collectProperties(rule, properties);
7204 if (hasProperties) {
7205 info.properties = properties;
7206 rule.rules = null;
7207 }
7208 info.cssText = this.collectCssText(rule);
7209 rule.propertyInfo = info;
7210 return info;
7211 },
7212 collectProperties: function (rule, properties) {
7213 var info = rule.propertyInfo;
7214 if (info) {
7215 if (info.properties) {
7216 Polymer.Base.mixin(properties, info.properties);
7217 return true;
7218 }
7219 } else {
7220 var m, rx = this.rx.VAR_ASSIGN;
7221 var cssText = rule.parsedCssText;
7222 var any;
7223 while (m = rx.exec(cssText)) {
7224 properties[m[1]] = (m[2] || m[3]).trim();
7225 any = true;
7226 }
7227 return any;
7228 }
7229 },
7230 collectCssText: function (rule) {
7231 var customCssText = '';
7232 var cssText = rule.parsedCssText;
7233 cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, '') ;
7234 var parts = cssText.split(';');
7235 for (var i = 0, p; i < parts.length; i++) {
7236 p = parts[i];
7237 if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) {
7238 customCssText += p + ';\n';
7239 }
7240 }
7241 return customCssText;
7242 },
7243 collectPropertiesInCssText: function (cssText, props) {
7244 var m;
7245 while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
7246 props[m[1]] = true;
7247 var def = m[2];
7248 if (def && def.match(this.rx.IS_VAR)) {
7249 props[def] = true;
7250 }
7251 }
7252 },
7253 reify: function (props) {
7254 var names = Object.getOwnPropertyNames(props);
7255 for (var i = 0, n; i < names.length; i++) {
7256 n = names[i];
7257 props[n] = this.valueForProperty(props[n], props);
7258 }
7259 },
7260 valueForProperty: function (property, props) {
7261 if (property) {
7262 if (property.indexOf(';') >= 0) {
7263 property = this.valueForProperties(property, props);
7264 } else {
7265 var self = this;
7266 var fn = function (all, prefix, value, fallback) {
7267 var propertyValue = self.valueForProperty(props[value], props) || (props[fallbac k] ? self.valueForProperty(props[fallback], props) : fallback);
7268 return prefix + (propertyValue || '');
7269 };
7270 property = property.replace(this.rx.VAR_MATCH, fn);
7271 }
7272 }
7273 return property && property.trim() || '';
7274 },
7275 valueForProperties: function (property, props) {
7276 var parts = property.split(';');
7277 for (var i = 0, p, m; i < parts.length; i++) {
7278 if (p = parts[i]) {
7279 m = p.match(this.rx.MIXIN_MATCH);
7280 if (m) {
7281 p = this.valueForProperty(props[m[1]], props);
7282 } else {
7283 var pp = p.split(':');
7284 if (pp[1]) {
7285 pp[1] = pp[1].trim();
7286 pp[1] = this.valueForProperty(pp[1], props) || pp[1];
7287 }
7288 p = pp.join(':');
7289 }
7290 parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || '';
7291 }
7292 }
7293 return parts.join(';');
7294 },
7295 applyProperties: function (rule, props) {
7296 var output = '';
7297 if (!rule.propertyInfo) {
7298 this.decorateRule(rule);
7299 }
7300 if (rule.propertyInfo.cssText) {
7301 output = this.valueForProperties(rule.propertyInfo.cssText, props);
7302 }
7303 rule.cssText = output;
7304 },
7305 propertyDataFromStyles: function (styles, element) {
7306 var props = {}, self = this;
7307 var o = [], i = 0;
7308 styleUtil.forRulesInStyles(styles, function (rule) {
7309 if (!rule.propertyInfo) {
7310 self.decorateRule(rule);
7311 }
7312 if (element && rule.propertyInfo.properties && matchesSelector.call(element, rul e.transformedSelector || rule.parsedSelector)) {
7313 self.collectProperties(rule, props);
7314 addToBitMask(i, o);
7315 }
7316 i++;
7317 });
7318 return {
7319 properties: props,
7320 key: o
7321 };
7322 },
7323 scopePropertiesFromStyles: function (styles) {
7324 if (!styles._scopeStyleProperties) {
7325 styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SC OPE_SELECTORS);
7326 }
7327 return styles._scopeStyleProperties;
7328 },
7329 hostPropertiesFromStyles: function (styles) {
7330 if (!styles._hostStyleProperties) {
7331 styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOS T_SELECTORS);
7332 }
7333 return styles._hostStyleProperties;
7334 },
7335 selectedPropertiesFromStyles: function (styles, selectors) {
7336 var props = {}, self = this;
7337 styleUtil.forRulesInStyles(styles, function (rule) {
7338 if (!rule.propertyInfo) {
7339 self.decorateRule(rule);
7340 }
7341 for (var i = 0; i < selectors.length; i++) {
7342 if (rule.parsedSelector === selectors[i]) {
7343 self.collectProperties(rule, props);
7344 return;
7345 }
7346 }
7347 });
7348 return props;
7349 },
7350 transformStyles: function (element, properties, scopeSelector) {
7351 var self = this;
7352 var hostSelector = styleTransformer._calcHostScope(element.is, element.extends);
7353 var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]' : hostSelector;
7354 var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFF IX);
7355 return styleTransformer.elementStyles(element, function (rule) {
7356 self.applyProperties(rule, properties);
7357 if (rule.cssText && !nativeShadow) {
7358 self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeS elector);
7359 }
7360 });
7361 },
7362 _scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) {
7363 rule.transformedSelector = rule.transformedSelector || rule.selector;
7364 var selector = rule.transformedSelector;
7365 var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' : '.' + scopeId;
7366 var parts = selector.split(',');
7367 for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) {
7368 parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : sco pe + ' ' + p;
7369 }
7370 rule.selector = parts.join(',');
7371 },
7372 applyElementScopeSelector: function (element, selector, old, viaAttr) {
7373 var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.cl assName;
7374 var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
7375 if (c !== v) {
7376 if (viaAttr) {
7377 element.setAttribute(styleTransformer.SCOPE_NAME, v);
7378 } else {
7379 element.className = v;
7380 }
7381 }
7382 },
7383 applyElementStyle: function (element, properties, selector, style) {
7384 var cssText = style ? style.textContent || '' : this.transformStyles(element, pr operties, selector);
7385 var s = element._customStyle;
7386 if (s && !nativeShadow && s !== style) {
7387 s._useCount--;
7388 if (s._useCount <= 0 && s.parentNode) {
7389 s.parentNode.removeChild(s);
7390 }
7391 }
7392 if (nativeShadow || (!style || !style.parentNode)) {
7393 if (nativeShadow && element._customStyle) {
7394 element._customStyle.textContent = cssText;
7395 style = element._customStyle;
7396 } else if (cssText) {
7397 style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null , element._scopeStyle);
7398 }
7399 }
7400 if (style) {
7401 style._useCount = style._useCount || 0;
7402 if (element._customStyle != style) {
7403 style._useCount++;
7404 }
7405 element._customStyle = style;
7406 }
7407 return style;
7408 },
7409 mixinCustomStyle: function (props, customStyle) {
7410 var v;
7411 for (var i in customStyle) {
7412 v = customStyle[i];
7413 if (v || v === 0) {
7414 props[i] = v;
7415 }
7416 }
7417 },
7418 rx: {
7419 VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/ gi,
7420 MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
7421 VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)) )[\s]*?\)/gi,
7422 VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
7423 IS_VAR: /^--/,
7424 BRACKETED: /\{[^}]*\}/g,
7425 HOST_PREFIX: '(?:^|[^.#[:])',
7426 HOST_SUFFIX: '($|[.:[\\s>+~])'
7427 },
7428 HOST_SELECTORS: [':host'],
7429 SCOPE_SELECTORS: [':root'],
7430 XSCOPE_NAME: 'x-scope'
7431 };
7432 function addToBitMask(n, bits) {
7433 var o = parseInt(n / 32);
7434 var v = 1 << n % 32;
7435 bits[o] = (bits[o] || 0) | v;
7436 }
7437 }();
7438 (function () {
7439 Polymer.StyleCache = function () {
7440 this.cache = {};
7441 };
7442 Polymer.StyleCache.prototype = {
7443 MAX: 100,
7444 store: function (is, data, keyValues, keyStyles) {
7445 data.keyValues = keyValues;
7446 data.styles = keyStyles;
7447 var s$ = this.cache[is] = this.cache[is] || [];
7448 s$.push(data);
7449 if (s$.length > this.MAX) {
7450 s$.shift();
7451 }
7452 },
7453 retrieve: function (is, keyValues, keyStyles) {
7454 var cache = this.cache[is];
7455 if (cache) {
7456 for (var i = cache.length - 1, data; i >= 0; i--) {
7457 data = cache[i];
7458 if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues)) {
7459 return data;
7460 }
7461 }
7462 }
7463 },
7464 clear: function () {
7465 this.cache = {};
7466 },
7467 _objectsEqual: function (target, source) {
7468 var t, s;
7469 for (var i in target) {
7470 t = target[i], s = source[i];
7471 if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s)) {
7472 return false;
7473 }
7474 }
7475 if (Array.isArray(target)) {
7476 return target.length === source.length;
7477 }
7478 return true;
7479 },
7480 _objectsStrictlyEqual: function (target, source) {
7481 return this._objectsEqual(target, source) && this._objectsEqual(source, target);
7482 }
7483 };
7484 }());
7485 Polymer.StyleDefaults = function () {
7486 var styleProperties = Polymer.StyleProperties;
7487 var styleUtil = Polymer.StyleUtil;
7488 var StyleCache = Polymer.StyleCache;
7489 var api = {
7490 _styles: [],
7491 _properties: null,
7492 customStyle: {},
7493 _styleCache: new StyleCache(),
7494 addStyle: function (style) {
7495 this._styles.push(style);
7496 this._properties = null;
7497 },
7498 get _styleProperties() {
7499 if (!this._properties) {
7500 styleProperties.decorateStyles(this._styles);
7501 this._styles._scopeStyleProperties = null;
7502 this._properties = styleProperties.scopePropertiesFromStyles(this._styles);
7503 styleProperties.mixinCustomStyle(this._properties, this.customStyle);
7504 styleProperties.reify(this._properties);
7505 }
7506 return this._properties;
7507 },
7508 _needsStyleProperties: function () {
7509 },
7510 _computeStyleProperties: function () {
7511 return this._styleProperties;
7512 },
7513 updateStyles: function (properties) {
7514 this._properties = null;
7515 if (properties) {
7516 Polymer.Base.mixin(this.customStyle, properties);
7517 }
7518 this._styleCache.clear();
7519 for (var i = 0, s; i < this._styles.length; i++) {
7520 s = this._styles[i];
7521 s = s.__importElement || s;
7522 s._apply();
7523 }
7524 }
7525 };
7526 return api;
7527 }();
7528 (function () {
7529 'use strict';
7530 var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
7531 var propertyUtils = Polymer.StyleProperties;
7532 var styleTransformer = Polymer.StyleTransformer;
7533 var styleUtil = Polymer.StyleUtil;
7534 var styleDefaults = Polymer.StyleDefaults;
7535 var nativeShadow = Polymer.Settings.useNativeShadow;
7536 Polymer.Base._addFeature({
7537 _prepStyleProperties: function () {
7538 this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._ styles) : [];
7539 },
7540 customStyle: {},
7541 _setupStyleProperties: function () {
7542 this.customStyle = {};
7543 },
7544 _needsStyleProperties: function () {
7545 return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length );
7546 },
7547 _beforeAttached: function () {
7548 if (!this._scopeSelector && this._needsStyleProperties()) {
7549 this._updateStyleProperties();
7550 }
7551 },
7552 _findStyleHost: function () {
7553 var e = this, root;
7554 while (root = Polymer.dom(e).getOwnerRoot()) {
7555 if (Polymer.isInstance(root.host)) {
7556 return root.host;
7557 }
7558 e = root.host;
7559 }
7560 return styleDefaults;
7561 },
7562 _updateStyleProperties: function () {
7563 var info, scope = this._findStyleHost();
7564 if (!scope._styleCache) {
7565 scope._styleCache = new Polymer.StyleCache();
7566 }
7567 var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this);
7568 scopeData.key.customStyle = this.customStyle;
7569 info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
7570 var scopeCached = Boolean(info);
7571 if (scopeCached) {
7572 this._styleProperties = info._styleProperties;
7573 } else {
7574 this._computeStyleProperties(scopeData.properties);
7575 }
7576 this._computeOwnStyleProperties();
7577 if (!scopeCached) {
7578 info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles);
7579 }
7580 var globalCached = Boolean(info) && !scopeCached;
7581 var style = this._applyStyleProperties(info);
7582 if (!scopeCached) {
7583 style = style && nativeShadow ? style.cloneNode(true) : style;
7584 info = {
7585 style: style,
7586 _scopeSelector: this._scopeSelector,
7587 _styleProperties: this._styleProperties
7588 };
7589 scopeData.key.customStyle = {};
7590 this.mixin(scopeData.key.customStyle, this.customStyle);
7591 scope._styleCache.store(this.is, info, scopeData.key, this._styles);
7592 if (!globalCached) {
7593 styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._s tyles);
7594 }
7595 }
7596 },
7597 _computeStyleProperties: function (scopeProps) {
7598 var scope = this._findStyleHost();
7599 if (!scope._styleProperties) {
7600 scope._computeStyleProperties();
7601 }
7602 var props = Object.create(scope._styleProperties);
7603 this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
7604 scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, t his).properties;
7605 this.mixin(props, scopeProps);
7606 this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles));
7607 propertyUtils.mixinCustomStyle(props, this.customStyle);
7608 propertyUtils.reify(props);
7609 this._styleProperties = props;
7610 },
7611 _computeOwnStyleProperties: function () {
7612 var props = {};
7613 for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) {
7614 n = this._ownStylePropertyNames[i];
7615 props[n] = this._styleProperties[n];
7616 }
7617 this._ownStyleProperties = props;
7618 },
7619 _scopeCount: 0,
7620 _applyStyleProperties: function (info) {
7621 var oldScopeSelector = this._scopeSelector;
7622 this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto_ _._scopeCount++;
7623 var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._s copeSelector, info && info.style);
7624 if (!nativeShadow) {
7625 propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelec tor, this._scopeCssViaAttr);
7626 }
7627 return style;
7628 },
7629 serializeValueToAttribute: function (value, attribute, node) {
7630 node = node || this;
7631 if (attribute === 'class' && !nativeShadow) {
7632 var host = node === this ? this.domHost || this.dataHost : this;
7633 if (host) {
7634 value = host._scopeElementClass(node, value);
7635 }
7636 }
7637 node = Polymer.dom(node);
7638 serializeValueToAttribute.call(this, value, attribute, node);
7639 },
7640 _scopeElementClass: function (element, selector) {
7641 if (!nativeShadow && !this._scopeCssViaAttr) {
7642 selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scope Selector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : '');
7643 }
7644 return selector;
7645 },
7646 updateStyles: function (properties) {
7647 if (this.isAttached) {
7648 if (properties) {
7649 this.mixin(this.customStyle, properties);
7650 }
7651 if (this._needsStyleProperties()) {
7652 this._updateStyleProperties();
7653 } else {
7654 this._styleProperties = null;
7655 }
7656 if (this._styleCache) {
7657 this._styleCache.clear();
7658 }
7659 this._updateRootStyles();
7660 }
7661 },
7662 _updateRootStyles: function (root) {
7663 root = root || this.root;
7664 var c$ = Polymer.dom(root)._query(function (e) {
7665 return e.shadyRoot || e.shadowRoot;
7666 });
7667 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
7668 if (c.updateStyles) {
7669 c.updateStyles();
7670 }
7671 }
7672 }
7673 });
7674 Polymer.updateStyles = function (properties) {
7675 styleDefaults.updateStyles(properties);
7676 Polymer.Base._updateRootStyles(document);
7677 };
7678 var styleCache = new Polymer.StyleCache();
7679 Polymer.customStyleCache = styleCache;
7680 var SCOPE_NAME = styleTransformer.SCOPE_NAME;
7681 var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
7682 }());
7683 Polymer.Base._addFeature({
7684 _registerFeatures: function () {
7685 this._prepIs();
7686 this._prepAttributes();
7687 this._prepConstructor();
7688 this._prepTemplate();
7689 this._prepStyles();
7690 this._prepStyleProperties();
7691 this._prepAnnotations();
7692 this._prepEffects();
7693 this._prepBehaviors();
7694 this._prepBindings();
7695 this._prepShady();
7696 },
7697 _prepBehavior: function (b) {
7698 this._addPropertyEffects(b.properties);
7699 this._addComplexObserverEffects(b.observers);
7700 this._addHostAttributes(b.hostAttributes);
7701 },
7702 _initFeatures: function () {
7703 this._poolContent();
7704 this._setupConfigure();
7705 this._setupStyleProperties();
7706 this._pushHost();
7707 this._stampTemplate();
7708 this._popHost();
7709 this._marshalAnnotationReferences();
7710 this._setupDebouncers();
7711 this._marshalInstanceEffects();
7712 this._marshalHostAttributes();
7713 this._marshalBehaviors();
7714 this._marshalAttributes();
7715 this._tryReady();
7716 },
7717 _marshalBehavior: function (b) {
7718 this._listenListeners(b.listeners);
7719 }
7720 });
7721 (function () {
7722 var nativeShadow = Polymer.Settings.useNativeShadow;
7723 var propertyUtils = Polymer.StyleProperties;
7724 var styleUtil = Polymer.StyleUtil;
7725 var cssParse = Polymer.CssParse;
7726 var styleDefaults = Polymer.StyleDefaults;
7727 var styleTransformer = Polymer.StyleTransformer;
7728 Polymer({
7729 is: 'custom-style',
7730 extends: 'style',
7731 properties: { include: String },
7732 ready: function () {
7733 this._tryApply();
7734 },
7735 attached: function () {
7736 this._tryApply();
7737 },
7738 _tryApply: function () {
7739 if (!this._appliesToDocument) {
7740 if (this.parentNode && this.parentNode.localName !== 'dom-module') {
7741 this._appliesToDocument = true;
7742 var e = this.__appliedElement || this;
7743 styleDefaults.addStyle(e);
7744 if (e.textContent || this.include) {
7745 this._apply();
7746 } else {
7747 var observer = new MutationObserver(function () {
7748 observer.disconnect();
7749 this._apply();
7750 }.bind(this));
7751 observer.observe(e, { childList: true });
7752 }
7753 }
7754 }
7755 },
7756 _apply: function () {
7757 var e = this.__appliedElement || this;
7758 if (this.include) {
7759 e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent;
7760 }
7761 if (e.textContent) {
7762 styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) {
7763 styleTransformer.documentRule(rule);
7764 });
7765 this._applyCustomProperties(e);
7766 }
7767 },
7768 _applyCustomProperties: function (element) {
7769 this._computeStyleProperties();
7770 var props = this._styleProperties;
7771 var rules = styleUtil.rulesForStyle(element);
7772 element.textContent = styleUtil.toCssText(rules, function (rule) {
7773 var css = rule.cssText = rule.parsedCssText;
7774 if (rule.propertyInfo && rule.propertyInfo.cssText) {
7775 css = cssParse.removeCustomPropAssignment(css);
7776 rule.cssText = propertyUtils.valueForProperties(css, props);
7777 }
7778 });
7779 }
7780 });
7781 }());
7782 Polymer.Templatizer = {
7783 properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } },
7784 _instanceProps: Polymer.nob,
7785 _parentPropPrefix: '_parent_',
7786 templatize: function (template) {
7787 if (!template._content) {
7788 template._content = template.content;
7789 }
7790 if (template._content._ctor) {
7791 this.ctor = template._content._ctor;
7792 this._prepParentProperties(this.ctor.prototype, template);
7793 return;
7794 }
7795 var archetype = Object.create(Polymer.Base);
7796 this._customPrepAnnotations(archetype, template);
7797 archetype._prepEffects();
7798 this._customPrepEffects(archetype);
7799 archetype._prepBehaviors();
7800 archetype._prepBindings();
7801 this._prepParentProperties(archetype, template);
7802 archetype._notifyPath = this._notifyPathImpl;
7803 archetype._scopeElementClass = this._scopeElementClassImpl;
7804 archetype.listen = this._listenImpl;
7805 archetype._showHideChildren = this._showHideChildrenImpl;
7806 var _constructor = this._constructorImpl;
7807 var ctor = function TemplateInstance(model, host) {
7808 _constructor.call(this, model, host);
7809 };
7810 ctor.prototype = archetype;
7811 archetype.constructor = ctor;
7812 template._content._ctor = ctor;
7813 this.ctor = ctor;
7814 },
7815 _getRootDataHost: function () {
7816 return this.dataHost && this.dataHost._rootDataHost || this.dataHost;
7817 },
7818 _showHideChildrenImpl: function (hide) {
7819 var c = this._children;
7820 for (var i = 0; i < c.length; i++) {
7821 var n = c[i];
7822 if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
7823 if (n.nodeType === Node.TEXT_NODE) {
7824 if (hide) {
7825 n.__polymerTextContent__ = n.textContent;
7826 n.textContent = '';
7827 } else {
7828 n.textContent = n.__polymerTextContent__;
7829 }
7830 } else if (n.style) {
7831 if (hide) {
7832 n.__polymerDisplay__ = n.style.display;
7833 n.style.display = 'none';
7834 } else {
7835 n.style.display = n.__polymerDisplay__;
7836 }
7837 }
7838 }
7839 n.__hideTemplateChildren__ = hide;
7840 }
7841 },
7842 _debounceTemplate: function (fn) {
7843 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn));
7844 },
7845 _flushTemplates: function (debouncerExpired) {
7846 Polymer.dom.flush();
7847 },
7848 _customPrepEffects: function (archetype) {
7849 var parentProps = archetype._parentProps;
7850 for (var prop in parentProps) {
7851 archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop ));
7852 }
7853 for (var prop in this._instanceProps) {
7854 archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector( prop));
7855 }
7856 },
7857 _customPrepAnnotations: function (archetype, template) {
7858 archetype._template = template;
7859 var c = template._content;
7860 if (!c._notes) {
7861 var rootDataHost = archetype._rootDataHost;
7862 if (rootDataHost) {
7863 Polymer.Annotations.prepElement = rootDataHost._prepElement.bind(rootDataHost);
7864 }
7865 c._notes = Polymer.Annotations.parseAnnotations(template);
7866 Polymer.Annotations.prepElement = null;
7867 this._processAnnotations(c._notes);
7868 }
7869 archetype._notes = c._notes;
7870 archetype._parentProps = c._parentProps;
7871 },
7872 _prepParentProperties: function (archetype, template) {
7873 var parentProps = this._parentProps = archetype._parentProps;
7874 if (this._forwardParentProp && parentProps) {
7875 var proto = archetype._parentPropProto;
7876 var prop;
7877 if (!proto) {
7878 for (prop in this._instanceProps) {
7879 delete parentProps[prop];
7880 }
7881 proto = archetype._parentPropProto = Object.create(null);
7882 if (template != this) {
7883 Polymer.Bind.prepareModel(proto);
7884 }
7885 for (prop in parentProps) {
7886 var parentProp = this._parentPropPrefix + prop;
7887 var effects = [
7888 {
7889 kind: 'function',
7890 effect: this._createForwardPropEffector(prop)
7891 },
7892 { kind: 'notify' }
7893 ];
7894 Polymer.Bind._createAccessors(proto, parentProp, effects);
7895 }
7896 }
7897 if (template != this) {
7898 Polymer.Bind.prepareInstance(template);
7899 template._forwardParentProp = this._forwardParentProp.bind(this);
7900 }
7901 this._extendTemplate(template, proto);
7902 }
7903 },
7904 _createForwardPropEffector: function (prop) {
7905 return function (source, value) {
7906 this._forwardParentProp(prop, value);
7907 };
7908 },
7909 _createHostPropEffector: function (prop) {
7910 var prefix = this._parentPropPrefix;
7911 return function (source, value) {
7912 this.dataHost[prefix + prop] = value;
7913 };
7914 },
7915 _createInstancePropEffector: function (prop) {
7916 return function (source, value, old, fromAbove) {
7917 if (!fromAbove) {
7918 this.dataHost._forwardInstanceProp(this, prop, value);
7919 }
7920 };
7921 },
7922 _extendTemplate: function (template, proto) {
7923 Object.getOwnPropertyNames(proto).forEach(function (n) {
7924 var val = template[n];
7925 var pd = Object.getOwnPropertyDescriptor(proto, n);
7926 Object.defineProperty(template, n, pd);
7927 if (val !== undefined) {
7928 template._propertySetter(n, val);
7929 }
7930 });
7931 },
7932 _showHideChildren: function (hidden) {
7933 },
7934 _forwardInstancePath: function (inst, path, value) {
7935 },
7936 _forwardInstanceProp: function (inst, prop, value) {
7937 },
7938 _notifyPathImpl: function (path, value) {
7939 var dataHost = this.dataHost;
7940 var dot = path.indexOf('.');
7941 var root = dot < 0 ? path : path.slice(0, dot);
7942 dataHost._forwardInstancePath.call(dataHost, this, path, value);
7943 if (root in dataHost._parentProps) {
7944 dataHost.notifyPath(dataHost._parentPropPrefix + path, value);
7945 }
7946 },
7947 _pathEffector: function (path, value, fromAbove) {
7948 if (this._forwardParentPath) {
7949 if (path.indexOf(this._parentPropPrefix) === 0) {
7950 this._forwardParentPath(path.substring(8), value);
7951 }
7952 }
7953 Polymer.Base._pathEffector.apply(this, arguments);
7954 },
7955 _constructorImpl: function (model, host) {
7956 this._rootDataHost = host._getRootDataHost();
7957 this._setupConfigure(model);
7958 this._pushHost(host);
7959 this.root = this.instanceTemplate(this._template);
7960 this.root.__noContent = !this._notes._hasContent;
7961 this.root.__styleScoped = true;
7962 this._popHost();
7963 this._marshalAnnotatedNodes();
7964 this._marshalInstanceEffects();
7965 this._marshalAnnotatedListeners();
7966 var children = [];
7967 for (var n = this.root.firstChild; n; n = n.nextSibling) {
7968 children.push(n);
7969 n._templateInstance = this;
7970 }
7971 this._children = children;
7972 if (host.__hideTemplateChildren__) {
7973 this._showHideChildren(true);
7974 }
7975 this._tryReady();
7976 },
7977 _listenImpl: function (node, eventName, methodName) {
7978 var model = this;
7979 var host = this._rootDataHost;
7980 var handler = host._createEventHandler(node, eventName, methodName);
7981 var decorated = function (e) {
7982 e.model = model;
7983 handler(e);
7984 };
7985 host._listen(node, eventName, decorated);
7986 },
7987 _scopeElementClassImpl: function (node, value) {
7988 var host = this._rootDataHost;
7989 if (host) {
7990 return host._scopeElementClass(node, value);
7991 }
7992 },
7993 stamp: function (model) {
7994 model = model || {};
7995 if (this._parentProps) {
7996 for (var prop in this._parentProps) {
7997 model[prop] = this[this._parentPropPrefix + prop];
7998 }
7999 }
8000 return new this.ctor(model, this);
8001 },
8002 modelForElement: function (el) {
8003 var model;
8004 while (el) {
8005 if (model = el._templateInstance) {
8006 if (model.dataHost != this) {
8007 el = model.dataHost;
8008 } else {
8009 return model;
8010 }
8011 } else {
8012 el = el.parentNode;
8013 }
8014 }
8015 }
8016 };
8017 Polymer({
8018 is: 'dom-template',
8019 extends: 'template',
8020 behaviors: [Polymer.Templatizer],
8021 ready: function () {
8022 this.templatize(this);
8023 }
8024 });
8025 Polymer._collections = new WeakMap();
8026 Polymer.Collection = function (userArray) {
8027 Polymer._collections.set(userArray, this);
8028 this.userArray = userArray;
8029 this.store = userArray.slice();
8030 this.initMap();
8031 };
8032 Polymer.Collection.prototype = {
8033 constructor: Polymer.Collection,
8034 initMap: function () {
8035 var omap = this.omap = new WeakMap();
8036 var pmap = this.pmap = {};
8037 var s = this.store;
8038 for (var i = 0; i < s.length; i++) {
8039 var item = s[i];
8040 if (item && typeof item == 'object') {
8041 omap.set(item, i);
8042 } else {
8043 pmap[item] = i;
8044 }
8045 }
8046 },
8047 add: function (item) {
8048 var key = this.store.push(item) - 1;
8049 if (item && typeof item == 'object') {
8050 this.omap.set(item, key);
8051 } else {
8052 this.pmap[item] = key;
8053 }
8054 return key;
8055 },
8056 removeKey: function (key) {
8057 this._removeFromMap(this.store[key]);
8058 delete this.store[key];
8059 },
8060 _removeFromMap: function (item) {
8061 if (item && typeof item == 'object') {
8062 this.omap.delete(item);
8063 } else {
8064 delete this.pmap[item];
8065 }
8066 },
8067 remove: function (item) {
8068 var key = this.getKey(item);
8069 this.removeKey(key);
8070 return key;
8071 },
8072 getKey: function (item) {
8073 if (item && typeof item == 'object') {
8074 return this.omap.get(item);
8075 } else {
8076 return this.pmap[item];
8077 }
8078 },
8079 getKeys: function () {
8080 return Object.keys(this.store);
8081 },
8082 setItem: function (key, item) {
8083 var old = this.store[key];
8084 if (old) {
8085 this._removeFromMap(old);
8086 }
8087 if (item && typeof item == 'object') {
8088 this.omap.set(item, key);
8089 } else {
8090 this.pmap[item] = key;
8091 }
8092 this.store[key] = item;
8093 },
8094 getItem: function (key) {
8095 return this.store[key];
8096 },
8097 getItems: function () {
8098 var items = [], store = this.store;
8099 for (var key in store) {
8100 items.push(store[key]);
8101 }
8102 return items;
8103 },
8104 _applySplices: function (splices) {
8105 var keyMap = {}, key, i;
8106 splices.forEach(function (s) {
8107 s.addedKeys = [];
8108 for (i = 0; i < s.removed.length; i++) {
8109 key = this.getKey(s.removed[i]);
8110 keyMap[key] = keyMap[key] ? null : -1;
8111 }
8112 for (i = 0; i < s.addedCount; i++) {
8113 var item = this.userArray[s.index + i];
8114 key = this.getKey(item);
8115 key = key === undefined ? this.add(item) : key;
8116 keyMap[key] = keyMap[key] ? null : 1;
8117 s.addedKeys.push(key);
8118 }
8119 }, this);
8120 var removed = [];
8121 var added = [];
8122 for (var key in keyMap) {
8123 if (keyMap[key] < 0) {
8124 this.removeKey(key);
8125 removed.push(key);
8126 }
8127 if (keyMap[key] > 0) {
8128 added.push(key);
8129 }
8130 }
8131 return [{
8132 removed: removed,
8133 added: added
8134 }];
8135 }
8136 };
8137 Polymer.Collection.get = function (userArray) {
8138 return Polymer._collections.get(userArray) || new Polymer.Collection(userArray);
8139 };
8140 Polymer.Collection.applySplices = function (userArray, splices) {
8141 var coll = Polymer._collections.get(userArray);
8142 return coll ? coll._applySplices(splices) : null;
8143 };
8144 Polymer({
8145 is: 'dom-repeat',
8146 extends: 'template',
8147 properties: {
8148 items: { type: Array },
8149 as: {
8150 type: String,
8151 value: 'item'
8152 },
8153 indexAs: {
8154 type: String,
8155 value: 'index'
8156 },
8157 sort: {
8158 type: Function,
8159 observer: '_sortChanged'
8160 },
8161 filter: {
8162 type: Function,
8163 observer: '_filterChanged'
8164 },
8165 observe: {
8166 type: String,
8167 observer: '_observeChanged'
8168 },
8169 delay: Number
8170 },
8171 behaviors: [Polymer.Templatizer],
8172 observers: ['_itemsChanged(items.*)'],
8173 created: function () {
8174 this._instances = [];
8175 },
8176 detached: function () {
8177 for (var i = 0; i < this._instances.length; i++) {
8178 this._detachRow(i);
8179 }
8180 },
8181 attached: function () {
8182 var parentNode = Polymer.dom(this).parentNode;
8183 for (var i = 0; i < this._instances.length; i++) {
8184 Polymer.dom(parentNode).insertBefore(this._instances[i].root, this);
8185 }
8186 },
8187 ready: function () {
8188 this._instanceProps = { __key__: true };
8189 this._instanceProps[this.as] = true;
8190 this._instanceProps[this.indexAs] = true;
8191 if (!this.ctor) {
8192 this.templatize(this);
8193 }
8194 },
8195 _sortChanged: function () {
8196 var dataHost = this._getRootDataHost();
8197 var sort = this.sort;
8198 this._sortFn = sort && (typeof sort == 'function' ? sort : function () {
8199 return dataHost[sort].apply(dataHost, arguments);
8200 });
8201 this._needFullRefresh = true;
8202 if (this.items) {
8203 this._debounceTemplate(this._render);
8204 }
8205 },
8206 _filterChanged: function () {
8207 var dataHost = this._getRootDataHost();
8208 var filter = this.filter;
8209 this._filterFn = filter && (typeof filter == 'function' ? filter : function () {
8210 return dataHost[filter].apply(dataHost, arguments);
8211 });
8212 this._needFullRefresh = true;
8213 if (this.items) {
8214 this._debounceTemplate(this._render);
8215 }
8216 },
8217 _observeChanged: function () {
8218 this._observePaths = this.observe && this.observe.replace('.*', '.').split(' ');
8219 },
8220 _itemsChanged: function (change) {
8221 if (change.path == 'items') {
8222 if (Array.isArray(this.items)) {
8223 this.collection = Polymer.Collection.get(this.items);
8224 } else if (!this.items) {
8225 this.collection = null;
8226 } else {
8227 this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', t his.items));
8228 }
8229 this._keySplices = [];
8230 this._indexSplices = [];
8231 this._needFullRefresh = true;
8232 this._debounceTemplate(this._render);
8233 } else if (change.path == 'items.splices') {
8234 this._keySplices = this._keySplices.concat(change.value.keySplices);
8235 this._indexSplices = this._indexSplices.concat(change.value.indexSplices);
8236 this._debounceTemplate(this._render);
8237 } else {
8238 var subpath = change.path.slice(6);
8239 this._forwardItemPath(subpath, change.value);
8240 this._checkObservedPaths(subpath);
8241 }
8242 },
8243 _checkObservedPaths: function (path) {
8244 if (this._observePaths) {
8245 path = path.substring(path.indexOf('.') + 1);
8246 var paths = this._observePaths;
8247 for (var i = 0; i < paths.length; i++) {
8248 if (path.indexOf(paths[i]) === 0) {
8249 this._needFullRefresh = true;
8250 if (this.delay) {
8251 this.debounce('render', this._render, this.delay);
8252 } else {
8253 this._debounceTemplate(this._render);
8254 }
8255 return;
8256 }
8257 }
8258 }
8259 },
8260 render: function () {
8261 this._needFullRefresh = true;
8262 this._debounceTemplate(this._render);
8263 this._flushTemplates();
8264 },
8265 _render: function () {
8266 var c = this.collection;
8267 if (this._needFullRefresh) {
8268 this._applyFullRefresh();
8269 this._needFullRefresh = false;
8270 } else {
8271 if (this._sortFn) {
8272 this._applySplicesUserSort(this._keySplices);
8273 } else {
8274 if (this._filterFn) {
8275 this._applyFullRefresh();
8276 } else {
8277 this._applySplicesArrayOrder(this._indexSplices);
8278 }
8279 }
8280 }
8281 this._keySplices = [];
8282 this._indexSplices = [];
8283 var keyToIdx = this._keyToInstIdx = {};
8284 for (var i = 0; i < this._instances.length; i++) {
8285 var inst = this._instances[i];
8286 keyToIdx[inst.__key__] = i;
8287 inst.__setProperty(this.indexAs, i, true);
8288 }
8289 this.fire('dom-change');
8290 },
8291 _applyFullRefresh: function () {
8292 var c = this.collection;
8293 var keys;
8294 if (this._sortFn) {
8295 keys = c ? c.getKeys() : [];
8296 } else {
8297 keys = [];
8298 var items = this.items;
8299 if (items) {
8300 for (var i = 0; i < items.length; i++) {
8301 keys.push(c.getKey(items[i]));
8302 }
8303 }
8304 }
8305 if (this._filterFn) {
8306 keys = keys.filter(function (a) {
8307 return this._filterFn(c.getItem(a));
8308 }, this);
8309 }
8310 if (this._sortFn) {
8311 keys.sort(function (a, b) {
8312 return this._sortFn(c.getItem(a), c.getItem(b));
8313 }.bind(this));
8314 }
8315 for (var i = 0; i < keys.length; i++) {
8316 var key = keys[i];
8317 var inst = this._instances[i];
8318 if (inst) {
8319 inst.__setProperty('__key__', key, true);
8320 inst.__setProperty(this.as, c.getItem(key), true);
8321 } else {
8322 this._instances.push(this._insertRow(i, key));
8323 }
8324 }
8325 for (; i < this._instances.length; i++) {
8326 this._detachRow(i);
8327 }
8328 this._instances.splice(keys.length, this._instances.length - keys.length);
8329 },
8330 _keySort: function (a, b) {
8331 return this.collection.getKey(a) - this.collection.getKey(b);
8332 },
8333 _numericSort: function (a, b) {
8334 return a - b;
8335 },
8336 _applySplicesUserSort: function (splices) {
8337 var c = this.collection;
8338 var instances = this._instances;
8339 var keyMap = {};
8340 var pool = [];
8341 var sortFn = this._sortFn || this._keySort.bind(this);
8342 splices.forEach(function (s) {
8343 for (var i = 0; i < s.removed.length; i++) {
8344 var key = s.removed[i];
8345 keyMap[key] = keyMap[key] ? null : -1;
8346 }
8347 for (var i = 0; i < s.added.length; i++) {
8348 var key = s.added[i];
8349 keyMap[key] = keyMap[key] ? null : 1;
8350 }
8351 }, this);
8352 var removedIdxs = [];
8353 var addedKeys = [];
8354 for (var key in keyMap) {
8355 if (keyMap[key] === -1) {
8356 removedIdxs.push(this._keyToInstIdx[key]);
8357 }
8358 if (keyMap[key] === 1) {
8359 addedKeys.push(key);
8360 }
8361 }
8362 if (removedIdxs.length) {
8363 removedIdxs.sort(this._numericSort);
8364 for (var i = removedIdxs.length - 1; i >= 0; i--) {
8365 var idx = removedIdxs[i];
8366 if (idx !== undefined) {
8367 pool.push(this._detachRow(idx));
8368 instances.splice(idx, 1);
8369 }
8370 }
8371 }
8372 if (addedKeys.length) {
8373 if (this._filterFn) {
8374 addedKeys = addedKeys.filter(function (a) {
8375 return this._filterFn(c.getItem(a));
8376 }, this);
8377 }
8378 addedKeys.sort(function (a, b) {
8379 return this._sortFn(c.getItem(a), c.getItem(b));
8380 }.bind(this));
8381 var start = 0;
8382 for (var i = 0; i < addedKeys.length; i++) {
8383 start = this._insertRowUserSort(start, addedKeys[i], pool);
8384 }
8385 }
8386 },
8387 _insertRowUserSort: function (start, key, pool) {
8388 var c = this.collection;
8389 var item = c.getItem(key);
8390 var end = this._instances.length - 1;
8391 var idx = -1;
8392 var sortFn = this._sortFn || this._keySort.bind(this);
8393 while (start <= end) {
8394 var mid = start + end >> 1;
8395 var midKey = this._instances[mid].__key__;
8396 var cmp = sortFn(c.getItem(midKey), item);
8397 if (cmp < 0) {
8398 start = mid + 1;
8399 } else if (cmp > 0) {
8400 end = mid - 1;
8401 } else {
8402 idx = mid;
8403 break;
8404 }
8405 }
8406 if (idx < 0) {
8407 idx = end + 1;
8408 }
8409 this._instances.splice(idx, 0, this._insertRow(idx, key, pool));
8410 return idx;
8411 },
8412 _applySplicesArrayOrder: function (splices) {
8413 var pool = [];
8414 var c = this.collection;
8415 splices.forEach(function (s) {
8416 for (var i = 0; i < s.removed.length; i++) {
8417 var inst = this._detachRow(s.index + i);
8418 if (!inst.isPlaceholder) {
8419 pool.push(inst);
8420 }
8421 }
8422 this._instances.splice(s.index, s.removed.length);
8423 for (var i = 0; i < s.addedKeys.length; i++) {
8424 var inst = {
8425 isPlaceholder: true,
8426 key: s.addedKeys[i]
8427 };
8428 this._instances.splice(s.index + i, 0, inst);
8429 }
8430 }, this);
8431 for (var i = this._instances.length - 1; i >= 0; i--) {
8432 var inst = this._instances[i];
8433 if (inst.isPlaceholder) {
8434 this._instances[i] = this._insertRow(i, inst.key, pool, true);
8435 }
8436 }
8437 },
8438 _detachRow: function (idx) {
8439 var inst = this._instances[idx];
8440 if (!inst.isPlaceholder) {
8441 var parentNode = Polymer.dom(this).parentNode;
8442 for (var i = 0; i < inst._children.length; i++) {
8443 var el = inst._children[i];
8444 Polymer.dom(inst.root).appendChild(el);
8445 }
8446 }
8447 return inst;
8448 },
8449 _insertRow: function (idx, key, pool, replace) {
8450 var inst;
8451 if (inst = pool && pool.pop()) {
8452 inst.__setProperty(this.as, this.collection.getItem(key), true);
8453 inst.__setProperty('__key__', key, true);
8454 } else {
8455 inst = this._generateRow(idx, key);
8456 }
8457 var beforeRow = this._instances[replace ? idx + 1 : idx];
8458 var beforeNode = beforeRow ? beforeRow._children[0] : this;
8459 var parentNode = Polymer.dom(this).parentNode;
8460 Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
8461 return inst;
8462 },
8463 _generateRow: function (idx, key) {
8464 var model = { __key__: key };
8465 model[this.as] = this.collection.getItem(key);
8466 model[this.indexAs] = idx;
8467 var inst = this.stamp(model);
8468 return inst;
8469 },
8470 _showHideChildren: function (hidden) {
8471 for (var i = 0; i < this._instances.length; i++) {
8472 this._instances[i]._showHideChildren(hidden);
8473 }
8474 },
8475 _forwardInstanceProp: function (inst, prop, value) {
8476 if (prop == this.as) {
8477 var idx;
8478 if (this._sortFn || this._filterFn) {
8479 idx = this.items.indexOf(this.collection.getItem(inst.__key__));
8480 } else {
8481 idx = inst[this.indexAs];
8482 }
8483 this.set('items.' + idx, value);
8484 }
8485 },
8486 _forwardInstancePath: function (inst, path, value) {
8487 if (path.indexOf(this.as + '.') === 0) {
8488 this.notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1), value);
8489 }
8490 },
8491 _forwardParentProp: function (prop, value) {
8492 this._instances.forEach(function (inst) {
8493 inst.__setProperty(prop, value, true);
8494 }, this);
8495 },
8496 _forwardParentPath: function (path, value) {
8497 this._instances.forEach(function (inst) {
8498 inst.notifyPath(path, value, true);
8499 }, this);
8500 },
8501 _forwardItemPath: function (path, value) {
8502 if (this._keyToInstIdx) {
8503 var dot = path.indexOf('.');
8504 var key = path.substring(0, dot < 0 ? path.length : dot);
8505 var idx = this._keyToInstIdx[key];
8506 var inst = this._instances[idx];
8507 if (inst) {
8508 if (dot >= 0) {
8509 path = this.as + '.' + path.substring(dot + 1);
8510 inst.notifyPath(path, value, true);
8511 } else {
8512 inst.__setProperty(this.as, value, true);
8513 }
8514 }
8515 }
8516 },
8517 itemForElement: function (el) {
8518 var instance = this.modelForElement(el);
8519 return instance && instance[this.as];
8520 },
8521 keyForElement: function (el) {
8522 var instance = this.modelForElement(el);
8523 return instance && instance.__key__;
8524 },
8525 indexForElement: function (el) {
8526 var instance = this.modelForElement(el);
8527 return instance && instance[this.indexAs];
8528 }
8529 });
8530 Polymer({
8531 is: 'array-selector',
8532 properties: {
8533 items: {
8534 type: Array,
8535 observer: 'clearSelection'
8536 },
8537 multi: {
8538 type: Boolean,
8539 value: false,
8540 observer: 'clearSelection'
8541 },
8542 selected: {
8543 type: Object,
8544 notify: true
8545 },
8546 selectedItem: {
8547 type: Object,
8548 notify: true
8549 },
8550 toggle: {
8551 type: Boolean,
8552 value: false
8553 }
8554 },
8555 clearSelection: function () {
8556 if (Array.isArray(this.selected)) {
8557 for (var i = 0; i < this.selected.length; i++) {
8558 this.unlinkPaths('selected.' + i);
8559 }
8560 } else {
8561 this.unlinkPaths('selected');
8562 }
8563 if (this.multi) {
8564 if (!this.selected || this.selected.length) {
8565 this.selected = [];
8566 this._selectedColl = Polymer.Collection.get(this.selected);
8567 }
8568 } else {
8569 this.selected = null;
8570 this._selectedColl = null;
8571 }
8572 this.selectedItem = null;
8573 },
8574 isSelected: function (item) {
8575 if (this.multi) {
8576 return this._selectedColl.getKey(item) !== undefined;
8577 } else {
8578 return this.selected == item;
8579 }
8580 },
8581 deselect: function (item) {
8582 if (this.multi) {
8583 if (this.isSelected(item)) {
8584 var skey = this._selectedColl.getKey(item);
8585 this.arrayDelete('selected', item);
8586 this.unlinkPaths('selected.' + skey);
8587 }
8588 } else {
8589 this.selected = null;
8590 this.selectedItem = null;
8591 this.unlinkPaths('selected');
8592 this.unlinkPaths('selectedItem');
8593 }
8594 },
8595 select: function (item) {
8596 var icol = Polymer.Collection.get(this.items);
8597 var key = icol.getKey(item);
8598 if (this.multi) {
8599 if (this.isSelected(item)) {
8600 if (this.toggle) {
8601 this.deselect(item);
8602 }
8603 } else {
8604 this.push('selected', item);
8605 skey = this._selectedColl.getKey(item);
8606 this.linkPaths('selected.' + skey, 'items.' + key);
8607 }
8608 } else {
8609 if (this.toggle && item == this.selected) {
8610 this.deselect();
8611 } else {
8612 this.selected = item;
8613 this.selectedItem = item;
8614 this.linkPaths('selected', 'items.' + key);
8615 this.linkPaths('selectedItem', 'items.' + key);
8616 }
8617 }
8618 }
8619 });
8620 Polymer({
8621 is: 'dom-if',
8622 extends: 'template',
8623 properties: {
8624 'if': {
8625 type: Boolean,
8626 value: false,
8627 observer: '_queueRender'
8628 },
8629 restamp: {
8630 type: Boolean,
8631 value: false,
8632 observer: '_queueRender'
8633 }
8634 },
8635 behaviors: [Polymer.Templatizer],
8636 _queueRender: function () {
8637 this._debounceTemplate(this._render);
8638 },
8639 detached: function () {
8640 this._teardownInstance();
8641 },
8642 attached: function () {
8643 if (this.if && this.ctor) {
8644 this.async(this._ensureInstance);
8645 }
8646 },
8647 render: function () {
8648 this._flushTemplates();
8649 },
8650 _render: function () {
8651 if (this.if) {
8652 if (!this.ctor) {
8653 this.templatize(this);
8654 }
8655 this._ensureInstance();
8656 this._showHideChildren();
8657 } else if (this.restamp) {
8658 this._teardownInstance();
8659 }
8660 if (!this.restamp && this._instance) {
8661 this._showHideChildren();
8662 }
8663 if (this.if != this._lastIf) {
8664 this.fire('dom-change');
8665 this._lastIf = this.if;
8666 }
8667 },
8668 _ensureInstance: function () {
8669 if (!this._instance) {
8670 this._instance = this.stamp();
8671 var root = this._instance.root;
8672 var parent = Polymer.dom(Polymer.dom(this).parentNode);
8673 parent.insertBefore(root, this);
8674 }
8675 },
8676 _teardownInstance: function () {
8677 if (this._instance) {
8678 var c = this._instance._children;
8679 if (c) {
8680 var parent = Polymer.dom(Polymer.dom(c[0]).parentNode);
8681 c.forEach(function (n) {
8682 parent.removeChild(n);
8683 });
8684 }
8685 this._instance = null;
8686 }
8687 },
8688 _showHideChildren: function () {
8689 var hidden = this.__hideTemplateChildren__ || !this.if;
8690 if (this._instance) {
8691 this._instance._showHideChildren(hidden);
8692 }
8693 },
8694 _forwardParentProp: function (prop, value) {
8695 if (this._instance) {
8696 this._instance[prop] = value;
8697 }
8698 },
8699 _forwardParentPath: function (path, value) {
8700 if (this._instance) {
8701 this._instance.notifyPath(path, value, true);
8702 }
8703 }
8704 });
8705 Polymer({
8706 is: 'dom-bind',
8707 extends: 'template',
8708 created: function () {
8709 Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this));
8710 },
8711 _ensureReady: function () {
8712 if (!this._readied) {
8713 this._readySelf();
8714 }
8715 },
8716 _markImportsReady: function () {
8717 this._importsReady = true;
8718 this._ensureReady();
8719 },
8720 _registerFeatures: function () {
8721 this._prepConstructor();
8722 },
8723 _insertChildren: function () {
8724 var parentDom = Polymer.dom(Polymer.dom(this).parentNode);
8725 parentDom.insertBefore(this.root, this);
8726 },
8727 _removeChildren: function () {
8728 if (this._children) {
8729 for (var i = 0; i < this._children.length; i++) {
8730 this.root.appendChild(this._children[i]);
8731 }
8732 }
8733 },
8734 _initFeatures: function () {
8735 },
8736 _scopeElementClass: function (element, selector) {
8737 if (this.dataHost) {
8738 return this.dataHost._scopeElementClass(element, selector);
8739 } else {
8740 return selector;
8741 }
8742 },
8743 _prepConfigure: function () {
8744 var config = {};
8745 for (var prop in this._propertyEffects) {
8746 config[prop] = this[prop];
8747 }
8748 this._setupConfigure = this._setupConfigure.bind(this, config);
8749 },
8750 attached: function () {
8751 if (this._importsReady) {
8752 this.render();
8753 }
8754 },
8755 detached: function () {
8756 this._removeChildren();
8757 },
8758 render: function () {
8759 this._ensureReady();
8760 if (!this._children) {
8761 this._template = this;
8762 this._prepAnnotations();
8763 this._prepEffects();
8764 this._prepBehaviors();
8765 this._prepConfigure();
8766 this._prepBindings();
8767 Polymer.Base._initFeatures.call(this);
8768 this._children = Array.prototype.slice.call(this.root.childNodes);
8769 }
8770 this._insertChildren();
8771 this.fire('dom-change');
8772 }
8773 });
8774 (function() {
8775
8776 'use strict';
8777
8778 var SHADOW_WHEN_SCROLLING = 1;
8779 var SHADOW_ALWAYS = 2;
8780
8781
8782 var MODE_CONFIGS = {
8783
8784 outerScroll: {
8785 'scroll': true
8786 },
8787
8788 shadowMode: {
8789 'standard': SHADOW_ALWAYS,
8790 'waterfall': SHADOW_WHEN_SCROLLING,
8791 'waterfall-tall': SHADOW_WHEN_SCROLLING
8792 },
8793
8794 tallMode: {
8795 'waterfall-tall': true
8796 }
8797 };
8798
8799 Polymer({
8800
8801 is: 'paper-header-panel',
8802
8803 /**
8804 * Fired when the content has been scrolled. `event.detail.target` return s
8805 * the scrollable element which you can use to access scroll info such as
8806 * `scrollTop`.
8807 *
8808 * <paper-header-panel on-content-scroll="scrollHandler">
8809 * ...
8810 * </paper-header-panel>
8811 *
8812 *
8813 * scrollHandler: function(event) {
8814 * var scroller = event.detail.target;
8815 * console.log(scroller.scrollTop);
8816 * }
8817 *
8818 * @event content-scroll
8819 */
8820
8821 properties: {
8822
8823 /**
8824 * Controls header and scrolling behavior. Options are
8825 * `standard`, `seamed`, `waterfall`, `waterfall-tall`, `scroll` and
8826 * `cover`. Default is `standard`.
8827 *
8828 * `standard`: The header is a step above the panel. The header will con sume the
8829 * panel at the point of entry, preventing it from passing through to th e
8830 * opposite side.
8831 *
8832 * `seamed`: The header is presented as seamed with the panel.
8833 *
8834 * `waterfall`: Similar to standard mode, but header is initially presen ted as
8835 * seamed with panel, but then separates to form the step.
8836 *
8837 * `waterfall-tall`: The header is initially taller (`tall` class is add ed to
8838 * the header). As the user scrolls, the header separates (forming an e dge)
8839 * while condensing (`tall` class is removed from the header).
8840 *
8841 * `scroll`: The header keeps its seam with the panel, and is pushed off screen.
8842 *
8843 * `cover`: The panel covers the whole `paper-header-panel` including th e
8844 * header. This allows user to style the panel in such a way that the pa nel is
8845 * partially covering the header.
8846 *
8847 * <paper-header-panel mode="cover">
8848 * <paper-toolbar class="tall">
8849 * <core-icon-button icon="menu"></core-icon-button>
8850 * </paper-toolbar>
8851 * <div class="content"></div>
8852 * </paper-header-panel>
8853 */
8854 mode: {
8855 type: String,
8856 value: 'standard',
8857 observer: '_modeChanged',
8858 reflectToAttribute: true
8859 },
8860
8861 /**
8862 * If true, the drop-shadow is always shown no matter what mode is set t o.
8863 */
8864 shadow: {
8865 type: Boolean,
8866 value: false
8867 },
8868
8869 /**
8870 * The class used in waterfall-tall mode. Change this if the header
8871 * accepts a different class for toggling height, e.g. "medium-tall"
8872 */
8873 tallClass: {
8874 type: String,
8875 value: 'tall'
8876 },
8877
8878 /**
8879 * If true, the scroller is at the top
8880 */
8881 atTop: {
8882 type: Boolean,
8883 value: true,
8884 readOnly: true
8885 }
8886 },
8887
8888 observers: [
8889 '_computeDropShadowHidden(atTop, mode, shadow)'
8890 ],
8891
8892 ready: function() {
8893 this.scrollHandler = this._scroll.bind(this);
8894 this._addListener();
8895
8896 // Run `scroll` logic once to initialze class names, etc.
8897 this._keepScrollingState();
8898 },
8899
8900 detached: function() {
8901 this._removeListener();
8902 },
8903
8904 /**
8905 * Returns the header element
8906 *
8907 * @property header
8908 * @type Object
8909 */
8910 get header() {
8911 return Polymer.dom(this.$.headerContent).getDistributedNodes()[0];
8912 },
8913
8914 /**
8915 * Returns the scrollable element.
8916 *
8917 * @property scroller
8918 * @type Object
8919 */
8920 get scroller() {
8921 return this._getScrollerForMode(this.mode);
8922 },
8923
8924 /**
8925 * Returns true if the scroller has a visible shadow.
8926 *
8927 * @property visibleShadow
8928 * @type Boolean
8929 */
8930 get visibleShadow() {
8931 return this.$.dropShadow.classList.contains('has-shadow');
8932 },
8933
8934 _computeDropShadowHidden: function(atTop, mode, shadow) {
8935
8936 var shadowMode = MODE_CONFIGS.shadowMode[mode];
8937
8938 if (this.shadow) {
8939 this.toggleClass('has-shadow', true, this.$.dropShadow);
8940
8941 } else if (shadowMode === SHADOW_ALWAYS) {
8942 this.toggleClass('has-shadow', true, this.$.dropShadow);
8943
8944 } else if (shadowMode === SHADOW_WHEN_SCROLLING && !atTop) {
8945 this.toggleClass('has-shadow', true, this.$.dropShadow);
8946
8947 } else {
8948 this.toggleClass('has-shadow', false, this.$.dropShadow);
8949
8950 }
8951 },
8952
8953 _computeMainContainerClass: function(mode) {
8954 // TODO: It will be useful to have a utility for classes
8955 // e.g. Polymer.Utils.classes({ foo: true });
8956
8957 var classes = {};
8958
8959 classes['flex'] = mode !== 'cover';
8960
8961 return Object.keys(classes).filter(
8962 function(className) {
8963 return classes[className];
8964 }).join(' ');
8965 },
8966
8967 _addListener: function() {
8968 this.scroller.addEventListener('scroll', this.scrollHandler, false);
8969 },
8970
8971 _removeListener: function() {
8972 this.scroller.removeEventListener('scroll', this.scrollHandler);
8973 },
8974
8975 _modeChanged: function(newMode, oldMode) {
8976 var configs = MODE_CONFIGS;
8977 var header = this.header;
8978 var animateDuration = 200;
8979
8980 if (header) {
8981 // in tallMode it may add tallClass to the header; so do the cleanup
8982 // when mode is changed from tallMode to not tallMode
8983 if (configs.tallMode[oldMode] && !configs.tallMode[newMode]) {
8984 header.classList.remove(this.tallClass);
8985 this.async(function() {
8986 header.classList.remove('animate');
8987 }, animateDuration);
8988 } else {
8989 header.classList.toggle('animate', configs.tallMode[newMode]);
8990 }
8991 }
8992 this._keepScrollingState();
8993 },
8994
8995 _keepScrollingState: function() {
8996 var main = this.scroller;
8997 var header = this.header;
8998
8999 this._setAtTop(main.scrollTop === 0);
9000
9001 if (header && this.tallClass && MODE_CONFIGS.tallMode[this.mode]) {
9002 this.toggleClass(this.tallClass, this.atTop ||
9003 header.classList.contains(this.tallClass) &&
9004 main.scrollHeight < this.offsetHeight, header);
9005 }
9006 },
9007
9008 _scroll: function() {
9009 this._keepScrollingState();
9010 this.fire('content-scroll', {target: this.scroller}, {bubbles: false});
9011 },
9012
9013 _getScrollerForMode: function(mode) {
9014 return MODE_CONFIGS.outerScroll[mode] ?
9015 this : this.$.mainContainer;
9016 }
9017
9018 });
9019
9020 })();
9021 Polymer({
9022 is: 'paper-material',
9023
9024 properties: {
9025
9026 /**
9027 * The z-depth of this element, from 0-5. Setting to 0 will remove the
9028 * shadow, and each increasing number greater than 0 will be "deeper"
9029 * than the last.
9030 *
9031 * @attribute elevation
9032 * @type number
9033 * @default 1
9034 */
9035 elevation: {
9036 type: Number,
9037 reflectToAttribute: true,
9038 value: 1
9039 },
9040
9041 /**
9042 * Set this to true to animate the shadow when setting a new
9043 * `elevation` value.
9044 *
9045 * @attribute animated
9046 * @type boolean
9047 * @default false
9048 */
9049 animated: {
9050 type: Boolean,
9051 reflectToAttribute: true,
9052 value: false
9053 }
9054 }
9055 });
9056 (function() {
9057 'use strict';
9058
9059 /**
9060 * Chrome uses an older version of DOM Level 3 Keyboard Events
9061 *
9062 * Most keys are labeled as text, but some are Unicode codepoints.
9063 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
9064 */
9065 var KEY_IDENTIFIER = {
9066 'U+0009': 'tab',
9067 'U+001B': 'esc',
9068 'U+0020': 'space',
9069 'U+002A': '*',
9070 'U+0030': '0',
9071 'U+0031': '1',
9072 'U+0032': '2',
9073 'U+0033': '3',
9074 'U+0034': '4',
9075 'U+0035': '5',
9076 'U+0036': '6',
9077 'U+0037': '7',
9078 'U+0038': '8',
9079 'U+0039': '9',
9080 'U+0041': 'a',
9081 'U+0042': 'b',
9082 'U+0043': 'c',
9083 'U+0044': 'd',
9084 'U+0045': 'e',
9085 'U+0046': 'f',
9086 'U+0047': 'g',
9087 'U+0048': 'h',
9088 'U+0049': 'i',
9089 'U+004A': 'j',
9090 'U+004B': 'k',
9091 'U+004C': 'l',
9092 'U+004D': 'm',
9093 'U+004E': 'n',
9094 'U+004F': 'o',
9095 'U+0050': 'p',
9096 'U+0051': 'q',
9097 'U+0052': 'r',
9098 'U+0053': 's',
9099 'U+0054': 't',
9100 'U+0055': 'u',
9101 'U+0056': 'v',
9102 'U+0057': 'w',
9103 'U+0058': 'x',
9104 'U+0059': 'y',
9105 'U+005A': 'z',
9106 'U+007F': 'del'
9107 };
9108
9109 /**
9110 * Special table for KeyboardEvent.keyCode.
9111 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
9112 * than that.
9113 *
9114 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
9115 */
9116 var KEY_CODE = {
9117 9: 'tab',
9118 13: 'enter',
9119 27: 'esc',
9120 33: 'pageup',
9121 34: 'pagedown',
9122 35: 'end',
9123 36: 'home',
9124 32: 'space',
9125 37: 'left',
9126 38: 'up',
9127 39: 'right',
9128 40: 'down',
9129 46: 'del',
9130 106: '*'
9131 };
9132
9133 /**
9134 * MODIFIER_KEYS maps the short name for modifier keys used in a key
9135 * combo string to the property name that references those same keys
9136 * in a KeyboardEvent instance.
9137 */
9138 var MODIFIER_KEYS = {
9139 'shift': 'shiftKey',
9140 'ctrl': 'ctrlKey',
9141 'alt': 'altKey',
9142 'meta': 'metaKey'
9143 };
9144
9145 /**
9146 * KeyboardEvent.key is mostly represented by printable character made by
9147 * the keyboard, with unprintable keys labeled nicely.
9148 *
9149 * However, on OS X, Alt+char can make a Unicode character that follows an
9150 * Apple-specific mapping. In this case, we
9151 * fall back to .keyCode.
9152 */
9153 var KEY_CHAR = /[a-z0-9*]/;
9154
9155 /**
9156 * Matches a keyIdentifier string.
9157 */
9158 var IDENT_CHAR = /U\+/;
9159
9160 /**
9161 * Matches arrow keys in Gecko 27.0+
9162 */
9163 var ARROW_KEY = /^arrow/;
9164
9165 /**
9166 * Matches space keys everywhere (notably including IE10's exceptional name
9167 * `spacebar`).
9168 */
9169 var SPACE_KEY = /^space(bar)?/;
9170
9171 function transformKey(key) {
9172 var validKey = '';
9173 if (key) {
9174 var lKey = key.toLowerCase();
9175 if (lKey.length == 1) {
9176 if (KEY_CHAR.test(lKey)) {
9177 validKey = lKey;
9178 }
9179 } else if (ARROW_KEY.test(lKey)) {
9180 validKey = lKey.replace('arrow', '');
9181 } else if (SPACE_KEY.test(lKey)) {
9182 validKey = 'space';
9183 } else if (lKey == 'multiply') {
9184 // numpad '*' can map to Multiply on IE/Windows
9185 validKey = '*';
9186 } else {
9187 validKey = lKey;
9188 }
9189 }
9190 return validKey;
9191 }
9192
9193 function transformKeyIdentifier(keyIdent) {
9194 var validKey = '';
9195 if (keyIdent) {
9196 if (IDENT_CHAR.test(keyIdent)) {
9197 validKey = KEY_IDENTIFIER[keyIdent];
9198 } else {
9199 validKey = keyIdent.toLowerCase();
9200 }
9201 }
9202 return validKey;
9203 }
9204
9205 function transformKeyCode(keyCode) {
9206 var validKey = '';
9207 if (Number(keyCode)) {
9208 if (keyCode >= 65 && keyCode <= 90) {
9209 // ascii a-z
9210 // lowercase is 32 offset from uppercase
9211 validKey = String.fromCharCode(32 + keyCode);
9212 } else if (keyCode >= 112 && keyCode <= 123) {
9213 // function keys f1-f12
9214 validKey = 'f' + (keyCode - 112);
9215 } else if (keyCode >= 48 && keyCode <= 57) {
9216 // top 0-9 keys
9217 validKey = String(48 - keyCode);
9218 } else if (keyCode >= 96 && keyCode <= 105) {
9219 // num pad 0-9
9220 validKey = String(96 - keyCode);
9221 } else {
9222 validKey = KEY_CODE[keyCode];
9223 }
9224 }
9225 return validKey;
9226 }
9227
9228 function normalizedKeyForEvent(keyEvent) {
9229 // fall back from .key, to .keyIdentifier, to .keyCode, and then to
9230 // .detail.key to support artificial keyboard events
9231 return transformKey(keyEvent.key) ||
9232 transformKeyIdentifier(keyEvent.keyIdentifier) ||
9233 transformKeyCode(keyEvent.keyCode) ||
9234 transformKey(keyEvent.detail.key) || '';
9235 }
9236
9237 function keyComboMatchesEvent(keyCombo, keyEvent) {
9238 return normalizedKeyForEvent(keyEvent) === keyCombo.key &&
9239 !!keyEvent.shiftKey === !!keyCombo.shiftKey &&
9240 !!keyEvent.ctrlKey === !!keyCombo.ctrlKey &&
9241 !!keyEvent.altKey === !!keyCombo.altKey &&
9242 !!keyEvent.metaKey === !!keyCombo.metaKey;
9243 }
9244
9245 function parseKeyComboString(keyComboString) {
9246 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP art) {
9247 var eventParts = keyComboPart.split(':');
9248 var keyName = eventParts[0];
9249 var event = eventParts[1];
9250
9251 if (keyName in MODIFIER_KEYS) {
9252 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
9253 } else {
9254 parsedKeyCombo.key = keyName;
9255 parsedKeyCombo.event = event || 'keydown';
9256 }
9257
9258 return parsedKeyCombo;
9259 }, {
9260 combo: keyComboString.split(':').shift()
9261 });
9262 }
9263
9264 function parseEventString(eventString) {
9265 return eventString.split(' ').map(function(keyComboString) {
9266 return parseKeyComboString(keyComboString);
9267 });
9268 }
9269
9270
9271 /**
9272 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
9273 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
9274 * The element takes care of browser differences with respect to Keyboard ev ents
9275 * and uses an expressive syntax to filter key presses.
9276 *
9277 * Use the `keyBindings` prototype property to express what combination of k eys
9278 * will trigger the event to fire.
9279 *
9280 * Use the `key-event-target` attribute to set up event handlers on a specif ic
9281 * node.
9282 * The `keys-pressed` event will fire when one of the key combinations set w ith the
9283 * `keys` property is pressed.
9284 *
9285 * @demo demo/index.html
9286 * @polymerBehavior
9287 */
9288 Polymer.IronA11yKeysBehavior = {
9289 properties: {
9290 /**
9291 * The HTMLElement that will be firing relevant KeyboardEvents.
9292 */
9293 keyEventTarget: {
9294 type: Object,
9295 value: function() {
9296 return this;
9297 }
9298 },
9299
9300 _boundKeyHandlers: {
9301 type: Array,
9302 value: function() {
9303 return [];
9304 }
9305 },
9306
9307 // We use this due to a limitation in IE10 where instances will have
9308 // own properties of everything on the "prototype".
9309 _imperativeKeyBindings: {
9310 type: Object,
9311 value: function() {
9312 return {};
9313 }
9314 }
9315 },
9316
9317 observers: [
9318 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
9319 ],
9320
9321 keyBindings: {},
9322
9323 registered: function() {
9324 this._prepKeyBindings();
9325 },
9326
9327 attached: function() {
9328 this._listenKeyEventListeners();
9329 },
9330
9331 detached: function() {
9332 this._unlistenKeyEventListeners();
9333 },
9334
9335 /**
9336 * Can be used to imperatively add a key binding to the implementing
9337 * element. This is the imperative equivalent of declaring a keybinding
9338 * in the `keyBindings` prototype property.
9339 */
9340 addOwnKeyBinding: function(eventString, handlerName) {
9341 this._imperativeKeyBindings[eventString] = handlerName;
9342 this._prepKeyBindings();
9343 this._resetKeyEventListeners();
9344 },
9345
9346 /**
9347 * When called, will remove all imperatively-added key bindings.
9348 */
9349 removeOwnKeyBindings: function() {
9350 this._imperativeKeyBindings = {};
9351 this._prepKeyBindings();
9352 this._resetKeyEventListeners();
9353 },
9354
9355 keyboardEventMatchesKeys: function(event, eventString) {
9356 var keyCombos = parseEventString(eventString);
9357 var index;
9358
9359 for (index = 0; index < keyCombos.length; ++index) {
9360 if (keyComboMatchesEvent(keyCombos[index], event)) {
9361 return true;
9362 }
9363 }
9364
9365 return false;
9366 },
9367
9368 _collectKeyBindings: function() {
9369 var keyBindings = this.behaviors.map(function(behavior) {
9370 return behavior.keyBindings;
9371 });
9372
9373 if (keyBindings.indexOf(this.keyBindings) === -1) {
9374 keyBindings.push(this.keyBindings);
9375 }
9376
9377 return keyBindings;
9378 },
9379
9380 _prepKeyBindings: function() {
9381 this._keyBindings = {};
9382
9383 this._collectKeyBindings().forEach(function(keyBindings) {
9384 for (var eventString in keyBindings) {
9385 this._addKeyBinding(eventString, keyBindings[eventString]);
9386 }
9387 }, this);
9388
9389 for (var eventString in this._imperativeKeyBindings) {
9390 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
9391 }
9392 },
9393
9394 _addKeyBinding: function(eventString, handlerName) {
9395 parseEventString(eventString).forEach(function(keyCombo) {
9396 this._keyBindings[keyCombo.event] =
9397 this._keyBindings[keyCombo.event] || [];
9398
9399 this._keyBindings[keyCombo.event].push([
9400 keyCombo,
9401 handlerName
9402 ]);
9403 }, this);
9404 },
9405
9406 _resetKeyEventListeners: function() {
9407 this._unlistenKeyEventListeners();
9408
9409 if (this.isAttached) {
9410 this._listenKeyEventListeners();
9411 }
9412 },
9413
9414 _listenKeyEventListeners: function() {
9415 Object.keys(this._keyBindings).forEach(function(eventName) {
9416 var keyBindings = this._keyBindings[eventName];
9417 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
9418
9419 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH andler]);
9420
9421 this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
9422 }, this);
9423 },
9424
9425 _unlistenKeyEventListeners: function() {
9426 var keyHandlerTuple;
9427 var keyEventTarget;
9428 var eventName;
9429 var boundKeyHandler;
9430
9431 while (this._boundKeyHandlers.length) {
9432 // My kingdom for block-scope binding and destructuring assignment..
9433 keyHandlerTuple = this._boundKeyHandlers.pop();
9434 keyEventTarget = keyHandlerTuple[0];
9435 eventName = keyHandlerTuple[1];
9436 boundKeyHandler = keyHandlerTuple[2];
9437
9438 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
9439 }
9440 },
9441
9442 _onKeyBindingEvent: function(keyBindings, event) {
9443 keyBindings.forEach(function(keyBinding) {
9444 var keyCombo = keyBinding[0];
9445 var handlerName = keyBinding[1];
9446
9447 if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event)) {
9448 this._triggerKeyHandler(keyCombo, handlerName, event);
9449 }
9450 }, this);
9451 },
9452
9453 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
9454 var detail = Object.create(keyCombo);
9455 detail.keyboardEvent = keyboardEvent;
9456
9457 this[handlerName].call(this, new CustomEvent(keyCombo.event, {
9458 detail: detail
9459 }));
9460 }
9461 };
9462 })();
9463 (function() {
9464 var Utility = {
9465 distance: function(x1, y1, x2, y2) {
9466 var xDelta = (x1 - x2);
9467 var yDelta = (y1 - y2);
9468
9469 return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
9470 },
9471
9472 now: window.performance && window.performance.now ?
9473 window.performance.now.bind(window.performance) : Date.now
9474 };
9475
9476 /**
9477 * @param {HTMLElement} element
9478 * @constructor
9479 */
9480 function ElementMetrics(element) {
9481 this.element = element;
9482 this.width = this.boundingRect.width;
9483 this.height = this.boundingRect.height;
9484
9485 this.size = Math.max(this.width, this.height);
9486 }
9487
9488 ElementMetrics.prototype = {
9489 get boundingRect () {
9490 return this.element.getBoundingClientRect();
9491 },
9492
9493 furthestCornerDistanceFrom: function(x, y) {
9494 var topLeft = Utility.distance(x, y, 0, 0);
9495 var topRight = Utility.distance(x, y, this.width, 0);
9496 var bottomLeft = Utility.distance(x, y, 0, this.height);
9497 var bottomRight = Utility.distance(x, y, this.width, this.height);
9498
9499 return Math.max(topLeft, topRight, bottomLeft, bottomRight);
9500 }
9501 };
9502
9503 /**
9504 * @param {HTMLElement} element
9505 * @constructor
9506 */
9507 function Ripple(element) {
9508 this.element = element;
9509 this.color = window.getComputedStyle(element).color;
9510
9511 this.wave = document.createElement('div');
9512 this.waveContainer = document.createElement('div');
9513 this.wave.style.backgroundColor = this.color;
9514 this.wave.classList.add('wave');
9515 this.waveContainer.classList.add('wave-container');
9516 Polymer.dom(this.waveContainer).appendChild(this.wave);
9517
9518 this.resetInteractionState();
9519 }
9520
9521 Ripple.MAX_RADIUS = 300;
9522
9523 Ripple.prototype = {
9524 get recenters() {
9525 return this.element.recenters;
9526 },
9527
9528 get center() {
9529 return this.element.center;
9530 },
9531
9532 get mouseDownElapsed() {
9533 var elapsed;
9534
9535 if (!this.mouseDownStart) {
9536 return 0;
9537 }
9538
9539 elapsed = Utility.now() - this.mouseDownStart;
9540
9541 if (this.mouseUpStart) {
9542 elapsed -= this.mouseUpElapsed;
9543 }
9544
9545 return elapsed;
9546 },
9547
9548 get mouseUpElapsed() {
9549 return this.mouseUpStart ?
9550 Utility.now () - this.mouseUpStart : 0;
9551 },
9552
9553 get mouseDownElapsedSeconds() {
9554 return this.mouseDownElapsed / 1000;
9555 },
9556
9557 get mouseUpElapsedSeconds() {
9558 return this.mouseUpElapsed / 1000;
9559 },
9560
9561 get mouseInteractionSeconds() {
9562 return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
9563 },
9564
9565 get initialOpacity() {
9566 return this.element.initialOpacity;
9567 },
9568
9569 get opacityDecayVelocity() {
9570 return this.element.opacityDecayVelocity;
9571 },
9572
9573 get radius() {
9574 var width2 = this.containerMetrics.width * this.containerMetrics.width;
9575 var height2 = this.containerMetrics.height * this.containerMetrics.heigh t;
9576 var waveRadius = Math.min(
9577 Math.sqrt(width2 + height2),
9578 Ripple.MAX_RADIUS
9579 ) * 1.1 + 5;
9580
9581 var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
9582 var timeNow = this.mouseInteractionSeconds / duration;
9583 var size = waveRadius * (1 - Math.pow(80, -timeNow));
9584
9585 return Math.abs(size);
9586 },
9587
9588 get opacity() {
9589 if (!this.mouseUpStart) {
9590 return this.initialOpacity;
9591 }
9592
9593 return Math.max(
9594 0,
9595 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe locity
9596 );
9597 },
9598
9599 get outerOpacity() {
9600 // Linear increase in background opacity, capped at the opacity
9601 // of the wavefront (waveOpacity).
9602 var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
9603 var waveOpacity = this.opacity;
9604
9605 return Math.max(
9606 0,
9607 Math.min(outerOpacity, waveOpacity)
9608 );
9609 },
9610
9611 get isOpacityFullyDecayed() {
9612 return this.opacity < 0.01 &&
9613 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
9614 },
9615
9616 get isRestingAtMaxRadius() {
9617 return this.opacity >= this.initialOpacity &&
9618 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
9619 },
9620
9621 get isAnimationComplete() {
9622 return this.mouseUpStart ?
9623 this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
9624 },
9625
9626 get translationFraction() {
9627 return Math.min(
9628 1,
9629 this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
9630 );
9631 },
9632
9633 get xNow() {
9634 if (this.xEnd) {
9635 return this.xStart + this.translationFraction * (this.xEnd - this.xSta rt);
9636 }
9637
9638 return this.xStart;
9639 },
9640
9641 get yNow() {
9642 if (this.yEnd) {
9643 return this.yStart + this.translationFraction * (this.yEnd - this.ySta rt);
9644 }
9645
9646 return this.yStart;
9647 },
9648
9649 get isMouseDown() {
9650 return this.mouseDownStart && !this.mouseUpStart;
9651 },
9652
9653 resetInteractionState: function() {
9654 this.maxRadius = 0;
9655 this.mouseDownStart = 0;
9656 this.mouseUpStart = 0;
9657
9658 this.xStart = 0;
9659 this.yStart = 0;
9660 this.xEnd = 0;
9661 this.yEnd = 0;
9662 this.slideDistance = 0;
9663
9664 this.containerMetrics = new ElementMetrics(this.element);
9665 },
9666
9667 draw: function() {
9668 var scale;
9669 var translateString;
9670 var dx;
9671 var dy;
9672
9673 this.wave.style.opacity = this.opacity;
9674
9675 scale = this.radius / (this.containerMetrics.size / 2);
9676 dx = this.xNow - (this.containerMetrics.width / 2);
9677 dy = this.yNow - (this.containerMetrics.height / 2);
9678
9679
9680 // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
9681 // https://bugs.webkit.org/show_bug.cgi?id=98538
9682 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
9683 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
9684 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
9685 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
9686 },
9687
9688 /** @param {Event=} event */
9689 downAction: function(event) {
9690 var xCenter = this.containerMetrics.width / 2;
9691 var yCenter = this.containerMetrics.height / 2;
9692
9693 this.resetInteractionState();
9694 this.mouseDownStart = Utility.now();
9695
9696 if (this.center) {
9697 this.xStart = xCenter;
9698 this.yStart = yCenter;
9699 this.slideDistance = Utility.distance(
9700 this.xStart, this.yStart, this.xEnd, this.yEnd
9701 );
9702 } else {
9703 this.xStart = event ?
9704 event.detail.x - this.containerMetrics.boundingRect.left :
9705 this.containerMetrics.width / 2;
9706 this.yStart = event ?
9707 event.detail.y - this.containerMetrics.boundingRect.top :
9708 this.containerMetrics.height / 2;
9709 }
9710
9711 if (this.recenters) {
9712 this.xEnd = xCenter;
9713 this.yEnd = yCenter;
9714 this.slideDistance = Utility.distance(
9715 this.xStart, this.yStart, this.xEnd, this.yEnd
9716 );
9717 }
9718
9719 this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
9720 this.xStart,
9721 this.yStart
9722 );
9723
9724 this.waveContainer.style.top =
9725 (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px' ;
9726 this.waveContainer.style.left =
9727 (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
9728
9729 this.waveContainer.style.width = this.containerMetrics.size + 'px';
9730 this.waveContainer.style.height = this.containerMetrics.size + 'px';
9731 },
9732
9733 /** @param {Event=} event */
9734 upAction: function(event) {
9735 if (!this.isMouseDown) {
9736 return;
9737 }
9738
9739 this.mouseUpStart = Utility.now();
9740 },
9741
9742 remove: function() {
9743 Polymer.dom(this.waveContainer.parentNode).removeChild(
9744 this.waveContainer
9745 );
9746 }
9747 };
9748
9749 Polymer({
9750 is: 'paper-ripple',
9751
9752 behaviors: [
9753 Polymer.IronA11yKeysBehavior
9754 ],
9755
9756 properties: {
9757 /**
9758 * The initial opacity set on the wave.
9759 *
9760 * @attribute initialOpacity
9761 * @type number
9762 * @default 0.25
9763 */
9764 initialOpacity: {
9765 type: Number,
9766 value: 0.25
9767 },
9768
9769 /**
9770 * How fast (opacity per second) the wave fades out.
9771 *
9772 * @attribute opacityDecayVelocity
9773 * @type number
9774 * @default 0.8
9775 */
9776 opacityDecayVelocity: {
9777 type: Number,
9778 value: 0.8
9779 },
9780
9781 /**
9782 * If true, ripples will exhibit a gravitational pull towards
9783 * the center of their container as they fade away.
9784 *
9785 * @attribute recenters
9786 * @type boolean
9787 * @default false
9788 */
9789 recenters: {
9790 type: Boolean,
9791 value: false
9792 },
9793
9794 /**
9795 * If true, ripples will center inside its container
9796 *
9797 * @attribute recenters
9798 * @type boolean
9799 * @default false
9800 */
9801 center: {
9802 type: Boolean,
9803 value: false
9804 },
9805
9806 /**
9807 * A list of the visual ripples.
9808 *
9809 * @attribute ripples
9810 * @type Array
9811 * @default []
9812 */
9813 ripples: {
9814 type: Array,
9815 value: function() {
9816 return [];
9817 }
9818 },
9819
9820 /**
9821 * True when there are visible ripples animating within the
9822 * element.
9823 */
9824 animating: {
9825 type: Boolean,
9826 readOnly: true,
9827 reflectToAttribute: true,
9828 value: false
9829 },
9830
9831 /**
9832 * If true, the ripple will remain in the "down" state until `holdDown`
9833 * is set to false again.
9834 */
9835 holdDown: {
9836 type: Boolean,
9837 value: false,
9838 observer: '_holdDownChanged'
9839 },
9840
9841 _animating: {
9842 type: Boolean
9843 },
9844
9845 _boundAnimate: {
9846 type: Function,
9847 value: function() {
9848 return this.animate.bind(this);
9849 }
9850 }
9851 },
9852
9853 get target () {
9854 var ownerRoot = Polymer.dom(this).getOwnerRoot();
9855 var target;
9856
9857 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
9858 target = ownerRoot.host;
9859 } else {
9860 target = this.parentNode;
9861 }
9862
9863 return target;
9864 },
9865
9866 keyBindings: {
9867 'enter:keydown': '_onEnterKeydown',
9868 'space:keydown': '_onSpaceKeydown',
9869 'space:keyup': '_onSpaceKeyup'
9870 },
9871
9872 attached: function() {
9873 this.listen(this.target, 'up', 'upAction');
9874 this.listen(this.target, 'down', 'downAction');
9875
9876 if (!this.target.hasAttribute('noink')) {
9877 this.keyEventTarget = this.target;
9878 }
9879 },
9880
9881 get shouldKeepAnimating () {
9882 for (var index = 0; index < this.ripples.length; ++index) {
9883 if (!this.ripples[index].isAnimationComplete) {
9884 return true;
9885 }
9886 }
9887
9888 return false;
9889 },
9890
9891 simulatedRipple: function() {
9892 this.downAction(null);
9893
9894 // Please see polymer/polymer#1305
9895 this.async(function() {
9896 this.upAction();
9897 }, 1);
9898 },
9899
9900 /** @param {Event=} event */
9901 downAction: function(event) {
9902 if (this.holdDown && this.ripples.length > 0) {
9903 return;
9904 }
9905
9906 var ripple = this.addRipple();
9907
9908 ripple.downAction(event);
9909
9910 if (!this._animating) {
9911 this.animate();
9912 }
9913 },
9914
9915 /** @param {Event=} event */
9916 upAction: function(event) {
9917 if (this.holdDown) {
9918 return;
9919 }
9920
9921 this.ripples.forEach(function(ripple) {
9922 ripple.upAction(event);
9923 });
9924
9925 this.animate();
9926 },
9927
9928 onAnimationComplete: function() {
9929 this._animating = false;
9930 this.$.background.style.backgroundColor = null;
9931 this.fire('transitionend');
9932 },
9933
9934 addRipple: function() {
9935 var ripple = new Ripple(this);
9936
9937 Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
9938 this.$.background.style.backgroundColor = ripple.color;
9939 this.ripples.push(ripple);
9940
9941 this._setAnimating(true);
9942
9943 return ripple;
9944 },
9945
9946 removeRipple: function(ripple) {
9947 var rippleIndex = this.ripples.indexOf(ripple);
9948
9949 if (rippleIndex < 0) {
9950 return;
9951 }
9952
9953 this.ripples.splice(rippleIndex, 1);
9954
9955 ripple.remove();
9956
9957 if (!this.ripples.length) {
9958 this._setAnimating(false);
9959 }
9960 },
9961
9962 animate: function() {
9963 var index;
9964 var ripple;
9965
9966 this._animating = true;
9967
9968 for (index = 0; index < this.ripples.length; ++index) {
9969 ripple = this.ripples[index];
9970
9971 ripple.draw();
9972
9973 this.$.background.style.opacity = ripple.outerOpacity;
9974
9975 if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
9976 this.removeRipple(ripple);
9977 }
9978 }
9979
9980 if (!this.shouldKeepAnimating && this.ripples.length === 0) {
9981 this.onAnimationComplete();
9982 } else {
9983 window.requestAnimationFrame(this._boundAnimate);
9984 }
9985 },
9986
9987 _onEnterKeydown: function() {
9988 this.downAction();
9989 this.async(this.upAction, 1);
9990 },
9991
9992 _onSpaceKeydown: function() {
9993 this.downAction();
9994 },
9995
9996 _onSpaceKeyup: function() {
9997 this.upAction();
9998 },
9999
10000 _holdDownChanged: function(holdDown) {
10001 if (holdDown) {
10002 this.downAction();
10003 } else {
10004 this.upAction();
10005 }
10006 }
10007 });
10008 })();
10009 /**
10010 * @demo demo/index.html
10011 * @polymerBehavior
10012 */
10013 Polymer.IronControlState = {
10014
10015 properties: {
10016
10017 /**
10018 * If true, the element currently has focus.
10019 */
10020 focused: {
10021 type: Boolean,
10022 value: false,
10023 notify: true,
10024 readOnly: true,
10025 reflectToAttribute: true
10026 },
10027
10028 /**
10029 * If true, the user cannot interact with this element.
10030 */
10031 disabled: {
10032 type: Boolean,
10033 value: false,
10034 notify: true,
10035 observer: '_disabledChanged',
10036 reflectToAttribute: true
10037 },
10038
10039 _oldTabIndex: {
10040 type: Number
10041 },
10042
10043 _boundFocusBlurHandler: {
10044 type: Function,
10045 value: function() {
10046 return this._focusBlurHandler.bind(this);
10047 }
10048 }
10049
10050 },
10051
10052 observers: [
10053 '_changedControlState(focused, disabled)'
10054 ],
10055
10056 ready: function() {
10057 this.addEventListener('focus', this._boundFocusBlurHandler, true);
10058 this.addEventListener('blur', this._boundFocusBlurHandler, true);
10059 },
10060
10061 _focusBlurHandler: function(event) {
10062 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will
10063 // eventually become `this` due to retargeting; if we are not in
10064 // ShadowDOM land, `event.target` will eventually become `this` due
10065 // to the second conditional which fires a synthetic event (that is also
10066 // handled). In either case, we can disregard `event.path`.
10067
10068 if (event.target === this) {
10069 var focused = event.type === 'focus';
10070 this._setFocused(focused);
10071 } else if (!this.shadowRoot) {
10072 this.fire(event.type, {sourceEvent: event}, {
10073 node: this,
10074 bubbles: event.bubbles,
10075 cancelable: event.cancelable
10076 });
10077 }
10078 },
10079
10080 _disabledChanged: function(disabled, old) {
10081 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
10082 this.style.pointerEvents = disabled ? 'none' : '';
10083 if (disabled) {
10084 this._oldTabIndex = this.tabIndex;
10085 this.focused = false;
10086 this.tabIndex = -1;
10087 } else if (this._oldTabIndex !== undefined) {
10088 this.tabIndex = this._oldTabIndex;
10089 }
10090 },
10091
10092 _changedControlState: function() {
10093 // _controlStateChanged is abstract, follow-on behaviors may implement it
10094 if (this._controlStateChanged) {
10095 this._controlStateChanged();
10096 }
10097 }
10098
10099 };
10100 /**
10101 * @demo demo/index.html
10102 * @polymerBehavior Polymer.IronButtonState
10103 */
10104 Polymer.IronButtonStateImpl = {
10105
10106 properties: {
10107
10108 /**
10109 * If true, the user is currently holding down the button.
10110 */
10111 pressed: {
10112 type: Boolean,
10113 readOnly: true,
10114 value: false,
10115 reflectToAttribute: true,
10116 observer: '_pressedChanged'
10117 },
10118
10119 /**
10120 * If true, the button toggles the active state with each tap or press
10121 * of the spacebar.
10122 */
10123 toggles: {
10124 type: Boolean,
10125 value: false,
10126 reflectToAttribute: true
10127 },
10128
10129 /**
10130 * If true, the button is a toggle and is currently in the active state.
10131 */
10132 active: {
10133 type: Boolean,
10134 value: false,
10135 notify: true,
10136 reflectToAttribute: true
10137 },
10138
10139 /**
10140 * True if the element is currently being pressed by a "pointer," which
10141 * is loosely defined as mouse or touch input (but specifically excluding
10142 * keyboard input).
10143 */
10144 pointerDown: {
10145 type: Boolean,
10146 readOnly: true,
10147 value: false
10148 },
10149
10150 /**
10151 * True if the input device that caused the element to receive focus
10152 * was a keyboard.
10153 */
10154 receivedFocusFromKeyboard: {
10155 type: Boolean,
10156 readOnly: true
10157 },
10158
10159 /**
10160 * The aria attribute to be set if the button is a toggle and in the
10161 * active state.
10162 */
10163 ariaActiveAttribute: {
10164 type: String,
10165 value: 'aria-pressed',
10166 observer: '_ariaActiveAttributeChanged'
10167 }
10168 },
10169
10170 listeners: {
10171 down: '_downHandler',
10172 up: '_upHandler',
10173 tap: '_tapHandler'
10174 },
10175
10176 observers: [
10177 '_detectKeyboardFocus(focused)',
10178 '_activeChanged(active, ariaActiveAttribute)'
10179 ],
10180
10181 keyBindings: {
10182 'enter:keydown': '_asyncClick',
10183 'space:keydown': '_spaceKeyDownHandler',
10184 'space:keyup': '_spaceKeyUpHandler',
10185 },
10186
10187 _mouseEventRe: /^mouse/,
10188
10189 _tapHandler: function() {
10190 if (this.toggles) {
10191 // a tap is needed to toggle the active state
10192 this._userActivate(!this.active);
10193 } else {
10194 this.active = false;
10195 }
10196 },
10197
10198 _detectKeyboardFocus: function(focused) {
10199 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
10200 },
10201
10202 // to emulate native checkbox, (de-)activations from a user interaction fire
10203 // 'change' events
10204 _userActivate: function(active) {
10205 if (this.active !== active) {
10206 this.active = active;
10207 this.fire('change');
10208 }
10209 },
10210
10211 _eventSourceIsPrimaryInput: function(event) {
10212 event = event.detail.sourceEvent || event;
10213
10214 // Always true for non-mouse events....
10215 if (!this._mouseEventRe.test(event.type)) {
10216 return true;
10217 }
10218
10219 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
10220 if ('buttons' in event) {
10221 return event.buttons === 1;
10222 }
10223
10224 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
10225 if (typeof event.which === 'number') {
10226 return event.which < 2;
10227 }
10228
10229 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
10230 return event.button < 1;
10231 },
10232
10233 _downHandler: function(event) {
10234 if (!this._eventSourceIsPrimaryInput(event)) {
10235 return;
10236 }
10237
10238 this._setPointerDown(true);
10239 this._setPressed(true);
10240 this._setReceivedFocusFromKeyboard(false);
10241 },
10242
10243 _upHandler: function() {
10244 this._setPointerDown(false);
10245 this._setPressed(false);
10246 },
10247
10248 _spaceKeyDownHandler: function(event) {
10249 var keyboardEvent = event.detail.keyboardEvent;
10250 keyboardEvent.preventDefault();
10251 keyboardEvent.stopImmediatePropagation();
10252 this._setPressed(true);
10253 },
10254
10255 _spaceKeyUpHandler: function() {
10256 if (this.pressed) {
10257 this._asyncClick();
10258 }
10259 this._setPressed(false);
10260 },
10261
10262 // trigger click asynchronously, the asynchrony is useful to allow one
10263 // event handler to unwind before triggering another event
10264 _asyncClick: function() {
10265 this.async(function() {
10266 this.click();
10267 }, 1);
10268 },
10269
10270 // any of these changes are considered a change to button state
10271
10272 _pressedChanged: function(pressed) {
10273 this._changedButtonState();
10274 },
10275
10276 _ariaActiveAttributeChanged: function(value, oldValue) {
10277 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
10278 this.removeAttribute(oldValue);
10279 }
10280 },
10281
10282 _activeChanged: function(active, ariaActiveAttribute) {
10283 if (this.toggles) {
10284 this.setAttribute(this.ariaActiveAttribute,
10285 active ? 'true' : 'false');
10286 } else {
10287 this.removeAttribute(this.ariaActiveAttribute);
10288 }
10289 this._changedButtonState();
10290 },
10291
10292 _controlStateChanged: function() {
10293 if (this.disabled) {
10294 this._setPressed(false);
10295 } else {
10296 this._changedButtonState();
10297 }
10298 },
10299
10300 // provide hook for follow-on behaviors to react to button-state
10301
10302 _changedButtonState: function() {
10303 if (this._buttonStateChanged) {
10304 this._buttonStateChanged(); // abstract
10305 }
10306 }
10307
10308 };
10309
10310 /** @polymerBehavior */
10311 Polymer.IronButtonState = [
10312 Polymer.IronA11yKeysBehavior,
10313 Polymer.IronButtonStateImpl
10314 ];
10315 /** @polymerBehavior */
10316 Polymer.PaperButtonBehaviorImpl = {
10317
10318 properties: {
10319
10320 _elevation: {
10321 type: Number
10322 }
10323
10324 },
10325
10326 observers: [
10327 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)'
10328 ],
10329
10330 hostAttributes: {
10331 role: 'button',
10332 tabindex: '0'
10333 },
10334
10335 _calculateElevation: function() {
10336 var e = 1;
10337 if (this.disabled) {
10338 e = 0;
10339 } else if (this.active || this.pressed) {
10340 e = 4;
10341 } else if (this.receivedFocusFromKeyboard) {
10342 e = 3;
10343 }
10344 this._elevation = e;
10345 }
10346 };
10347
10348 /** @polymerBehavior */
10349 Polymer.PaperButtonBehavior = [
10350 Polymer.IronButtonState,
10351 Polymer.IronControlState,
10352 Polymer.PaperButtonBehaviorImpl
10353 ];
10354 Polymer({
10355 is: 'paper-button',
10356
10357 behaviors: [
10358 Polymer.PaperButtonBehavior
10359 ],
10360
10361 properties: {
10362 /**
10363 * If true, the button should be styled with a shadow.
10364 */
10365 raised: {
10366 type: Boolean,
10367 reflectToAttribute: true,
10368 value: false,
10369 observer: '_calculateElevation'
10370 }
10371 },
10372
10373 _calculateElevation: function() {
10374 if (!this.raised) {
10375 this._elevation = 0;
10376 } else {
10377 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
10378 }
10379 },
10380
10381 _computeContentClass: function(receivedFocusFromKeyboard) {
10382 var className = 'content ';
10383 if (receivedFocusFromKeyboard) {
10384 className += ' keyboard-focus';
10385 }
10386 return className;
10387 }
10388 });
10389 /**
10390 * `iron-range-behavior` provides the behavior for something with a minimum to m aximum range.
10391 *
10392 * @demo demo/index.html
10393 * @polymerBehavior
10394 */
10395 Polymer.IronRangeBehavior = {
10396
10397 properties: {
10398
10399 /**
10400 * The number that represents the current value.
10401 */
10402 value: {
10403 type: Number,
10404 value: 0,
10405 notify: true,
10406 reflectToAttribute: true
10407 },
10408
10409 /**
10410 * The number that indicates the minimum value of the range.
10411 */
10412 min: {
10413 type: Number,
10414 value: 0,
10415 notify: true
10416 },
10417
10418 /**
10419 * The number that indicates the maximum value of the range.
10420 */
10421 max: {
10422 type: Number,
10423 value: 100,
10424 notify: true
10425 },
10426
10427 /**
10428 * Specifies the value granularity of the range's value.
10429 */
10430 step: {
10431 type: Number,
10432 value: 1,
10433 notify: true
10434 },
10435
10436 /**
10437 * Returns the ratio of the value.
10438 */
10439 ratio: {
10440 type: Number,
10441 value: 0,
10442 readOnly: true,
10443 notify: true
10444 },
10445 },
10446
10447 observers: [
10448 '_update(value, min, max, step)'
10449 ],
10450
10451 _calcRatio: function(value) {
10452 return (this._clampValue(value) - this.min) / (this.max - this.min);
10453 },
10454
10455 _clampValue: function(value) {
10456 return Math.min(this.max, Math.max(this.min, this._calcStep(value)));
10457 },
10458
10459 _calcStep: function(value) {
10460 /**
10461 * if we calculate the step using
10462 * `Math.round(value / step) * step` we may hit a precision point issue
10463 * eg. 0.1 * 0.2 = 0.020000000000000004
10464 * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
10465 *
10466 * as a work around we can divide by the reciprocal of `step`
10467 */
10468 // polymer/issues/2493
10469 value = parseFloat(value);
10470 return this.step ? (Math.round((value + this.min) / this.step) / (1 / this.s tep)) - this.min : value;
10471 },
10472
10473 _validateValue: function() {
10474 var v = this._clampValue(this.value);
10475 this.value = this.oldValue = isNaN(v) ? this.oldValue : v;
10476 return this.value !== v;
10477 },
10478
10479 _update: function() {
10480 this._validateValue();
10481 this._setRatio(this._calcRatio(this.value) * 100);
10482 }
10483
10484 };
10485 Polymer({
10486
10487 is: 'paper-progress',
10488
10489 behaviors: [
10490 Polymer.IronRangeBehavior
10491 ],
10492
10493 properties: {
10494
10495 /**
10496 * The number that represents the current secondary progress.
10497 */
10498 secondaryProgress: {
10499 type: Number,
10500 value: 0
10501 },
10502
10503 /**
10504 * The secondary ratio
10505 */
10506 secondaryRatio: {
10507 type: Number,
10508 value: 0,
10509 readOnly: true
10510 },
10511
10512 /**
10513 * Use an indeterminate progress indicator.
10514 */
10515 indeterminate: {
10516 type: Boolean,
10517 value: false,
10518 observer: '_toggleIndeterminate'
10519 },
10520
10521 /**
10522 * True if the progress is disabled.
10523 */
10524 disabled: {
10525 type: Boolean,
10526 value: false,
10527 reflectToAttribute: true,
10528 observer: '_disabledChanged'
10529 }
10530 },
10531
10532 observers: [
10533 '_progressChanged(secondaryProgress, value, min, max)'
10534 ],
10535
10536 hostAttributes: {
10537 role: 'progressbar'
10538 },
10539
10540 _toggleIndeterminate: function(indeterminate) {
10541 // If we use attribute/class binding, the animation sometimes doesn't tran slate properly
10542 // on Safari 7.1. So instead, we toggle the class here in the update metho d.
10543 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress);
10544 },
10545
10546 _transformProgress: function(progress, ratio) {
10547 var transform = 'scaleX(' + (ratio / 100) + ')';
10548 progress.style.transform = progress.style.webkitTransform = transform;
10549 },
10550
10551 _mainRatioChanged: function(ratio) {
10552 this._transformProgress(this.$.primaryProgress, ratio);
10553 },
10554
10555 _progressChanged: function(secondaryProgress, value, min, max) {
10556 secondaryProgress = this._clampValue(secondaryProgress);
10557 value = this._clampValue(value);
10558
10559 var secondaryRatio = this._calcRatio(secondaryProgress) * 100;
10560 var mainRatio = this._calcRatio(value) * 100;
10561
10562 this._setSecondaryRatio(secondaryRatio);
10563 this._transformProgress(this.$.secondaryProgress, secondaryRatio);
10564 this._transformProgress(this.$.primaryProgress, mainRatio);
10565
10566 this.secondaryProgress = secondaryProgress;
10567
10568 this.setAttribute('aria-valuenow', value);
10569 this.setAttribute('aria-valuemin', min);
10570 this.setAttribute('aria-valuemax', max);
10571 },
10572
10573 _disabledChanged: function(disabled) {
10574 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
10575 },
10576
10577 _hideSecondaryProgress: function(secondaryRatio) {
10578 return secondaryRatio === 0;
10579 }
10580
10581 });
10582 // Copyright 2015 The Chromium Authors. All rights reserved.
10583 // Use of this source code is governed by a BSD-style license that can be
10584 // found in the LICENSE file.
10585
10586 cr.define('downloads', function() {
10587 var Item = Polymer({
10588 is: 'downloads-item',
10589
10590 /**
10591 * @param {!downloads.ThrottledIconLoader} iconLoader
10592 */
10593 factoryImpl: function(iconLoader) {
10594 /** @private {!downloads.ThrottledIconLoader} */
10595 this.iconLoader_ = iconLoader;
10596 },
10597
10598 properties: {
10599 data: {
10600 type: Object,
10601 },
10602
10603 hideDate: {
10604 type: Boolean,
10605 value: true,
10606 },
10607
10608 readyPromise: {
10609 type: Object,
10610 value: function() {
10611 return new Promise(function(resolve, reject) {
10612 this.resolveReadyPromise_ = resolve;
10613 }.bind(this));
10614 },
10615 },
10616
10617 completelyOnDisk_: {
10618 computed: 'computeCompletelyOnDisk_(' +
10619 'data.state, data.file_externally_removed)',
10620 type: Boolean,
10621 value: true,
10622 },
10623
10624 controlledBy_: {
10625 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)',
10626 type: String,
10627 value: '',
10628 },
10629
10630 i18n_: {
10631 readOnly: true,
10632 type: Object,
10633 value: function() {
10634 return {
10635 cancel: loadTimeData.getString('controlCancel'),
10636 discard: loadTimeData.getString('dangerDiscard'),
10637 pause: loadTimeData.getString('controlPause'),
10638 remove: loadTimeData.getString('controlRemoveFromList'),
10639 resume: loadTimeData.getString('controlResume'),
10640 restore: loadTimeData.getString('dangerRestore'),
10641 retry: loadTimeData.getString('controlRetry'),
10642 save: loadTimeData.getString('dangerSave'),
10643 };
10644 },
10645 },
10646
10647 isActive_: {
10648 computed: 'computeIsActive_(' +
10649 'data.state, data.file_externally_removed)',
10650 type: Boolean,
10651 value: true,
10652 },
10653
10654 isDangerous_: {
10655 computed: 'computeIsDangerous_(data.state)',
10656 type: Boolean,
10657 value: false,
10658 },
10659
10660 isInProgress_: {
10661 computed: 'computeIsInProgress_(data.state)',
10662 type: Boolean,
10663 value: false,
10664 },
10665
10666 showCancel_: {
10667 computed: 'computeShowCancel_(data.state)',
10668 type: Boolean,
10669 value: false,
10670 },
10671
10672 showProgress_: {
10673 computed: 'computeShowProgress_(showCancel_, data.percent)',
10674 type: Boolean,
10675 value: false,
10676 },
10677
10678 isMalware_: {
10679 computed: 'computeIsMalware_(isDangerous_, data.danger_type)',
10680 type: Boolean,
10681 value: false,
10682 },
10683 },
10684
10685 observers: [
10686 // TODO(dbeam): this gets called way more when I observe data.by_ext_id
10687 // and data.by_ext_name directly. Why?
10688 'observeControlledBy_(controlledBy_)',
10689 ],
10690
10691 ready: function() {
10692 this.content = this.$.content;
10693 this.resolveReadyPromise_();
10694 },
10695
10696 /** @param {!downloads.Data} data */
10697 update: function(data) {
10698 this.data = data;
10699
10700 if (!this.isDangerous_) {
10701 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
10702 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
10703 }
10704 },
10705
10706 /** @private */
10707 computeClass_: function() {
10708 var classes = [];
10709
10710 if (this.isActive_)
10711 classes.push('is-active');
10712
10713 if (this.isDangerous_)
10714 classes.push('dangerous');
10715
10716 if (this.showProgress_)
10717 classes.push('show-progress');
10718
10719 return classes.join(' ');
10720 },
10721
10722 /** @private */
10723 computeCompletelyOnDisk_: function() {
10724 return this.data.state == downloads.States.COMPLETE &&
10725 !this.data.file_externally_removed;
10726 },
10727
10728 /** @private */
10729 computeControlledBy_: function() {
10730 if (!this.data.by_ext_id || !this.data.by_ext_name)
10731 return '';
10732
10733 var url = 'chrome://extensions#' + this.data.by_ext_id;
10734 var name = this.data.by_ext_name;
10735 return loadTimeData.getStringF('controlledByUrl', url, name);
10736 },
10737
10738 /** @private */
10739 computeDate_: function() {
10740 if (this.hideDate)
10741 return '';
10742 return assert(this.data.since_string || this.data.date_string);
10743 },
10744
10745 /** @private */
10746 computeDescription_: function() {
10747 var data = this.data;
10748
10749 switch (data.state) {
10750 case downloads.States.DANGEROUS:
10751 var fileName = data.file_name;
10752 switch (data.danger_type) {
10753 case downloads.DangerType.DANGEROUS_FILE:
10754 return loadTimeData.getStringF('dangerFileDesc', fileName);
10755 case downloads.DangerType.DANGEROUS_URL:
10756 return loadTimeData.getString('dangerUrlDesc');
10757 case downloads.DangerType.DANGEROUS_CONTENT: // Fall through.
10758 case downloads.DangerType.DANGEROUS_HOST:
10759 return loadTimeData.getStringF('dangerContentDesc', fileName);
10760 case downloads.DangerType.UNCOMMON_CONTENT:
10761 return loadTimeData.getStringF('dangerUncommonDesc', fileName);
10762 case downloads.DangerType.POTENTIALLY_UNWANTED:
10763 return loadTimeData.getStringF('dangerSettingsDesc', fileName);
10764 }
10765 break;
10766
10767 case downloads.States.IN_PROGRESS:
10768 case downloads.States.PAUSED: // Fallthrough.
10769 return data.progress_status_text;
10770 }
10771
10772 return '';
10773 },
10774
10775 /** @private */
10776 computeIsActive_: function() {
10777 return this.data.state != downloads.States.CANCELLED &&
10778 this.data.state != downloads.States.INTERRUPTED &&
10779 !this.data.file_externally_removed;
10780 },
10781
10782 /** @private */
10783 computeIsDangerous_: function() {
10784 return this.data.state == downloads.States.DANGEROUS;
10785 },
10786
10787 /** @private */
10788 computeIsInProgress_: function() {
10789 return this.data.state == downloads.States.IN_PROGRESS;
10790 },
10791
10792 /** @private */
10793 computeIsMalware_: function() {
10794 return this.isDangerous_ &&
10795 (this.data.danger_type == downloads.DangerType.DANGEROUS_CONTENT ||
10796 this.data.danger_type == downloads.DangerType.DANGEROUS_HOST ||
10797 this.data.danger_type == downloads.DangerType.DANGEROUS_URL ||
10798 this.data.danger_type == downloads.DangerType.POTENTIALLY_UNWANTED);
10799 },
10800
10801 /** @private */
10802 computeRemoveStyle_: function() {
10803 var canDelete = loadTimeData.getBoolean('allowDeletingHistory');
10804 var hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete;
10805 return hideRemove ? 'visibility: hidden' : '';
10806 },
10807
10808 /** @private */
10809 computeShowCancel_: function() {
10810 return this.data.state == downloads.States.IN_PROGRESS ||
10811 this.data.state == downloads.States.PAUSED;
10812 },
10813
10814 /** @private */
10815 computeShowProgress_: function() {
10816 return this.showCancel_ && this.data.percent >= -1;
10817 },
10818
10819 /** @private */
10820 computeTag_: function() {
10821 switch (this.data.state) {
10822 case downloads.States.CANCELLED:
10823 return loadTimeData.getString('statusCancelled');
10824
10825 case downloads.States.INTERRUPTED:
10826 return this.data.last_reason_text;
10827
10828 case downloads.States.COMPLETE:
10829 return this.data.file_externally_removed ?
10830 loadTimeData.getString('statusRemoved') : '';
10831 }
10832
10833 return '';
10834 },
10835
10836 /** @private */
10837 isIndeterminate_: function() {
10838 return this.data.percent == -1;
10839 },
10840
10841 /** @private */
10842 observeControlledBy_: function() {
10843 this.$['controlled-by'].innerHTML = this.controlledBy_;
10844 },
10845
10846 /** @private */
10847 onCancelClick_: function() {
10848 downloads.ActionService.getInstance().cancel(this.data.id);
10849 },
10850
10851 /** @private */
10852 onDiscardDangerous_: function() {
10853 downloads.ActionService.getInstance().discardDangerous(this.data.id);
10854 },
10855
10856 /**
10857 * @private
10858 * @param {Event} e
10859 */
10860 onDragStart_: function(e) {
10861 e.preventDefault();
10862 downloads.ActionService.getInstance().drag(this.data.id);
10863 },
10864
10865 /**
10866 * @param {Event} e
10867 * @private
10868 */
10869 onFileLinkClick_: function(e) {
10870 e.preventDefault();
10871 downloads.ActionService.getInstance().openFile(this.data.id);
10872 },
10873
10874 /** @private */
10875 onPauseClick_: function() {
10876 downloads.ActionService.getInstance().pause(this.data.id);
10877 },
10878
10879 /** @private */
10880 onRemoveClick_: function() {
10881 downloads.ActionService.getInstance().remove(this.data.id);
10882 },
10883
10884 /** @private */
10885 onResumeClick_: function() {
10886 downloads.ActionService.getInstance().resume(this.data.id);
10887 },
10888
10889 /** @private */
10890 onRetryClick_: function() {
10891 downloads.ActionService.getInstance().download(this.data.url);
10892 },
10893
10894 /** @private */
10895 onSaveDangerous_: function() {
10896 downloads.ActionService.getInstance().saveDangerous(this.data.id);
10897 },
10898
10899 /** @private */
10900 onShowClick_: function() {
10901 downloads.ActionService.getInstance().show(this.data.id);
10902 },
10903 });
10904
10905 return {Item: Item};
10906 });
10907 (function() {
10908
10909 // monostate data
10910 var metaDatas = {};
10911 var metaArrays = {};
10912
10913 Polymer.IronMeta = Polymer({
10914
10915 is: 'iron-meta',
10916
10917 properties: {
10918
10919 /**
10920 * The type of meta-data. All meta-data of the same type is stored
10921 * together.
10922 */
10923 type: {
10924 type: String,
10925 value: 'default',
10926 observer: '_typeChanged'
10927 },
10928
10929 /**
10930 * The key used to store `value` under the `type` namespace.
10931 */
10932 key: {
10933 type: String,
10934 observer: '_keyChanged'
10935 },
10936
10937 /**
10938 * The meta-data to store or retrieve.
10939 */
10940 value: {
10941 type: Object,
10942 notify: true,
10943 observer: '_valueChanged'
10944 },
10945
10946 /**
10947 * If true, `value` is set to the iron-meta instance itself.
10948 */
10949 self: {
10950 type: Boolean,
10951 observer: '_selfChanged'
10952 },
10953
10954 /**
10955 * Array of all meta-data values for the given type.
10956 */
10957 list: {
10958 type: Array,
10959 notify: true
10960 }
10961
10962 },
10963
10964 /**
10965 * Only runs if someone invokes the factory/constructor directly
10966 * e.g. `new Polymer.IronMeta()`
10967 */
10968 factoryImpl: function(config) {
10969 if (config) {
10970 for (var n in config) {
10971 switch(n) {
10972 case 'type':
10973 case 'key':
10974 case 'value':
10975 this[n] = config[n];
10976 break;
10977 }
10978 }
10979 }
10980 },
10981
10982 created: function() {
10983 // TODO(sjmiles): good for debugging?
10984 this._metaDatas = metaDatas;
10985 this._metaArrays = metaArrays;
10986 },
10987
10988 _keyChanged: function(key, old) {
10989 this._resetRegistration(old);
10990 },
10991
10992 _valueChanged: function(value) {
10993 this._resetRegistration(this.key);
10994 },
10995
10996 _selfChanged: function(self) {
10997 if (self) {
10998 this.value = this;
10999 }
11000 },
11001
11002 _typeChanged: function(type) {
11003 this._unregisterKey(this.key);
11004 if (!metaDatas[type]) {
11005 metaDatas[type] = {};
11006 }
11007 this._metaData = metaDatas[type];
11008 if (!metaArrays[type]) {
11009 metaArrays[type] = [];
11010 }
11011 this.list = metaArrays[type];
11012 this._registerKeyValue(this.key, this.value);
11013 },
11014
11015 /**
11016 * Retrieves meta data value by key.
11017 *
11018 * @method byKey
11019 * @param {string} key The key of the meta-data to be returned.
11020 * @return {*}
11021 */
11022 byKey: function(key) {
11023 return this._metaData && this._metaData[key];
11024 },
11025
11026 _resetRegistration: function(oldKey) {
11027 this._unregisterKey(oldKey);
11028 this._registerKeyValue(this.key, this.value);
11029 },
11030
11031 _unregisterKey: function(key) {
11032 this._unregister(key, this._metaData, this.list);
11033 },
11034
11035 _registerKeyValue: function(key, value) {
11036 this._register(key, value, this._metaData, this.list);
11037 },
11038
11039 _register: function(key, value, data, list) {
11040 if (key && data && value !== undefined) {
11041 data[key] = value;
11042 list.push(value);
11043 }
11044 },
11045
11046 _unregister: function(key, data, list) {
11047 if (key && data) {
11048 if (key in data) {
11049 var value = data[key];
11050 delete data[key];
11051 this.arrayDelete(list, value);
11052 }
11053 }
11054 }
11055
11056 });
11057
11058 /**
11059 `iron-meta-query` can be used to access infomation stored in `iron-meta`.
11060
11061 Examples:
11062
11063 If I create an instance like this:
11064
11065 <iron-meta key="info" value="foo/bar"></iron-meta>
11066
11067 Note that value="foo/bar" is the metadata I've defined. I could define more
11068 attributes or use child nodes to define additional metadata.
11069
11070 Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
11071
11072 var value = new Polymer.IronMetaQuery({key: 'info'}).value;
11073
11074 @group Polymer Iron Elements
11075 @element iron-meta-query
11076 */
11077 Polymer.IronMetaQuery = Polymer({
11078
11079 is: 'iron-meta-query',
11080
11081 properties: {
11082
11083 /**
11084 * The type of meta-data. All meta-data of the same type is stored
11085 * together.
11086 */
11087 type: {
11088 type: String,
11089 value: 'default',
11090 observer: '_typeChanged'
11091 },
11092
11093 /**
11094 * Specifies a key to use for retrieving `value` from the `type`
11095 * namespace.
11096 */
11097 key: {
11098 type: String,
11099 observer: '_keyChanged'
11100 },
11101
11102 /**
11103 * The meta-data to store or retrieve.
11104 */
11105 value: {
11106 type: Object,
11107 notify: true,
11108 readOnly: true
11109 },
11110
11111 /**
11112 * Array of all meta-data values for the given type.
11113 */
11114 list: {
11115 type: Array,
11116 notify: true
11117 }
11118
11119 },
11120
11121 /**
11122 * Actually a factory method, not a true constructor. Only runs if
11123 * someone invokes it directly (via `new Polymer.IronMeta()`);
11124 */
11125 factoryImpl: function(config) {
11126 if (config) {
11127 for (var n in config) {
11128 switch(n) {
11129 case 'type':
11130 case 'key':
11131 this[n] = config[n];
11132 break;
11133 }
11134 }
11135 }
11136 },
11137
11138 created: function() {
11139 // TODO(sjmiles): good for debugging?
11140 this._metaDatas = metaDatas;
11141 this._metaArrays = metaArrays;
11142 },
11143
11144 _keyChanged: function(key) {
11145 this._setValue(this._metaData && this._metaData[key]);
11146 },
11147
11148 _typeChanged: function(type) {
11149 this._metaData = metaDatas[type];
11150 this.list = metaArrays[type];
11151 if (this.key) {
11152 this._keyChanged(this.key);
11153 }
11154 },
11155
11156 /**
11157 * Retrieves meta data value by key.
11158 * @param {string} key The key of the meta-data to be returned.
11159 * @return {*}
11160 */
11161 byKey: function(key) {
11162 return this._metaData && this._metaData[key];
11163 }
11164
11165 });
11166
11167 })();
11168 Polymer({
11169
11170 is: 'iron-icon',
11171
11172 properties: {
11173
11174 /**
11175 * The name of the icon to use. The name should be of the form:
11176 * `iconset_name:icon_name`.
11177 */
11178 icon: {
11179 type: String,
11180 observer: '_iconChanged'
11181 },
11182
11183 /**
11184 * The name of the theme to used, if one is specified by the
11185 * iconset.
11186 */
11187 theme: {
11188 type: String,
11189 observer: '_updateIcon'
11190 },
11191
11192 /**
11193 * If using iron-icon without an iconset, you can set the src to be
11194 * the URL of an individual icon image file. Note that this will take
11195 * precedence over a given icon attribute.
11196 */
11197 src: {
11198 type: String,
11199 observer: '_srcChanged'
11200 },
11201
11202 /**
11203 * @type {!Polymer.IronMeta}
11204 */
11205 _meta: {
11206 value: Polymer.Base.create('iron-meta', {type: 'iconset'})
11207 }
11208
11209 },
11210
11211 _DEFAULT_ICONSET: 'icons',
11212
11213 _iconChanged: function(icon) {
11214 var parts = (icon || '').split(':');
11215 this._iconName = parts.pop();
11216 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
11217 this._updateIcon();
11218 },
11219
11220 _srcChanged: function(src) {
11221 this._updateIcon();
11222 },
11223
11224 _usesIconset: function() {
11225 return this.icon || !this.src;
11226 },
11227
11228 /** @suppress {visibility} */
11229 _updateIcon: function() {
11230 if (this._usesIconset()) {
11231 if (this._iconsetName) {
11232 this._iconset = /** @type {?Polymer.Iconset} */ (
11233 this._meta.byKey(this._iconsetName));
11234 if (this._iconset) {
11235 this._iconset.applyIcon(this, this._iconName, this.theme);
11236 this.unlisten(window, 'iron-iconset-added', '_updateIcon');
11237 } else {
11238 this.listen(window, 'iron-iconset-added', '_updateIcon');
11239 }
11240 }
11241 } else {
11242 if (!this._img) {
11243 this._img = document.createElement('img');
11244 this._img.style.width = '100%';
11245 this._img.style.height = '100%';
11246 this._img.draggable = false;
11247 }
11248 this._img.src = this.src;
11249 Polymer.dom(this.root).appendChild(this._img);
11250 }
11251 }
11252
11253 });
11254 /**
11255 * The `iron-iconset-svg` element allows users to define their own icon sets
11256 * that contain svg icons. The svg icon elements should be children of the
11257 * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
11258 *
11259 * Using svg elements to create icons has a few advantages over traditional
11260 * bitmap graphics like jpg or png. Icons that use svg are vector based so the y
11261 * are resolution independent and should look good on any device. They are
11262 * stylable via css. Icons can be themed, colorized, and even animated.
11263 *
11264 * Example:
11265 *
11266 * <iron-iconset-svg name="my-svg-icons" size="24">
11267 * <svg>
11268 * <defs>
11269 * <g id="shape">
11270 * <rect x="50" y="50" width="50" height="50" />
11271 * <circle cx="50" cy="50" r="50" />
11272 * </g>
11273 * </defs>
11274 * </svg>
11275 * </iron-iconset-svg>
11276 *
11277 * This will automatically register the icon set "my-svg-icons" to the iconset
11278 * database. To use these icons from within another element, make a
11279 * `iron-iconset` element and call the `byId` method
11280 * to retrieve a given iconset. To apply a particular icon inside an
11281 * element use the `applyIcon` method. For example:
11282 *
11283 * iconset.applyIcon(iconNode, 'car');
11284 *
11285 * @element iron-iconset-svg
11286 * @demo demo/index.html
11287 */
11288 Polymer({
11289
11290 is: 'iron-iconset-svg',
11291
11292 properties: {
11293
11294 /**
11295 * The name of the iconset.
11296 *
11297 * @attribute name
11298 * @type string
11299 */
11300 name: {
11301 type: String,
11302 observer: '_nameChanged'
11303 },
11304
11305 /**
11306 * The size of an individual icon. Note that icons must be square.
11307 *
11308 * @attribute iconSize
11309 * @type number
11310 * @default 24
11311 */
11312 size: {
11313 type: Number,
11314 value: 24
11315 }
11316
11317 },
11318
11319 /**
11320 * Construct an array of all icon names in this iconset.
11321 *
11322 * @return {!Array} Array of icon names.
11323 */
11324 getIconNames: function() {
11325 this._icons = this._createIconMap();
11326 return Object.keys(this._icons).map(function(n) {
11327 return this.name + ':' + n;
11328 }, this);
11329 },
11330
11331 /**
11332 * Applies an icon to the given element.
11333 *
11334 * An svg icon is prepended to the element's shadowRoot if it exists,
11335 * otherwise to the element itself.
11336 *
11337 * @method applyIcon
11338 * @param {Element} element Element to which the icon is applied.
11339 * @param {string} iconName Name of the icon to apply.
11340 * @return {Element} The svg element which renders the icon.
11341 */
11342 applyIcon: function(element, iconName) {
11343 // insert svg element into shadow root, if it exists
11344 element = element.root || element;
11345 // Remove old svg element
11346 this.removeIcon(element);
11347 // install new svg element
11348 var svg = this._cloneIcon(iconName);
11349 if (svg) {
11350 var pde = Polymer.dom(element);
11351 pde.insertBefore(svg, pde.childNodes[0]);
11352 return element._svgIcon = svg;
11353 }
11354 return null;
11355 },
11356
11357 /**
11358 * Remove an icon from the given element by undoing the changes effected
11359 * by `applyIcon`.
11360 *
11361 * @param {Element} element The element from which the icon is removed.
11362 */
11363 removeIcon: function(element) {
11364 // Remove old svg element
11365 if (element._svgIcon) {
11366 Polymer.dom(element).removeChild(element._svgIcon);
11367 element._svgIcon = null;
11368 }
11369 },
11370
11371 /**
11372 *
11373 * When name is changed, register iconset metadata
11374 *
11375 */
11376 _nameChanged: function() {
11377 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
11378 this.async(function() {
11379 this.fire('iron-iconset-added', this, {node: window});
11380 });
11381 },
11382
11383 /**
11384 * Create a map of child SVG elements by id.
11385 *
11386 * @return {!Object} Map of id's to SVG elements.
11387 */
11388 _createIconMap: function() {
11389 // Objects chained to Object.prototype (`{}`) have members. Specifically,
11390 // on FF there is a `watch` method that confuses the icon map, so we
11391 // need to use a null-based object here.
11392 var icons = Object.create(null);
11393 Polymer.dom(this).querySelectorAll('[id]')
11394 .forEach(function(icon) {
11395 icons[icon.id] = icon;
11396 });
11397 return icons;
11398 },
11399
11400 /**
11401 * Produce installable clone of the SVG element matching `id` in this
11402 * iconset, or `undefined` if there is no matching element.
11403 *
11404 * @return {Element} Returns an installable clone of the SVG element
11405 * matching `id`.
11406 */
11407 _cloneIcon: function(id) {
11408 // create the icon map on-demand, since the iconset itself has no discrete
11409 // signal to know when it's children are fully parsed
11410 this._icons = this._icons || this._createIconMap();
11411 return this._prepareSvgClone(this._icons[id], this.size);
11412 },
11413
11414 /**
11415 * @param {Element} sourceSvg
11416 * @param {number} size
11417 * @return {Element}
11418 */
11419 _prepareSvgClone: function(sourceSvg, size) {
11420 if (sourceSvg) {
11421 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
11422 svg.setAttribute('viewBox', ['0', '0', size, size].join(' '));
11423 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
11424 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136
11425 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
11426 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
11427 svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id');
11428 return svg;
11429 }
11430 return null;
11431 }
11432
11433 });
11434 Polymer({
11435 is: 'paper-item',
11436
11437 hostAttributes: {
11438 role: 'listitem',
11439 tabindex: '0'
11440 },
11441
11442 behaviors: [
11443 Polymer.IronControlState,
11444 Polymer.IronButtonState
11445 ]
11446 });
11447 /**
11448 * @param {!Function} selectCallback
11449 * @constructor
11450 */
11451 Polymer.IronSelection = function(selectCallback) {
11452 this.selection = [];
11453 this.selectCallback = selectCallback;
11454 };
11455
11456 Polymer.IronSelection.prototype = {
11457
11458 /**
11459 * Retrieves the selected item(s).
11460 *
11461 * @method get
11462 * @returns Returns the selected item(s). If the multi property is true,
11463 * `get` will return an array, otherwise it will return
11464 * the selected item or undefined if there is no selection.
11465 */
11466 get: function() {
11467 return this.multi ? this.selection.slice() : this.selection[0];
11468 },
11469
11470 /**
11471 * Clears all the selection except the ones indicated.
11472 *
11473 * @method clear
11474 * @param {Array} excludes items to be excluded.
11475 */
11476 clear: function(excludes) {
11477 this.selection.slice().forEach(function(item) {
11478 if (!excludes || excludes.indexOf(item) < 0) {
11479 this.setItemSelected(item, false);
11480 }
11481 }, this);
11482 },
11483
11484 /**
11485 * Indicates if a given item is selected.
11486 *
11487 * @method isSelected
11488 * @param {*} item The item whose selection state should be checked.
11489 * @returns Returns true if `item` is selected.
11490 */
11491 isSelected: function(item) {
11492 return this.selection.indexOf(item) >= 0;
11493 },
11494
11495 /**
11496 * Sets the selection state for a given item to either selected or deselecte d.
11497 *
11498 * @method setItemSelected
11499 * @param {*} item The item to select.
11500 * @param {boolean} isSelected True for selected, false for deselected.
11501 */
11502 setItemSelected: function(item, isSelected) {
11503 if (item != null) {
11504 if (isSelected) {
11505 this.selection.push(item);
11506 } else {
11507 var i = this.selection.indexOf(item);
11508 if (i >= 0) {
11509 this.selection.splice(i, 1);
11510 }
11511 }
11512 if (this.selectCallback) {
11513 this.selectCallback(item, isSelected);
11514 }
11515 }
11516 },
11517
11518 /**
11519 * Sets the selection state for a given item. If the `multi` property
11520 * is true, then the selected state of `item` will be toggled; otherwise
11521 * the `item` will be selected.
11522 *
11523 * @method select
11524 * @param {*} item The item to select.
11525 */
11526 select: function(item) {
11527 if (this.multi) {
11528 this.toggle(item);
11529 } else if (this.get() !== item) {
11530 this.setItemSelected(this.get(), false);
11531 this.setItemSelected(item, true);
11532 }
11533 },
11534
11535 /**
11536 * Toggles the selection state for `item`.
11537 *
11538 * @method toggle
11539 * @param {*} item The item to toggle.
11540 */
11541 toggle: function(item) {
11542 this.setItemSelected(item, !this.isSelected(item));
11543 }
11544
11545 };
11546 /** @polymerBehavior */
11547 Polymer.IronSelectableBehavior = {
11548
11549 /**
11550 * Fired when iron-selector is activated (selected or deselected).
11551 * It is fired before the selected items are changed.
11552 * Cancel the event to abort selection.
11553 *
11554 * @event iron-activate
11555 */
11556
11557 /**
11558 * Fired when an item is selected
11559 *
11560 * @event iron-select
11561 */
11562
11563 /**
11564 * Fired when an item is deselected
11565 *
11566 * @event iron-deselect
11567 */
11568
11569 /**
11570 * Fired when the list of selectable items changes (e.g., items are
11571 * added or removed). The detail of the event is a list of mutation
11572 * records that describe what changed.
11573 *
11574 * @event iron-items-changed
11575 */
11576
11577 properties: {
11578
11579 /**
11580 * If you want to use the attribute value of an element for `selected` ins tead of the index,
11581 * set this to the name of the attribute.
11582 */
11583 attrForSelected: {
11584 type: String,
11585 value: null
11586 },
11587
11588 /**
11589 * Gets or sets the selected element. The default is to use the index of t he item.
11590 */
11591 selected: {
11592 type: String,
11593 notify: true
11594 },
11595
11596 /**
11597 * Returns the currently selected item.
11598 */
11599 selectedItem: {
11600 type: Object,
11601 readOnly: true,
11602 notify: true
11603 },
11604
11605 /**
11606 * The event that fires from items when they are selected. Selectable
11607 * will listen for this event from items and update the selection state.
11608 * Set to empty string to listen to no events.
11609 */
11610 activateEvent: {
11611 type: String,
11612 value: 'tap',
11613 observer: '_activateEventChanged'
11614 },
11615
11616 /**
11617 * This is a CSS selector string. If this is set, only items that match t he CSS selector
11618 * are selectable.
11619 */
11620 selectable: String,
11621
11622 /**
11623 * The class to set on elements when selected.
11624 */
11625 selectedClass: {
11626 type: String,
11627 value: 'iron-selected'
11628 },
11629
11630 /**
11631 * The attribute to set on elements when selected.
11632 */
11633 selectedAttribute: {
11634 type: String,
11635 value: null
11636 },
11637
11638 /**
11639 * The set of excluded elements where the key is the `localName`
11640 * of the element that will be ignored from the item list.
11641 *
11642 * @type {object}
11643 * @default {template: 1}
11644 */
11645 excludedLocalNames: {
11646 type: Object,
11647 value: function() {
11648 return {
11649 'template': 1
11650 };
11651 }
11652 }
11653 },
11654
11655 observers: [
11656 '_updateSelected(attrForSelected, selected)'
11657 ],
11658
11659 created: function() {
11660 this._bindFilterItem = this._filterItem.bind(this);
11661 this._selection = new Polymer.IronSelection(this._applySelection.bind(this ));
11662 },
11663
11664 attached: function() {
11665 this._observer = this._observeItems(this);
11666 this._contentObserver = this._observeContent(this);
11667 if (!this.selectedItem && this.selected) {
11668 this._updateSelected(this.attrForSelected,this.selected)
11669 }
11670 },
11671
11672 detached: function() {
11673 if (this._observer) {
11674 this._observer.disconnect();
11675 }
11676 if (this._contentObserver) {
11677 this._contentObserver.disconnect();
11678 }
11679 this._removeListener(this.activateEvent);
11680 },
11681
11682 /**
11683 * Returns an array of selectable items.
11684 *
11685 * @property items
11686 * @type Array
11687 */
11688 get items() {
11689 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
11690 return Array.prototype.filter.call(nodes, this._bindFilterItem);
11691 },
11692
11693 /**
11694 * Returns the index of the given item.
11695 *
11696 * @method indexOf
11697 * @param {Object} item
11698 * @returns Returns the index of the item
11699 */
11700 indexOf: function(item) {
11701 return this.items.indexOf(item);
11702 },
11703
11704 /**
11705 * Selects the given value.
11706 *
11707 * @method select
11708 * @param {string} value the value to select.
11709 */
11710 select: function(value) {
11711 this.selected = value;
11712 },
11713
11714 /**
11715 * Selects the previous item.
11716 *
11717 * @method selectPrevious
11718 */
11719 selectPrevious: function() {
11720 var length = this.items.length;
11721 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len gth;
11722 this.selected = this._indexToValue(index);
11723 },
11724
11725 /**
11726 * Selects the next item.
11727 *
11728 * @method selectNext
11729 */
11730 selectNext: function() {
11731 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l ength;
11732 this.selected = this._indexToValue(index);
11733 },
11734
11735 _addListener: function(eventName) {
11736 this.listen(this, eventName, '_activateHandler');
11737 },
11738
11739 _removeListener: function(eventName) {
11740 this.unlisten(this, eventName, '_activateHandler');
11741 },
11742
11743 _activateEventChanged: function(eventName, old) {
11744 this._removeListener(old);
11745 this._addListener(eventName);
11746 },
11747
11748 _updateSelected: function() {
11749 this._selectSelected(this.selected);
11750 },
11751
11752 _selectSelected: function(selected) {
11753 this._selection.select(this._valueToItem(this.selected));
11754 },
11755
11756 _filterItem: function(node) {
11757 return !this.excludedLocalNames[node.localName];
11758 },
11759
11760 _valueToItem: function(value) {
11761 return (value == null) ? null : this.items[this._valueToIndex(value)];
11762 },
11763
11764 _valueToIndex: function(value) {
11765 if (this.attrForSelected) {
11766 for (var i = 0, item; item = this.items[i]; i++) {
11767 if (this._valueForItem(item) == value) {
11768 return i;
11769 }
11770 }
11771 } else {
11772 return Number(value);
11773 }
11774 },
11775
11776 _indexToValue: function(index) {
11777 if (this.attrForSelected) {
11778 var item = this.items[index];
11779 if (item) {
11780 return this._valueForItem(item);
11781 }
11782 } else {
11783 return index;
11784 }
11785 },
11786
11787 _valueForItem: function(item) {
11788 return item[this.attrForSelected] || item.getAttribute(this.attrForSelecte d);
11789 },
11790
11791 _applySelection: function(item, isSelected) {
11792 if (this.selectedClass) {
11793 this.toggleClass(this.selectedClass, isSelected, item);
11794 }
11795 if (this.selectedAttribute) {
11796 this.toggleAttribute(this.selectedAttribute, isSelected, item);
11797 }
11798 this._selectionChange();
11799 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
11800 },
11801
11802 _selectionChange: function() {
11803 this._setSelectedItem(this._selection.get());
11804 },
11805
11806 // observe content changes under the given node.
11807 _observeContent: function(node) {
11808 var content = node.querySelector('content');
11809 if (content && content.parentElement === node) {
11810 return this._observeItems(node.domHost);
11811 }
11812 },
11813
11814 // observe items change under the given node.
11815 _observeItems: function(node) {
11816 // TODO(cdata): Update this when we get distributed children changed.
11817 var observer = new MutationObserver(function(mutations) {
11818 // Let other interested parties know about the change so that
11819 // we don't have to recreate mutation observers everywher.
11820 this.fire('iron-items-changed', mutations, {
11821 bubbles: false,
11822 cancelable: false
11823 });
11824
11825 if (this.selected != null) {
11826 this._updateSelected();
11827 }
11828 }.bind(this));
11829 observer.observe(node, {
11830 childList: true,
11831 subtree: true
11832 });
11833 return observer;
11834 },
11835
11836 _activateHandler: function(e) {
11837 var t = e.target;
11838 var items = this.items;
11839 while (t && t != this) {
11840 var i = items.indexOf(t);
11841 if (i >= 0) {
11842 var value = this._indexToValue(i);
11843 this._itemActivate(value, t);
11844 return;
11845 }
11846 t = t.parentNode;
11847 }
11848 },
11849
11850 _itemActivate: function(value, item) {
11851 if (!this.fire('iron-activate',
11852 {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
11853 this.select(value);
11854 }
11855 }
11856
11857 };
11858 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */
11859 Polymer.IronMultiSelectableBehaviorImpl = {
11860 properties: {
11861
11862 /**
11863 * If true, multiple selections are allowed.
11864 */
11865 multi: {
11866 type: Boolean,
11867 value: false,
11868 observer: 'multiChanged'
11869 },
11870
11871 /**
11872 * Gets or sets the selected elements. This is used instead of `selected` when `multi`
11873 * is true.
11874 */
11875 selectedValues: {
11876 type: Array,
11877 notify: true
11878 },
11879
11880 /**
11881 * Returns an array of currently selected items.
11882 */
11883 selectedItems: {
11884 type: Array,
11885 readOnly: true,
11886 notify: true
11887 },
11888
11889 },
11890
11891 observers: [
11892 '_updateSelected(attrForSelected, selectedValues)'
11893 ],
11894
11895 /**
11896 * Selects the given value. If the `multi` property is true, then the select ed state of the
11897 * `value` will be toggled; otherwise the `value` will be selected.
11898 *
11899 * @method select
11900 * @param {string} value the value to select.
11901 */
11902 select: function(value) {
11903 if (this.multi) {
11904 if (this.selectedValues) {
11905 this._toggleSelected(value);
11906 } else {
11907 this.selectedValues = [value];
11908 }
11909 } else {
11910 this.selected = value;
11911 }
11912 },
11913
11914 multiChanged: function(multi) {
11915 this._selection.multi = multi;
11916 },
11917
11918 _updateSelected: function() {
11919 if (this.multi) {
11920 this._selectMulti(this.selectedValues);
11921 } else {
11922 this._selectSelected(this.selected);
11923 }
11924 },
11925
11926 _selectMulti: function(values) {
11927 this._selection.clear();
11928 if (values) {
11929 for (var i = 0; i < values.length; i++) {
11930 this._selection.setItemSelected(this._valueToItem(values[i]), true);
11931 }
11932 }
11933 },
11934
11935 _selectionChange: function() {
11936 var s = this._selection.get();
11937 if (this.multi) {
11938 this._setSelectedItems(s);
11939 } else {
11940 this._setSelectedItems([s]);
11941 this._setSelectedItem(s);
11942 }
11943 },
11944
11945 _toggleSelected: function(value) {
11946 var i = this.selectedValues.indexOf(value);
11947 var unselected = i < 0;
11948 if (unselected) {
11949 this.push('selectedValues',value);
11950 } else {
11951 this.splice('selectedValues',i,1);
11952 }
11953 this._selection.setItemSelected(this._valueToItem(value), unselected);
11954 }
11955 };
11956
11957 /** @polymerBehavior */
11958 Polymer.IronMultiSelectableBehavior = [
11959 Polymer.IronSelectableBehavior,
11960 Polymer.IronMultiSelectableBehaviorImpl
11961 ];
11962 /**
11963 * `Polymer.IronMenuBehavior` implements accessible menu behavior.
11964 *
11965 * @demo demo/index.html
11966 * @polymerBehavior Polymer.IronMenuBehavior
11967 */
11968 Polymer.IronMenuBehaviorImpl = {
11969
11970 properties: {
11971
11972 /**
11973 * Returns the currently focused item.
11974 * @type {?Object}
11975 */
11976 focusedItem: {
11977 observer: '_focusedItemChanged',
11978 readOnly: true,
11979 type: Object
11980 },
11981
11982 /**
11983 * The attribute to use on menu items to look up the item title. Typing th e first
11984 * letter of an item when the menu is open focuses that item. If unset, `t extContent`
11985 * will be used.
11986 */
11987 attrForItemTitle: {
11988 type: String
11989 }
11990 },
11991
11992 hostAttributes: {
11993 'role': 'menu',
11994 'tabindex': '0'
11995 },
11996
11997 observers: [
11998 '_updateMultiselectable(multi)'
11999 ],
12000
12001 listeners: {
12002 'focus': '_onFocus',
12003 'keydown': '_onKeydown',
12004 'iron-items-changed': '_onIronItemsChanged'
12005 },
12006
12007 keyBindings: {
12008 'up': '_onUpKey',
12009 'down': '_onDownKey',
12010 'esc': '_onEscKey',
12011 'shift+tab:keydown': '_onShiftTabDown'
12012 },
12013
12014 attached: function() {
12015 this._resetTabindices();
12016 },
12017
12018 /**
12019 * Selects the given value. If the `multi` property is true, then the select ed state of the
12020 * `value` will be toggled; otherwise the `value` will be selected.
12021 *
12022 * @param {string} value the value to select.
12023 */
12024 select: function(value) {
12025 if (this._defaultFocusAsync) {
12026 this.cancelAsync(this._defaultFocusAsync);
12027 this._defaultFocusAsync = null;
12028 }
12029 var item = this._valueToItem(value);
12030 if (item && item.hasAttribute('disabled')) return;
12031 this._setFocusedItem(item);
12032 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
12033 },
12034
12035 /**
12036 * Resets all tabindex attributes to the appropriate value based on the
12037 * current selection state. The appropriate value is `0` (focusable) for
12038 * the default selected item, and `-1` (not keyboard focusable) for all
12039 * other items.
12040 */
12041 _resetTabindices: function() {
12042 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[ 0]) : this.selectedItem;
12043
12044 this.items.forEach(function(item) {
12045 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
12046 }, this);
12047 },
12048
12049 /**
12050 * Sets appropriate ARIA based on whether or not the menu is meant to be
12051 * multi-selectable.
12052 *
12053 * @param {boolean} multi True if the menu should be multi-selectable.
12054 */
12055 _updateMultiselectable: function(multi) {
12056 if (multi) {
12057 this.setAttribute('aria-multiselectable', 'true');
12058 } else {
12059 this.removeAttribute('aria-multiselectable');
12060 }
12061 },
12062
12063 /**
12064 * Given a KeyboardEvent, this method will focus the appropriate item in the
12065 * menu (if there is a relevant item, and it is possible to focus it).
12066 *
12067 * @param {KeyboardEvent} event A KeyboardEvent.
12068 */
12069 _focusWithKeyboardEvent: function(event) {
12070 for (var i = 0, item; item = this.items[i]; i++) {
12071 var attr = this.attrForItemTitle || 'textContent';
12072 var title = item[attr] || item.getAttribute(attr);
12073 if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCod e(event.keyCode).toLowerCase()) {
12074 this._setFocusedItem(item);
12075 break;
12076 }
12077 }
12078 },
12079
12080 /**
12081 * Focuses the previous item (relative to the currently focused item) in the
12082 * menu.
12083 */
12084 _focusPrevious: function() {
12085 var length = this.items.length;
12086 var index = (Number(this.indexOf(this.focusedItem)) - 1 + length) % length ;
12087 this._setFocusedItem(this.items[index]);
12088 },
12089
12090 /**
12091 * Focuses the next item (relative to the currently focused item) in the
12092 * menu.
12093 */
12094 _focusNext: function() {
12095 var index = (Number(this.indexOf(this.focusedItem)) + 1) % this.items.leng th;
12096 this._setFocusedItem(this.items[index]);
12097 },
12098
12099 /**
12100 * Mutates items in the menu based on provided selection details, so that
12101 * all items correctly reflect selection state.
12102 *
12103 * @param {Element} item An item in the menu.
12104 * @param {boolean} isSelected True if the item should be shown in a
12105 * selected state, otherwise false.
12106 */
12107 _applySelection: function(item, isSelected) {
12108 if (isSelected) {
12109 item.setAttribute('aria-selected', 'true');
12110 } else {
12111 item.removeAttribute('aria-selected');
12112 }
12113
12114 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
12115 },
12116
12117 /**
12118 * Discretely updates tabindex values among menu items as the focused item
12119 * changes.
12120 *
12121 * @param {Element} focusedItem The element that is currently focused.
12122 * @param {?Element} old The last element that was considered focused, if
12123 * applicable.
12124 */
12125 _focusedItemChanged: function(focusedItem, old) {
12126 old && old.setAttribute('tabindex', '-1');
12127 if (focusedItem) {
12128 focusedItem.setAttribute('tabindex', '0');
12129 focusedItem.focus();
12130 }
12131 },
12132
12133 /**
12134 * A handler that responds to mutation changes related to the list of items
12135 * in the menu.
12136 *
12137 * @param {CustomEvent} event An event containing mutation records as its
12138 * detail.
12139 */
12140 _onIronItemsChanged: function(event) {
12141 var mutations = event.detail;
12142 var mutation;
12143 var index;
12144
12145 for (index = 0; index < mutations.length; ++index) {
12146 mutation = mutations[index];
12147
12148 if (mutation.addedNodes.length) {
12149 this._resetTabindices();
12150 break;
12151 }
12152 }
12153 },
12154
12155 /**
12156 * Handler that is called when a shift+tab keypress is detected by the menu.
12157 *
12158 * @param {CustomEvent} event A key combination event.
12159 */
12160 _onShiftTabDown: function(event) {
12161 var oldTabIndex;
12162
12163 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;
12164
12165 oldTabIndex = this.getAttribute('tabindex');
12166
12167 this.setAttribute('tabindex', '-1');
12168
12169 this.async(function() {
12170 this.setAttribute('tabindex', oldTabIndex);
12171 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
12172 // NOTE(cdata): polymer/polymer#1305
12173 }, 1);
12174 },
12175
12176 /**
12177 * Handler that is called when the menu receives focus.
12178 *
12179 * @param {FocusEvent} event A focus event.
12180 */
12181 _onFocus: function(event) {
12182 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
12183 return;
12184 }
12185 // do not focus the menu itself
12186 this.blur();
12187 // clear the cached focus item
12188 this._setFocusedItem(null);
12189 this._defaultFocusAsync = this.async(function() {
12190 // focus the selected item when the menu receives focus, or the first it em
12191 // if no item is selected
12192 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem;
12193 if (selectedItem) {
12194 this._setFocusedItem(selectedItem);
12195 } else {
12196 this._setFocusedItem(this.items[0]);
12197 }
12198 // async 100ms to wait for `select` to get called from `_itemActivate`
12199 }, 100);
12200 },
12201
12202 /**
12203 * Handler that is called when the up key is pressed.
12204 *
12205 * @param {CustomEvent} event A key combination event.
12206 */
12207 _onUpKey: function(event) {
12208 // up and down arrows moves the focus
12209 this._focusPrevious();
12210 },
12211
12212 /**
12213 * Handler that is called when the down key is pressed.
12214 *
12215 * @param {CustomEvent} event A key combination event.
12216 */
12217 _onDownKey: function(event) {
12218 this._focusNext();
12219 },
12220
12221 /**
12222 * Handler that is called when the esc key is pressed.
12223 *
12224 * @param {CustomEvent} event A key combination event.
12225 */
12226 _onEscKey: function(event) {
12227 // esc blurs the control
12228 this.focusedItem.blur();
12229 },
12230
12231 /**
12232 * Handler that is called when a keydown event is detected.
12233 *
12234 * @param {KeyboardEvent} event A keyboard event.
12235 */
12236 _onKeydown: function(event) {
12237 if (this.keyboardEventMatchesKeys(event, 'up down esc')) {
12238 return;
12239 }
12240
12241 // all other keys focus the menu item starting with that character
12242 this._focusWithKeyboardEvent(event);
12243 }
12244 };
12245
12246 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
12247
12248 /** @polymerBehavior Polymer.IronMenuBehavior */
12249 Polymer.IronMenuBehavior = [
12250 Polymer.IronMultiSelectableBehavior,
12251 Polymer.IronA11yKeysBehavior,
12252 Polymer.IronMenuBehaviorImpl
12253 ];
12254 (function() {
12255
12256 Polymer({
12257
12258 is: 'paper-menu',
12259
12260 behaviors: [
12261 Polymer.IronMenuBehavior
12262 ]
12263
12264 });
12265
12266 })();
12267 /**
12268 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
12269 * coordinate the flow of resize events between "resizers" (elements that cont rol the
12270 * size or hidden state of their children) and "resizables" (elements that nee d to be
12271 * notified when they are resized or un-hidden by their parents in order to ta ke
12272 * action on their new measurements).
12273 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
12274 * their element definition and listen for the `iron-resize` event on themselv es.
12275 * This event will be fired when they become showing after having been hidden,
12276 * when they are resized explicitly by another resizable, or when the window h as been
12277 * resized.
12278 * Note, the `iron-resize` event is non-bubbling.
12279 *
12280 * @polymerBehavior Polymer.IronResizableBehavior
12281 * @demo demo/index.html
12282 **/
12283 Polymer.IronResizableBehavior = {
12284 properties: {
12285 /**
12286 * The closest ancestor element that implements `IronResizableBehavior`.
12287 */
12288 _parentResizable: {
12289 type: Object,
12290 observer: '_parentResizableChanged'
12291 },
12292
12293 /**
12294 * True if this element is currently notifying its descedant elements of
12295 * resize.
12296 */
12297 _notifyingDescendant: {
12298 type: Boolean,
12299 value: false
12300 }
12301 },
12302
12303 listeners: {
12304 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
12305 },
12306
12307 created: function() {
12308 // We don't really need property effects on these, and also we want them
12309 // to be created before the `_parentResizable` observer fires:
12310 this._interestedResizables = [];
12311 this._boundNotifyResize = this.notifyResize.bind(this);
12312 },
12313
12314 attached: function() {
12315 this.fire('iron-request-resize-notifications', null, {
12316 node: this,
12317 bubbles: true,
12318 cancelable: true
12319 });
12320
12321 if (!this._parentResizable) {
12322 window.addEventListener('resize', this._boundNotifyResize);
12323 this.notifyResize();
12324 }
12325 },
12326
12327 detached: function() {
12328 if (this._parentResizable) {
12329 this._parentResizable.stopResizeNotificationsFor(this);
12330 } else {
12331 window.removeEventListener('resize', this._boundNotifyResize);
12332 }
12333
12334 this._parentResizable = null;
12335 },
12336
12337 /**
12338 * Can be called to manually notify a resizable and its descendant
12339 * resizables of a resize change.
12340 */
12341 notifyResize: function() {
12342 if (!this.isAttached) {
12343 return;
12344 }
12345
12346 this._interestedResizables.forEach(function(resizable) {
12347 if (this.resizerShouldNotify(resizable)) {
12348 this._notifyDescendant(resizable);
12349 }
12350 }, this);
12351
12352 this._fireResize();
12353 },
12354
12355 /**
12356 * Used to assign the closest resizable ancestor to this resizable
12357 * if the ancestor detects a request for notifications.
12358 */
12359 assignParentResizable: function(parentResizable) {
12360 this._parentResizable = parentResizable;
12361 },
12362
12363 /**
12364 * Used to remove a resizable descendant from the list of descendants
12365 * that should be notified of a resize change.
12366 */
12367 stopResizeNotificationsFor: function(target) {
12368 var index = this._interestedResizables.indexOf(target);
12369
12370 if (index > -1) {
12371 this._interestedResizables.splice(index, 1);
12372 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
12373 }
12374 },
12375
12376 /**
12377 * This method can be overridden to filter nested elements that should or
12378 * should not be notified by the current element. Return true if an element
12379 * should be notified, or false if it should not be notified.
12380 *
12381 * @param {HTMLElement} element A candidate descendant element that
12382 * implements `IronResizableBehavior`.
12383 * @return {boolean} True if the `element` should be notified of resize.
12384 */
12385 resizerShouldNotify: function(element) { return true; },
12386
12387 _onDescendantIronResize: function(event) {
12388 if (this._notifyingDescendant) {
12389 event.stopPropagation();
12390 return;
12391 }
12392
12393 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
12394 // otherwise non-bubbling event "just work." We do it manually here for
12395 // the case where Polymer is not using shadow roots for whatever reason:
12396 if (!Polymer.Settings.useShadow) {
12397 this._fireResize();
12398 }
12399 },
12400
12401 _fireResize: function() {
12402 this.fire('iron-resize', null, {
12403 node: this,
12404 bubbles: false
12405 });
12406 },
12407
12408 _onIronRequestResizeNotifications: function(event) {
12409 var target = event.path ? event.path[0] : event.target;
12410
12411 if (target === this) {
12412 return;
12413 }
12414
12415 if (this._interestedResizables.indexOf(target) === -1) {
12416 this._interestedResizables.push(target);
12417 this.listen(target, 'iron-resize', '_onDescendantIronResize');
12418 }
12419
12420 target.assignParentResizable(this);
12421 this._notifyDescendant(target);
12422
12423 event.stopPropagation();
12424 },
12425
12426 _parentResizableChanged: function(parentResizable) {
12427 if (parentResizable) {
12428 window.removeEventListener('resize', this._boundNotifyResize);
12429 }
12430 },
12431
12432 _notifyDescendant: function(descendant) {
12433 // NOTE(cdata): In IE10, attached is fired on children first, so it's
12434 // important not to notify them if the parent is not attached yet (or
12435 // else they will get redundantly notified when the parent attaches).
12436 if (!this.isAttached) {
12437 return;
12438 }
12439
12440 this._notifyingDescendant = true;
12441 descendant.notifyResize();
12442 this._notifyingDescendant = false;
12443 }
12444 };
12445 /**
12446 Polymer.IronFitBehavior fits an element in another element using `max-height` an d `max-width`, and
12447 optionally centers it in the window or another element.
12448
12449 The element will only be sized and/or positioned if it has not already been size d and/or positioned
12450 by CSS.
12451
12452 CSS properties | Action
12453 -----------------------------|-------------------------------------------
12454 `position` set | Element is not centered horizontally or verticall y
12455 `top` or `bottom` set | Element is not vertically centered
12456 `left` or `right` set | Element is not horizontally centered
12457 `max-height` or `height` set | Element respects `max-height` or `height`
12458 `max-width` or `width` set | Element respects `max-width` or `width`
12459
12460 @demo demo/index.html
12461 @polymerBehavior
12462 */
12463
12464 Polymer.IronFitBehavior = {
12465
12466 properties: {
12467
12468 /**
12469 * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
12470 * but it can be set to a child element. This is useful, for example, for implementing a
12471 * scrolling region inside the element.
12472 * @type {!Element}
12473 */
12474 sizingTarget: {
12475 type: Object,
12476 value: function() {
12477 return this;
12478 }
12479 },
12480
12481 /**
12482 * The element to fit `this` into.
12483 */
12484 fitInto: {
12485 type: Object,
12486 value: window
12487 },
12488
12489 /**
12490 * Set to true to auto-fit on attach.
12491 */
12492 autoFitOnAttach: {
12493 type: Boolean,
12494 value: false
12495 },
12496
12497 /** @type {?Object} */
12498 _fitInfo: {
12499 type: Object
12500 }
12501
12502 },
12503
12504 get _fitWidth() {
12505 var fitWidth;
12506 if (this.fitInto === window) {
12507 fitWidth = this.fitInto.innerWidth;
12508 } else {
12509 fitWidth = this.fitInto.getBoundingClientRect().width;
12510 }
12511 return fitWidth;
12512 },
12513
12514 get _fitHeight() {
12515 var fitHeight;
12516 if (this.fitInto === window) {
12517 fitHeight = this.fitInto.innerHeight;
12518 } else {
12519 fitHeight = this.fitInto.getBoundingClientRect().height;
12520 }
12521 return fitHeight;
12522 },
12523
12524 get _fitLeft() {
12525 var fitLeft;
12526 if (this.fitInto === window) {
12527 fitLeft = 0;
12528 } else {
12529 fitLeft = this.fitInto.getBoundingClientRect().left;
12530 }
12531 return fitLeft;
12532 },
12533
12534 get _fitTop() {
12535 var fitTop;
12536 if (this.fitInto === window) {
12537 fitTop = 0;
12538 } else {
12539 fitTop = this.fitInto.getBoundingClientRect().top;
12540 }
12541 return fitTop;
12542 },
12543
12544 attached: function() {
12545 if (this.autoFitOnAttach) {
12546 if (window.getComputedStyle(this).display === 'none') {
12547 setTimeout(function() {
12548 this.fit();
12549 }.bind(this));
12550 } else {
12551 this.fit();
12552 }
12553 }
12554 },
12555
12556 /**
12557 * Fits and optionally centers the element into the window, or `fitInfo` if specified.
12558 */
12559 fit: function() {
12560 this._discoverInfo();
12561 this.constrain();
12562 this.center();
12563 },
12564
12565 /**
12566 * Memoize information needed to position and size the target element.
12567 */
12568 _discoverInfo: function() {
12569 if (this._fitInfo) {
12570 return;
12571 }
12572 var target = window.getComputedStyle(this);
12573 var sizer = window.getComputedStyle(this.sizingTarget);
12574 this._fitInfo = {
12575 inlineStyle: {
12576 top: this.style.top || '',
12577 left: this.style.left || ''
12578 },
12579 positionedBy: {
12580 vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
12581 'bottom' : null),
12582 horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'aut o' ?
12583 'right' : null),
12584 css: target.position
12585 },
12586 sizedBy: {
12587 height: sizer.maxHeight !== 'none',
12588 width: sizer.maxWidth !== 'none'
12589 },
12590 margin: {
12591 top: parseInt(target.marginTop, 10) || 0,
12592 right: parseInt(target.marginRight, 10) || 0,
12593 bottom: parseInt(target.marginBottom, 10) || 0,
12594 left: parseInt(target.marginLeft, 10) || 0
12595 }
12596 };
12597 },
12598
12599 /**
12600 * Resets the target element's position and size constraints, and clear
12601 * the memoized data.
12602 */
12603 resetFit: function() {
12604 if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
12605 this.sizingTarget.style.maxHeight = '';
12606 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
12607 }
12608 if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
12609 this.sizingTarget.style.maxWidth = '';
12610 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
12611 }
12612 if (this._fitInfo) {
12613 this.style.position = this._fitInfo.positionedBy.css;
12614 }
12615 this._fitInfo = null;
12616 },
12617
12618 /**
12619 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
12620 * the window, or the `fitInfo` element has been resized.
12621 */
12622 refit: function() {
12623 this.resetFit();
12624 this.fit();
12625 },
12626
12627 /**
12628 * Constrains the size of the element to the window or `fitInfo` by setting `max-height`
12629 * and/or `max-width`.
12630 */
12631 constrain: function() {
12632 var info = this._fitInfo;
12633 // position at (0px, 0px) if not already positioned, so we can measure the natural size.
12634 if (!this._fitInfo.positionedBy.vertically) {
12635 this.style.top = '0px';
12636 }
12637 if (!this._fitInfo.positionedBy.horizontally) {
12638 this.style.left = '0px';
12639 }
12640 // need border-box for margin/padding
12641 this.sizingTarget.style.boxSizing = 'border-box';
12642 // constrain the width and height if not already set
12643 var rect = this.getBoundingClientRect();
12644 if (!info.sizedBy.height) {
12645 this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
12646 }
12647 if (!info.sizedBy.width) {
12648 this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right ', 'Width');
12649 }
12650 },
12651
12652 _sizeDimension: function(rect, positionedBy, start, end, extent) {
12653 var info = this._fitInfo;
12654 var max = extent === 'Width' ? this._fitWidth : this._fitHeight;
12655 var flip = (positionedBy === end);
12656 var offset = flip ? max - rect[end] : rect[start];
12657 var margin = info.margin[flip ? start : end];
12658 var offsetExtent = 'offset' + extent;
12659 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
12660 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px';
12661 },
12662
12663 /**
12664 * Centers horizontally and vertically if not already positioned. This also sets
12665 * `position:fixed`.
12666 */
12667 center: function() {
12668 if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy. horizontally) {
12669 // need position:fixed to center
12670 this.style.position = 'fixed';
12671 }
12672 if (!this._fitInfo.positionedBy.vertically) {
12673 var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop;
12674 top -= this._fitInfo.margin.top;
12675 this.style.top = top + 'px';
12676 }
12677 if (!this._fitInfo.positionedBy.horizontally) {
12678 var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft;
12679 left -= this._fitInfo.margin.left;
12680 this.style.left = left + 'px';
12681 }
12682 }
12683
12684 };
12685 Polymer.IronOverlayManager = (function() {
12686
12687 var overlays = [];
12688 var DEFAULT_Z = 10;
12689 var backdrops = [];
12690
12691 // track overlays for z-index and focus managemant
12692 function addOverlay(overlay) {
12693 var z0 = currentOverlayZ();
12694 overlays.push(overlay);
12695 var z1 = currentOverlayZ();
12696 if (z1 <= z0) {
12697 applyOverlayZ(overlay, z0);
12698 }
12699 }
12700
12701 function removeOverlay(overlay) {
12702 var i = overlays.indexOf(overlay);
12703 if (i >= 0) {
12704 overlays.splice(i, 1);
12705 setZ(overlay, '');
12706 }
12707 }
12708
12709 function applyOverlayZ(overlay, aboveZ) {
12710 setZ(overlay, aboveZ + 2);
12711 }
12712
12713 function setZ(element, z) {
12714 element.style.zIndex = z;
12715 }
12716
12717 function currentOverlay() {
12718 var i = overlays.length - 1;
12719 while (overlays[i] && !overlays[i].opened) {
12720 --i;
12721 }
12722 return overlays[i];
12723 }
12724
12725 function currentOverlayZ() {
12726 var z;
12727 var current = currentOverlay();
12728 if (current) {
12729 var z1 = window.getComputedStyle(current).zIndex;
12730 if (!isNaN(z1)) {
12731 z = Number(z1);
12732 }
12733 }
12734 return z || DEFAULT_Z;
12735 }
12736
12737 function focusOverlay() {
12738 var current = currentOverlay();
12739 // We have to be careful to focus the next overlay _after_ any current
12740 // transitions are complete (due to the state being toggled prior to the
12741 // transition). Otherwise, we risk infinite recursion when a transitioning
12742 // (closed) overlay becomes the current overlay.
12743 //
12744 // NOTE: We make the assumption that any overlay that completes a transiti on
12745 // will call into focusOverlay to kick the process back off. Currently:
12746 // transitionend -> _applyFocus -> focusOverlay.
12747 if (current && !current.transitioning) {
12748 current._applyFocus();
12749 }
12750 }
12751
12752 function trackBackdrop(element) {
12753 // backdrops contains the overlays with a backdrop that are currently
12754 // visible
12755 if (element.opened) {
12756 backdrops.push(element);
12757 } else {
12758 var index = backdrops.indexOf(element);
12759 if (index >= 0) {
12760 backdrops.splice(index, 1);
12761 }
12762 }
12763 }
12764
12765 function getBackdrops() {
12766 return backdrops;
12767 }
12768
12769 return {
12770 addOverlay: addOverlay,
12771 removeOverlay: removeOverlay,
12772 currentOverlay: currentOverlay,
12773 currentOverlayZ: currentOverlayZ,
12774 focusOverlay: focusOverlay,
12775 trackBackdrop: trackBackdrop,
12776 getBackdrops: getBackdrops
12777 };
12778
12779 })();
12780 (function() {
12781
12782 Polymer({
12783
12784 is: 'iron-overlay-backdrop',
12785
12786 properties: {
12787
12788 /**
12789 * Returns true if the backdrop is opened.
12790 */
12791 opened: {
12792 readOnly: true,
12793 reflectToAttribute: true,
12794 type: Boolean,
12795 value: false
12796 },
12797
12798 _manager: {
12799 type: Object,
12800 value: Polymer.IronOverlayManager
12801 }
12802
12803 },
12804
12805 /**
12806 * Appends the backdrop to document body and sets its `z-index` to be below the latest overlay.
12807 */
12808 prepare: function() {
12809 if (!this.parentNode) {
12810 Polymer.dom(document.body).appendChild(this);
12811 this.style.zIndex = this._manager.currentOverlayZ() - 1;
12812 }
12813 },
12814
12815 /**
12816 * Shows the backdrop if needed.
12817 */
12818 open: function() {
12819 // only need to make the backdrop visible if this is called by the first o verlay with a backdrop
12820 if (this._manager.getBackdrops().length < 2) {
12821 this._setOpened(true);
12822 }
12823 },
12824
12825 /**
12826 * Hides the backdrop if needed.
12827 */
12828 close: function() {
12829 // only need to make the backdrop invisible if this is called by the last overlay with a backdrop
12830 if (this._manager.getBackdrops().length < 2) {
12831 this._setOpened(false);
12832 }
12833 },
12834
12835 /**
12836 * Removes the backdrop from document body if needed.
12837 */
12838 complete: function() {
12839 // only remove the backdrop if there are no more overlays with backdrops
12840 if (this._manager.getBackdrops().length === 0 && this.parentNode) {
12841 Polymer.dom(this.parentNode).removeChild(this);
12842 }
12843 }
12844
12845 });
12846
12847 })();
12848 /**
12849 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
12850 on top of other content. It includes an optional backdrop, and can be used to im plement a variety
12851 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once.
12852
12853 ### Closing and canceling
12854
12855 A dialog may be hidden by closing or canceling. The difference between close and cancel is user
12856 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
12857 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
12858 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click ` properties.
12859 `close()` should be called explicitly by the implementer when the user interacts with a control
12860 in the overlay element.
12861
12862 ### Positioning
12863
12864 By default the element is sized and positioned to fit and centered inside the wi ndow. You can
12865 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
12866
12867 ### Backdrop
12868
12869 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
12870 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
12871 options.
12872
12873 ### Limitations
12874
12875 The element is styled to appear on top of other content by setting its `z-index` property. You
12876 must ensure no element has a stacking context with a higher `z-index` than its p arent stacking
12877 context. You should place this element as a child of `<body>` whenever possible.
12878
12879 @demo demo/index.html
12880 @polymerBehavior Polymer.IronOverlayBehavior
12881 */
12882
12883 Polymer.IronOverlayBehaviorImpl = {
12884
12885 properties: {
12886
12887 /**
12888 * True if the overlay is currently displayed.
12889 */
12890 opened: {
12891 observer: '_openedChanged',
12892 type: Boolean,
12893 value: false,
12894 notify: true
12895 },
12896
12897 /**
12898 * True if the overlay was canceled when it was last closed.
12899 */
12900 canceled: {
12901 observer: '_canceledChanged',
12902 readOnly: true,
12903 type: Boolean,
12904 value: false
12905 },
12906
12907 /**
12908 * Set to true to display a backdrop behind the overlay.
12909 */
12910 withBackdrop: {
12911 type: Boolean,
12912 value: false
12913 },
12914
12915 /**
12916 * Set to true to disable auto-focusing the overlay or child nodes with
12917 * the `autofocus` attribute` when the overlay is opened.
12918 */
12919 noAutoFocus: {
12920 type: Boolean,
12921 value: false
12922 },
12923
12924 /**
12925 * Set to true to disable canceling the overlay with the ESC key.
12926 */
12927 noCancelOnEscKey: {
12928 type: Boolean,
12929 value: false
12930 },
12931
12932 /**
12933 * Set to true to disable canceling the overlay by clicking outside it.
12934 */
12935 noCancelOnOutsideClick: {
12936 type: Boolean,
12937 value: false
12938 },
12939
12940 /**
12941 * Returns the reason this dialog was last closed.
12942 */
12943 closingReason: {
12944 // was a getter before, but needs to be a property so other
12945 // behaviors can override this.
12946 type: Object
12947 },
12948
12949 _manager: {
12950 type: Object,
12951 value: Polymer.IronOverlayManager
12952 },
12953
12954 _boundOnCaptureClick: {
12955 type: Function,
12956 value: function() {
12957 return this._onCaptureClick.bind(this);
12958 }
12959 },
12960
12961 _boundOnCaptureKeydown: {
12962 type: Function,
12963 value: function() {
12964 return this._onCaptureKeydown.bind(this);
12965 }
12966 }
12967
12968 },
12969
12970 listeners: {
12971 'tap': '_onClick',
12972 'iron-resize': '_onIronResize'
12973 },
12974
12975 /**
12976 * The backdrop element.
12977 * @type Node
12978 */
12979 get backdropElement() {
12980 return this._backdrop;
12981 },
12982
12983 get _focusNode() {
12984 return Polymer.dom(this).querySelector('[autofocus]') || this;
12985 },
12986
12987 registered: function() {
12988 this._backdrop = document.createElement('iron-overlay-backdrop');
12989 },
12990
12991 ready: function() {
12992 this._ensureSetup();
12993 if (this._callOpenedWhenReady) {
12994 this._openedChanged();
12995 }
12996 },
12997
12998 detached: function() {
12999 this.opened = false;
13000 this._completeBackdrop();
13001 this._manager.removeOverlay(this);
13002 },
13003
13004 /**
13005 * Toggle the opened state of the overlay.
13006 */
13007 toggle: function() {
13008 this.opened = !this.opened;
13009 },
13010
13011 /**
13012 * Open the overlay.
13013 */
13014 open: function() {
13015 this.opened = true;
13016 this.closingReason = {canceled: false};
13017 },
13018
13019 /**
13020 * Close the overlay.
13021 */
13022 close: function() {
13023 this.opened = false;
13024 this._setCanceled(false);
13025 },
13026
13027 /**
13028 * Cancels the overlay.
13029 */
13030 cancel: function() {
13031 this.opened = false;
13032 this._setCanceled(true);
13033 },
13034
13035 _ensureSetup: function() {
13036 if (this._overlaySetup) {
13037 return;
13038 }
13039 this._overlaySetup = true;
13040 this.style.outline = 'none';
13041 this.style.display = 'none';
13042 },
13043
13044 _openedChanged: function() {
13045 if (this.opened) {
13046 this.removeAttribute('aria-hidden');
13047 } else {
13048 this.setAttribute('aria-hidden', 'true');
13049 }
13050
13051 // wait to call after ready only if we're initially open
13052 if (!this._overlaySetup) {
13053 this._callOpenedWhenReady = this.opened;
13054 return;
13055 }
13056 if (this._openChangedAsync) {
13057 this.cancelAsync(this._openChangedAsync);
13058 }
13059
13060 this._toggleListeners();
13061
13062 if (this.opened) {
13063 this._prepareRenderOpened();
13064 }
13065
13066 // async here to allow overlay layer to become visible.
13067 this._openChangedAsync = this.async(function() {
13068 // overlay becomes visible here
13069 this.style.display = '';
13070 // force layout to ensure transitions will go
13071 /** @suppress {suspiciousCode} */ this.offsetWidth;
13072 if (this.opened) {
13073 this._renderOpened();
13074 } else {
13075 this._renderClosed();
13076 }
13077 this._openChangedAsync = null;
13078 });
13079
13080 },
13081
13082 _canceledChanged: function() {
13083 this.closingReason = this.closingReason || {};
13084 this.closingReason.canceled = this.canceled;
13085 },
13086
13087 _toggleListener: function(enable, node, event, boundListener, capture) {
13088 if (enable) {
13089 // enable document-wide tap recognizer
13090 if (event === 'tap') {
13091 Polymer.Gestures.add(document, 'tap', null);
13092 }
13093 node.addEventListener(event, boundListener, capture);
13094 } else {
13095 // disable document-wide tap recognizer
13096 if (event === 'tap') {
13097 Polymer.Gestures.remove(document, 'tap', null);
13098 }
13099 node.removeEventListener(event, boundListener, capture);
13100 }
13101 },
13102
13103 _toggleListeners: function() {
13104 if (this._toggleListenersAsync) {
13105 this.cancelAsync(this._toggleListenersAsync);
13106 }
13107 // async so we don't auto-close immediately via a click.
13108 this._toggleListenersAsync = this.async(function() {
13109 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureC lick, true);
13110 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt ureKeydown, true);
13111 this._toggleListenersAsync = null;
13112 }, 1);
13113 },
13114
13115 // tasks which must occur before opening; e.g. making the element visible
13116 _prepareRenderOpened: function() {
13117 this._manager.addOverlay(this);
13118
13119 if (this.withBackdrop) {
13120 this.backdropElement.prepare();
13121 this._manager.trackBackdrop(this);
13122 }
13123
13124 this._preparePositioning();
13125 this.fit();
13126 this._finishPositioning();
13127 },
13128
13129 // tasks which cause the overlay to actually open; typically play an
13130 // animation
13131 _renderOpened: function() {
13132 if (this.withBackdrop) {
13133 this.backdropElement.open();
13134 }
13135 this._finishRenderOpened();
13136 },
13137
13138 _renderClosed: function() {
13139 if (this.withBackdrop) {
13140 this.backdropElement.close();
13141 }
13142 this._finishRenderClosed();
13143 },
13144
13145 _onTransitionend: function(event) {
13146 // make sure this is our transition event.
13147 if (event && event.target !== this) {
13148 return;
13149 }
13150 if (this.opened) {
13151 this._finishRenderOpened();
13152 } else {
13153 this._finishRenderClosed();
13154 }
13155 },
13156
13157 _finishRenderOpened: function() {
13158 // focus the child node with [autofocus]
13159 if (!this.noAutoFocus) {
13160 this._focusNode.focus();
13161 }
13162
13163 this.fire('iron-overlay-opened');
13164
13165 this._squelchNextResize = true;
13166 this.async(this.notifyResize);
13167 },
13168
13169 _finishRenderClosed: function() {
13170 // hide the overlay and remove the backdrop
13171 this.resetFit();
13172 this.style.display = 'none';
13173 this._completeBackdrop();
13174 this._manager.removeOverlay(this);
13175
13176 this._focusNode.blur();
13177 // focus the next overlay, if there is one
13178 this._manager.focusOverlay();
13179
13180 this.fire('iron-overlay-closed', this.closingReason);
13181
13182 this._squelchNextResize = true;
13183 this.async(this.notifyResize);
13184 },
13185
13186 _completeBackdrop: function() {
13187 if (this.withBackdrop) {
13188 this._manager.trackBackdrop(this);
13189 this.backdropElement.complete();
13190 }
13191 },
13192
13193 _preparePositioning: function() {
13194 this.style.transition = this.style.webkitTransition = 'none';
13195 this.style.transform = this.style.webkitTransform = 'none';
13196 this.style.display = '';
13197 },
13198
13199 _finishPositioning: function() {
13200 this.style.display = 'none';
13201 this.style.transform = this.style.webkitTransform = '';
13202 // force layout to avoid application of transform
13203 /** @suppress {suspiciousCode} */ this.offsetWidth;
13204 this.style.transition = this.style.webkitTransition = '';
13205 },
13206
13207 _applyFocus: function() {
13208 if (this.opened) {
13209 if (!this.noAutoFocus) {
13210 this._focusNode.focus();
13211 }
13212 } else {
13213 this._focusNode.blur();
13214 this._manager.focusOverlay();
13215 }
13216 },
13217
13218 _onCaptureClick: function(event) {
13219 // attempt to close asynchronously and prevent the close of a tap event is immediately heard
13220 // on target. This is because in shadow dom due to event retargetting even t.target is not
13221 // useful.
13222 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi s)) {
13223 this._cancelJob = this.async(function() {
13224 this.cancel();
13225 }, 10);
13226 }
13227 },
13228
13229 _onClick: function(event) {
13230 if (this._cancelJob) {
13231 this.cancelAsync(this._cancelJob);
13232 this._cancelJob = null;
13233 }
13234 },
13235
13236 _onCaptureKeydown: function(event) {
13237 var ESC = 27;
13238 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
13239 this.cancel();
13240 event.stopPropagation();
13241 }
13242 },
13243
13244 _onIronResize: function() {
13245 if (this._squelchNextResize) {
13246 this._squelchNextResize = false;
13247 return;
13248 }
13249 if (this.opened) {
13250 this.refit();
13251 }
13252 }
13253
13254 /**
13255 * Fired after the `iron-overlay` opens.
13256 * @event iron-overlay-opened
13257 */
13258
13259 /**
13260 * Fired after the `iron-overlay` closes.
13261 * @event iron-overlay-closed
13262 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
13263 */
13264 };
13265
13266 /** @polymerBehavior */
13267 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl];
13268 /**
13269 * Use `Polymer.NeonAnimationBehavior` to implement an animation.
13270 * @polymerBehavior
13271 */
13272 Polymer.NeonAnimationBehavior = {
13273
13274 properties: {
13275
13276 /**
13277 * Defines the animation timing.
13278 */
13279 animationTiming: {
13280 type: Object,
13281 value: function() {
13282 return {
13283 duration: 500,
13284 easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
13285 fill: 'both'
13286 }
13287 }
13288 }
13289
13290 },
13291
13292 registered: function() {
13293 new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constru ctor});
13294 },
13295
13296 /**
13297 * Do any animation configuration here.
13298 */
13299 // configure: function(config) {
13300 // },
13301
13302 /**
13303 * Returns the animation timing by mixing in properties from `config` to the defaults defined
13304 * by the animation.
13305 */
13306 timingFromConfig: function(config) {
13307 if (config.timing) {
13308 for (var property in config.timing) {
13309 this.animationTiming[property] = config.timing[property];
13310 }
13311 }
13312 return this.animationTiming;
13313 },
13314
13315 /**
13316 * Sets `transform` and `transformOrigin` properties along with the prefixed versions.
13317 */
13318 setPrefixedProperty: function(node, property, value) {
13319 var map = {
13320 'transform': ['webkitTransform'],
13321 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin']
13322 };
13323 var prefixes = map[property];
13324 for (var prefix, index = 0; prefix = prefixes[index]; index++) {
13325 node.style[prefix] = value;
13326 }
13327 node.style[property] = value;
13328 },
13329
13330 /**
13331 * Called when the animation finishes.
13332 */
13333 complete: function() {}
13334
13335 };
13336 Polymer({
13337
13338 is: 'opaque-animation',
13339
13340 behaviors: [
13341 Polymer.NeonAnimationBehavior
13342 ],
13343
13344 configure: function(config) {
13345 var node = config.node;
13346 node.style.opacity = '0';
13347 this._effect = new KeyframeEffect(node, [
13348 {'opacity': '1'},
13349 {'opacity': '1'}
13350 ], this.timingFromConfig(config));
13351 return this._effect;
13352 },
13353
13354 complete: function(config) {
13355 config.node.style.opacity = '';
13356 }
13357
13358 });
13359 /**
13360 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim ations for use with
13361 * elements implementing `Polymer.NeonAnimationRunnerBehavior`.
13362 * @polymerBehavior
13363 */
13364 Polymer.NeonAnimatableBehavior = {
13365
13366 properties: {
13367
13368 /**
13369 * Animation configuration. See README for more info.
13370 */
13371 animationConfig: {
13372 type: Object
13373 },
13374
13375 /**
13376 * Convenience property for setting an 'entry' animation. Do not set `anim ationConfig.entry`
13377 * manually if using this. The animated node is set to `this` if using thi s property.
13378 */
13379 entryAnimation: {
13380 observer: '_entryAnimationChanged',
13381 type: String
13382 },
13383
13384 /**
13385 * Convenience property for setting an 'exit' animation. Do not set `anima tionConfig.exit`
13386 * manually if using this. The animated node is set to `this` if using thi s property.
13387 */
13388 exitAnimation: {
13389 observer: '_exitAnimationChanged',
13390 type: String
13391 }
13392
13393 },
13394
13395 _entryAnimationChanged: function() {
13396 this.animationConfig = this.animationConfig || {};
13397 if (this.entryAnimation !== 'fade-in-animation') {
13398 // insert polyfill hack
13399 this.animationConfig['entry'] = [{
13400 name: 'opaque-animation',
13401 node: this
13402 }, {
13403 name: this.entryAnimation,
13404 node: this
13405 }];
13406 } else {
13407 this.animationConfig['entry'] = [{
13408 name: this.entryAnimation,
13409 node: this
13410 }];
13411 }
13412 },
13413
13414 _exitAnimationChanged: function() {
13415 this.animationConfig = this.animationConfig || {};
13416 this.animationConfig['exit'] = [{
13417 name: this.exitAnimation,
13418 node: this
13419 }];
13420 },
13421
13422 _copyProperties: function(config1, config2) {
13423 // shallowly copy properties from config2 to config1
13424 for (var property in config2) {
13425 config1[property] = config2[property];
13426 }
13427 },
13428
13429 _cloneConfig: function(config) {
13430 var clone = {
13431 isClone: true
13432 };
13433 this._copyProperties(clone, config);
13434 return clone;
13435 },
13436
13437 _getAnimationConfigRecursive: function(type, map, allConfigs) {
13438 if (!this.animationConfig) {
13439 return;
13440 }
13441
13442 // type is optional
13443 var thisConfig;
13444 if (type) {
13445 thisConfig = this.animationConfig[type];
13446 } else {
13447 thisConfig = this.animationConfig;
13448 }
13449
13450 if (!Array.isArray(thisConfig)) {
13451 thisConfig = [thisConfig];
13452 }
13453
13454 // iterate animations and recurse to process configurations from child nod es
13455 if (thisConfig) {
13456 for (var config, index = 0; config = thisConfig[index]; index++) {
13457 if (config.animatable) {
13458 config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs);
13459 } else {
13460 if (config.id) {
13461 var cachedConfig = map[config.id];
13462 if (cachedConfig) {
13463 // merge configurations with the same id, making a clone lazily
13464 if (!cachedConfig.isClone) {
13465 map[config.id] = this._cloneConfig(cachedConfig)
13466 cachedConfig = map[config.id];
13467 }
13468 this._copyProperties(cachedConfig, config);
13469 } else {
13470 // put any configs with an id into a map
13471 map[config.id] = config;
13472 }
13473 } else {
13474 allConfigs.push(config);
13475 }
13476 }
13477 }
13478 }
13479 },
13480
13481 /**
13482 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure
13483 * an animation with an optional type. Elements implementing `Polymer.NeonAn imatableBehavior`
13484 * should define the property `animationConfig`, which is either a configura tion object
13485 * or a map of animation type to array of configuration objects.
13486 */
13487 getAnimationConfig: function(type) {
13488 var map = [];
13489 var allConfigs = [];
13490 this._getAnimationConfigRecursive(type, map, allConfigs);
13491 // append the configurations saved in the map to the array
13492 for (var key in map) {
13493 allConfigs.push(map[key]);
13494 }
13495 return allConfigs;
13496 }
13497
13498 };
13499 /**
13500 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations.
13501 *
13502 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior
13503 */
13504 Polymer.NeonAnimationRunnerBehaviorImpl = {
13505
13506 properties: {
13507
13508 _animationMeta: {
13509 type: Object,
13510 value: function() {
13511 return new Polymer.IronMeta({type: 'animation'});
13512 }
13513 },
13514
13515 /** @type {?Object} */
13516 _player: {
13517 type: Object
13518 }
13519
13520 },
13521
13522 _configureAnimationEffects: function(allConfigs) {
13523 var allAnimations = [];
13524 if (allConfigs.length > 0) {
13525 for (var config, index = 0; config = allConfigs[index]; index++) {
13526 var animationConstructor = this._animationMeta.byKey(config.name);
13527 if (animationConstructor) {
13528 var animation = animationConstructor && new animationConstructor();
13529 var effect = animation.configure(config);
13530 if (effect) {
13531 allAnimations.push({
13532 animation: animation,
13533 config: config,
13534 effect: effect
13535 });
13536 }
13537 } else {
13538 console.warn(this.is + ':', config.name, 'not found!');
13539 }
13540 }
13541 }
13542 return allAnimations;
13543 },
13544
13545 _runAnimationEffects: function(allEffects) {
13546 return document.timeline.play(new GroupEffect(allEffects));
13547 },
13548
13549 _completeAnimations: function(allAnimations) {
13550 for (var animation, index = 0; animation = allAnimations[index]; index++) {
13551 animation.animation.complete(animation.config);
13552 }
13553 },
13554
13555 /**
13556 * Plays an animation with an optional `type`.
13557 * @param {string=} type
13558 * @param {!Object=} cookie
13559 */
13560 playAnimation: function(type, cookie) {
13561 var allConfigs = this.getAnimationConfig(type);
13562 if (!allConfigs) {
13563 return;
13564 }
13565 var allAnimations = this._configureAnimationEffects(allConfigs);
13566 var allEffects = allAnimations.map(function(animation) {
13567 return animation.effect;
13568 });
13569
13570 if (allEffects.length > 0) {
13571 this._player = this._runAnimationEffects(allEffects);
13572 this._player.onfinish = function() {
13573 this._completeAnimations(allAnimations);
13574
13575 if (this._player) {
13576 this._player.cancel();
13577 this._player = null;
13578 }
13579
13580 this.fire('neon-animation-finish', cookie, {bubbles: false});
13581 }.bind(this);
13582
13583 } else {
13584 this.fire('neon-animation-finish', cookie, {bubbles: false});
13585 }
13586 },
13587
13588 /**
13589 * Cancels the currently running animation.
13590 */
13591 cancelAnimation: function() {
13592 if (this._player) {
13593 this._player.cancel();
13594 }
13595 }
13596 };
13597
13598 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */
13599 Polymer.NeonAnimationRunnerBehavior = [
13600 Polymer.NeonAnimatableBehavior,
13601 Polymer.NeonAnimationRunnerBehaviorImpl
13602 ];
13603 (function() {
13604 'use strict';
13605
13606 /**
13607 * The IronDropdownScrollManager is intended to provide a central source
13608 * of authority and control over which elements in a document are currently
13609 * allowed to scroll.
13610 */
13611
13612 Polymer.IronDropdownScrollManager = {
13613
13614 /**
13615 * The current element that defines the DOM boundaries of the
13616 * scroll lock. This is always the most recently locking element.
13617 */
13618 get currentLockingElement() {
13619 return this._lockingElements[this._lockingElements.length - 1];
13620 },
13621
13622
13623 /**
13624 * Returns true if the provided element is "scroll locked," which is to
13625 * say that it cannot be scrolled via pointer or keyboard interactions.
13626 *
13627 * @param {HTMLElement} element An HTML element instance which may or may
13628 * not be scroll locked.
13629 */
13630 elementIsScrollLocked: function(element) {
13631 var currentLockingElement = this.currentLockingElement;
13632 var scrollLocked;
13633
13634 if (this._hasCachedLockedElement(element)) {
13635 return true;
13636 }
13637
13638 if (this._hasCachedUnlockedElement(element)) {
13639 return false;
13640 }
13641
13642 scrollLocked = !!currentLockingElement &&
13643 currentLockingElement !== element &&
13644 !this._composedTreeContains(currentLockingElement, element);
13645
13646 if (scrollLocked) {
13647 this._lockedElementCache.push(element);
13648 } else {
13649 this._unlockedElementCache.push(element);
13650 }
13651
13652 return scrollLocked;
13653 },
13654
13655 /**
13656 * Push an element onto the current scroll lock stack. The most recently
13657 * pushed element and its children will be considered scrollable. All
13658 * other elements will not be scrollable.
13659 *
13660 * Scroll locking is implemented as a stack so that cases such as
13661 * dropdowns within dropdowns are handled well.
13662 *
13663 * @param {HTMLElement} element The element that should lock scroll.
13664 */
13665 pushScrollLock: function(element) {
13666 if (this._lockingElements.length === 0) {
13667 this._lockScrollInteractions();
13668 }
13669
13670 this._lockingElements.push(element);
13671
13672 this._lockedElementCache = [];
13673 this._unlockedElementCache = [];
13674 },
13675
13676 /**
13677 * Remove an element from the scroll lock stack. The element being
13678 * removed does not need to be the most recently pushed element. However,
13679 * the scroll lock constraints only change when the most recently pushed
13680 * element is removed.
13681 *
13682 * @param {HTMLElement} element The element to remove from the scroll
13683 * lock stack.
13684 */
13685 removeScrollLock: function(element) {
13686 var index = this._lockingElements.indexOf(element);
13687
13688 if (index === -1) {
13689 return;
13690 }
13691
13692 this._lockingElements.splice(index, 1);
13693
13694 this._lockedElementCache = [];
13695 this._unlockedElementCache = [];
13696
13697 if (this._lockingElements.length === 0) {
13698 this._unlockScrollInteractions();
13699 }
13700 },
13701
13702 _lockingElements: [],
13703
13704 _lockedElementCache: null,
13705
13706 _unlockedElementCache: null,
13707
13708 _originalBodyStyles: {},
13709
13710 _isScrollingKeypress: function(event) {
13711 return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(
13712 event, 'pageup pagedown home end up left down right');
13713 },
13714
13715 _hasCachedLockedElement: function(element) {
13716 return this._lockedElementCache.indexOf(element) > -1;
13717 },
13718
13719 _hasCachedUnlockedElement: function(element) {
13720 return this._unlockedElementCache.indexOf(element) > -1;
13721 },
13722
13723 _composedTreeContains: function(element, child) {
13724 // NOTE(cdata): This method iterates over content elements and their
13725 // corresponding distributed nodes to implement a contains-like method
13726 // that pierces through the composed tree of the ShadowDOM. Results of
13727 // this operation are cached (elsewhere) on a per-scroll-lock basis, to
13728 // guard against potentially expensive lookups happening repeatedly as
13729 // a user scrolls / touchmoves.
13730 var contentElements;
13731 var distributedNodes;
13732 var contentIndex;
13733 var nodeIndex;
13734
13735 if (element.contains(child)) {
13736 return true;
13737 }
13738
13739 contentElements = Polymer.dom(element).querySelectorAll('content');
13740
13741 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI ndex) {
13742
13743 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr ibutedNodes();
13744
13745 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
13746
13747 if (this._composedTreeContains(distributedNodes[nodeIndex], child)) {
13748 return true;
13749 }
13750 }
13751 }
13752
13753 return false;
13754 },
13755
13756 _scrollInteractionHandler: function(event) {
13757 if (Polymer
13758 .IronDropdownScrollManager
13759 .elementIsScrollLocked(event.target)) {
13760 if (event.type === 'keydown' &&
13761 !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) {
13762 return;
13763 }
13764
13765 event.preventDefault();
13766 }
13767 },
13768
13769 _lockScrollInteractions: function() {
13770 // Memoize body inline styles:
13771 this._originalBodyStyles.overflow = document.body.style.overflow;
13772 this._originalBodyStyles.overflowX = document.body.style.overflowX;
13773 this._originalBodyStyles.overflowY = document.body.style.overflowY;
13774
13775 // Disable overflow scrolling on body:
13776 // TODO(cdata): It is technically not sufficient to hide overflow on
13777 // body alone. A better solution might be to traverse all ancestors of
13778 // the current scroll locking element and hide overflow on them. This
13779 // becomes expensive, though, as it would have to be redone every time
13780 // a new scroll locking element is added.
13781 document.body.style.overflow = 'hidden';
13782 document.body.style.overflowX = 'hidden';
13783 document.body.style.overflowY = 'hidden';
13784
13785 // Modern `wheel` event for mouse wheel scrolling:
13786 window.addEventListener('wheel', this._scrollInteractionHandler, true);
13787 // Older, non-standard `mousewheel` event for some FF:
13788 window.addEventListener('mousewheel', this._scrollInteractionHandler, tr ue);
13789 // IE:
13790 window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler , true);
13791 // Mobile devices can scroll on touch move:
13792 window.addEventListener('touchmove', this._scrollInteractionHandler, tru e);
13793 // Capture keydown to prevent scrolling keys (pageup, pagedown etc.)
13794 document.addEventListener('keydown', this._scrollInteractionHandler, tru e);
13795 },
13796
13797 _unlockScrollInteractions: function() {
13798 document.body.style.overflow = this._originalBodyStyles.overflow;
13799 document.body.style.overflowX = this._originalBodyStyles.overflowX;
13800 document.body.style.overflowY = this._originalBodyStyles.overflowY;
13801
13802 window.removeEventListener('wheel', this._scrollInteractionHandler, true );
13803 window.removeEventListener('mousewheel', this._scrollInteractionHandler, true);
13804 window.removeEventListener('DOMMouseScroll', this._scrollInteractionHand ler, true);
13805 window.removeEventListener('touchmove', this._scrollInteractionHandler, true);
13806 document.removeEventListener('keydown', this._scrollInteractionHandler, true);
13807 }
13808 };
13809 })();
13810 (function() {
13811 'use strict';
13812
13813 Polymer({
13814 is: 'iron-dropdown',
13815
13816 behaviors: [
13817 Polymer.IronControlState,
13818 Polymer.IronA11yKeysBehavior,
13819 Polymer.IronOverlayBehavior,
13820 Polymer.NeonAnimationRunnerBehavior
13821 ],
13822
13823 properties: {
13824 /**
13825 * The orientation against which to align the dropdown content
13826 * horizontally relative to the dropdown trigger.
13827 */
13828 horizontalAlign: {
13829 type: String,
13830 value: 'left',
13831 reflectToAttribute: true
13832 },
13833
13834 /**
13835 * The orientation against which to align the dropdown content
13836 * vertically relative to the dropdown trigger.
13837 */
13838 verticalAlign: {
13839 type: String,
13840 value: 'top',
13841 reflectToAttribute: true
13842 },
13843
13844 /**
13845 * A pixel value that will be added to the position calculated for the
13846 * given `horizontalAlign`. Use a negative value to offset to the
13847 * left, or a positive value to offset to the right.
13848 */
13849 horizontalOffset: {
13850 type: Number,
13851 value: 0,
13852 notify: true
13853 },
13854
13855 /**
13856 * A pixel value that will be added to the position calculated for the
13857 * given `verticalAlign`. Use a negative value to offset towards the
13858 * top, or a positive value to offset towards the bottom.
13859 */
13860 verticalOffset: {
13861 type: Number,
13862 value: 0,
13863 notify: true
13864 },
13865
13866 /**
13867 * The element that should be used to position the dropdown when
13868 * it is opened.
13869 */
13870 positionTarget: {
13871 type: Object,
13872 observer: '_positionTargetChanged'
13873 },
13874
13875 /**
13876 * An animation config. If provided, this will be used to animate the
13877 * opening of the dropdown.
13878 */
13879 openAnimationConfig: {
13880 type: Object
13881 },
13882
13883 /**
13884 * An animation config. If provided, this will be used to animate the
13885 * closing of the dropdown.
13886 */
13887 closeAnimationConfig: {
13888 type: Object
13889 },
13890
13891 /**
13892 * If provided, this will be the element that will be focused when
13893 * the dropdown opens.
13894 */
13895 focusTarget: {
13896 type: Object
13897 },
13898
13899 /**
13900 * Set to true to disable animations when opening and closing the
13901 * dropdown.
13902 */
13903 noAnimations: {
13904 type: Boolean,
13905 value: false
13906 },
13907
13908 /**
13909 * By default, the dropdown will constrain scrolling on the page
13910 * to itself when opened.
13911 * Set to true in order to prevent scroll from being constrained
13912 * to the dropdown when it opens.
13913 */
13914 allowOutsideScroll: {
13915 type: Boolean,
13916 value: false
13917 },
13918
13919 /**
13920 * We memoize the positionTarget bounding rectangle so that we can
13921 * limit the number of times it is queried per resize / relayout.
13922 * @type {?Object}
13923 */
13924 _positionRectMemo: {
13925 type: Object
13926 }
13927 },
13928
13929 listeners: {
13930 'neon-animation-finish': '_onNeonAnimationFinish'
13931 },
13932
13933 observers: [
13934 '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset , horizontalOffset)'
13935 ],
13936
13937 attached: function() {
13938 if (this.positionTarget === undefined) {
13939 this.positionTarget = this._defaultPositionTarget;
13940 }
13941 },
13942
13943 /**
13944 * The element that is contained by the dropdown, if any.
13945 */
13946 get containedElement() {
13947 return Polymer.dom(this.$.content).getDistributedNodes()[0];
13948 },
13949
13950 /**
13951 * The element that should be focused when the dropdown opens.
13952 */
13953 get _focusTarget() {
13954 return this.focusTarget || this.containedElement;
13955 },
13956
13957 /**
13958 * The element that should be used to position the dropdown when
13959 * it opens, if no position target is configured.
13960 */
13961 get _defaultPositionTarget() {
13962 var parent = Polymer.dom(this).parentNode;
13963
13964 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
13965 parent = parent.host;
13966 }
13967
13968 return parent;
13969 },
13970
13971 /**
13972 * The bounding rect of the position target.
13973 */
13974 get _positionRect() {
13975 if (!this._positionRectMemo && this.positionTarget) {
13976 this._positionRectMemo = this.positionTarget.getBoundingClientRect() ;
13977 }
13978
13979 return this._positionRectMemo;
13980 },
13981
13982 /**
13983 * The horizontal offset value used to position the dropdown.
13984 */
13985 get _horizontalAlignTargetValue() {
13986 var target;
13987
13988 if (this.horizontalAlign === 'right') {
13989 target = document.documentElement.clientWidth - this._positionRect.r ight;
13990 } else {
13991 target = this._positionRect.left;
13992 }
13993
13994 target += this.horizontalOffset;
13995
13996 return Math.max(target, 0);
13997 },
13998
13999 /**
14000 * The vertical offset value used to position the dropdown.
14001 */
14002 get _verticalAlignTargetValue() {
14003 var target;
14004
14005 if (this.verticalAlign === 'bottom') {
14006 target = document.documentElement.clientHeight - this._positionRect. bottom;
14007 } else {
14008 target = this._positionRect.top;
14009 }
14010
14011 target += this.verticalOffset;
14012
14013 return Math.max(target, 0);
14014 },
14015
14016 /**
14017 * Called when the value of `opened` changes.
14018 *
14019 * @param {boolean} opened True if the dropdown is opened.
14020 */
14021 _openedChanged: function(opened) {
14022 if (opened && this.disabled) {
14023 this.cancel();
14024 } else {
14025 this.cancelAnimation();
14026 this._prepareDropdown();
14027 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments );
14028 }
14029
14030 if (this.opened) {
14031 this._focusContent();
14032 }
14033 },
14034
14035 /**
14036 * Overridden from `IronOverlayBehavior`.
14037 */
14038 _renderOpened: function() {
14039 if (!this.allowOutsideScroll) {
14040 Polymer.IronDropdownScrollManager.pushScrollLock(this);
14041 }
14042
14043 if (!this.noAnimations && this.animationConfig && this.animationConfig .open) {
14044 this.$.contentWrapper.classList.add('animating');
14045 this.playAnimation('open');
14046 } else {
14047 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments) ;
14048 }
14049 },
14050
14051 /**
14052 * Overridden from `IronOverlayBehavior`.
14053 */
14054 _renderClosed: function() {
14055 Polymer.IronDropdownScrollManager.removeScrollLock(this);
14056 if (!this.noAnimations && this.animationConfig && this.animationConfig .close) {
14057 this.$.contentWrapper.classList.add('animating');
14058 this.playAnimation('close');
14059 } else {
14060 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments) ;
14061 }
14062 },
14063
14064 /**
14065 * Called when animation finishes on the dropdown (when opening or
14066 * closing). Responsible for "completing" the process of opening or
14067 * closing the dropdown by positioning it or setting its display to
14068 * none.
14069 */
14070 _onNeonAnimationFinish: function() {
14071 this.$.contentWrapper.classList.remove('animating');
14072 if (this.opened) {
14073 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this);
14074 } else {
14075 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this);
14076 }
14077 },
14078
14079 /**
14080 * Called when an `iron-resize` event fires.
14081 */
14082 _onIronResize: function() {
14083 var containedElement = this.containedElement;
14084 var scrollTop;
14085 var scrollLeft;
14086
14087 if (containedElement) {
14088 scrollTop = containedElement.scrollTop;
14089 scrollLeft = containedElement.scrollLeft;
14090 }
14091
14092 if (this.opened) {
14093 this._updateOverlayPosition();
14094 }
14095
14096 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments);
14097
14098 if (containedElement) {
14099 containedElement.scrollTop = scrollTop;
14100 containedElement.scrollLeft = scrollLeft;
14101 }
14102 },
14103
14104 /**
14105 * Called when the `positionTarget` property changes.
14106 */
14107 _positionTargetChanged: function() {
14108 this._updateOverlayPosition();
14109 },
14110
14111 /**
14112 * Constructs the final animation config from different properties used
14113 * to configure specific parts of the opening and closing animations.
14114 */
14115 _updateAnimationConfig: function() {
14116 var animationConfig = {};
14117 var animations = [];
14118
14119 if (this.openAnimationConfig) {
14120 // NOTE(cdata): When making `display:none` elements visible in Safar i,
14121 // the element will paint once in a fully visible state, causing the
14122 // dropdown to flash before it fades in. We prepend an
14123 // `opaque-animation` to fix this problem:
14124 animationConfig.open = [{
14125 name: 'opaque-animation',
14126 }].concat(this.openAnimationConfig);
14127 animations = animations.concat(animationConfig.open);
14128 }
14129
14130 if (this.closeAnimationConfig) {
14131 animationConfig.close = this.closeAnimationConfig;
14132 animations = animations.concat(animationConfig.close);
14133 }
14134
14135 animations.forEach(function(animation) {
14136 animation.node = this.containedElement;
14137 }, this);
14138
14139 this.animationConfig = animationConfig;
14140 },
14141
14142 /**
14143 * Prepares the dropdown for opening by updating measured layout
14144 * values.
14145 */
14146 _prepareDropdown: function() {
14147 this.sizingTarget = this.containedElement || this.sizingTarget;
14148 this._updateAnimationConfig();
14149 this._updateOverlayPosition();
14150 },
14151
14152 /**
14153 * Updates the overlay position based on configured horizontal
14154 * and vertical alignment, and re-memoizes these values for the sake
14155 * of behavior in `IronFitBehavior`.
14156 */
14157 _updateOverlayPosition: function() {
14158 this._positionRectMemo = null;
14159
14160 if (!this.positionTarget) {
14161 return;
14162 }
14163
14164 this.style[this.horizontalAlign] =
14165 this._horizontalAlignTargetValue + 'px';
14166
14167 this.style[this.verticalAlign] =
14168 this._verticalAlignTargetValue + 'px';
14169
14170 // NOTE(cdata): We re-memoize inline styles here, otherwise
14171 // calling `refit` from `IronFitBehavior` will reset inline styles
14172 // to whatever they were when the dropdown first opened.
14173 if (this._fitInfo) {
14174 this._fitInfo.inlineStyle[this.horizontalAlign] =
14175 this.style[this.horizontalAlign];
14176
14177 this._fitInfo.inlineStyle[this.verticalAlign] =
14178 this.style[this.verticalAlign];
14179 }
14180 },
14181
14182 /**
14183 * Focuses the configured focus target.
14184 */
14185 _focusContent: function() {
14186 // NOTE(cdata): This is async so that it can attempt the focus after
14187 // `display: none` is removed from the element.
14188 this.async(function() {
14189 if (this._focusTarget) {
14190 this._focusTarget.focus();
14191 }
14192 });
14193 }
14194 });
14195 })();
14196 Polymer({
14197
14198 is: 'fade-in-animation',
14199
14200 behaviors: [
14201 Polymer.NeonAnimationBehavior
14202 ],
14203
14204 configure: function(config) {
14205 var node = config.node;
14206 this._effect = new KeyframeEffect(node, [
14207 {'opacity': '0'},
14208 {'opacity': '1'}
14209 ], this.timingFromConfig(config));
14210 return this._effect;
14211 }
14212
14213 });
14214 Polymer({
14215
14216 is: 'fade-out-animation',
14217
14218 behaviors: [
14219 Polymer.NeonAnimationBehavior
14220 ],
14221
14222 configure: function(config) {
14223 var node = config.node;
14224 this._effect = new KeyframeEffect(node, [
14225 {'opacity': '1'},
14226 {'opacity': '0'}
14227 ], this.timingFromConfig(config));
14228 return this._effect;
14229 }
14230
14231 });
14232 Polymer({
14233 is: 'paper-menu-grow-height-animation',
14234
14235 behaviors: [
14236 Polymer.NeonAnimationBehavior
14237 ],
14238
14239 configure: function(config) {
14240 var node = config.node;
14241 var rect = node.getBoundingClientRect();
14242 var height = rect.height;
14243
14244 this._effect = new KeyframeEffect(node, [{
14245 height: (height / 2) + 'px'
14246 }, {
14247 height: height + 'px'
14248 }], this.timingFromConfig(config));
14249
14250 return this._effect;
14251 }
14252 });
14253
14254 Polymer({
14255 is: 'paper-menu-grow-width-animation',
14256
14257 behaviors: [
14258 Polymer.NeonAnimationBehavior
14259 ],
14260
14261 configure: function(config) {
14262 var node = config.node;
14263 var rect = node.getBoundingClientRect();
14264 var width = rect.width;
14265
14266 this._effect = new KeyframeEffect(node, [{
14267 width: (width / 2) + 'px'
14268 }, {
14269 width: width + 'px'
14270 }], this.timingFromConfig(config));
14271
14272 return this._effect;
14273 }
14274 });
14275
14276 Polymer({
14277 is: 'paper-menu-shrink-width-animation',
14278
14279 behaviors: [
14280 Polymer.NeonAnimationBehavior
14281 ],
14282
14283 configure: function(config) {
14284 var node = config.node;
14285 var rect = node.getBoundingClientRect();
14286 var width = rect.width;
14287
14288 this._effect = new KeyframeEffect(node, [{
14289 width: width + 'px'
14290 }, {
14291 width: width - (width / 20) + 'px'
14292 }], this.timingFromConfig(config));
14293
14294 return this._effect;
14295 }
14296 });
14297
14298 Polymer({
14299 is: 'paper-menu-shrink-height-animation',
14300
14301 behaviors: [
14302 Polymer.NeonAnimationBehavior
14303 ],
14304
14305 configure: function(config) {
14306 var node = config.node;
14307 var rect = node.getBoundingClientRect();
14308 var height = rect.height;
14309 var top = rect.top;
14310
14311 this.setPrefixedProperty(node, 'transformOrigin', '0 0');
14312
14313 this._effect = new KeyframeEffect(node, [{
14314 height: height + 'px',
14315 transform: 'translateY(0)'
14316 }, {
14317 height: height / 2 + 'px',
14318 transform: 'translateY(-20px)'
14319 }], this.timingFromConfig(config));
14320
14321 return this._effect;
14322 }
14323 });
14324 (function() {
14325 'use strict';
14326
14327 var PaperMenuButton = Polymer({
14328 is: 'paper-menu-button',
14329
14330 /**
14331 * Fired when the dropdown opens.
14332 *
14333 * @event paper-dropdown-open
14334 */
14335
14336 /**
14337 * Fired when the dropdown closes.
14338 *
14339 * @event paper-dropdown-close
14340 */
14341
14342 behaviors: [
14343 Polymer.IronA11yKeysBehavior,
14344 Polymer.IronControlState
14345 ],
14346
14347 properties: {
14348
14349 /**
14350 * True if the content is currently displayed.
14351 */
14352 opened: {
14353 type: Boolean,
14354 value: false,
14355 notify: true,
14356 observer: '_openedChanged'
14357 },
14358
14359 /**
14360 * The orientation against which to align the menu dropdown
14361 * horizontally relative to the dropdown trigger.
14362 */
14363 horizontalAlign: {
14364 type: String,
14365 value: 'left',
14366 reflectToAttribute: true
14367 },
14368
14369 /**
14370 * The orientation against which to align the menu dropdown
14371 * vertically relative to the dropdown trigger.
14372 */
14373 verticalAlign: {
14374 type: String,
14375 value: 'top',
14376 reflectToAttribute: true
14377 },
14378
14379 /**
14380 * A pixel value that will be added to the position calculated for the
14381 * given `horizontalAlign`. Use a negative value to offset to the
14382 * left, or a positive value to offset to the right.
14383 */
14384 horizontalOffset: {
14385 type: Number,
14386 value: 0,
14387 notify: true
14388 },
14389
14390 /**
14391 * A pixel value that will be added to the position calculated for the
14392 * given `verticalAlign`. Use a negative value to offset towards the
14393 * top, or a positive value to offset towards the bottom.
14394 */
14395 verticalOffset: {
14396 type: Number,
14397 value: 0,
14398 notify: true
14399 },
14400
14401 /**
14402 * Set to true to disable animations when opening and closing the
14403 * dropdown.
14404 */
14405 noAnimations: {
14406 type: Boolean,
14407 value: false
14408 },
14409
14410 /**
14411 * Set to true to disable automatically closing the dropdown after
14412 * a selection has been made.
14413 */
14414 ignoreSelect: {
14415 type: Boolean,
14416 value: false
14417 },
14418
14419 /**
14420 * An animation config. If provided, this will be used to animate the
14421 * opening of the dropdown.
14422 */
14423 openAnimationConfig: {
14424 type: Object,
14425 value: function() {
14426 return [{
14427 name: 'fade-in-animation',
14428 timing: {
14429 delay: 100,
14430 duration: 200
14431 }
14432 }, {
14433 name: 'paper-menu-grow-width-animation',
14434 timing: {
14435 delay: 100,
14436 duration: 150,
14437 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
14438 }
14439 }, {
14440 name: 'paper-menu-grow-height-animation',
14441 timing: {
14442 delay: 100,
14443 duration: 275,
14444 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
14445 }
14446 }];
14447 }
14448 },
14449
14450 /**
14451 * An animation config. If provided, this will be used to animate the
14452 * closing of the dropdown.
14453 */
14454 closeAnimationConfig: {
14455 type: Object,
14456 value: function() {
14457 return [{
14458 name: 'fade-out-animation',
14459 timing: {
14460 duration: 150
14461 }
14462 }, {
14463 name: 'paper-menu-shrink-width-animation',
14464 timing: {
14465 delay: 100,
14466 duration: 50,
14467 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
14468 }
14469 }, {
14470 name: 'paper-menu-shrink-height-animation',
14471 timing: {
14472 duration: 200,
14473 easing: 'ease-in'
14474 }
14475 }];
14476 }
14477 },
14478
14479 /**
14480 * This is the element intended to be bound as the focus target
14481 * for the `iron-dropdown` contained by `paper-menu-button`.
14482 */
14483 _dropdownContent: {
14484 type: Object
14485 }
14486 },
14487
14488 hostAttributes: {
14489 role: 'group',
14490 'aria-haspopup': 'true'
14491 },
14492
14493 listeners: {
14494 'iron-select': '_onIronSelect'
14495 },
14496
14497 /**
14498 * The content element that is contained by the menu button, if any.
14499 */
14500 get contentElement() {
14501 return Polymer.dom(this.$.content).getDistributedNodes()[0];
14502 },
14503
14504 /**
14505 * Make the dropdown content appear as an overlay positioned relative
14506 * to the dropdown trigger.
14507 */
14508 open: function() {
14509 if (this.disabled) {
14510 return;
14511 }
14512
14513 this.$.dropdown.open();
14514 },
14515
14516 /**
14517 * Hide the dropdown content.
14518 */
14519 close: function() {
14520 this.$.dropdown.close();
14521 },
14522
14523 /**
14524 * When an `iron-select` event is received, the dropdown should
14525 * automatically close on the assumption that a value has been chosen.
14526 *
14527 * @param {CustomEvent} event A CustomEvent instance with type
14528 * set to `"iron-select"`.
14529 */
14530 _onIronSelect: function(event) {
14531 if (!this.ignoreSelect) {
14532 this.close();
14533 }
14534 },
14535
14536 /**
14537 * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
14538 * When the dropdown closes, the `paper-menu-button` fires `paper-close`.
14539 *
14540 * @param {boolean} opened True if the dropdown is opened, otherwise false .
14541 * @param {boolean} oldOpened The previous value of `opened`.
14542 */
14543 _openedChanged: function(opened, oldOpened) {
14544 if (opened) {
14545 // TODO(cdata): Update this when we can measure changes in distributed
14546 // children in an idiomatic way.
14547 // We poke this property in case the element has changed. This will
14548 // cause the focus target for the `iron-dropdown` to be updated as
14549 // necessary:
14550 this._dropdownContent = this.contentElement;
14551 this.fire('paper-dropdown-open');
14552 } else if (oldOpened != null) {
14553 this.fire('paper-dropdown-close');
14554 }
14555 },
14556
14557 /**
14558 * If the dropdown is open when disabled becomes true, close the
14559 * dropdown.
14560 *
14561 * @param {boolean} disabled True if disabled, otherwise false.
14562 */
14563 _disabledChanged: function(disabled) {
14564 Polymer.IronControlState._disabledChanged.apply(this, arguments);
14565 if (disabled && this.opened) {
14566 this.close();
14567 }
14568 }
14569 });
14570
14571 PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)';
14572 PaperMenuButton.MAX_ANIMATION_TIME_MS = 400;
14573
14574 Polymer.PaperMenuButton = PaperMenuButton;
14575 })();
14576 /**
14577 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k eyboard focus.
14578 *
14579 * @polymerBehavior Polymer.PaperInkyFocusBehavior
14580 */
14581 Polymer.PaperInkyFocusBehaviorImpl = {
14582
14583 observers: [
14584 '_focusedChanged(receivedFocusFromKeyboard)'
14585 ],
14586
14587 _focusedChanged: function(receivedFocusFromKeyboard) {
14588 if (!this.$.ink) {
14589 return;
14590 }
14591
14592 this.$.ink.holdDown = receivedFocusFromKeyboard;
14593 }
14594
14595 };
14596
14597 /** @polymerBehavior Polymer.PaperInkyFocusBehavior */
14598 Polymer.PaperInkyFocusBehavior = [
14599 Polymer.IronButtonState,
14600 Polymer.IronControlState,
14601 Polymer.PaperInkyFocusBehaviorImpl
14602 ];
14603 Polymer({
14604 is: 'paper-icon-button',
14605
14606 hostAttributes: {
14607 role: 'button',
14608 tabindex: '0'
14609 },
14610
14611 behaviors: [
14612 Polymer.PaperInkyFocusBehavior
14613 ],
14614
14615 properties: {
14616 /**
14617 * The URL of an image for the icon. If the src property is specified,
14618 * the icon property should not be.
14619 */
14620 src: {
14621 type: String
14622 },
14623
14624 /**
14625 * Specifies the icon name or index in the set of icons available in
14626 * the icon's icon set. If the icon property is specified,
14627 * the src property should not be.
14628 */
14629 icon: {
14630 type: String
14631 },
14632
14633 /**
14634 * Specifies the alternate text for the button, for accessibility.
14635 */
14636 alt: {
14637 type: String,
14638 observer: "_altChanged"
14639 }
14640 },
14641
14642 _altChanged: function(newValue, oldValue) {
14643 var label = this.getAttribute('aria-label');
14644
14645 // Don't stomp over a user-set aria-label.
14646 if (!label || oldValue == label) {
14647 this.setAttribute('aria-label', newValue);
14648 }
14649 }
14650 });
14651 /**
14652 * Use `Polymer.IronValidatableBehavior` to implement an element that validate s user input.
14653 *
14654 * ### Accessibility
14655 *
14656 * Changing the `invalid` property, either manually or by calling `validate()` will update the
14657 * `aria-invalid` attribute.
14658 *
14659 * @demo demo/index.html
14660 * @polymerBehavior
14661 */
14662 Polymer.IronValidatableBehavior = {
14663
14664 properties: {
14665
14666 /**
14667 * Namespace for this validator.
14668 */
14669 validatorType: {
14670 type: String,
14671 value: 'validator'
14672 },
14673
14674 /**
14675 * Name of the validator to use.
14676 */
14677 validator: {
14678 type: String
14679 },
14680
14681 /**
14682 * True if the last call to `validate` is invalid.
14683 */
14684 invalid: {
14685 notify: true,
14686 reflectToAttribute: true,
14687 type: Boolean,
14688 value: false
14689 },
14690
14691 _validatorMeta: {
14692 type: Object
14693 }
14694
14695 },
14696
14697 observers: [
14698 '_invalidChanged(invalid)'
14699 ],
14700
14701 get _validator() {
14702 return this._validatorMeta && this._validatorMeta.byKey(this.validator);
14703 },
14704
14705 ready: function() {
14706 this._validatorMeta = new Polymer.IronMeta({type: this.validatorType});
14707 },
14708
14709 _invalidChanged: function() {
14710 if (this.invalid) {
14711 this.setAttribute('aria-invalid', 'true');
14712 } else {
14713 this.removeAttribute('aria-invalid');
14714 }
14715 },
14716
14717 /**
14718 * @return {boolean} True if the validator `validator` exists.
14719 */
14720 hasValidator: function() {
14721 return this._validator != null;
14722 },
14723
14724 /**
14725 * Returns true if the `value` is valid, and updates `invalid`. If you want
14726 * your element to have custom validation logic, do not override this method ;
14727 * override `_getValidity(value)` instead.
14728
14729 * @param {Object} value The value to be validated. By default, it is passed
14730 * to the validator's `validate()` function, if a validator is set.
14731 * @return {boolean} True if `value` is valid.
14732 */
14733 validate: function(value) {
14734 this.invalid = !this._getValidity(value);
14735 return !this.invalid;
14736 },
14737
14738 /**
14739 * Returns true if `value` is valid. By default, it is passed
14740 * to the validator's `validate()` function, if a validator is set. You
14741 * should override this method if you want to implement custom validity
14742 * logic for your element.
14743 *
14744 * @param {Object} value The value to be validated.
14745 * @return {boolean} True if `value` is valid.
14746 */
14747
14748 _getValidity: function(value) {
14749 if (this.hasValidator()) {
14750 return this._validator.validate(value);
14751 }
14752 return true;
14753 }
14754 };
14755 /*
14756 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal idatorBehavior`
14757 to `<input>`.
14758
14759 ### Two-way binding
14760
14761 By default you can only get notified of changes to an `input`'s `value` due to u ser input:
14762
14763 <input value="{{myValue::input}}">
14764
14765 `iron-input` adds the `bind-value` property that mirrors the `value` property, a nd can be used
14766 for two-way data binding. `bind-value` will notify if it is changed either by us er input or by script.
14767
14768 <input is="iron-input" bind-value="{{myValue}}">
14769
14770 ### Custom validators
14771
14772 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit h `<iron-input>`.
14773
14774 <input is="iron-input" validator="my-custom-validator">
14775
14776 ### Stopping invalid input
14777
14778 It may be desirable to only allow users to enter certain characters. You can use the
14779 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature
14780 is separate from validation, and `allowed-pattern` does not affect how the input is validated.
14781
14782 <!-- only allow characters that match [0-9] -->
14783 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">
14784
14785 @hero hero.svg
14786 @demo demo/index.html
14787 */
14788
14789 Polymer({
14790
14791 is: 'iron-input',
14792
14793 extends: 'input',
14794
14795 behaviors: [
14796 Polymer.IronValidatableBehavior
14797 ],
14798
14799 properties: {
14800
14801 /**
14802 * Use this property instead of `value` for two-way data binding.
14803 */
14804 bindValue: {
14805 observer: '_bindValueChanged',
14806 type: String
14807 },
14808
14809 /**
14810 * Set to true to prevent the user from entering invalid input. The new in put characters are
14811 * matched with `allowedPattern` if it is set, otherwise it will use the ` pattern` attribute if
14812 * set, or the `type` attribute (only supported for `type=number`).
14813 */
14814 preventInvalidInput: {
14815 type: Boolean
14816 },
14817
14818 /**
14819 * Regular expression to match valid input characters.
14820 */
14821 allowedPattern: {
14822 type: String
14823 },
14824
14825 _previousValidInput: {
14826 type: String,
14827 value: ''
14828 },
14829
14830 _patternAlreadyChecked: {
14831 type: Boolean,
14832 value: false
14833 }
14834
14835 },
14836
14837 listeners: {
14838 'input': '_onInput',
14839 'keypress': '_onKeypress'
14840 },
14841
14842 get _patternRegExp() {
14843 var pattern;
14844 if (this.allowedPattern) {
14845 pattern = new RegExp(this.allowedPattern);
14846 } else if (this.pattern) {
14847 pattern = new RegExp(this.pattern);
14848 } else {
14849 switch (this.type) {
14850 case 'number':
14851 pattern = /[0-9.,e-]/;
14852 break;
14853 }
14854 }
14855 return pattern;
14856 },
14857
14858 ready: function() {
14859 this.bindValue = this.value;
14860 },
14861
14862 /**
14863 * @suppress {checkTypes}
14864 */
14865 _bindValueChanged: function() {
14866 if (this.value !== this.bindValue) {
14867 this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindV alue;
14868 }
14869 // manually notify because we don't want to notify until after setting val ue
14870 this.fire('bind-value-changed', {value: this.bindValue});
14871 },
14872
14873 _onInput: function() {
14874 // Need to validate each of the characters pasted if they haven't
14875 // been validated inside `_onKeypress` already.
14876 if (this.preventInvalidInput && !this._patternAlreadyChecked) {
14877 var valid = this._checkPatternValidity();
14878 if (!valid) {
14879 this.value = this._previousValidInput;
14880 }
14881 }
14882
14883 this.bindValue = this.value;
14884 this._previousValidInput = this.value;
14885 this._patternAlreadyChecked = false;
14886 },
14887
14888 _isPrintable: function(event) {
14889 // What a control/printable character is varies wildly based on the browse r.
14890 // - most control characters (arrows, backspace) do not send a `keypress` event
14891 // in Chrome, but the *do* on Firefox
14892 // - in Firefox, when they do send a `keypress` event, control chars have
14893 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
14894 // - printable characters always send a keypress event.
14895 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
14896 // always matches the charCode.
14897 // None of this makes any sense.
14898
14899 // For these keys, ASCII code == browser keycode.
14900 var anyNonPrintable =
14901 (event.keyCode == 8) || // backspace
14902 (event.keyCode == 9) || // tab
14903 (event.keyCode == 13) || // enter
14904 (event.keyCode == 27); // escape
14905
14906 // For these keys, make sure it's a browser keycode and not an ASCII code.
14907 var mozNonPrintable =
14908 (event.keyCode == 19) || // pause
14909 (event.keyCode == 20) || // caps lock
14910 (event.keyCode == 45) || // insert
14911 (event.keyCode == 46) || // delete
14912 (event.keyCode == 144) || // num lock
14913 (event.keyCode == 145) || // scroll lock
14914 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho me, arrows
14915 (event.keyCode > 111 && event.keyCode < 124); // fn keys
14916
14917 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
14918 },
14919
14920 _onKeypress: function(event) {
14921 if (!this.preventInvalidInput && this.type !== 'number') {
14922 return;
14923 }
14924 var regexp = this._patternRegExp;
14925 if (!regexp) {
14926 return;
14927 }
14928
14929 // Handle special keys and backspace
14930 if (event.metaKey || event.ctrlKey || event.altKey)
14931 return;
14932
14933 // Check the pattern either here or in `_onInput`, but not in both.
14934 this._patternAlreadyChecked = true;
14935
14936 var thisChar = String.fromCharCode(event.charCode);
14937 if (this._isPrintable(event) && !regexp.test(thisChar)) {
14938 event.preventDefault();
14939 }
14940 },
14941
14942 _checkPatternValidity: function() {
14943 var regexp = this._patternRegExp;
14944 if (!regexp) {
14945 return true;
14946 }
14947 for (var i = 0; i < this.value.length; i++) {
14948 if (!regexp.test(this.value[i])) {
14949 return false;
14950 }
14951 }
14952 return true;
14953 },
14954
14955 /**
14956 * Returns true if `value` is valid. The validator provided in `validator` w ill be used first,
14957 * then any constraints.
14958 * @return {boolean} True if the value is valid.
14959 */
14960 validate: function() {
14961 // Empty, non-required input is valid.
14962 if (!this.required && this.value == '') {
14963 this.invalid = false;
14964 return true;
14965 }
14966
14967 var valid;
14968 if (this.hasValidator()) {
14969 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
14970 } else {
14971 this.invalid = !this.validity.valid;
14972 valid = this.validity.valid;
14973 }
14974 this.fire('iron-input-validate');
14975 return valid;
14976 }
14977
14978 });
14979
14980 /*
14981 The `iron-input-validate` event is fired whenever `validate()` is called.
14982 @event iron-input-validate
14983 */
14984 Polymer({
14985 is: 'paper-input-container',
14986
14987 properties: {
14988 /**
14989 * Set to true to disable the floating label. The label disappears when th e input value is
14990 * not null.
14991 */
14992 noLabelFloat: {
14993 type: Boolean,
14994 value: false
14995 },
14996
14997 /**
14998 * Set to true to always float the floating label.
14999 */
15000 alwaysFloatLabel: {
15001 type: Boolean,
15002 value: false
15003 },
15004
15005 /**
15006 * The attribute to listen for value changes on.
15007 */
15008 attrForValue: {
15009 type: String,
15010 value: 'bind-value'
15011 },
15012
15013 /**
15014 * Set to true to auto-validate the input value when it changes.
15015 */
15016 autoValidate: {
15017 type: Boolean,
15018 value: false
15019 },
15020
15021 /**
15022 * True if the input is invalid. This property is set automatically when t he input value
15023 * changes if auto-validating, or when the `iron-input-validate` event is heard from a child.
15024 */
15025 invalid: {
15026 observer: '_invalidChanged',
15027 type: Boolean,
15028 value: false
15029 },
15030
15031 /**
15032 * True if the input has focus.
15033 */
15034 focused: {
15035 readOnly: true,
15036 type: Boolean,
15037 value: false,
15038 notify: true
15039 },
15040
15041 _addons: {
15042 type: Array
15043 // do not set a default value here intentionally - it will be initialize d lazily when a
15044 // distributed child is attached, which may occur before configuration f or this element
15045 // in polyfill.
15046 },
15047
15048 _inputHasContent: {
15049 type: Boolean,
15050 value: false
15051 },
15052
15053 _inputSelector: {
15054 type: String,
15055 value: 'input,textarea,.paper-input-input'
15056 },
15057
15058 _boundOnFocus: {
15059 type: Function,
15060 value: function() {
15061 return this._onFocus.bind(this);
15062 }
15063 },
15064
15065 _boundOnBlur: {
15066 type: Function,
15067 value: function() {
15068 return this._onBlur.bind(this);
15069 }
15070 },
15071
15072 _boundOnInput: {
15073 type: Function,
15074 value: function() {
15075 return this._onInput.bind(this);
15076 }
15077 },
15078
15079 _boundValueChanged: {
15080 type: Function,
15081 value: function() {
15082 return this._onValueChanged.bind(this);
15083 }
15084 }
15085 },
15086
15087 listeners: {
15088 'addon-attached': '_onAddonAttached',
15089 'iron-input-validate': '_onIronInputValidate'
15090 },
15091
15092 get _valueChangedEvent() {
15093 return this.attrForValue + '-changed';
15094 },
15095
15096 get _propertyForValue() {
15097 return Polymer.CaseMap.dashToCamelCase(this.attrForValue);
15098 },
15099
15100 get _inputElement() {
15101 return Polymer.dom(this).querySelector(this._inputSelector);
15102 },
15103
15104 get _inputElementValue() {
15105 return this._inputElement[this._propertyForValue] || this._inputElement.va lue;
15106 },
15107
15108 ready: function() {
15109 if (!this._addons) {
15110 this._addons = [];
15111 }
15112 this.addEventListener('focus', this._boundOnFocus, true);
15113 this.addEventListener('blur', this._boundOnBlur, true);
15114 if (this.attrForValue) {
15115 this._inputElement.addEventListener(this._valueChangedEvent, this._bound ValueChanged);
15116 } else {
15117 this.addEventListener('input', this._onInput);
15118 }
15119 },
15120
15121 attached: function() {
15122 // Only validate when attached if the input already has a value.
15123 if (this._inputElementValue != '') {
15124 this._handleValueAndAutoValidate(this._inputElement);
15125 } else {
15126 this._handleValue(this._inputElement);
15127 }
15128 },
15129
15130 _onAddonAttached: function(event) {
15131 if (!this._addons) {
15132 this._addons = [];
15133 }
15134 var target = event.target;
15135 if (this._addons.indexOf(target) === -1) {
15136 this._addons.push(target);
15137 if (this.isAttached) {
15138 this._handleValue(this._inputElement);
15139 }
15140 }
15141 },
15142
15143 _onFocus: function() {
15144 this._setFocused(true);
15145 },
15146
15147 _onBlur: function() {
15148 this._setFocused(false);
15149 this._handleValueAndAutoValidate(this._inputElement);
15150 },
15151
15152 _onInput: function(event) {
15153 this._handleValueAndAutoValidate(event.target);
15154 },
15155
15156 _onValueChanged: function(event) {
15157 this._handleValueAndAutoValidate(event.target);
15158 },
15159
15160 _handleValue: function(inputElement) {
15161 var value = this._inputElementValue;
15162
15163 // type="number" hack needed because this.value is empty until it's valid
15164 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme nt.checkValidity())) {
15165 this._inputHasContent = true;
15166 } else {
15167 this._inputHasContent = false;
15168 }
15169
15170 this.updateAddons({
15171 inputElement: inputElement,
15172 value: value,
15173 invalid: this.invalid
15174 });
15175 },
15176
15177 _handleValueAndAutoValidate: function(inputElement) {
15178 if (this.autoValidate) {
15179 var valid;
15180 if (inputElement.validate) {
15181 valid = inputElement.validate(this._inputElementValue);
15182 } else {
15183 valid = inputElement.checkValidity();
15184 }
15185 this.invalid = !valid;
15186 }
15187
15188 // Call this last to notify the add-ons.
15189 this._handleValue(inputElement);
15190 },
15191
15192 _onIronInputValidate: function(event) {
15193 this.invalid = this._inputElement.invalid;
15194 },
15195
15196 _invalidChanged: function() {
15197 if (this._addons) {
15198 this.updateAddons({invalid: this.invalid});
15199 }
15200 },
15201
15202 /**
15203 * Call this to update the state of add-ons.
15204 * @param {Object} state Add-on state.
15205 */
15206 updateAddons: function(state) {
15207 for (var addon, index = 0; addon = this._addons[index]; index++) {
15208 addon.update(state);
15209 }
15210 },
15211
15212 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) {
15213 var cls = 'input-content';
15214 if (!noLabelFloat) {
15215 var label = this.querySelector('label');
15216
15217 if (alwaysFloatLabel || _inputHasContent) {
15218 cls += ' label-is-floating';
15219 if (invalid) {
15220 cls += ' is-invalid';
15221 } else if (focused) {
15222 cls += " label-is-highlighted";
15223 }
15224 // The label might have a horizontal offset if a prefix element exists
15225 // which needs to be undone when displayed as a floating label.
15226 if (Polymer.dom(this.$.prefix).getDistributedNodes().length > 0 &&
15227 label && label.offsetParent) {
15228 label.style.left = -label.offsetParent.offsetLeft + 'px';
15229 }
15230 } else {
15231 // When the label is not floating, it should overlap the input element .
15232 if (label) {
15233 label.style.left = 0;
15234 }
15235 }
15236 } else {
15237 if (_inputHasContent) {
15238 cls += ' label-is-hidden';
15239 }
15240 }
15241 return cls;
15242 },
15243
15244 _computeUnderlineClass: function(focused, invalid) {
15245 var cls = 'underline';
15246 if (invalid) {
15247 cls += ' is-invalid';
15248 } else if (focused) {
15249 cls += ' is-highlighted'
15250 }
15251 return cls;
15252 },
15253
15254 _computeAddOnContentClass: function(focused, invalid) {
15255 var cls = 'add-on-content';
15256 if (invalid) {
15257 cls += ' is-invalid';
15258 } else if (focused) {
15259 cls += ' is-highlighted'
15260 }
15261 return cls;
15262 }
15263 });
15264 // Copyright 2015 The Chromium Authors. All rights reserved.
15265 // Use of this source code is governed by a BSD-style license that can be
15266 // found in the LICENSE file.
15267
15268 /** @interface */
15269 var SearchFieldDelegate = function() {};
15270
15271 SearchFieldDelegate.prototype = {
15272 /**
15273 * @param {string} value
15274 */
15275 onSearchTermSearch: assertNotReached,
15276 };
15277
15278 var SearchField = Polymer({
15279 is: 'cr-search-field',
15280
15281 properties: {
15282 label: {
15283 type: String,
15284 value: '',
15285 },
15286
15287 clearLabel: {
15288 type: String,
15289 value: '',
15290 },
15291
15292 showingSearch_: {
15293 type: Boolean,
15294 value: false,
15295 },
15296 },
15297
15298 /** @param {SearchFieldDelegate} delegate */
15299 setDelegate: function(delegate) {
15300 this.delegate_ = delegate;
15301 },
15302
15303 /**
15304 * Returns the value of the search field.
15305 * @return {string}
15306 */
15307 getValue: function() {
15308 var searchInput = this.$$('#search-input');
15309 return searchInput ? searchInput.value : '';
15310 },
15311
15312 /** @private */
15313 onSearchTermSearch_: function() {
15314 if (this.delegate_)
15315 this.delegate_.onSearchTermSearch(this.getValue());
15316 },
15317
15318 /** @private */
15319 onSearchTermKeydown_: function(e) {
15320 assert(this.showingSearch_);
15321 if (e.keyIdentifier == 'U+001B') // Escape.
15322 this.toggleShowingSearch_();
15323 },
15324
15325 /** @private */
15326 toggleShowingSearch_: function() {
15327 this.showingSearch_ = !this.showingSearch_;
15328 this.async(function() {
15329 var searchInput = this.$$('#search-input');
15330 if (this.showingSearch_) {
15331 searchInput.focus();
15332 } else {
15333 searchInput.value = '';
15334 this.onSearchTermSearch_();
15335 }
15336 });
15337 },
15338 });
15339 // Copyright 2015 The Chromium Authors. All rights reserved.
15340 // Use of this source code is governed by a BSD-style license that can be
15341 // found in the LICENSE file.
15342
15343 cr.define('downloads', function() {
15344 var Toolbar = Polymer({
15345 is: 'downloads-toolbar',
15346
15347 attached: function() {
15348 /** @private {!SearchFieldDelegate} */
15349 this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this);
15350 this.$['search-input'].setDelegate(this.searchFieldDelegate_);
15351 },
15352
15353 properties: {
15354 downloadsShowing: {
15355 reflectToAttribute: true,
15356 type: Boolean,
15357 value: false,
15358 observer: 'onDownloadsShowingChange_',
15359 },
15360 },
15361
15362 /** @return {boolean} Whether removal can be undone. */
15363 canUndo: function() {
15364 return this.$['search-input'] != this.shadowRoot.activeElement;
15365 },
15366
15367 /** @return {boolean} Whether "Clear all" should be allowed. */
15368 canClearAll: function() {
15369 return !this.$['search-input'].getValue() && this.downloadsShowing;
15370 },
15371
15372 /** @private */
15373 onClearAllClick_: function() {
15374 assert(this.canClearAll());
15375 downloads.ActionService.getInstance().clearAll();
15376 },
15377
15378 /** @private */
15379 onDownloadsShowingChange_: function() {
15380 this.updateClearAll_();
15381 },
15382
15383 /** @param {string} searchTerm */
15384 onSearchTermSearch: function(searchTerm) {
15385 downloads.ActionService.getInstance().search(searchTerm);
15386 this.updateClearAll_();
15387 },
15388
15389 /** @private */
15390 onOpenDownloadsFolderClick_: function() {
15391 downloads.ActionService.getInstance().openDownloadsFolder();
15392 },
15393
15394 /** @private */
15395 updateClearAll_: function() {
15396 this.$$('#actions .clear-all').hidden = !this.canClearAll();
15397 this.$$('paper-menu .clear-all').hidden = !this.canClearAll();
15398 },
15399 });
15400
15401 /**
15402 * @constructor
15403 * @implements {SearchFieldDelegate}
15404 */
15405 // TODO(devlin): This is a bit excessive, and it would be better to just have
15406 // Toolbar implement SearchFieldDelegate. But for now, we don't know how to
15407 // make that happen with closure compiler.
15408 function ToolbarSearchFieldDelegate(toolbar) {
15409 this.toolbar_ = toolbar;
15410 }
15411
15412 ToolbarSearchFieldDelegate.prototype = {
15413 /** @override */
15414 onSearchTermSearch: function(searchTerm) {
15415 this.toolbar_.onSearchTermSearch(searchTerm);
15416 }
15417 };
15418
15419 return {Toolbar: Toolbar};
15420 });
15421
15422 // TODO(dbeam): https://github.com/PolymerElements/iron-dropdown/pull/16/files
15423 /** @suppress {checkTypes} */
15424 (function() {
15425 Polymer.IronDropdownScrollManager.pushScrollLock = function() {};
15426 })();
15427 // Copyright 2015 The Chromium Authors. All rights reserved.
15428 // Use of this source code is governed by a BSD-style license that can be
15429 // found in the LICENSE file.
15430
15431 cr.define('downloads', function() {
15432 var Manager = Polymer({
15433 is: 'downloads-manager',
15434
15435 properties: {
15436 hasDownloads_: {
15437 type: Boolean,
15438 value: false,
15439 },
15440 },
15441
15442 /**
15443 * @return {number} A guess at how many items could be visible at once.
15444 * @private
15445 */
15446 guesstimateNumberOfVisibleItems_: function() {
15447 var toolbarHeight = this.$.toolbar.offsetHeight;
15448 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
15449 },
15450
15451 /**
15452 * @param {Event} e
15453 * @private
15454 */
15455 onCanExecute_: function(e) {
15456 e = /** @type {cr.ui.CanExecuteEvent} */(e);
15457 switch (e.command.id) {
15458 case 'undo-command':
15459 e.canExecute = this.$.toolbar.canUndo();
15460 break;
15461 case 'clear-all-command':
15462 e.canExecute = this.$.toolbar.canClearAll();
15463 break;
15464 }
15465 },
15466
15467 /**
15468 * @param {Event} e
15469 * @private
15470 */
15471 onCommand_: function(e) {
15472 if (e.command.id == 'clear-all-command')
15473 downloads.ActionService.getInstance().clearAll();
15474 else if (e.command.id == 'undo-command')
15475 downloads.ActionService.getInstance().undo();
15476 },
15477
15478 /** @private */
15479 onLoad_: function() {
15480 cr.ui.decorate('command', cr.ui.Command);
15481 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
15482 document.addEventListener('command', this.onCommand_.bind(this));
15483
15484 // Shows all downloads.
15485 downloads.ActionService.getInstance().search('');
15486 },
15487
15488 /** @private */
15489 rebuildFocusGrid_: function() {
15490 var activeElement = this.shadowRoot.activeElement;
15491
15492 var activeItem;
15493 if (activeElement && activeElement.tagName == 'downloads-item')
15494 activeItem = activeElement;
15495
15496 var activeControl = activeItem && activeItem.shadowRoot.activeElement;
15497
15498 /** @private {!cr.ui.FocusGrid} */
15499 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
15500 this.focusGrid_.destroy();
15501
15502 var boundary = this.$['downloads-list'];
15503
15504 this.items_.forEach(function(item) {
15505 var focusRow = new downloads.FocusRow(item.content, boundary);
15506 this.focusGrid_.addRow(focusRow);
15507
15508 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
15509 focusRow.getEquivalentElement(activeControl).focus();
15510 }, this);
15511
15512 this.focusGrid_.ensureRowActive();
15513 },
15514
15515 /**
15516 * @return {number} The number of downloads shown on the page.
15517 * @private
15518 */
15519 size_: function() {
15520 return this.items_.length;
15521 },
15522
15523 /**
15524 * Called when all items need to be updated.
15525 * @param {!Array<!downloads.Data>} list A list of new download data.
15526 * @private
15527 */
15528 updateAll_: function(list) {
15529 var oldIdMap = this.idMap_ || {};
15530
15531 /** @private {!Object<!downloads.Item>} */
15532 this.idMap_ = {};
15533
15534 /** @private {!Array<!downloads.Item>} */
15535 this.items_ = [];
15536
15537 if (!this.iconLoader_) {
15538 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
15539 /** @private {downloads.ThrottledIconLoader} */
15540 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
15541 }
15542
15543 for (var i = 0; i < list.length; ++i) {
15544 var data = list[i];
15545 var id = data.id;
15546
15547 // Re-use old items when possible (saves work, preserves focus).
15548 var item = oldIdMap[id] || new downloads.Item(this.iconLoader_);
15549
15550 this.idMap_[id] = item; // Associated by ID for fast lookup.
15551 this.items_.push(item); // Add to sorted list for order.
15552
15553 // Render |item| but don't actually add to the DOM yet. |this.items_|
15554 // must be fully created to be able to find the right spot to insert.
15555 item.update(data);
15556
15557 // Collapse redundant dates.
15558 var prev = list[i - 1];
15559 item.hideDate = !!prev && prev.date_string == data.date_string;
15560
15561 delete oldIdMap[id];
15562 }
15563
15564 // Remove stale, previously rendered items from the DOM.
15565 for (var id in oldIdMap) {
15566 if (oldIdMap[id].parentNode)
15567 oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
15568 delete oldIdMap[id];
15569 }
15570
15571 for (var i = 0; i < this.items_.length; ++i) {
15572 var item = this.items_[i];
15573 if (item.parentNode) // Already in the DOM; skip.
15574 continue;
15575
15576 var before = null;
15577 // Find the next rendered item after this one, and insert before it.
15578 for (var j = i + 1; !before && j < this.items_.length; ++j) {
15579 if (this.items_[j].parentNode)
15580 before = this.items_[j];
15581 }
15582 // If |before| is null, |item| will just get added at the end.
15583 this.$['downloads-list'].insertBefore(item, before);
15584 }
15585
15586 var hasDownloads = this.size_() > 0;
15587 if (!hasDownloads) {
15588 var isSearching = downloads.ActionService.getInstance().isSearching();
15589 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
15590 this.$['no-downloads'].querySelector('span').textContent =
15591 loadTimeData.getString(messageToShow);
15592 }
15593 this.hasDownloads_ = hasDownloads;
15594
15595 if (loadTimeData.getBoolean('allowDeletingHistory'))
15596 this.$.toolbar.downloadsShowing = this.hasDownloads_;
15597
15598 this.$.panel.classList.remove('loading');
15599
15600 var allReady = this.items_.map(function(i) { return i.readyPromise; });
15601 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
15602 },
15603
15604 /**
15605 * @param {!downloads.Data} data
15606 * @private
15607 */
15608 updateItem_: function(data) {
15609 var item = this.idMap_[data.id];
15610
15611 var activeControl = this.shadowRoot.activeElement == item ?
15612 item.shadowRoot.activeElement : null;
15613
15614 item.update(data);
15615
15616 this.async(function() {
15617 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
15618 var focusRow = this.focusGrid_.getRowForRoot(item.content);
15619 focusRow.getEquivalentElement(activeControl).focus();
15620 }
15621 }.bind(this));
15622 },
15623 });
15624
15625 Manager.size = function() {
15626 return document.querySelector('downloads-manager').size_();
15627 };
15628
15629 Manager.updateAll = function(list) {
15630 document.querySelector('downloads-manager').updateAll_(list);
15631 };
15632
15633 Manager.updateItem = function(item) {
15634 document.querySelector('downloads-manager').updateItem_(item);
15635 };
15636
15637 Manager.onLoad = function() {
15638 document.querySelector('downloads-manager').onLoad_();
15639 };
15640
15641 return {Manager: Manager};
15642 });
15643
15644 window.addEventListener('load', downloads.Manager.onLoad);
OLDNEW
« no previous file with comments | « chrome/browser/browser_resources.grd ('k') | chrome/browser/resources/md_downloads/item.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698