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

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: mo bugs 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
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 return {ActionService: ActionService};
1864 });
1865 // Copyright 2015 The Chromium Authors. All rights reserved.
1866 // Use of this source code is governed by a BSD-style license that can be
1867 // found in the LICENSE file.
1868
1869 cr.define('downloads', function() {
1870 /**
1871 * Explains why a download is in DANGEROUS state.
1872 * @enum {string}
1873 */
1874 var DangerType = {
1875 NOT_DANGEROUS: 'NOT_DANGEROUS',
1876 DANGEROUS_FILE: 'DANGEROUS_FILE',
1877 DANGEROUS_URL: 'DANGEROUS_URL',
1878 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
1879 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
1880 DANGEROUS_HOST: 'DANGEROUS_HOST',
1881 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
1882 };
1883
1884 /**
1885 * The states a download can be in. These correspond to states defined in
1886 * DownloadsDOMHandler::CreateDownloadItemValue
1887 * @enum {string}
1888 */
1889 var States = {
1890 IN_PROGRESS: 'IN_PROGRESS',
1891 CANCELLED: 'CANCELLED',
1892 COMPLETE: 'COMPLETE',
1893 PAUSED: 'PAUSED',
1894 DANGEROUS: 'DANGEROUS',
1895 INTERRUPTED: 'INTERRUPTED',
1896 };
1897
1898 return {
1899 DangerType: DangerType,
1900 States: States,
1901 };
1902 });
1903 // Copyright 2014 The Chromium Authors. All rights reserved.
1904 // Use of this source code is governed by a BSD-style license that can be
1905 // found in the LICENSE file.
1906
1907 cr.define('cr.ui', function() {
1908 /**
1909 * A class to manage focus between given horizontally arranged elements.
1910 *
1911 * Pressing left cycles backward and pressing right cycles forward in item
1912 * order. Pressing Home goes to the beginning of the list and End goes to the
1913 * end of the list.
1914 *
1915 * If an item in this row is focused, it'll stay active (accessible via tab).
1916 * If no items in this row are focused, the row can stay active until focus
1917 * changes to a node inside |this.boundary_|. If |boundary| isn't specified,
1918 * any focus change deactivates the row.
1919 *
1920 * @param {!Element} root The root of this focus row. Focus classes are
1921 * applied to |root| and all added elements must live within |root|.
1922 * @param {?Node} boundary Focus events are ignored outside of this node.
1923 * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate.
1924 * @constructor
1925 */
1926 function FocusRow(root, boundary, opt_delegate) {
1927 /** @type {!Element} */
1928 this.root = root;
1929
1930 /** @private {!Node} */
1931 this.boundary_ = boundary || document;
1932
1933 /** @type {cr.ui.FocusRow.Delegate|undefined} */
1934 this.delegate = opt_delegate;
1935
1936 /** @protected {!EventTracker} */
1937 this.eventTracker = new EventTracker;
1938 }
1939
1940 /** @interface */
1941 FocusRow.Delegate = function() {};
1942
1943 FocusRow.Delegate.prototype = {
1944 /**
1945 * Called when a key is pressed while on a FocusRow's item. If true is
1946 * returned, further processing is skipped.
1947 * @param {!cr.ui.FocusRow} row The row that detected a keydown.
1948 * @param {!Event} e
1949 * @return {boolean} Whether the event was handled.
1950 */
1951 onKeydown: assertNotReached,
1952
1953 /**
1954 * @param {!cr.ui.FocusRow} row
1955 * @param {!Event} e
1956 */
1957 onFocus: assertNotReached,
1958 };
1959
1960 /** @const {string} */
1961 FocusRow.ACTIVE_CLASS = 'focus-row-active';
1962
1963 /**
1964 * Whether it's possible that |element| can be focused.
1965 * @param {Element} element
1966 * @return {boolean} Whether the item is focusable.
1967 */
1968 FocusRow.isFocusable = function(element) {
1969 if (!element || element.disabled)
1970 return false;
1971
1972 // We don't check that element.tabIndex >= 0 here because inactive rows set
1973 // a tabIndex of -1.
1974
1975 function isVisible(element) {
1976 assertInstanceof(element, Element);
1977
1978 var style = window.getComputedStyle(element);
1979 if (style.visibility == 'hidden' || style.display == 'none')
1980 return false;
1981
1982 var parent = element.parentNode;
1983 if (!parent)
1984 return false;
1985
1986 if (parent == element.ownerDocument || parent instanceof DocumentFragment)
1987 return true;
1988
1989 return isVisible(parent);
1990 }
1991
1992 return isVisible(element);
1993 };
1994
1995 FocusRow.prototype = {
1996 /**
1997 * Register a new type of focusable element (or add to an existing one).
1998 *
1999 * Example: an (X) button might be 'delete' or 'close'.
2000 *
2001 * When FocusRow is used within a FocusGrid, these types are used to
2002 * determine equivalent controls when Up/Down are pressed to change rows.
2003 *
2004 * Another example: mutually exclusive controls that hide eachother on
2005 * activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause')
2006 * to indicate they're equivalent.
2007 *
2008 * @param {string} type The type of element to track focus of.
2009 * @param {string} query The selector of the element from this row's root.
2010 * @return {boolean} Whether a new item was added.
2011 */
2012 addItem: function(type, query) {
2013 assert(type);
2014
2015 var element = this.root.querySelector(query);
2016 if (!element)
2017 return false;
2018
2019 element.setAttribute('focus-type', type);
2020 element.tabIndex = this.isActive() ? 0 : -1;
2021
2022 this.eventTracker.add(element, 'blur', this.onBlur_.bind(this));
2023 this.eventTracker.add(element, 'focus', this.onFocus_.bind(this));
2024 this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this));
2025 this.eventTracker.add(element, 'mousedown',
2026 this.onMousedown_.bind(this));
2027 return true;
2028 },
2029
2030 /** Dereferences nodes and removes event handlers. */
2031 destroy: function() {
2032 this.eventTracker.removeAll();
2033 },
2034
2035 /**
2036 * @param {Element} sampleElement An element for to find an equivalent for.
2037 * @return {!Element} An equivalent element to focus for |sampleElement|.
2038 * @protected
2039 */
2040 getCustomEquivalent: function(sampleElement) {
2041 return assert(this.getFirstFocusable());
2042 },
2043
2044 /**
2045 * @return {!Array<!Element>} All registered elements (regardless of
2046 * focusability).
2047 */
2048 getElements: function() {
2049 var elements = this.root.querySelectorAll('[focus-type]');
2050 return Array.prototype.slice.call(elements);
2051 },
2052
2053 /**
2054 * Find the element that best matches |sampleElement|.
2055 * @param {!Element} sampleElement An element from a row of the same type
2056 * which previously held focus.
2057 * @return {!Element} The element that best matches sampleElement.
2058 */
2059 getEquivalentElement: function(sampleElement) {
2060 if (this.getFocusableElements().indexOf(sampleElement) >= 0)
2061 return sampleElement;
2062
2063 var sampleFocusType = this.getTypeForElement(sampleElement);
2064 if (sampleFocusType) {
2065 var sameType = this.getFirstFocusable(sampleFocusType);
2066 if (sameType)
2067 return sameType;
2068 }
2069
2070 return this.getCustomEquivalent(sampleElement);
2071 },
2072
2073 /**
2074 * @param {string=} opt_type An optional type to search for.
2075 * @return {?Element} The first focusable element with |type|.
2076 */
2077 getFirstFocusable: function(opt_type) {
2078 var filter = opt_type ? '="' + opt_type + '"' : '';
2079 var elements = this.root.querySelectorAll('[focus-type' + filter + ']');
2080 for (var i = 0; i < elements.length; ++i) {
2081 if (cr.ui.FocusRow.isFocusable(elements[i]))
2082 return elements[i];
2083 }
2084 return null;
2085 },
2086
2087 /** @return {!Array<!Element>} Registered, focusable elements. */
2088 getFocusableElements: function() {
2089 return this.getElements().filter(cr.ui.FocusRow.isFocusable);
2090 },
2091
2092 /**
2093 * @param {!Element} element An element to determine a focus type for.
2094 * @return {string} The focus type for |element| or '' if none.
2095 */
2096 getTypeForElement: function(element) {
2097 return element.getAttribute('focus-type') || '';
2098 },
2099
2100 /** @return {boolean} Whether this row is currently active. */
2101 isActive: function() {
2102 return this.root.classList.contains(FocusRow.ACTIVE_CLASS);
2103 },
2104
2105 /**
2106 * Enables/disables the tabIndex of the focusable elements in the FocusRow.
2107 * tabIndex can be set properly.
2108 * @param {boolean} active True if tab is allowed for this row.
2109 */
2110 makeActive: function(active) {
2111 if (active == this.isActive())
2112 return;
2113
2114 this.getElements().forEach(function(element) {
2115 element.tabIndex = active ? 0 : -1;
2116 });
2117
2118 this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active);
2119 },
2120
2121 /**
2122 * @param {!Event} e
2123 * @private
2124 */
2125 onBlur_: function(e) {
2126 if (!this.boundary_.contains(/** @type {Node} */(e.relatedTarget)))
2127 return;
2128
2129 if (this.getFocusableElements().indexOf(e.currentTarget) >= 0)
2130 this.makeActive(false);
2131 },
2132
2133 /**
2134 * @param {!Event} e
2135 * @private
2136 */
2137 onFocus_: function(e) {
2138 if (this.delegate)
2139 this.delegate.onFocus(this, e);
2140 },
2141
2142 /**
2143 * @param {!Event} e A mousedown event.
2144 * @private
2145 */
2146 onMousedown_: function(e) {
2147 // Only accept left mouse clicks.
2148 if (e.button)
2149 return;
2150
2151 // Allow the element under the mouse cursor to be focusable.
2152 if (!e.currentTarget.disabled)
2153 e.currentTarget.tabIndex = 0;
2154 },
2155
2156 /**
2157 * @param {Event} e The keydown event.
2158 * @private
2159 */
2160 onKeydown_: function(e) {
2161 var elements = this.getFocusableElements();
2162 var elementIndex = elements.indexOf(e.currentTarget);
2163 assert(elementIndex >= 0);
2164
2165 if (this.delegate && this.delegate.onKeydown(this, e))
2166 return;
2167
2168 if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
2169 return;
2170
2171 var index = -1;
2172
2173 if (e.keyIdentifier == 'Left')
2174 index = elementIndex + (isRTL() ? 1 : -1);
2175 else if (e.keyIdentifier == 'Right')
2176 index = elementIndex + (isRTL() ? -1 : 1);
2177 else if (e.keyIdentifier == 'Home')
2178 index = 0;
2179 else if (e.keyIdentifier == 'End')
2180 index = elements.length - 1;
2181
2182 var elementToFocus = elements[index];
2183 if (elementToFocus) {
2184 this.getEquivalentElement(elementToFocus).focus();
2185 e.preventDefault();
2186 }
2187 },
2188 };
2189
2190 return {
2191 FocusRow: FocusRow,
2192 };
2193 });
2194 // Copyright 2015 The Chromium Authors. All rights reserved.
2195 // Use of this source code is governed by a BSD-style license that can be
2196 // found in the LICENSE file.
2197
2198 cr.define('downloads', function() {
2199 /**
2200 * @param {!Element} root
2201 * @param {?Node} boundary
2202 * @constructor
2203 * @extends {cr.ui.FocusRow}
2204 */
2205 function FocusRow(root, boundary) {
2206 cr.ui.FocusRow.call(this, root, boundary);
2207 this.addItems();
2208 }
2209
2210 FocusRow.prototype = {
2211 __proto__: cr.ui.FocusRow.prototype,
2212
2213 addItems: function() {
2214 this.destroy();
2215
2216 this.addItem('name-file-link',
2217 'content.is-active:not(.show-progress):not(.dangerous) #name');
2218 assert(this.addItem('name-file-link', '#file-link'));
2219 assert(this.addItem('url', '#url'));
2220 this.addItem('show-retry', '#show');
2221 this.addItem('show-retry', '#retry');
2222 this.addItem('pause-resume', '#pause');
2223 this.addItem('pause-resume', '#resume');
2224 this.addItem('cancel', '#cancel');
2225 this.addItem('controlled-by', '#controlled-by a');
2226 this.addItem('danger-remove-discard', '#discard');
2227 this.addItem('restore-save', '#save');
2228 this.addItem('danger-remove-discard', '#danger-remove');
2229 this.addItem('restore-save', '#restore');
2230 assert(this.addItem('remove', '#remove'));
2231
2232 // TODO(dbeam): it would be nice to do this asynchronously (so if multiple
2233 // templates get rendered we only re-add once), but Manager#updateItem_()
2234 // relies on the DOM being re-rendered synchronously.
2235 this.eventTracker.add(this.root, 'dom-change', this.addItems.bind(this));
2236 },
2237 };
2238
2239 return {FocusRow: FocusRow};
2240 });
2241 // Copyright 2014 The Chromium Authors. All rights reserved.
2242 // Use of this source code is governed by a BSD-style license that can be
2243 // found in the LICENSE file.
2244
2245 // Action links are elements that are used to perform an in-page navigation or
2246 // action (e.g. showing a dialog).
2247 //
2248 // They look like normal anchor (<a>) tags as their text color is blue. However,
2249 // they're subtly different as they're not initially underlined (giving users a
2250 // clue that underlined links navigate while action links don't).
2251 //
2252 // Action links look very similar to normal links when hovered (hand cursor,
2253 // underlined). This gives the user an idea that clicking this link will do
2254 // something similar to navigation but in the same page.
2255 //
2256 // They can be created in JavaScript like this:
2257 //
2258 // var link = document.createElement('a', 'action-link'); // Note second arg.
2259 //
2260 // or with a constructor like this:
2261 //
2262 // var link = new ActionLink();
2263 //
2264 // They can be used easily from HTML as well, like so:
2265 //
2266 // <a is="action-link">Click me!</a>
2267 //
2268 // NOTE: <action-link> and document.createElement('action-link') don't work.
2269
2270 /**
2271 * @constructor
2272 * @extends {HTMLAnchorElement}
2273 */
2274 var ActionLink = document.registerElement('action-link', {
2275 prototype: {
2276 __proto__: HTMLAnchorElement.prototype,
2277
2278 /** @this {ActionLink} */
2279 createdCallback: function() {
2280 // Action links can start disabled (e.g. <a is="action-link" disabled>).
2281 this.tabIndex = this.disabled ? -1 : 0;
2282
2283 if (!this.hasAttribute('role'))
2284 this.setAttribute('role', 'link');
2285
2286 this.addEventListener('keydown', function(e) {
2287 if (!this.disabled && e.keyIdentifier == 'Enter') {
2288 // Schedule a click asynchronously because other 'keydown' handlers
2289 // may still run later (e.g. document.addEventListener('keydown')).
2290 // Specifically options dialogs break when this timeout isn't here.
2291 // NOTE: this affects the "trusted" state of the ensuing click. I
2292 // haven't found anything that breaks because of this (yet).
2293 window.setTimeout(this.click.bind(this), 0);
2294 }
2295 });
2296
2297 function preventDefault(e) {
2298 e.preventDefault();
2299 }
2300
2301 function removePreventDefault() {
2302 document.removeEventListener('selectstart', preventDefault);
2303 document.removeEventListener('mouseup', removePreventDefault);
2304 }
2305
2306 this.addEventListener('mousedown', function() {
2307 // This handlers strives to match the behavior of <a href="...">.
2308
2309 // While the mouse is down, prevent text selection from dragging.
2310 document.addEventListener('selectstart', preventDefault);
2311 document.addEventListener('mouseup', removePreventDefault);
2312
2313 // If focus started via mouse press, don't show an outline.
2314 if (document.activeElement != this)
2315 this.classList.add('no-outline');
2316 });
2317
2318 this.addEventListener('blur', function() {
2319 this.classList.remove('no-outline');
2320 });
2321 },
2322
2323 /** @type {boolean} */
2324 set disabled(disabled) {
2325 if (disabled)
2326 HTMLAnchorElement.prototype.setAttribute.call(this, 'disabled', '');
2327 else
2328 HTMLAnchorElement.prototype.removeAttribute.call(this, 'disabled');
2329 this.tabIndex = disabled ? -1 : 0;
2330 },
2331 get disabled() {
2332 return this.hasAttribute('disabled');
2333 },
2334
2335 /** @override */
2336 setAttribute: function(attr, val) {
2337 if (attr.toLowerCase() == 'disabled')
2338 this.disabled = true;
2339 else
2340 HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
2341 },
2342
2343 /** @override */
2344 removeAttribute: function(attr) {
2345 if (attr.toLowerCase() == 'disabled')
2346 this.disabled = false;
2347 else
2348 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
2349 },
2350 },
2351
2352 extends: 'a',
2353 });
2354 // Copyright 2015 The Chromium Authors. All rights reserved.
2355 // Use of this source code is governed by a BSD-style license that can be
2356 // found in the LICENSE file.
2357
2358 /** @typedef {{img: HTMLImageElement, url: string}} */
2359 var LoadIconRequest;
2360
2361 cr.define('downloads', function() {
2362 /**
2363 * @param {number} maxAllowed The maximum number of simultaneous downloads
2364 * allowed.
2365 * @constructor
2366 */
2367 function ThrottledIconLoader(maxAllowed) {
2368 assert(maxAllowed > 0);
2369
2370 /** @private {number} */
2371 this.maxAllowed_ = maxAllowed;
2372
2373 /** @private {!Array<!LoadIconRequest>} */
2374 this.requests_ = [];
2375 }
2376
2377 ThrottledIconLoader.prototype = {
2378 /** @private {number} */
2379 loading_: 0,
2380
2381 /**
2382 * Load the provided |url| into |img.src| after appending ?scale=.
2383 * @param {!HTMLImageElement} img An <img> to show the loaded image in.
2384 * @param {string} url A remote image URL to load.
2385 */
2386 loadScaledIcon: function(img, url) {
2387 var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
2388 if (img.src == scaledUrl)
2389 return;
2390
2391 this.requests_.push({img: img, url: scaledUrl});
2392 this.loadNextIcon_();
2393 },
2394
2395 /** @private */
2396 loadNextIcon_: function() {
2397 if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
2398 return;
2399
2400 var request = this.requests_.shift();
2401 var img = request.img;
2402
2403 img.onabort = img.onerror = img.onload = function() {
2404 this.loading_--;
2405 this.loadNextIcon_();
2406 }.bind(this);
2407
2408 this.loading_++;
2409 img.src = request.url;
2410 },
2411 };
2412
2413 return {ThrottledIconLoader: ThrottledIconLoader};
2414 });
2415 // Copyright 2014 Google Inc. All rights reserved.
2416 //
2417 // Licensed under the Apache License, Version 2.0 (the "License");
2418 // you may not use this file except in compliance with the License.
2419 // You may obtain a copy of the License at
2420 //
2421 // http://www.apache.org/licenses/LICENSE-2.0
2422 //
2423 // Unless required by applicable law or agreed to in writing, software
2424 // distributed under the License is distributed on an "AS IS" BASIS,
2425 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2426 // See the License for the specific language governing permissions and
2427 // limitations under the License.
2428
2429 !function(a,b){b["true"]=a;var c={},d={},e={},f=null;!function(a){function b(a){ if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}functi on c(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,t his._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal" ,this._easing="linear"}function d(b,d){var e=new c;return d&&(e.fill="both",e.du ration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNam es(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof e[c]||"duration" ==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==s.indexOf(b[ c]))return;if("direction"==c&&-1==t.indexOf(b[c]))return;if("playbackRate"==c&&1 !==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use A nimation.playbackRate instead."))return;e[c]=b[c]}}):e.duration=b,e}function e(a ){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function f( b,c){b=a.numericTimingToObject(b);var e=d(b,c);return e._easing=i(e.easing),e}fu nction g(a,b,c,d){return 0>a||a>1||0>c||c>1?B:function(e){function f(a,b,c){retu rn 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(0==e||1==e)return e;for(var g=0,h=1; ;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}} function h(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c% d}}function i(a){var b=z.exec(a);if(b)return g.apply(this,b.slice(1).map(Number) );var c=A.exec(a);if(c)return h(Number(c[1]),{start:u,middle:v,end:w}[c[2]]);var d=x[a];return d?d:B}function j(a){return Math.abs(k(a)/a.playbackRate)}function k(a){return a.duration*a.iterations}function l(a,b,c){return null==b?C:b<c.dela y?D:b>=c.delay+a?E:F}function m(a,b,c,d,e){switch(d){case D:return"backwards"==b ||"both"==b?0:null;case F:return c-e;case E:return"forwards"==b||"both"==b?a:nul l;case C:return null}}function n(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playb ackRate+c}function o(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&& (e.iterations+e.iterationStart)%1==0?a:c%a}function p(a,b,c,d){return 0===c?0:b= =a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function q(a,b,c,d){var e=a%2 >=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g= f?c:b-c,h=g/b;return b*d.easing(h)}function r(a,b,c){var d=l(a,b,c),e=m(a,c.fill ,b,d,c.delay);if(null===e)return null;if(0===a)return d===D?0:1;var f=c.iteratio nStart*c.duration,g=n(a,e,f,c),h=o(c.duration,k(c),g,f,c),i=p(c.duration,h,g,c); return q(i,c.duration,h,c)/c.duration}var s="backwards|forwards|both|none".split ("|"),t="reverse|alternate|alternate-reverse".split("|");c.prototype={_setMember :function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this. _effect._timing=a.normalizeTimingInput(a.normalizeTimingInput(this._effect._timi ngInput)),this._effect.activeDuration=a.calculateActiveDuration(this._effect._ti ming),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimati on())},get playbackRate(){return this._playbackRate},set delay(a){this._setMembe r("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("e ndDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember(" fill",a)},get fill(){return this._fill},set iterationStart(a){this._setMember("i terationStart",a)},get iterationStart(){return this._iterationStart},set duratio n(a){this._setMember("duration",a)},get duration(){return this._duration},set di rection(a){this._setMember("direction",a)},get direction(){return this._directio n},set easing(a){this._setMember("easing",a)},get easing(){return this._easing}, set iterations(a){this._setMember("iterations",a)},get iterations(){return this. _iterations}};var u=1,v=.5,w=0,x={ease:g(.25,.1,.25,1),"ease-in":g(.42,0,1,1),"e ase-out":g(0,0,.58,1),"ease-in-out":g(.42,0,.58,1),"step-start":h(1,u),"step-mid dle":h(1,v),"step-end":h(1,w)},y="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",z=new RegE xp("cubic-bezier\\("+y+","+y+","+y+","+y+"\\)"),A=/steps\(\s*(\d+)\s*,\s*(start| middle|end)\s*\)/,B=function(a){return a},C=0,D=1,E=2,F=3;a.cloneTimingInput=b,a .makeTiming=d,a.numericTimingToObject=e,a.normalizeTimingInput=f,a.calculateActi veDuration=j,a.calculateTimeFraction=r,a.calculatePhase=l,a.toTimingFunction=i}( c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){va r g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)} }else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset& &(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].of fset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].off set=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError ("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null! =f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be num bers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"N otSupportedError",message:"add compositing is not supported"};f="easing"==e?a.to TimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0= =d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;h<e.lengt h;h++){var i=e[h].offset;if(null!=i){if(g>i)throw{code:DOMException.INVALID_MODI FICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely sorted by offset. Sort or specify offsets."};g=i}else f=!1}return e=e.filter(fun ction(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={background:["backgrou ndImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAtt achment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderT opColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle" ,"borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth", "borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBott omWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor", "borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLef tWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius" ,"borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],bord erRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["b orderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth", "borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flex Shrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fo ntWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","margin Left"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingT op","paddingRight","paddingBottom","paddingLeft"]},f=document.createElementNS("h ttp://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={b orderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,fontSiz e:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-la rge":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth :g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.normalizeKeyframes=d}(c,f),function(a){var b={};a.isDeprecated= function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth( h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecate d and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecat ed=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Err or(b+" "+f+" no longer supported. "+d)}}(c),function(){if(document.documentEleme nt.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play| currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split( "|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}!function(a,b){funct ion c(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easi ng"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][ d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!= g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupporte dError",message:"Partial keyframes are not supported"}}return b}function d(a){va r c=[];for(var d in a)for(var e=a[d],f=0;f<e.length-1;f++){var g=e[f].offset,h=e [f+1].offset,i=e[f].value,j=e[f+1].value;g==h&&(1==h?i=j:j=i),c.push({startTime: g,endTime:h,easing:e[f].easing,property:d,interpolation:b.propertyInterpolation( d,i,j)})}return c.sort(function(a,b){return a.startTime-b.startTime}),c}b.conver tEffectInput=function(e){var f=a.normalizeKeyframes(e),g=c(f),h=d(g);return func tion(a,c){if(null!=c)h.filter(function(a){return 0>=c&&0==a.startTime||c>=1&&1== a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTim e,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpol ation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clea r(a,d)}}}(c,d,f),function(a){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}fu nction c(a,c,d){for(var e=0;e<d.length;e++){var f=d[e];b(a,c,f),/-/.test(f)&&b(a ,c,f.replace(/-(.)/g,function(a,b){return b.toUpperCase()}))}}function d(b,c,d){ if("initial"==c||"initial"==d){var g=b.replace(/-(.)/g,function(a,b){return b.to UpperCase()});"initial"==c&&(c=f[g]),"initial"==d&&(d=f[g])}for(var h=c==d?[]:e[ b],i=0;h&&i<h.length;i++){var j=h[i][0](c),k=h[i][0](d);if(void 0!==j&&void 0!== k){var l=h[i][1](j,k);if(l){var m=a.Interpolation.apply(null,l);return function( a){return 0==a?c:1==a?d:m(a)}}}}return a.Interpolation(!1,!0,function(a){return a?d:c})}var e={};a.addPropertiesHandler=c;var f={backgroundColor:"transparent",b ackgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadiu s:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"c urrentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWi dth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius: "0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0 px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto" ,left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginL eft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",min Height:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0 px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px", paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transpare nt",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto ",wordSpacing:"normal",zIndex:"auto"};a.propertyInterpolation=d}(d,f),function(a ,b){function c(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calc ulateTimeFraction(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d._isCurr ent=function(d){var e=a.calculatePhase(c,d,b);return e===PhaseActive||e===PhaseB efore},d}b.KeyframeEffect=function(d,e,f){var g,h=c(a.normalizeTimingInput(f)),i =b.convertEffectInput(e),j=function(){i(d,g)};return j._update=function(a){retur n g=h(a),null!==g},j._clear=function(){i(d,null)},j._hasSameTarget=function(a){r eturn d===a},j._isCurrent=h._isCurrent,j._totalDuration=h._totalDuration,j},b.Nu llEffect=function(a){var b=function(){a&&(a(),a=null)};return b._update=function (){return null},b._totalDuration=0,b._isCurrent=function(){return!1},b._hasSameT arget=function(){return!1},b}}(c,d,f),function(a){a.apply=function(b,c,d){b.styl e[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(d, f),function(a){window.Element.prototype.animate=function(b,c){return a.timeline. _play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number" ==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boole an"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;f<a.length; f++)e.push(b(a[f],c[f],d));return e}throw"Mismatched interpolation arguments "+a +":"+c}a.Interpolation=function(a,c,d){return function(e){return d(b(a,c,e))}}}( d,f),function(a,b){a.sequenceNumber=0;var c=function(a,b,c){this.target=a,this.c urrentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancela ble=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TA RGET,this.timeStamp=Date.now()};b.Animation=function(b){this._sequenceNumber=a.s equenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._p laybackRate=1,this._inTimeline=!0,this._finishedFlag=!1,this.onfinish=null,this. _finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._i dle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function (){this._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime ?-1:this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(th is._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function( a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._cur rentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get c urrentTime(){return this._idle||this._currentTimePending?null:this._currentTime} ,set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startT ime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._cur rentTimePending=!1,this._currentTime!=a&&(this._tickCurrentTime(a,!0),b.invalida teEffects()))},get startTime(){return this._startTime},set startTime(a){a=+a,isN aN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this. _timeline.currentTime-this._startTime)*this.playbackRate),b.invalidateEffects()) },get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._ playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null," paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.curr entTime=b)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._c urrentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},ge t _totalDuration(){return this._effect._totalDuration},get playState(){return th is._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this ._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished" :"running"},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(thi s._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null,b .invalidateEffects()),this._finishedFlag=!1,b.restart(),this._idle=!1,this._ensu reAlive()},pause:function(){this._isFinished||this._paused||this._idle||(this._c urrentTimePending=!0),this._startTime=null,this._paused=!0},finish:function(){th is._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._sta rtTime=this._totalDuration-this.currentTime,this._currentTimePending=!1)},cancel :function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this.currentTime=0, this._startTime=null,this._effect._update(null),b.invalidateEffects(),b.restart( ))},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:funct ion(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},remove EventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b );c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){var b=this._i sFinished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._curren tTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTime out(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag =b},_tick:function(a){return this._idle||this._paused||(null==this._startTime?th is.startTime=a-this._currentTime/this.playbackRate:this._isFinished||this._tickC urrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,t his._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(c,d,f), function(a,b){function c(a){var b=i;i=[],a<s.currentTime&&(a=s.currentTime),g(a) ,b.forEach(function(b){b[1](a)}),o&&g(a),f(),l=void 0}function d(a,b){return a._ sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTi me=window.performance&&performance.now?performance.now():0}function f(){p.forEac h(function(a){a()}),p.length=0}function g(a){n=!1;var c=b.timeline;c.currentTime =a,c._animations.sort(d),m=!1;var e=c._animations;c._animations=[];var f=[],g=[] ;e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._ef fect):f.push(b._effect),b._isFinished||b._paused||b._idle||(m=!0),b._inTimeline} ),p.push.apply(p,f),p.push.apply(p,g),c._animations.push.apply(c._animations,e), o=!1,m&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i =[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h (c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function( b){b[0]==a&&(b[1]=function(){})})},e.prototype={_play:function(c){c._timing=a.no rmalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timel ine=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var k,l= void 0,k=function(){return void 0==l&&(l=performance.now()),l},m=!1,n=!1;b.resta rt=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n};var o =!1;b.invalidateEffects=function(){o=!0};var p=[],q=1e3/60,r=window.getComputedS tyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable :!0,value:function(){if(o){var a=k();a-s.currentTime>0&&(s.currentTime+=q*(Math. floor((a-s.currentTime)/q)+1)),g(s.currentTime)}return f(),r.apply(this,argument s)}});var s=new e;b.timeline=s}(c,d,f),function(a){function b(a,b){var c=a.exec( b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void 0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace (/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e );if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g [1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++) if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,0>=c))break;var e=a(b.substr( 0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c& &d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b) ;return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return d?d:[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrim med(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}return""== c?d:void 0}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k =0;j>k;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.pus h(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b ](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h= 0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0] ),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(functi on(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+ =f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a .consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRep eated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(d),functio n(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);if(c)return d .inset=!0,c;var c=a.consumeLengthOrPercent(b);if(c)return d.lengths.push(c[0]),c ;var c=a.consumeColor(b);return c?(d.color=c[0],c):void 0}var d={inset:!1,length s:[],color:null},e=a.consumeRepeated(c,/^/,b);return e&&e[0].length?[d,e[1]]:voi d 0}function c(c){var d=a.consumeRepeated(b,/^,/,c);return d&&""==d[1]?d[0]:void 0}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.len gth);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.l engths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color ){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDime nsions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[ 2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1 ],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f+ +)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(v ar g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f (d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.b ind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(d),fu nction(a){function b(a){return a.toFixed(3).replace(".000","")}function c(a,b,c) {return Math.min(b,Math.max(a,c))}function d(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/ .test(a)?Number(a):void 0}function e(a,c){return[a,c,b]}function f(a,b){return 0 !=a?h(0,1/0)(a,b):void 0}function g(a,b){return[a,b,function(a){return Math.roun d(c(1,1/0,a))}]}function h(a,d){return function(e,f){return[e,f,function(e){retu rn b(c(a,d,e))}]}}function i(a,b){return[a,b,Math.round]}a.clamp=c,a.addProperti esHandler(d,h(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandle r(d,h(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(d,f,["fle x-grow","flex-shrink"]),a.addPropertiesHandler(d,g,["orphans","widows"]),a.addPr opertiesHandler(d,i,["z-index"]),a.parseNumber=d,a.mergeNumbers=e,a.numberToStri ng=b}(d,f),function(a){function b(a,b){return"visible"==a||"visible"==b?[0,1,fun ction(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b ,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.f illStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle) {e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(b,c){return[b,c,function( b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++) b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"r gba("+b.join(",")+")"}]}var d=document.createElementNS("http://www.w3.org/1999/x html","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandl er(b,c,["background-color","border-bottom-color","border-left-color","border-rig ht-color","border-top-color","color","outline-color","text-decoration-color"]),a .consumeColor=a.consumeParenthesised.bind(null,b),a.mergeColors=c}(d,f),function (a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)retu rn{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.rep lace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.repl ace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\ s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*/]N/g,/(N|D)O\1/g,/\((N|D)\)/g], g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g" ),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(a,b){return e(a,b,! 0)}function e(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&& f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c= Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+ c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,n ew RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|g rad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a. consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.con sumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consum eRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l( a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o= a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPrope rtiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom -width","border-image-width","border-left-width","border-right-width","border-to p-width","flex-basis","font-size","height","line-height","max-height","max-width ","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radi us","border-bottom-right-radius","border-top-left-radius","border-top-right-radi us","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right ","margin-top","min-height","min-width","outline-offset","padding-bottom","paddi ng-left","padding-right","padding-top","perspective","right","shape-margin","tex t-indent","top","vertical-align","word-spacing"])}(d,f),function(a){function b(b ){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){va r d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consum eToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeT oken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(b,c){ return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"au to";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}func tion e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d," , ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"])}(d,f),func tion(a){function b(a){return function(b){var c=0;return a.map(function(a){return a===j?b[c++]:a})}}function c(a){return a}function d(b){if(b=b.toLowerCase().tri m(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);) {if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=m[g];if(!h)return;var i =c[2].split(","),j=h[0];if(j.length<i.length)return;for(var n=[],o=0;o<j.length; o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?l:a.parseAngl e(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()] (q):{a:l,n:n[0],t:k}[r],void 0===p)return;n.push(p)}if(e.push({t:g,d:n}),d.lastI ndex==b.length)return e}}function e(a){return a.toFixed(6).replace(".000000","") }function f(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeM atrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var f=a. makeMatrixDecomposition(c)}return null==d[0]||null==f[0]?[[!1],[!0],function(a){ return a?c[0].d:b[0].d}]:(d[0].push(0),f[0].push(1),[d,f,function(b){var c=a.qua t(d[0][3],f[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(e).join (",");return h}])}function g(a){return a.replace(/[xy]/,"")}function h(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function i(b,c){var d=a.makeMatrixDecomposition& &!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var i=0;i<b.leng th;i++){var j=b[i].t,k=b[i].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(f unction(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return b})})}}var n=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"== a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.l ength){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else fo r(var i=0;i<b.length;i++){var j,s=b[i].t,t=c[i].t,u=b[i].d,v=c[i].d,w=m[s],x=m[t ];if(n(s,t)){if(!d)return;var r=f([b[i]],[c[i]]);o.push(r[0]),p.push(r[1]),q.pus h(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&g(s)==g(t))j=g(s),u=w[ 2](u),v=x[2](v);else{if(!w[1]||!x[1]||h(s)!=h(t)){if(!d)return;var r=f(b,c);o=[r [0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=h(s),u=w[1](u),v=x[1](v)}for(var y=[ ],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mer geDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z) ,q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(functi on(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix "==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).jo in(" ")}]}var j=null,k={px:0},l={deg:0},m={matrix:["NNNNNN",[j,j,0,0,j,j,0,0,0,0 ,1,0,j,j,0,1],c],matrix3d:["NNNNNNNNNNNNNNNN",c],rotate:["A"],rotatex:["A"],rota tey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",b([j,j,1 ]),c],scalex:["N",b([j,1,1]),b([j,1])],scaley:["N",b([1,j,1]),b([1,j])],scalez:[ "N",b([1,1,j])],scale3d:["NNN",c],skew:["Aa",null,c],skewx:["A",null,b([j,l])],s kewy:["A",null,b([l,j])],translate:["Tt",b([j,j,k]),c],translatex:["T",b([j,k,k] ),b([j,k])],translatey:["T",b([k,j,k]),b([k,j])],translatez:["L",b([k,k,j])],tra nslate3d:["TTL",c]};a.addPropertiesHandler(d,i,["transform"])}(d,f),function(a){ function b(a,b){b.concat([a]).forEach(function(b){b in document.documentElement. style&&(c[a]=b)})}var c={};b("transform",["webkitTransform","msTransform"]),b("t ransformOrigin",["webkitTransformOrigin"]),b("perspective",["webkitPerspective"] ),b("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){ return c[a]||a}}(d,f)}(),!function(a,b){function c(a){var b=window.document.time line;b.currentTime=a,b._discardAnimations(),0==b._animations.length?e=!1:request AnimationFrame(c)}var d=window.requestAnimationFrame;window.requestAnimationFram e=function(a){return d(function(b){window.document.timeline._updateAnimationsPro mises(),a(b),window.document.timeline._updateAnimationsPromises()})},b.Animation Timeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTime line.prototype={getAnimations:function(){return this._discardAnimations(),this._ animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromise s=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_dis cardAnimations:function(){this._updateAnimationsPromises(),this._animations=this ._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playSta te})},_play:function(a){var c=new b.Animation(a,this);return this._animations.pu sh(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c ._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var e=!1;b.restartWebAnimationsNextTick=function(){e||(e=!0,requestAnimationFrame(c) )};var f=new b.AnimationTimeline;b.timeline=f;try{Object.defineProperty(window.d ocument,"timeline",{configurable:!0,get:function(){return f}})}catch(g){}try{win dow.document.timeline=f}catch(g){}}(c,e,f),function(a,b){b.animationsWithPromise s=[],b.Animation=function(b,c){if(this.effect=b,b&&(b._animation=this),!c)throw new Error("Animation with null timeline is not supported");this._timeline=c,this ._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGro up=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._ol dPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),th is._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=th is._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?( this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolve ReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise& &b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0): "finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromi se=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finished Promise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d ,e,f=this._animation?!0:!1;f&&(a=this.playbackRate,c=this._paused,d=this.startTi me,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,thi s._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)& &(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindA nimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect|| this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnim ationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effe ct._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate= a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&& (this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises()
2430 },_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this. effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChil dren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration (c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&thi s._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b] ._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChild Animations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.d elay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var d=window.document.timeline._play(c);this._childAnimations.push(d),d.playbackRate =this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,t his._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.gr oupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.s tartTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.s tartTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)}, get timeline(){return this._timeline},get playState(){return this._animation?thi s._animation.playState:"idle"},get finished(){return window.Promise?(this._finis hedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromise s.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinish edPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOM Exception.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState &&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animatio n Promises require JavaScript Promise constructor"),null)},get ready(){return wi ndow.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b .animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){ this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function( ){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==t his.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("A nimation Promises require JavaScript Promise constructor"),null)},get onfinish() {return this._onfinish},set onfinish(a){"function"==typeof a?(this._onfinish=a,t his._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this)):(t his._animation.onfinish=a,this.onfinish=this._animation.onfinish)},get currentTi me(){this._updatePromises();var a=this._animation.currentTime;return this._updat ePromises(),a},set currentTime(a){this._updatePromises(),this._animation.current Time=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachC hild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){r eturn this._animation.startTime},set startTime(a){this._updatePromises(),this._a nimation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(), this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get p laybackRate(){return this._animation.playbackRate},set playbackRate(a){this._upd atePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEac hChild(function(b){b.playbackRate=a}),"paused"!=this.playState&&"idle"!=this.pla yState&&this.play(),null!==b&&(this.currentTime=b),this._updatePromises()},play: function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==thi s._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),thi s._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.curr entTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this ._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._ani mation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this. _paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this ._animation.finish(),this._register(),this._updatePromises()},cancel:function(){ this._updatePromises(),this._animation.cancel(),this._register(),this._removeChi ldAnimations(),this._updatePromises()},reverse:function(){this._updatePromises() ;var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a) {a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventLi stener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this, b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)}, removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wr apper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;) this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this. effect.children&&this._childAnimations.length<this.effect.children.length&&this. _constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(thi s,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration )}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.curre ntTime;null!==e&&(e=a.calculateTimeFraction(a.calculateActiveDuration(d),e,d)),( null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animatio n}(c,e,f),function(a,b){function c(b){this._frames=a.normalizeKeyframes(b)}funct ion d(){for(var a=!1;h.length;){var b=h.shift();b._updateChildren(),a=!0}return a}var e=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)e(a.children[ b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._pa rent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._p arent.children.indexOf(d),1),d._parent=null,e(d)):d._animation&&d._animation.eff ect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d ._animation._callback&&(d._animation._callback._animation=null),d._animation._re buildUnderlyingAnimation(),e(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.Keyfr ameEffect=function(b,d,e){return this.target=b,this._parent=null,e=a.numericTimi ngToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTi mingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"funct ion"==typeof d?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeE ffect.onsample instead."),this._normalizedKeyframes=d):this._normalizedKeyframes =new c(d),this._keyframes=d,this.activeDuration=a.calculateActiveDuration(this._ timing),this},b.KeyframeEffect.prototype={getFrames:function(){return"function"= =typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyf rames._frames},set onsample(a){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this ._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFr ames())throw new Error("Cloning custom effects is not supported.");var b=new Key frameEffect(this.target,[],a.cloneTimingInput(this._timingInput));return b._norm alizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove :function(){b.removeMulti([this])}};var f=Element.prototype.animate;Element.prot otype.animate=function(a,c){return b.timeline._play(new b.KeyframeEffect(this,a, c))};var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newU nderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||g,c=a._key frames;"function"==typeof c&&(c=[]);var d=a._timingInput}else var b=g,c=[],d=0;r eturn f.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&" function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect( a)};var h=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==h. length&&requestAnimationFrame(d),h.push(a))};var i=window.getComputedStyle;Objec t.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value: function(){window.document.timeline._updateAnimationsPromises();var a=i.apply(th is,arguments);return d()&&(a=i.apply(this,arguments)),window.document.timeline._ updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.El ement.prototype.getAnimations=function(){return document.timeline.getAnimations( ).filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))} }(c,e,f),function(a,b){function c(a){a._registered||(a._registered=!0,f.push(a), g||(g=!0,requestAnimationFrame(d)))}function d(){var a=f;f=[],a.sort(function(a, b){return a._sequenceNumber-b._sequenceNumber}),a=a.filter(function(a){a();var b =a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a ._registered=!1),a._registered}),f.push.apply(f,a),f.length?(g=!0,requestAnimati onFrame(d)):g=!1}var e=(document.createElementNS("http://www.w3.org/1999/xhtml", "div"),0);b.bindAnimationForCustomEffect=function(b){var d,f=b.effect.target,g=" function"==typeof b.effect.getFrames();d=g?b.effect.getFrames():b.effect._onsamp le;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var c=j._animation?j._animation.currentTime:null;null!==c&&(c=a.calculateTimeFracti on(a.calculateActiveDuration(h),c,h),isNaN(c)&&(c=null)),c!==i&&(g?d(c,f,b.effec t):d(c,b.effect,b.effect._animation)),i=c};j._animation=b,j._registered=!1,j._se quenceNumber=e++,b._callback=j,c(j)};var f=[],g=!1;b.Animation.prototype._regist er=function(){this._callback&&c(this._callback)}}(c,e,f),function(a,b){function c(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function d(b,c){ this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericT imingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normaliz eTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this, "auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}wind ow.SequenceEffect=function(){d.apply(this,arguments)},window.GroupEffect=functio n(){d.apply(this,arguments)},d.prototype={_isAncestor:function(a){for(var b=this ;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a =this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._ parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent :function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_put Child:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prep end an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type :DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for( var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);th is._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)} ,prepend:function(){this._putChild(arguments,!1)},get parent(){return this._pare nt},get firstChild(){return this.children.length?this.children[0]:null},get last Child(){return this.children.length?this.children[this.children.length-1]:null}, clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this .children.length;d++)c.push(this.children[d].clone());return this instanceof Gro upEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.remov eMulti([this])}},window.SequenceEffect.prototype=Object.create(d.prototype),Obje ct.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function (){var a=0;return this.children.forEach(function(b){a+=c(b)}),Math.max(a,0)}}),w indow.GroupEffect.prototype=Object.create(d.prototype),Object.defineProperty(win dow.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.c hildren.forEach(function(b){a=Math.max(a,c(b))}),a}}),b.newUnderlyingAnimationFo rGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;return c&&"pendin g"!=c.playState&&c.effect?null==b?void c._removeChildAnimations():0==b&&c.playba ckRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateTimeFract ion(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(funct ion(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0:void 0},g=new KeyframeEffect(null,[],c._timing);return g.onsample=f,d=b.timeline._play(g)},b.b indAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitSt artTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChil dDuration=c}(c,e,f)}({},function(){return this}());
2431 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2432 // Use of this source code is governed by a BSD-style license that can be
2433 // found in the LICENSE file.
2434
2435 // <include src="../../../../ui/webui/resources/js/i18n_template_no_process.js">
2436
2437 i18nTemplate.process(document, loadTimeData);
2438 (function () {
2439 function resolve() {
2440 document.body.removeAttribute('unresolved');
2441 }
2442 if (window.WebComponents) {
2443 addEventListener('WebComponentsReady', resolve);
2444 } else {
2445 if (document.readyState === 'interactive' || document.readyState === 'complete') {
2446 resolve();
2447 } else {
2448 addEventListener('DOMContentLoaded', resolve);
2449 }
2450 }
2451 }());
2452 window.Polymer = {
2453 Settings: function () {
2454 var user = window.Polymer || {};
2455 location.search.slice(1).split('&').forEach(function (o) {
2456 o = o.split('=');
2457 o[0] && (user[o[0]] = o[1] || true);
2458 });
2459 var wantShadow = user.dom === 'shadow';
2460 var hasShadow = Boolean(Element.prototype.createShadowRoot);
2461 var nativeShadow = hasShadow && !window.ShadowDOMPolyfill;
2462 var useShadow = wantShadow && hasShadow;
2463 var hasNativeImports = Boolean('import' in document.createElement('link'));
2464 var useNativeImports = hasNativeImports;
2465 var useNativeCustomElements = !window.CustomElements || window.CustomElements.us eNative;
2466 return {
2467 wantShadow: wantShadow,
2468 hasShadow: hasShadow,
2469 nativeShadow: nativeShadow,
2470 useShadow: useShadow,
2471 useNativeShadow: useShadow && nativeShadow,
2472 useNativeImports: useNativeImports,
2473 useNativeCustomElements: useNativeCustomElements
2474 };
2475 }()
2476 };
2477 (function () {
2478 var userPolymer = window.Polymer;
2479 window.Polymer = function (prototype) {
2480 if (typeof prototype === 'function') {
2481 prototype = prototype.prototype;
2482 }
2483 if (!prototype) {
2484 prototype = {};
2485 }
2486 var factory = desugar(prototype);
2487 prototype = factory.prototype;
2488 var options = { prototype: prototype };
2489 if (prototype.extends) {
2490 options.extends = prototype.extends;
2491 }
2492 Polymer.telemetry._registrate(prototype);
2493 document.registerElement(prototype.is, options);
2494 return factory;
2495 };
2496 var desugar = function (prototype) {
2497 var base = Polymer.Base;
2498 if (prototype.extends) {
2499 base = Polymer.Base._getExtendedPrototype(prototype.extends);
2500 }
2501 prototype = Polymer.Base.chainObject(prototype, base);
2502 prototype.registerCallback();
2503 return prototype.constructor;
2504 };
2505 window.Polymer = Polymer;
2506 if (userPolymer) {
2507 for (var i in userPolymer) {
2508 Polymer[i] = userPolymer[i];
2509 }
2510 }
2511 Polymer.Class = desugar;
2512 }());
2513 Polymer.telemetry = {
2514 registrations: [],
2515 _regLog: function (prototype) {
2516 console.log('[' + prototype.is + ']: registered');
2517 },
2518 _registrate: function (prototype) {
2519 this.registrations.push(prototype);
2520 Polymer.log && this._regLog(prototype);
2521 },
2522 dumpRegistrations: function () {
2523 this.registrations.forEach(this._regLog);
2524 }
2525 };
2526 Object.defineProperty(window, 'currentImport', {
2527 enumerable: true,
2528 configurable: true,
2529 get: function () {
2530 return (document._currentScript || document.currentScript).ownerDocument;
2531 }
2532 });
2533 Polymer.RenderStatus = {
2534 _ready: false,
2535 _callbacks: [],
2536 whenReady: function (cb) {
2537 if (this._ready) {
2538 cb();
2539 } else {
2540 this._callbacks.push(cb);
2541 }
2542 },
2543 _makeReady: function () {
2544 this._ready = true;
2545 this._callbacks.forEach(function (cb) {
2546 cb();
2547 });
2548 this._callbacks = [];
2549 },
2550 _catchFirstRender: function () {
2551 requestAnimationFrame(function () {
2552 Polymer.RenderStatus._makeReady();
2553 });
2554 }
2555 };
2556 if (window.HTMLImports) {
2557 HTMLImports.whenReady(function () {
2558 Polymer.RenderStatus._catchFirstRender();
2559 });
2560 } else {
2561 Polymer.RenderStatus._catchFirstRender();
2562 }
2563 Polymer.ImportStatus = Polymer.RenderStatus;
2564 Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady;
2565 Polymer.Base = {
2566 __isPolymerInstance__: true,
2567 _addFeature: function (feature) {
2568 this.extend(this, feature);
2569 },
2570 registerCallback: function () {
2571 this._desugarBehaviors();
2572 this._doBehavior('beforeRegister');
2573 this._registerFeatures();
2574 this._doBehavior('registered');
2575 },
2576 createdCallback: function () {
2577 Polymer.telemetry.instanceCount++;
2578 this.root = this;
2579 this._doBehavior('created');
2580 this._initFeatures();
2581 },
2582 attachedCallback: function () {
2583 Polymer.RenderStatus.whenReady(function () {
2584 this.isAttached = true;
2585 this._doBehavior('attached');
2586 }.bind(this));
2587 },
2588 detachedCallback: function () {
2589 this.isAttached = false;
2590 this._doBehavior('detached');
2591 },
2592 attributeChangedCallback: function (name) {
2593 this._attributeChangedImpl(name);
2594 this._doBehavior('attributeChanged', arguments);
2595 },
2596 _attributeChangedImpl: function (name) {
2597 this._setAttributeToProperty(this, name);
2598 },
2599 extend: function (prototype, api) {
2600 if (prototype && api) {
2601 Object.getOwnPropertyNames(api).forEach(function (n) {
2602 this.copyOwnProperty(n, api, prototype);
2603 }, this);
2604 }
2605 return prototype || api;
2606 },
2607 mixin: function (target, source) {
2608 for (var i in source) {
2609 target[i] = source[i];
2610 }
2611 return target;
2612 },
2613 copyOwnProperty: function (name, source, target) {
2614 var pd = Object.getOwnPropertyDescriptor(source, name);
2615 if (pd) {
2616 Object.defineProperty(target, name, pd);
2617 }
2618 },
2619 _log: console.log.apply.bind(console.log, console),
2620 _warn: console.warn.apply.bind(console.warn, console),
2621 _error: console.error.apply.bind(console.error, console),
2622 _logf: function () {
2623 return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(argum ents, 0));
2624 }
2625 };
2626 Polymer.Base._logPrefix = function () {
2627 var color = window.chrome || /firefox/i.test(navigator.userAgent);
2628 return color ? [
2629 '%c[%s::%s]:',
2630 'font-weight: bold; background-color:#EEEE00;'
2631 ] : ['[%s::%s]:'];
2632 }();
2633 Polymer.Base.chainObject = function (object, inherited) {
2634 if (object && inherited && object !== inherited) {
2635 if (!Object.__proto__) {
2636 object = Polymer.Base.extend(Object.create(inherited), object);
2637 }
2638 object.__proto__ = inherited;
2639 }
2640 return object;
2641 };
2642 Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype);
2643 if (window.CustomElements) {
2644 Polymer.instanceof = CustomElements.instanceof;
2645 } else {
2646 Polymer.instanceof = function (obj, ctor) {
2647 return obj instanceof ctor;
2648 };
2649 }
2650 Polymer.isInstance = function (obj) {
2651 return Boolean(obj && obj.__isPolymerInstance__);
2652 };
2653 Polymer.telemetry.instanceCount = 0;
2654 (function () {
2655 var modules = {};
2656 var lcModules = {};
2657 var findModule = function (id) {
2658 return modules[id] || lcModules[id.toLowerCase()];
2659 };
2660 var DomModule = function () {
2661 return document.createElement('dom-module');
2662 };
2663 DomModule.prototype = Object.create(HTMLElement.prototype);
2664 Polymer.Base.extend(DomModule.prototype, {
2665 constructor: DomModule,
2666 createdCallback: function () {
2667 this.register();
2668 },
2669 register: function (id) {
2670 var id = id || this.id || this.getAttribute('name') || this.getAttribute('is');
2671 if (id) {
2672 this.id = id;
2673 modules[id] = this;
2674 lcModules[id.toLowerCase()] = this;
2675 }
2676 },
2677 import: function (id, selector) {
2678 if (id) {
2679 var m = findModule(id);
2680 if (!m) {
2681 forceDocumentUpgrade();
2682 m = findModule(id);
2683 }
2684 if (m && selector) {
2685 m = m.querySelector(selector);
2686 }
2687 return m;
2688 }
2689 }
2690 });
2691 var cePolyfill = window.CustomElements && !CustomElements.useNative;
2692 document.registerElement('dom-module', DomModule);
2693 function forceDocumentUpgrade() {
2694 if (cePolyfill) {
2695 var script = document._currentScript || document.currentScript;
2696 var doc = script && script.ownerDocument;
2697 if (doc) {
2698 CustomElements.upgradeAll(doc);
2699 }
2700 }
2701 }
2702 }());
2703 Polymer.Base._addFeature({
2704 _prepIs: function () {
2705 if (!this.is) {
2706 var module = (document._currentScript || document.currentScript).parentNode;
2707 if (module.localName === 'dom-module') {
2708 var id = module.id || module.getAttribute('name') || module.getAttribute('is');
2709 this.is = id;
2710 }
2711 }
2712 if (this.is) {
2713 this.is = this.is.toLowerCase();
2714 }
2715 }
2716 });
2717 Polymer.Base._addFeature({
2718 behaviors: [],
2719 _desugarBehaviors: function () {
2720 if (this.behaviors.length) {
2721 this.behaviors = this._desugarSomeBehaviors(this.behaviors);
2722 }
2723 },
2724 _desugarSomeBehaviors: function (behaviors) {
2725 behaviors = this._flattenBehaviorsList(behaviors);
2726 for (var i = behaviors.length - 1; i >= 0; i--) {
2727 this._mixinBehavior(behaviors[i]);
2728 }
2729 return behaviors;
2730 },
2731 _flattenBehaviorsList: function (behaviors) {
2732 var flat = [];
2733 behaviors.forEach(function (b) {
2734 if (b instanceof Array) {
2735 flat = flat.concat(this._flattenBehaviorsList(b));
2736 } else if (b) {
2737 flat.push(b);
2738 } else {
2739 this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for miss ing or 404 import'));
2740 }
2741 }, this);
2742 return flat;
2743 },
2744 _mixinBehavior: function (b) {
2745 Object.getOwnPropertyNames(b).forEach(function (n) {
2746 switch (n) {
2747 case 'hostAttributes':
2748 case 'registered':
2749 case 'properties':
2750 case 'observers':
2751 case 'listeners':
2752 case 'created':
2753 case 'attached':
2754 case 'detached':
2755 case 'attributeChanged':
2756 case 'configure':
2757 case 'ready':
2758 break;
2759 default:
2760 if (!this.hasOwnProperty(n)) {
2761 this.copyOwnProperty(n, b, this);
2762 }
2763 break;
2764 }
2765 }, this);
2766 },
2767 _prepBehaviors: function () {
2768 this._prepFlattenedBehaviors(this.behaviors);
2769 },
2770 _prepFlattenedBehaviors: function (behaviors) {
2771 for (var i = 0, l = behaviors.length; i < l; i++) {
2772 this._prepBehavior(behaviors[i]);
2773 }
2774 this._prepBehavior(this);
2775 },
2776 _doBehavior: function (name, args) {
2777 this.behaviors.forEach(function (b) {
2778 this._invokeBehavior(b, name, args);
2779 }, this);
2780 this._invokeBehavior(this, name, args);
2781 },
2782 _invokeBehavior: function (b, name, args) {
2783 var fn = b[name];
2784 if (fn) {
2785 fn.apply(this, args || Polymer.nar);
2786 }
2787 },
2788 _marshalBehaviors: function () {
2789 this.behaviors.forEach(function (b) {
2790 this._marshalBehavior(b);
2791 }, this);
2792 this._marshalBehavior(this);
2793 }
2794 });
2795 Polymer.Base._addFeature({
2796 _getExtendedPrototype: function (tag) {
2797 return this._getExtendedNativePrototype(tag);
2798 },
2799 _nativePrototypes: {},
2800 _getExtendedNativePrototype: function (tag) {
2801 var p = this._nativePrototypes[tag];
2802 if (!p) {
2803 var np = this.getNativePrototype(tag);
2804 p = this.extend(Object.create(np), Polymer.Base);
2805 this._nativePrototypes[tag] = p;
2806 }
2807 return p;
2808 },
2809 getNativePrototype: function (tag) {
2810 return Object.getPrototypeOf(document.createElement(tag));
2811 }
2812 });
2813 Polymer.Base._addFeature({
2814 _prepConstructor: function () {
2815 this._factoryArgs = this.extends ? [
2816 this.extends,
2817 this.is
2818 ] : [this.is];
2819 var ctor = function () {
2820 return this._factory(arguments);
2821 };
2822 if (this.hasOwnProperty('extends')) {
2823 ctor.extends = this.extends;
2824 }
2825 Object.defineProperty(this, 'constructor', {
2826 value: ctor,
2827 writable: true,
2828 configurable: true
2829 });
2830 ctor.prototype = this;
2831 },
2832 _factory: function (args) {
2833 var elt = document.createElement.apply(document, this._factoryArgs);
2834 if (this.factoryImpl) {
2835 this.factoryImpl.apply(elt, args);
2836 }
2837 return elt;
2838 }
2839 });
2840 Polymer.nob = Object.create(null);
2841 Polymer.Base._addFeature({
2842 properties: {},
2843 getPropertyInfo: function (property) {
2844 var info = this._getPropertyInfo(property, this.properties);
2845 if (!info) {
2846 this.behaviors.some(function (b) {
2847 return info = this._getPropertyInfo(property, b.properties);
2848 }, this);
2849 }
2850 return info || Polymer.nob;
2851 },
2852 _getPropertyInfo: function (property, properties) {
2853 var p = properties && properties[property];
2854 if (typeof p === 'function') {
2855 p = properties[property] = { type: p };
2856 }
2857 if (p) {
2858 p.defined = true;
2859 }
2860 return p;
2861 }
2862 });
2863 Polymer.CaseMap = {
2864 _caseMap: {},
2865 dashToCamelCase: function (dash) {
2866 var mapped = Polymer.CaseMap._caseMap[dash];
2867 if (mapped) {
2868 return mapped;
2869 }
2870 if (dash.indexOf('-') < 0) {
2871 return Polymer.CaseMap._caseMap[dash] = dash;
2872 }
2873 return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) {
2874 return m[1].toUpperCase();
2875 });
2876 },
2877 camelToDashCase: function (camel) {
2878 var mapped = Polymer.CaseMap._caseMap[camel];
2879 if (mapped) {
2880 return mapped;
2881 }
2882 return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function (g) {
2883 return g[0] + '-' + g[1].toLowerCase();
2884 });
2885 }
2886 };
2887 Polymer.Base._addFeature({
2888 _prepAttributes: function () {
2889 this._aggregatedAttributes = {};
2890 },
2891 _addHostAttributes: function (attributes) {
2892 if (attributes) {
2893 this.mixin(this._aggregatedAttributes, attributes);
2894 }
2895 },
2896 _marshalHostAttributes: function () {
2897 this._applyAttributes(this, this._aggregatedAttributes);
2898 },
2899 _applyAttributes: function (node, attr$) {
2900 for (var n in attr$) {
2901 if (!this.hasAttribute(n) && n !== 'class') {
2902 this.serializeValueToAttribute(attr$[n], n, this);
2903 }
2904 }
2905 },
2906 _marshalAttributes: function () {
2907 this._takeAttributesToModel(this);
2908 },
2909 _takeAttributesToModel: function (model) {
2910 for (var i = 0, l = this.attributes.length; i < l; i++) {
2911 this._setAttributeToProperty(model, this.attributes[i].name);
2912 }
2913 },
2914 _setAttributeToProperty: function (model, attrName) {
2915 if (!this._serializing) {
2916 var propName = Polymer.CaseMap.dashToCamelCase(attrName);
2917 var info = this.getPropertyInfo(propName);
2918 if (info.defined || this._propertyEffects && this._propertyEffects[propName]) {
2919 var val = this.getAttribute(attrName);
2920 model[propName] = this.deserialize(val, info.type);
2921 }
2922 }
2923 },
2924 _serializing: false,
2925 reflectPropertyToAttribute: function (name) {
2926 this._serializing = true;
2927 this.serializeValueToAttribute(this[name], Polymer.CaseMap.camelToDashCase(name) );
2928 this._serializing = false;
2929 },
2930 serializeValueToAttribute: function (value, attribute, node) {
2931 var str = this.serialize(value);
2932 (node || this)[str === undefined ? 'removeAttribute' : 'setAttribute'](attribute , str);
2933 },
2934 deserialize: function (value, type) {
2935 switch (type) {
2936 case Number:
2937 value = Number(value);
2938 break;
2939 case Boolean:
2940 value = value !== null;
2941 break;
2942 case Object:
2943 try {
2944 value = JSON.parse(value);
2945 } catch (x) {
2946 }
2947 break;
2948 case Array:
2949 try {
2950 value = JSON.parse(value);
2951 } catch (x) {
2952 value = null;
2953 console.warn('Polymer::Attributes: couldn`t decode Array as JSON');
2954 }
2955 break;
2956 case Date:
2957 value = new Date(value);
2958 break;
2959 case String:
2960 default:
2961 break;
2962 }
2963 return value;
2964 },
2965 serialize: function (value) {
2966 switch (typeof value) {
2967 case 'boolean':
2968 return value ? '' : undefined;
2969 case 'object':
2970 if (value instanceof Date) {
2971 return value;
2972 } else if (value) {
2973 try {
2974 return JSON.stringify(value);
2975 } catch (x) {
2976 return '';
2977 }
2978 }
2979 default:
2980 return value != null ? value : undefined;
2981 }
2982 }
2983 });
2984 Polymer.Base._addFeature({
2985 _setupDebouncers: function () {
2986 this._debouncers = {};
2987 },
2988 debounce: function (jobName, callback, wait) {
2989 return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[ jobName], callback, wait);
2990 },
2991 isDebouncerActive: function (jobName) {
2992 var debouncer = this._debouncers[jobName];
2993 return debouncer && debouncer.finish;
2994 },
2995 flushDebouncer: function (jobName) {
2996 var debouncer = this._debouncers[jobName];
2997 if (debouncer) {
2998 debouncer.complete();
2999 }
3000 },
3001 cancelDebouncer: function (jobName) {
3002 var debouncer = this._debouncers[jobName];
3003 if (debouncer) {
3004 debouncer.stop();
3005 }
3006 }
3007 });
3008 Polymer.version = '1.1.4';
3009 Polymer.Base._addFeature({
3010 _registerFeatures: function () {
3011 this._prepIs();
3012 this._prepAttributes();
3013 this._prepBehaviors();
3014 this._prepConstructor();
3015 },
3016 _prepBehavior: function (b) {
3017 this._addHostAttributes(b.hostAttributes);
3018 },
3019 _marshalBehavior: function (b) {
3020 },
3021 _initFeatures: function () {
3022 this._marshalHostAttributes();
3023 this._setupDebouncers();
3024 this._marshalBehaviors();
3025 }
3026 });
3027 Polymer.Base._addFeature({
3028 _prepTemplate: function () {
3029 this._template = this._template || Polymer.DomModule.import(this.is, 'template') ;
3030 if (this._template && this._template.hasAttribute('is')) {
3031 this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not be a type-extension, found', this._template, 'Move inside simple <template>.')) ;
3032 }
3033 if (this._template && !this._template.content && HTMLTemplateElement.bootstrap) {
3034 HTMLTemplateElement.decorate(this._template);
3035 HTMLTemplateElement.bootstrap(this._template.content);
3036 }
3037 },
3038 _stampTemplate: function () {
3039 if (this._template) {
3040 this.root = this.instanceTemplate(this._template);
3041 }
3042 },
3043 instanceTemplate: function (template) {
3044 var dom = document.importNode(template._content || template.content, true);
3045 return dom;
3046 }
3047 });
3048 (function () {
3049 var baseAttachedCallback = Polymer.Base.attachedCallback;
3050 Polymer.Base._addFeature({
3051 _hostStack: [],
3052 ready: function () {
3053 },
3054 _pushHost: function (host) {
3055 this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.l ength - 1];
3056 if (host && host._clients) {
3057 host._clients.push(this);
3058 }
3059 this._beginHost();
3060 },
3061 _beginHost: function () {
3062 Polymer.Base._hostStack.push(this);
3063 if (!this._clients) {
3064 this._clients = [];
3065 }
3066 },
3067 _popHost: function () {
3068 Polymer.Base._hostStack.pop();
3069 },
3070 _tryReady: function () {
3071 if (this._canReady()) {
3072 this._ready();
3073 }
3074 },
3075 _canReady: function () {
3076 return !this.dataHost || this.dataHost._clientsReadied;
3077 },
3078 _ready: function () {
3079 this._beforeClientsReady();
3080 this._setupRoot();
3081 this._readyClients();
3082 this._afterClientsReady();
3083 this._readySelf();
3084 },
3085 _readyClients: function () {
3086 this._beginDistribute();
3087 var c$ = this._clients;
3088 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
3089 c._ready();
3090 }
3091 this._finishDistribute();
3092 this._clientsReadied = true;
3093 this._clients = null;
3094 },
3095 _readySelf: function () {
3096 this._doBehavior('ready');
3097 this._readied = true;
3098 if (this._attachedPending) {
3099 this._attachedPending = false;
3100 this.attachedCallback();
3101 }
3102 },
3103 _beforeClientsReady: function () {
3104 },
3105 _afterClientsReady: function () {
3106 },
3107 _beforeAttached: function () {
3108 },
3109 attachedCallback: function () {
3110 if (this._readied) {
3111 this._beforeAttached();
3112 baseAttachedCallback.call(this);
3113 } else {
3114 this._attachedPending = true;
3115 }
3116 }
3117 });
3118 }());
3119 Polymer.ArraySplice = function () {
3120 function newSplice(index, removed, addedCount) {
3121 return {
3122 index: index,
3123 removed: removed,
3124 addedCount: addedCount
3125 };
3126 }
3127 var EDIT_LEAVE = 0;
3128 var EDIT_UPDATE = 1;
3129 var EDIT_ADD = 2;
3130 var EDIT_DELETE = 3;
3131 function ArraySplice() {
3132 }
3133 ArraySplice.prototype = {
3134 calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, o ldEnd) {
3135 var rowCount = oldEnd - oldStart + 1;
3136 var columnCount = currentEnd - currentStart + 1;
3137 var distances = new Array(rowCount);
3138 for (var i = 0; i < rowCount; i++) {
3139 distances[i] = new Array(columnCount);
3140 distances[i][0] = i;
3141 }
3142 for (var j = 0; j < columnCount; j++)
3143 distances[0][j] = j;
3144 for (var i = 1; i < rowCount; i++) {
3145 for (var j = 1; j < columnCount; j++) {
3146 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
3147 distances[i][j] = distances[i - 1][j - 1];
3148 else {
3149 var north = distances[i - 1][j] + 1;
3150 var west = distances[i][j - 1] + 1;
3151 distances[i][j] = north < west ? north : west;
3152 }
3153 }
3154 }
3155 return distances;
3156 },
3157 spliceOperationsFromEditDistances: function (distances) {
3158 var i = distances.length - 1;
3159 var j = distances[0].length - 1;
3160 var current = distances[i][j];
3161 var edits = [];
3162 while (i > 0 || j > 0) {
3163 if (i == 0) {
3164 edits.push(EDIT_ADD);
3165 j--;
3166 continue;
3167 }
3168 if (j == 0) {
3169 edits.push(EDIT_DELETE);
3170 i--;
3171 continue;
3172 }
3173 var northWest = distances[i - 1][j - 1];
3174 var west = distances[i - 1][j];
3175 var north = distances[i][j - 1];
3176 var min;
3177 if (west < north)
3178 min = west < northWest ? west : northWest;
3179 else
3180 min = north < northWest ? north : northWest;
3181 if (min == northWest) {
3182 if (northWest == current) {
3183 edits.push(EDIT_LEAVE);
3184 } else {
3185 edits.push(EDIT_UPDATE);
3186 current = northWest;
3187 }
3188 i--;
3189 j--;
3190 } else if (min == west) {
3191 edits.push(EDIT_DELETE);
3192 i--;
3193 current = west;
3194 } else {
3195 edits.push(EDIT_ADD);
3196 j--;
3197 current = north;
3198 }
3199 }
3200 edits.reverse();
3201 return edits;
3202 },
3203 calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd) {
3204 var prefixCount = 0;
3205 var suffixCount = 0;
3206 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
3207 if (currentStart == 0 && oldStart == 0)
3208 prefixCount = this.sharedPrefix(current, old, minLength);
3209 if (currentEnd == current.length && oldEnd == old.length)
3210 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
3211 currentStart += prefixCount;
3212 oldStart += prefixCount;
3213 currentEnd -= suffixCount;
3214 oldEnd -= suffixCount;
3215 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
3216 return [];
3217 if (currentStart == currentEnd) {
3218 var splice = newSplice(currentStart, [], 0);
3219 while (oldStart < oldEnd)
3220 splice.removed.push(old[oldStart++]);
3221 return [splice];
3222 } else if (oldStart == oldEnd)
3223 return [newSplice(currentStart, [], currentEnd - currentStart)];
3224 var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
3225 var splice = undefined;
3226 var splices = [];
3227 var index = currentStart;
3228 var oldIndex = oldStart;
3229 for (var i = 0; i < ops.length; i++) {
3230 switch (ops[i]) {
3231 case EDIT_LEAVE:
3232 if (splice) {
3233 splices.push(splice);
3234 splice = undefined;
3235 }
3236 index++;
3237 oldIndex++;
3238 break;
3239 case EDIT_UPDATE:
3240 if (!splice)
3241 splice = newSplice(index, [], 0);
3242 splice.addedCount++;
3243 index++;
3244 splice.removed.push(old[oldIndex]);
3245 oldIndex++;
3246 break;
3247 case EDIT_ADD:
3248 if (!splice)
3249 splice = newSplice(index, [], 0);
3250 splice.addedCount++;
3251 index++;
3252 break;
3253 case EDIT_DELETE:
3254 if (!splice)
3255 splice = newSplice(index, [], 0);
3256 splice.removed.push(old[oldIndex]);
3257 oldIndex++;
3258 break;
3259 }
3260 }
3261 if (splice) {
3262 splices.push(splice);
3263 }
3264 return splices;
3265 },
3266 sharedPrefix: function (current, old, searchLength) {
3267 for (var i = 0; i < searchLength; i++)
3268 if (!this.equals(current[i], old[i]))
3269 return i;
3270 return searchLength;
3271 },
3272 sharedSuffix: function (current, old, searchLength) {
3273 var index1 = current.length;
3274 var index2 = old.length;
3275 var count = 0;
3276 while (count < searchLength && this.equals(current[--index1], old[--index2]))
3277 count++;
3278 return count;
3279 },
3280 calculateSplices: function (current, previous) {
3281 return this.calcSplices(current, 0, current.length, previous, 0, previous.length );
3282 },
3283 equals: function (currentValue, previousValue) {
3284 return currentValue === previousValue;
3285 }
3286 };
3287 return new ArraySplice();
3288 }();
3289 Polymer.EventApi = function () {
3290 var Settings = Polymer.Settings;
3291 var EventApi = function (event) {
3292 this.event = event;
3293 };
3294 if (Settings.useShadow) {
3295 EventApi.prototype = {
3296 get rootTarget() {
3297 return this.event.path[0];
3298 },
3299 get localTarget() {
3300 return this.event.target;
3301 },
3302 get path() {
3303 return this.event.path;
3304 }
3305 };
3306 } else {
3307 EventApi.prototype = {
3308 get rootTarget() {
3309 return this.event.target;
3310 },
3311 get localTarget() {
3312 var current = this.event.currentTarget;
3313 var currentRoot = current && Polymer.dom(current).getOwnerRoot();
3314 var p$ = this.path;
3315 for (var i = 0; i < p$.length; i++) {
3316 if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) {
3317 return p$[i];
3318 }
3319 }
3320 },
3321 get path() {
3322 if (!this.event._path) {
3323 var path = [];
3324 var o = this.rootTarget;
3325 while (o) {
3326 path.push(o);
3327 o = Polymer.dom(o).parentNode || o.host;
3328 }
3329 path.push(window);
3330 this.event._path = path;
3331 }
3332 return this.event._path;
3333 }
3334 };
3335 }
3336 var factory = function (event) {
3337 if (!event.__eventApi) {
3338 event.__eventApi = new EventApi(event);
3339 }
3340 return event.__eventApi;
3341 };
3342 return { factory: factory };
3343 }();
3344 Polymer.domInnerHTML = function () {
3345 var escapeAttrRegExp = /[&\u00A0"]/g;
3346 var escapeDataRegExp = /[&\u00A0<>]/g;
3347 function escapeReplace(c) {
3348 switch (c) {
3349 case '&':
3350 return '&amp;';
3351 case '<':
3352 return '&lt;';
3353 case '>':
3354 return '&gt;';
3355 case '"':
3356 return '&quot;';
3357 case '\xA0':
3358 return '&nbsp;';
3359 }
3360 }
3361 function escapeAttr(s) {
3362 return s.replace(escapeAttrRegExp, escapeReplace);
3363 }
3364 function escapeData(s) {
3365 return s.replace(escapeDataRegExp, escapeReplace);
3366 }
3367 function makeSet(arr) {
3368 var set = {};
3369 for (var i = 0; i < arr.length; i++) {
3370 set[arr[i]] = true;
3371 }
3372 return set;
3373 }
3374 var voidElements = makeSet([
3375 'area',
3376 'base',
3377 'br',
3378 'col',
3379 'command',
3380 'embed',
3381 'hr',
3382 'img',
3383 'input',
3384 'keygen',
3385 'link',
3386 'meta',
3387 'param',
3388 'source',
3389 'track',
3390 'wbr'
3391 ]);
3392 var plaintextParents = makeSet([
3393 'style',
3394 'script',
3395 'xmp',
3396 'iframe',
3397 'noembed',
3398 'noframes',
3399 'plaintext',
3400 'noscript'
3401 ]);
3402 function getOuterHTML(node, parentNode, composed) {
3403 switch (node.nodeType) {
3404 case Node.ELEMENT_NODE:
3405 var tagName = node.localName;
3406 var s = '<' + tagName;
3407 var attrs = node.attributes;
3408 for (var i = 0, attr; attr = attrs[i]; i++) {
3409 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
3410 }
3411 s += '>';
3412 if (voidElements[tagName]) {
3413 return s;
3414 }
3415 return s + getInnerHTML(node, composed) + '</' + tagName + '>';
3416 case Node.TEXT_NODE:
3417 var data = node.data;
3418 if (parentNode && plaintextParents[parentNode.localName]) {
3419 return data;
3420 }
3421 return escapeData(data);
3422 case Node.COMMENT_NODE:
3423 return '<!--' + node.data + '-->';
3424 default:
3425 console.error(node);
3426 throw new Error('not implemented');
3427 }
3428 }
3429 function getInnerHTML(node, composed) {
3430 if (node instanceof HTMLTemplateElement)
3431 node = node.content;
3432 var s = '';
3433 var c$ = Polymer.dom(node).childNodes;
3434 c$ = composed ? node._composedChildren : c$;
3435 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
3436 s += getOuterHTML(child, node, composed);
3437 }
3438 return s;
3439 }
3440 return { getInnerHTML: getInnerHTML };
3441 }();
3442 Polymer.DomApi = function () {
3443 'use strict';
3444 var Settings = Polymer.Settings;
3445 var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
3446 var nativeInsertBefore = Element.prototype.insertBefore;
3447 var nativeRemoveChild = Element.prototype.removeChild;
3448 var nativeAppendChild = Element.prototype.appendChild;
3449 var nativeCloneNode = Element.prototype.cloneNode;
3450 var nativeImportNode = Document.prototype.importNode;
3451 var DomApi = function (node) {
3452 this.node = node;
3453 if (this.patch) {
3454 this.patch();
3455 }
3456 };
3457 if (window.wrap && Settings.useShadow && !Settings.useNativeShadow) {
3458 DomApi = function (node) {
3459 this.node = wrap(node);
3460 if (this.patch) {
3461 this.patch();
3462 }
3463 };
3464 }
3465 DomApi.prototype = {
3466 flush: function () {
3467 Polymer.dom.flush();
3468 },
3469 _lazyDistribute: function (host) {
3470 if (host.shadyRoot && host.shadyRoot._distributionClean) {
3471 host.shadyRoot._distributionClean = false;
3472 Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent));
3473 }
3474 },
3475 appendChild: function (node) {
3476 return this._addNode(node);
3477 },
3478 insertBefore: function (node, ref_node) {
3479 return this._addNode(node, ref_node);
3480 },
3481 _addNode: function (node, ref_node) {
3482 this._removeNodeFromHost(node, true);
3483 var addedInsertionPoint;
3484 var root = this.getOwnerRoot();
3485 if (root) {
3486 addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node);
3487 }
3488 if (this._nodeHasLogicalChildren(this.node)) {
3489 if (ref_node) {
3490 var children = this.childNodes;
3491 var index = children.indexOf(ref_node);
3492 if (index < 0) {
3493 throw Error('The ref_node to be inserted before is not a child ' + 'of this node ');
3494 }
3495 }
3496 this._addLogicalInfo(node, this.node, index);
3497 }
3498 this._addNodeToHost(node);
3499 if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode (node)) {
3500 if (ref_node) {
3501 ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) : ref_node;
3502 }
3503 var container = this.node._isShadyRoot ? this.node.host : this.node;
3504 addToComposedParent(container, node, ref_node);
3505 if (ref_node) {
3506 nativeInsertBefore.call(container, node, ref_node);
3507 } else {
3508 nativeAppendChild.call(container, node);
3509 }
3510 }
3511 if (addedInsertionPoint) {
3512 this._updateInsertionPoints(root.host);
3513 }
3514 return node;
3515 },
3516 removeChild: function (node) {
3517 if (factory(node).parentNode !== this.node) {
3518 console.warn('The node to be removed is not a child of this node', node);
3519 }
3520 this._removeNodeFromHost(node);
3521 if (!this._maybeDistribute(node, this.node)) {
3522 var container = this.node._isShadyRoot ? this.node.host : this.node;
3523 if (container === node.parentNode) {
3524 removeFromComposedParent(container, node);
3525 nativeRemoveChild.call(container, node);
3526 }
3527 }
3528 return node;
3529 },
3530 replaceChild: function (node, ref_node) {
3531 this.insertBefore(node, ref_node);
3532 this.removeChild(ref_node);
3533 return node;
3534 },
3535 _hasCachedOwnerRoot: function (node) {
3536 return Boolean(node._ownerShadyRoot !== undefined);
3537 },
3538 getOwnerRoot: function () {
3539 return this._ownerShadyRootForNode(this.node);
3540 },
3541 _ownerShadyRootForNode: function (node) {
3542 if (!node) {
3543 return;
3544 }
3545 if (node._ownerShadyRoot === undefined) {
3546 var root;
3547 if (node._isShadyRoot) {
3548 root = node;
3549 } else {
3550 var parent = Polymer.dom(node).parentNode;
3551 if (parent) {
3552 root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent);
3553 } else {
3554 root = null;
3555 }
3556 }
3557 node._ownerShadyRoot = root;
3558 }
3559 return node._ownerShadyRoot;
3560 },
3561 _maybeDistribute: function (node, parent) {
3562 var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noCon tent && Polymer.dom(node).querySelector(CONTENT);
3563 var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE;
3564 var hasContent = fragContent || node.localName === CONTENT;
3565 if (hasContent) {
3566 var root = this._ownerShadyRootForNode(parent);
3567 if (root) {
3568 var host = root.host;
3569 this._lazyDistribute(host);
3570 }
3571 }
3572 var parentNeedsDist = this._parentNeedsDistribution(parent);
3573 if (parentNeedsDist) {
3574 this._lazyDistribute(parent);
3575 }
3576 return parentNeedsDist || hasContent && !wrappedContent;
3577 },
3578 _maybeAddInsertionPoint: function (node, parent) {
3579 var added;
3580 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) {
3581 var c$ = factory(node).querySelectorAll(CONTENT);
3582 for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) {
3583 np = factory(n).parentNode;
3584 if (np === node) {
3585 np = parent;
3586 }
3587 na = this._maybeAddInsertionPoint(n, np);
3588 added = added || na;
3589 }
3590 } else if (node.localName === CONTENT) {
3591 saveLightChildrenIfNeeded(parent);
3592 saveLightChildrenIfNeeded(node);
3593 added = true;
3594 }
3595 return added;
3596 },
3597 _tryRemoveUndistributedNode: function (node) {
3598 if (this.node.shadyRoot) {
3599 var parent = getComposedParent(node);
3600 if (parent) {
3601 nativeRemoveChild.call(parent, node);
3602 }
3603 return true;
3604 }
3605 },
3606 _updateInsertionPoints: function (host) {
3607 var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelector All(CONTENT);
3608 for (var i = 0, c; i < i$.length; i++) {
3609 c = i$[i];
3610 saveLightChildrenIfNeeded(c);
3611 saveLightChildrenIfNeeded(factory(c).parentNode);
3612 }
3613 },
3614 _nodeHasLogicalChildren: function (node) {
3615 return Boolean(node._lightChildren !== undefined);
3616 },
3617 _parentNeedsDistribution: function (parent) {
3618 return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
3619 },
3620 _removeNodeFromHost: function (node, ensureComposedRemoval) {
3621 var hostNeedsDist;
3622 var root;
3623 var parent = node._lightParent;
3624 if (parent) {
3625 factory(node)._distributeParent();
3626 root = this._ownerShadyRootForNode(node);
3627 if (root) {
3628 root.host._elementRemove(node);
3629 hostNeedsDist = this._removeDistributedChildren(root, node);
3630 }
3631 this._removeLogicalInfo(node, node._lightParent);
3632 }
3633 this._removeOwnerShadyRoot(node);
3634 if (root && hostNeedsDist) {
3635 this._updateInsertionPoints(root.host);
3636 this._lazyDistribute(root.host);
3637 } else if (ensureComposedRemoval) {
3638 removeFromComposedParent(getComposedParent(node), node);
3639 }
3640 },
3641 _removeDistributedChildren: function (root, container) {
3642 var hostNeedsDist;
3643 var ip$ = root._insertionPoints;
3644 for (var i = 0; i < ip$.length; i++) {
3645 var content = ip$[i];
3646 if (this._contains(container, content)) {
3647 var dc$ = factory(content).getDistributedNodes();
3648 for (var j = 0; j < dc$.length; j++) {
3649 hostNeedsDist = true;
3650 var node = dc$[j];
3651 var parent = node.parentNode;
3652 if (parent) {
3653 removeFromComposedParent(parent, node);
3654 nativeRemoveChild.call(parent, node);
3655 }
3656 }
3657 }
3658 }
3659 return hostNeedsDist;
3660 },
3661 _contains: function (container, node) {
3662 while (node) {
3663 if (node == container) {
3664 return true;
3665 }
3666 node = factory(node).parentNode;
3667 }
3668 },
3669 _addNodeToHost: function (node) {
3670 var root = this.getOwnerRoot();
3671 if (root) {
3672 root.host._elementAdd(node);
3673 }
3674 },
3675 _addLogicalInfo: function (node, container, index) {
3676 var children = factory(container).childNodes;
3677 index = index === undefined ? children.length : index;
3678 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
3679 var c$ = Array.prototype.slice.call(node.childNodes);
3680 for (var i = 0, n; i < c$.length && (n = c$[i]); i++) {
3681 children.splice(index++, 0, n);
3682 n._lightParent = container;
3683 }
3684 } else {
3685 children.splice(index, 0, node);
3686 node._lightParent = container;
3687 }
3688 },
3689 _removeLogicalInfo: function (node, container) {
3690 var children = factory(container).childNodes;
3691 var index = children.indexOf(node);
3692 if (index < 0 || container !== node._lightParent) {
3693 throw Error('The node to be removed is not a child of this node');
3694 }
3695 children.splice(index, 1);
3696 node._lightParent = null;
3697 },
3698 _removeOwnerShadyRoot: function (node) {
3699 if (this._hasCachedOwnerRoot(node)) {
3700 var c$ = factory(node).childNodes;
3701 for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) {
3702 this._removeOwnerShadyRoot(n);
3703 }
3704 }
3705 node._ownerShadyRoot = undefined;
3706 },
3707 _firstComposedNode: function (content) {
3708 var n$ = factory(content).getDistributedNodes();
3709 for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) {
3710 p$ = factory(n).getDestinationInsertionPoints();
3711 if (p$[p$.length - 1] === content) {
3712 return n;
3713 }
3714 }
3715 },
3716 querySelector: function (selector) {
3717 return this.querySelectorAll(selector)[0];
3718 },
3719 querySelectorAll: function (selector) {
3720 return this._query(function (n) {
3721 return matchesSelector.call(n, selector);
3722 }, this.node);
3723 },
3724 _query: function (matcher, node) {
3725 node = node || this.node;
3726 var list = [];
3727 this._queryElements(factory(node).childNodes, matcher, list);
3728 return list;
3729 },
3730 _queryElements: function (elements, matcher, list) {
3731 for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) {
3732 if (c.nodeType === Node.ELEMENT_NODE) {
3733 this._queryElement(c, matcher, list);
3734 }
3735 }
3736 },
3737 _queryElement: function (node, matcher, list) {
3738 if (matcher(node)) {
3739 list.push(node);
3740 }
3741 this._queryElements(factory(node).childNodes, matcher, list);
3742 },
3743 getDestinationInsertionPoints: function () {
3744 return this.node._destinationInsertionPoints || [];
3745 },
3746 getDistributedNodes: function () {
3747 return this.node._distributedNodes || [];
3748 },
3749 queryDistributedElements: function (selector) {
3750 var c$ = this.childNodes;
3751 var list = [];
3752 this._distributedFilter(selector, c$, list);
3753 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
3754 if (c.localName === CONTENT) {
3755 this._distributedFilter(selector, factory(c).getDistributedNodes(), list);
3756 }
3757 }
3758 return list;
3759 },
3760 _distributedFilter: function (selector, list, results) {
3761 results = results || [];
3762 for (var i = 0, l = list.length, d; i < l && (d = list[i]); i++) {
3763 if (d.nodeType === Node.ELEMENT_NODE && d.localName !== CONTENT && matchesSelect or.call(d, selector)) {
3764 results.push(d);
3765 }
3766 }
3767 return results;
3768 },
3769 _clear: function () {
3770 while (this.childNodes.length) {
3771 this.removeChild(this.childNodes[0]);
3772 }
3773 },
3774 setAttribute: function (name, value) {
3775 this.node.setAttribute(name, value);
3776 this._distributeParent();
3777 },
3778 removeAttribute: function (name) {
3779 this.node.removeAttribute(name);
3780 this._distributeParent();
3781 },
3782 _distributeParent: function () {
3783 if (this._parentNeedsDistribution(this.parentNode)) {
3784 this._lazyDistribute(this.parentNode);
3785 }
3786 },
3787 cloneNode: function (deep) {
3788 var n = nativeCloneNode.call(this.node, false);
3789 if (deep) {
3790 var c$ = this.childNodes;
3791 var d = factory(n);
3792 for (var i = 0, nc; i < c$.length; i++) {
3793 nc = factory(c$[i]).cloneNode(true);
3794 d.appendChild(nc);
3795 }
3796 }
3797 return n;
3798 },
3799 importNode: function (externalNode, deep) {
3800 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
3801 var n = nativeImportNode.call(doc, externalNode, false);
3802 if (deep) {
3803 var c$ = factory(externalNode).childNodes;
3804 var d = factory(n);
3805 for (var i = 0, nc; i < c$.length; i++) {
3806 nc = factory(doc).importNode(c$[i], true);
3807 d.appendChild(nc);
3808 }
3809 }
3810 return n;
3811 }
3812 };
3813 Object.defineProperty(DomApi.prototype, 'classList', {
3814 get: function () {
3815 if (!this._classList) {
3816 this._classList = new DomApi.ClassList(this);
3817 }
3818 return this._classList;
3819 },
3820 configurable: true
3821 });
3822 DomApi.ClassList = function (host) {
3823 this.domApi = host;
3824 this.node = host.node;
3825 };
3826 DomApi.ClassList.prototype = {
3827 add: function () {
3828 this.node.classList.add.apply(this.node.classList, arguments);
3829 this.domApi._distributeParent();
3830 },
3831 remove: function () {
3832 this.node.classList.remove.apply(this.node.classList, arguments);
3833 this.domApi._distributeParent();
3834 },
3835 toggle: function () {
3836 this.node.classList.toggle.apply(this.node.classList, arguments);
3837 this.domApi._distributeParent();
3838 },
3839 contains: function () {
3840 return this.node.classList.contains.apply(this.node.classList, arguments);
3841 }
3842 };
3843 if (!Settings.useShadow) {
3844 Object.defineProperties(DomApi.prototype, {
3845 childNodes: {
3846 get: function () {
3847 var c$ = getLightChildren(this.node);
3848 return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$);
3849 },
3850 configurable: true
3851 },
3852 children: {
3853 get: function () {
3854 return Array.prototype.filter.call(this.childNodes, function (n) {
3855 return n.nodeType === Node.ELEMENT_NODE;
3856 });
3857 },
3858 configurable: true
3859 },
3860 parentNode: {
3861 get: function () {
3862 return this.node._lightParent || getComposedParent(this.node);
3863 },
3864 configurable: true
3865 },
3866 firstChild: {
3867 get: function () {
3868 return this.childNodes[0];
3869 },
3870 configurable: true
3871 },
3872 lastChild: {
3873 get: function () {
3874 var c$ = this.childNodes;
3875 return c$[c$.length - 1];
3876 },
3877 configurable: true
3878 },
3879 nextSibling: {
3880 get: function () {
3881 var c$ = this.parentNode && factory(this.parentNode).childNodes;
3882 if (c$) {
3883 return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
3884 }
3885 },
3886 configurable: true
3887 },
3888 previousSibling: {
3889 get: function () {
3890 var c$ = this.parentNode && factory(this.parentNode).childNodes;
3891 if (c$) {
3892 return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
3893 }
3894 },
3895 configurable: true
3896 },
3897 firstElementChild: {
3898 get: function () {
3899 return this.children[0];
3900 },
3901 configurable: true
3902 },
3903 lastElementChild: {
3904 get: function () {
3905 var c$ = this.children;
3906 return c$[c$.length - 1];
3907 },
3908 configurable: true
3909 },
3910 nextElementSibling: {
3911 get: function () {
3912 var c$ = this.parentNode && factory(this.parentNode).children;
3913 if (c$) {
3914 return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
3915 }
3916 },
3917 configurable: true
3918 },
3919 previousElementSibling: {
3920 get: function () {
3921 var c$ = this.parentNode && factory(this.parentNode).children;
3922 if (c$) {
3923 return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
3924 }
3925 },
3926 configurable: true
3927 },
3928 textContent: {
3929 get: function () {
3930 var nt = this.node.nodeType;
3931 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
3932 return this.node.textContent;
3933 } else {
3934 var tc = [];
3935 for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
3936 if (c.nodeType !== Node.COMMENT_NODE) {
3937 tc.push(c.textContent);
3938 }
3939 }
3940 return tc.join('');
3941 }
3942 },
3943 set: function (text) {
3944 var nt = this.node.nodeType;
3945 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
3946 this.node.textContent = text;
3947 } else {
3948 this._clear();
3949 if (text) {
3950 this.appendChild(document.createTextNode(text));
3951 }
3952 }
3953 },
3954 configurable: true
3955 },
3956 innerHTML: {
3957 get: function () {
3958 var nt = this.node.nodeType;
3959 if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
3960 return null;
3961 } else {
3962 return getInnerHTML(this.node);
3963 }
3964 },
3965 set: function (text) {
3966 var nt = this.node.nodeType;
3967 if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
3968 this._clear();
3969 var d = document.createElement('div');
3970 d.innerHTML = text;
3971 var c$ = Array.prototype.slice.call(d.childNodes);
3972 for (var i = 0; i < c$.length; i++) {
3973 this.appendChild(c$[i]);
3974 }
3975 }
3976 },
3977 configurable: true
3978 }
3979 });
3980 DomApi.prototype._getComposedInnerHTML = function () {
3981 return getInnerHTML(this.node, true);
3982 };
3983 } else {
3984 var forwardMethods = [
3985 'cloneNode',
3986 'appendChild',
3987 'insertBefore',
3988 'removeChild',
3989 'replaceChild'
3990 ];
3991 forwardMethods.forEach(function (name) {
3992 DomApi.prototype[name] = function () {
3993 return this.node[name].apply(this.node, arguments);
3994 };
3995 });
3996 DomApi.prototype.querySelectorAll = function (selector) {
3997 return Array.prototype.slice.call(this.node.querySelectorAll(selector));
3998 };
3999 DomApi.prototype.getOwnerRoot = function () {
4000 var n = this.node;
4001 while (n) {
4002 if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
4003 return n;
4004 }
4005 n = n.parentNode;
4006 }
4007 };
4008 DomApi.prototype.importNode = function (externalNode, deep) {
4009 var doc = this.node instanceof Document ? this.node : this.node.ownerDocument;
4010 return doc.importNode(externalNode, deep);
4011 };
4012 DomApi.prototype.getDestinationInsertionPoints = function () {
4013 var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInse rtionPoints();
4014 return n$ ? Array.prototype.slice.call(n$) : [];
4015 };
4016 DomApi.prototype.getDistributedNodes = function () {
4017 var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes();
4018 return n$ ? Array.prototype.slice.call(n$) : [];
4019 };
4020 DomApi.prototype._distributeParent = function () {
4021 };
4022 Object.defineProperties(DomApi.prototype, {
4023 childNodes: {
4024 get: function () {
4025 return Array.prototype.slice.call(this.node.childNodes);
4026 },
4027 configurable: true
4028 },
4029 children: {
4030 get: function () {
4031 return Array.prototype.slice.call(this.node.children);
4032 },
4033 configurable: true
4034 },
4035 textContent: {
4036 get: function () {
4037 return this.node.textContent;
4038 },
4039 set: function (value) {
4040 return this.node.textContent = value;
4041 },
4042 configurable: true
4043 },
4044 innerHTML: {
4045 get: function () {
4046 return this.node.innerHTML;
4047 },
4048 set: function (value) {
4049 return this.node.innerHTML = value;
4050 },
4051 configurable: true
4052 }
4053 });
4054 var forwardProperties = [
4055 'parentNode',
4056 'firstChild',
4057 'lastChild',
4058 'nextSibling',
4059 'previousSibling',
4060 'firstElementChild',
4061 'lastElementChild',
4062 'nextElementSibling',
4063 'previousElementSibling'
4064 ];
4065 forwardProperties.forEach(function (name) {
4066 Object.defineProperty(DomApi.prototype, name, {
4067 get: function () {
4068 return this.node[name];
4069 },
4070 configurable: true
4071 });
4072 });
4073 }
4074 var CONTENT = 'content';
4075 var factory = function (node, patch) {
4076 node = node || document;
4077 if (!node.__domApi) {
4078 node.__domApi = new DomApi(node, patch);
4079 }
4080 return node.__domApi;
4081 };
4082 Polymer.dom = function (obj, patch) {
4083 if (obj instanceof Event) {
4084 return Polymer.EventApi.factory(obj);
4085 } else {
4086 return factory(obj, patch);
4087 }
4088 };
4089 Polymer.Base.extend(Polymer.dom, {
4090 _flushGuard: 0,
4091 _FLUSH_MAX: 100,
4092 _needsTakeRecords: !Polymer.Settings.useNativeCustomElements,
4093 _debouncers: [],
4094 _finishDebouncer: null,
4095 flush: function () {
4096 for (var i = 0; i < this._debouncers.length; i++) {
4097 this._debouncers[i].complete();
4098 }
4099 if (this._finishDebouncer) {
4100 this._finishDebouncer.complete();
4101 }
4102 this._flushPolyfills();
4103 if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) {
4104 this._flushGuard++;
4105 this.flush();
4106 } else {
4107 if (this._flushGuard >= this._FLUSH_MAX) {
4108 console.warn('Polymer.dom.flush aborted. Flush may not be complete.');
4109 }
4110 this._flushGuard = 0;
4111 }
4112 },
4113 _flushPolyfills: function () {
4114 if (this._needsTakeRecords) {
4115 CustomElements.takeRecords();
4116 }
4117 },
4118 addDebouncer: function (debouncer) {
4119 this._debouncers.push(debouncer);
4120 this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlus h);
4121 },
4122 _finishFlush: function () {
4123 Polymer.dom._debouncers = [];
4124 }
4125 });
4126 function getLightChildren(node) {
4127 var children = node._lightChildren;
4128 return children ? children : node.childNodes;
4129 }
4130 function getComposedChildren(node) {
4131 if (!node._composedChildren) {
4132 node._composedChildren = Array.prototype.slice.call(node.childNodes);
4133 }
4134 return node._composedChildren;
4135 }
4136 function addToComposedParent(parent, node, ref_node) {
4137 var children = getComposedChildren(parent);
4138 var i = ref_node ? children.indexOf(ref_node) : -1;
4139 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
4140 var fragChildren = getComposedChildren(node);
4141 for (var j = 0; j < fragChildren.length; j++) {
4142 addNodeToComposedChildren(fragChildren[j], parent, children, i + j);
4143 }
4144 node._composedChildren = null;
4145 } else {
4146 addNodeToComposedChildren(node, parent, children, i);
4147 }
4148 }
4149 function getComposedParent(node) {
4150 return node.__patched ? node._composedParent : node.parentNode;
4151 }
4152 function addNodeToComposedChildren(node, parent, children, i) {
4153 node._composedParent = parent;
4154 children.splice(i >= 0 ? i : children.length, 0, node);
4155 }
4156 function removeFromComposedParent(parent, node) {
4157 node._composedParent = null;
4158 if (parent) {
4159 var children = getComposedChildren(parent);
4160 var i = children.indexOf(node);
4161 if (i >= 0) {
4162 children.splice(i, 1);
4163 }
4164 }
4165 }
4166 function saveLightChildrenIfNeeded(node) {
4167 if (!node._lightChildren) {
4168 var c$ = Array.prototype.slice.call(node.childNodes);
4169 for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) {
4170 child._lightParent = child._lightParent || node;
4171 }
4172 node._lightChildren = c$;
4173 }
4174 }
4175 function hasInsertionPoint(root) {
4176 return Boolean(root && root._insertionPoints.length);
4177 }
4178 var p = Element.prototype;
4179 var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector;
4180 return {
4181 getLightChildren: getLightChildren,
4182 getComposedParent: getComposedParent,
4183 getComposedChildren: getComposedChildren,
4184 removeFromComposedParent: removeFromComposedParent,
4185 saveLightChildrenIfNeeded: saveLightChildrenIfNeeded,
4186 matchesSelector: matchesSelector,
4187 hasInsertionPoint: hasInsertionPoint,
4188 ctor: DomApi,
4189 factory: factory
4190 };
4191 }();
4192 (function () {
4193 Polymer.Base._addFeature({
4194 _prepShady: function () {
4195 this._useContent = this._useContent || Boolean(this._template);
4196 },
4197 _poolContent: function () {
4198 if (this._useContent) {
4199 saveLightChildrenIfNeeded(this);
4200 }
4201 },
4202 _setupRoot: function () {
4203 if (this._useContent) {
4204 this._createLocalRoot();
4205 if (!this.dataHost) {
4206 upgradeLightChildren(this._lightChildren);
4207 }
4208 }
4209 },
4210 _createLocalRoot: function () {
4211 this.shadyRoot = this.root;
4212 this.shadyRoot._distributionClean = false;
4213 this.shadyRoot._isShadyRoot = true;
4214 this.shadyRoot._dirtyRoots = [];
4215 var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasConte nt ? this.shadyRoot.querySelectorAll('content') : [];
4216 saveLightChildrenIfNeeded(this.shadyRoot);
4217 for (var i = 0, c; i < i$.length; i++) {
4218 c = i$[i];
4219 saveLightChildrenIfNeeded(c);
4220 saveLightChildrenIfNeeded(c.parentNode);
4221 }
4222 this.shadyRoot.host = this;
4223 },
4224 get domHost() {
4225 var root = Polymer.dom(this).getOwnerRoot();
4226 return root && root.host;
4227 },
4228 distributeContent: function (updateInsertionPoints) {
4229 if (this.shadyRoot) {
4230 var dom = Polymer.dom(this);
4231 if (updateInsertionPoints) {
4232 dom._updateInsertionPoints(this);
4233 }
4234 var host = getTopDistributingHost(this);
4235 dom._lazyDistribute(host);
4236 }
4237 },
4238 _distributeContent: function () {
4239 if (this._useContent && !this.shadyRoot._distributionClean) {
4240 this._beginDistribute();
4241 this._distributeDirtyRoots();
4242 this._finishDistribute();
4243 }
4244 },
4245 _beginDistribute: function () {
4246 if (this._useContent && hasInsertionPoint(this.shadyRoot)) {
4247 this._resetDistribution();
4248 this._distributePool(this.shadyRoot, this._collectPool());
4249 }
4250 },
4251 _distributeDirtyRoots: function () {
4252 var c$ = this.shadyRoot._dirtyRoots;
4253 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
4254 c._distributeContent();
4255 }
4256 this.shadyRoot._dirtyRoots = [];
4257 },
4258 _finishDistribute: function () {
4259 if (this._useContent) {
4260 this.shadyRoot._distributionClean = true;
4261 if (hasInsertionPoint(this.shadyRoot)) {
4262 this._composeTree();
4263 } else {
4264 if (!this.shadyRoot._hasDistributed) {
4265 this.textContent = '';
4266 this._composedChildren = null;
4267 this.appendChild(this.shadyRoot);
4268 } else {
4269 var children = this._composeNode(this);
4270 this._updateChildNodes(this, children);
4271 }
4272 }
4273 this.shadyRoot._hasDistributed = true;
4274 }
4275 },
4276 elementMatches: function (selector, node) {
4277 node = node || this;
4278 return matchesSelector.call(node, selector);
4279 },
4280 _resetDistribution: function () {
4281 var children = getLightChildren(this);
4282 for (var i = 0; i < children.length; i++) {
4283 var child = children[i];
4284 if (child._destinationInsertionPoints) {
4285 child._destinationInsertionPoints = undefined;
4286 }
4287 if (isInsertionPoint(child)) {
4288 clearDistributedDestinationInsertionPoints(child);
4289 }
4290 }
4291 var root = this.shadyRoot;
4292 var p$ = root._insertionPoints;
4293 for (var j = 0; j < p$.length; j++) {
4294 p$[j]._distributedNodes = [];
4295 }
4296 },
4297 _collectPool: function () {
4298 var pool = [];
4299 var children = getLightChildren(this);
4300 for (var i = 0; i < children.length; i++) {
4301 var child = children[i];
4302 if (isInsertionPoint(child)) {
4303 pool.push.apply(pool, child._distributedNodes);
4304 } else {
4305 pool.push(child);
4306 }
4307 }
4308 return pool;
4309 },
4310 _distributePool: function (node, pool) {
4311 var p$ = node._insertionPoints;
4312 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
4313 this._distributeInsertionPoint(p, pool);
4314 maybeRedistributeParent(p, this);
4315 }
4316 },
4317 _distributeInsertionPoint: function (content, pool) {
4318 var anyDistributed = false;
4319 for (var i = 0, l = pool.length, node; i < l; i++) {
4320 node = pool[i];
4321 if (!node) {
4322 continue;
4323 }
4324 if (this._matchesContentSelect(node, content)) {
4325 distributeNodeInto(node, content);
4326 pool[i] = undefined;
4327 anyDistributed = true;
4328 }
4329 }
4330 if (!anyDistributed) {
4331 var children = getLightChildren(content);
4332 for (var j = 0; j < children.length; j++) {
4333 distributeNodeInto(children[j], content);
4334 }
4335 }
4336 },
4337 _composeTree: function () {
4338 this._updateChildNodes(this, this._composeNode(this));
4339 var p$ = this.shadyRoot._insertionPoints;
4340 for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) {
4341 parent = p._lightParent || p.parentNode;
4342 if (!parent._useContent && parent !== this && parent !== this.shadyRoot) {
4343 this._updateChildNodes(parent, this._composeNode(parent));
4344 }
4345 }
4346 },
4347 _composeNode: function (node) {
4348 var children = [];
4349 var c$ = getLightChildren(node.shadyRoot || node);
4350 for (var i = 0; i < c$.length; i++) {
4351 var child = c$[i];
4352 if (isInsertionPoint(child)) {
4353 var distributedNodes = child._distributedNodes;
4354 for (var j = 0; j < distributedNodes.length; j++) {
4355 var distributedNode = distributedNodes[j];
4356 if (isFinalDestination(child, distributedNode)) {
4357 children.push(distributedNode);
4358 }
4359 }
4360 } else {
4361 children.push(child);
4362 }
4363 }
4364 return children;
4365 },
4366 _updateChildNodes: function (container, children) {
4367 var composed = getComposedChildren(container);
4368 var splices = Polymer.ArraySplice.calculateSplices(children, composed);
4369 for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) {
4370 for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) {
4371 if (getComposedParent(n) === container) {
4372 remove(n);
4373 }
4374 composed.splice(s.index + d, 1);
4375 }
4376 d -= s.addedCount;
4377 }
4378 for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) {
4379 next = composed[s.index];
4380 for (var j = s.index, n; j < s.index + s.addedCount; j++) {
4381 n = children[j];
4382 insertBefore(container, n, next);
4383 composed.splice(j, 0, n);
4384 }
4385 }
4386 ensureComposedParent(container, children);
4387 },
4388 _matchesContentSelect: function (node, contentElement) {
4389 var select = contentElement.getAttribute('select');
4390 if (!select) {
4391 return true;
4392 }
4393 select = select.trim();
4394 if (!select) {
4395 return true;
4396 }
4397 if (!(node instanceof Element)) {
4398 return false;
4399 }
4400 var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/;
4401 if (!validSelectors.test(select)) {
4402 return false;
4403 }
4404 return this.elementMatches(select, node);
4405 },
4406 _elementAdd: function () {
4407 },
4408 _elementRemove: function () {
4409 }
4410 });
4411 var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
4412 var getLightChildren = Polymer.DomApi.getLightChildren;
4413 var matchesSelector = Polymer.DomApi.matchesSelector;
4414 var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint;
4415 var getComposedChildren = Polymer.DomApi.getComposedChildren;
4416 var getComposedParent = Polymer.DomApi.getComposedParent;
4417 var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent;
4418 function distributeNodeInto(child, insertionPoint) {
4419 insertionPoint._distributedNodes.push(child);
4420 var points = child._destinationInsertionPoints;
4421 if (!points) {
4422 child._destinationInsertionPoints = [insertionPoint];
4423 } else {
4424 points.push(insertionPoint);
4425 }
4426 }
4427 function clearDistributedDestinationInsertionPoints(content) {
4428 var e$ = content._distributedNodes;
4429 if (e$) {
4430 for (var i = 0; i < e$.length; i++) {
4431 var d = e$[i]._destinationInsertionPoints;
4432 if (d) {
4433 d.splice(d.indexOf(content) + 1, d.length);
4434 }
4435 }
4436 }
4437 }
4438 function maybeRedistributeParent(content, host) {
4439 var parent = content._lightParent;
4440 if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent. shadyRoot._distributionClean) {
4441 parent.shadyRoot._distributionClean = false;
4442 host.shadyRoot._dirtyRoots.push(parent);
4443 }
4444 }
4445 function isFinalDestination(insertionPoint, node) {
4446 var points = node._destinationInsertionPoints;
4447 return points && points[points.length - 1] === insertionPoint;
4448 }
4449 function isInsertionPoint(node) {
4450 return node.localName == 'content';
4451 }
4452 var nativeInsertBefore = Element.prototype.insertBefore;
4453 var nativeRemoveChild = Element.prototype.removeChild;
4454 function insertBefore(parentNode, newChild, refChild) {
4455 var newChildParent = getComposedParent(newChild);
4456 if (newChildParent !== parentNode) {
4457 removeFromComposedParent(newChildParent, newChild);
4458 }
4459 remove(newChild);
4460 nativeInsertBefore.call(parentNode, newChild, refChild || null);
4461 newChild._composedParent = parentNode;
4462 }
4463 function remove(node) {
4464 var parentNode = getComposedParent(node);
4465 if (parentNode) {
4466 node._composedParent = null;
4467 nativeRemoveChild.call(parentNode, node);
4468 }
4469 }
4470 function ensureComposedParent(parent, children) {
4471 for (var i = 0, n; i < children.length; i++) {
4472 children[i]._composedParent = parent;
4473 }
4474 }
4475 function getTopDistributingHost(host) {
4476 while (host && hostNeedsRedistribution(host)) {
4477 host = host.domHost;
4478 }
4479 return host;
4480 }
4481 function hostNeedsRedistribution(host) {
4482 var c$ = Polymer.dom(host).children;
4483 for (var i = 0, c; i < c$.length; i++) {
4484 c = c$[i];
4485 if (c.localName === 'content') {
4486 return host.domHost;
4487 }
4488 }
4489 }
4490 var needsUpgrade = window.CustomElements && !CustomElements.useNative;
4491 function upgradeLightChildren(children) {
4492 if (needsUpgrade && children) {
4493 for (var i = 0; i < children.length; i++) {
4494 CustomElements.upgrade(children[i]);
4495 }
4496 }
4497 }
4498 }());
4499 if (Polymer.Settings.useShadow) {
4500 Polymer.Base._addFeature({
4501 _poolContent: function () {
4502 },
4503 _beginDistribute: function () {
4504 },
4505 distributeContent: function () {
4506 },
4507 _distributeContent: function () {
4508 },
4509 _finishDistribute: function () {
4510 },
4511 _createLocalRoot: function () {
4512 this.createShadowRoot();
4513 this.shadowRoot.appendChild(this.root);
4514 this.root = this.shadowRoot;
4515 }
4516 });
4517 }
4518 Polymer.DomModule = document.createElement('dom-module');
4519 Polymer.Base._addFeature({
4520 _registerFeatures: function () {
4521 this._prepIs();
4522 this._prepAttributes();
4523 this._prepBehaviors();
4524 this._prepConstructor();
4525 this._prepTemplate();
4526 this._prepShady();
4527 },
4528 _prepBehavior: function (b) {
4529 this._addHostAttributes(b.hostAttributes);
4530 },
4531 _initFeatures: function () {
4532 this._poolContent();
4533 this._pushHost();
4534 this._stampTemplate();
4535 this._popHost();
4536 this._marshalHostAttributes();
4537 this._setupDebouncers();
4538 this._marshalBehaviors();
4539 this._tryReady();
4540 },
4541 _marshalBehavior: function (b) {
4542 }
4543 });
4544 Polymer.nar = [];
4545 Polymer.Annotations = {
4546 parseAnnotations: function (template) {
4547 var list = [];
4548 var content = template._content || template.content;
4549 this._parseNodeAnnotations(content, list);
4550 return list;
4551 },
4552 _parseNodeAnnotations: function (node, list) {
4553 return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, li st) : this._parseElementAnnotations(node, list);
4554 },
4555 _testEscape: function (value) {
4556 var escape = value.slice(0, 2);
4557 if (escape === '{{' || escape === '[[') {
4558 return escape;
4559 }
4560 },
4561 _parseTextNodeAnnotation: function (node, list) {
4562 var v = node.textContent;
4563 var escape = this._testEscape(v);
4564 if (escape) {
4565 node.textContent = ' ';
4566 var annote = {
4567 bindings: [{
4568 kind: 'text',
4569 mode: escape[0],
4570 value: v.slice(2, -2).trim()
4571 }]
4572 };
4573 list.push(annote);
4574 return annote;
4575 }
4576 },
4577 _parseElementAnnotations: function (element, list) {
4578 var annote = {
4579 bindings: [],
4580 events: []
4581 };
4582 if (element.localName === 'content') {
4583 list._hasContent = true;
4584 }
4585 this._parseChildNodesAnnotations(element, annote, list);
4586 if (element.attributes) {
4587 this._parseNodeAttributeAnnotations(element, annote, list);
4588 if (this.prepElement) {
4589 this.prepElement(element);
4590 }
4591 }
4592 if (annote.bindings.length || annote.events.length || annote.id) {
4593 list.push(annote);
4594 }
4595 return annote;
4596 },
4597 _parseChildNodesAnnotations: function (root, annote, list, callback) {
4598 if (root.firstChild) {
4599 for (var i = 0, node = root.firstChild; node; node = node.nextSibling, i++) {
4600 if (node.localName === 'template' && !node.hasAttribute('preserve-content')) {
4601 this._parseTemplate(node, i, list, annote);
4602 }
4603 if (node.nodeType === Node.TEXT_NODE) {
4604 var n = node.nextSibling;
4605 while (n && n.nodeType === Node.TEXT_NODE) {
4606 node.textContent += n.textContent;
4607 root.removeChild(n);
4608 n = n.nextSibling;
4609 }
4610 }
4611 var childAnnotation = this._parseNodeAnnotations(node, list, callback);
4612 if (childAnnotation) {
4613 childAnnotation.parent = annote;
4614 childAnnotation.index = i;
4615 }
4616 }
4617 }
4618 },
4619 _parseTemplate: function (node, index, list, parent) {
4620 var content = document.createDocumentFragment();
4621 content._notes = this.parseAnnotations(node);
4622 content.appendChild(node.content);
4623 list.push({
4624 bindings: Polymer.nar,
4625 events: Polymer.nar,
4626 templateContent: content,
4627 parent: parent,
4628 index: index
4629 });
4630 },
4631 _parseNodeAttributeAnnotations: function (node, annotation) {
4632 for (var i = node.attributes.length - 1, a; a = node.attributes[i]; i--) {
4633 var n = a.name, v = a.value;
4634 if (n === 'id' && !this._testEscape(v)) {
4635 annotation.id = v;
4636 } else if (n.slice(0, 3) === 'on-') {
4637 node.removeAttribute(n);
4638 annotation.events.push({
4639 name: n.slice(3),
4640 value: v
4641 });
4642 } else {
4643 var b = this._parseNodeAttributeAnnotation(node, n, v);
4644 if (b) {
4645 annotation.bindings.push(b);
4646 }
4647 }
4648 }
4649 },
4650 _parseNodeAttributeAnnotation: function (node, n, v) {
4651 var escape = this._testEscape(v);
4652 if (escape) {
4653 var customEvent;
4654 var name = n;
4655 var mode = escape[0];
4656 v = v.slice(2, -2).trim();
4657 var not = false;
4658 if (v[0] == '!') {
4659 v = v.substring(1);
4660 not = true;
4661 }
4662 var kind = 'property';
4663 if (n[n.length - 1] == '$') {
4664 name = n.slice(0, -1);
4665 kind = 'attribute';
4666 }
4667 var notifyEvent, colon;
4668 if (mode == '{' && (colon = v.indexOf('::')) > 0) {
4669 notifyEvent = v.substring(colon + 2);
4670 v = v.substring(0, colon);
4671 customEvent = true;
4672 }
4673 if (node.localName == 'input' && n == 'value') {
4674 node.setAttribute(n, '');
4675 }
4676 node.removeAttribute(n);
4677 if (kind === 'property') {
4678 name = Polymer.CaseMap.dashToCamelCase(name);
4679 }
4680 return {
4681 kind: kind,
4682 mode: mode,
4683 name: name,
4684 value: v,
4685 negate: not,
4686 event: notifyEvent,
4687 customEvent: customEvent
4688 };
4689 }
4690 },
4691 _localSubTree: function (node, host) {
4692 return node === host ? node.childNodes : node._lightChildren || node.childNodes;
4693 },
4694 findAnnotatedNode: function (root, annote) {
4695 var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote .parent);
4696 return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.i ndex];
4697 }
4698 };
4699 (function () {
4700 function resolveCss(cssText, ownerDocument) {
4701 return cssText.replace(CSS_URL_RX, function (m, pre, url, post) {
4702 return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + po st;
4703 });
4704 }
4705 function resolveAttrs(element, ownerDocument) {
4706 for (var name in URL_ATTRS) {
4707 var a$ = URL_ATTRS[name];
4708 for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) {
4709 if (name === '*' || element.localName === name) {
4710 at = element.attributes[a];
4711 v = at && at.value;
4712 if (v && v.search(BINDING_RX) < 0) {
4713 at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocume nt);
4714 }
4715 }
4716 }
4717 }
4718 }
4719 function resolve(url, ownerDocument) {
4720 if (url && url[0] === '#') {
4721 return url;
4722 }
4723 var resolver = getUrlResolver(ownerDocument);
4724 resolver.href = url;
4725 return resolver.href || url;
4726 }
4727 var tempDoc;
4728 var tempDocBase;
4729 function resolveUrl(url, baseUri) {
4730 if (!tempDoc) {
4731 tempDoc = document.implementation.createHTMLDocument('temp');
4732 tempDocBase = tempDoc.createElement('base');
4733 tempDoc.head.appendChild(tempDocBase);
4734 }
4735 tempDocBase.href = baseUri;
4736 return resolve(url, tempDoc);
4737 }
4738 function getUrlResolver(ownerDocument) {
4739 return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocume nt.createElement('a'));
4740 }
4741 var CSS_URL_RX = /(url\()([^)]*)(\))/g;
4742 var URL_ATTRS = {
4743 '*': [
4744 'href',
4745 'src',
4746 'style',
4747 'url'
4748 ],
4749 form: ['action']
4750 };
4751 var BINDING_RX = /\{\{|\[\[/;
4752 Polymer.ResolveUrl = {
4753 resolveCss: resolveCss,
4754 resolveAttrs: resolveAttrs,
4755 resolveUrl: resolveUrl
4756 };
4757 }());
4758 Polymer.Base._addFeature({
4759 _prepAnnotations: function () {
4760 if (!this._template) {
4761 this._notes = [];
4762 } else {
4763 Polymer.Annotations.prepElement = this._prepElement.bind(this);
4764 if (this._template._content && this._template._content._notes) {
4765 this._notes = this._template._content._notes;
4766 } else {
4767 this._notes = Polymer.Annotations.parseAnnotations(this._template);
4768 }
4769 this._processAnnotations(this._notes);
4770 Polymer.Annotations.prepElement = null;
4771 }
4772 },
4773 _processAnnotations: function (notes) {
4774 for (var i = 0; i < notes.length; i++) {
4775 var note = notes[i];
4776 for (var j = 0; j < note.bindings.length; j++) {
4777 var b = note.bindings[j];
4778 b.signature = this._parseMethod(b.value);
4779 if (!b.signature) {
4780 b.model = this._modelForPath(b.value);
4781 }
4782 }
4783 if (note.templateContent) {
4784 this._processAnnotations(note.templateContent._notes);
4785 var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(n ote.templateContent._notes);
4786 var bindings = [];
4787 for (var prop in pp) {
4788 bindings.push({
4789 index: note.index,
4790 kind: 'property',
4791 mode: '{',
4792 name: '_parent_' + prop,
4793 model: prop,
4794 value: prop
4795 });
4796 }
4797 note.bindings = note.bindings.concat(bindings);
4798 }
4799 }
4800 },
4801 _discoverTemplateParentProps: function (notes) {
4802 var pp = {};
4803 notes.forEach(function (n) {
4804 n.bindings.forEach(function (b) {
4805 if (b.signature) {
4806 var args = b.signature.args;
4807 for (var k = 0; k < args.length; k++) {
4808 pp[args[k].model] = true;
4809 }
4810 } else {
4811 pp[b.model] = true;
4812 }
4813 });
4814 if (n.templateContent) {
4815 var tpp = n.templateContent._parentProps;
4816 Polymer.Base.mixin(pp, tpp);
4817 }
4818 });
4819 return pp;
4820 },
4821 _prepElement: function (element) {
4822 Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument);
4823 },
4824 _findAnnotatedNode: Polymer.Annotations.findAnnotatedNode,
4825 _marshalAnnotationReferences: function () {
4826 if (this._template) {
4827 this._marshalIdNodes();
4828 this._marshalAnnotatedNodes();
4829 this._marshalAnnotatedListeners();
4830 }
4831 },
4832 _configureAnnotationReferences: function () {
4833 this._configureTemplateContent();
4834 },
4835 _configureTemplateContent: function () {
4836 this._notes.forEach(function (note, i) {
4837 if (note.templateContent) {
4838 this._nodes[i]._content = note.templateContent;
4839 }
4840 }, this);
4841 },
4842 _marshalIdNodes: function () {
4843 this.$ = {};
4844 this._notes.forEach(function (a) {
4845 if (a.id) {
4846 this.$[a.id] = this._findAnnotatedNode(this.root, a);
4847 }
4848 }, this);
4849 },
4850 _marshalAnnotatedNodes: function () {
4851 if (this._nodes) {
4852 this._nodes = this._nodes.map(function (a) {
4853 return this._findAnnotatedNode(this.root, a);
4854 }, this);
4855 }
4856 },
4857 _marshalAnnotatedListeners: function () {
4858 this._notes.forEach(function (a) {
4859 if (a.events && a.events.length) {
4860 var node = this._findAnnotatedNode(this.root, a);
4861 a.events.forEach(function (e) {
4862 this.listen(node, e.name, e.value);
4863 }, this);
4864 }
4865 }, this);
4866 }
4867 });
4868 Polymer.Base._addFeature({
4869 listeners: {},
4870 _listenListeners: function (listeners) {
4871 var node, name, key;
4872 for (key in listeners) {
4873 if (key.indexOf('.') < 0) {
4874 node = this;
4875 name = key;
4876 } else {
4877 name = key.split('.');
4878 node = this.$[name[0]];
4879 name = name[1];
4880 }
4881 this.listen(node, name, listeners[key]);
4882 }
4883 },
4884 listen: function (node, eventName, methodName) {
4885 this._listen(node, eventName, this._createEventHandler(node, eventName, methodNa me));
4886 },
4887 _boundListenerKey: function (eventName, methodName) {
4888 return eventName + ':' + methodName;
4889 },
4890 _recordEventHandler: function (host, eventName, target, methodName, handler) {
4891 var hbl = host.__boundListeners;
4892 if (!hbl) {
4893 hbl = host.__boundListeners = new WeakMap();
4894 }
4895 var bl = hbl.get(target);
4896 if (!bl) {
4897 bl = {};
4898 hbl.set(target, bl);
4899 }
4900 var key = this._boundListenerKey(eventName, methodName);
4901 bl[key] = handler;
4902 },
4903 _recallEventHandler: function (host, eventName, target, methodName) {
4904 var hbl = host.__boundListeners;
4905 if (!hbl) {
4906 return;
4907 }
4908 var bl = hbl.get(target);
4909 if (!bl) {
4910 return;
4911 }
4912 var key = this._boundListenerKey(eventName, methodName);
4913 return bl[key];
4914 },
4915 _createEventHandler: function (node, eventName, methodName) {
4916 var host = this;
4917 var handler = function (e) {
4918 if (host[methodName]) {
4919 host[methodName](e, e.detail);
4920 } else {
4921 host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined'));
4922 }
4923 };
4924 this._recordEventHandler(host, eventName, node, methodName, handler);
4925 return handler;
4926 },
4927 unlisten: function (node, eventName, methodName) {
4928 var handler = this._recallEventHandler(this, eventName, node, methodName);
4929 if (handler) {
4930 this._unlisten(node, eventName, handler);
4931 }
4932 },
4933 _listen: function (node, eventName, handler) {
4934 node.addEventListener(eventName, handler);
4935 },
4936 _unlisten: function (node, eventName, handler) {
4937 node.removeEventListener(eventName, handler);
4938 }
4939 });
4940 (function () {
4941 'use strict';
4942 var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
4943 var GESTURE_KEY = '__polymerGestures';
4944 var HANDLED_OBJ = '__polymerGesturesHandled';
4945 var TOUCH_ACTION = '__polymerGesturesTouchAction';
4946 var TAP_DISTANCE = 25;
4947 var TRACK_DISTANCE = 5;
4948 var TRACK_LENGTH = 2;
4949 var MOUSE_TIMEOUT = 2500;
4950 var MOUSE_EVENTS = [
4951 'mousedown',
4952 'mousemove',
4953 'mouseup',
4954 'click'
4955 ];
4956 var MOUSE_WHICH_TO_BUTTONS = [
4957 0,
4958 1,
4959 4,
4960 2
4961 ];
4962 var MOUSE_HAS_BUTTONS = function () {
4963 try {
4964 return new MouseEvent('test', { buttons: 1 }).buttons === 1;
4965 } catch (e) {
4966 return false;
4967 }
4968 }();
4969 var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
4970 var mouseCanceller = function (mouseEvent) {
4971 mouseEvent[HANDLED_OBJ] = { skip: true };
4972 if (mouseEvent.type === 'click') {
4973 var path = Polymer.dom(mouseEvent).path;
4974 for (var i = 0; i < path.length; i++) {
4975 if (path[i] === POINTERSTATE.mouse.target) {
4976 return;
4977 }
4978 }
4979 mouseEvent.preventDefault();
4980 mouseEvent.stopPropagation();
4981 }
4982 };
4983 function setupTeardownMouseCanceller(setup) {
4984 for (var i = 0, en; i < MOUSE_EVENTS.length; i++) {
4985 en = MOUSE_EVENTS[i];
4986 if (setup) {
4987 document.addEventListener(en, mouseCanceller, true);
4988 } else {
4989 document.removeEventListener(en, mouseCanceller, true);
4990 }
4991 }
4992 }
4993 function ignoreMouse() {
4994 if (IS_TOUCH_ONLY) {
4995 return;
4996 }
4997 if (!POINTERSTATE.mouse.mouseIgnoreJob) {
4998 setupTeardownMouseCanceller(true);
4999 }
5000 var unset = function () {
5001 setupTeardownMouseCanceller();
5002 POINTERSTATE.mouse.target = null;
5003 POINTERSTATE.mouse.mouseIgnoreJob = null;
5004 };
5005 POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgn oreJob, unset, MOUSE_TIMEOUT);
5006 }
5007 function hasLeftMouseButton(ev) {
5008 var type = ev.type;
5009 if (MOUSE_EVENTS.indexOf(type) === -1) {
5010 return false;
5011 }
5012 if (type === 'mousemove') {
5013 var buttons = ev.buttons === undefined ? 1 : ev.buttons;
5014 if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) {
5015 buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
5016 }
5017 return Boolean(buttons & 1);
5018 } else {
5019 var button = ev.button === undefined ? 0 : ev.button;
5020 return button === 0;
5021 }
5022 }
5023 function isSyntheticClick(ev) {
5024 if (ev.type === 'click') {
5025 if (ev.detail === 0) {
5026 return true;
5027 }
5028 var t = Gestures.findOriginalTarget(ev);
5029 var bcr = t.getBoundingClientRect();
5030 var x = ev.pageX, y = ev.pageY;
5031 return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom));
5032 }
5033 return false;
5034 }
5035 var POINTERSTATE = {
5036 mouse: {
5037 target: null,
5038 mouseIgnoreJob: null
5039 },
5040 touch: {
5041 x: 0,
5042 y: 0,
5043 id: -1,
5044 scrollDecided: false
5045 }
5046 };
5047 function firstTouchAction(ev) {
5048 var path = Polymer.dom(ev).path;
5049 var ta = 'auto';
5050 for (var i = 0, n; i < path.length; i++) {
5051 n = path[i];
5052 if (n[TOUCH_ACTION]) {
5053 ta = n[TOUCH_ACTION];
5054 break;
5055 }
5056 }
5057 return ta;
5058 }
5059 function trackDocument(stateObj, movefn, upfn) {
5060 stateObj.movefn = movefn;
5061 stateObj.upfn = upfn;
5062 document.addEventListener('mousemove', movefn);
5063 document.addEventListener('mouseup', upfn);
5064 }
5065 function untrackDocument(stateObj) {
5066 document.removeEventListener('mousemove', stateObj.movefn);
5067 document.removeEventListener('mouseup', stateObj.upfn);
5068 }
5069 var Gestures = {
5070 gestures: {},
5071 recognizers: [],
5072 deepTargetFind: function (x, y) {
5073 var node = document.elementFromPoint(x, y);
5074 var next = node;
5075 while (next && next.shadowRoot) {
5076 next = next.shadowRoot.elementFromPoint(x, y);
5077 if (next) {
5078 node = next;
5079 }
5080 }
5081 return node;
5082 },
5083 findOriginalTarget: function (ev) {
5084 if (ev.path) {
5085 return ev.path[0];
5086 }
5087 return ev.target;
5088 },
5089 handleNative: function (ev) {
5090 var handled;
5091 var type = ev.type;
5092 var node = ev.currentTarget;
5093 var gobj = node[GESTURE_KEY];
5094 var gs = gobj[type];
5095 if (!gs) {
5096 return;
5097 }
5098 if (!ev[HANDLED_OBJ]) {
5099 ev[HANDLED_OBJ] = {};
5100 if (type.slice(0, 5) === 'touch') {
5101 var t = ev.changedTouches[0];
5102 if (type === 'touchstart') {
5103 if (ev.touches.length === 1) {
5104 POINTERSTATE.touch.id = t.identifier;
5105 }
5106 }
5107 if (POINTERSTATE.touch.id !== t.identifier) {
5108 return;
5109 }
5110 if (!HAS_NATIVE_TA) {
5111 if (type === 'touchstart' || type === 'touchmove') {
5112 Gestures.handleTouchAction(ev);
5113 }
5114 }
5115 if (type === 'touchend') {
5116 POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget;
5117 ignoreMouse(true);
5118 }
5119 }
5120 }
5121 handled = ev[HANDLED_OBJ];
5122 if (handled.skip) {
5123 return;
5124 }
5125 var recognizers = Gestures.recognizers;
5126 for (var i = 0, r; i < recognizers.length; i++) {
5127 r = recognizers[i];
5128 if (gs[r.name] && !handled[r.name]) {
5129 if (r.flow && r.flow.start.indexOf(ev.type) > -1) {
5130 if (r.reset) {
5131 r.reset();
5132 }
5133 }
5134 }
5135 }
5136 for (var i = 0, r; i < recognizers.length; i++) {
5137 r = recognizers[i];
5138 if (gs[r.name] && !handled[r.name]) {
5139 handled[r.name] = true;
5140 r[type](ev);
5141 }
5142 }
5143 },
5144 handleTouchAction: function (ev) {
5145 var t = ev.changedTouches[0];
5146 var type = ev.type;
5147 if (type === 'touchstart') {
5148 POINTERSTATE.touch.x = t.clientX;
5149 POINTERSTATE.touch.y = t.clientY;
5150 POINTERSTATE.touch.scrollDecided = false;
5151 } else if (type === 'touchmove') {
5152 if (POINTERSTATE.touch.scrollDecided) {
5153 return;
5154 }
5155 POINTERSTATE.touch.scrollDecided = true;
5156 var ta = firstTouchAction(ev);
5157 var prevent = false;
5158 var dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
5159 var dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
5160 if (!ev.cancelable) {
5161 } else if (ta === 'none') {
5162 prevent = true;
5163 } else if (ta === 'pan-x') {
5164 prevent = dy > dx;
5165 } else if (ta === 'pan-y') {
5166 prevent = dx > dy;
5167 }
5168 if (prevent) {
5169 ev.preventDefault();
5170 } else {
5171 Gestures.prevent('track');
5172 }
5173 }
5174 },
5175 add: function (node, evType, handler) {
5176 var recognizer = this.gestures[evType];
5177 var deps = recognizer.deps;
5178 var name = recognizer.name;
5179 var gobj = node[GESTURE_KEY];
5180 if (!gobj) {
5181 node[GESTURE_KEY] = gobj = {};
5182 }
5183 for (var i = 0, dep, gd; i < deps.length; i++) {
5184 dep = deps[i];
5185 if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) {
5186 continue;
5187 }
5188 gd = gobj[dep];
5189 if (!gd) {
5190 gobj[dep] = gd = { _count: 0 };
5191 }
5192 if (gd._count === 0) {
5193 node.addEventListener(dep, this.handleNative);
5194 }
5195 gd[name] = (gd[name] || 0) + 1;
5196 gd._count = (gd._count || 0) + 1;
5197 }
5198 node.addEventListener(evType, handler);
5199 if (recognizer.touchAction) {
5200 this.setTouchAction(node, recognizer.touchAction);
5201 }
5202 },
5203 remove: function (node, evType, handler) {
5204 var recognizer = this.gestures[evType];
5205 var deps = recognizer.deps;
5206 var name = recognizer.name;
5207 var gobj = node[GESTURE_KEY];
5208 if (gobj) {
5209 for (var i = 0, dep, gd; i < deps.length; i++) {
5210 dep = deps[i];
5211 gd = gobj[dep];
5212 if (gd && gd[name]) {
5213 gd[name] = (gd[name] || 1) - 1;
5214 gd._count = (gd._count || 1) - 1;
5215 if (gd._count === 0) {
5216 node.removeEventListener(dep, this.handleNative);
5217 }
5218 }
5219 }
5220 }
5221 node.removeEventListener(evType, handler);
5222 },
5223 register: function (recog) {
5224 this.recognizers.push(recog);
5225 for (var i = 0; i < recog.emits.length; i++) {
5226 this.gestures[recog.emits[i]] = recog;
5227 }
5228 },
5229 findRecognizerByEvent: function (evName) {
5230 for (var i = 0, r; i < this.recognizers.length; i++) {
5231 r = this.recognizers[i];
5232 for (var j = 0, n; j < r.emits.length; j++) {
5233 n = r.emits[j];
5234 if (n === evName) {
5235 return r;
5236 }
5237 }
5238 }
5239 return null;
5240 },
5241 setTouchAction: function (node, value) {
5242 if (HAS_NATIVE_TA) {
5243 node.style.touchAction = value;
5244 }
5245 node[TOUCH_ACTION] = value;
5246 },
5247 fire: function (target, type, detail) {
5248 var ev = Polymer.Base.fire(type, detail, {
5249 node: target,
5250 bubbles: true,
5251 cancelable: true
5252 });
5253 if (ev.defaultPrevented) {
5254 var se = detail.sourceEvent;
5255 if (se && se.preventDefault) {
5256 se.preventDefault();
5257 }
5258 }
5259 },
5260 prevent: function (evName) {
5261 var recognizer = this.findRecognizerByEvent(evName);
5262 if (recognizer.info) {
5263 recognizer.info.prevent = true;
5264 }
5265 }
5266 };
5267 Gestures.register({
5268 name: 'downup',
5269 deps: [
5270 'mousedown',
5271 'touchstart',
5272 'touchend'
5273 ],
5274 flow: {
5275 start: [
5276 'mousedown',
5277 'touchstart'
5278 ],
5279 end: [
5280 'mouseup',
5281 'touchend'
5282 ]
5283 },
5284 emits: [
5285 'down',
5286 'up'
5287 ],
5288 info: {
5289 movefn: function () {
5290 },
5291 upfn: function () {
5292 }
5293 },
5294 reset: function () {
5295 untrackDocument(this.info);
5296 },
5297 mousedown: function (e) {
5298 if (!hasLeftMouseButton(e)) {
5299 return;
5300 }
5301 var t = Gestures.findOriginalTarget(e);
5302 var self = this;
5303 var movefn = function movefn(e) {
5304 if (!hasLeftMouseButton(e)) {
5305 self.fire('up', t, e);
5306 untrackDocument(self.info);
5307 }
5308 };
5309 var upfn = function upfn(e) {
5310 if (hasLeftMouseButton(e)) {
5311 self.fire('up', t, e);
5312 }
5313 untrackDocument(self.info);
5314 };
5315 trackDocument(this.info, movefn, upfn);
5316 this.fire('down', t, e);
5317 },
5318 touchstart: function (e) {
5319 this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]);
5320 },
5321 touchend: function (e) {
5322 this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]);
5323 },
5324 fire: function (type, target, event) {
5325 var self = this;
5326 Gestures.fire(target, type, {
5327 x: event.clientX,
5328 y: event.clientY,
5329 sourceEvent: event,
5330 prevent: Gestures.prevent.bind(Gestures)
5331 });
5332 }
5333 });
5334 Gestures.register({
5335 name: 'track',
5336 touchAction: 'none',
5337 deps: [
5338 'mousedown',
5339 'touchstart',
5340 'touchmove',
5341 'touchend'
5342 ],
5343 flow: {
5344 start: [
5345 'mousedown',
5346 'touchstart'
5347 ],
5348 end: [
5349 'mouseup',
5350 'touchend'
5351 ]
5352 },
5353 emits: ['track'],
5354 info: {
5355 x: 0,
5356 y: 0,
5357 state: 'start',
5358 started: false,
5359 moves: [],
5360 addMove: function (move) {
5361 if (this.moves.length > TRACK_LENGTH) {
5362 this.moves.shift();
5363 }
5364 this.moves.push(move);
5365 },
5366 movefn: function () {
5367 },
5368 upfn: function () {
5369 },
5370 prevent: false
5371 },
5372 reset: function () {
5373 this.info.state = 'start';
5374 this.info.started = false;
5375 this.info.moves = [];
5376 this.info.x = 0;
5377 this.info.y = 0;
5378 this.info.prevent = false;
5379 untrackDocument(this.info);
5380 },
5381 hasMovedEnough: function (x, y) {
5382 if (this.info.prevent) {
5383 return false;
5384 }
5385 if (this.info.started) {
5386 return true;
5387 }
5388 var dx = Math.abs(this.info.x - x);
5389 var dy = Math.abs(this.info.y - y);
5390 return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE;
5391 },
5392 mousedown: function (e) {
5393 if (!hasLeftMouseButton(e)) {
5394 return;
5395 }
5396 var t = Gestures.findOriginalTarget(e);
5397 var self = this;
5398 var movefn = function movefn(e) {
5399 var x = e.clientX, y = e.clientY;
5400 if (self.hasMovedEnough(x, y)) {
5401 self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : ' start';
5402 self.info.addMove({
5403 x: x,
5404 y: y
5405 });
5406 if (!hasLeftMouseButton(e)) {
5407 self.info.state = 'end';
5408 untrackDocument(self.info);
5409 }
5410 self.fire(t, e);
5411 self.info.started = true;
5412 }
5413 };
5414 var upfn = function upfn(e) {
5415 if (self.info.started) {
5416 Gestures.prevent('tap');
5417 movefn(e);
5418 }
5419 untrackDocument(self.info);
5420 };
5421 trackDocument(this.info, movefn, upfn);
5422 this.info.x = e.clientX;
5423 this.info.y = e.clientY;
5424 },
5425 touchstart: function (e) {
5426 var ct = e.changedTouches[0];
5427 this.info.x = ct.clientX;
5428 this.info.y = ct.clientY;
5429 },
5430 touchmove: function (e) {
5431 var t = Gestures.findOriginalTarget(e);
5432 var ct = e.changedTouches[0];
5433 var x = ct.clientX, y = ct.clientY;
5434 if (this.hasMovedEnough(x, y)) {
5435 this.info.addMove({
5436 x: x,
5437 y: y
5438 });
5439 this.fire(t, ct);
5440 this.info.state = 'track';
5441 this.info.started = true;
5442 }
5443 },
5444 touchend: function (e) {
5445 var t = Gestures.findOriginalTarget(e);
5446 var ct = e.changedTouches[0];
5447 if (this.info.started) {
5448 Gestures.prevent('tap');
5449 this.info.state = 'end';
5450 this.info.addMove({
5451 x: ct.clientX,
5452 y: ct.clientY
5453 });
5454 this.fire(t, ct);
5455 }
5456 },
5457 fire: function (target, touch) {
5458 var secondlast = this.info.moves[this.info.moves.length - 2];
5459 var lastmove = this.info.moves[this.info.moves.length - 1];
5460 var dx = lastmove.x - this.info.x;
5461 var dy = lastmove.y - this.info.y;
5462 var ddx, ddy = 0;
5463 if (secondlast) {
5464 ddx = lastmove.x - secondlast.x;
5465 ddy = lastmove.y - secondlast.y;
5466 }
5467 return Gestures.fire(target, 'track', {
5468 state: this.info.state,
5469 x: touch.clientX,
5470 y: touch.clientY,
5471 dx: dx,
5472 dy: dy,
5473 ddx: ddx,
5474 ddy: ddy,
5475 sourceEvent: touch,
5476 hover: function () {
5477 return Gestures.deepTargetFind(touch.clientX, touch.clientY);
5478 }
5479 });
5480 }
5481 });
5482 Gestures.register({
5483 name: 'tap',
5484 deps: [
5485 'mousedown',
5486 'click',
5487 'touchstart',
5488 'touchend'
5489 ],
5490 flow: {
5491 start: [
5492 'mousedown',
5493 'touchstart'
5494 ],
5495 end: [
5496 'click',
5497 'touchend'
5498 ]
5499 },
5500 emits: ['tap'],
5501 info: {
5502 x: NaN,
5503 y: NaN,
5504 prevent: false
5505 },
5506 reset: function () {
5507 this.info.x = NaN;
5508 this.info.y = NaN;
5509 this.info.prevent = false;
5510 },
5511 save: function (e) {
5512 this.info.x = e.clientX;
5513 this.info.y = e.clientY;
5514 },
5515 mousedown: function (e) {
5516 if (hasLeftMouseButton(e)) {
5517 this.save(e);
5518 }
5519 },
5520 click: function (e) {
5521 if (hasLeftMouseButton(e)) {
5522 this.forward(e);
5523 }
5524 },
5525 touchstart: function (e) {
5526 this.save(e.changedTouches[0]);
5527 },
5528 touchend: function (e) {
5529 this.forward(e.changedTouches[0]);
5530 },
5531 forward: function (e) {
5532 var dx = Math.abs(e.clientX - this.info.x);
5533 var dy = Math.abs(e.clientY - this.info.y);
5534 var t = Gestures.findOriginalTarget(e);
5535 if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSynt heticClick(e)) {
5536 if (!this.info.prevent) {
5537 Gestures.fire(t, 'tap', {
5538 x: e.clientX,
5539 y: e.clientY,
5540 sourceEvent: e
5541 });
5542 }
5543 }
5544 }
5545 });
5546 var DIRECTION_MAP = {
5547 x: 'pan-x',
5548 y: 'pan-y',
5549 none: 'none',
5550 all: 'auto'
5551 };
5552 Polymer.Base._addFeature({
5553 _listen: function (node, eventName, handler) {
5554 if (Gestures.gestures[eventName]) {
5555 Gestures.add(node, eventName, handler);
5556 } else {
5557 node.addEventListener(eventName, handler);
5558 }
5559 },
5560 _unlisten: function (node, eventName, handler) {
5561 if (Gestures.gestures[eventName]) {
5562 Gestures.remove(node, eventName, handler);
5563 } else {
5564 node.removeEventListener(eventName, handler);
5565 }
5566 },
5567 setScrollDirection: function (direction, node) {
5568 node = node || this;
5569 Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto');
5570 }
5571 });
5572 Polymer.Gestures = Gestures;
5573 }());
5574 Polymer.Async = {
5575 _currVal: 0,
5576 _lastVal: 0,
5577 _callbacks: [],
5578 _twiddleContent: 0,
5579 _twiddle: document.createTextNode(''),
5580 run: function (callback, waitTime) {
5581 if (waitTime > 0) {
5582 return ~setTimeout(callback, waitTime);
5583 } else {
5584 this._twiddle.textContent = this._twiddleContent++;
5585 this._callbacks.push(callback);
5586 return this._currVal++;
5587 }
5588 },
5589 cancel: function (handle) {
5590 if (handle < 0) {
5591 clearTimeout(~handle);
5592 } else {
5593 var idx = handle - this._lastVal;
5594 if (idx >= 0) {
5595 if (!this._callbacks[idx]) {
5596 throw 'invalid async handle: ' + handle;
5597 }
5598 this._callbacks[idx] = null;
5599 }
5600 }
5601 },
5602 _atEndOfMicrotask: function () {
5603 var len = this._callbacks.length;
5604 for (var i = 0; i < len; i++) {
5605 var cb = this._callbacks[i];
5606 if (cb) {
5607 try {
5608 cb();
5609 } catch (e) {
5610 i++;
5611 this._callbacks.splice(0, i);
5612 this._lastVal += i;
5613 this._twiddle.textContent = this._twiddleContent++;
5614 throw e;
5615 }
5616 }
5617 }
5618 this._callbacks.splice(0, len);
5619 this._lastVal += len;
5620 }
5621 };
5622 new (window.MutationObserver || JsMutationObserver)(Polymer.Async._atEndOfMicrot ask.bind(Polymer.Async)).observe(Polymer.Async._twiddle, { characterData: true } );
5623 Polymer.Debounce = function () {
5624 var Async = Polymer.Async;
5625 var Debouncer = function (context) {
5626 this.context = context;
5627 this.boundComplete = this.complete.bind(this);
5628 };
5629 Debouncer.prototype = {
5630 go: function (callback, wait) {
5631 var h;
5632 this.finish = function () {
5633 Async.cancel(h);
5634 };
5635 h = Async.run(this.boundComplete, wait);
5636 this.callback = callback;
5637 },
5638 stop: function () {
5639 if (this.finish) {
5640 this.finish();
5641 this.finish = null;
5642 }
5643 },
5644 complete: function () {
5645 if (this.finish) {
5646 this.stop();
5647 this.callback.call(this.context);
5648 }
5649 }
5650 };
5651 function debounce(debouncer, callback, wait) {
5652 if (debouncer) {
5653 debouncer.stop();
5654 } else {
5655 debouncer = new Debouncer(this);
5656 }
5657 debouncer.go(callback, wait);
5658 return debouncer;
5659 }
5660 return debounce;
5661 }();
5662 Polymer.Base._addFeature({
5663 $$: function (slctr) {
5664 return Polymer.dom(this.root).querySelector(slctr);
5665 },
5666 toggleClass: function (name, bool, node) {
5667 node = node || this;
5668 if (arguments.length == 1) {
5669 bool = !node.classList.contains(name);
5670 }
5671 if (bool) {
5672 Polymer.dom(node).classList.add(name);
5673 } else {
5674 Polymer.dom(node).classList.remove(name);
5675 }
5676 },
5677 toggleAttribute: function (name, bool, node) {
5678 node = node || this;
5679 if (arguments.length == 1) {
5680 bool = !node.hasAttribute(name);
5681 }
5682 if (bool) {
5683 Polymer.dom(node).setAttribute(name, '');
5684 } else {
5685 Polymer.dom(node).removeAttribute(name);
5686 }
5687 },
5688 classFollows: function (name, toElement, fromElement) {
5689 if (fromElement) {
5690 Polymer.dom(fromElement).classList.remove(name);
5691 }
5692 if (toElement) {
5693 Polymer.dom(toElement).classList.add(name);
5694 }
5695 },
5696 attributeFollows: function (name, toElement, fromElement) {
5697 if (fromElement) {
5698 Polymer.dom(fromElement).removeAttribute(name);
5699 }
5700 if (toElement) {
5701 Polymer.dom(toElement).setAttribute(name, '');
5702 }
5703 },
5704 getContentChildNodes: function (slctr) {
5705 var content = Polymer.dom(this.root).querySelector(slctr || 'content');
5706 return content ? Polymer.dom(content).getDistributedNodes() : [];
5707 },
5708 getContentChildren: function (slctr) {
5709 return this.getContentChildNodes(slctr).filter(function (n) {
5710 return n.nodeType === Node.ELEMENT_NODE;
5711 });
5712 },
5713 fire: function (type, detail, options) {
5714 options = options || Polymer.nob;
5715 var node = options.node || this;
5716 var detail = detail === null || detail === undefined ? Polymer.nob : detail;
5717 var bubbles = options.bubbles === undefined ? true : options.bubbles;
5718 var cancelable = Boolean(options.cancelable);
5719 var event = new CustomEvent(type, {
5720 bubbles: Boolean(bubbles),
5721 cancelable: cancelable,
5722 detail: detail
5723 });
5724 node.dispatchEvent(event);
5725 return event;
5726 },
5727 async: function (callback, waitTime) {
5728 return Polymer.Async.run(callback.bind(this), waitTime);
5729 },
5730 cancelAsync: function (handle) {
5731 Polymer.Async.cancel(handle);
5732 },
5733 arrayDelete: function (path, item) {
5734 var index;
5735 if (Array.isArray(path)) {
5736 index = path.indexOf(item);
5737 if (index >= 0) {
5738 return path.splice(index, 1);
5739 }
5740 } else {
5741 var arr = this.get(path);
5742 index = arr.indexOf(item);
5743 if (index >= 0) {
5744 return this.splice(path, index, 1);
5745 }
5746 }
5747 },
5748 transform: function (transform, node) {
5749 node = node || this;
5750 node.style.webkitTransform = transform;
5751 node.style.transform = transform;
5752 },
5753 translate3d: function (x, y, z, node) {
5754 node = node || this;
5755 this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node);
5756 },
5757 importHref: function (href, onload, onerror) {
5758 var l = document.createElement('link');
5759 l.rel = 'import';
5760 l.href = href;
5761 if (onload) {
5762 l.onload = onload.bind(this);
5763 }
5764 if (onerror) {
5765 l.onerror = onerror.bind(this);
5766 }
5767 document.head.appendChild(l);
5768 return l;
5769 },
5770 create: function (tag, props) {
5771 var elt = document.createElement(tag);
5772 if (props) {
5773 for (var n in props) {
5774 elt[n] = props[n];
5775 }
5776 }
5777 return elt;
5778 }
5779 });
5780 Polymer.Bind = {
5781 prepareModel: function (model) {
5782 model._propertyEffects = {};
5783 model._bindListeners = [];
5784 Polymer.Base.mixin(model, this._modelApi);
5785 },
5786 _modelApi: {
5787 _notifyChange: function (property) {
5788 var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed';
5789 Polymer.Base.fire(eventName, { value: this[property] }, {
5790 bubbles: false,
5791 node: this
5792 });
5793 },
5794 _propertySetter: function (property, value, effects, fromAbove) {
5795 var old = this.__data__[property];
5796 if (old !== value && (old === old || value === value)) {
5797 this.__data__[property] = value;
5798 if (typeof value == 'object') {
5799 this._clearPath(property);
5800 }
5801 if (this._propertyChanged) {
5802 this._propertyChanged(property, value, old);
5803 }
5804 if (effects) {
5805 this._effectEffects(property, value, effects, old, fromAbove);
5806 }
5807 }
5808 return old;
5809 },
5810 __setProperty: function (property, value, quiet, node) {
5811 node = node || this;
5812 var effects = node._propertyEffects && node._propertyEffects[property];
5813 if (effects) {
5814 node._propertySetter(property, value, effects, quiet);
5815 } else {
5816 node[property] = value;
5817 }
5818 },
5819 _effectEffects: function (property, value, effects, old, fromAbove) {
5820 effects.forEach(function (fx) {
5821 var fn = Polymer.Bind['_' + fx.kind + 'Effect'];
5822 if (fn) {
5823 fn.call(this, property, value, fx.effect, old, fromAbove);
5824 }
5825 }, this);
5826 },
5827 _clearPath: function (path) {
5828 for (var prop in this.__data__) {
5829 if (prop.indexOf(path + '.') === 0) {
5830 this.__data__[prop] = undefined;
5831 }
5832 }
5833 }
5834 },
5835 ensurePropertyEffects: function (model, property) {
5836 var fx = model._propertyEffects[property];
5837 if (!fx) {
5838 fx = model._propertyEffects[property] = [];
5839 }
5840 return fx;
5841 },
5842 addPropertyEffect: function (model, property, kind, effect) {
5843 var fx = this.ensurePropertyEffects(model, property);
5844 fx.push({
5845 kind: kind,
5846 effect: effect
5847 });
5848 },
5849 createBindings: function (model) {
5850 var fx$ = model._propertyEffects;
5851 if (fx$) {
5852 for (var n in fx$) {
5853 var fx = fx$[n];
5854 fx.sort(this._sortPropertyEffects);
5855 this._createAccessors(model, n, fx);
5856 }
5857 }
5858 },
5859 _sortPropertyEffects: function () {
5860 var EFFECT_ORDER = {
5861 'compute': 0,
5862 'annotation': 1,
5863 'computedAnnotation': 2,
5864 'reflect': 3,
5865 'notify': 4,
5866 'observer': 5,
5867 'complexObserver': 6,
5868 'function': 7
5869 };
5870 return function (a, b) {
5871 return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind];
5872 };
5873 }(),
5874 _createAccessors: function (model, property, effects) {
5875 var defun = {
5876 get: function () {
5877 return this.__data__[property];
5878 }
5879 };
5880 var setter = function (value) {
5881 this._propertySetter(property, value, effects);
5882 };
5883 var info = model.getPropertyInfo && model.getPropertyInfo(property);
5884 if (info && info.readOnly) {
5885 if (!info.computed) {
5886 model['_set' + this.upper(property)] = setter;
5887 }
5888 } else {
5889 defun.set = setter;
5890 }
5891 Object.defineProperty(model, property, defun);
5892 },
5893 upper: function (name) {
5894 return name[0].toUpperCase() + name.substring(1);
5895 },
5896 _addAnnotatedListener: function (model, index, property, path, event) {
5897 var fn = this._notedListenerFactory(property, path, this._isStructured(path), th is._isEventBogus);
5898 var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed';
5899 model._bindListeners.push({
5900 index: index,
5901 property: property,
5902 path: path,
5903 changedFn: fn,
5904 event: eventName
5905 });
5906 },
5907 _isStructured: function (path) {
5908 return path.indexOf('.') > 0;
5909 },
5910 _isEventBogus: function (e, target) {
5911 return e.path && e.path[0] !== target;
5912 },
5913 _notedListenerFactory: function (property, path, isStructured, bogusTest) {
5914 return function (e, target) {
5915 if (!bogusTest(e, target)) {
5916 if (e.detail && e.detail.path) {
5917 this.notifyPath(this._fixPath(path, property, e.detail.path), e.detail.value);
5918 } else {
5919 var value = target[property];
5920 if (!isStructured) {
5921 this[path] = target[property];
5922 } else {
5923 if (this.__data__[path] != value) {
5924 this.set(path, value);
5925 }
5926 }
5927 }
5928 }
5929 };
5930 },
5931 prepareInstance: function (inst) {
5932 inst.__data__ = Object.create(null);
5933 },
5934 setupBindListeners: function (inst) {
5935 inst._bindListeners.forEach(function (info) {
5936 var node = inst._nodes[info.index];
5937 node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn ));
5938 });
5939 }
5940 };
5941 Polymer.Base.extend(Polymer.Bind, {
5942 _shouldAddListener: function (effect) {
5943 return effect.name && effect.mode === '{' && !effect.negate && effect.kind != 'a ttribute';
5944 },
5945 _annotationEffect: function (source, value, effect) {
5946 if (source != effect.value) {
5947 value = this.get(effect.value);
5948 this.__data__[effect.value] = value;
5949 }
5950 var calc = effect.negate ? !value : value;
5951 if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) {
5952 return this._applyEffectValue(calc, effect);
5953 }
5954 },
5955 _reflectEffect: function (source) {
5956 this.reflectPropertyToAttribute(source);
5957 },
5958 _notifyEffect: function (source, value, effect, old, fromAbove) {
5959 if (!fromAbove) {
5960 this._notifyChange(source);
5961 }
5962 },
5963 _functionEffect: function (source, value, fn, old, fromAbove) {
5964 fn.call(this, source, value, old, fromAbove);
5965 },
5966 _observerEffect: function (source, value, effect, old) {
5967 var fn = this[effect.method];
5968 if (fn) {
5969 fn.call(this, value, old);
5970 } else {
5971 this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + ' ` not defined'));
5972 }
5973 },
5974 _complexObserverEffect: function (source, value, effect) {
5975 var fn = this[effect.method];
5976 if (fn) {
5977 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
5978 if (args) {
5979 fn.apply(this, args);
5980 }
5981 } else {
5982 this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.met hod + '` not defined'));
5983 }
5984 },
5985 _computeEffect: function (source, value, effect) {
5986 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
5987 if (args) {
5988 var fn = this[effect.method];
5989 if (fn) {
5990 this.__setProperty(effect.property, fn.apply(this, args));
5991 } else {
5992 this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '` not defined'));
5993 }
5994 }
5995 },
5996 _annotatedComputationEffect: function (source, value, effect) {
5997 var computedHost = this._rootDataHost || this;
5998 var fn = computedHost[effect.method];
5999 if (fn) {
6000 var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
6001 if (args) {
6002 var computedvalue = fn.apply(computedHost, args);
6003 if (effect.negate) {
6004 computedvalue = !computedvalue;
6005 }
6006 this._applyEffectValue(computedvalue, effect);
6007 }
6008 } else {
6009 computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute me thod `' + effect.method + '` not defined'));
6010 }
6011 },
6012 _marshalArgs: function (model, effect, path, value) {
6013 var values = [];
6014 var args = effect.args;
6015 for (var i = 0, l = args.length; i < l; i++) {
6016 var arg = args[i];
6017 var name = arg.name;
6018 var v;
6019 if (arg.literal) {
6020 v = arg.value;
6021 } else if (arg.structured) {
6022 v = Polymer.Base.get(name, model);
6023 } else {
6024 v = model[name];
6025 }
6026 if (args.length > 1 && v === undefined) {
6027 return;
6028 }
6029 if (arg.wildcard) {
6030 var baseChanged = name.indexOf(path + '.') === 0;
6031 var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged;
6032 values[i] = {
6033 path: matches ? path : name,
6034 value: matches ? value : v,
6035 base: v
6036 };
6037 } else {
6038 values[i] = v;
6039 }
6040 }
6041 return values;
6042 }
6043 });
6044 Polymer.Base._addFeature({
6045 _addPropertyEffect: function (property, kind, effect) {
6046 Polymer.Bind.addPropertyEffect(this, property, kind, effect);
6047 },
6048 _prepEffects: function () {
6049 Polymer.Bind.prepareModel(this);
6050 this._addAnnotationEffects(this._notes);
6051 },
6052 _prepBindings: function () {
6053 Polymer.Bind.createBindings(this);
6054 },
6055 _addPropertyEffects: function (properties) {
6056 if (properties) {
6057 for (var p in properties) {
6058 var prop = properties[p];
6059 if (prop.observer) {
6060 this._addObserverEffect(p, prop.observer);
6061 }
6062 if (prop.computed) {
6063 prop.readOnly = true;
6064 this._addComputedEffect(p, prop.computed);
6065 }
6066 if (prop.notify) {
6067 this._addPropertyEffect(p, 'notify');
6068 }
6069 if (prop.reflectToAttribute) {
6070 this._addPropertyEffect(p, 'reflect');
6071 }
6072 if (prop.readOnly) {
6073 Polymer.Bind.ensurePropertyEffects(this, p);
6074 }
6075 }
6076 }
6077 },
6078 _addComputedEffect: function (name, expression) {
6079 var sig = this._parseMethod(expression);
6080 sig.args.forEach(function (arg) {
6081 this._addPropertyEffect(arg.model, 'compute', {
6082 method: sig.method,
6083 args: sig.args,
6084 trigger: arg,
6085 property: name
6086 });
6087 }, this);
6088 },
6089 _addObserverEffect: function (property, observer) {
6090 this._addPropertyEffect(property, 'observer', {
6091 method: observer,
6092 property: property
6093 });
6094 },
6095 _addComplexObserverEffects: function (observers) {
6096 if (observers) {
6097 observers.forEach(function (observer) {
6098 this._addComplexObserverEffect(observer);
6099 }, this);
6100 }
6101 },
6102 _addComplexObserverEffect: function (observer) {
6103 var sig = this._parseMethod(observer);
6104 sig.args.forEach(function (arg) {
6105 this._addPropertyEffect(arg.model, 'complexObserver', {
6106 method: sig.method,
6107 args: sig.args,
6108 trigger: arg
6109 });
6110 }, this);
6111 },
6112 _addAnnotationEffects: function (notes) {
6113 this._nodes = [];
6114 notes.forEach(function (note) {
6115 var index = this._nodes.push(note) - 1;
6116 note.bindings.forEach(function (binding) {
6117 this._addAnnotationEffect(binding, index);
6118 }, this);
6119 }, this);
6120 },
6121 _addAnnotationEffect: function (note, index) {
6122 if (Polymer.Bind._shouldAddListener(note)) {
6123 Polymer.Bind._addAnnotatedListener(this, index, note.name, note.value, note.even t);
6124 }
6125 if (note.signature) {
6126 this._addAnnotatedComputationEffect(note, index);
6127 } else {
6128 note.index = index;
6129 this._addPropertyEffect(note.model, 'annotation', note);
6130 }
6131 },
6132 _addAnnotatedComputationEffect: function (note, index) {
6133 var sig = note.signature;
6134 if (sig.static) {
6135 this.__addAnnotatedComputationEffect('__static__', index, note, sig, null);
6136 } else {
6137 sig.args.forEach(function (arg) {
6138 if (!arg.literal) {
6139 this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg);
6140 }
6141 }, this);
6142 }
6143 },
6144 __addAnnotatedComputationEffect: function (property, index, note, sig, trigger) {
6145 this._addPropertyEffect(property, 'annotatedComputation', {
6146 index: index,
6147 kind: note.kind,
6148 property: note.name,
6149 negate: note.negate,
6150 method: sig.method,
6151 args: sig.args,
6152 trigger: trigger
6153 });
6154 },
6155 _parseMethod: function (expression) {
6156 var m = expression.match(/([^\s]+)\((.*)\)/);
6157 if (m) {
6158 var sig = {
6159 method: m[1],
6160 static: true
6161 };
6162 if (m[2].trim()) {
6163 var args = m[2].replace(/\\,/g, '&comma;').split(',');
6164 return this._parseArgs(args, sig);
6165 } else {
6166 sig.args = Polymer.nar;
6167 return sig;
6168 }
6169 }
6170 },
6171 _parseArgs: function (argList, sig) {
6172 sig.args = argList.map(function (rawArg) {
6173 var arg = this._parseArg(rawArg);
6174 if (!arg.literal) {
6175 sig.static = false;
6176 }
6177 return arg;
6178 }, this);
6179 return sig;
6180 },
6181 _parseArg: function (rawArg) {
6182 var arg = rawArg.trim().replace(/&comma;/g, ',').replace(/\\(.)/g, '$1');
6183 var a = {
6184 name: arg,
6185 model: this._modelForPath(arg)
6186 };
6187 var fc = arg[0];
6188 if (fc === '-') {
6189 fc = arg[1];
6190 }
6191 if (fc >= '0' && fc <= '9') {
6192 fc = '#';
6193 }
6194 switch (fc) {
6195 case '\'':
6196 case '"':
6197 a.value = arg.slice(1, -1);
6198 a.literal = true;
6199 break;
6200 case '#':
6201 a.value = Number(arg);
6202 a.literal = true;
6203 break;
6204 }
6205 if (!a.literal) {
6206 a.structured = arg.indexOf('.') > 0;
6207 if (a.structured) {
6208 a.wildcard = arg.slice(-2) == '.*';
6209 if (a.wildcard) {
6210 a.name = arg.slice(0, -2);
6211 }
6212 }
6213 }
6214 return a;
6215 },
6216 _marshalInstanceEffects: function () {
6217 Polymer.Bind.prepareInstance(this);
6218 Polymer.Bind.setupBindListeners(this);
6219 },
6220 _applyEffectValue: function (value, info) {
6221 var node = this._nodes[info.index];
6222 var property = info.property || info.name || 'textContent';
6223 if (info.kind == 'attribute') {
6224 this.serializeValueToAttribute(value, property, node);
6225 } else {
6226 if (property === 'className') {
6227 value = this._scopeElementClass(node, value);
6228 }
6229 if (property === 'textContent' || node.localName == 'input' && property == 'valu e') {
6230 value = value == undefined ? '' : value;
6231 }
6232 return node[property] = value;
6233 }
6234 },
6235 _executeStaticEffects: function () {
6236 if (this._propertyEffects.__static__) {
6237 this._effectEffects('__static__', null, this._propertyEffects.__static__);
6238 }
6239 }
6240 });
6241 Polymer.Base._addFeature({
6242 _setupConfigure: function (initialConfig) {
6243 this._config = {};
6244 for (var i in initialConfig) {
6245 if (initialConfig[i] !== undefined) {
6246 this._config[i] = initialConfig[i];
6247 }
6248 }
6249 this._handlers = [];
6250 },
6251 _marshalAttributes: function () {
6252 this._takeAttributesToModel(this._config);
6253 },
6254 _attributeChangedImpl: function (name) {
6255 var model = this._clientsReadied ? this : this._config;
6256 this._setAttributeToProperty(model, name);
6257 },
6258 _configValue: function (name, value) {
6259 this._config[name] = value;
6260 },
6261 _beforeClientsReady: function () {
6262 this._configure();
6263 },
6264 _configure: function () {
6265 this._configureAnnotationReferences();
6266 this._aboveConfig = this.mixin({}, this._config);
6267 var config = {};
6268 this.behaviors.forEach(function (b) {
6269 this._configureProperties(b.properties, config);
6270 }, this);
6271 this._configureProperties(this.properties, config);
6272 this._mixinConfigure(config, this._aboveConfig);
6273 this._config = config;
6274 this._distributeConfig(this._config);
6275 },
6276 _configureProperties: function (properties, config) {
6277 for (var i in properties) {
6278 var c = properties[i];
6279 if (c.value !== undefined) {
6280 var value = c.value;
6281 if (typeof value == 'function') {
6282 value = value.call(this, this._config);
6283 }
6284 config[i] = value;
6285 }
6286 }
6287 },
6288 _mixinConfigure: function (a, b) {
6289 for (var prop in b) {
6290 if (!this.getPropertyInfo(prop).readOnly) {
6291 a[prop] = b[prop];
6292 }
6293 }
6294 },
6295 _distributeConfig: function (config) {
6296 var fx$ = this._propertyEffects;
6297 if (fx$) {
6298 for (var p in config) {
6299 var fx = fx$[p];
6300 if (fx) {
6301 for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) {
6302 if (x.kind === 'annotation') {
6303 var node = this._nodes[x.effect.index];
6304 if (node._configValue) {
6305 var value = p === x.effect.value ? config[p] : this.get(x.effect.value, config);
6306 node._configValue(x.effect.name, value);
6307 }
6308 }
6309 }
6310 }
6311 }
6312 }
6313 },
6314 _afterClientsReady: function () {
6315 this._executeStaticEffects();
6316 this._applyConfig(this._config, this._aboveConfig);
6317 this._flushHandlers();
6318 },
6319 _applyConfig: function (config, aboveConfig) {
6320 for (var n in config) {
6321 if (this[n] === undefined) {
6322 this.__setProperty(n, config[n], n in aboveConfig);
6323 }
6324 }
6325 },
6326 _notifyListener: function (fn, e) {
6327 if (!this._clientsReadied) {
6328 this._queueHandler([
6329 fn,
6330 e,
6331 e.target
6332 ]);
6333 } else {
6334 return fn.call(this, e, e.target);
6335 }
6336 },
6337 _queueHandler: function (args) {
6338 this._handlers.push(args);
6339 },
6340 _flushHandlers: function () {
6341 var h$ = this._handlers;
6342 for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) {
6343 h[0].call(this, h[1], h[2]);
6344 }
6345 this._handlers = [];
6346 }
6347 });
6348 (function () {
6349 'use strict';
6350 Polymer.Base._addFeature({
6351 notifyPath: function (path, value, fromAbove) {
6352 var old = this._propertySetter(path, value);
6353 if (old !== value && (old === old || value === value)) {
6354 this._pathEffector(path, value);
6355 if (!fromAbove) {
6356 this._notifyPath(path, value);
6357 }
6358 return true;
6359 }
6360 },
6361 _getPathParts: function (path) {
6362 if (Array.isArray(path)) {
6363 var parts = [];
6364 for (var i = 0; i < path.length; i++) {
6365 var args = path[i].toString().split('.');
6366 for (var j = 0; j < args.length; j++) {
6367 parts.push(args[j]);
6368 }
6369 }
6370 return parts;
6371 } else {
6372 return path.toString().split('.');
6373 }
6374 },
6375 set: function (path, value, root) {
6376 var prop = root || this;
6377 var parts = this._getPathParts(path);
6378 var array;
6379 var last = parts[parts.length - 1];
6380 if (parts.length > 1) {
6381 for (var i = 0; i < parts.length - 1; i++) {
6382 var part = parts[i];
6383 prop = prop[part];
6384 if (array && parseInt(part) == part) {
6385 parts[i] = Polymer.Collection.get(array).getKey(prop);
6386 }
6387 if (!prop) {
6388 return;
6389 }
6390 array = Array.isArray(prop) ? prop : null;
6391 }
6392 if (array && parseInt(last) == last) {
6393 var coll = Polymer.Collection.get(array);
6394 var old = prop[last];
6395 var key = coll.getKey(old);
6396 parts[i] = key;
6397 coll.setItem(key, value);
6398 }
6399 prop[last] = value;
6400 if (!root) {
6401 this.notifyPath(parts.join('.'), value);
6402 }
6403 } else {
6404 prop[path] = value;
6405 }
6406 },
6407 get: function (path, root) {
6408 var prop = root || this;
6409 var parts = this._getPathParts(path);
6410 var last = parts.pop();
6411 while (parts.length) {
6412 prop = prop[parts.shift()];
6413 if (!prop) {
6414 return;
6415 }
6416 }
6417 return prop[last];
6418 },
6419 _pathEffector: function (path, value) {
6420 var model = this._modelForPath(path);
6421 var fx$ = this._propertyEffects[model];
6422 if (fx$) {
6423 fx$.forEach(function (fx) {
6424 var fxFn = this['_' + fx.kind + 'PathEffect'];
6425 if (fxFn) {
6426 fxFn.call(this, path, value, fx.effect);
6427 }
6428 }, this);
6429 }
6430 if (this._boundPaths) {
6431 this._notifyBoundPaths(path, value);
6432 }
6433 },
6434 _annotationPathEffect: function (path, value, effect) {
6435 if (effect.value === path || effect.value.indexOf(path + '.') === 0) {
6436 Polymer.Bind._annotationEffect.call(this, path, value, effect);
6437 } else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) {
6438 var node = this._nodes[effect.index];
6439 if (node && node.notifyPath) {
6440 var p = this._fixPath(effect.name, effect.value, path);
6441 node.notifyPath(p, value, true);
6442 }
6443 }
6444 },
6445 _complexObserverPathEffect: function (path, value, effect) {
6446 if (this._pathMatchesEffect(path, effect)) {
6447 Polymer.Bind._complexObserverEffect.call(this, path, value, effect);
6448 }
6449 },
6450 _computePathEffect: function (path, value, effect) {
6451 if (this._pathMatchesEffect(path, effect)) {
6452 Polymer.Bind._computeEffect.call(this, path, value, effect);
6453 }
6454 },
6455 _annotatedComputationPathEffect: function (path, value, effect) {
6456 if (this._pathMatchesEffect(path, effect)) {
6457 Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect);
6458 }
6459 },
6460 _pathMatchesEffect: function (path, effect) {
6461 var effectArg = effect.trigger.name;
6462 return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigge r.wildcard && path.indexOf(effectArg) === 0;
6463 },
6464 linkPaths: function (to, from) {
6465 this._boundPaths = this._boundPaths || {};
6466 if (from) {
6467 this._boundPaths[to] = from;
6468 } else {
6469 this.unlinkPaths(to);
6470 }
6471 },
6472 unlinkPaths: function (path) {
6473 if (this._boundPaths) {
6474 delete this._boundPaths[path];
6475 }
6476 },
6477 _notifyBoundPaths: function (path, value) {
6478 for (var a in this._boundPaths) {
6479 var b = this._boundPaths[a];
6480 if (path.indexOf(a + '.') == 0) {
6481 this.notifyPath(this._fixPath(b, a, path), value);
6482 } else if (path.indexOf(b + '.') == 0) {
6483 this.notifyPath(this._fixPath(a, b, path), value);
6484 }
6485 }
6486 },
6487 _fixPath: function (property, root, path) {
6488 return property + path.slice(root.length);
6489 },
6490 _notifyPath: function (path, value) {
6491 var rootName = this._modelForPath(path);
6492 var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName);
6493 var eventName = dashCaseName + this._EVENT_CHANGED;
6494 this.fire(eventName, {
6495 path: path,
6496 value: value
6497 }, { bubbles: false });
6498 },
6499 _modelForPath: function (path) {
6500 var dot = path.indexOf('.');
6501 return dot < 0 ? path : path.slice(0, dot);
6502 },
6503 _EVENT_CHANGED: '-changed',
6504 _notifySplice: function (array, path, index, added, removed) {
6505 var splices = [{
6506 index: index,
6507 addedCount: added,
6508 removed: removed,
6509 object: array,
6510 type: 'splice'
6511 }];
6512 var change = {
6513 keySplices: Polymer.Collection.applySplices(array, splices),
6514 indexSplices: splices
6515 };
6516 this.set(path + '.splices', change);
6517 if (added != removed.length) {
6518 this.notifyPath(path + '.length', array.length);
6519 }
6520 change.keySplices = null;
6521 change.indexSplices = null;
6522 },
6523 push: function (path) {
6524 var array = this.get(path);
6525 var args = Array.prototype.slice.call(arguments, 1);
6526 var len = array.length;
6527 var ret = array.push.apply(array, args);
6528 if (args.length) {
6529 this._notifySplice(array, path, len, args.length, []);
6530 }
6531 return ret;
6532 },
6533 pop: function (path) {
6534 var array = this.get(path);
6535 var hadLength = Boolean(array.length);
6536 var args = Array.prototype.slice.call(arguments, 1);
6537 var ret = array.pop.apply(array, args);
6538 if (hadLength) {
6539 this._notifySplice(array, path, array.length, 0, [ret]);
6540 }
6541 return ret;
6542 },
6543 splice: function (path, start, deleteCount) {
6544 var array = this.get(path);
6545 if (start < 0) {
6546 start = array.length - Math.floor(-start);
6547 } else {
6548 start = Math.floor(start);
6549 }
6550 if (!start) {
6551 start = 0;
6552 }
6553 var args = Array.prototype.slice.call(arguments, 1);
6554 var ret = array.splice.apply(array, args);
6555 var addedCount = Math.max(args.length - 2, 0);
6556 if (addedCount || ret.length) {
6557 this._notifySplice(array, path, start, addedCount, ret);
6558 }
6559 return ret;
6560 },
6561 shift: function (path) {
6562 var array = this.get(path);
6563 var hadLength = Boolean(array.length);
6564 var args = Array.prototype.slice.call(arguments, 1);
6565 var ret = array.shift.apply(array, args);
6566 if (hadLength) {
6567 this._notifySplice(array, path, 0, 0, [ret]);
6568 }
6569 return ret;
6570 },
6571 unshift: function (path) {
6572 var array = this.get(path);
6573 var args = Array.prototype.slice.call(arguments, 1);
6574 var ret = array.unshift.apply(array, args);
6575 if (args.length) {
6576 this._notifySplice(array, path, 0, args.length, []);
6577 }
6578 return ret;
6579 }
6580 });
6581 }());
6582 Polymer.Base._addFeature({
6583 resolveUrl: function (url) {
6584 var module = Polymer.DomModule.import(this.is);
6585 var root = '';
6586 if (module) {
6587 var assetPath = module.getAttribute('assetpath') || '';
6588 root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI);
6589 }
6590 return Polymer.ResolveUrl.resolveUrl(url, root);
6591 }
6592 });
6593 Polymer.CssParse = function () {
6594 var api = {
6595 parse: function (text) {
6596 text = this._clean(text);
6597 return this._parseCss(this._lex(text), text);
6598 },
6599 _clean: function (cssText) {
6600 return cssText.replace(this._rx.comments, '').replace(this._rx.port, '');
6601 },
6602 _lex: function (text) {
6603 var root = {
6604 start: 0,
6605 end: text.length
6606 };
6607 var n = root;
6608 for (var i = 0, s = 0, l = text.length; i < l; i++) {
6609 switch (text[i]) {
6610 case this.OPEN_BRACE:
6611 if (!n.rules) {
6612 n.rules = [];
6613 }
6614 var p = n;
6615 var previous = p.rules[p.rules.length - 1];
6616 n = {
6617 start: i + 1,
6618 parent: p,
6619 previous: previous
6620 };
6621 p.rules.push(n);
6622 break;
6623 case this.CLOSE_BRACE:
6624 n.end = i + 1;
6625 n = n.parent || root;
6626 break;
6627 }
6628 }
6629 return root;
6630 },
6631 _parseCss: function (node, text) {
6632 var t = text.substring(node.start, node.end - 1);
6633 node.parsedCssText = node.cssText = t.trim();
6634 if (node.parent) {
6635 var ss = node.previous ? node.previous.end : node.parent.start;
6636 t = text.substring(ss, node.start - 1);
6637 t = t.substring(t.lastIndexOf(';') + 1);
6638 var s = node.parsedSelector = node.selector = t.trim();
6639 node.atRule = s.indexOf(this.AT_START) === 0;
6640 if (node.atRule) {
6641 if (s.indexOf(this.MEDIA_START) === 0) {
6642 node.type = this.types.MEDIA_RULE;
6643 } else if (s.match(this._rx.keyframesRule)) {
6644 node.type = this.types.KEYFRAMES_RULE;
6645 }
6646 } else {
6647 if (s.indexOf(this.VAR_START) === 0) {
6648 node.type = this.types.MIXIN_RULE;
6649 } else {
6650 node.type = this.types.STYLE_RULE;
6651 }
6652 }
6653 }
6654 var r$ = node.rules;
6655 if (r$) {
6656 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
6657 this._parseCss(r, text);
6658 }
6659 }
6660 return node;
6661 },
6662 stringify: function (node, preserveProperties, text) {
6663 text = text || '';
6664 var cssText = '';
6665 if (node.cssText || node.rules) {
6666 var r$ = node.rules;
6667 if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
6668 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
6669 cssText = this.stringify(r, preserveProperties, cssText);
6670 }
6671 } else {
6672 cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssTex t);
6673 cssText = cssText.trim();
6674 if (cssText) {
6675 cssText = ' ' + cssText + '\n';
6676 }
6677 }
6678 }
6679 if (cssText) {
6680 if (node.selector) {
6681 text += node.selector + ' ' + this.OPEN_BRACE + '\n';
6682 }
6683 text += cssText;
6684 if (node.selector) {
6685 text += this.CLOSE_BRACE + '\n\n';
6686 }
6687 }
6688 return text;
6689 },
6690 _hasMixinRules: function (rules) {
6691 return rules[0].selector.indexOf(this.VAR_START) >= 0;
6692 },
6693 removeCustomProps: function (cssText) {
6694 return cssText;
6695 },
6696 removeCustomPropAssignment: function (cssText) {
6697 return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, '');
6698 },
6699 removeCustomPropApply: function (cssText) {
6700 return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, '');
6701 },
6702 types: {
6703 STYLE_RULE: 1,
6704 KEYFRAMES_RULE: 7,
6705 MEDIA_RULE: 4,
6706 MIXIN_RULE: 1000
6707 },
6708 OPEN_BRACE: '{',
6709 CLOSE_BRACE: '}',
6710 _rx: {
6711 comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,
6712 port: /@import[^;]*;/gim,
6713 customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim,
6714 mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim,
6715 mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
6716 varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim,
6717 keyframesRule: /^@[^\s]*keyframes/
6718 },
6719 VAR_START: '--',
6720 MEDIA_START: '@media',
6721 AT_START: '@'
6722 };
6723 return api;
6724 }();
6725 Polymer.StyleUtil = function () {
6726 return {
6727 MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template',
6728 INCLUDE_ATTR: 'include',
6729 toCssText: function (rules, callback, preserveProperties) {
6730 if (typeof rules === 'string') {
6731 rules = this.parser.parse(rules);
6732 }
6733 if (callback) {
6734 this.forEachStyleRule(rules, callback);
6735 }
6736 return this.parser.stringify(rules, preserveProperties);
6737 },
6738 forRulesInStyles: function (styles, callback) {
6739 if (styles) {
6740 for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) {
6741 this.forEachStyleRule(this.rulesForStyle(s), callback);
6742 }
6743 }
6744 },
6745 rulesForStyle: function (style) {
6746 if (!style.__cssRules && style.textContent) {
6747 style.__cssRules = this.parser.parse(style.textContent);
6748 }
6749 return style.__cssRules;
6750 },
6751 clearStyleRules: function (style) {
6752 style.__cssRules = null;
6753 },
6754 forEachStyleRule: function (node, callback) {
6755 if (!node) {
6756 return;
6757 }
6758 var s = node.parsedSelector;
6759 var skipRules = false;
6760 if (node.type === this.ruleTypes.STYLE_RULE) {
6761 callback(node);
6762 } else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.rul eTypes.MIXIN_RULE) {
6763 skipRules = true;
6764 }
6765 var r$ = node.rules;
6766 if (r$ && !skipRules) {
6767 for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) {
6768 this.forEachStyleRule(r, callback);
6769 }
6770 }
6771 },
6772 applyCss: function (cssText, moniker, target, afterNode) {
6773 var style = document.createElement('style');
6774 if (moniker) {
6775 style.setAttribute('scope', moniker);
6776 }
6777 style.textContent = cssText;
6778 target = target || document.head;
6779 if (!afterNode) {
6780 var n$ = target.querySelectorAll('style[scope]');
6781 afterNode = n$[n$.length - 1];
6782 }
6783 target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChi ld);
6784 return style;
6785 },
6786 cssFromModules: function (moduleIds, warnIfNotFound) {
6787 var modules = moduleIds.trim().split(' ');
6788 var cssText = '';
6789 for (var i = 0; i < modules.length; i++) {
6790 cssText += this.cssFromModule(modules[i], warnIfNotFound);
6791 }
6792 return cssText;
6793 },
6794 cssFromModule: function (moduleId, warnIfNotFound) {
6795 var m = Polymer.DomModule.import(moduleId);
6796 if (m && !m._cssText) {
6797 m._cssText = this._cssFromElement(m);
6798 }
6799 if (!m && warnIfNotFound) {
6800 console.warn('Could not find style data in module named', moduleId);
6801 }
6802 return m && m._cssText || '';
6803 },
6804 _cssFromElement: function (element) {
6805 var cssText = '';
6806 var content = element.content || element;
6807 var e$ = Array.prototype.slice.call(content.querySelectorAll(this.MODULE_STYLES_ SELECTOR));
6808 for (var i = 0, e; i < e$.length; i++) {
6809 e = e$[i];
6810 if (e.localName === 'template') {
6811 cssText += this._cssFromElement(e);
6812 } else {
6813 if (e.localName === 'style') {
6814 var include = e.getAttribute(this.INCLUDE_ATTR);
6815 if (include) {
6816 cssText += this.cssFromModules(include, true);
6817 }
6818 e = e.__appliedElement || e;
6819 e.parentNode.removeChild(e);
6820 cssText += this.resolveCss(e.textContent, element.ownerDocument);
6821 } else if (e.import && e.import.body) {
6822 cssText += this.resolveCss(e.import.body.textContent, e.import);
6823 }
6824 }
6825 }
6826 return cssText;
6827 },
6828 resolveCss: Polymer.ResolveUrl.resolveCss,
6829 parser: Polymer.CssParse,
6830 ruleTypes: Polymer.CssParse.types
6831 };
6832 }();
6833 Polymer.StyleTransformer = function () {
6834 var nativeShadow = Polymer.Settings.useNativeShadow;
6835 var styleUtil = Polymer.StyleUtil;
6836 var api = {
6837 dom: function (node, scope, useAttr, shouldRemoveScope) {
6838 this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
6839 },
6840 _transformDom: function (node, selector, useAttr, shouldRemoveScope) {
6841 if (node.setAttribute) {
6842 this.element(node, selector, useAttr, shouldRemoveScope);
6843 }
6844 var c$ = Polymer.dom(node).childNodes;
6845 for (var i = 0; i < c$.length; i++) {
6846 this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
6847 }
6848 },
6849 element: function (element, scope, useAttr, shouldRemoveScope) {
6850 if (useAttr) {
6851 if (shouldRemoveScope) {
6852 element.removeAttribute(SCOPE_NAME);
6853 } else {
6854 element.setAttribute(SCOPE_NAME, scope);
6855 }
6856 } else {
6857 if (scope) {
6858 if (element.classList) {
6859 if (shouldRemoveScope) {
6860 element.classList.remove(SCOPE_NAME);
6861 element.classList.remove(scope);
6862 } else {
6863 element.classList.add(SCOPE_NAME);
6864 element.classList.add(scope);
6865 }
6866 } else if (element.getAttribute) {
6867 var c = element.getAttribute(CLASS);
6868 if (shouldRemoveScope) {
6869 if (c) {
6870 element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, ''));
6871 }
6872 } else {
6873 element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope);
6874 }
6875 }
6876 }
6877 }
6878 },
6879 elementStyles: function (element, callback) {
6880 var styles = element._styles;
6881 var cssText = '';
6882 for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) {
6883 var rules = styleUtil.rulesForStyle(s);
6884 cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules, element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n';
6885 }
6886 return cssText.trim();
6887 },
6888 css: function (rules, scope, ext, callback, useAttr) {
6889 var hostScope = this._calcHostScope(scope, ext);
6890 scope = this._calcElementScope(scope, useAttr);
6891 var self = this;
6892 return styleUtil.toCssText(rules, function (rule) {
6893 if (!rule.isScoped) {
6894 self.rule(rule, scope, hostScope);
6895 rule.isScoped = true;
6896 }
6897 if (callback) {
6898 callback(rule, scope, hostScope);
6899 }
6900 });
6901 },
6902 _calcElementScope: function (scope, useAttr) {
6903 if (scope) {
6904 return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX + scope;
6905 } else {
6906 return '';
6907 }
6908 },
6909 _calcHostScope: function (scope, ext) {
6910 return ext ? '[is=' + scope + ']' : scope;
6911 },
6912 rule: function (rule, scope, hostScope) {
6913 this._transformRule(rule, this._transformComplexSelector, scope, hostScope);
6914 },
6915 _transformRule: function (rule, transformer, scope, hostScope) {
6916 var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
6917 for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) {
6918 p$[i] = transformer.call(this, p, scope, hostScope);
6919 }
6920 rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP);
6921 },
6922 _transformComplexSelector: function (selector, scope, hostScope) {
6923 var stop = false;
6924 var hostContext = false;
6925 var self = this;
6926 selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) {
6927 if (!stop) {
6928 var info = self._transformCompoundSelector(s, c, scope, hostScope);
6929 stop = stop || info.stop;
6930 hostContext = hostContext || info.hostContext;
6931 c = info.combinator;
6932 s = info.value;
6933 } else {
6934 s = s.replace(SCOPE_JUMP, ' ');
6935 }
6936 return c + s;
6937 });
6938 if (hostContext) {
6939 selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) {
6940 return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
6941 });
6942 }
6943 return selector;
6944 },
6945 _transformCompoundSelector: function (selector, combinator, scope, hostScope) {
6946 var jumpIndex = selector.search(SCOPE_JUMP);
6947 var hostContext = false;
6948 if (selector.indexOf(HOST_CONTEXT) >= 0) {
6949 hostContext = true;
6950 } else if (selector.indexOf(HOST) >= 0) {
6951 selector = selector.replace(HOST_PAREN, function (m, host, paren) {
6952 return hostScope + paren;
6953 });
6954 selector = selector.replace(HOST, hostScope);
6955 } else if (jumpIndex !== 0) {
6956 selector = scope ? this._transformSimpleSelector(selector, scope) : selector;
6957 }
6958 if (selector.indexOf(CONTENT) >= 0) {
6959 combinator = '';
6960 }
6961 var stop;
6962 if (jumpIndex >= 0) {
6963 selector = selector.replace(SCOPE_JUMP, ' ');
6964 stop = true;
6965 }
6966 return {
6967 value: selector,
6968 combinator: combinator,
6969 stop: stop,
6970 hostContext: hostContext
6971 };
6972 },
6973 _transformSimpleSelector: function (selector, scope) {
6974 var p$ = selector.split(PSEUDO_PREFIX);
6975 p$[0] += scope;
6976 return p$.join(PSEUDO_PREFIX);
6977 },
6978 documentRule: function (rule) {
6979 rule.selector = rule.parsedSelector;
6980 this.normalizeRootSelector(rule);
6981 if (!nativeShadow) {
6982 this._transformRule(rule, this._transformDocumentSelector);
6983 }
6984 },
6985 normalizeRootSelector: function (rule) {
6986 if (rule.selector === ROOT) {
6987 rule.selector = 'body';
6988 }
6989 },
6990 _transformDocumentSelector: function (selector) {
6991 return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCO PE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELE CTOR);
6992 },
6993 SCOPE_NAME: 'style-scope'
6994 };
6995 var SCOPE_NAME = api.SCOPE_NAME;
6996 var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME + ')';
6997 var COMPLEX_SELECTOR_SEP = ',';
6998 var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
6999 var HOST = ':host';
7000 var ROOT = ':root';
7001 var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
7002 var HOST_CONTEXT = ':host-context';
7003 var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\) )(.*)/;
7004 var CONTENT = '::content';
7005 var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//;
7006 var CSS_CLASS_PREFIX = '.';
7007 var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
7008 var CSS_ATTR_SUFFIX = ']';
7009 var PSEUDO_PREFIX = ':';
7010 var CLASS = 'class';
7011 return api;
7012 }();
7013 Polymer.StyleExtends = function () {
7014 var styleUtil = Polymer.StyleUtil;
7015 return {
7016 hasExtends: function (cssText) {
7017 return Boolean(cssText.match(this.rx.EXTEND));
7018 },
7019 transform: function (style) {
7020 var rules = styleUtil.rulesForStyle(style);
7021 var self = this;
7022 styleUtil.forEachStyleRule(rules, function (rule) {
7023 var map = self._mapRule(rule);
7024 if (rule.parent) {
7025 var m;
7026 while (m = self.rx.EXTEND.exec(rule.cssText)) {
7027 var extend = m[1];
7028 var extendor = self._findExtendor(extend, rule);
7029 if (extendor) {
7030 self._extendRule(rule, extendor);
7031 }
7032 }
7033 }
7034 rule.cssText = rule.cssText.replace(self.rx.EXTEND, '');
7035 });
7036 return styleUtil.toCssText(rules, function (rule) {
7037 if (rule.selector.match(self.rx.STRIP)) {
7038 rule.cssText = '';
7039 }
7040 }, true);
7041 },
7042 _mapRule: function (rule) {
7043 if (rule.parent) {
7044 var map = rule.parent.map || (rule.parent.map = {});
7045 var parts = rule.selector.split(',');
7046 for (var i = 0, p; i < parts.length; i++) {
7047 p = parts[i];
7048 map[p.trim()] = rule;
7049 }
7050 return map;
7051 }
7052 },
7053 _findExtendor: function (extend, rule) {
7054 return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findEx tendor(extend, rule.parent);
7055 },
7056 _extendRule: function (target, source) {
7057 if (target.parent !== source.parent) {
7058 this._cloneAndAddRuleToParent(source, target.parent);
7059 }
7060 target.extends = target.extends || (target.extends = []);
7061 target.extends.push(source);
7062 source.selector = source.selector.replace(this.rx.STRIP, '');
7063 source.selector = (source.selector && source.selector + ',\n') + target.selector ;
7064 if (source.extends) {
7065 source.extends.forEach(function (e) {
7066 this._extendRule(target, e);
7067 }, this);
7068 }
7069 },
7070 _cloneAndAddRuleToParent: function (rule, parent) {
7071 rule = Object.create(rule);
7072 rule.parent = parent;
7073 if (rule.extends) {
7074 rule.extends = rule.extends.slice();
7075 }
7076 parent.rules.push(rule);
7077 },
7078 rx: {
7079 EXTEND: /@extends\(([^)]*)\)\s*?;/gim,
7080 STRIP: /%[^,]*$/
7081 }
7082 };
7083 }();
7084 (function () {
7085 var prepElement = Polymer.Base._prepElement;
7086 var nativeShadow = Polymer.Settings.useNativeShadow;
7087 var styleUtil = Polymer.StyleUtil;
7088 var styleTransformer = Polymer.StyleTransformer;
7089 var styleExtends = Polymer.StyleExtends;
7090 Polymer.Base._addFeature({
7091 _prepElement: function (element) {
7092 if (this._encapsulateStyle) {
7093 styleTransformer.element(element, this.is, this._scopeCssViaAttr);
7094 }
7095 prepElement.call(this, element);
7096 },
7097 _prepStyles: function () {
7098 if (this._encapsulateStyle === undefined) {
7099 this._encapsulateStyle = !nativeShadow && Boolean(this._template);
7100 }
7101 this._styles = this._collectStyles();
7102 var cssText = styleTransformer.elementStyles(this);
7103 if (cssText && this._template) {
7104 var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.c ontent : null);
7105 if (!nativeShadow) {
7106 this._scopeStyle = style;
7107 }
7108 }
7109 },
7110 _collectStyles: function () {
7111 var styles = [];
7112 var cssText = '', m$ = this.styleModules;
7113 if (m$) {
7114 for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) {
7115 cssText += styleUtil.cssFromModule(m);
7116 }
7117 }
7118 cssText += styleUtil.cssFromModule(this.is);
7119 if (cssText) {
7120 var style = document.createElement('style');
7121 style.textContent = cssText;
7122 if (styleExtends.hasExtends(style.textContent)) {
7123 cssText = styleExtends.transform(style);
7124 }
7125 styles.push(style);
7126 }
7127 return styles;
7128 },
7129 _elementAdd: function (node) {
7130 if (this._encapsulateStyle) {
7131 if (node.__styleScoped) {
7132 node.__styleScoped = false;
7133 } else {
7134 styleTransformer.dom(node, this.is, this._scopeCssViaAttr);
7135 }
7136 }
7137 },
7138 _elementRemove: function (node) {
7139 if (this._encapsulateStyle) {
7140 styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true);
7141 }
7142 },
7143 scopeSubtree: function (container, shouldObserve) {
7144 if (nativeShadow) {
7145 return;
7146 }
7147 var self = this;
7148 var scopify = function (node) {
7149 if (node.nodeType === Node.ELEMENT_NODE) {
7150 node.className = self._scopeElementClass(node, node.className);
7151 var n$ = node.querySelectorAll('*');
7152 Array.prototype.forEach.call(n$, function (n) {
7153 n.className = self._scopeElementClass(n, n.className);
7154 });
7155 }
7156 };
7157 scopify(container);
7158 if (shouldObserve) {
7159 var mo = new MutationObserver(function (mxns) {
7160 mxns.forEach(function (m) {
7161 if (m.addedNodes) {
7162 for (var i = 0; i < m.addedNodes.length; i++) {
7163 scopify(m.addedNodes[i]);
7164 }
7165 }
7166 });
7167 });
7168 mo.observe(container, {
7169 childList: true,
7170 subtree: true
7171 });
7172 return mo;
7173 }
7174 }
7175 });
7176 }());
7177 Polymer.StyleProperties = function () {
7178 'use strict';
7179 var nativeShadow = Polymer.Settings.useNativeShadow;
7180 var matchesSelector = Polymer.DomApi.matchesSelector;
7181 var styleUtil = Polymer.StyleUtil;
7182 var styleTransformer = Polymer.StyleTransformer;
7183 return {
7184 decorateStyles: function (styles) {
7185 var self = this, props = {};
7186 styleUtil.forRulesInStyles(styles, function (rule) {
7187 self.decorateRule(rule);
7188 self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
7189 });
7190 var names = [];
7191 for (var i in props) {
7192 names.push(i);
7193 }
7194 return names;
7195 },
7196 decorateRule: function (rule) {
7197 if (rule.propertyInfo) {
7198 return rule.propertyInfo;
7199 }
7200 var info = {}, properties = {};
7201 var hasProperties = this.collectProperties(rule, properties);
7202 if (hasProperties) {
7203 info.properties = properties;
7204 rule.rules = null;
7205 }
7206 info.cssText = this.collectCssText(rule);
7207 rule.propertyInfo = info;
7208 return info;
7209 },
7210 collectProperties: function (rule, properties) {
7211 var info = rule.propertyInfo;
7212 if (info) {
7213 if (info.properties) {
7214 Polymer.Base.mixin(properties, info.properties);
7215 return true;
7216 }
7217 } else {
7218 var m, rx = this.rx.VAR_ASSIGN;
7219 var cssText = rule.parsedCssText;
7220 var any;
7221 while (m = rx.exec(cssText)) {
7222 properties[m[1]] = (m[2] || m[3]).trim();
7223 any = true;
7224 }
7225 return any;
7226 }
7227 },
7228 collectCssText: function (rule) {
7229 var customCssText = '';
7230 var cssText = rule.parsedCssText;
7231 cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, '') ;
7232 var parts = cssText.split(';');
7233 for (var i = 0, p; i < parts.length; i++) {
7234 p = parts[i];
7235 if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) {
7236 customCssText += p + ';\n';
7237 }
7238 }
7239 return customCssText;
7240 },
7241 collectPropertiesInCssText: function (cssText, props) {
7242 var m;
7243 while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
7244 props[m[1]] = true;
7245 var def = m[2];
7246 if (def && def.match(this.rx.IS_VAR)) {
7247 props[def] = true;
7248 }
7249 }
7250 },
7251 reify: function (props) {
7252 var names = Object.getOwnPropertyNames(props);
7253 for (var i = 0, n; i < names.length; i++) {
7254 n = names[i];
7255 props[n] = this.valueForProperty(props[n], props);
7256 }
7257 },
7258 valueForProperty: function (property, props) {
7259 if (property) {
7260 if (property.indexOf(';') >= 0) {
7261 property = this.valueForProperties(property, props);
7262 } else {
7263 var self = this;
7264 var fn = function (all, prefix, value, fallback) {
7265 var propertyValue = self.valueForProperty(props[value], props) || (props[fallbac k] ? self.valueForProperty(props[fallback], props) : fallback);
7266 return prefix + (propertyValue || '');
7267 };
7268 property = property.replace(this.rx.VAR_MATCH, fn);
7269 }
7270 }
7271 return property && property.trim() || '';
7272 },
7273 valueForProperties: function (property, props) {
7274 var parts = property.split(';');
7275 for (var i = 0, p, m; i < parts.length; i++) {
7276 if (p = parts[i]) {
7277 m = p.match(this.rx.MIXIN_MATCH);
7278 if (m) {
7279 p = this.valueForProperty(props[m[1]], props);
7280 } else {
7281 var pp = p.split(':');
7282 if (pp[1]) {
7283 pp[1] = pp[1].trim();
7284 pp[1] = this.valueForProperty(pp[1], props) || pp[1];
7285 }
7286 p = pp.join(':');
7287 }
7288 parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || '';
7289 }
7290 }
7291 return parts.join(';');
7292 },
7293 applyProperties: function (rule, props) {
7294 var output = '';
7295 if (!rule.propertyInfo) {
7296 this.decorateRule(rule);
7297 }
7298 if (rule.propertyInfo.cssText) {
7299 output = this.valueForProperties(rule.propertyInfo.cssText, props);
7300 }
7301 rule.cssText = output;
7302 },
7303 propertyDataFromStyles: function (styles, element) {
7304 var props = {}, self = this;
7305 var o = [], i = 0;
7306 styleUtil.forRulesInStyles(styles, function (rule) {
7307 if (!rule.propertyInfo) {
7308 self.decorateRule(rule);
7309 }
7310 if (element && rule.propertyInfo.properties && matchesSelector.call(element, rul e.transformedSelector || rule.parsedSelector)) {
7311 self.collectProperties(rule, props);
7312 addToBitMask(i, o);
7313 }
7314 i++;
7315 });
7316 return {
7317 properties: props,
7318 key: o
7319 };
7320 },
7321 scopePropertiesFromStyles: function (styles) {
7322 if (!styles._scopeStyleProperties) {
7323 styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SC OPE_SELECTORS);
7324 }
7325 return styles._scopeStyleProperties;
7326 },
7327 hostPropertiesFromStyles: function (styles) {
7328 if (!styles._hostStyleProperties) {
7329 styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOS T_SELECTORS);
7330 }
7331 return styles._hostStyleProperties;
7332 },
7333 selectedPropertiesFromStyles: function (styles, selectors) {
7334 var props = {}, self = this;
7335 styleUtil.forRulesInStyles(styles, function (rule) {
7336 if (!rule.propertyInfo) {
7337 self.decorateRule(rule);
7338 }
7339 for (var i = 0; i < selectors.length; i++) {
7340 if (rule.parsedSelector === selectors[i]) {
7341 self.collectProperties(rule, props);
7342 return;
7343 }
7344 }
7345 });
7346 return props;
7347 },
7348 transformStyles: function (element, properties, scopeSelector) {
7349 var self = this;
7350 var hostSelector = styleTransformer._calcHostScope(element.is, element.extends);
7351 var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]' : hostSelector;
7352 var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFF IX);
7353 return styleTransformer.elementStyles(element, function (rule) {
7354 self.applyProperties(rule, properties);
7355 if (rule.cssText && !nativeShadow) {
7356 self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeS elector);
7357 }
7358 });
7359 },
7360 _scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) {
7361 rule.transformedSelector = rule.transformedSelector || rule.selector;
7362 var selector = rule.transformedSelector;
7363 var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' : '.' + scopeId;
7364 var parts = selector.split(',');
7365 for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) {
7366 parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : sco pe + ' ' + p;
7367 }
7368 rule.selector = parts.join(',');
7369 },
7370 applyElementScopeSelector: function (element, selector, old, viaAttr) {
7371 var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.cl assName;
7372 var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
7373 if (c !== v) {
7374 if (viaAttr) {
7375 element.setAttribute(styleTransformer.SCOPE_NAME, v);
7376 } else {
7377 element.className = v;
7378 }
7379 }
7380 },
7381 applyElementStyle: function (element, properties, selector, style) {
7382 var cssText = style ? style.textContent || '' : this.transformStyles(element, pr operties, selector);
7383 var s = element._customStyle;
7384 if (s && !nativeShadow && s !== style) {
7385 s._useCount--;
7386 if (s._useCount <= 0 && s.parentNode) {
7387 s.parentNode.removeChild(s);
7388 }
7389 }
7390 if (nativeShadow || (!style || !style.parentNode)) {
7391 if (nativeShadow && element._customStyle) {
7392 element._customStyle.textContent = cssText;
7393 style = element._customStyle;
7394 } else if (cssText) {
7395 style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null , element._scopeStyle);
7396 }
7397 }
7398 if (style) {
7399 style._useCount = style._useCount || 0;
7400 if (element._customStyle != style) {
7401 style._useCount++;
7402 }
7403 element._customStyle = style;
7404 }
7405 return style;
7406 },
7407 mixinCustomStyle: function (props, customStyle) {
7408 var v;
7409 for (var i in customStyle) {
7410 v = customStyle[i];
7411 if (v || v === 0) {
7412 props[i] = v;
7413 }
7414 }
7415 },
7416 rx: {
7417 VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/ gi,
7418 MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
7419 VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)) )[\s]*?\)/gi,
7420 VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
7421 IS_VAR: /^--/,
7422 BRACKETED: /\{[^}]*\}/g,
7423 HOST_PREFIX: '(?:^|[^.#[:])',
7424 HOST_SUFFIX: '($|[.:[\\s>+~])'
7425 },
7426 HOST_SELECTORS: [':host'],
7427 SCOPE_SELECTORS: [':root'],
7428 XSCOPE_NAME: 'x-scope'
7429 };
7430 function addToBitMask(n, bits) {
7431 var o = parseInt(n / 32);
7432 var v = 1 << n % 32;
7433 bits[o] = (bits[o] || 0) | v;
7434 }
7435 }();
7436 (function () {
7437 Polymer.StyleCache = function () {
7438 this.cache = {};
7439 };
7440 Polymer.StyleCache.prototype = {
7441 MAX: 100,
7442 store: function (is, data, keyValues, keyStyles) {
7443 data.keyValues = keyValues;
7444 data.styles = keyStyles;
7445 var s$ = this.cache[is] = this.cache[is] || [];
7446 s$.push(data);
7447 if (s$.length > this.MAX) {
7448 s$.shift();
7449 }
7450 },
7451 retrieve: function (is, keyValues, keyStyles) {
7452 var cache = this.cache[is];
7453 if (cache) {
7454 for (var i = cache.length - 1, data; i >= 0; i--) {
7455 data = cache[i];
7456 if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues)) {
7457 return data;
7458 }
7459 }
7460 }
7461 },
7462 clear: function () {
7463 this.cache = {};
7464 },
7465 _objectsEqual: function (target, source) {
7466 var t, s;
7467 for (var i in target) {
7468 t = target[i], s = source[i];
7469 if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s)) {
7470 return false;
7471 }
7472 }
7473 if (Array.isArray(target)) {
7474 return target.length === source.length;
7475 }
7476 return true;
7477 },
7478 _objectsStrictlyEqual: function (target, source) {
7479 return this._objectsEqual(target, source) && this._objectsEqual(source, target);
7480 }
7481 };
7482 }());
7483 Polymer.StyleDefaults = function () {
7484 var styleProperties = Polymer.StyleProperties;
7485 var styleUtil = Polymer.StyleUtil;
7486 var StyleCache = Polymer.StyleCache;
7487 var api = {
7488 _styles: [],
7489 _properties: null,
7490 customStyle: {},
7491 _styleCache: new StyleCache(),
7492 addStyle: function (style) {
7493 this._styles.push(style);
7494 this._properties = null;
7495 },
7496 get _styleProperties() {
7497 if (!this._properties) {
7498 styleProperties.decorateStyles(this._styles);
7499 this._styles._scopeStyleProperties = null;
7500 this._properties = styleProperties.scopePropertiesFromStyles(this._styles);
7501 styleProperties.mixinCustomStyle(this._properties, this.customStyle);
7502 styleProperties.reify(this._properties);
7503 }
7504 return this._properties;
7505 },
7506 _needsStyleProperties: function () {
7507 },
7508 _computeStyleProperties: function () {
7509 return this._styleProperties;
7510 },
7511 updateStyles: function (properties) {
7512 this._properties = null;
7513 if (properties) {
7514 Polymer.Base.mixin(this.customStyle, properties);
7515 }
7516 this._styleCache.clear();
7517 for (var i = 0, s; i < this._styles.length; i++) {
7518 s = this._styles[i];
7519 s = s.__importElement || s;
7520 s._apply();
7521 }
7522 }
7523 };
7524 return api;
7525 }();
7526 (function () {
7527 'use strict';
7528 var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
7529 var propertyUtils = Polymer.StyleProperties;
7530 var styleTransformer = Polymer.StyleTransformer;
7531 var styleUtil = Polymer.StyleUtil;
7532 var styleDefaults = Polymer.StyleDefaults;
7533 var nativeShadow = Polymer.Settings.useNativeShadow;
7534 Polymer.Base._addFeature({
7535 _prepStyleProperties: function () {
7536 this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._ styles) : [];
7537 },
7538 customStyle: {},
7539 _setupStyleProperties: function () {
7540 this.customStyle = {};
7541 },
7542 _needsStyleProperties: function () {
7543 return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length );
7544 },
7545 _beforeAttached: function () {
7546 if (!this._scopeSelector && this._needsStyleProperties()) {
7547 this._updateStyleProperties();
7548 }
7549 },
7550 _findStyleHost: function () {
7551 var e = this, root;
7552 while (root = Polymer.dom(e).getOwnerRoot()) {
7553 if (Polymer.isInstance(root.host)) {
7554 return root.host;
7555 }
7556 e = root.host;
7557 }
7558 return styleDefaults;
7559 },
7560 _updateStyleProperties: function () {
7561 var info, scope = this._findStyleHost();
7562 if (!scope._styleCache) {
7563 scope._styleCache = new Polymer.StyleCache();
7564 }
7565 var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this);
7566 scopeData.key.customStyle = this.customStyle;
7567 info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
7568 var scopeCached = Boolean(info);
7569 if (scopeCached) {
7570 this._styleProperties = info._styleProperties;
7571 } else {
7572 this._computeStyleProperties(scopeData.properties);
7573 }
7574 this._computeOwnStyleProperties();
7575 if (!scopeCached) {
7576 info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles);
7577 }
7578 var globalCached = Boolean(info) && !scopeCached;
7579 var style = this._applyStyleProperties(info);
7580 if (!scopeCached) {
7581 style = style && nativeShadow ? style.cloneNode(true) : style;
7582 info = {
7583 style: style,
7584 _scopeSelector: this._scopeSelector,
7585 _styleProperties: this._styleProperties
7586 };
7587 scopeData.key.customStyle = {};
7588 this.mixin(scopeData.key.customStyle, this.customStyle);
7589 scope._styleCache.store(this.is, info, scopeData.key, this._styles);
7590 if (!globalCached) {
7591 styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._s tyles);
7592 }
7593 }
7594 },
7595 _computeStyleProperties: function (scopeProps) {
7596 var scope = this._findStyleHost();
7597 if (!scope._styleProperties) {
7598 scope._computeStyleProperties();
7599 }
7600 var props = Object.create(scope._styleProperties);
7601 this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
7602 scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, t his).properties;
7603 this.mixin(props, scopeProps);
7604 this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles));
7605 propertyUtils.mixinCustomStyle(props, this.customStyle);
7606 propertyUtils.reify(props);
7607 this._styleProperties = props;
7608 },
7609 _computeOwnStyleProperties: function () {
7610 var props = {};
7611 for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) {
7612 n = this._ownStylePropertyNames[i];
7613 props[n] = this._styleProperties[n];
7614 }
7615 this._ownStyleProperties = props;
7616 },
7617 _scopeCount: 0,
7618 _applyStyleProperties: function (info) {
7619 var oldScopeSelector = this._scopeSelector;
7620 this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto_ _._scopeCount++;
7621 var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._s copeSelector, info && info.style);
7622 if (!nativeShadow) {
7623 propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelec tor, this._scopeCssViaAttr);
7624 }
7625 return style;
7626 },
7627 serializeValueToAttribute: function (value, attribute, node) {
7628 node = node || this;
7629 if (attribute === 'class' && !nativeShadow) {
7630 var host = node === this ? this.domHost || this.dataHost : this;
7631 if (host) {
7632 value = host._scopeElementClass(node, value);
7633 }
7634 }
7635 node = Polymer.dom(node);
7636 serializeValueToAttribute.call(this, value, attribute, node);
7637 },
7638 _scopeElementClass: function (element, selector) {
7639 if (!nativeShadow && !this._scopeCssViaAttr) {
7640 selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scope Selector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : '');
7641 }
7642 return selector;
7643 },
7644 updateStyles: function (properties) {
7645 if (this.isAttached) {
7646 if (properties) {
7647 this.mixin(this.customStyle, properties);
7648 }
7649 if (this._needsStyleProperties()) {
7650 this._updateStyleProperties();
7651 } else {
7652 this._styleProperties = null;
7653 }
7654 if (this._styleCache) {
7655 this._styleCache.clear();
7656 }
7657 this._updateRootStyles();
7658 }
7659 },
7660 _updateRootStyles: function (root) {
7661 root = root || this.root;
7662 var c$ = Polymer.dom(root)._query(function (e) {
7663 return e.shadyRoot || e.shadowRoot;
7664 });
7665 for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) {
7666 if (c.updateStyles) {
7667 c.updateStyles();
7668 }
7669 }
7670 }
7671 });
7672 Polymer.updateStyles = function (properties) {
7673 styleDefaults.updateStyles(properties);
7674 Polymer.Base._updateRootStyles(document);
7675 };
7676 var styleCache = new Polymer.StyleCache();
7677 Polymer.customStyleCache = styleCache;
7678 var SCOPE_NAME = styleTransformer.SCOPE_NAME;
7679 var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
7680 }());
7681 Polymer.Base._addFeature({
7682 _registerFeatures: function () {
7683 this._prepIs();
7684 this._prepAttributes();
7685 this._prepConstructor();
7686 this._prepTemplate();
7687 this._prepStyles();
7688 this._prepStyleProperties();
7689 this._prepAnnotations();
7690 this._prepEffects();
7691 this._prepBehaviors();
7692 this._prepBindings();
7693 this._prepShady();
7694 },
7695 _prepBehavior: function (b) {
7696 this._addPropertyEffects(b.properties);
7697 this._addComplexObserverEffects(b.observers);
7698 this._addHostAttributes(b.hostAttributes);
7699 },
7700 _initFeatures: function () {
7701 this._poolContent();
7702 this._setupConfigure();
7703 this._setupStyleProperties();
7704 this._pushHost();
7705 this._stampTemplate();
7706 this._popHost();
7707 this._marshalAnnotationReferences();
7708 this._setupDebouncers();
7709 this._marshalInstanceEffects();
7710 this._marshalHostAttributes();
7711 this._marshalBehaviors();
7712 this._marshalAttributes();
7713 this._tryReady();
7714 },
7715 _marshalBehavior: function (b) {
7716 this._listenListeners(b.listeners);
7717 }
7718 });
7719 (function () {
7720 var nativeShadow = Polymer.Settings.useNativeShadow;
7721 var propertyUtils = Polymer.StyleProperties;
7722 var styleUtil = Polymer.StyleUtil;
7723 var cssParse = Polymer.CssParse;
7724 var styleDefaults = Polymer.StyleDefaults;
7725 var styleTransformer = Polymer.StyleTransformer;
7726 Polymer({
7727 is: 'custom-style',
7728 extends: 'style',
7729 properties: { include: String },
7730 ready: function () {
7731 this._tryApply();
7732 },
7733 attached: function () {
7734 this._tryApply();
7735 },
7736 _tryApply: function () {
7737 if (!this._appliesToDocument) {
7738 if (this.parentNode && this.parentNode.localName !== 'dom-module') {
7739 this._appliesToDocument = true;
7740 var e = this.__appliedElement || this;
7741 styleDefaults.addStyle(e);
7742 if (e.textContent || this.include) {
7743 this._apply();
7744 } else {
7745 var observer = new MutationObserver(function () {
7746 observer.disconnect();
7747 this._apply();
7748 }.bind(this));
7749 observer.observe(e, { childList: true });
7750 }
7751 }
7752 }
7753 },
7754 _apply: function () {
7755 var e = this.__appliedElement || this;
7756 if (this.include) {
7757 e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent;
7758 }
7759 if (e.textContent) {
7760 styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) {
7761 styleTransformer.documentRule(rule);
7762 });
7763 this._applyCustomProperties(e);
7764 }
7765 },
7766 _applyCustomProperties: function (element) {
7767 this._computeStyleProperties();
7768 var props = this._styleProperties;
7769 var rules = styleUtil.rulesForStyle(element);
7770 element.textContent = styleUtil.toCssText(rules, function (rule) {
7771 var css = rule.cssText = rule.parsedCssText;
7772 if (rule.propertyInfo && rule.propertyInfo.cssText) {
7773 css = cssParse.removeCustomPropAssignment(css);
7774 rule.cssText = propertyUtils.valueForProperties(css, props);
7775 }
7776 });
7777 }
7778 });
7779 }());
7780 Polymer.Templatizer = {
7781 properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } },
7782 _instanceProps: Polymer.nob,
7783 _parentPropPrefix: '_parent_',
7784 templatize: function (template) {
7785 if (!template._content) {
7786 template._content = template.content;
7787 }
7788 if (template._content._ctor) {
7789 this.ctor = template._content._ctor;
7790 this._prepParentProperties(this.ctor.prototype, template);
7791 return;
7792 }
7793 var archetype = Object.create(Polymer.Base);
7794 this._customPrepAnnotations(archetype, template);
7795 archetype._prepEffects();
7796 this._customPrepEffects(archetype);
7797 archetype._prepBehaviors();
7798 archetype._prepBindings();
7799 this._prepParentProperties(archetype, template);
7800 archetype._notifyPath = this._notifyPathImpl;
7801 archetype._scopeElementClass = this._scopeElementClassImpl;
7802 archetype.listen = this._listenImpl;
7803 archetype._showHideChildren = this._showHideChildrenImpl;
7804 var _constructor = this._constructorImpl;
7805 var ctor = function TemplateInstance(model, host) {
7806 _constructor.call(this, model, host);
7807 };
7808 ctor.prototype = archetype;
7809 archetype.constructor = ctor;
7810 template._content._ctor = ctor;
7811 this.ctor = ctor;
7812 },
7813 _getRootDataHost: function () {
7814 return this.dataHost && this.dataHost._rootDataHost || this.dataHost;
7815 },
7816 _showHideChildrenImpl: function (hide) {
7817 var c = this._children;
7818 for (var i = 0; i < c.length; i++) {
7819 var n = c[i];
7820 if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
7821 if (n.nodeType === Node.TEXT_NODE) {
7822 if (hide) {
7823 n.__polymerTextContent__ = n.textContent;
7824 n.textContent = '';
7825 } else {
7826 n.textContent = n.__polymerTextContent__;
7827 }
7828 } else if (n.style) {
7829 if (hide) {
7830 n.__polymerDisplay__ = n.style.display;
7831 n.style.display = 'none';
7832 } else {
7833 n.style.display = n.__polymerDisplay__;
7834 }
7835 }
7836 }
7837 n.__hideTemplateChildren__ = hide;
7838 }
7839 },
7840 _debounceTemplate: function (fn) {
7841 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn));
7842 },
7843 _flushTemplates: function (debouncerExpired) {
7844 Polymer.dom.flush();
7845 },
7846 _customPrepEffects: function (archetype) {
7847 var parentProps = archetype._parentProps;
7848 for (var prop in parentProps) {
7849 archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop ));
7850 }
7851 for (var prop in this._instanceProps) {
7852 archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector( prop));
7853 }
7854 },
7855 _customPrepAnnotations: function (archetype, template) {
7856 archetype._template = template;
7857 var c = template._content;
7858 if (!c._notes) {
7859 var rootDataHost = archetype._rootDataHost;
7860 if (rootDataHost) {
7861 Polymer.Annotations.prepElement = rootDataHost._prepElement.bind(rootDataHost);
7862 }
7863 c._notes = Polymer.Annotations.parseAnnotations(template);
7864 Polymer.Annotations.prepElement = null;
7865 this._processAnnotations(c._notes);
7866 }
7867 archetype._notes = c._notes;
7868 archetype._parentProps = c._parentProps;
7869 },
7870 _prepParentProperties: function (archetype, template) {
7871 var parentProps = this._parentProps = archetype._parentProps;
7872 if (this._forwardParentProp && parentProps) {
7873 var proto = archetype._parentPropProto;
7874 var prop;
7875 if (!proto) {
7876 for (prop in this._instanceProps) {
7877 delete parentProps[prop];
7878 }
7879 proto = archetype._parentPropProto = Object.create(null);
7880 if (template != this) {
7881 Polymer.Bind.prepareModel(proto);
7882 }
7883 for (prop in parentProps) {
7884 var parentProp = this._parentPropPrefix + prop;
7885 var effects = [
7886 {
7887 kind: 'function',
7888 effect: this._createForwardPropEffector(prop)
7889 },
7890 { kind: 'notify' }
7891 ];
7892 Polymer.Bind._createAccessors(proto, parentProp, effects);
7893 }
7894 }
7895 if (template != this) {
7896 Polymer.Bind.prepareInstance(template);
7897 template._forwardParentProp = this._forwardParentProp.bind(this);
7898 }
7899 this._extendTemplate(template, proto);
7900 }
7901 },
7902 _createForwardPropEffector: function (prop) {
7903 return function (source, value) {
7904 this._forwardParentProp(prop, value);
7905 };
7906 },
7907 _createHostPropEffector: function (prop) {
7908 var prefix = this._parentPropPrefix;
7909 return function (source, value) {
7910 this.dataHost[prefix + prop] = value;
7911 };
7912 },
7913 _createInstancePropEffector: function (prop) {
7914 return function (source, value, old, fromAbove) {
7915 if (!fromAbove) {
7916 this.dataHost._forwardInstanceProp(this, prop, value);
7917 }
7918 };
7919 },
7920 _extendTemplate: function (template, proto) {
7921 Object.getOwnPropertyNames(proto).forEach(function (n) {
7922 var val = template[n];
7923 var pd = Object.getOwnPropertyDescriptor(proto, n);
7924 Object.defineProperty(template, n, pd);
7925 if (val !== undefined) {
7926 template._propertySetter(n, val);
7927 }
7928 });
7929 },
7930 _showHideChildren: function (hidden) {
7931 },
7932 _forwardInstancePath: function (inst, path, value) {
7933 },
7934 _forwardInstanceProp: function (inst, prop, value) {
7935 },
7936 _notifyPathImpl: function (path, value) {
7937 var dataHost = this.dataHost;
7938 var dot = path.indexOf('.');
7939 var root = dot < 0 ? path : path.slice(0, dot);
7940 dataHost._forwardInstancePath.call(dataHost, this, path, value);
7941 if (root in dataHost._parentProps) {
7942 dataHost.notifyPath(dataHost._parentPropPrefix + path, value);
7943 }
7944 },
7945 _pathEffector: function (path, value, fromAbove) {
7946 if (this._forwardParentPath) {
7947 if (path.indexOf(this._parentPropPrefix) === 0) {
7948 this._forwardParentPath(path.substring(8), value);
7949 }
7950 }
7951 Polymer.Base._pathEffector.apply(this, arguments);
7952 },
7953 _constructorImpl: function (model, host) {
7954 this._rootDataHost = host._getRootDataHost();
7955 this._setupConfigure(model);
7956 this._pushHost(host);
7957 this.root = this.instanceTemplate(this._template);
7958 this.root.__noContent = !this._notes._hasContent;
7959 this.root.__styleScoped = true;
7960 this._popHost();
7961 this._marshalAnnotatedNodes();
7962 this._marshalInstanceEffects();
7963 this._marshalAnnotatedListeners();
7964 var children = [];
7965 for (var n = this.root.firstChild; n; n = n.nextSibling) {
7966 children.push(n);
7967 n._templateInstance = this;
7968 }
7969 this._children = children;
7970 if (host.__hideTemplateChildren__) {
7971 this._showHideChildren(true);
7972 }
7973 this._tryReady();
7974 },
7975 _listenImpl: function (node, eventName, methodName) {
7976 var model = this;
7977 var host = this._rootDataHost;
7978 var handler = host._createEventHandler(node, eventName, methodName);
7979 var decorated = function (e) {
7980 e.model = model;
7981 handler(e);
7982 };
7983 host._listen(node, eventName, decorated);
7984 },
7985 _scopeElementClassImpl: function (node, value) {
7986 var host = this._rootDataHost;
7987 if (host) {
7988 return host._scopeElementClass(node, value);
7989 }
7990 },
7991 stamp: function (model) {
7992 model = model || {};
7993 if (this._parentProps) {
7994 for (var prop in this._parentProps) {
7995 model[prop] = this[this._parentPropPrefix + prop];
7996 }
7997 }
7998 return new this.ctor(model, this);
7999 },
8000 modelForElement: function (el) {
8001 var model;
8002 while (el) {
8003 if (model = el._templateInstance) {
8004 if (model.dataHost != this) {
8005 el = model.dataHost;
8006 } else {
8007 return model;
8008 }
8009 } else {
8010 el = el.parentNode;
8011 }
8012 }
8013 }
8014 };
8015 Polymer({
8016 is: 'dom-template',
8017 extends: 'template',
8018 behaviors: [Polymer.Templatizer],
8019 ready: function () {
8020 this.templatize(this);
8021 }
8022 });
8023 Polymer._collections = new WeakMap();
8024 Polymer.Collection = function (userArray) {
8025 Polymer._collections.set(userArray, this);
8026 this.userArray = userArray;
8027 this.store = userArray.slice();
8028 this.initMap();
8029 };
8030 Polymer.Collection.prototype = {
8031 constructor: Polymer.Collection,
8032 initMap: function () {
8033 var omap = this.omap = new WeakMap();
8034 var pmap = this.pmap = {};
8035 var s = this.store;
8036 for (var i = 0; i < s.length; i++) {
8037 var item = s[i];
8038 if (item && typeof item == 'object') {
8039 omap.set(item, i);
8040 } else {
8041 pmap[item] = i;
8042 }
8043 }
8044 },
8045 add: function (item) {
8046 var key = this.store.push(item) - 1;
8047 if (item && typeof item == 'object') {
8048 this.omap.set(item, key);
8049 } else {
8050 this.pmap[item] = key;
8051 }
8052 return key;
8053 },
8054 removeKey: function (key) {
8055 this._removeFromMap(this.store[key]);
8056 delete this.store[key];
8057 },
8058 _removeFromMap: function (item) {
8059 if (item && typeof item == 'object') {
8060 this.omap.delete(item);
8061 } else {
8062 delete this.pmap[item];
8063 }
8064 },
8065 remove: function (item) {
8066 var key = this.getKey(item);
8067 this.removeKey(key);
8068 return key;
8069 },
8070 getKey: function (item) {
8071 if (item && typeof item == 'object') {
8072 return this.omap.get(item);
8073 } else {
8074 return this.pmap[item];
8075 }
8076 },
8077 getKeys: function () {
8078 return Object.keys(this.store);
8079 },
8080 setItem: function (key, item) {
8081 var old = this.store[key];
8082 if (old) {
8083 this._removeFromMap(old);
8084 }
8085 if (item && typeof item == 'object') {
8086 this.omap.set(item, key);
8087 } else {
8088 this.pmap[item] = key;
8089 }
8090 this.store[key] = item;
8091 },
8092 getItem: function (key) {
8093 return this.store[key];
8094 },
8095 getItems: function () {
8096 var items = [], store = this.store;
8097 for (var key in store) {
8098 items.push(store[key]);
8099 }
8100 return items;
8101 },
8102 _applySplices: function (splices) {
8103 var keyMap = {}, key, i;
8104 splices.forEach(function (s) {
8105 s.addedKeys = [];
8106 for (i = 0; i < s.removed.length; i++) {
8107 key = this.getKey(s.removed[i]);
8108 keyMap[key] = keyMap[key] ? null : -1;
8109 }
8110 for (i = 0; i < s.addedCount; i++) {
8111 var item = this.userArray[s.index + i];
8112 key = this.getKey(item);
8113 key = key === undefined ? this.add(item) : key;
8114 keyMap[key] = keyMap[key] ? null : 1;
8115 s.addedKeys.push(key);
8116 }
8117 }, this);
8118 var removed = [];
8119 var added = [];
8120 for (var key in keyMap) {
8121 if (keyMap[key] < 0) {
8122 this.removeKey(key);
8123 removed.push(key);
8124 }
8125 if (keyMap[key] > 0) {
8126 added.push(key);
8127 }
8128 }
8129 return [{
8130 removed: removed,
8131 added: added
8132 }];
8133 }
8134 };
8135 Polymer.Collection.get = function (userArray) {
8136 return Polymer._collections.get(userArray) || new Polymer.Collection(userArray);
8137 };
8138 Polymer.Collection.applySplices = function (userArray, splices) {
8139 var coll = Polymer._collections.get(userArray);
8140 return coll ? coll._applySplices(splices) : null;
8141 };
8142 Polymer({
8143 is: 'dom-repeat',
8144 extends: 'template',
8145 properties: {
8146 items: { type: Array },
8147 as: {
8148 type: String,
8149 value: 'item'
8150 },
8151 indexAs: {
8152 type: String,
8153 value: 'index'
8154 },
8155 sort: {
8156 type: Function,
8157 observer: '_sortChanged'
8158 },
8159 filter: {
8160 type: Function,
8161 observer: '_filterChanged'
8162 },
8163 observe: {
8164 type: String,
8165 observer: '_observeChanged'
8166 },
8167 delay: Number
8168 },
8169 behaviors: [Polymer.Templatizer],
8170 observers: ['_itemsChanged(items.*)'],
8171 created: function () {
8172 this._instances = [];
8173 },
8174 detached: function () {
8175 for (var i = 0; i < this._instances.length; i++) {
8176 this._detachRow(i);
8177 }
8178 },
8179 attached: function () {
8180 var parentNode = Polymer.dom(this).parentNode;
8181 for (var i = 0; i < this._instances.length; i++) {
8182 Polymer.dom(parentNode).insertBefore(this._instances[i].root, this);
8183 }
8184 },
8185 ready: function () {
8186 this._instanceProps = { __key__: true };
8187 this._instanceProps[this.as] = true;
8188 this._instanceProps[this.indexAs] = true;
8189 if (!this.ctor) {
8190 this.templatize(this);
8191 }
8192 },
8193 _sortChanged: function () {
8194 var dataHost = this._getRootDataHost();
8195 var sort = this.sort;
8196 this._sortFn = sort && (typeof sort == 'function' ? sort : function () {
8197 return dataHost[sort].apply(dataHost, arguments);
8198 });
8199 this._needFullRefresh = true;
8200 if (this.items) {
8201 this._debounceTemplate(this._render);
8202 }
8203 },
8204 _filterChanged: function () {
8205 var dataHost = this._getRootDataHost();
8206 var filter = this.filter;
8207 this._filterFn = filter && (typeof filter == 'function' ? filter : function () {
8208 return dataHost[filter].apply(dataHost, arguments);
8209 });
8210 this._needFullRefresh = true;
8211 if (this.items) {
8212 this._debounceTemplate(this._render);
8213 }
8214 },
8215 _observeChanged: function () {
8216 this._observePaths = this.observe && this.observe.replace('.*', '.').split(' ');
8217 },
8218 _itemsChanged: function (change) {
8219 if (change.path == 'items') {
8220 if (Array.isArray(this.items)) {
8221 this.collection = Polymer.Collection.get(this.items);
8222 } else if (!this.items) {
8223 this.collection = null;
8224 } else {
8225 this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', t his.items));
8226 }
8227 this._keySplices = [];
8228 this._indexSplices = [];
8229 this._needFullRefresh = true;
8230 this._debounceTemplate(this._render);
8231 } else if (change.path == 'items.splices') {
8232 this._keySplices = this._keySplices.concat(change.value.keySplices);
8233 this._indexSplices = this._indexSplices.concat(change.value.indexSplices);
8234 this._debounceTemplate(this._render);
8235 } else {
8236 var subpath = change.path.slice(6);
8237 this._forwardItemPath(subpath, change.value);
8238 this._checkObservedPaths(subpath);
8239 }
8240 },
8241 _checkObservedPaths: function (path) {
8242 if (this._observePaths) {
8243 path = path.substring(path.indexOf('.') + 1);
8244 var paths = this._observePaths;
8245 for (var i = 0; i < paths.length; i++) {
8246 if (path.indexOf(paths[i]) === 0) {
8247 this._needFullRefresh = true;
8248 if (this.delay) {
8249 this.debounce('render', this._render, this.delay);
8250 } else {
8251 this._debounceTemplate(this._render);
8252 }
8253 return;
8254 }
8255 }
8256 }
8257 },
8258 render: function () {
8259 this._needFullRefresh = true;
8260 this._debounceTemplate(this._render);
8261 this._flushTemplates();
8262 },
8263 _render: function () {
8264 var c = this.collection;
8265 if (this._needFullRefresh) {
8266 this._applyFullRefresh();
8267 this._needFullRefresh = false;
8268 } else {
8269 if (this._sortFn) {
8270 this._applySplicesUserSort(this._keySplices);
8271 } else {
8272 if (this._filterFn) {
8273 this._applyFullRefresh();
8274 } else {
8275 this._applySplicesArrayOrder(this._indexSplices);
8276 }
8277 }
8278 }
8279 this._keySplices = [];
8280 this._indexSplices = [];
8281 var keyToIdx = this._keyToInstIdx = {};
8282 for (var i = 0; i < this._instances.length; i++) {
8283 var inst = this._instances[i];
8284 keyToIdx[inst.__key__] = i;
8285 inst.__setProperty(this.indexAs, i, true);
8286 }
8287 this.fire('dom-change');
8288 },
8289 _applyFullRefresh: function () {
8290 var c = this.collection;
8291 var keys;
8292 if (this._sortFn) {
8293 keys = c ? c.getKeys() : [];
8294 } else {
8295 keys = [];
8296 var items = this.items;
8297 if (items) {
8298 for (var i = 0; i < items.length; i++) {
8299 keys.push(c.getKey(items[i]));
8300 }
8301 }
8302 }
8303 if (this._filterFn) {
8304 keys = keys.filter(function (a) {
8305 return this._filterFn(c.getItem(a));
8306 }, this);
8307 }
8308 if (this._sortFn) {
8309 keys.sort(function (a, b) {
8310 return this._sortFn(c.getItem(a), c.getItem(b));
8311 }.bind(this));
8312 }
8313 for (var i = 0; i < keys.length; i++) {
8314 var key = keys[i];
8315 var inst = this._instances[i];
8316 if (inst) {
8317 inst.__setProperty('__key__', key, true);
8318 inst.__setProperty(this.as, c.getItem(key), true);
8319 } else {
8320 this._instances.push(this._insertRow(i, key));
8321 }
8322 }
8323 for (; i < this._instances.length; i++) {
8324 this._detachRow(i);
8325 }
8326 this._instances.splice(keys.length, this._instances.length - keys.length);
8327 },
8328 _keySort: function (a, b) {
8329 return this.collection.getKey(a) - this.collection.getKey(b);
8330 },
8331 _numericSort: function (a, b) {
8332 return a - b;
8333 },
8334 _applySplicesUserSort: function (splices) {
8335 var c = this.collection;
8336 var instances = this._instances;
8337 var keyMap = {};
8338 var pool = [];
8339 var sortFn = this._sortFn || this._keySort.bind(this);
8340 splices.forEach(function (s) {
8341 for (var i = 0; i < s.removed.length; i++) {
8342 var key = s.removed[i];
8343 keyMap[key] = keyMap[key] ? null : -1;
8344 }
8345 for (var i = 0; i < s.added.length; i++) {
8346 var key = s.added[i];
8347 keyMap[key] = keyMap[key] ? null : 1;
8348 }
8349 }, this);
8350 var removedIdxs = [];
8351 var addedKeys = [];
8352 for (var key in keyMap) {
8353 if (keyMap[key] === -1) {
8354 removedIdxs.push(this._keyToInstIdx[key]);
8355 }
8356 if (keyMap[key] === 1) {
8357 addedKeys.push(key);
8358 }
8359 }
8360 if (removedIdxs.length) {
8361 removedIdxs.sort(this._numericSort);
8362 for (var i = removedIdxs.length - 1; i >= 0; i--) {
8363 var idx = removedIdxs[i];
8364 if (idx !== undefined) {
8365 pool.push(this._detachRow(idx));
8366 instances.splice(idx, 1);
8367 }
8368 }
8369 }
8370 if (addedKeys.length) {
8371 if (this._filterFn) {
8372 addedKeys = addedKeys.filter(function (a) {
8373 return this._filterFn(c.getItem(a));
8374 }, this);
8375 }
8376 addedKeys.sort(function (a, b) {
8377 return this._sortFn(c.getItem(a), c.getItem(b));
8378 }.bind(this));
8379 var start = 0;
8380 for (var i = 0; i < addedKeys.length; i++) {
8381 start = this._insertRowUserSort(start, addedKeys[i], pool);
8382 }
8383 }
8384 },
8385 _insertRowUserSort: function (start, key, pool) {
8386 var c = this.collection;
8387 var item = c.getItem(key);
8388 var end = this._instances.length - 1;
8389 var idx = -1;
8390 var sortFn = this._sortFn || this._keySort.bind(this);
8391 while (start <= end) {
8392 var mid = start + end >> 1;
8393 var midKey = this._instances[mid].__key__;
8394 var cmp = sortFn(c.getItem(midKey), item);
8395 if (cmp < 0) {
8396 start = mid + 1;
8397 } else if (cmp > 0) {
8398 end = mid - 1;
8399 } else {
8400 idx = mid;
8401 break;
8402 }
8403 }
8404 if (idx < 0) {
8405 idx = end + 1;
8406 }
8407 this._instances.splice(idx, 0, this._insertRow(idx, key, pool));
8408 return idx;
8409 },
8410 _applySplicesArrayOrder: function (splices) {
8411 var pool = [];
8412 var c = this.collection;
8413 splices.forEach(function (s) {
8414 for (var i = 0; i < s.removed.length; i++) {
8415 var inst = this._detachRow(s.index + i);
8416 if (!inst.isPlaceholder) {
8417 pool.push(inst);
8418 }
8419 }
8420 this._instances.splice(s.index, s.removed.length);
8421 for (var i = 0; i < s.addedKeys.length; i++) {
8422 var inst = {
8423 isPlaceholder: true,
8424 key: s.addedKeys[i]
8425 };
8426 this._instances.splice(s.index + i, 0, inst);
8427 }
8428 }, this);
8429 for (var i = this._instances.length - 1; i >= 0; i--) {
8430 var inst = this._instances[i];
8431 if (inst.isPlaceholder) {
8432 this._instances[i] = this._insertRow(i, inst.key, pool, true);
8433 }
8434 }
8435 },
8436 _detachRow: function (idx) {
8437 var inst = this._instances[idx];
8438 if (!inst.isPlaceholder) {
8439 var parentNode = Polymer.dom(this).parentNode;
8440 for (var i = 0; i < inst._children.length; i++) {
8441 var el = inst._children[i];
8442 Polymer.dom(inst.root).appendChild(el);
8443 }
8444 }
8445 return inst;
8446 },
8447 _insertRow: function (idx, key, pool, replace) {
8448 var inst;
8449 if (inst = pool && pool.pop()) {
8450 inst.__setProperty(this.as, this.collection.getItem(key), true);
8451 inst.__setProperty('__key__', key, true);
8452 } else {
8453 inst = this._generateRow(idx, key);
8454 }
8455 var beforeRow = this._instances[replace ? idx + 1 : idx];
8456 var beforeNode = beforeRow ? beforeRow._children[0] : this;
8457 var parentNode = Polymer.dom(this).parentNode;
8458 Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
8459 return inst;
8460 },
8461 _generateRow: function (idx, key) {
8462 var model = { __key__: key };
8463 model[this.as] = this.collection.getItem(key);
8464 model[this.indexAs] = idx;
8465 var inst = this.stamp(model);
8466 return inst;
8467 },
8468 _showHideChildren: function (hidden) {
8469 for (var i = 0; i < this._instances.length; i++) {
8470 this._instances[i]._showHideChildren(hidden);
8471 }
8472 },
8473 _forwardInstanceProp: function (inst, prop, value) {
8474 if (prop == this.as) {
8475 var idx;
8476 if (this._sortFn || this._filterFn) {
8477 idx = this.items.indexOf(this.collection.getItem(inst.__key__));
8478 } else {
8479 idx = inst[this.indexAs];
8480 }
8481 this.set('items.' + idx, value);
8482 }
8483 },
8484 _forwardInstancePath: function (inst, path, value) {
8485 if (path.indexOf(this.as + '.') === 0) {
8486 this.notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1), value);
8487 }
8488 },
8489 _forwardParentProp: function (prop, value) {
8490 this._instances.forEach(function (inst) {
8491 inst.__setProperty(prop, value, true);
8492 }, this);
8493 },
8494 _forwardParentPath: function (path, value) {
8495 this._instances.forEach(function (inst) {
8496 inst.notifyPath(path, value, true);
8497 }, this);
8498 },
8499 _forwardItemPath: function (path, value) {
8500 if (this._keyToInstIdx) {
8501 var dot = path.indexOf('.');
8502 var key = path.substring(0, dot < 0 ? path.length : dot);
8503 var idx = this._keyToInstIdx[key];
8504 var inst = this._instances[idx];
8505 if (inst) {
8506 if (dot >= 0) {
8507 path = this.as + '.' + path.substring(dot + 1);
8508 inst.notifyPath(path, value, true);
8509 } else {
8510 inst.__setProperty(this.as, value, true);
8511 }
8512 }
8513 }
8514 },
8515 itemForElement: function (el) {
8516 var instance = this.modelForElement(el);
8517 return instance && instance[this.as];
8518 },
8519 keyForElement: function (el) {
8520 var instance = this.modelForElement(el);
8521 return instance && instance.__key__;
8522 },
8523 indexForElement: function (el) {
8524 var instance = this.modelForElement(el);
8525 return instance && instance[this.indexAs];
8526 }
8527 });
8528 Polymer({
8529 is: 'array-selector',
8530 properties: {
8531 items: {
8532 type: Array,
8533 observer: 'clearSelection'
8534 },
8535 multi: {
8536 type: Boolean,
8537 value: false,
8538 observer: 'clearSelection'
8539 },
8540 selected: {
8541 type: Object,
8542 notify: true
8543 },
8544 selectedItem: {
8545 type: Object,
8546 notify: true
8547 },
8548 toggle: {
8549 type: Boolean,
8550 value: false
8551 }
8552 },
8553 clearSelection: function () {
8554 if (Array.isArray(this.selected)) {
8555 for (var i = 0; i < this.selected.length; i++) {
8556 this.unlinkPaths('selected.' + i);
8557 }
8558 } else {
8559 this.unlinkPaths('selected');
8560 }
8561 if (this.multi) {
8562 if (!this.selected || this.selected.length) {
8563 this.selected = [];
8564 this._selectedColl = Polymer.Collection.get(this.selected);
8565 }
8566 } else {
8567 this.selected = null;
8568 this._selectedColl = null;
8569 }
8570 this.selectedItem = null;
8571 },
8572 isSelected: function (item) {
8573 if (this.multi) {
8574 return this._selectedColl.getKey(item) !== undefined;
8575 } else {
8576 return this.selected == item;
8577 }
8578 },
8579 deselect: function (item) {
8580 if (this.multi) {
8581 if (this.isSelected(item)) {
8582 var skey = this._selectedColl.getKey(item);
8583 this.arrayDelete('selected', item);
8584 this.unlinkPaths('selected.' + skey);
8585 }
8586 } else {
8587 this.selected = null;
8588 this.selectedItem = null;
8589 this.unlinkPaths('selected');
8590 this.unlinkPaths('selectedItem');
8591 }
8592 },
8593 select: function (item) {
8594 var icol = Polymer.Collection.get(this.items);
8595 var key = icol.getKey(item);
8596 if (this.multi) {
8597 if (this.isSelected(item)) {
8598 if (this.toggle) {
8599 this.deselect(item);
8600 }
8601 } else {
8602 this.push('selected', item);
8603 skey = this._selectedColl.getKey(item);
8604 this.linkPaths('selected.' + skey, 'items.' + key);
8605 }
8606 } else {
8607 if (this.toggle && item == this.selected) {
8608 this.deselect();
8609 } else {
8610 this.selected = item;
8611 this.selectedItem = item;
8612 this.linkPaths('selected', 'items.' + key);
8613 this.linkPaths('selectedItem', 'items.' + key);
8614 }
8615 }
8616 }
8617 });
8618 Polymer({
8619 is: 'dom-if',
8620 extends: 'template',
8621 properties: {
8622 'if': {
8623 type: Boolean,
8624 value: false,
8625 observer: '_queueRender'
8626 },
8627 restamp: {
8628 type: Boolean,
8629 value: false,
8630 observer: '_queueRender'
8631 }
8632 },
8633 behaviors: [Polymer.Templatizer],
8634 _queueRender: function () {
8635 this._debounceTemplate(this._render);
8636 },
8637 detached: function () {
8638 this._teardownInstance();
8639 },
8640 attached: function () {
8641 if (this.if && this.ctor) {
8642 this.async(this._ensureInstance);
8643 }
8644 },
8645 render: function () {
8646 this._flushTemplates();
8647 },
8648 _render: function () {
8649 if (this.if) {
8650 if (!this.ctor) {
8651 this.templatize(this);
8652 }
8653 this._ensureInstance();
8654 this._showHideChildren();
8655 } else if (this.restamp) {
8656 this._teardownInstance();
8657 }
8658 if (!this.restamp && this._instance) {
8659 this._showHideChildren();
8660 }
8661 if (this.if != this._lastIf) {
8662 this.fire('dom-change');
8663 this._lastIf = this.if;
8664 }
8665 },
8666 _ensureInstance: function () {
8667 if (!this._instance) {
8668 this._instance = this.stamp();
8669 var root = this._instance.root;
8670 var parent = Polymer.dom(Polymer.dom(this).parentNode);
8671 parent.insertBefore(root, this);
8672 }
8673 },
8674 _teardownInstance: function () {
8675 if (this._instance) {
8676 var c = this._instance._children;
8677 if (c) {
8678 var parent = Polymer.dom(Polymer.dom(c[0]).parentNode);
8679 c.forEach(function (n) {
8680 parent.removeChild(n);
8681 });
8682 }
8683 this._instance = null;
8684 }
8685 },
8686 _showHideChildren: function () {
8687 var hidden = this.__hideTemplateChildren__ || !this.if;
8688 if (this._instance) {
8689 this._instance._showHideChildren(hidden);
8690 }
8691 },
8692 _forwardParentProp: function (prop, value) {
8693 if (this._instance) {
8694 this._instance[prop] = value;
8695 }
8696 },
8697 _forwardParentPath: function (path, value) {
8698 if (this._instance) {
8699 this._instance.notifyPath(path, value, true);
8700 }
8701 }
8702 });
8703 Polymer({
8704 is: 'dom-bind',
8705 extends: 'template',
8706 created: function () {
8707 Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this));
8708 },
8709 _ensureReady: function () {
8710 if (!this._readied) {
8711 this._readySelf();
8712 }
8713 },
8714 _markImportsReady: function () {
8715 this._importsReady = true;
8716 this._ensureReady();
8717 },
8718 _registerFeatures: function () {
8719 this._prepConstructor();
8720 },
8721 _insertChildren: function () {
8722 var parentDom = Polymer.dom(Polymer.dom(this).parentNode);
8723 parentDom.insertBefore(this.root, this);
8724 },
8725 _removeChildren: function () {
8726 if (this._children) {
8727 for (var i = 0; i < this._children.length; i++) {
8728 this.root.appendChild(this._children[i]);
8729 }
8730 }
8731 },
8732 _initFeatures: function () {
8733 },
8734 _scopeElementClass: function (element, selector) {
8735 if (this.dataHost) {
8736 return this.dataHost._scopeElementClass(element, selector);
8737 } else {
8738 return selector;
8739 }
8740 },
8741 _prepConfigure: function () {
8742 var config = {};
8743 for (var prop in this._propertyEffects) {
8744 config[prop] = this[prop];
8745 }
8746 this._setupConfigure = this._setupConfigure.bind(this, config);
8747 },
8748 attached: function () {
8749 if (this._importsReady) {
8750 this.render();
8751 }
8752 },
8753 detached: function () {
8754 this._removeChildren();
8755 },
8756 render: function () {
8757 this._ensureReady();
8758 if (!this._children) {
8759 this._template = this;
8760 this._prepAnnotations();
8761 this._prepEffects();
8762 this._prepBehaviors();
8763 this._prepConfigure();
8764 this._prepBindings();
8765 Polymer.Base._initFeatures.call(this);
8766 this._children = Array.prototype.slice.call(this.root.childNodes);
8767 }
8768 this._insertChildren();
8769 this.fire('dom-change');
8770 }
8771 });
8772 (function() {
8773
8774 'use strict';
8775
8776 var SHADOW_WHEN_SCROLLING = 1;
8777 var SHADOW_ALWAYS = 2;
8778
8779
8780 var MODE_CONFIGS = {
8781
8782 outerScroll: {
8783 'scroll': true
8784 },
8785
8786 shadowMode: {
8787 'standard': SHADOW_ALWAYS,
8788 'waterfall': SHADOW_WHEN_SCROLLING,
8789 'waterfall-tall': SHADOW_WHEN_SCROLLING
8790 },
8791
8792 tallMode: {
8793 'waterfall-tall': true
8794 }
8795 };
8796
8797 Polymer({
8798
8799 is: 'paper-header-panel',
8800
8801 /**
8802 * Fired when the content has been scrolled. `event.detail.target` return s
8803 * the scrollable element which you can use to access scroll info such as
8804 * `scrollTop`.
8805 *
8806 * <paper-header-panel on-content-scroll="scrollHandler">
8807 * ...
8808 * </paper-header-panel>
8809 *
8810 *
8811 * scrollHandler: function(event) {
8812 * var scroller = event.detail.target;
8813 * console.log(scroller.scrollTop);
8814 * }
8815 *
8816 * @event content-scroll
8817 */
8818
8819 properties: {
8820
8821 /**
8822 * Controls header and scrolling behavior. Options are
8823 * `standard`, `seamed`, `waterfall`, `waterfall-tall`, `scroll` and
8824 * `cover`. Default is `standard`.
8825 *
8826 * `standard`: The header is a step above the panel. The header will con sume the
8827 * panel at the point of entry, preventing it from passing through to th e
8828 * opposite side.
8829 *
8830 * `seamed`: The header is presented as seamed with the panel.
8831 *
8832 * `waterfall`: Similar to standard mode, but header is initially presen ted as
8833 * seamed with panel, but then separates to form the step.
8834 *
8835 * `waterfall-tall`: The header is initially taller (`tall` class is add ed to
8836 * the header). As the user scrolls, the header separates (forming an e dge)
8837 * while condensing (`tall` class is removed from the header).
8838 *
8839 * `scroll`: The header keeps its seam with the panel, and is pushed off screen.
8840 *
8841 * `cover`: The panel covers the whole `paper-header-panel` including th e
8842 * header. This allows user to style the panel in such a way that the pa nel is
8843 * partially covering the header.
8844 *
8845 * <paper-header-panel mode="cover">
8846 * <paper-toolbar class="tall">
8847 * <core-icon-button icon="menu"></core-icon-button>
8848 * </paper-toolbar>
8849 * <div class="content"></div>
8850 * </paper-header-panel>
8851 */
8852 mode: {
8853 type: String,
8854 value: 'standard',
8855 observer: '_modeChanged',
8856 reflectToAttribute: true
8857 },
8858
8859 /**
8860 * If true, the drop-shadow is always shown no matter what mode is set t o.
8861 */
8862 shadow: {
8863 type: Boolean,
8864 value: false
8865 },
8866
8867 /**
8868 * The class used in waterfall-tall mode. Change this if the header
8869 * accepts a different class for toggling height, e.g. "medium-tall"
8870 */
8871 tallClass: {
8872 type: String,
8873 value: 'tall'
8874 },
8875
8876 /**
8877 * If true, the scroller is at the top
8878 */
8879 atTop: {
8880 type: Boolean,
8881 value: true,
8882 readOnly: true
8883 }
8884 },
8885
8886 observers: [
8887 '_computeDropShadowHidden(atTop, mode, shadow)'
8888 ],
8889
8890 ready: function() {
8891 this.scrollHandler = this._scroll.bind(this);
8892 this._addListener();
8893
8894 // Run `scroll` logic once to initialze class names, etc.
8895 this._keepScrollingState();
8896 },
8897
8898 detached: function() {
8899 this._removeListener();
8900 },
8901
8902 /**
8903 * Returns the header element
8904 *
8905 * @property header
8906 * @type Object
8907 */
8908 get header() {
8909 return Polymer.dom(this.$.headerContent).getDistributedNodes()[0];
8910 },
8911
8912 /**
8913 * Returns the scrollable element.
8914 *
8915 * @property scroller
8916 * @type Object
8917 */
8918 get scroller() {
8919 return this._getScrollerForMode(this.mode);
8920 },
8921
8922 /**
8923 * Returns true if the scroller has a visible shadow.
8924 *
8925 * @property visibleShadow
8926 * @type Boolean
8927 */
8928 get visibleShadow() {
8929 return this.$.dropShadow.classList.contains('has-shadow');
8930 },
8931
8932 _computeDropShadowHidden: function(atTop, mode, shadow) {
8933
8934 var shadowMode = MODE_CONFIGS.shadowMode[mode];
8935
8936 if (this.shadow) {
8937 this.toggleClass('has-shadow', true, this.$.dropShadow);
8938
8939 } else if (shadowMode === SHADOW_ALWAYS) {
8940 this.toggleClass('has-shadow', true, this.$.dropShadow);
8941
8942 } else if (shadowMode === SHADOW_WHEN_SCROLLING && !atTop) {
8943 this.toggleClass('has-shadow', true, this.$.dropShadow);
8944
8945 } else {
8946 this.toggleClass('has-shadow', false, this.$.dropShadow);
8947
8948 }
8949 },
8950
8951 _computeMainContainerClass: function(mode) {
8952 // TODO: It will be useful to have a utility for classes
8953 // e.g. Polymer.Utils.classes({ foo: true });
8954
8955 var classes = {};
8956
8957 classes['flex'] = mode !== 'cover';
8958
8959 return Object.keys(classes).filter(
8960 function(className) {
8961 return classes[className];
8962 }).join(' ');
8963 },
8964
8965 _addListener: function() {
8966 this.scroller.addEventListener('scroll', this.scrollHandler, false);
8967 },
8968
8969 _removeListener: function() {
8970 this.scroller.removeEventListener('scroll', this.scrollHandler);
8971 },
8972
8973 _modeChanged: function(newMode, oldMode) {
8974 var configs = MODE_CONFIGS;
8975 var header = this.header;
8976 var animateDuration = 200;
8977
8978 if (header) {
8979 // in tallMode it may add tallClass to the header; so do the cleanup
8980 // when mode is changed from tallMode to not tallMode
8981 if (configs.tallMode[oldMode] && !configs.tallMode[newMode]) {
8982 header.classList.remove(this.tallClass);
8983 this.async(function() {
8984 header.classList.remove('animate');
8985 }, animateDuration);
8986 } else {
8987 header.classList.toggle('animate', configs.tallMode[newMode]);
8988 }
8989 }
8990 this._keepScrollingState();
8991 },
8992
8993 _keepScrollingState: function() {
8994 var main = this.scroller;
8995 var header = this.header;
8996
8997 this._setAtTop(main.scrollTop === 0);
8998
8999 if (header && this.tallClass && MODE_CONFIGS.tallMode[this.mode]) {
9000 this.toggleClass(this.tallClass, this.atTop ||
9001 header.classList.contains(this.tallClass) &&
9002 main.scrollHeight < this.offsetHeight, header);
9003 }
9004 },
9005
9006 _scroll: function() {
9007 this._keepScrollingState();
9008 this.fire('content-scroll', {target: this.scroller}, {bubbles: false});
9009 },
9010
9011 _getScrollerForMode: function(mode) {
9012 return MODE_CONFIGS.outerScroll[mode] ?
9013 this : this.$.mainContainer;
9014 }
9015
9016 });
9017
9018 })();
9019 Polymer({
9020 is: 'paper-material',
9021
9022 properties: {
9023
9024 /**
9025 * The z-depth of this element, from 0-5. Setting to 0 will remove the
9026 * shadow, and each increasing number greater than 0 will be "deeper"
9027 * than the last.
9028 *
9029 * @attribute elevation
9030 * @type number
9031 * @default 1
9032 */
9033 elevation: {
9034 type: Number,
9035 reflectToAttribute: true,
9036 value: 1
9037 },
9038
9039 /**
9040 * Set this to true to animate the shadow when setting a new
9041 * `elevation` value.
9042 *
9043 * @attribute animated
9044 * @type boolean
9045 * @default false
9046 */
9047 animated: {
9048 type: Boolean,
9049 reflectToAttribute: true,
9050 value: false
9051 }
9052 }
9053 });
9054 (function() {
9055 'use strict';
9056
9057 /**
9058 * Chrome uses an older version of DOM Level 3 Keyboard Events
9059 *
9060 * Most keys are labeled as text, but some are Unicode codepoints.
9061 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
9062 */
9063 var KEY_IDENTIFIER = {
9064 'U+0009': 'tab',
9065 'U+001B': 'esc',
9066 'U+0020': 'space',
9067 'U+002A': '*',
9068 'U+0030': '0',
9069 'U+0031': '1',
9070 'U+0032': '2',
9071 'U+0033': '3',
9072 'U+0034': '4',
9073 'U+0035': '5',
9074 'U+0036': '6',
9075 'U+0037': '7',
9076 'U+0038': '8',
9077 'U+0039': '9',
9078 'U+0041': 'a',
9079 'U+0042': 'b',
9080 'U+0043': 'c',
9081 'U+0044': 'd',
9082 'U+0045': 'e',
9083 'U+0046': 'f',
9084 'U+0047': 'g',
9085 'U+0048': 'h',
9086 'U+0049': 'i',
9087 'U+004A': 'j',
9088 'U+004B': 'k',
9089 'U+004C': 'l',
9090 'U+004D': 'm',
9091 'U+004E': 'n',
9092 'U+004F': 'o',
9093 'U+0050': 'p',
9094 'U+0051': 'q',
9095 'U+0052': 'r',
9096 'U+0053': 's',
9097 'U+0054': 't',
9098 'U+0055': 'u',
9099 'U+0056': 'v',
9100 'U+0057': 'w',
9101 'U+0058': 'x',
9102 'U+0059': 'y',
9103 'U+005A': 'z',
9104 'U+007F': 'del'
9105 };
9106
9107 /**
9108 * Special table for KeyboardEvent.keyCode.
9109 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
9110 * than that.
9111 *
9112 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
9113 */
9114 var KEY_CODE = {
9115 9: 'tab',
9116 13: 'enter',
9117 27: 'esc',
9118 33: 'pageup',
9119 34: 'pagedown',
9120 35: 'end',
9121 36: 'home',
9122 32: 'space',
9123 37: 'left',
9124 38: 'up',
9125 39: 'right',
9126 40: 'down',
9127 46: 'del',
9128 106: '*'
9129 };
9130
9131 /**
9132 * MODIFIER_KEYS maps the short name for modifier keys used in a key
9133 * combo string to the property name that references those same keys
9134 * in a KeyboardEvent instance.
9135 */
9136 var MODIFIER_KEYS = {
9137 'shift': 'shiftKey',
9138 'ctrl': 'ctrlKey',
9139 'alt': 'altKey',
9140 'meta': 'metaKey'
9141 };
9142
9143 /**
9144 * KeyboardEvent.key is mostly represented by printable character made by
9145 * the keyboard, with unprintable keys labeled nicely.
9146 *
9147 * However, on OS X, Alt+char can make a Unicode character that follows an
9148 * Apple-specific mapping. In this case, we
9149 * fall back to .keyCode.
9150 */
9151 var KEY_CHAR = /[a-z0-9*]/;
9152
9153 /**
9154 * Matches a keyIdentifier string.
9155 */
9156 var IDENT_CHAR = /U\+/;
9157
9158 /**
9159 * Matches arrow keys in Gecko 27.0+
9160 */
9161 var ARROW_KEY = /^arrow/;
9162
9163 /**
9164 * Matches space keys everywhere (notably including IE10's exceptional name
9165 * `spacebar`).
9166 */
9167 var SPACE_KEY = /^space(bar)?/;
9168
9169 function transformKey(key) {
9170 var validKey = '';
9171 if (key) {
9172 var lKey = key.toLowerCase();
9173 if (lKey.length == 1) {
9174 if (KEY_CHAR.test(lKey)) {
9175 validKey = lKey;
9176 }
9177 } else if (ARROW_KEY.test(lKey)) {
9178 validKey = lKey.replace('arrow', '');
9179 } else if (SPACE_KEY.test(lKey)) {
9180 validKey = 'space';
9181 } else if (lKey == 'multiply') {
9182 // numpad '*' can map to Multiply on IE/Windows
9183 validKey = '*';
9184 } else {
9185 validKey = lKey;
9186 }
9187 }
9188 return validKey;
9189 }
9190
9191 function transformKeyIdentifier(keyIdent) {
9192 var validKey = '';
9193 if (keyIdent) {
9194 if (IDENT_CHAR.test(keyIdent)) {
9195 validKey = KEY_IDENTIFIER[keyIdent];
9196 } else {
9197 validKey = keyIdent.toLowerCase();
9198 }
9199 }
9200 return validKey;
9201 }
9202
9203 function transformKeyCode(keyCode) {
9204 var validKey = '';
9205 if (Number(keyCode)) {
9206 if (keyCode >= 65 && keyCode <= 90) {
9207 // ascii a-z
9208 // lowercase is 32 offset from uppercase
9209 validKey = String.fromCharCode(32 + keyCode);
9210 } else if (keyCode >= 112 && keyCode <= 123) {
9211 // function keys f1-f12
9212 validKey = 'f' + (keyCode - 112);
9213 } else if (keyCode >= 48 && keyCode <= 57) {
9214 // top 0-9 keys
9215 validKey = String(48 - keyCode);
9216 } else if (keyCode >= 96 && keyCode <= 105) {
9217 // num pad 0-9
9218 validKey = String(96 - keyCode);
9219 } else {
9220 validKey = KEY_CODE[keyCode];
9221 }
9222 }
9223 return validKey;
9224 }
9225
9226 function normalizedKeyForEvent(keyEvent) {
9227 // fall back from .key, to .keyIdentifier, to .keyCode, and then to
9228 // .detail.key to support artificial keyboard events
9229 return transformKey(keyEvent.key) ||
9230 transformKeyIdentifier(keyEvent.keyIdentifier) ||
9231 transformKeyCode(keyEvent.keyCode) ||
9232 transformKey(keyEvent.detail.key) || '';
9233 }
9234
9235 function keyComboMatchesEvent(keyCombo, keyEvent) {
9236 return normalizedKeyForEvent(keyEvent) === keyCombo.key &&
9237 !!keyEvent.shiftKey === !!keyCombo.shiftKey &&
9238 !!keyEvent.ctrlKey === !!keyCombo.ctrlKey &&
9239 !!keyEvent.altKey === !!keyCombo.altKey &&
9240 !!keyEvent.metaKey === !!keyCombo.metaKey;
9241 }
9242
9243 function parseKeyComboString(keyComboString) {
9244 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP art) {
9245 var eventParts = keyComboPart.split(':');
9246 var keyName = eventParts[0];
9247 var event = eventParts[1];
9248
9249 if (keyName in MODIFIER_KEYS) {
9250 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
9251 } else {
9252 parsedKeyCombo.key = keyName;
9253 parsedKeyCombo.event = event || 'keydown';
9254 }
9255
9256 return parsedKeyCombo;
9257 }, {
9258 combo: keyComboString.split(':').shift()
9259 });
9260 }
9261
9262 function parseEventString(eventString) {
9263 return eventString.split(' ').map(function(keyComboString) {
9264 return parseKeyComboString(keyComboString);
9265 });
9266 }
9267
9268
9269 /**
9270 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
9271 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
9272 * The element takes care of browser differences with respect to Keyboard ev ents
9273 * and uses an expressive syntax to filter key presses.
9274 *
9275 * Use the `keyBindings` prototype property to express what combination of k eys
9276 * will trigger the event to fire.
9277 *
9278 * Use the `key-event-target` attribute to set up event handlers on a specif ic
9279 * node.
9280 * The `keys-pressed` event will fire when one of the key combinations set w ith the
9281 * `keys` property is pressed.
9282 *
9283 * @demo demo/index.html
9284 * @polymerBehavior
9285 */
9286 Polymer.IronA11yKeysBehavior = {
9287 properties: {
9288 /**
9289 * The HTMLElement that will be firing relevant KeyboardEvents.
9290 */
9291 keyEventTarget: {
9292 type: Object,
9293 value: function() {
9294 return this;
9295 }
9296 },
9297
9298 _boundKeyHandlers: {
9299 type: Array,
9300 value: function() {
9301 return [];
9302 }
9303 },
9304
9305 // We use this due to a limitation in IE10 where instances will have
9306 // own properties of everything on the "prototype".
9307 _imperativeKeyBindings: {
9308 type: Object,
9309 value: function() {
9310 return {};
9311 }
9312 }
9313 },
9314
9315 observers: [
9316 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
9317 ],
9318
9319 keyBindings: {},
9320
9321 registered: function() {
9322 this._prepKeyBindings();
9323 },
9324
9325 attached: function() {
9326 this._listenKeyEventListeners();
9327 },
9328
9329 detached: function() {
9330 this._unlistenKeyEventListeners();
9331 },
9332
9333 /**
9334 * Can be used to imperatively add a key binding to the implementing
9335 * element. This is the imperative equivalent of declaring a keybinding
9336 * in the `keyBindings` prototype property.
9337 */
9338 addOwnKeyBinding: function(eventString, handlerName) {
9339 this._imperativeKeyBindings[eventString] = handlerName;
9340 this._prepKeyBindings();
9341 this._resetKeyEventListeners();
9342 },
9343
9344 /**
9345 * When called, will remove all imperatively-added key bindings.
9346 */
9347 removeOwnKeyBindings: function() {
9348 this._imperativeKeyBindings = {};
9349 this._prepKeyBindings();
9350 this._resetKeyEventListeners();
9351 },
9352
9353 keyboardEventMatchesKeys: function(event, eventString) {
9354 var keyCombos = parseEventString(eventString);
9355 var index;
9356
9357 for (index = 0; index < keyCombos.length; ++index) {
9358 if (keyComboMatchesEvent(keyCombos[index], event)) {
9359 return true;
9360 }
9361 }
9362
9363 return false;
9364 },
9365
9366 _collectKeyBindings: function() {
9367 var keyBindings = this.behaviors.map(function(behavior) {
9368 return behavior.keyBindings;
9369 });
9370
9371 if (keyBindings.indexOf(this.keyBindings) === -1) {
9372 keyBindings.push(this.keyBindings);
9373 }
9374
9375 return keyBindings;
9376 },
9377
9378 _prepKeyBindings: function() {
9379 this._keyBindings = {};
9380
9381 this._collectKeyBindings().forEach(function(keyBindings) {
9382 for (var eventString in keyBindings) {
9383 this._addKeyBinding(eventString, keyBindings[eventString]);
9384 }
9385 }, this);
9386
9387 for (var eventString in this._imperativeKeyBindings) {
9388 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
9389 }
9390 },
9391
9392 _addKeyBinding: function(eventString, handlerName) {
9393 parseEventString(eventString).forEach(function(keyCombo) {
9394 this._keyBindings[keyCombo.event] =
9395 this._keyBindings[keyCombo.event] || [];
9396
9397 this._keyBindings[keyCombo.event].push([
9398 keyCombo,
9399 handlerName
9400 ]);
9401 }, this);
9402 },
9403
9404 _resetKeyEventListeners: function() {
9405 this._unlistenKeyEventListeners();
9406
9407 if (this.isAttached) {
9408 this._listenKeyEventListeners();
9409 }
9410 },
9411
9412 _listenKeyEventListeners: function() {
9413 Object.keys(this._keyBindings).forEach(function(eventName) {
9414 var keyBindings = this._keyBindings[eventName];
9415 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
9416
9417 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH andler]);
9418
9419 this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
9420 }, this);
9421 },
9422
9423 _unlistenKeyEventListeners: function() {
9424 var keyHandlerTuple;
9425 var keyEventTarget;
9426 var eventName;
9427 var boundKeyHandler;
9428
9429 while (this._boundKeyHandlers.length) {
9430 // My kingdom for block-scope binding and destructuring assignment..
9431 keyHandlerTuple = this._boundKeyHandlers.pop();
9432 keyEventTarget = keyHandlerTuple[0];
9433 eventName = keyHandlerTuple[1];
9434 boundKeyHandler = keyHandlerTuple[2];
9435
9436 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
9437 }
9438 },
9439
9440 _onKeyBindingEvent: function(keyBindings, event) {
9441 keyBindings.forEach(function(keyBinding) {
9442 var keyCombo = keyBinding[0];
9443 var handlerName = keyBinding[1];
9444
9445 if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event)) {
9446 this._triggerKeyHandler(keyCombo, handlerName, event);
9447 }
9448 }, this);
9449 },
9450
9451 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
9452 var detail = Object.create(keyCombo);
9453 detail.keyboardEvent = keyboardEvent;
9454
9455 this[handlerName].call(this, new CustomEvent(keyCombo.event, {
9456 detail: detail
9457 }));
9458 }
9459 };
9460 })();
9461 (function() {
9462 var Utility = {
9463 distance: function(x1, y1, x2, y2) {
9464 var xDelta = (x1 - x2);
9465 var yDelta = (y1 - y2);
9466
9467 return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
9468 },
9469
9470 now: window.performance && window.performance.now ?
9471 window.performance.now.bind(window.performance) : Date.now
9472 };
9473
9474 /**
9475 * @param {HTMLElement} element
9476 * @constructor
9477 */
9478 function ElementMetrics(element) {
9479 this.element = element;
9480 this.width = this.boundingRect.width;
9481 this.height = this.boundingRect.height;
9482
9483 this.size = Math.max(this.width, this.height);
9484 }
9485
9486 ElementMetrics.prototype = {
9487 get boundingRect () {
9488 return this.element.getBoundingClientRect();
9489 },
9490
9491 furthestCornerDistanceFrom: function(x, y) {
9492 var topLeft = Utility.distance(x, y, 0, 0);
9493 var topRight = Utility.distance(x, y, this.width, 0);
9494 var bottomLeft = Utility.distance(x, y, 0, this.height);
9495 var bottomRight = Utility.distance(x, y, this.width, this.height);
9496
9497 return Math.max(topLeft, topRight, bottomLeft, bottomRight);
9498 }
9499 };
9500
9501 /**
9502 * @param {HTMLElement} element
9503 * @constructor
9504 */
9505 function Ripple(element) {
9506 this.element = element;
9507 this.color = window.getComputedStyle(element).color;
9508
9509 this.wave = document.createElement('div');
9510 this.waveContainer = document.createElement('div');
9511 this.wave.style.backgroundColor = this.color;
9512 this.wave.classList.add('wave');
9513 this.waveContainer.classList.add('wave-container');
9514 Polymer.dom(this.waveContainer).appendChild(this.wave);
9515
9516 this.resetInteractionState();
9517 }
9518
9519 Ripple.MAX_RADIUS = 300;
9520
9521 Ripple.prototype = {
9522 get recenters() {
9523 return this.element.recenters;
9524 },
9525
9526 get center() {
9527 return this.element.center;
9528 },
9529
9530 get mouseDownElapsed() {
9531 var elapsed;
9532
9533 if (!this.mouseDownStart) {
9534 return 0;
9535 }
9536
9537 elapsed = Utility.now() - this.mouseDownStart;
9538
9539 if (this.mouseUpStart) {
9540 elapsed -= this.mouseUpElapsed;
9541 }
9542
9543 return elapsed;
9544 },
9545
9546 get mouseUpElapsed() {
9547 return this.mouseUpStart ?
9548 Utility.now () - this.mouseUpStart : 0;
9549 },
9550
9551 get mouseDownElapsedSeconds() {
9552 return this.mouseDownElapsed / 1000;
9553 },
9554
9555 get mouseUpElapsedSeconds() {
9556 return this.mouseUpElapsed / 1000;
9557 },
9558
9559 get mouseInteractionSeconds() {
9560 return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
9561 },
9562
9563 get initialOpacity() {
9564 return this.element.initialOpacity;
9565 },
9566
9567 get opacityDecayVelocity() {
9568 return this.element.opacityDecayVelocity;
9569 },
9570
9571 get radius() {
9572 var width2 = this.containerMetrics.width * this.containerMetrics.width;
9573 var height2 = this.containerMetrics.height * this.containerMetrics.heigh t;
9574 var waveRadius = Math.min(
9575 Math.sqrt(width2 + height2),
9576 Ripple.MAX_RADIUS
9577 ) * 1.1 + 5;
9578
9579 var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
9580 var timeNow = this.mouseInteractionSeconds / duration;
9581 var size = waveRadius * (1 - Math.pow(80, -timeNow));
9582
9583 return Math.abs(size);
9584 },
9585
9586 get opacity() {
9587 if (!this.mouseUpStart) {
9588 return this.initialOpacity;
9589 }
9590
9591 return Math.max(
9592 0,
9593 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe locity
9594 );
9595 },
9596
9597 get outerOpacity() {
9598 // Linear increase in background opacity, capped at the opacity
9599 // of the wavefront (waveOpacity).
9600 var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
9601 var waveOpacity = this.opacity;
9602
9603 return Math.max(
9604 0,
9605 Math.min(outerOpacity, waveOpacity)
9606 );
9607 },
9608
9609 get isOpacityFullyDecayed() {
9610 return this.opacity < 0.01 &&
9611 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
9612 },
9613
9614 get isRestingAtMaxRadius() {
9615 return this.opacity >= this.initialOpacity &&
9616 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
9617 },
9618
9619 get isAnimationComplete() {
9620 return this.mouseUpStart ?
9621 this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
9622 },
9623
9624 get translationFraction() {
9625 return Math.min(
9626 1,
9627 this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
9628 );
9629 },
9630
9631 get xNow() {
9632 if (this.xEnd) {
9633 return this.xStart + this.translationFraction * (this.xEnd - this.xSta rt);
9634 }
9635
9636 return this.xStart;
9637 },
9638
9639 get yNow() {
9640 if (this.yEnd) {
9641 return this.yStart + this.translationFraction * (this.yEnd - this.ySta rt);
9642 }
9643
9644 return this.yStart;
9645 },
9646
9647 get isMouseDown() {
9648 return this.mouseDownStart && !this.mouseUpStart;
9649 },
9650
9651 resetInteractionState: function() {
9652 this.maxRadius = 0;
9653 this.mouseDownStart = 0;
9654 this.mouseUpStart = 0;
9655
9656 this.xStart = 0;
9657 this.yStart = 0;
9658 this.xEnd = 0;
9659 this.yEnd = 0;
9660 this.slideDistance = 0;
9661
9662 this.containerMetrics = new ElementMetrics(this.element);
9663 },
9664
9665 draw: function() {
9666 var scale;
9667 var translateString;
9668 var dx;
9669 var dy;
9670
9671 this.wave.style.opacity = this.opacity;
9672
9673 scale = this.radius / (this.containerMetrics.size / 2);
9674 dx = this.xNow - (this.containerMetrics.width / 2);
9675 dy = this.yNow - (this.containerMetrics.height / 2);
9676
9677
9678 // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
9679 // https://bugs.webkit.org/show_bug.cgi?id=98538
9680 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
9681 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
9682 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
9683 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
9684 },
9685
9686 /** @param {Event=} event */
9687 downAction: function(event) {
9688 var xCenter = this.containerMetrics.width / 2;
9689 var yCenter = this.containerMetrics.height / 2;
9690
9691 this.resetInteractionState();
9692 this.mouseDownStart = Utility.now();
9693
9694 if (this.center) {
9695 this.xStart = xCenter;
9696 this.yStart = yCenter;
9697 this.slideDistance = Utility.distance(
9698 this.xStart, this.yStart, this.xEnd, this.yEnd
9699 );
9700 } else {
9701 this.xStart = event ?
9702 event.detail.x - this.containerMetrics.boundingRect.left :
9703 this.containerMetrics.width / 2;
9704 this.yStart = event ?
9705 event.detail.y - this.containerMetrics.boundingRect.top :
9706 this.containerMetrics.height / 2;
9707 }
9708
9709 if (this.recenters) {
9710 this.xEnd = xCenter;
9711 this.yEnd = yCenter;
9712 this.slideDistance = Utility.distance(
9713 this.xStart, this.yStart, this.xEnd, this.yEnd
9714 );
9715 }
9716
9717 this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
9718 this.xStart,
9719 this.yStart
9720 );
9721
9722 this.waveContainer.style.top =
9723 (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px' ;
9724 this.waveContainer.style.left =
9725 (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
9726
9727 this.waveContainer.style.width = this.containerMetrics.size + 'px';
9728 this.waveContainer.style.height = this.containerMetrics.size + 'px';
9729 },
9730
9731 /** @param {Event=} event */
9732 upAction: function(event) {
9733 if (!this.isMouseDown) {
9734 return;
9735 }
9736
9737 this.mouseUpStart = Utility.now();
9738 },
9739
9740 remove: function() {
9741 Polymer.dom(this.waveContainer.parentNode).removeChild(
9742 this.waveContainer
9743 );
9744 }
9745 };
9746
9747 Polymer({
9748 is: 'paper-ripple',
9749
9750 behaviors: [
9751 Polymer.IronA11yKeysBehavior
9752 ],
9753
9754 properties: {
9755 /**
9756 * The initial opacity set on the wave.
9757 *
9758 * @attribute initialOpacity
9759 * @type number
9760 * @default 0.25
9761 */
9762 initialOpacity: {
9763 type: Number,
9764 value: 0.25
9765 },
9766
9767 /**
9768 * How fast (opacity per second) the wave fades out.
9769 *
9770 * @attribute opacityDecayVelocity
9771 * @type number
9772 * @default 0.8
9773 */
9774 opacityDecayVelocity: {
9775 type: Number,
9776 value: 0.8
9777 },
9778
9779 /**
9780 * If true, ripples will exhibit a gravitational pull towards
9781 * the center of their container as they fade away.
9782 *
9783 * @attribute recenters
9784 * @type boolean
9785 * @default false
9786 */
9787 recenters: {
9788 type: Boolean,
9789 value: false
9790 },
9791
9792 /**
9793 * If true, ripples will center inside its container
9794 *
9795 * @attribute recenters
9796 * @type boolean
9797 * @default false
9798 */
9799 center: {
9800 type: Boolean,
9801 value: false
9802 },
9803
9804 /**
9805 * A list of the visual ripples.
9806 *
9807 * @attribute ripples
9808 * @type Array
9809 * @default []
9810 */
9811 ripples: {
9812 type: Array,
9813 value: function() {
9814 return [];
9815 }
9816 },
9817
9818 /**
9819 * True when there are visible ripples animating within the
9820 * element.
9821 */
9822 animating: {
9823 type: Boolean,
9824 readOnly: true,
9825 reflectToAttribute: true,
9826 value: false
9827 },
9828
9829 /**
9830 * If true, the ripple will remain in the "down" state until `holdDown`
9831 * is set to false again.
9832 */
9833 holdDown: {
9834 type: Boolean,
9835 value: false,
9836 observer: '_holdDownChanged'
9837 },
9838
9839 _animating: {
9840 type: Boolean
9841 },
9842
9843 _boundAnimate: {
9844 type: Function,
9845 value: function() {
9846 return this.animate.bind(this);
9847 }
9848 }
9849 },
9850
9851 get target () {
9852 var ownerRoot = Polymer.dom(this).getOwnerRoot();
9853 var target;
9854
9855 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
9856 target = ownerRoot.host;
9857 } else {
9858 target = this.parentNode;
9859 }
9860
9861 return target;
9862 },
9863
9864 keyBindings: {
9865 'enter:keydown': '_onEnterKeydown',
9866 'space:keydown': '_onSpaceKeydown',
9867 'space:keyup': '_onSpaceKeyup'
9868 },
9869
9870 attached: function() {
9871 this.listen(this.target, 'up', 'upAction');
9872 this.listen(this.target, 'down', 'downAction');
9873
9874 if (!this.target.hasAttribute('noink')) {
9875 this.keyEventTarget = this.target;
9876 }
9877 },
9878
9879 get shouldKeepAnimating () {
9880 for (var index = 0; index < this.ripples.length; ++index) {
9881 if (!this.ripples[index].isAnimationComplete) {
9882 return true;
9883 }
9884 }
9885
9886 return false;
9887 },
9888
9889 simulatedRipple: function() {
9890 this.downAction(null);
9891
9892 // Please see polymer/polymer#1305
9893 this.async(function() {
9894 this.upAction();
9895 }, 1);
9896 },
9897
9898 /** @param {Event=} event */
9899 downAction: function(event) {
9900 if (this.holdDown && this.ripples.length > 0) {
9901 return;
9902 }
9903
9904 var ripple = this.addRipple();
9905
9906 ripple.downAction(event);
9907
9908 if (!this._animating) {
9909 this.animate();
9910 }
9911 },
9912
9913 /** @param {Event=} event */
9914 upAction: function(event) {
9915 if (this.holdDown) {
9916 return;
9917 }
9918
9919 this.ripples.forEach(function(ripple) {
9920 ripple.upAction(event);
9921 });
9922
9923 this.animate();
9924 },
9925
9926 onAnimationComplete: function() {
9927 this._animating = false;
9928 this.$.background.style.backgroundColor = null;
9929 this.fire('transitionend');
9930 },
9931
9932 addRipple: function() {
9933 var ripple = new Ripple(this);
9934
9935 Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
9936 this.$.background.style.backgroundColor = ripple.color;
9937 this.ripples.push(ripple);
9938
9939 this._setAnimating(true);
9940
9941 return ripple;
9942 },
9943
9944 removeRipple: function(ripple) {
9945 var rippleIndex = this.ripples.indexOf(ripple);
9946
9947 if (rippleIndex < 0) {
9948 return;
9949 }
9950
9951 this.ripples.splice(rippleIndex, 1);
9952
9953 ripple.remove();
9954
9955 if (!this.ripples.length) {
9956 this._setAnimating(false);
9957 }
9958 },
9959
9960 animate: function() {
9961 var index;
9962 var ripple;
9963
9964 this._animating = true;
9965
9966 for (index = 0; index < this.ripples.length; ++index) {
9967 ripple = this.ripples[index];
9968
9969 ripple.draw();
9970
9971 this.$.background.style.opacity = ripple.outerOpacity;
9972
9973 if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
9974 this.removeRipple(ripple);
9975 }
9976 }
9977
9978 if (!this.shouldKeepAnimating && this.ripples.length === 0) {
9979 this.onAnimationComplete();
9980 } else {
9981 window.requestAnimationFrame(this._boundAnimate);
9982 }
9983 },
9984
9985 _onEnterKeydown: function() {
9986 this.downAction();
9987 this.async(this.upAction, 1);
9988 },
9989
9990 _onSpaceKeydown: function() {
9991 this.downAction();
9992 },
9993
9994 _onSpaceKeyup: function() {
9995 this.upAction();
9996 },
9997
9998 _holdDownChanged: function(holdDown) {
9999 if (holdDown) {
10000 this.downAction();
10001 } else {
10002 this.upAction();
10003 }
10004 }
10005 });
10006 })();
10007 /**
10008 * @demo demo/index.html
10009 * @polymerBehavior
10010 */
10011 Polymer.IronControlState = {
10012
10013 properties: {
10014
10015 /**
10016 * If true, the element currently has focus.
10017 */
10018 focused: {
10019 type: Boolean,
10020 value: false,
10021 notify: true,
10022 readOnly: true,
10023 reflectToAttribute: true
10024 },
10025
10026 /**
10027 * If true, the user cannot interact with this element.
10028 */
10029 disabled: {
10030 type: Boolean,
10031 value: false,
10032 notify: true,
10033 observer: '_disabledChanged',
10034 reflectToAttribute: true
10035 },
10036
10037 _oldTabIndex: {
10038 type: Number
10039 },
10040
10041 _boundFocusBlurHandler: {
10042 type: Function,
10043 value: function() {
10044 return this._focusBlurHandler.bind(this);
10045 }
10046 }
10047
10048 },
10049
10050 observers: [
10051 '_changedControlState(focused, disabled)'
10052 ],
10053
10054 ready: function() {
10055 this.addEventListener('focus', this._boundFocusBlurHandler, true);
10056 this.addEventListener('blur', this._boundFocusBlurHandler, true);
10057 },
10058
10059 _focusBlurHandler: function(event) {
10060 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will
10061 // eventually become `this` due to retargeting; if we are not in
10062 // ShadowDOM land, `event.target` will eventually become `this` due
10063 // to the second conditional which fires a synthetic event (that is also
10064 // handled). In either case, we can disregard `event.path`.
10065
10066 if (event.target === this) {
10067 var focused = event.type === 'focus';
10068 this._setFocused(focused);
10069 } else if (!this.shadowRoot) {
10070 this.fire(event.type, {sourceEvent: event}, {
10071 node: this,
10072 bubbles: event.bubbles,
10073 cancelable: event.cancelable
10074 });
10075 }
10076 },
10077
10078 _disabledChanged: function(disabled, old) {
10079 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
10080 this.style.pointerEvents = disabled ? 'none' : '';
10081 if (disabled) {
10082 this._oldTabIndex = this.tabIndex;
10083 this.focused = false;
10084 this.tabIndex = -1;
10085 } else if (this._oldTabIndex !== undefined) {
10086 this.tabIndex = this._oldTabIndex;
10087 }
10088 },
10089
10090 _changedControlState: function() {
10091 // _controlStateChanged is abstract, follow-on behaviors may implement it
10092 if (this._controlStateChanged) {
10093 this._controlStateChanged();
10094 }
10095 }
10096
10097 };
10098 /**
10099 * @demo demo/index.html
10100 * @polymerBehavior Polymer.IronButtonState
10101 */
10102 Polymer.IronButtonStateImpl = {
10103
10104 properties: {
10105
10106 /**
10107 * If true, the user is currently holding down the button.
10108 */
10109 pressed: {
10110 type: Boolean,
10111 readOnly: true,
10112 value: false,
10113 reflectToAttribute: true,
10114 observer: '_pressedChanged'
10115 },
10116
10117 /**
10118 * If true, the button toggles the active state with each tap or press
10119 * of the spacebar.
10120 */
10121 toggles: {
10122 type: Boolean,
10123 value: false,
10124 reflectToAttribute: true
10125 },
10126
10127 /**
10128 * If true, the button is a toggle and is currently in the active state.
10129 */
10130 active: {
10131 type: Boolean,
10132 value: false,
10133 notify: true,
10134 reflectToAttribute: true
10135 },
10136
10137 /**
10138 * True if the element is currently being pressed by a "pointer," which
10139 * is loosely defined as mouse or touch input (but specifically excluding
10140 * keyboard input).
10141 */
10142 pointerDown: {
10143 type: Boolean,
10144 readOnly: true,
10145 value: false
10146 },
10147
10148 /**
10149 * True if the input device that caused the element to receive focus
10150 * was a keyboard.
10151 */
10152 receivedFocusFromKeyboard: {
10153 type: Boolean,
10154 readOnly: true
10155 },
10156
10157 /**
10158 * The aria attribute to be set if the button is a toggle and in the
10159 * active state.
10160 */
10161 ariaActiveAttribute: {
10162 type: String,
10163 value: 'aria-pressed',
10164 observer: '_ariaActiveAttributeChanged'
10165 }
10166 },
10167
10168 listeners: {
10169 down: '_downHandler',
10170 up: '_upHandler',
10171 tap: '_tapHandler'
10172 },
10173
10174 observers: [
10175 '_detectKeyboardFocus(focused)',
10176 '_activeChanged(active, ariaActiveAttribute)'
10177 ],
10178
10179 keyBindings: {
10180 'enter:keydown': '_asyncClick',
10181 'space:keydown': '_spaceKeyDownHandler',
10182 'space:keyup': '_spaceKeyUpHandler',
10183 },
10184
10185 _mouseEventRe: /^mouse/,
10186
10187 _tapHandler: function() {
10188 if (this.toggles) {
10189 // a tap is needed to toggle the active state
10190 this._userActivate(!this.active);
10191 } else {
10192 this.active = false;
10193 }
10194 },
10195
10196 _detectKeyboardFocus: function(focused) {
10197 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
10198 },
10199
10200 // to emulate native checkbox, (de-)activations from a user interaction fire
10201 // 'change' events
10202 _userActivate: function(active) {
10203 if (this.active !== active) {
10204 this.active = active;
10205 this.fire('change');
10206 }
10207 },
10208
10209 _eventSourceIsPrimaryInput: function(event) {
10210 event = event.detail.sourceEvent || event;
10211
10212 // Always true for non-mouse events....
10213 if (!this._mouseEventRe.test(event.type)) {
10214 return true;
10215 }
10216
10217 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
10218 if ('buttons' in event) {
10219 return event.buttons === 1;
10220 }
10221
10222 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
10223 if (typeof event.which === 'number') {
10224 return event.which < 2;
10225 }
10226
10227 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
10228 return event.button < 1;
10229 },
10230
10231 _downHandler: function(event) {
10232 if (!this._eventSourceIsPrimaryInput(event)) {
10233 return;
10234 }
10235
10236 this._setPointerDown(true);
10237 this._setPressed(true);
10238 this._setReceivedFocusFromKeyboard(false);
10239 },
10240
10241 _upHandler: function() {
10242 this._setPointerDown(false);
10243 this._setPressed(false);
10244 },
10245
10246 _spaceKeyDownHandler: function(event) {
10247 var keyboardEvent = event.detail.keyboardEvent;
10248 keyboardEvent.preventDefault();
10249 keyboardEvent.stopImmediatePropagation();
10250 this._setPressed(true);
10251 },
10252
10253 _spaceKeyUpHandler: function() {
10254 if (this.pressed) {
10255 this._asyncClick();
10256 }
10257 this._setPressed(false);
10258 },
10259
10260 // trigger click asynchronously, the asynchrony is useful to allow one
10261 // event handler to unwind before triggering another event
10262 _asyncClick: function() {
10263 this.async(function() {
10264 this.click();
10265 }, 1);
10266 },
10267
10268 // any of these changes are considered a change to button state
10269
10270 _pressedChanged: function(pressed) {
10271 this._changedButtonState();
10272 },
10273
10274 _ariaActiveAttributeChanged: function(value, oldValue) {
10275 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
10276 this.removeAttribute(oldValue);
10277 }
10278 },
10279
10280 _activeChanged: function(active, ariaActiveAttribute) {
10281 if (this.toggles) {
10282 this.setAttribute(this.ariaActiveAttribute,
10283 active ? 'true' : 'false');
10284 } else {
10285 this.removeAttribute(this.ariaActiveAttribute);
10286 }
10287 this._changedButtonState();
10288 },
10289
10290 _controlStateChanged: function() {
10291 if (this.disabled) {
10292 this._setPressed(false);
10293 } else {
10294 this._changedButtonState();
10295 }
10296 },
10297
10298 // provide hook for follow-on behaviors to react to button-state
10299
10300 _changedButtonState: function() {
10301 if (this._buttonStateChanged) {
10302 this._buttonStateChanged(); // abstract
10303 }
10304 }
10305
10306 };
10307
10308 /** @polymerBehavior */
10309 Polymer.IronButtonState = [
10310 Polymer.IronA11yKeysBehavior,
10311 Polymer.IronButtonStateImpl
10312 ];
10313 /** @polymerBehavior */
10314 Polymer.PaperButtonBehaviorImpl = {
10315
10316 properties: {
10317
10318 _elevation: {
10319 type: Number
10320 }
10321
10322 },
10323
10324 observers: [
10325 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)'
10326 ],
10327
10328 hostAttributes: {
10329 role: 'button',
10330 tabindex: '0'
10331 },
10332
10333 _calculateElevation: function() {
10334 var e = 1;
10335 if (this.disabled) {
10336 e = 0;
10337 } else if (this.active || this.pressed) {
10338 e = 4;
10339 } else if (this.receivedFocusFromKeyboard) {
10340 e = 3;
10341 }
10342 this._elevation = e;
10343 }
10344 };
10345
10346 /** @polymerBehavior */
10347 Polymer.PaperButtonBehavior = [
10348 Polymer.IronButtonState,
10349 Polymer.IronControlState,
10350 Polymer.PaperButtonBehaviorImpl
10351 ];
10352 Polymer({
10353 is: 'paper-button',
10354
10355 behaviors: [
10356 Polymer.PaperButtonBehavior
10357 ],
10358
10359 properties: {
10360 /**
10361 * If true, the button should be styled with a shadow.
10362 */
10363 raised: {
10364 type: Boolean,
10365 reflectToAttribute: true,
10366 value: false,
10367 observer: '_calculateElevation'
10368 }
10369 },
10370
10371 _calculateElevation: function() {
10372 if (!this.raised) {
10373 this._elevation = 0;
10374 } else {
10375 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
10376 }
10377 },
10378
10379 _computeContentClass: function(receivedFocusFromKeyboard) {
10380 var className = 'content ';
10381 if (receivedFocusFromKeyboard) {
10382 className += ' keyboard-focus';
10383 }
10384 return className;
10385 }
10386 });
10387 /**
10388 * `iron-range-behavior` provides the behavior for something with a minimum to m aximum range.
10389 *
10390 * @demo demo/index.html
10391 * @polymerBehavior
10392 */
10393 Polymer.IronRangeBehavior = {
10394
10395 properties: {
10396
10397 /**
10398 * The number that represents the current value.
10399 */
10400 value: {
10401 type: Number,
10402 value: 0,
10403 notify: true,
10404 reflectToAttribute: true
10405 },
10406
10407 /**
10408 * The number that indicates the minimum value of the range.
10409 */
10410 min: {
10411 type: Number,
10412 value: 0,
10413 notify: true
10414 },
10415
10416 /**
10417 * The number that indicates the maximum value of the range.
10418 */
10419 max: {
10420 type: Number,
10421 value: 100,
10422 notify: true
10423 },
10424
10425 /**
10426 * Specifies the value granularity of the range's value.
10427 */
10428 step: {
10429 type: Number,
10430 value: 1,
10431 notify: true
10432 },
10433
10434 /**
10435 * Returns the ratio of the value.
10436 */
10437 ratio: {
10438 type: Number,
10439 value: 0,
10440 readOnly: true,
10441 notify: true
10442 },
10443 },
10444
10445 observers: [
10446 '_update(value, min, max, step)'
10447 ],
10448
10449 _calcRatio: function(value) {
10450 return (this._clampValue(value) - this.min) / (this.max - this.min);
10451 },
10452
10453 _clampValue: function(value) {
10454 return Math.min(this.max, Math.max(this.min, this._calcStep(value)));
10455 },
10456
10457 _calcStep: function(value) {
10458 /**
10459 * if we calculate the step using
10460 * `Math.round(value / step) * step` we may hit a precision point issue
10461 * eg. 0.1 * 0.2 = 0.020000000000000004
10462 * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
10463 *
10464 * as a work around we can divide by the reciprocal of `step`
10465 */
10466 // polymer/issues/2493
10467 value = parseFloat(value);
10468 return this.step ? (Math.round((value + this.min) / this.step) / (1 / this.s tep)) - this.min : value;
10469 },
10470
10471 _validateValue: function() {
10472 var v = this._clampValue(this.value);
10473 this.value = this.oldValue = isNaN(v) ? this.oldValue : v;
10474 return this.value !== v;
10475 },
10476
10477 _update: function() {
10478 this._validateValue();
10479 this._setRatio(this._calcRatio(this.value) * 100);
10480 }
10481
10482 };
10483 Polymer({
10484
10485 is: 'paper-progress',
10486
10487 behaviors: [
10488 Polymer.IronRangeBehavior
10489 ],
10490
10491 properties: {
10492
10493 /**
10494 * The number that represents the current secondary progress.
10495 */
10496 secondaryProgress: {
10497 type: Number,
10498 value: 0
10499 },
10500
10501 /**
10502 * The secondary ratio
10503 */
10504 secondaryRatio: {
10505 type: Number,
10506 value: 0,
10507 readOnly: true
10508 },
10509
10510 /**
10511 * Use an indeterminate progress indicator.
10512 */
10513 indeterminate: {
10514 type: Boolean,
10515 value: false,
10516 observer: '_toggleIndeterminate'
10517 },
10518
10519 /**
10520 * True if the progress is disabled.
10521 */
10522 disabled: {
10523 type: Boolean,
10524 value: false,
10525 reflectToAttribute: true,
10526 observer: '_disabledChanged'
10527 }
10528 },
10529
10530 observers: [
10531 '_progressChanged(secondaryProgress, value, min, max)'
10532 ],
10533
10534 hostAttributes: {
10535 role: 'progressbar'
10536 },
10537
10538 _toggleIndeterminate: function(indeterminate) {
10539 // If we use attribute/class binding, the animation sometimes doesn't tran slate properly
10540 // on Safari 7.1. So instead, we toggle the class here in the update metho d.
10541 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress);
10542 },
10543
10544 _transformProgress: function(progress, ratio) {
10545 var transform = 'scaleX(' + (ratio / 100) + ')';
10546 progress.style.transform = progress.style.webkitTransform = transform;
10547 },
10548
10549 _mainRatioChanged: function(ratio) {
10550 this._transformProgress(this.$.primaryProgress, ratio);
10551 },
10552
10553 _progressChanged: function(secondaryProgress, value, min, max) {
10554 secondaryProgress = this._clampValue(secondaryProgress);
10555 value = this._clampValue(value);
10556
10557 var secondaryRatio = this._calcRatio(secondaryProgress) * 100;
10558 var mainRatio = this._calcRatio(value) * 100;
10559
10560 this._setSecondaryRatio(secondaryRatio);
10561 this._transformProgress(this.$.secondaryProgress, secondaryRatio);
10562 this._transformProgress(this.$.primaryProgress, mainRatio);
10563
10564 this.secondaryProgress = secondaryProgress;
10565
10566 this.setAttribute('aria-valuenow', value);
10567 this.setAttribute('aria-valuemin', min);
10568 this.setAttribute('aria-valuemax', max);
10569 },
10570
10571 _disabledChanged: function(disabled) {
10572 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
10573 },
10574
10575 _hideSecondaryProgress: function(secondaryRatio) {
10576 return secondaryRatio === 0;
10577 }
10578
10579 });
10580 // Copyright 2015 The Chromium Authors. All rights reserved.
10581 // Use of this source code is governed by a BSD-style license that can be
10582 // found in the LICENSE file.
10583
10584 cr.define('downloads', function() {
10585 var Item = Polymer({
10586 is: 'downloads-item',
10587
10588 /**
10589 * @param {!downloads.ThrottledIconLoader} iconLoader
10590 * @param {!downloads.ActionService} actionService
10591 */
10592 factoryImpl: function(iconLoader, actionService) {
10593 /** @private {!downloads.ThrottledIconLoader} */
10594 this.iconLoader_ = iconLoader;
10595
10596 /** @private {!downloads.ActionService} */
10597 this.actionService_ = actionService;
10598 },
10599
10600 properties: {
10601 hideDate: {
10602 type: Boolean,
10603 value: true,
10604 },
10605
10606 readyPromise: {
10607 type: Object,
10608 value: function() {
10609 return new Promise(function(resolve, reject) {
10610 this.resolveReadyPromise_ = resolve;
10611 }.bind(this));
10612 },
10613 },
10614
10615 completelyOnDisk_: {
10616 computed: 'computeCompletelyOnDisk_(' +
10617 'data_.state, data_.file_externally_removed)',
10618 type: Boolean,
10619 value: true,
10620 },
10621
10622 controlledBy_: {
10623 computed: 'computeControlledBy_(data_.by_ext_id, data_.by_ext_name)',
10624 type: String,
10625 value: '',
10626 },
10627
10628 i18n_: {
10629 readOnly: true,
10630 type: Object,
10631 value: function() {
10632 return {
10633 cancel: loadTimeData.getString('controlCancel'),
10634 discard: loadTimeData.getString('dangerDiscard'),
10635 pause: loadTimeData.getString('controlPause'),
10636 remove: loadTimeData.getString('controlRemoveFromList'),
10637 resume: loadTimeData.getString('controlResume'),
10638 restore: loadTimeData.getString('dangerRestore'),
10639 retry: loadTimeData.getString('controlRetry'),
10640 save: loadTimeData.getString('dangerSave'),
10641 };
10642 },
10643 },
10644
10645 isActive_: {
10646 computed: 'computeIsActive_(' +
10647 'data_.state, data_.file_externally_removed)',
10648 type: Boolean,
10649 value: true,
10650 },
10651
10652 isDangerous_: {
10653 computed: 'computeIsDangerous_(data_.state)',
10654 type: Boolean,
10655 value: false,
10656 },
10657
10658 isInProgress_: {
10659 computed: 'computeIsInProgress_(data_.state)',
10660 type: Boolean,
10661 value: false,
10662 },
10663
10664 showCancel_: {
10665 computed: 'computeShowCancel_(data_.state)',
10666 type: Boolean,
10667 value: false,
10668 },
10669
10670 showProgress_: {
10671 computed: 'computeShowProgress_(showCancel_, data_.percent)',
10672 type: Boolean,
10673 value: false,
10674 },
10675
10676 isMalware_: {
10677 computed: 'computeIsMalware_(isDangerous_, data_.danger_type)',
10678 type: Boolean,
10679 value: false,
10680 },
10681
10682 data_: {
10683 type: Object,
10684 },
10685 },
10686
10687 observers: [
10688 // TODO(dbeam): this gets called way more when I observe data_.by_ext_id
10689 // and data_.by_ext_name directly. Why?
10690 'observeControlledBy_(controlledBy_)',
10691 ],
10692
10693 ready: function() {
10694 this.content = this.$.content;
10695 this.resolveReadyPromise_();
10696 },
10697
10698 /** @param {!downloads.Data} data */
10699 update: function(data) {
10700 this.data_ = data;
10701
10702 if (!this.isDangerous_) {
10703 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
10704 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
10705 }
10706 },
10707
10708 /** @private */
10709 computeClass_: function() {
10710 var classes = [];
10711
10712 if (this.isActive_)
10713 classes.push('is-active');
10714
10715 if (this.isDangerous_)
10716 classes.push('dangerous');
10717
10718 if (this.showProgress_)
10719 classes.push('show-progress');
10720
10721 return classes.join(' ');
10722 },
10723
10724 /** @private */
10725 computeCompletelyOnDisk_: function() {
10726 return this.data_.state == downloads.States.COMPLETE &&
10727 !this.data_.file_externally_removed;
10728 },
10729
10730 /** @private */
10731 computeControlledBy_: function() {
10732 if (!this.data_.by_ext_id || !this.data_.by_ext_name)
10733 return '';
10734
10735 var url = 'chrome://extensions#' + this.data_.by_ext_id;
10736 var name = this.data_.by_ext_name;
10737 return loadTimeData.getStringF('controlledByUrl', url, name);
10738 },
10739
10740 /** @private */
10741 computeDate_: function() {
10742 if (this.hideDate)
10743 return '';
10744 return assert(this.data_.since_string || this.data_.date_string);
10745 },
10746
10747 /** @private */
10748 computeDescription_: function() {
10749 var data = this.data_;
10750
10751 switch (data.state) {
10752 case downloads.States.DANGEROUS:
10753 var fileName = data.file_name;
10754 switch (data.danger_type) {
10755 case downloads.DangerType.DANGEROUS_FILE:
10756 return loadTimeData.getStringF('dangerFileDesc', fileName);
10757 case downloads.DangerType.DANGEROUS_URL:
10758 return loadTimeData.getString('dangerUrlDesc');
10759 case downloads.DangerType.DANGEROUS_CONTENT: // Fall through.
10760 case downloads.DangerType.DANGEROUS_HOST:
10761 return loadTimeData.getStringF('dangerContentDesc', fileName);
10762 case downloads.DangerType.UNCOMMON_CONTENT:
10763 return loadTimeData.getStringF('dangerUncommonDesc', fileName);
10764 case downloads.DangerType.POTENTIALLY_UNWANTED:
10765 return loadTimeData.getStringF('dangerSettingsDesc', fileName);
10766 }
10767 break;
10768
10769 case downloads.States.IN_PROGRESS:
10770 case downloads.States.PAUSED: // Fallthrough.
10771 return data.progress_status_text;
10772 }
10773
10774 return '';
10775 },
10776
10777 /** @private */
10778 computeIsActive_: function() {
10779 return this.data_.state != downloads.States.CANCELLED &&
10780 this.data_.state != downloads.States.INTERRUPTED &&
10781 !this.data_.file_externally_removed;
10782 },
10783
10784 /** @private */
10785 computeIsDangerous_: function() {
10786 return this.data_.state == downloads.States.DANGEROUS;
10787 },
10788
10789 /** @private */
10790 computeIsInProgress_: function() {
10791 return this.data_.state == downloads.States.IN_PROGRESS;
10792 },
10793
10794 /** @private */
10795 computeIsMalware_: function() {
10796 return this.isDangerous_ &&
10797 (this.data_.danger_type == downloads.DangerType.DANGEROUS_CONTENT ||
10798 this.data_.danger_type == downloads.DangerType.DANGEROUS_HOST ||
10799 this.data_.danger_type == downloads.DangerType.DANGEROUS_URL ||
10800 this.data_.danger_type == downloads.DangerType.POTENTIALLY_UNWANTED);
10801 },
10802
10803 /** @private */
10804 computeRemoveStyle_: function() {
10805 var canDelete = loadTimeData.getBoolean('allowDeletingHistory');
10806 var hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete;
10807 return hideRemove ? 'visibility: hidden' : '';
10808 },
10809
10810 /** @private */
10811 computeShowCancel_: function() {
10812 return this.data_.state == downloads.States.IN_PROGRESS ||
10813 this.data_.state == downloads.States.PAUSED;
10814 },
10815
10816 /** @private */
10817 computeShowProgress_: function() {
10818 return this.showCancel_ && this.data_.percent >= -1;
10819 },
10820
10821 /** @private */
10822 computeTag_: function() {
10823 switch (this.data_.state) {
10824 case downloads.States.CANCELLED:
10825 return loadTimeData.getString('statusCancelled');
10826
10827 case downloads.States.INTERRUPTED:
10828 return this.data_.last_reason_text;
10829
10830 case downloads.States.COMPLETE:
10831 return this.data_.file_externally_removed ?
10832 loadTimeData.getString('statusRemoved') : '';
10833 }
10834
10835 return '';
10836 },
10837
10838 /** @private */
10839 isIndeterminate_: function() {
10840 return this.data_.percent == -1;
10841 },
10842
10843 /** @private */
10844 observeControlledBy_: function() {
10845 this.$['controlled-by'].innerHTML = this.controlledBy_;
10846 },
10847
10848 /** @private */
10849 onCancelClick_: function() {
10850 this.actionService_.cancel(this.data_.id);
10851 },
10852
10853 /** @private */
10854 onDiscardDangerous_: function() {
10855 this.actionService_.discardDangerous(this.data_.id);
10856 },
10857
10858 /**
10859 * @private
10860 * @param {Event} e
10861 */
10862 onDragStart_: function(e) {
10863 e.preventDefault();
10864 this.actionService_.drag(this.data_.id);
10865 },
10866
10867 /**
10868 * @param {Event} e
10869 * @private
10870 */
10871 onFileLinkClick_: function(e) {
10872 e.preventDefault();
10873 this.actionService_.openFile(this.data_.id);
10874 },
10875
10876 /** @private */
10877 onPauseClick_: function() {
10878 this.actionService_.pause(this.data_.id);
10879 },
10880
10881 /** @private */
10882 onRemoveClick_: function() {
10883 this.actionService_.remove(this.data_.id);
10884 },
10885
10886 /** @private */
10887 onResumeClick_: function() {
10888 this.actionService_.resume(this.data_.id);
10889 },
10890
10891 /** @private */
10892 onRetryClick_: function() {
10893 this.actionService_.download(this.$['file-link'].href);
10894 },
10895
10896 /** @private */
10897 onSaveDangerous_: function() {
10898 this.actionService_.saveDangerous(this.data_.id);
10899 },
10900
10901 /** @private */
10902 onShowClick_: function() {
10903 this.actionService_.show(this.data_.id);
10904 },
10905 });
10906
10907 return {Item: Item};
10908 });
10909 (function() {
10910
10911 // monostate data
10912 var metaDatas = {};
10913 var metaArrays = {};
10914
10915 Polymer.IronMeta = Polymer({
10916
10917 is: 'iron-meta',
10918
10919 properties: {
10920
10921 /**
10922 * The type of meta-data. All meta-data of the same type is stored
10923 * together.
10924 */
10925 type: {
10926 type: String,
10927 value: 'default',
10928 observer: '_typeChanged'
10929 },
10930
10931 /**
10932 * The key used to store `value` under the `type` namespace.
10933 */
10934 key: {
10935 type: String,
10936 observer: '_keyChanged'
10937 },
10938
10939 /**
10940 * The meta-data to store or retrieve.
10941 */
10942 value: {
10943 type: Object,
10944 notify: true,
10945 observer: '_valueChanged'
10946 },
10947
10948 /**
10949 * If true, `value` is set to the iron-meta instance itself.
10950 */
10951 self: {
10952 type: Boolean,
10953 observer: '_selfChanged'
10954 },
10955
10956 /**
10957 * Array of all meta-data values for the given type.
10958 */
10959 list: {
10960 type: Array,
10961 notify: true
10962 }
10963
10964 },
10965
10966 /**
10967 * Only runs if someone invokes the factory/constructor directly
10968 * e.g. `new Polymer.IronMeta()`
10969 */
10970 factoryImpl: function(config) {
10971 if (config) {
10972 for (var n in config) {
10973 switch(n) {
10974 case 'type':
10975 case 'key':
10976 case 'value':
10977 this[n] = config[n];
10978 break;
10979 }
10980 }
10981 }
10982 },
10983
10984 created: function() {
10985 // TODO(sjmiles): good for debugging?
10986 this._metaDatas = metaDatas;
10987 this._metaArrays = metaArrays;
10988 },
10989
10990 _keyChanged: function(key, old) {
10991 this._resetRegistration(old);
10992 },
10993
10994 _valueChanged: function(value) {
10995 this._resetRegistration(this.key);
10996 },
10997
10998 _selfChanged: function(self) {
10999 if (self) {
11000 this.value = this;
11001 }
11002 },
11003
11004 _typeChanged: function(type) {
11005 this._unregisterKey(this.key);
11006 if (!metaDatas[type]) {
11007 metaDatas[type] = {};
11008 }
11009 this._metaData = metaDatas[type];
11010 if (!metaArrays[type]) {
11011 metaArrays[type] = [];
11012 }
11013 this.list = metaArrays[type];
11014 this._registerKeyValue(this.key, this.value);
11015 },
11016
11017 /**
11018 * Retrieves meta data value by key.
11019 *
11020 * @method byKey
11021 * @param {string} key The key of the meta-data to be returned.
11022 * @return {*}
11023 */
11024 byKey: function(key) {
11025 return this._metaData && this._metaData[key];
11026 },
11027
11028 _resetRegistration: function(oldKey) {
11029 this._unregisterKey(oldKey);
11030 this._registerKeyValue(this.key, this.value);
11031 },
11032
11033 _unregisterKey: function(key) {
11034 this._unregister(key, this._metaData, this.list);
11035 },
11036
11037 _registerKeyValue: function(key, value) {
11038 this._register(key, value, this._metaData, this.list);
11039 },
11040
11041 _register: function(key, value, data, list) {
11042 if (key && data && value !== undefined) {
11043 data[key] = value;
11044 list.push(value);
11045 }
11046 },
11047
11048 _unregister: function(key, data, list) {
11049 if (key && data) {
11050 if (key in data) {
11051 var value = data[key];
11052 delete data[key];
11053 this.arrayDelete(list, value);
11054 }
11055 }
11056 }
11057
11058 });
11059
11060 /**
11061 `iron-meta-query` can be used to access infomation stored in `iron-meta`.
11062
11063 Examples:
11064
11065 If I create an instance like this:
11066
11067 <iron-meta key="info" value="foo/bar"></iron-meta>
11068
11069 Note that value="foo/bar" is the metadata I've defined. I could define more
11070 attributes or use child nodes to define additional metadata.
11071
11072 Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
11073
11074 var value = new Polymer.IronMetaQuery({key: 'info'}).value;
11075
11076 @group Polymer Iron Elements
11077 @element iron-meta-query
11078 */
11079 Polymer.IronMetaQuery = Polymer({
11080
11081 is: 'iron-meta-query',
11082
11083 properties: {
11084
11085 /**
11086 * The type of meta-data. All meta-data of the same type is stored
11087 * together.
11088 */
11089 type: {
11090 type: String,
11091 value: 'default',
11092 observer: '_typeChanged'
11093 },
11094
11095 /**
11096 * Specifies a key to use for retrieving `value` from the `type`
11097 * namespace.
11098 */
11099 key: {
11100 type: String,
11101 observer: '_keyChanged'
11102 },
11103
11104 /**
11105 * The meta-data to store or retrieve.
11106 */
11107 value: {
11108 type: Object,
11109 notify: true,
11110 readOnly: true
11111 },
11112
11113 /**
11114 * Array of all meta-data values for the given type.
11115 */
11116 list: {
11117 type: Array,
11118 notify: true
11119 }
11120
11121 },
11122
11123 /**
11124 * Actually a factory method, not a true constructor. Only runs if
11125 * someone invokes it directly (via `new Polymer.IronMeta()`);
11126 */
11127 factoryImpl: function(config) {
11128 if (config) {
11129 for (var n in config) {
11130 switch(n) {
11131 case 'type':
11132 case 'key':
11133 this[n] = config[n];
11134 break;
11135 }
11136 }
11137 }
11138 },
11139
11140 created: function() {
11141 // TODO(sjmiles): good for debugging?
11142 this._metaDatas = metaDatas;
11143 this._metaArrays = metaArrays;
11144 },
11145
11146 _keyChanged: function(key) {
11147 this._setValue(this._metaData && this._metaData[key]);
11148 },
11149
11150 _typeChanged: function(type) {
11151 this._metaData = metaDatas[type];
11152 this.list = metaArrays[type];
11153 if (this.key) {
11154 this._keyChanged(this.key);
11155 }
11156 },
11157
11158 /**
11159 * Retrieves meta data value by key.
11160 * @param {string} key The key of the meta-data to be returned.
11161 * @return {*}
11162 */
11163 byKey: function(key) {
11164 return this._metaData && this._metaData[key];
11165 }
11166
11167 });
11168
11169 })();
11170 Polymer({
11171
11172 is: 'iron-icon',
11173
11174 properties: {
11175
11176 /**
11177 * The name of the icon to use. The name should be of the form:
11178 * `iconset_name:icon_name`.
11179 */
11180 icon: {
11181 type: String,
11182 observer: '_iconChanged'
11183 },
11184
11185 /**
11186 * The name of the theme to used, if one is specified by the
11187 * iconset.
11188 */
11189 theme: {
11190 type: String,
11191 observer: '_updateIcon'
11192 },
11193
11194 /**
11195 * If using iron-icon without an iconset, you can set the src to be
11196 * the URL of an individual icon image file. Note that this will take
11197 * precedence over a given icon attribute.
11198 */
11199 src: {
11200 type: String,
11201 observer: '_srcChanged'
11202 },
11203
11204 /**
11205 * @type {!Polymer.IronMeta}
11206 */
11207 _meta: {
11208 value: Polymer.Base.create('iron-meta', {type: 'iconset'})
11209 }
11210
11211 },
11212
11213 _DEFAULT_ICONSET: 'icons',
11214
11215 _iconChanged: function(icon) {
11216 var parts = (icon || '').split(':');
11217 this._iconName = parts.pop();
11218 this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
11219 this._updateIcon();
11220 },
11221
11222 _srcChanged: function(src) {
11223 this._updateIcon();
11224 },
11225
11226 _usesIconset: function() {
11227 return this.icon || !this.src;
11228 },
11229
11230 /** @suppress {visibility} */
11231 _updateIcon: function() {
11232 if (this._usesIconset()) {
11233 if (this._iconsetName) {
11234 this._iconset = /** @type {?Polymer.Iconset} */ (
11235 this._meta.byKey(this._iconsetName));
11236 if (this._iconset) {
11237 this._iconset.applyIcon(this, this._iconName, this.theme);
11238 this.unlisten(window, 'iron-iconset-added', '_updateIcon');
11239 } else {
11240 this.listen(window, 'iron-iconset-added', '_updateIcon');
11241 }
11242 }
11243 } else {
11244 if (!this._img) {
11245 this._img = document.createElement('img');
11246 this._img.style.width = '100%';
11247 this._img.style.height = '100%';
11248 this._img.draggable = false;
11249 }
11250 this._img.src = this.src;
11251 Polymer.dom(this.root).appendChild(this._img);
11252 }
11253 }
11254
11255 });
11256 /**
11257 * The `iron-iconset-svg` element allows users to define their own icon sets
11258 * that contain svg icons. The svg icon elements should be children of the
11259 * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
11260 *
11261 * Using svg elements to create icons has a few advantages over traditional
11262 * bitmap graphics like jpg or png. Icons that use svg are vector based so the y
11263 * are resolution independent and should look good on any device. They are
11264 * stylable via css. Icons can be themed, colorized, and even animated.
11265 *
11266 * Example:
11267 *
11268 * <iron-iconset-svg name="my-svg-icons" size="24">
11269 * <svg>
11270 * <defs>
11271 * <g id="shape">
11272 * <rect x="50" y="50" width="50" height="50" />
11273 * <circle cx="50" cy="50" r="50" />
11274 * </g>
11275 * </defs>
11276 * </svg>
11277 * </iron-iconset-svg>
11278 *
11279 * This will automatically register the icon set "my-svg-icons" to the iconset
11280 * database. To use these icons from within another element, make a
11281 * `iron-iconset` element and call the `byId` method
11282 * to retrieve a given iconset. To apply a particular icon inside an
11283 * element use the `applyIcon` method. For example:
11284 *
11285 * iconset.applyIcon(iconNode, 'car');
11286 *
11287 * @element iron-iconset-svg
11288 * @demo demo/index.html
11289 */
11290 Polymer({
11291
11292 is: 'iron-iconset-svg',
11293
11294 properties: {
11295
11296 /**
11297 * The name of the iconset.
11298 *
11299 * @attribute name
11300 * @type string
11301 */
11302 name: {
11303 type: String,
11304 observer: '_nameChanged'
11305 },
11306
11307 /**
11308 * The size of an individual icon. Note that icons must be square.
11309 *
11310 * @attribute iconSize
11311 * @type number
11312 * @default 24
11313 */
11314 size: {
11315 type: Number,
11316 value: 24
11317 }
11318
11319 },
11320
11321 /**
11322 * Construct an array of all icon names in this iconset.
11323 *
11324 * @return {!Array} Array of icon names.
11325 */
11326 getIconNames: function() {
11327 this._icons = this._createIconMap();
11328 return Object.keys(this._icons).map(function(n) {
11329 return this.name + ':' + n;
11330 }, this);
11331 },
11332
11333 /**
11334 * Applies an icon to the given element.
11335 *
11336 * An svg icon is prepended to the element's shadowRoot if it exists,
11337 * otherwise to the element itself.
11338 *
11339 * @method applyIcon
11340 * @param {Element} element Element to which the icon is applied.
11341 * @param {string} iconName Name of the icon to apply.
11342 * @return {Element} The svg element which renders the icon.
11343 */
11344 applyIcon: function(element, iconName) {
11345 // insert svg element into shadow root, if it exists
11346 element = element.root || element;
11347 // Remove old svg element
11348 this.removeIcon(element);
11349 // install new svg element
11350 var svg = this._cloneIcon(iconName);
11351 if (svg) {
11352 var pde = Polymer.dom(element);
11353 pde.insertBefore(svg, pde.childNodes[0]);
11354 return element._svgIcon = svg;
11355 }
11356 return null;
11357 },
11358
11359 /**
11360 * Remove an icon from the given element by undoing the changes effected
11361 * by `applyIcon`.
11362 *
11363 * @param {Element} element The element from which the icon is removed.
11364 */
11365 removeIcon: function(element) {
11366 // Remove old svg element
11367 if (element._svgIcon) {
11368 Polymer.dom(element).removeChild(element._svgIcon);
11369 element._svgIcon = null;
11370 }
11371 },
11372
11373 /**
11374 *
11375 * When name is changed, register iconset metadata
11376 *
11377 */
11378 _nameChanged: function() {
11379 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
11380 this.async(function() {
11381 this.fire('iron-iconset-added', this, {node: window});
11382 });
11383 },
11384
11385 /**
11386 * Create a map of child SVG elements by id.
11387 *
11388 * @return {!Object} Map of id's to SVG elements.
11389 */
11390 _createIconMap: function() {
11391 // Objects chained to Object.prototype (`{}`) have members. Specifically,
11392 // on FF there is a `watch` method that confuses the icon map, so we
11393 // need to use a null-based object here.
11394 var icons = Object.create(null);
11395 Polymer.dom(this).querySelectorAll('[id]')
11396 .forEach(function(icon) {
11397 icons[icon.id] = icon;
11398 });
11399 return icons;
11400 },
11401
11402 /**
11403 * Produce installable clone of the SVG element matching `id` in this
11404 * iconset, or `undefined` if there is no matching element.
11405 *
11406 * @return {Element} Returns an installable clone of the SVG element
11407 * matching `id`.
11408 */
11409 _cloneIcon: function(id) {
11410 // create the icon map on-demand, since the iconset itself has no discrete
11411 // signal to know when it's children are fully parsed
11412 this._icons = this._icons || this._createIconMap();
11413 return this._prepareSvgClone(this._icons[id], this.size);
11414 },
11415
11416 /**
11417 * @param {Element} sourceSvg
11418 * @param {number} size
11419 * @return {Element}
11420 */
11421 _prepareSvgClone: function(sourceSvg, size) {
11422 if (sourceSvg) {
11423 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
11424 svg.setAttribute('viewBox', ['0', '0', size, size].join(' '));
11425 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
11426 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136
11427 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
11428 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
11429 svg.appendChild(sourceSvg.cloneNode(true)).removeAttribute('id');
11430 return svg;
11431 }
11432 return null;
11433 }
11434
11435 });
11436 Polymer({
11437 is: 'paper-item',
11438
11439 hostAttributes: {
11440 role: 'listitem',
11441 tabindex: '0'
11442 },
11443
11444 behaviors: [
11445 Polymer.IronControlState,
11446 Polymer.IronButtonState
11447 ]
11448 });
11449 /**
11450 * @param {!Function} selectCallback
11451 * @constructor
11452 */
11453 Polymer.IronSelection = function(selectCallback) {
11454 this.selection = [];
11455 this.selectCallback = selectCallback;
11456 };
11457
11458 Polymer.IronSelection.prototype = {
11459
11460 /**
11461 * Retrieves the selected item(s).
11462 *
11463 * @method get
11464 * @returns Returns the selected item(s). If the multi property is true,
11465 * `get` will return an array, otherwise it will return
11466 * the selected item or undefined if there is no selection.
11467 */
11468 get: function() {
11469 return this.multi ? this.selection.slice() : this.selection[0];
11470 },
11471
11472 /**
11473 * Clears all the selection except the ones indicated.
11474 *
11475 * @method clear
11476 * @param {Array} excludes items to be excluded.
11477 */
11478 clear: function(excludes) {
11479 this.selection.slice().forEach(function(item) {
11480 if (!excludes || excludes.indexOf(item) < 0) {
11481 this.setItemSelected(item, false);
11482 }
11483 }, this);
11484 },
11485
11486 /**
11487 * Indicates if a given item is selected.
11488 *
11489 * @method isSelected
11490 * @param {*} item The item whose selection state should be checked.
11491 * @returns Returns true if `item` is selected.
11492 */
11493 isSelected: function(item) {
11494 return this.selection.indexOf(item) >= 0;
11495 },
11496
11497 /**
11498 * Sets the selection state for a given item to either selected or deselecte d.
11499 *
11500 * @method setItemSelected
11501 * @param {*} item The item to select.
11502 * @param {boolean} isSelected True for selected, false for deselected.
11503 */
11504 setItemSelected: function(item, isSelected) {
11505 if (item != null) {
11506 if (isSelected) {
11507 this.selection.push(item);
11508 } else {
11509 var i = this.selection.indexOf(item);
11510 if (i >= 0) {
11511 this.selection.splice(i, 1);
11512 }
11513 }
11514 if (this.selectCallback) {
11515 this.selectCallback(item, isSelected);
11516 }
11517 }
11518 },
11519
11520 /**
11521 * Sets the selection state for a given item. If the `multi` property
11522 * is true, then the selected state of `item` will be toggled; otherwise
11523 * the `item` will be selected.
11524 *
11525 * @method select
11526 * @param {*} item The item to select.
11527 */
11528 select: function(item) {
11529 if (this.multi) {
11530 this.toggle(item);
11531 } else if (this.get() !== item) {
11532 this.setItemSelected(this.get(), false);
11533 this.setItemSelected(item, true);
11534 }
11535 },
11536
11537 /**
11538 * Toggles the selection state for `item`.
11539 *
11540 * @method toggle
11541 * @param {*} item The item to toggle.
11542 */
11543 toggle: function(item) {
11544 this.setItemSelected(item, !this.isSelected(item));
11545 }
11546
11547 };
11548 /** @polymerBehavior */
11549 Polymer.IronSelectableBehavior = {
11550
11551 /**
11552 * Fired when iron-selector is activated (selected or deselected).
11553 * It is fired before the selected items are changed.
11554 * Cancel the event to abort selection.
11555 *
11556 * @event iron-activate
11557 */
11558
11559 /**
11560 * Fired when an item is selected
11561 *
11562 * @event iron-select
11563 */
11564
11565 /**
11566 * Fired when an item is deselected
11567 *
11568 * @event iron-deselect
11569 */
11570
11571 /**
11572 * Fired when the list of selectable items changes (e.g., items are
11573 * added or removed). The detail of the event is a list of mutation
11574 * records that describe what changed.
11575 *
11576 * @event iron-items-changed
11577 */
11578
11579 properties: {
11580
11581 /**
11582 * If you want to use the attribute value of an element for `selected` ins tead of the index,
11583 * set this to the name of the attribute.
11584 */
11585 attrForSelected: {
11586 type: String,
11587 value: null
11588 },
11589
11590 /**
11591 * Gets or sets the selected element. The default is to use the index of t he item.
11592 */
11593 selected: {
11594 type: String,
11595 notify: true
11596 },
11597
11598 /**
11599 * Returns the currently selected item.
11600 */
11601 selectedItem: {
11602 type: Object,
11603 readOnly: true,
11604 notify: true
11605 },
11606
11607 /**
11608 * The event that fires from items when they are selected. Selectable
11609 * will listen for this event from items and update the selection state.
11610 * Set to empty string to listen to no events.
11611 */
11612 activateEvent: {
11613 type: String,
11614 value: 'tap',
11615 observer: '_activateEventChanged'
11616 },
11617
11618 /**
11619 * This is a CSS selector string. If this is set, only items that match t he CSS selector
11620 * are selectable.
11621 */
11622 selectable: String,
11623
11624 /**
11625 * The class to set on elements when selected.
11626 */
11627 selectedClass: {
11628 type: String,
11629 value: 'iron-selected'
11630 },
11631
11632 /**
11633 * The attribute to set on elements when selected.
11634 */
11635 selectedAttribute: {
11636 type: String,
11637 value: null
11638 },
11639
11640 /**
11641 * The set of excluded elements where the key is the `localName`
11642 * of the element that will be ignored from the item list.
11643 *
11644 * @type {object}
11645 * @default {template: 1}
11646 */
11647 excludedLocalNames: {
11648 type: Object,
11649 value: function() {
11650 return {
11651 'template': 1
11652 };
11653 }
11654 }
11655 },
11656
11657 observers: [
11658 '_updateSelected(attrForSelected, selected)'
11659 ],
11660
11661 created: function() {
11662 this._bindFilterItem = this._filterItem.bind(this);
11663 this._selection = new Polymer.IronSelection(this._applySelection.bind(this ));
11664 },
11665
11666 attached: function() {
11667 this._observer = this._observeItems(this);
11668 this._contentObserver = this._observeContent(this);
11669 if (!this.selectedItem && this.selected) {
11670 this._updateSelected(this.attrForSelected,this.selected)
11671 }
11672 },
11673
11674 detached: function() {
11675 if (this._observer) {
11676 this._observer.disconnect();
11677 }
11678 if (this._contentObserver) {
11679 this._contentObserver.disconnect();
11680 }
11681 this._removeListener(this.activateEvent);
11682 },
11683
11684 /**
11685 * Returns an array of selectable items.
11686 *
11687 * @property items
11688 * @type Array
11689 */
11690 get items() {
11691 var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
11692 return Array.prototype.filter.call(nodes, this._bindFilterItem);
11693 },
11694
11695 /**
11696 * Returns the index of the given item.
11697 *
11698 * @method indexOf
11699 * @param {Object} item
11700 * @returns Returns the index of the item
11701 */
11702 indexOf: function(item) {
11703 return this.items.indexOf(item);
11704 },
11705
11706 /**
11707 * Selects the given value.
11708 *
11709 * @method select
11710 * @param {string} value the value to select.
11711 */
11712 select: function(value) {
11713 this.selected = value;
11714 },
11715
11716 /**
11717 * Selects the previous item.
11718 *
11719 * @method selectPrevious
11720 */
11721 selectPrevious: function() {
11722 var length = this.items.length;
11723 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len gth;
11724 this.selected = this._indexToValue(index);
11725 },
11726
11727 /**
11728 * Selects the next item.
11729 *
11730 * @method selectNext
11731 */
11732 selectNext: function() {
11733 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l ength;
11734 this.selected = this._indexToValue(index);
11735 },
11736
11737 _addListener: function(eventName) {
11738 this.listen(this, eventName, '_activateHandler');
11739 },
11740
11741 _removeListener: function(eventName) {
11742 this.unlisten(this, eventName, '_activateHandler');
11743 },
11744
11745 _activateEventChanged: function(eventName, old) {
11746 this._removeListener(old);
11747 this._addListener(eventName);
11748 },
11749
11750 _updateSelected: function() {
11751 this._selectSelected(this.selected);
11752 },
11753
11754 _selectSelected: function(selected) {
11755 this._selection.select(this._valueToItem(this.selected));
11756 },
11757
11758 _filterItem: function(node) {
11759 return !this.excludedLocalNames[node.localName];
11760 },
11761
11762 _valueToItem: function(value) {
11763 return (value == null) ? null : this.items[this._valueToIndex(value)];
11764 },
11765
11766 _valueToIndex: function(value) {
11767 if (this.attrForSelected) {
11768 for (var i = 0, item; item = this.items[i]; i++) {
11769 if (this._valueForItem(item) == value) {
11770 return i;
11771 }
11772 }
11773 } else {
11774 return Number(value);
11775 }
11776 },
11777
11778 _indexToValue: function(index) {
11779 if (this.attrForSelected) {
11780 var item = this.items[index];
11781 if (item) {
11782 return this._valueForItem(item);
11783 }
11784 } else {
11785 return index;
11786 }
11787 },
11788
11789 _valueForItem: function(item) {
11790 return item[this.attrForSelected] || item.getAttribute(this.attrForSelecte d);
11791 },
11792
11793 _applySelection: function(item, isSelected) {
11794 if (this.selectedClass) {
11795 this.toggleClass(this.selectedClass, isSelected, item);
11796 }
11797 if (this.selectedAttribute) {
11798 this.toggleAttribute(this.selectedAttribute, isSelected, item);
11799 }
11800 this._selectionChange();
11801 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
11802 },
11803
11804 _selectionChange: function() {
11805 this._setSelectedItem(this._selection.get());
11806 },
11807
11808 // observe content changes under the given node.
11809 _observeContent: function(node) {
11810 var content = node.querySelector('content');
11811 if (content && content.parentElement === node) {
11812 return this._observeItems(node.domHost);
11813 }
11814 },
11815
11816 // observe items change under the given node.
11817 _observeItems: function(node) {
11818 // TODO(cdata): Update this when we get distributed children changed.
11819 var observer = new MutationObserver(function(mutations) {
11820 // Let other interested parties know about the change so that
11821 // we don't have to recreate mutation observers everywher.
11822 this.fire('iron-items-changed', mutations, {
11823 bubbles: false,
11824 cancelable: false
11825 });
11826
11827 if (this.selected != null) {
11828 this._updateSelected();
11829 }
11830 }.bind(this));
11831 observer.observe(node, {
11832 childList: true,
11833 subtree: true
11834 });
11835 return observer;
11836 },
11837
11838 _activateHandler: function(e) {
11839 var t = e.target;
11840 var items = this.items;
11841 while (t && t != this) {
11842 var i = items.indexOf(t);
11843 if (i >= 0) {
11844 var value = this._indexToValue(i);
11845 this._itemActivate(value, t);
11846 return;
11847 }
11848 t = t.parentNode;
11849 }
11850 },
11851
11852 _itemActivate: function(value, item) {
11853 if (!this.fire('iron-activate',
11854 {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
11855 this.select(value);
11856 }
11857 }
11858
11859 };
11860 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */
11861 Polymer.IronMultiSelectableBehaviorImpl = {
11862 properties: {
11863
11864 /**
11865 * If true, multiple selections are allowed.
11866 */
11867 multi: {
11868 type: Boolean,
11869 value: false,
11870 observer: 'multiChanged'
11871 },
11872
11873 /**
11874 * Gets or sets the selected elements. This is used instead of `selected` when `multi`
11875 * is true.
11876 */
11877 selectedValues: {
11878 type: Array,
11879 notify: true
11880 },
11881
11882 /**
11883 * Returns an array of currently selected items.
11884 */
11885 selectedItems: {
11886 type: Array,
11887 readOnly: true,
11888 notify: true
11889 },
11890
11891 },
11892
11893 observers: [
11894 '_updateSelected(attrForSelected, selectedValues)'
11895 ],
11896
11897 /**
11898 * Selects the given value. If the `multi` property is true, then the select ed state of the
11899 * `value` will be toggled; otherwise the `value` will be selected.
11900 *
11901 * @method select
11902 * @param {string} value the value to select.
11903 */
11904 select: function(value) {
11905 if (this.multi) {
11906 if (this.selectedValues) {
11907 this._toggleSelected(value);
11908 } else {
11909 this.selectedValues = [value];
11910 }
11911 } else {
11912 this.selected = value;
11913 }
11914 },
11915
11916 multiChanged: function(multi) {
11917 this._selection.multi = multi;
11918 },
11919
11920 _updateSelected: function() {
11921 if (this.multi) {
11922 this._selectMulti(this.selectedValues);
11923 } else {
11924 this._selectSelected(this.selected);
11925 }
11926 },
11927
11928 _selectMulti: function(values) {
11929 this._selection.clear();
11930 if (values) {
11931 for (var i = 0; i < values.length; i++) {
11932 this._selection.setItemSelected(this._valueToItem(values[i]), true);
11933 }
11934 }
11935 },
11936
11937 _selectionChange: function() {
11938 var s = this._selection.get();
11939 if (this.multi) {
11940 this._setSelectedItems(s);
11941 } else {
11942 this._setSelectedItems([s]);
11943 this._setSelectedItem(s);
11944 }
11945 },
11946
11947 _toggleSelected: function(value) {
11948 var i = this.selectedValues.indexOf(value);
11949 var unselected = i < 0;
11950 if (unselected) {
11951 this.push('selectedValues',value);
11952 } else {
11953 this.splice('selectedValues',i,1);
11954 }
11955 this._selection.setItemSelected(this._valueToItem(value), unselected);
11956 }
11957 };
11958
11959 /** @polymerBehavior */
11960 Polymer.IronMultiSelectableBehavior = [
11961 Polymer.IronSelectableBehavior,
11962 Polymer.IronMultiSelectableBehaviorImpl
11963 ];
11964 /**
11965 * `Polymer.IronMenuBehavior` implements accessible menu behavior.
11966 *
11967 * @demo demo/index.html
11968 * @polymerBehavior Polymer.IronMenuBehavior
11969 */
11970 Polymer.IronMenuBehaviorImpl = {
11971
11972 properties: {
11973
11974 /**
11975 * Returns the currently focused item.
11976 * @type {?Object}
11977 */
11978 focusedItem: {
11979 observer: '_focusedItemChanged',
11980 readOnly: true,
11981 type: Object
11982 },
11983
11984 /**
11985 * The attribute to use on menu items to look up the item title. Typing th e first
11986 * letter of an item when the menu is open focuses that item. If unset, `t extContent`
11987 * will be used.
11988 */
11989 attrForItemTitle: {
11990 type: String
11991 }
11992 },
11993
11994 hostAttributes: {
11995 'role': 'menu',
11996 'tabindex': '0'
11997 },
11998
11999 observers: [
12000 '_updateMultiselectable(multi)'
12001 ],
12002
12003 listeners: {
12004 'focus': '_onFocus',
12005 'keydown': '_onKeydown',
12006 'iron-items-changed': '_onIronItemsChanged'
12007 },
12008
12009 keyBindings: {
12010 'up': '_onUpKey',
12011 'down': '_onDownKey',
12012 'esc': '_onEscKey',
12013 'shift+tab:keydown': '_onShiftTabDown'
12014 },
12015
12016 attached: function() {
12017 this._resetTabindices();
12018 },
12019
12020 /**
12021 * Selects the given value. If the `multi` property is true, then the select ed state of the
12022 * `value` will be toggled; otherwise the `value` will be selected.
12023 *
12024 * @param {string} value the value to select.
12025 */
12026 select: function(value) {
12027 if (this._defaultFocusAsync) {
12028 this.cancelAsync(this._defaultFocusAsync);
12029 this._defaultFocusAsync = null;
12030 }
12031 var item = this._valueToItem(value);
12032 if (item && item.hasAttribute('disabled')) return;
12033 this._setFocusedItem(item);
12034 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
12035 },
12036
12037 /**
12038 * Resets all tabindex attributes to the appropriate value based on the
12039 * current selection state. The appropriate value is `0` (focusable) for
12040 * the default selected item, and `-1` (not keyboard focusable) for all
12041 * other items.
12042 */
12043 _resetTabindices: function() {
12044 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[ 0]) : this.selectedItem;
12045
12046 this.items.forEach(function(item) {
12047 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
12048 }, this);
12049 },
12050
12051 /**
12052 * Sets appropriate ARIA based on whether or not the menu is meant to be
12053 * multi-selectable.
12054 *
12055 * @param {boolean} multi True if the menu should be multi-selectable.
12056 */
12057 _updateMultiselectable: function(multi) {
12058 if (multi) {
12059 this.setAttribute('aria-multiselectable', 'true');
12060 } else {
12061 this.removeAttribute('aria-multiselectable');
12062 }
12063 },
12064
12065 /**
12066 * Given a KeyboardEvent, this method will focus the appropriate item in the
12067 * menu (if there is a relevant item, and it is possible to focus it).
12068 *
12069 * @param {KeyboardEvent} event A KeyboardEvent.
12070 */
12071 _focusWithKeyboardEvent: function(event) {
12072 for (var i = 0, item; item = this.items[i]; i++) {
12073 var attr = this.attrForItemTitle || 'textContent';
12074 var title = item[attr] || item.getAttribute(attr);
12075 if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCod e(event.keyCode).toLowerCase()) {
12076 this._setFocusedItem(item);
12077 break;
12078 }
12079 }
12080 },
12081
12082 /**
12083 * Focuses the previous item (relative to the currently focused item) in the
12084 * menu.
12085 */
12086 _focusPrevious: function() {
12087 var length = this.items.length;
12088 var index = (Number(this.indexOf(this.focusedItem)) - 1 + length) % length ;
12089 this._setFocusedItem(this.items[index]);
12090 },
12091
12092 /**
12093 * Focuses the next item (relative to the currently focused item) in the
12094 * menu.
12095 */
12096 _focusNext: function() {
12097 var index = (Number(this.indexOf(this.focusedItem)) + 1) % this.items.leng th;
12098 this._setFocusedItem(this.items[index]);
12099 },
12100
12101 /**
12102 * Mutates items in the menu based on provided selection details, so that
12103 * all items correctly reflect selection state.
12104 *
12105 * @param {Element} item An item in the menu.
12106 * @param {boolean} isSelected True if the item should be shown in a
12107 * selected state, otherwise false.
12108 */
12109 _applySelection: function(item, isSelected) {
12110 if (isSelected) {
12111 item.setAttribute('aria-selected', 'true');
12112 } else {
12113 item.removeAttribute('aria-selected');
12114 }
12115
12116 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
12117 },
12118
12119 /**
12120 * Discretely updates tabindex values among menu items as the focused item
12121 * changes.
12122 *
12123 * @param {Element} focusedItem The element that is currently focused.
12124 * @param {?Element} old The last element that was considered focused, if
12125 * applicable.
12126 */
12127 _focusedItemChanged: function(focusedItem, old) {
12128 old && old.setAttribute('tabindex', '-1');
12129 if (focusedItem) {
12130 focusedItem.setAttribute('tabindex', '0');
12131 focusedItem.focus();
12132 }
12133 },
12134
12135 /**
12136 * A handler that responds to mutation changes related to the list of items
12137 * in the menu.
12138 *
12139 * @param {CustomEvent} event An event containing mutation records as its
12140 * detail.
12141 */
12142 _onIronItemsChanged: function(event) {
12143 var mutations = event.detail;
12144 var mutation;
12145 var index;
12146
12147 for (index = 0; index < mutations.length; ++index) {
12148 mutation = mutations[index];
12149
12150 if (mutation.addedNodes.length) {
12151 this._resetTabindices();
12152 break;
12153 }
12154 }
12155 },
12156
12157 /**
12158 * Handler that is called when a shift+tab keypress is detected by the menu.
12159 *
12160 * @param {CustomEvent} event A key combination event.
12161 */
12162 _onShiftTabDown: function(event) {
12163 var oldTabIndex;
12164
12165 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;
12166
12167 oldTabIndex = this.getAttribute('tabindex');
12168
12169 this.setAttribute('tabindex', '-1');
12170
12171 this.async(function() {
12172 this.setAttribute('tabindex', oldTabIndex);
12173 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
12174 // NOTE(cdata): polymer/polymer#1305
12175 }, 1);
12176 },
12177
12178 /**
12179 * Handler that is called when the menu receives focus.
12180 *
12181 * @param {FocusEvent} event A focus event.
12182 */
12183 _onFocus: function(event) {
12184 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
12185 return;
12186 }
12187 // do not focus the menu itself
12188 this.blur();
12189 // clear the cached focus item
12190 this._setFocusedItem(null);
12191 this._defaultFocusAsync = this.async(function() {
12192 // focus the selected item when the menu receives focus, or the first it em
12193 // if no item is selected
12194 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem;
12195 if (selectedItem) {
12196 this._setFocusedItem(selectedItem);
12197 } else {
12198 this._setFocusedItem(this.items[0]);
12199 }
12200 // async 100ms to wait for `select` to get called from `_itemActivate`
12201 }, 100);
12202 },
12203
12204 /**
12205 * Handler that is called when the up key is pressed.
12206 *
12207 * @param {CustomEvent} event A key combination event.
12208 */
12209 _onUpKey: function(event) {
12210 // up and down arrows moves the focus
12211 this._focusPrevious();
12212 },
12213
12214 /**
12215 * Handler that is called when the down key is pressed.
12216 *
12217 * @param {CustomEvent} event A key combination event.
12218 */
12219 _onDownKey: function(event) {
12220 this._focusNext();
12221 },
12222
12223 /**
12224 * Handler that is called when the esc key is pressed.
12225 *
12226 * @param {CustomEvent} event A key combination event.
12227 */
12228 _onEscKey: function(event) {
12229 // esc blurs the control
12230 this.focusedItem.blur();
12231 },
12232
12233 /**
12234 * Handler that is called when a keydown event is detected.
12235 *
12236 * @param {KeyboardEvent} event A keyboard event.
12237 */
12238 _onKeydown: function(event) {
12239 if (this.keyboardEventMatchesKeys(event, 'up down esc')) {
12240 return;
12241 }
12242
12243 // all other keys focus the menu item starting with that character
12244 this._focusWithKeyboardEvent(event);
12245 }
12246 };
12247
12248 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
12249
12250 /** @polymerBehavior Polymer.IronMenuBehavior */
12251 Polymer.IronMenuBehavior = [
12252 Polymer.IronMultiSelectableBehavior,
12253 Polymer.IronA11yKeysBehavior,
12254 Polymer.IronMenuBehaviorImpl
12255 ];
12256 (function() {
12257
12258 Polymer({
12259
12260 is: 'paper-menu',
12261
12262 behaviors: [
12263 Polymer.IronMenuBehavior
12264 ]
12265
12266 });
12267
12268 })();
12269 /**
12270 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
12271 * coordinate the flow of resize events between "resizers" (elements that cont rol the
12272 * size or hidden state of their children) and "resizables" (elements that nee d to be
12273 * notified when they are resized or un-hidden by their parents in order to ta ke
12274 * action on their new measurements).
12275 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
12276 * their element definition and listen for the `iron-resize` event on themselv es.
12277 * This event will be fired when they become showing after having been hidden,
12278 * when they are resized explicitly by another resizable, or when the window h as been
12279 * resized.
12280 * Note, the `iron-resize` event is non-bubbling.
12281 *
12282 * @polymerBehavior Polymer.IronResizableBehavior
12283 * @demo demo/index.html
12284 **/
12285 Polymer.IronResizableBehavior = {
12286 properties: {
12287 /**
12288 * The closest ancestor element that implements `IronResizableBehavior`.
12289 */
12290 _parentResizable: {
12291 type: Object,
12292 observer: '_parentResizableChanged'
12293 },
12294
12295 /**
12296 * True if this element is currently notifying its descedant elements of
12297 * resize.
12298 */
12299 _notifyingDescendant: {
12300 type: Boolean,
12301 value: false
12302 }
12303 },
12304
12305 listeners: {
12306 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
12307 },
12308
12309 created: function() {
12310 // We don't really need property effects on these, and also we want them
12311 // to be created before the `_parentResizable` observer fires:
12312 this._interestedResizables = [];
12313 this._boundNotifyResize = this.notifyResize.bind(this);
12314 },
12315
12316 attached: function() {
12317 this.fire('iron-request-resize-notifications', null, {
12318 node: this,
12319 bubbles: true,
12320 cancelable: true
12321 });
12322
12323 if (!this._parentResizable) {
12324 window.addEventListener('resize', this._boundNotifyResize);
12325 this.notifyResize();
12326 }
12327 },
12328
12329 detached: function() {
12330 if (this._parentResizable) {
12331 this._parentResizable.stopResizeNotificationsFor(this);
12332 } else {
12333 window.removeEventListener('resize', this._boundNotifyResize);
12334 }
12335
12336 this._parentResizable = null;
12337 },
12338
12339 /**
12340 * Can be called to manually notify a resizable and its descendant
12341 * resizables of a resize change.
12342 */
12343 notifyResize: function() {
12344 if (!this.isAttached) {
12345 return;
12346 }
12347
12348 this._interestedResizables.forEach(function(resizable) {
12349 if (this.resizerShouldNotify(resizable)) {
12350 this._notifyDescendant(resizable);
12351 }
12352 }, this);
12353
12354 this._fireResize();
12355 },
12356
12357 /**
12358 * Used to assign the closest resizable ancestor to this resizable
12359 * if the ancestor detects a request for notifications.
12360 */
12361 assignParentResizable: function(parentResizable) {
12362 this._parentResizable = parentResizable;
12363 },
12364
12365 /**
12366 * Used to remove a resizable descendant from the list of descendants
12367 * that should be notified of a resize change.
12368 */
12369 stopResizeNotificationsFor: function(target) {
12370 var index = this._interestedResizables.indexOf(target);
12371
12372 if (index > -1) {
12373 this._interestedResizables.splice(index, 1);
12374 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
12375 }
12376 },
12377
12378 /**
12379 * This method can be overridden to filter nested elements that should or
12380 * should not be notified by the current element. Return true if an element
12381 * should be notified, or false if it should not be notified.
12382 *
12383 * @param {HTMLElement} element A candidate descendant element that
12384 * implements `IronResizableBehavior`.
12385 * @return {boolean} True if the `element` should be notified of resize.
12386 */
12387 resizerShouldNotify: function(element) { return true; },
12388
12389 _onDescendantIronResize: function(event) {
12390 if (this._notifyingDescendant) {
12391 event.stopPropagation();
12392 return;
12393 }
12394
12395 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
12396 // otherwise non-bubbling event "just work." We do it manually here for
12397 // the case where Polymer is not using shadow roots for whatever reason:
12398 if (!Polymer.Settings.useShadow) {
12399 this._fireResize();
12400 }
12401 },
12402
12403 _fireResize: function() {
12404 this.fire('iron-resize', null, {
12405 node: this,
12406 bubbles: false
12407 });
12408 },
12409
12410 _onIronRequestResizeNotifications: function(event) {
12411 var target = event.path ? event.path[0] : event.target;
12412
12413 if (target === this) {
12414 return;
12415 }
12416
12417 if (this._interestedResizables.indexOf(target) === -1) {
12418 this._interestedResizables.push(target);
12419 this.listen(target, 'iron-resize', '_onDescendantIronResize');
12420 }
12421
12422 target.assignParentResizable(this);
12423 this._notifyDescendant(target);
12424
12425 event.stopPropagation();
12426 },
12427
12428 _parentResizableChanged: function(parentResizable) {
12429 if (parentResizable) {
12430 window.removeEventListener('resize', this._boundNotifyResize);
12431 }
12432 },
12433
12434 _notifyDescendant: function(descendant) {
12435 // NOTE(cdata): In IE10, attached is fired on children first, so it's
12436 // important not to notify them if the parent is not attached yet (or
12437 // else they will get redundantly notified when the parent attaches).
12438 if (!this.isAttached) {
12439 return;
12440 }
12441
12442 this._notifyingDescendant = true;
12443 descendant.notifyResize();
12444 this._notifyingDescendant = false;
12445 }
12446 };
12447 /**
12448 Polymer.IronFitBehavior fits an element in another element using `max-height` an d `max-width`, and
12449 optionally centers it in the window or another element.
12450
12451 The element will only be sized and/or positioned if it has not already been size d and/or positioned
12452 by CSS.
12453
12454 CSS properties | Action
12455 -----------------------------|-------------------------------------------
12456 `position` set | Element is not centered horizontally or verticall y
12457 `top` or `bottom` set | Element is not vertically centered
12458 `left` or `right` set | Element is not horizontally centered
12459 `max-height` or `height` set | Element respects `max-height` or `height`
12460 `max-width` or `width` set | Element respects `max-width` or `width`
12461
12462 @demo demo/index.html
12463 @polymerBehavior
12464 */
12465
12466 Polymer.IronFitBehavior = {
12467
12468 properties: {
12469
12470 /**
12471 * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
12472 * but it can be set to a child element. This is useful, for example, for implementing a
12473 * scrolling region inside the element.
12474 * @type {!Element}
12475 */
12476 sizingTarget: {
12477 type: Object,
12478 value: function() {
12479 return this;
12480 }
12481 },
12482
12483 /**
12484 * The element to fit `this` into.
12485 */
12486 fitInto: {
12487 type: Object,
12488 value: window
12489 },
12490
12491 /**
12492 * Set to true to auto-fit on attach.
12493 */
12494 autoFitOnAttach: {
12495 type: Boolean,
12496 value: false
12497 },
12498
12499 /** @type {?Object} */
12500 _fitInfo: {
12501 type: Object
12502 }
12503
12504 },
12505
12506 get _fitWidth() {
12507 var fitWidth;
12508 if (this.fitInto === window) {
12509 fitWidth = this.fitInto.innerWidth;
12510 } else {
12511 fitWidth = this.fitInto.getBoundingClientRect().width;
12512 }
12513 return fitWidth;
12514 },
12515
12516 get _fitHeight() {
12517 var fitHeight;
12518 if (this.fitInto === window) {
12519 fitHeight = this.fitInto.innerHeight;
12520 } else {
12521 fitHeight = this.fitInto.getBoundingClientRect().height;
12522 }
12523 return fitHeight;
12524 },
12525
12526 get _fitLeft() {
12527 var fitLeft;
12528 if (this.fitInto === window) {
12529 fitLeft = 0;
12530 } else {
12531 fitLeft = this.fitInto.getBoundingClientRect().left;
12532 }
12533 return fitLeft;
12534 },
12535
12536 get _fitTop() {
12537 var fitTop;
12538 if (this.fitInto === window) {
12539 fitTop = 0;
12540 } else {
12541 fitTop = this.fitInto.getBoundingClientRect().top;
12542 }
12543 return fitTop;
12544 },
12545
12546 attached: function() {
12547 if (this.autoFitOnAttach) {
12548 if (window.getComputedStyle(this).display === 'none') {
12549 setTimeout(function() {
12550 this.fit();
12551 }.bind(this));
12552 } else {
12553 this.fit();
12554 }
12555 }
12556 },
12557
12558 /**
12559 * Fits and optionally centers the element into the window, or `fitInfo` if specified.
12560 */
12561 fit: function() {
12562 this._discoverInfo();
12563 this.constrain();
12564 this.center();
12565 },
12566
12567 /**
12568 * Memoize information needed to position and size the target element.
12569 */
12570 _discoverInfo: function() {
12571 if (this._fitInfo) {
12572 return;
12573 }
12574 var target = window.getComputedStyle(this);
12575 var sizer = window.getComputedStyle(this.sizingTarget);
12576 this._fitInfo = {
12577 inlineStyle: {
12578 top: this.style.top || '',
12579 left: this.style.left || ''
12580 },
12581 positionedBy: {
12582 vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
12583 'bottom' : null),
12584 horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'aut o' ?
12585 'right' : null),
12586 css: target.position
12587 },
12588 sizedBy: {
12589 height: sizer.maxHeight !== 'none',
12590 width: sizer.maxWidth !== 'none'
12591 },
12592 margin: {
12593 top: parseInt(target.marginTop, 10) || 0,
12594 right: parseInt(target.marginRight, 10) || 0,
12595 bottom: parseInt(target.marginBottom, 10) || 0,
12596 left: parseInt(target.marginLeft, 10) || 0
12597 }
12598 };
12599 },
12600
12601 /**
12602 * Resets the target element's position and size constraints, and clear
12603 * the memoized data.
12604 */
12605 resetFit: function() {
12606 if (!this._fitInfo || !this._fitInfo.sizedBy.height) {
12607 this.sizingTarget.style.maxHeight = '';
12608 this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : '';
12609 }
12610 if (!this._fitInfo || !this._fitInfo.sizedBy.width) {
12611 this.sizingTarget.style.maxWidth = '';
12612 this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : '';
12613 }
12614 if (this._fitInfo) {
12615 this.style.position = this._fitInfo.positionedBy.css;
12616 }
12617 this._fitInfo = null;
12618 },
12619
12620 /**
12621 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element,
12622 * the window, or the `fitInfo` element has been resized.
12623 */
12624 refit: function() {
12625 this.resetFit();
12626 this.fit();
12627 },
12628
12629 /**
12630 * Constrains the size of the element to the window or `fitInfo` by setting `max-height`
12631 * and/or `max-width`.
12632 */
12633 constrain: function() {
12634 var info = this._fitInfo;
12635 // position at (0px, 0px) if not already positioned, so we can measure the natural size.
12636 if (!this._fitInfo.positionedBy.vertically) {
12637 this.style.top = '0px';
12638 }
12639 if (!this._fitInfo.positionedBy.horizontally) {
12640 this.style.left = '0px';
12641 }
12642 // need border-box for margin/padding
12643 this.sizingTarget.style.boxSizing = 'border-box';
12644 // constrain the width and height if not already set
12645 var rect = this.getBoundingClientRect();
12646 if (!info.sizedBy.height) {
12647 this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
12648 }
12649 if (!info.sizedBy.width) {
12650 this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right ', 'Width');
12651 }
12652 },
12653
12654 _sizeDimension: function(rect, positionedBy, start, end, extent) {
12655 var info = this._fitInfo;
12656 var max = extent === 'Width' ? this._fitWidth : this._fitHeight;
12657 var flip = (positionedBy === end);
12658 var offset = flip ? max - rect[end] : rect[start];
12659 var margin = info.margin[flip ? start : end];
12660 var offsetExtent = 'offset' + extent;
12661 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
12662 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px';
12663 },
12664
12665 /**
12666 * Centers horizontally and vertically if not already positioned. This also sets
12667 * `position:fixed`.
12668 */
12669 center: function() {
12670 if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy. horizontally) {
12671 // need position:fixed to center
12672 this.style.position = 'fixed';
12673 }
12674 if (!this._fitInfo.positionedBy.vertically) {
12675 var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop;
12676 top -= this._fitInfo.margin.top;
12677 this.style.top = top + 'px';
12678 }
12679 if (!this._fitInfo.positionedBy.horizontally) {
12680 var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft;
12681 left -= this._fitInfo.margin.left;
12682 this.style.left = left + 'px';
12683 }
12684 }
12685
12686 };
12687 Polymer.IronOverlayManager = (function() {
12688
12689 var overlays = [];
12690 var DEFAULT_Z = 10;
12691 var backdrops = [];
12692
12693 // track overlays for z-index and focus managemant
12694 function addOverlay(overlay) {
12695 var z0 = currentOverlayZ();
12696 overlays.push(overlay);
12697 var z1 = currentOverlayZ();
12698 if (z1 <= z0) {
12699 applyOverlayZ(overlay, z0);
12700 }
12701 }
12702
12703 function removeOverlay(overlay) {
12704 var i = overlays.indexOf(overlay);
12705 if (i >= 0) {
12706 overlays.splice(i, 1);
12707 setZ(overlay, '');
12708 }
12709 }
12710
12711 function applyOverlayZ(overlay, aboveZ) {
12712 setZ(overlay, aboveZ + 2);
12713 }
12714
12715 function setZ(element, z) {
12716 element.style.zIndex = z;
12717 }
12718
12719 function currentOverlay() {
12720 var i = overlays.length - 1;
12721 while (overlays[i] && !overlays[i].opened) {
12722 --i;
12723 }
12724 return overlays[i];
12725 }
12726
12727 function currentOverlayZ() {
12728 var z;
12729 var current = currentOverlay();
12730 if (current) {
12731 var z1 = window.getComputedStyle(current).zIndex;
12732 if (!isNaN(z1)) {
12733 z = Number(z1);
12734 }
12735 }
12736 return z || DEFAULT_Z;
12737 }
12738
12739 function focusOverlay() {
12740 var current = currentOverlay();
12741 // We have to be careful to focus the next overlay _after_ any current
12742 // transitions are complete (due to the state being toggled prior to the
12743 // transition). Otherwise, we risk infinite recursion when a transitioning
12744 // (closed) overlay becomes the current overlay.
12745 //
12746 // NOTE: We make the assumption that any overlay that completes a transiti on
12747 // will call into focusOverlay to kick the process back off. Currently:
12748 // transitionend -> _applyFocus -> focusOverlay.
12749 if (current && !current.transitioning) {
12750 current._applyFocus();
12751 }
12752 }
12753
12754 function trackBackdrop(element) {
12755 // backdrops contains the overlays with a backdrop that are currently
12756 // visible
12757 if (element.opened) {
12758 backdrops.push(element);
12759 } else {
12760 var index = backdrops.indexOf(element);
12761 if (index >= 0) {
12762 backdrops.splice(index, 1);
12763 }
12764 }
12765 }
12766
12767 function getBackdrops() {
12768 return backdrops;
12769 }
12770
12771 return {
12772 addOverlay: addOverlay,
12773 removeOverlay: removeOverlay,
12774 currentOverlay: currentOverlay,
12775 currentOverlayZ: currentOverlayZ,
12776 focusOverlay: focusOverlay,
12777 trackBackdrop: trackBackdrop,
12778 getBackdrops: getBackdrops
12779 };
12780
12781 })();
12782 (function() {
12783
12784 Polymer({
12785
12786 is: 'iron-overlay-backdrop',
12787
12788 properties: {
12789
12790 /**
12791 * Returns true if the backdrop is opened.
12792 */
12793 opened: {
12794 readOnly: true,
12795 reflectToAttribute: true,
12796 type: Boolean,
12797 value: false
12798 },
12799
12800 _manager: {
12801 type: Object,
12802 value: Polymer.IronOverlayManager
12803 }
12804
12805 },
12806
12807 /**
12808 * Appends the backdrop to document body and sets its `z-index` to be below the latest overlay.
12809 */
12810 prepare: function() {
12811 if (!this.parentNode) {
12812 Polymer.dom(document.body).appendChild(this);
12813 this.style.zIndex = this._manager.currentOverlayZ() - 1;
12814 }
12815 },
12816
12817 /**
12818 * Shows the backdrop if needed.
12819 */
12820 open: function() {
12821 // only need to make the backdrop visible if this is called by the first o verlay with a backdrop
12822 if (this._manager.getBackdrops().length < 2) {
12823 this._setOpened(true);
12824 }
12825 },
12826
12827 /**
12828 * Hides the backdrop if needed.
12829 */
12830 close: function() {
12831 // only need to make the backdrop invisible if this is called by the last overlay with a backdrop
12832 if (this._manager.getBackdrops().length < 2) {
12833 this._setOpened(false);
12834 }
12835 },
12836
12837 /**
12838 * Removes the backdrop from document body if needed.
12839 */
12840 complete: function() {
12841 // only remove the backdrop if there are no more overlays with backdrops
12842 if (this._manager.getBackdrops().length === 0 && this.parentNode) {
12843 Polymer.dom(this.parentNode).removeChild(this);
12844 }
12845 }
12846
12847 });
12848
12849 })();
12850 /**
12851 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
12852 on top of other content. It includes an optional backdrop, and can be used to im plement a variety
12853 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once.
12854
12855 ### Closing and canceling
12856
12857 A dialog may be hidden by closing or canceling. The difference between close and cancel is user
12858 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
12859 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
12860 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click ` properties.
12861 `close()` should be called explicitly by the implementer when the user interacts with a control
12862 in the overlay element.
12863
12864 ### Positioning
12865
12866 By default the element is sized and positioned to fit and centered inside the wi ndow. You can
12867 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
12868
12869 ### Backdrop
12870
12871 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
12872 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
12873 options.
12874
12875 ### Limitations
12876
12877 The element is styled to appear on top of other content by setting its `z-index` property. You
12878 must ensure no element has a stacking context with a higher `z-index` than its p arent stacking
12879 context. You should place this element as a child of `<body>` whenever possible.
12880
12881 @demo demo/index.html
12882 @polymerBehavior Polymer.IronOverlayBehavior
12883 */
12884
12885 Polymer.IronOverlayBehaviorImpl = {
12886
12887 properties: {
12888
12889 /**
12890 * True if the overlay is currently displayed.
12891 */
12892 opened: {
12893 observer: '_openedChanged',
12894 type: Boolean,
12895 value: false,
12896 notify: true
12897 },
12898
12899 /**
12900 * True if the overlay was canceled when it was last closed.
12901 */
12902 canceled: {
12903 observer: '_canceledChanged',
12904 readOnly: true,
12905 type: Boolean,
12906 value: false
12907 },
12908
12909 /**
12910 * Set to true to display a backdrop behind the overlay.
12911 */
12912 withBackdrop: {
12913 type: Boolean,
12914 value: false
12915 },
12916
12917 /**
12918 * Set to true to disable auto-focusing the overlay or child nodes with
12919 * the `autofocus` attribute` when the overlay is opened.
12920 */
12921 noAutoFocus: {
12922 type: Boolean,
12923 value: false
12924 },
12925
12926 /**
12927 * Set to true to disable canceling the overlay with the ESC key.
12928 */
12929 noCancelOnEscKey: {
12930 type: Boolean,
12931 value: false
12932 },
12933
12934 /**
12935 * Set to true to disable canceling the overlay by clicking outside it.
12936 */
12937 noCancelOnOutsideClick: {
12938 type: Boolean,
12939 value: false
12940 },
12941
12942 /**
12943 * Returns the reason this dialog was last closed.
12944 */
12945 closingReason: {
12946 // was a getter before, but needs to be a property so other
12947 // behaviors can override this.
12948 type: Object
12949 },
12950
12951 _manager: {
12952 type: Object,
12953 value: Polymer.IronOverlayManager
12954 },
12955
12956 _boundOnCaptureClick: {
12957 type: Function,
12958 value: function() {
12959 return this._onCaptureClick.bind(this);
12960 }
12961 },
12962
12963 _boundOnCaptureKeydown: {
12964 type: Function,
12965 value: function() {
12966 return this._onCaptureKeydown.bind(this);
12967 }
12968 }
12969
12970 },
12971
12972 listeners: {
12973 'tap': '_onClick',
12974 'iron-resize': '_onIronResize'
12975 },
12976
12977 /**
12978 * The backdrop element.
12979 * @type Node
12980 */
12981 get backdropElement() {
12982 return this._backdrop;
12983 },
12984
12985 get _focusNode() {
12986 return Polymer.dom(this).querySelector('[autofocus]') || this;
12987 },
12988
12989 registered: function() {
12990 this._backdrop = document.createElement('iron-overlay-backdrop');
12991 },
12992
12993 ready: function() {
12994 this._ensureSetup();
12995 if (this._callOpenedWhenReady) {
12996 this._openedChanged();
12997 }
12998 },
12999
13000 detached: function() {
13001 this.opened = false;
13002 this._completeBackdrop();
13003 this._manager.removeOverlay(this);
13004 },
13005
13006 /**
13007 * Toggle the opened state of the overlay.
13008 */
13009 toggle: function() {
13010 this.opened = !this.opened;
13011 },
13012
13013 /**
13014 * Open the overlay.
13015 */
13016 open: function() {
13017 this.opened = true;
13018 this.closingReason = {canceled: false};
13019 },
13020
13021 /**
13022 * Close the overlay.
13023 */
13024 close: function() {
13025 this.opened = false;
13026 this._setCanceled(false);
13027 },
13028
13029 /**
13030 * Cancels the overlay.
13031 */
13032 cancel: function() {
13033 this.opened = false;
13034 this._setCanceled(true);
13035 },
13036
13037 _ensureSetup: function() {
13038 if (this._overlaySetup) {
13039 return;
13040 }
13041 this._overlaySetup = true;
13042 this.style.outline = 'none';
13043 this.style.display = 'none';
13044 },
13045
13046 _openedChanged: function() {
13047 if (this.opened) {
13048 this.removeAttribute('aria-hidden');
13049 } else {
13050 this.setAttribute('aria-hidden', 'true');
13051 }
13052
13053 // wait to call after ready only if we're initially open
13054 if (!this._overlaySetup) {
13055 this._callOpenedWhenReady = this.opened;
13056 return;
13057 }
13058 if (this._openChangedAsync) {
13059 this.cancelAsync(this._openChangedAsync);
13060 }
13061
13062 this._toggleListeners();
13063
13064 if (this.opened) {
13065 this._prepareRenderOpened();
13066 }
13067
13068 // async here to allow overlay layer to become visible.
13069 this._openChangedAsync = this.async(function() {
13070 // overlay becomes visible here
13071 this.style.display = '';
13072 // force layout to ensure transitions will go
13073 /** @suppress {suspiciousCode} */ this.offsetWidth;
13074 if (this.opened) {
13075 this._renderOpened();
13076 } else {
13077 this._renderClosed();
13078 }
13079 this._openChangedAsync = null;
13080 });
13081
13082 },
13083
13084 _canceledChanged: function() {
13085 this.closingReason = this.closingReason || {};
13086 this.closingReason.canceled = this.canceled;
13087 },
13088
13089 _toggleListener: function(enable, node, event, boundListener, capture) {
13090 if (enable) {
13091 // enable document-wide tap recognizer
13092 if (event === 'tap') {
13093 Polymer.Gestures.add(document, 'tap', null);
13094 }
13095 node.addEventListener(event, boundListener, capture);
13096 } else {
13097 // disable document-wide tap recognizer
13098 if (event === 'tap') {
13099 Polymer.Gestures.remove(document, 'tap', null);
13100 }
13101 node.removeEventListener(event, boundListener, capture);
13102 }
13103 },
13104
13105 _toggleListeners: function() {
13106 if (this._toggleListenersAsync) {
13107 this.cancelAsync(this._toggleListenersAsync);
13108 }
13109 // async so we don't auto-close immediately via a click.
13110 this._toggleListenersAsync = this.async(function() {
13111 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureC lick, true);
13112 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt ureKeydown, true);
13113 this._toggleListenersAsync = null;
13114 }, 1);
13115 },
13116
13117 // tasks which must occur before opening; e.g. making the element visible
13118 _prepareRenderOpened: function() {
13119 this._manager.addOverlay(this);
13120
13121 if (this.withBackdrop) {
13122 this.backdropElement.prepare();
13123 this._manager.trackBackdrop(this);
13124 }
13125
13126 this._preparePositioning();
13127 this.fit();
13128 this._finishPositioning();
13129 },
13130
13131 // tasks which cause the overlay to actually open; typically play an
13132 // animation
13133 _renderOpened: function() {
13134 if (this.withBackdrop) {
13135 this.backdropElement.open();
13136 }
13137 this._finishRenderOpened();
13138 },
13139
13140 _renderClosed: function() {
13141 if (this.withBackdrop) {
13142 this.backdropElement.close();
13143 }
13144 this._finishRenderClosed();
13145 },
13146
13147 _onTransitionend: function(event) {
13148 // make sure this is our transition event.
13149 if (event && event.target !== this) {
13150 return;
13151 }
13152 if (this.opened) {
13153 this._finishRenderOpened();
13154 } else {
13155 this._finishRenderClosed();
13156 }
13157 },
13158
13159 _finishRenderOpened: function() {
13160 // focus the child node with [autofocus]
13161 if (!this.noAutoFocus) {
13162 this._focusNode.focus();
13163 }
13164
13165 this.fire('iron-overlay-opened');
13166
13167 this._squelchNextResize = true;
13168 this.async(this.notifyResize);
13169 },
13170
13171 _finishRenderClosed: function() {
13172 // hide the overlay and remove the backdrop
13173 this.resetFit();
13174 this.style.display = 'none';
13175 this._completeBackdrop();
13176 this._manager.removeOverlay(this);
13177
13178 this._focusNode.blur();
13179 // focus the next overlay, if there is one
13180 this._manager.focusOverlay();
13181
13182 this.fire('iron-overlay-closed', this.closingReason);
13183
13184 this._squelchNextResize = true;
13185 this.async(this.notifyResize);
13186 },
13187
13188 _completeBackdrop: function() {
13189 if (this.withBackdrop) {
13190 this._manager.trackBackdrop(this);
13191 this.backdropElement.complete();
13192 }
13193 },
13194
13195 _preparePositioning: function() {
13196 this.style.transition = this.style.webkitTransition = 'none';
13197 this.style.transform = this.style.webkitTransform = 'none';
13198 this.style.display = '';
13199 },
13200
13201 _finishPositioning: function() {
13202 this.style.display = 'none';
13203 this.style.transform = this.style.webkitTransform = '';
13204 // force layout to avoid application of transform
13205 /** @suppress {suspiciousCode} */ this.offsetWidth;
13206 this.style.transition = this.style.webkitTransition = '';
13207 },
13208
13209 _applyFocus: function() {
13210 if (this.opened) {
13211 if (!this.noAutoFocus) {
13212 this._focusNode.focus();
13213 }
13214 } else {
13215 this._focusNode.blur();
13216 this._manager.focusOverlay();
13217 }
13218 },
13219
13220 _onCaptureClick: function(event) {
13221 // attempt to close asynchronously and prevent the close of a tap event is immediately heard
13222 // on target. This is because in shadow dom due to event retargetting even t.target is not
13223 // useful.
13224 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi s)) {
13225 this._cancelJob = this.async(function() {
13226 this.cancel();
13227 }, 10);
13228 }
13229 },
13230
13231 _onClick: function(event) {
13232 if (this._cancelJob) {
13233 this.cancelAsync(this._cancelJob);
13234 this._cancelJob = null;
13235 }
13236 },
13237
13238 _onCaptureKeydown: function(event) {
13239 var ESC = 27;
13240 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
13241 this.cancel();
13242 event.stopPropagation();
13243 }
13244 },
13245
13246 _onIronResize: function() {
13247 if (this._squelchNextResize) {
13248 this._squelchNextResize = false;
13249 return;
13250 }
13251 if (this.opened) {
13252 this.refit();
13253 }
13254 }
13255
13256 /**
13257 * Fired after the `iron-overlay` opens.
13258 * @event iron-overlay-opened
13259 */
13260
13261 /**
13262 * Fired after the `iron-overlay` closes.
13263 * @event iron-overlay-closed
13264 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
13265 */
13266 };
13267
13268 /** @polymerBehavior */
13269 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl];
13270 /**
13271 * Use `Polymer.NeonAnimationBehavior` to implement an animation.
13272 * @polymerBehavior
13273 */
13274 Polymer.NeonAnimationBehavior = {
13275
13276 properties: {
13277
13278 /**
13279 * Defines the animation timing.
13280 */
13281 animationTiming: {
13282 type: Object,
13283 value: function() {
13284 return {
13285 duration: 500,
13286 easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
13287 fill: 'both'
13288 }
13289 }
13290 }
13291
13292 },
13293
13294 registered: function() {
13295 new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constru ctor});
13296 },
13297
13298 /**
13299 * Do any animation configuration here.
13300 */
13301 // configure: function(config) {
13302 // },
13303
13304 /**
13305 * Returns the animation timing by mixing in properties from `config` to the defaults defined
13306 * by the animation.
13307 */
13308 timingFromConfig: function(config) {
13309 if (config.timing) {
13310 for (var property in config.timing) {
13311 this.animationTiming[property] = config.timing[property];
13312 }
13313 }
13314 return this.animationTiming;
13315 },
13316
13317 /**
13318 * Sets `transform` and `transformOrigin` properties along with the prefixed versions.
13319 */
13320 setPrefixedProperty: function(node, property, value) {
13321 var map = {
13322 'transform': ['webkitTransform'],
13323 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin']
13324 };
13325 var prefixes = map[property];
13326 for (var prefix, index = 0; prefix = prefixes[index]; index++) {
13327 node.style[prefix] = value;
13328 }
13329 node.style[property] = value;
13330 },
13331
13332 /**
13333 * Called when the animation finishes.
13334 */
13335 complete: function() {}
13336
13337 };
13338 Polymer({
13339
13340 is: 'opaque-animation',
13341
13342 behaviors: [
13343 Polymer.NeonAnimationBehavior
13344 ],
13345
13346 configure: function(config) {
13347 var node = config.node;
13348 node.style.opacity = '0';
13349 this._effect = new KeyframeEffect(node, [
13350 {'opacity': '1'},
13351 {'opacity': '1'}
13352 ], this.timingFromConfig(config));
13353 return this._effect;
13354 },
13355
13356 complete: function(config) {
13357 config.node.style.opacity = '';
13358 }
13359
13360 });
13361 /**
13362 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim ations for use with
13363 * elements implementing `Polymer.NeonAnimationRunnerBehavior`.
13364 * @polymerBehavior
13365 */
13366 Polymer.NeonAnimatableBehavior = {
13367
13368 properties: {
13369
13370 /**
13371 * Animation configuration. See README for more info.
13372 */
13373 animationConfig: {
13374 type: Object
13375 },
13376
13377 /**
13378 * Convenience property for setting an 'entry' animation. Do not set `anim ationConfig.entry`
13379 * manually if using this. The animated node is set to `this` if using thi s property.
13380 */
13381 entryAnimation: {
13382 observer: '_entryAnimationChanged',
13383 type: String
13384 },
13385
13386 /**
13387 * Convenience property for setting an 'exit' animation. Do not set `anima tionConfig.exit`
13388 * manually if using this. The animated node is set to `this` if using thi s property.
13389 */
13390 exitAnimation: {
13391 observer: '_exitAnimationChanged',
13392 type: String
13393 }
13394
13395 },
13396
13397 _entryAnimationChanged: function() {
13398 this.animationConfig = this.animationConfig || {};
13399 if (this.entryAnimation !== 'fade-in-animation') {
13400 // insert polyfill hack
13401 this.animationConfig['entry'] = [{
13402 name: 'opaque-animation',
13403 node: this
13404 }, {
13405 name: this.entryAnimation,
13406 node: this
13407 }];
13408 } else {
13409 this.animationConfig['entry'] = [{
13410 name: this.entryAnimation,
13411 node: this
13412 }];
13413 }
13414 },
13415
13416 _exitAnimationChanged: function() {
13417 this.animationConfig = this.animationConfig || {};
13418 this.animationConfig['exit'] = [{
13419 name: this.exitAnimation,
13420 node: this
13421 }];
13422 },
13423
13424 _copyProperties: function(config1, config2) {
13425 // shallowly copy properties from config2 to config1
13426 for (var property in config2) {
13427 config1[property] = config2[property];
13428 }
13429 },
13430
13431 _cloneConfig: function(config) {
13432 var clone = {
13433 isClone: true
13434 };
13435 this._copyProperties(clone, config);
13436 return clone;
13437 },
13438
13439 _getAnimationConfigRecursive: function(type, map, allConfigs) {
13440 if (!this.animationConfig) {
13441 return;
13442 }
13443
13444 // type is optional
13445 var thisConfig;
13446 if (type) {
13447 thisConfig = this.animationConfig[type];
13448 } else {
13449 thisConfig = this.animationConfig;
13450 }
13451
13452 if (!Array.isArray(thisConfig)) {
13453 thisConfig = [thisConfig];
13454 }
13455
13456 // iterate animations and recurse to process configurations from child nod es
13457 if (thisConfig) {
13458 for (var config, index = 0; config = thisConfig[index]; index++) {
13459 if (config.animatable) {
13460 config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs);
13461 } else {
13462 if (config.id) {
13463 var cachedConfig = map[config.id];
13464 if (cachedConfig) {
13465 // merge configurations with the same id, making a clone lazily
13466 if (!cachedConfig.isClone) {
13467 map[config.id] = this._cloneConfig(cachedConfig)
13468 cachedConfig = map[config.id];
13469 }
13470 this._copyProperties(cachedConfig, config);
13471 } else {
13472 // put any configs with an id into a map
13473 map[config.id] = config;
13474 }
13475 } else {
13476 allConfigs.push(config);
13477 }
13478 }
13479 }
13480 }
13481 },
13482
13483 /**
13484 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure
13485 * an animation with an optional type. Elements implementing `Polymer.NeonAn imatableBehavior`
13486 * should define the property `animationConfig`, which is either a configura tion object
13487 * or a map of animation type to array of configuration objects.
13488 */
13489 getAnimationConfig: function(type) {
13490 var map = [];
13491 var allConfigs = [];
13492 this._getAnimationConfigRecursive(type, map, allConfigs);
13493 // append the configurations saved in the map to the array
13494 for (var key in map) {
13495 allConfigs.push(map[key]);
13496 }
13497 return allConfigs;
13498 }
13499
13500 };
13501 /**
13502 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations.
13503 *
13504 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior
13505 */
13506 Polymer.NeonAnimationRunnerBehaviorImpl = {
13507
13508 properties: {
13509
13510 _animationMeta: {
13511 type: Object,
13512 value: function() {
13513 return new Polymer.IronMeta({type: 'animation'});
13514 }
13515 },
13516
13517 /** @type {?Object} */
13518 _player: {
13519 type: Object
13520 }
13521
13522 },
13523
13524 _configureAnimationEffects: function(allConfigs) {
13525 var allAnimations = [];
13526 if (allConfigs.length > 0) {
13527 for (var config, index = 0; config = allConfigs[index]; index++) {
13528 var animationConstructor = this._animationMeta.byKey(config.name);
13529 if (animationConstructor) {
13530 var animation = animationConstructor && new animationConstructor();
13531 var effect = animation.configure(config);
13532 if (effect) {
13533 allAnimations.push({
13534 animation: animation,
13535 config: config,
13536 effect: effect
13537 });
13538 }
13539 } else {
13540 console.warn(this.is + ':', config.name, 'not found!');
13541 }
13542 }
13543 }
13544 return allAnimations;
13545 },
13546
13547 _runAnimationEffects: function(allEffects) {
13548 return document.timeline.play(new GroupEffect(allEffects));
13549 },
13550
13551 _completeAnimations: function(allAnimations) {
13552 for (var animation, index = 0; animation = allAnimations[index]; index++) {
13553 animation.animation.complete(animation.config);
13554 }
13555 },
13556
13557 /**
13558 * Plays an animation with an optional `type`.
13559 * @param {string=} type
13560 * @param {!Object=} cookie
13561 */
13562 playAnimation: function(type, cookie) {
13563 var allConfigs = this.getAnimationConfig(type);
13564 if (!allConfigs) {
13565 return;
13566 }
13567 var allAnimations = this._configureAnimationEffects(allConfigs);
13568 var allEffects = allAnimations.map(function(animation) {
13569 return animation.effect;
13570 });
13571
13572 if (allEffects.length > 0) {
13573 this._player = this._runAnimationEffects(allEffects);
13574 this._player.onfinish = function() {
13575 this._completeAnimations(allAnimations);
13576
13577 if (this._player) {
13578 this._player.cancel();
13579 this._player = null;
13580 }
13581
13582 this.fire('neon-animation-finish', cookie, {bubbles: false});
13583 }.bind(this);
13584
13585 } else {
13586 this.fire('neon-animation-finish', cookie, {bubbles: false});
13587 }
13588 },
13589
13590 /**
13591 * Cancels the currently running animation.
13592 */
13593 cancelAnimation: function() {
13594 if (this._player) {
13595 this._player.cancel();
13596 }
13597 }
13598 };
13599
13600 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */
13601 Polymer.NeonAnimationRunnerBehavior = [
13602 Polymer.NeonAnimatableBehavior,
13603 Polymer.NeonAnimationRunnerBehaviorImpl
13604 ];
13605 (function() {
13606 'use strict';
13607
13608 /**
13609 * The IronDropdownScrollManager is intended to provide a central source
13610 * of authority and control over which elements in a document are currently
13611 * allowed to scroll.
13612 */
13613
13614 Polymer.IronDropdownScrollManager = {
13615
13616 /**
13617 * The current element that defines the DOM boundaries of the
13618 * scroll lock. This is always the most recently locking element.
13619 */
13620 get currentLockingElement() {
13621 return this._lockingElements[this._lockingElements.length - 1];
13622 },
13623
13624
13625 /**
13626 * Returns true if the provided element is "scroll locked," which is to
13627 * say that it cannot be scrolled via pointer or keyboard interactions.
13628 *
13629 * @param {HTMLElement} element An HTML element instance which may or may
13630 * not be scroll locked.
13631 */
13632 elementIsScrollLocked: function(element) {
13633 var currentLockingElement = this.currentLockingElement;
13634 var scrollLocked;
13635
13636 if (this._hasCachedLockedElement(element)) {
13637 return true;
13638 }
13639
13640 if (this._hasCachedUnlockedElement(element)) {
13641 return false;
13642 }
13643
13644 scrollLocked = !!currentLockingElement &&
13645 currentLockingElement !== element &&
13646 !this._composedTreeContains(currentLockingElement, element);
13647
13648 if (scrollLocked) {
13649 this._lockedElementCache.push(element);
13650 } else {
13651 this._unlockedElementCache.push(element);
13652 }
13653
13654 return scrollLocked;
13655 },
13656
13657 /**
13658 * Push an element onto the current scroll lock stack. The most recently
13659 * pushed element and its children will be considered scrollable. All
13660 * other elements will not be scrollable.
13661 *
13662 * Scroll locking is implemented as a stack so that cases such as
13663 * dropdowns within dropdowns are handled well.
13664 *
13665 * @param {HTMLElement} element The element that should lock scroll.
13666 */
13667 pushScrollLock: function(element) {
13668 if (this._lockingElements.length === 0) {
13669 this._lockScrollInteractions();
13670 }
13671
13672 this._lockingElements.push(element);
13673
13674 this._lockedElementCache = [];
13675 this._unlockedElementCache = [];
13676 },
13677
13678 /**
13679 * Remove an element from the scroll lock stack. The element being
13680 * removed does not need to be the most recently pushed element. However,
13681 * the scroll lock constraints only change when the most recently pushed
13682 * element is removed.
13683 *
13684 * @param {HTMLElement} element The element to remove from the scroll
13685 * lock stack.
13686 */
13687 removeScrollLock: function(element) {
13688 var index = this._lockingElements.indexOf(element);
13689
13690 if (index === -1) {
13691 return;
13692 }
13693
13694 this._lockingElements.splice(index, 1);
13695
13696 this._lockedElementCache = [];
13697 this._unlockedElementCache = [];
13698
13699 if (this._lockingElements.length === 0) {
13700 this._unlockScrollInteractions();
13701 }
13702 },
13703
13704 _lockingElements: [],
13705
13706 _lockedElementCache: null,
13707
13708 _unlockedElementCache: null,
13709
13710 _originalBodyStyles: {},
13711
13712 _isScrollingKeypress: function(event) {
13713 return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(
13714 event, 'pageup pagedown home end up left down right');
13715 },
13716
13717 _hasCachedLockedElement: function(element) {
13718 return this._lockedElementCache.indexOf(element) > -1;
13719 },
13720
13721 _hasCachedUnlockedElement: function(element) {
13722 return this._unlockedElementCache.indexOf(element) > -1;
13723 },
13724
13725 _composedTreeContains: function(element, child) {
13726 // NOTE(cdata): This method iterates over content elements and their
13727 // corresponding distributed nodes to implement a contains-like method
13728 // that pierces through the composed tree of the ShadowDOM. Results of
13729 // this operation are cached (elsewhere) on a per-scroll-lock basis, to
13730 // guard against potentially expensive lookups happening repeatedly as
13731 // a user scrolls / touchmoves.
13732 var contentElements;
13733 var distributedNodes;
13734 var contentIndex;
13735 var nodeIndex;
13736
13737 if (element.contains(child)) {
13738 return true;
13739 }
13740
13741 contentElements = Polymer.dom(element).querySelectorAll('content');
13742
13743 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI ndex) {
13744
13745 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr ibutedNodes();
13746
13747 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
13748
13749 if (this._composedTreeContains(distributedNodes[nodeIndex], child)) {
13750 return true;
13751 }
13752 }
13753 }
13754
13755 return false;
13756 },
13757
13758 _scrollInteractionHandler: function(event) {
13759 if (Polymer
13760 .IronDropdownScrollManager
13761 .elementIsScrollLocked(event.target)) {
13762 if (event.type === 'keydown' &&
13763 !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) {
13764 return;
13765 }
13766
13767 event.preventDefault();
13768 }
13769 },
13770
13771 _lockScrollInteractions: function() {
13772 // Memoize body inline styles:
13773 this._originalBodyStyles.overflow = document.body.style.overflow;
13774 this._originalBodyStyles.overflowX = document.body.style.overflowX;
13775 this._originalBodyStyles.overflowY = document.body.style.overflowY;
13776
13777 // Disable overflow scrolling on body:
13778 // TODO(cdata): It is technically not sufficient to hide overflow on
13779 // body alone. A better solution might be to traverse all ancestors of
13780 // the current scroll locking element and hide overflow on them. This
13781 // becomes expensive, though, as it would have to be redone every time
13782 // a new scroll locking element is added.
13783 document.body.style.overflow = 'hidden';
13784 document.body.style.overflowX = 'hidden';
13785 document.body.style.overflowY = 'hidden';
13786
13787 // Modern `wheel` event for mouse wheel scrolling:
13788 window.addEventListener('wheel', this._scrollInteractionHandler, true);
13789 // Older, non-standard `mousewheel` event for some FF:
13790 window.addEventListener('mousewheel', this._scrollInteractionHandler, tr ue);
13791 // IE:
13792 window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler , true);
13793 // Mobile devices can scroll on touch move:
13794 window.addEventListener('touchmove', this._scrollInteractionHandler, tru e);
13795 // Capture keydown to prevent scrolling keys (pageup, pagedown etc.)
13796 document.addEventListener('keydown', this._scrollInteractionHandler, tru e);
13797 },
13798
13799 _unlockScrollInteractions: function() {
13800 document.body.style.overflow = this._originalBodyStyles.overflow;
13801 document.body.style.overflowX = this._originalBodyStyles.overflowX;
13802 document.body.style.overflowY = this._originalBodyStyles.overflowY;
13803
13804 window.removeEventListener('wheel', this._scrollInteractionHandler, true );
13805 window.removeEventListener('mousewheel', this._scrollInteractionHandler, true);
13806 window.removeEventListener('DOMMouseScroll', this._scrollInteractionHand ler, true);
13807 window.removeEventListener('touchmove', this._scrollInteractionHandler, true);
13808 document.removeEventListener('keydown', this._scrollInteractionHandler, true);
13809 }
13810 };
13811 })();
13812 (function() {
13813 'use strict';
13814
13815 Polymer({
13816 is: 'iron-dropdown',
13817
13818 behaviors: [
13819 Polymer.IronControlState,
13820 Polymer.IronA11yKeysBehavior,
13821 Polymer.IronOverlayBehavior,
13822 Polymer.NeonAnimationRunnerBehavior
13823 ],
13824
13825 properties: {
13826 /**
13827 * The orientation against which to align the dropdown content
13828 * horizontally relative to the dropdown trigger.
13829 */
13830 horizontalAlign: {
13831 type: String,
13832 value: 'left',
13833 reflectToAttribute: true
13834 },
13835
13836 /**
13837 * The orientation against which to align the dropdown content
13838 * vertically relative to the dropdown trigger.
13839 */
13840 verticalAlign: {
13841 type: String,
13842 value: 'top',
13843 reflectToAttribute: true
13844 },
13845
13846 /**
13847 * A pixel value that will be added to the position calculated for the
13848 * given `horizontalAlign`. Use a negative value to offset to the
13849 * left, or a positive value to offset to the right.
13850 */
13851 horizontalOffset: {
13852 type: Number,
13853 value: 0,
13854 notify: true
13855 },
13856
13857 /**
13858 * A pixel value that will be added to the position calculated for the
13859 * given `verticalAlign`. Use a negative value to offset towards the
13860 * top, or a positive value to offset towards the bottom.
13861 */
13862 verticalOffset: {
13863 type: Number,
13864 value: 0,
13865 notify: true
13866 },
13867
13868 /**
13869 * The element that should be used to position the dropdown when
13870 * it is opened.
13871 */
13872 positionTarget: {
13873 type: Object,
13874 observer: '_positionTargetChanged'
13875 },
13876
13877 /**
13878 * An animation config. If provided, this will be used to animate the
13879 * opening of the dropdown.
13880 */
13881 openAnimationConfig: {
13882 type: Object
13883 },
13884
13885 /**
13886 * An animation config. If provided, this will be used to animate the
13887 * closing of the dropdown.
13888 */
13889 closeAnimationConfig: {
13890 type: Object
13891 },
13892
13893 /**
13894 * If provided, this will be the element that will be focused when
13895 * the dropdown opens.
13896 */
13897 focusTarget: {
13898 type: Object
13899 },
13900
13901 /**
13902 * Set to true to disable animations when opening and closing the
13903 * dropdown.
13904 */
13905 noAnimations: {
13906 type: Boolean,
13907 value: false
13908 },
13909
13910 /**
13911 * By default, the dropdown will constrain scrolling on the page
13912 * to itself when opened.
13913 * Set to true in order to prevent scroll from being constrained
13914 * to the dropdown when it opens.
13915 */
13916 allowOutsideScroll: {
13917 type: Boolean,
13918 value: false
13919 },
13920
13921 /**
13922 * We memoize the positionTarget bounding rectangle so that we can
13923 * limit the number of times it is queried per resize / relayout.
13924 * @type {?Object}
13925 */
13926 _positionRectMemo: {
13927 type: Object
13928 }
13929 },
13930
13931 listeners: {
13932 'neon-animation-finish': '_onNeonAnimationFinish'
13933 },
13934
13935 observers: [
13936 '_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset , horizontalOffset)'
13937 ],
13938
13939 attached: function() {
13940 if (this.positionTarget === undefined) {
13941 this.positionTarget = this._defaultPositionTarget;
13942 }
13943 },
13944
13945 /**
13946 * The element that is contained by the dropdown, if any.
13947 */
13948 get containedElement() {
13949 return Polymer.dom(this.$.content).getDistributedNodes()[0];
13950 },
13951
13952 /**
13953 * The element that should be focused when the dropdown opens.
13954 */
13955 get _focusTarget() {
13956 return this.focusTarget || this.containedElement;
13957 },
13958
13959 /**
13960 * The element that should be used to position the dropdown when
13961 * it opens, if no position target is configured.
13962 */
13963 get _defaultPositionTarget() {
13964 var parent = Polymer.dom(this).parentNode;
13965
13966 if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
13967 parent = parent.host;
13968 }
13969
13970 return parent;
13971 },
13972
13973 /**
13974 * The bounding rect of the position target.
13975 */
13976 get _positionRect() {
13977 if (!this._positionRectMemo && this.positionTarget) {
13978 this._positionRectMemo = this.positionTarget.getBoundingClientRect() ;
13979 }
13980
13981 return this._positionRectMemo;
13982 },
13983
13984 /**
13985 * The horizontal offset value used to position the dropdown.
13986 */
13987 get _horizontalAlignTargetValue() {
13988 var target;
13989
13990 if (this.horizontalAlign === 'right') {
13991 target = document.documentElement.clientWidth - this._positionRect.r ight;
13992 } else {
13993 target = this._positionRect.left;
13994 }
13995
13996 target += this.horizontalOffset;
13997
13998 return Math.max(target, 0);
13999 },
14000
14001 /**
14002 * The vertical offset value used to position the dropdown.
14003 */
14004 get _verticalAlignTargetValue() {
14005 var target;
14006
14007 if (this.verticalAlign === 'bottom') {
14008 target = document.documentElement.clientHeight - this._positionRect. bottom;
14009 } else {
14010 target = this._positionRect.top;
14011 }
14012
14013 target += this.verticalOffset;
14014
14015 return Math.max(target, 0);
14016 },
14017
14018 /**
14019 * Called when the value of `opened` changes.
14020 *
14021 * @param {boolean} opened True if the dropdown is opened.
14022 */
14023 _openedChanged: function(opened) {
14024 if (opened && this.disabled) {
14025 this.cancel();
14026 } else {
14027 this.cancelAnimation();
14028 this._prepareDropdown();
14029 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments );
14030 }
14031
14032 if (this.opened) {
14033 this._focusContent();
14034 }
14035 },
14036
14037 /**
14038 * Overridden from `IronOverlayBehavior`.
14039 */
14040 _renderOpened: function() {
14041 if (!this.allowOutsideScroll) {
14042 Polymer.IronDropdownScrollManager.pushScrollLock(this);
14043 }
14044
14045 if (!this.noAnimations && this.animationConfig && this.animationConfig .open) {
14046 this.$.contentWrapper.classList.add('animating');
14047 this.playAnimation('open');
14048 } else {
14049 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments) ;
14050 }
14051 },
14052
14053 /**
14054 * Overridden from `IronOverlayBehavior`.
14055 */
14056 _renderClosed: function() {
14057 Polymer.IronDropdownScrollManager.removeScrollLock(this);
14058 if (!this.noAnimations && this.animationConfig && this.animationConfig .close) {
14059 this.$.contentWrapper.classList.add('animating');
14060 this.playAnimation('close');
14061 } else {
14062 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments) ;
14063 }
14064 },
14065
14066 /**
14067 * Called when animation finishes on the dropdown (when opening or
14068 * closing). Responsible for "completing" the process of opening or
14069 * closing the dropdown by positioning it or setting its display to
14070 * none.
14071 */
14072 _onNeonAnimationFinish: function() {
14073 this.$.contentWrapper.classList.remove('animating');
14074 if (this.opened) {
14075 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this);
14076 } else {
14077 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this);
14078 }
14079 },
14080
14081 /**
14082 * Called when an `iron-resize` event fires.
14083 */
14084 _onIronResize: function() {
14085 var containedElement = this.containedElement;
14086 var scrollTop;
14087 var scrollLeft;
14088
14089 if (containedElement) {
14090 scrollTop = containedElement.scrollTop;
14091 scrollLeft = containedElement.scrollLeft;
14092 }
14093
14094 if (this.opened) {
14095 this._updateOverlayPosition();
14096 }
14097
14098 Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments);
14099
14100 if (containedElement) {
14101 containedElement.scrollTop = scrollTop;
14102 containedElement.scrollLeft = scrollLeft;
14103 }
14104 },
14105
14106 /**
14107 * Called when the `positionTarget` property changes.
14108 */
14109 _positionTargetChanged: function() {
14110 this._updateOverlayPosition();
14111 },
14112
14113 /**
14114 * Constructs the final animation config from different properties used
14115 * to configure specific parts of the opening and closing animations.
14116 */
14117 _updateAnimationConfig: function() {
14118 var animationConfig = {};
14119 var animations = [];
14120
14121 if (this.openAnimationConfig) {
14122 // NOTE(cdata): When making `display:none` elements visible in Safar i,
14123 // the element will paint once in a fully visible state, causing the
14124 // dropdown to flash before it fades in. We prepend an
14125 // `opaque-animation` to fix this problem:
14126 animationConfig.open = [{
14127 name: 'opaque-animation',
14128 }].concat(this.openAnimationConfig);
14129 animations = animations.concat(animationConfig.open);
14130 }
14131
14132 if (this.closeAnimationConfig) {
14133 animationConfig.close = this.closeAnimationConfig;
14134 animations = animations.concat(animationConfig.close);
14135 }
14136
14137 animations.forEach(function(animation) {
14138 animation.node = this.containedElement;
14139 }, this);
14140
14141 this.animationConfig = animationConfig;
14142 },
14143
14144 /**
14145 * Prepares the dropdown for opening by updating measured layout
14146 * values.
14147 */
14148 _prepareDropdown: function() {
14149 this.sizingTarget = this.containedElement || this.sizingTarget;
14150 this._updateAnimationConfig();
14151 this._updateOverlayPosition();
14152 },
14153
14154 /**
14155 * Updates the overlay position based on configured horizontal
14156 * and vertical alignment, and re-memoizes these values for the sake
14157 * of behavior in `IronFitBehavior`.
14158 */
14159 _updateOverlayPosition: function() {
14160 this._positionRectMemo = null;
14161
14162 if (!this.positionTarget) {
14163 return;
14164 }
14165
14166 this.style[this.horizontalAlign] =
14167 this._horizontalAlignTargetValue + 'px';
14168
14169 this.style[this.verticalAlign] =
14170 this._verticalAlignTargetValue + 'px';
14171
14172 // NOTE(cdata): We re-memoize inline styles here, otherwise
14173 // calling `refit` from `IronFitBehavior` will reset inline styles
14174 // to whatever they were when the dropdown first opened.
14175 if (this._fitInfo) {
14176 this._fitInfo.inlineStyle[this.horizontalAlign] =
14177 this.style[this.horizontalAlign];
14178
14179 this._fitInfo.inlineStyle[this.verticalAlign] =
14180 this.style[this.verticalAlign];
14181 }
14182 },
14183
14184 /**
14185 * Focuses the configured focus target.
14186 */
14187 _focusContent: function() {
14188 // NOTE(cdata): This is async so that it can attempt the focus after
14189 // `display: none` is removed from the element.
14190 this.async(function() {
14191 if (this._focusTarget) {
14192 this._focusTarget.focus();
14193 }
14194 });
14195 }
14196 });
14197 })();
14198 Polymer({
14199
14200 is: 'fade-in-animation',
14201
14202 behaviors: [
14203 Polymer.NeonAnimationBehavior
14204 ],
14205
14206 configure: function(config) {
14207 var node = config.node;
14208 this._effect = new KeyframeEffect(node, [
14209 {'opacity': '0'},
14210 {'opacity': '1'}
14211 ], this.timingFromConfig(config));
14212 return this._effect;
14213 }
14214
14215 });
14216 Polymer({
14217
14218 is: 'fade-out-animation',
14219
14220 behaviors: [
14221 Polymer.NeonAnimationBehavior
14222 ],
14223
14224 configure: function(config) {
14225 var node = config.node;
14226 this._effect = new KeyframeEffect(node, [
14227 {'opacity': '1'},
14228 {'opacity': '0'}
14229 ], this.timingFromConfig(config));
14230 return this._effect;
14231 }
14232
14233 });
14234 Polymer({
14235 is: 'paper-menu-grow-height-animation',
14236
14237 behaviors: [
14238 Polymer.NeonAnimationBehavior
14239 ],
14240
14241 configure: function(config) {
14242 var node = config.node;
14243 var rect = node.getBoundingClientRect();
14244 var height = rect.height;
14245
14246 this._effect = new KeyframeEffect(node, [{
14247 height: (height / 2) + 'px'
14248 }, {
14249 height: height + 'px'
14250 }], this.timingFromConfig(config));
14251
14252 return this._effect;
14253 }
14254 });
14255
14256 Polymer({
14257 is: 'paper-menu-grow-width-animation',
14258
14259 behaviors: [
14260 Polymer.NeonAnimationBehavior
14261 ],
14262
14263 configure: function(config) {
14264 var node = config.node;
14265 var rect = node.getBoundingClientRect();
14266 var width = rect.width;
14267
14268 this._effect = new KeyframeEffect(node, [{
14269 width: (width / 2) + 'px'
14270 }, {
14271 width: width + 'px'
14272 }], this.timingFromConfig(config));
14273
14274 return this._effect;
14275 }
14276 });
14277
14278 Polymer({
14279 is: 'paper-menu-shrink-width-animation',
14280
14281 behaviors: [
14282 Polymer.NeonAnimationBehavior
14283 ],
14284
14285 configure: function(config) {
14286 var node = config.node;
14287 var rect = node.getBoundingClientRect();
14288 var width = rect.width;
14289
14290 this._effect = new KeyframeEffect(node, [{
14291 width: width + 'px'
14292 }, {
14293 width: width - (width / 20) + 'px'
14294 }], this.timingFromConfig(config));
14295
14296 return this._effect;
14297 }
14298 });
14299
14300 Polymer({
14301 is: 'paper-menu-shrink-height-animation',
14302
14303 behaviors: [
14304 Polymer.NeonAnimationBehavior
14305 ],
14306
14307 configure: function(config) {
14308 var node = config.node;
14309 var rect = node.getBoundingClientRect();
14310 var height = rect.height;
14311 var top = rect.top;
14312
14313 this.setPrefixedProperty(node, 'transformOrigin', '0 0');
14314
14315 this._effect = new KeyframeEffect(node, [{
14316 height: height + 'px',
14317 transform: 'translateY(0)'
14318 }, {
14319 height: height / 2 + 'px',
14320 transform: 'translateY(-20px)'
14321 }], this.timingFromConfig(config));
14322
14323 return this._effect;
14324 }
14325 });
14326 (function() {
14327 'use strict';
14328
14329 var PaperMenuButton = Polymer({
14330 is: 'paper-menu-button',
14331
14332 /**
14333 * Fired when the dropdown opens.
14334 *
14335 * @event paper-dropdown-open
14336 */
14337
14338 /**
14339 * Fired when the dropdown closes.
14340 *
14341 * @event paper-dropdown-close
14342 */
14343
14344 behaviors: [
14345 Polymer.IronA11yKeysBehavior,
14346 Polymer.IronControlState
14347 ],
14348
14349 properties: {
14350
14351 /**
14352 * True if the content is currently displayed.
14353 */
14354 opened: {
14355 type: Boolean,
14356 value: false,
14357 notify: true,
14358 observer: '_openedChanged'
14359 },
14360
14361 /**
14362 * The orientation against which to align the menu dropdown
14363 * horizontally relative to the dropdown trigger.
14364 */
14365 horizontalAlign: {
14366 type: String,
14367 value: 'left',
14368 reflectToAttribute: true
14369 },
14370
14371 /**
14372 * The orientation against which to align the menu dropdown
14373 * vertically relative to the dropdown trigger.
14374 */
14375 verticalAlign: {
14376 type: String,
14377 value: 'top',
14378 reflectToAttribute: true
14379 },
14380
14381 /**
14382 * A pixel value that will be added to the position calculated for the
14383 * given `horizontalAlign`. Use a negative value to offset to the
14384 * left, or a positive value to offset to the right.
14385 */
14386 horizontalOffset: {
14387 type: Number,
14388 value: 0,
14389 notify: true
14390 },
14391
14392 /**
14393 * A pixel value that will be added to the position calculated for the
14394 * given `verticalAlign`. Use a negative value to offset towards the
14395 * top, or a positive value to offset towards the bottom.
14396 */
14397 verticalOffset: {
14398 type: Number,
14399 value: 0,
14400 notify: true
14401 },
14402
14403 /**
14404 * Set to true to disable animations when opening and closing the
14405 * dropdown.
14406 */
14407 noAnimations: {
14408 type: Boolean,
14409 value: false
14410 },
14411
14412 /**
14413 * Set to true to disable automatically closing the dropdown after
14414 * a selection has been made.
14415 */
14416 ignoreSelect: {
14417 type: Boolean,
14418 value: false
14419 },
14420
14421 /**
14422 * An animation config. If provided, this will be used to animate the
14423 * opening of the dropdown.
14424 */
14425 openAnimationConfig: {
14426 type: Object,
14427 value: function() {
14428 return [{
14429 name: 'fade-in-animation',
14430 timing: {
14431 delay: 100,
14432 duration: 200
14433 }
14434 }, {
14435 name: 'paper-menu-grow-width-animation',
14436 timing: {
14437 delay: 100,
14438 duration: 150,
14439 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
14440 }
14441 }, {
14442 name: 'paper-menu-grow-height-animation',
14443 timing: {
14444 delay: 100,
14445 duration: 275,
14446 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
14447 }
14448 }];
14449 }
14450 },
14451
14452 /**
14453 * An animation config. If provided, this will be used to animate the
14454 * closing of the dropdown.
14455 */
14456 closeAnimationConfig: {
14457 type: Object,
14458 value: function() {
14459 return [{
14460 name: 'fade-out-animation',
14461 timing: {
14462 duration: 150
14463 }
14464 }, {
14465 name: 'paper-menu-shrink-width-animation',
14466 timing: {
14467 delay: 100,
14468 duration: 50,
14469 easing: PaperMenuButton.ANIMATION_CUBIC_BEZIER
14470 }
14471 }, {
14472 name: 'paper-menu-shrink-height-animation',
14473 timing: {
14474 duration: 200,
14475 easing: 'ease-in'
14476 }
14477 }];
14478 }
14479 },
14480
14481 /**
14482 * This is the element intended to be bound as the focus target
14483 * for the `iron-dropdown` contained by `paper-menu-button`.
14484 */
14485 _dropdownContent: {
14486 type: Object
14487 }
14488 },
14489
14490 hostAttributes: {
14491 role: 'group',
14492 'aria-haspopup': 'true'
14493 },
14494
14495 listeners: {
14496 'iron-select': '_onIronSelect'
14497 },
14498
14499 /**
14500 * The content element that is contained by the menu button, if any.
14501 */
14502 get contentElement() {
14503 return Polymer.dom(this.$.content).getDistributedNodes()[0];
14504 },
14505
14506 /**
14507 * Make the dropdown content appear as an overlay positioned relative
14508 * to the dropdown trigger.
14509 */
14510 open: function() {
14511 if (this.disabled) {
14512 return;
14513 }
14514
14515 this.$.dropdown.open();
14516 },
14517
14518 /**
14519 * Hide the dropdown content.
14520 */
14521 close: function() {
14522 this.$.dropdown.close();
14523 },
14524
14525 /**
14526 * When an `iron-select` event is received, the dropdown should
14527 * automatically close on the assumption that a value has been chosen.
14528 *
14529 * @param {CustomEvent} event A CustomEvent instance with type
14530 * set to `"iron-select"`.
14531 */
14532 _onIronSelect: function(event) {
14533 if (!this.ignoreSelect) {
14534 this.close();
14535 }
14536 },
14537
14538 /**
14539 * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
14540 * When the dropdown closes, the `paper-menu-button` fires `paper-close`.
14541 *
14542 * @param {boolean} opened True if the dropdown is opened, otherwise false .
14543 * @param {boolean} oldOpened The previous value of `opened`.
14544 */
14545 _openedChanged: function(opened, oldOpened) {
14546 if (opened) {
14547 // TODO(cdata): Update this when we can measure changes in distributed
14548 // children in an idiomatic way.
14549 // We poke this property in case the element has changed. This will
14550 // cause the focus target for the `iron-dropdown` to be updated as
14551 // necessary:
14552 this._dropdownContent = this.contentElement;
14553 this.fire('paper-dropdown-open');
14554 } else if (oldOpened != null) {
14555 this.fire('paper-dropdown-close');
14556 }
14557 },
14558
14559 /**
14560 * If the dropdown is open when disabled becomes true, close the
14561 * dropdown.
14562 *
14563 * @param {boolean} disabled True if disabled, otherwise false.
14564 */
14565 _disabledChanged: function(disabled) {
14566 Polymer.IronControlState._disabledChanged.apply(this, arguments);
14567 if (disabled && this.opened) {
14568 this.close();
14569 }
14570 }
14571 });
14572
14573 PaperMenuButton.ANIMATION_CUBIC_BEZIER = 'cubic-bezier(.3,.95,.5,1)';
14574 PaperMenuButton.MAX_ANIMATION_TIME_MS = 400;
14575
14576 Polymer.PaperMenuButton = PaperMenuButton;
14577 })();
14578 /**
14579 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k eyboard focus.
14580 *
14581 * @polymerBehavior Polymer.PaperInkyFocusBehavior
14582 */
14583 Polymer.PaperInkyFocusBehaviorImpl = {
14584
14585 observers: [
14586 '_focusedChanged(receivedFocusFromKeyboard)'
14587 ],
14588
14589 _focusedChanged: function(receivedFocusFromKeyboard) {
14590 if (!this.$.ink) {
14591 return;
14592 }
14593
14594 this.$.ink.holdDown = receivedFocusFromKeyboard;
14595 }
14596
14597 };
14598
14599 /** @polymerBehavior Polymer.PaperInkyFocusBehavior */
14600 Polymer.PaperInkyFocusBehavior = [
14601 Polymer.IronButtonState,
14602 Polymer.IronControlState,
14603 Polymer.PaperInkyFocusBehaviorImpl
14604 ];
14605 Polymer({
14606 is: 'paper-icon-button',
14607
14608 hostAttributes: {
14609 role: 'button',
14610 tabindex: '0'
14611 },
14612
14613 behaviors: [
14614 Polymer.PaperInkyFocusBehavior
14615 ],
14616
14617 properties: {
14618 /**
14619 * The URL of an image for the icon. If the src property is specified,
14620 * the icon property should not be.
14621 */
14622 src: {
14623 type: String
14624 },
14625
14626 /**
14627 * Specifies the icon name or index in the set of icons available in
14628 * the icon's icon set. If the icon property is specified,
14629 * the src property should not be.
14630 */
14631 icon: {
14632 type: String
14633 },
14634
14635 /**
14636 * Specifies the alternate text for the button, for accessibility.
14637 */
14638 alt: {
14639 type: String,
14640 observer: "_altChanged"
14641 }
14642 },
14643
14644 _altChanged: function(newValue, oldValue) {
14645 var label = this.getAttribute('aria-label');
14646
14647 // Don't stomp over a user-set aria-label.
14648 if (!label || oldValue == label) {
14649 this.setAttribute('aria-label', newValue);
14650 }
14651 }
14652 });
14653 /**
14654 * Use `Polymer.IronValidatableBehavior` to implement an element that validate s user input.
14655 *
14656 * ### Accessibility
14657 *
14658 * Changing the `invalid` property, either manually or by calling `validate()` will update the
14659 * `aria-invalid` attribute.
14660 *
14661 * @demo demo/index.html
14662 * @polymerBehavior
14663 */
14664 Polymer.IronValidatableBehavior = {
14665
14666 properties: {
14667
14668 /**
14669 * Namespace for this validator.
14670 */
14671 validatorType: {
14672 type: String,
14673 value: 'validator'
14674 },
14675
14676 /**
14677 * Name of the validator to use.
14678 */
14679 validator: {
14680 type: String
14681 },
14682
14683 /**
14684 * True if the last call to `validate` is invalid.
14685 */
14686 invalid: {
14687 notify: true,
14688 reflectToAttribute: true,
14689 type: Boolean,
14690 value: false
14691 },
14692
14693 _validatorMeta: {
14694 type: Object
14695 }
14696
14697 },
14698
14699 observers: [
14700 '_invalidChanged(invalid)'
14701 ],
14702
14703 get _validator() {
14704 return this._validatorMeta && this._validatorMeta.byKey(this.validator);
14705 },
14706
14707 ready: function() {
14708 this._validatorMeta = new Polymer.IronMeta({type: this.validatorType});
14709 },
14710
14711 _invalidChanged: function() {
14712 if (this.invalid) {
14713 this.setAttribute('aria-invalid', 'true');
14714 } else {
14715 this.removeAttribute('aria-invalid');
14716 }
14717 },
14718
14719 /**
14720 * @return {boolean} True if the validator `validator` exists.
14721 */
14722 hasValidator: function() {
14723 return this._validator != null;
14724 },
14725
14726 /**
14727 * Returns true if the `value` is valid, and updates `invalid`. If you want
14728 * your element to have custom validation logic, do not override this method ;
14729 * override `_getValidity(value)` instead.
14730
14731 * @param {Object} value The value to be validated. By default, it is passed
14732 * to the validator's `validate()` function, if a validator is set.
14733 * @return {boolean} True if `value` is valid.
14734 */
14735 validate: function(value) {
14736 this.invalid = !this._getValidity(value);
14737 return !this.invalid;
14738 },
14739
14740 /**
14741 * Returns true if `value` is valid. By default, it is passed
14742 * to the validator's `validate()` function, if a validator is set. You
14743 * should override this method if you want to implement custom validity
14744 * logic for your element.
14745 *
14746 * @param {Object} value The value to be validated.
14747 * @return {boolean} True if `value` is valid.
14748 */
14749
14750 _getValidity: function(value) {
14751 if (this.hasValidator()) {
14752 return this._validator.validate(value);
14753 }
14754 return true;
14755 }
14756 };
14757 /*
14758 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal idatorBehavior`
14759 to `<input>`.
14760
14761 ### Two-way binding
14762
14763 By default you can only get notified of changes to an `input`'s `value` due to u ser input:
14764
14765 <input value="{{myValue::input}}">
14766
14767 `iron-input` adds the `bind-value` property that mirrors the `value` property, a nd can be used
14768 for two-way data binding. `bind-value` will notify if it is changed either by us er input or by script.
14769
14770 <input is="iron-input" bind-value="{{myValue}}">
14771
14772 ### Custom validators
14773
14774 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit h `<iron-input>`.
14775
14776 <input is="iron-input" validator="my-custom-validator">
14777
14778 ### Stopping invalid input
14779
14780 It may be desirable to only allow users to enter certain characters. You can use the
14781 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature
14782 is separate from validation, and `allowed-pattern` does not affect how the input is validated.
14783
14784 <!-- only allow characters that match [0-9] -->
14785 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">
14786
14787 @hero hero.svg
14788 @demo demo/index.html
14789 */
14790
14791 Polymer({
14792
14793 is: 'iron-input',
14794
14795 extends: 'input',
14796
14797 behaviors: [
14798 Polymer.IronValidatableBehavior
14799 ],
14800
14801 properties: {
14802
14803 /**
14804 * Use this property instead of `value` for two-way data binding.
14805 */
14806 bindValue: {
14807 observer: '_bindValueChanged',
14808 type: String
14809 },
14810
14811 /**
14812 * Set to true to prevent the user from entering invalid input. The new in put characters are
14813 * matched with `allowedPattern` if it is set, otherwise it will use the ` pattern` attribute if
14814 * set, or the `type` attribute (only supported for `type=number`).
14815 */
14816 preventInvalidInput: {
14817 type: Boolean
14818 },
14819
14820 /**
14821 * Regular expression to match valid input characters.
14822 */
14823 allowedPattern: {
14824 type: String
14825 },
14826
14827 _previousValidInput: {
14828 type: String,
14829 value: ''
14830 },
14831
14832 _patternAlreadyChecked: {
14833 type: Boolean,
14834 value: false
14835 }
14836
14837 },
14838
14839 listeners: {
14840 'input': '_onInput',
14841 'keypress': '_onKeypress'
14842 },
14843
14844 get _patternRegExp() {
14845 var pattern;
14846 if (this.allowedPattern) {
14847 pattern = new RegExp(this.allowedPattern);
14848 } else if (this.pattern) {
14849 pattern = new RegExp(this.pattern);
14850 } else {
14851 switch (this.type) {
14852 case 'number':
14853 pattern = /[0-9.,e-]/;
14854 break;
14855 }
14856 }
14857 return pattern;
14858 },
14859
14860 ready: function() {
14861 this.bindValue = this.value;
14862 },
14863
14864 /**
14865 * @suppress {checkTypes}
14866 */
14867 _bindValueChanged: function() {
14868 if (this.value !== this.bindValue) {
14869 this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindV alue;
14870 }
14871 // manually notify because we don't want to notify until after setting val ue
14872 this.fire('bind-value-changed', {value: this.bindValue});
14873 },
14874
14875 _onInput: function() {
14876 // Need to validate each of the characters pasted if they haven't
14877 // been validated inside `_onKeypress` already.
14878 if (this.preventInvalidInput && !this._patternAlreadyChecked) {
14879 var valid = this._checkPatternValidity();
14880 if (!valid) {
14881 this.value = this._previousValidInput;
14882 }
14883 }
14884
14885 this.bindValue = this.value;
14886 this._previousValidInput = this.value;
14887 this._patternAlreadyChecked = false;
14888 },
14889
14890 _isPrintable: function(event) {
14891 // What a control/printable character is varies wildly based on the browse r.
14892 // - most control characters (arrows, backspace) do not send a `keypress` event
14893 // in Chrome, but the *do* on Firefox
14894 // - in Firefox, when they do send a `keypress` event, control chars have
14895 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
14896 // - printable characters always send a keypress event.
14897 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
14898 // always matches the charCode.
14899 // None of this makes any sense.
14900
14901 // For these keys, ASCII code == browser keycode.
14902 var anyNonPrintable =
14903 (event.keyCode == 8) || // backspace
14904 (event.keyCode == 9) || // tab
14905 (event.keyCode == 13) || // enter
14906 (event.keyCode == 27); // escape
14907
14908 // For these keys, make sure it's a browser keycode and not an ASCII code.
14909 var mozNonPrintable =
14910 (event.keyCode == 19) || // pause
14911 (event.keyCode == 20) || // caps lock
14912 (event.keyCode == 45) || // insert
14913 (event.keyCode == 46) || // delete
14914 (event.keyCode == 144) || // num lock
14915 (event.keyCode == 145) || // scroll lock
14916 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho me, arrows
14917 (event.keyCode > 111 && event.keyCode < 124); // fn keys
14918
14919 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
14920 },
14921
14922 _onKeypress: function(event) {
14923 if (!this.preventInvalidInput && this.type !== 'number') {
14924 return;
14925 }
14926 var regexp = this._patternRegExp;
14927 if (!regexp) {
14928 return;
14929 }
14930
14931 // Handle special keys and backspace
14932 if (event.metaKey || event.ctrlKey || event.altKey)
14933 return;
14934
14935 // Check the pattern either here or in `_onInput`, but not in both.
14936 this._patternAlreadyChecked = true;
14937
14938 var thisChar = String.fromCharCode(event.charCode);
14939 if (this._isPrintable(event) && !regexp.test(thisChar)) {
14940 event.preventDefault();
14941 }
14942 },
14943
14944 _checkPatternValidity: function() {
14945 var regexp = this._patternRegExp;
14946 if (!regexp) {
14947 return true;
14948 }
14949 for (var i = 0; i < this.value.length; i++) {
14950 if (!regexp.test(this.value[i])) {
14951 return false;
14952 }
14953 }
14954 return true;
14955 },
14956
14957 /**
14958 * Returns true if `value` is valid. The validator provided in `validator` w ill be used first,
14959 * then any constraints.
14960 * @return {boolean} True if the value is valid.
14961 */
14962 validate: function() {
14963 // Empty, non-required input is valid.
14964 if (!this.required && this.value == '') {
14965 this.invalid = false;
14966 return true;
14967 }
14968
14969 var valid;
14970 if (this.hasValidator()) {
14971 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
14972 } else {
14973 this.invalid = !this.validity.valid;
14974 valid = this.validity.valid;
14975 }
14976 this.fire('iron-input-validate');
14977 return valid;
14978 }
14979
14980 });
14981
14982 /*
14983 The `iron-input-validate` event is fired whenever `validate()` is called.
14984 @event iron-input-validate
14985 */
14986 Polymer({
14987 is: 'paper-input-container',
14988
14989 properties: {
14990 /**
14991 * Set to true to disable the floating label. The label disappears when th e input value is
14992 * not null.
14993 */
14994 noLabelFloat: {
14995 type: Boolean,
14996 value: false
14997 },
14998
14999 /**
15000 * Set to true to always float the floating label.
15001 */
15002 alwaysFloatLabel: {
15003 type: Boolean,
15004 value: false
15005 },
15006
15007 /**
15008 * The attribute to listen for value changes on.
15009 */
15010 attrForValue: {
15011 type: String,
15012 value: 'bind-value'
15013 },
15014
15015 /**
15016 * Set to true to auto-validate the input value when it changes.
15017 */
15018 autoValidate: {
15019 type: Boolean,
15020 value: false
15021 },
15022
15023 /**
15024 * True if the input is invalid. This property is set automatically when t he input value
15025 * changes if auto-validating, or when the `iron-input-validate` event is heard from a child.
15026 */
15027 invalid: {
15028 observer: '_invalidChanged',
15029 type: Boolean,
15030 value: false
15031 },
15032
15033 /**
15034 * True if the input has focus.
15035 */
15036 focused: {
15037 readOnly: true,
15038 type: Boolean,
15039 value: false,
15040 notify: true
15041 },
15042
15043 _addons: {
15044 type: Array
15045 // do not set a default value here intentionally - it will be initialize d lazily when a
15046 // distributed child is attached, which may occur before configuration f or this element
15047 // in polyfill.
15048 },
15049
15050 _inputHasContent: {
15051 type: Boolean,
15052 value: false
15053 },
15054
15055 _inputSelector: {
15056 type: String,
15057 value: 'input,textarea,.paper-input-input'
15058 },
15059
15060 _boundOnFocus: {
15061 type: Function,
15062 value: function() {
15063 return this._onFocus.bind(this);
15064 }
15065 },
15066
15067 _boundOnBlur: {
15068 type: Function,
15069 value: function() {
15070 return this._onBlur.bind(this);
15071 }
15072 },
15073
15074 _boundOnInput: {
15075 type: Function,
15076 value: function() {
15077 return this._onInput.bind(this);
15078 }
15079 },
15080
15081 _boundValueChanged: {
15082 type: Function,
15083 value: function() {
15084 return this._onValueChanged.bind(this);
15085 }
15086 }
15087 },
15088
15089 listeners: {
15090 'addon-attached': '_onAddonAttached',
15091 'iron-input-validate': '_onIronInputValidate'
15092 },
15093
15094 get _valueChangedEvent() {
15095 return this.attrForValue + '-changed';
15096 },
15097
15098 get _propertyForValue() {
15099 return Polymer.CaseMap.dashToCamelCase(this.attrForValue);
15100 },
15101
15102 get _inputElement() {
15103 return Polymer.dom(this).querySelector(this._inputSelector);
15104 },
15105
15106 get _inputElementValue() {
15107 return this._inputElement[this._propertyForValue] || this._inputElement.va lue;
15108 },
15109
15110 ready: function() {
15111 if (!this._addons) {
15112 this._addons = [];
15113 }
15114 this.addEventListener('focus', this._boundOnFocus, true);
15115 this.addEventListener('blur', this._boundOnBlur, true);
15116 if (this.attrForValue) {
15117 this._inputElement.addEventListener(this._valueChangedEvent, this._bound ValueChanged);
15118 } else {
15119 this.addEventListener('input', this._onInput);
15120 }
15121 },
15122
15123 attached: function() {
15124 // Only validate when attached if the input already has a value.
15125 if (this._inputElementValue != '') {
15126 this._handleValueAndAutoValidate(this._inputElement);
15127 } else {
15128 this._handleValue(this._inputElement);
15129 }
15130 },
15131
15132 _onAddonAttached: function(event) {
15133 if (!this._addons) {
15134 this._addons = [];
15135 }
15136 var target = event.target;
15137 if (this._addons.indexOf(target) === -1) {
15138 this._addons.push(target);
15139 if (this.isAttached) {
15140 this._handleValue(this._inputElement);
15141 }
15142 }
15143 },
15144
15145 _onFocus: function() {
15146 this._setFocused(true);
15147 },
15148
15149 _onBlur: function() {
15150 this._setFocused(false);
15151 this._handleValueAndAutoValidate(this._inputElement);
15152 },
15153
15154 _onInput: function(event) {
15155 this._handleValueAndAutoValidate(event.target);
15156 },
15157
15158 _onValueChanged: function(event) {
15159 this._handleValueAndAutoValidate(event.target);
15160 },
15161
15162 _handleValue: function(inputElement) {
15163 var value = this._inputElementValue;
15164
15165 // type="number" hack needed because this.value is empty until it's valid
15166 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme nt.checkValidity())) {
15167 this._inputHasContent = true;
15168 } else {
15169 this._inputHasContent = false;
15170 }
15171
15172 this.updateAddons({
15173 inputElement: inputElement,
15174 value: value,
15175 invalid: this.invalid
15176 });
15177 },
15178
15179 _handleValueAndAutoValidate: function(inputElement) {
15180 if (this.autoValidate) {
15181 var valid;
15182 if (inputElement.validate) {
15183 valid = inputElement.validate(this._inputElementValue);
15184 } else {
15185 valid = inputElement.checkValidity();
15186 }
15187 this.invalid = !valid;
15188 }
15189
15190 // Call this last to notify the add-ons.
15191 this._handleValue(inputElement);
15192 },
15193
15194 _onIronInputValidate: function(event) {
15195 this.invalid = this._inputElement.invalid;
15196 },
15197
15198 _invalidChanged: function() {
15199 if (this._addons) {
15200 this.updateAddons({invalid: this.invalid});
15201 }
15202 },
15203
15204 /**
15205 * Call this to update the state of add-ons.
15206 * @param {Object} state Add-on state.
15207 */
15208 updateAddons: function(state) {
15209 for (var addon, index = 0; addon = this._addons[index]; index++) {
15210 addon.update(state);
15211 }
15212 },
15213
15214 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) {
15215 var cls = 'input-content';
15216 if (!noLabelFloat) {
15217 var label = this.querySelector('label');
15218
15219 if (alwaysFloatLabel || _inputHasContent) {
15220 cls += ' label-is-floating';
15221 if (invalid) {
15222 cls += ' is-invalid';
15223 } else if (focused) {
15224 cls += " label-is-highlighted";
15225 }
15226 // The label might have a horizontal offset if a prefix element exists
15227 // which needs to be undone when displayed as a floating label.
15228 if (Polymer.dom(this.$.prefix).getDistributedNodes().length > 0 &&
15229 label && label.offsetParent) {
15230 label.style.left = -label.offsetParent.offsetLeft + 'px';
15231 }
15232 } else {
15233 // When the label is not floating, it should overlap the input element .
15234 if (label) {
15235 label.style.left = 0;
15236 }
15237 }
15238 } else {
15239 if (_inputHasContent) {
15240 cls += ' label-is-hidden';
15241 }
15242 }
15243 return cls;
15244 },
15245
15246 _computeUnderlineClass: function(focused, invalid) {
15247 var cls = 'underline';
15248 if (invalid) {
15249 cls += ' is-invalid';
15250 } else if (focused) {
15251 cls += ' is-highlighted'
15252 }
15253 return cls;
15254 },
15255
15256 _computeAddOnContentClass: function(focused, invalid) {
15257 var cls = 'add-on-content';
15258 if (invalid) {
15259 cls += ' is-invalid';
15260 } else if (focused) {
15261 cls += ' is-highlighted'
15262 }
15263 return cls;
15264 }
15265 });
15266 // Copyright 2015 The Chromium Authors. All rights reserved.
15267 // Use of this source code is governed by a BSD-style license that can be
15268 // found in the LICENSE file.
15269
15270 /** @interface */
15271 var SearchFieldDelegate = function() {};
15272
15273 SearchFieldDelegate.prototype = {
15274 /**
15275 * @param {string} value
15276 */
15277 onSearchTermSearch: assertNotReached,
15278 };
15279
15280 var SearchField = Polymer({
15281 is: 'cr-search-field',
15282
15283 properties: {
15284 label: {
15285 type: String,
15286 value: '',
15287 },
15288
15289 clearLabel: {
15290 type: String,
15291 value: '',
15292 },
15293
15294 showingSearch_: {
15295 type: Boolean,
15296 value: false,
15297 },
15298 },
15299
15300 /** @param {SearchFieldDelegate} delegate */
15301 setDelegate: function(delegate) {
15302 this.delegate_ = delegate;
15303 },
15304
15305 /**
15306 * Returns the value of the search field.
15307 * @return {string}
15308 */
15309 getValue: function() {
15310 var searchInput = this.$$('#search-input');
15311 return searchInput ? searchInput.value : '';
15312 },
15313
15314 /** @private */
15315 onSearchTermSearch_: function() {
15316 if (this.delegate_)
15317 this.delegate_.onSearchTermSearch(this.getValue());
15318 },
15319
15320 /** @private */
15321 onSearchTermKeydown_: function(e) {
15322 assert(this.showingSearch_);
15323 if (e.keyIdentifier == 'U+001B') // Escape.
15324 this.toggleShowingSearch_();
15325 },
15326
15327 /** @private */
15328 toggleShowingSearch_: function() {
15329 this.showingSearch_ = !this.showingSearch_;
15330 this.async(function() {
15331 var searchInput = this.$$('#search-input');
15332 if (this.showingSearch_) {
15333 searchInput.focus();
15334 } else {
15335 searchInput.value = '';
15336 this.onSearchTermSearch_();
15337 }
15338 });
15339 },
15340 });
15341 // Copyright 2015 The Chromium Authors. All rights reserved.
15342 // Use of this source code is governed by a BSD-style license that can be
15343 // found in the LICENSE file.
15344
15345 cr.define('downloads', function() {
15346 var Toolbar = Polymer({
15347 is: 'downloads-toolbar',
15348
15349 /** @param {!downloads.ActionService} actionService */
15350 setActionService: function(actionService) {
15351 /** @private {!downloads.ActionService} */
15352 this.actionService_ = actionService;
15353 },
15354
15355 attached: function() {
15356 /** @private {!SearchFieldDelegate} */
15357 this.searchFieldDelegate_ = new ToolbarSearchFieldDelegate(this);
15358 this.$['search-input'].setDelegate(this.searchFieldDelegate_);
15359 },
15360
15361 properties: {
15362 downloadsShowing: {
15363 reflectToAttribute: true,
15364 type: Boolean,
15365 value: false,
15366 observer: 'onDownloadsShowingChange_',
15367 },
15368 },
15369
15370 /** @return {boolean} Whether removal can be undone. */
15371 canUndo: function() {
15372 return this.$['search-input'] != this.shadowRoot.activeElement;
15373 },
15374
15375 /** @return {boolean} Whether "Clear all" should be allowed. */
15376 canClearAll: function() {
15377 return !this.$['search-input'].getValue() && this.downloadsShowing;
15378 },
15379
15380 /** @private */
15381 onClearAllClick_: function() {
15382 assert(this.canClearAll());
15383 this.actionService_.clearAll();
15384 },
15385
15386 /** @private */
15387 onDownloadsShowingChange_: function() {
15388 this.updateClearAll_();
15389 },
15390
15391 /** @param {string} searchTerm */
15392 onSearchTermSearch: function(searchTerm) {
15393 this.actionService_.search(searchTerm);
15394 this.updateClearAll_();
15395 },
15396
15397 /** @private */
15398 onOpenDownloadsFolderClick_: function() {
15399 this.actionService_.openDownloadsFolder();
15400 },
15401
15402 /** @private */
15403 updateClearAll_: function() {
15404 this.$$('#actions .clear-all').hidden = !this.canClearAll();
15405 this.$$('paper-menu .clear-all').hidden = !this.canClearAll();
15406 },
15407 });
15408
15409 /**
15410 * @constructor
15411 * @implements {SearchFieldDelegate}
15412 */
15413 // TODO(devlin): This is a bit excessive, and it would be better to just have
15414 // Toolbar implement SearchFieldDelegate. But for now, we don't know how to
15415 // make that happen with closure compiler.
15416 function ToolbarSearchFieldDelegate(toolbar) {
15417 this.toolbar_ = toolbar;
15418 }
15419
15420 ToolbarSearchFieldDelegate.prototype = {
15421 /** @override */
15422 onSearchTermSearch: function(searchTerm) {
15423 this.toolbar_.onSearchTermSearch(searchTerm);
15424 }
15425 };
15426
15427 return {Toolbar: Toolbar};
15428 });
15429
15430 // TODO(dbeam): https://github.com/PolymerElements/iron-dropdown/pull/16/files
15431 /** @suppress {checkTypes} */
15432 (function() {
15433 Polymer.IronDropdownScrollManager.pushScrollLock = function() {};
15434 })();
15435 // Copyright 2015 The Chromium Authors. All rights reserved.
15436 // Use of this source code is governed by a BSD-style license that can be
15437 // found in the LICENSE file.
15438
15439 cr.define('downloads', function() {
15440 var Manager = Polymer({
15441 is: 'downloads-manager',
15442
15443 created: function() {
15444 /** @private {!downloads.ActionService} */
15445 this.actionService_ = new downloads.ActionService;
15446 },
15447
15448 properties: {
15449 hasDownloads_: {
15450 type: Boolean,
15451 value: false,
15452 },
15453 },
15454
15455 /**
15456 * @return {number} A guess at how many items could be visible at once.
15457 * @private
15458 */
15459 guesstimateNumberOfVisibleItems_: function() {
15460 var toolbarHeight = this.$.toolbar.offsetHeight;
15461 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
15462 },
15463
15464 /**
15465 * @param {Event} e
15466 * @private
15467 */
15468 onCanExecute_: function(e) {
15469 e = /** @type {cr.ui.CanExecuteEvent} */(e);
15470 switch (e.command.id) {
15471 case 'undo-command':
15472 e.canExecute = this.$.toolbar.canUndo();
15473 break;
15474 case 'clear-all-command':
15475 e.canExecute = this.$.toolbar.canClearAll();
15476 break;
15477 }
15478 },
15479
15480 /**
15481 * @param {Event} e
15482 * @private
15483 */
15484 onCommand_: function(e) {
15485 if (e.command.id == 'clear-all-command')
15486 this.actionService_.clearAll();
15487 else if (e.command.id == 'undo-command')
15488 this.actionService_.undo();
15489 },
15490
15491 /** @private */
15492 onLoad_: function() {
15493 this.$.toolbar.setActionService(this.actionService_);
15494
15495 cr.ui.decorate('command', cr.ui.Command);
15496 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
15497 document.addEventListener('command', this.onCommand_.bind(this));
15498
15499 // Shows all downloads.
15500 this.actionService_.search('');
15501 },
15502
15503 /** @private */
15504 rebuildFocusGrid_: function() {
15505 var activeElement = this.shadowRoot.activeElement;
15506
15507 var activeItem;
15508 if (activeElement && activeElement.tagName == 'downloads-item')
15509 activeItem = activeElement;
15510
15511 var activeControl = activeItem && activeItem.shadowRoot.activeElement;
15512
15513 /** @private {!cr.ui.FocusGrid} */
15514 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
15515 this.focusGrid_.destroy();
15516
15517 var boundary = this.$['downloads-list'];
15518
15519 this.items_.forEach(function(item) {
15520 var focusRow = new downloads.FocusRow(item.content, boundary);
15521 this.focusGrid_.addRow(focusRow);
15522
15523 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
15524 focusRow.getEquivalentElement(activeControl).focus();
15525 }, this);
15526
15527 this.focusGrid_.ensureRowActive();
15528 },
15529
15530 /**
15531 * @return {number} The number of downloads shown on the page.
15532 * @private
15533 */
15534 size_: function() {
15535 return this.items_.length;
15536 },
15537
15538 /**
15539 * Called when all items need to be updated.
15540 * @param {!Array<!downloads.Data>} list A list of new download data.
15541 * @private
15542 */
15543 updateAll_: function(list) {
15544 var oldIdMap = this.idMap_ || {};
15545
15546 /** @private {!Object<!downloads.Item>} */
15547 this.idMap_ = {};
15548
15549 /** @private {!Array<!downloads.Item>} */
15550 this.items_ = [];
15551
15552 if (!this.iconLoader_) {
15553 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
15554 /** @private {downloads.ThrottledIconLoader} */
15555 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
15556 }
15557
15558 for (var i = 0; i < list.length; ++i) {
15559 var data = list[i];
15560 var id = data.id;
15561
15562 // Re-use old items when possible (saves work, preserves focus).
15563 var item = oldIdMap[id] ||
15564 new downloads.Item(this.iconLoader_, this.actionService_);
15565
15566 this.idMap_[id] = item; // Associated by ID for fast lookup.
15567 this.items_.push(item); // Add to sorted list for order.
15568
15569 // Render |item| but don't actually add to the DOM yet. |this.items_|
15570 // must be fully created to be able to find the right spot to insert.
15571 item.update(data);
15572
15573 // Collapse redundant dates.
15574 var prev = list[i - 1];
15575 item.hideDate = !!prev && prev.date_string == data.date_string;
15576
15577 delete oldIdMap[id];
15578 }
15579
15580 // Remove stale, previously rendered items from the DOM.
15581 for (var id in oldIdMap) {
15582 if (oldIdMap[id].parentNode)
15583 oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
15584 delete oldIdMap[id];
15585 }
15586
15587 for (var i = 0; i < this.items_.length; ++i) {
15588 var item = this.items_[i];
15589 if (item.parentNode) // Already in the DOM; skip.
15590 continue;
15591
15592 var before = null;
15593 // Find the next rendered item after this one, and insert before it.
15594 for (var j = i + 1; !before && j < this.items_.length; ++j) {
15595 if (this.items_[j].parentNode)
15596 before = this.items_[j];
15597 }
15598 // If |before| is null, |item| will just get added at the end.
15599 this.$['downloads-list'].insertBefore(item, before);
15600 }
15601
15602 var hasDownloads = this.size_() > 0;
15603 if (!hasDownloads) {
15604 var isSearching = this.actionService_.isSearching();
15605 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
15606 this.$['no-downloads'].querySelector('span').textContent =
15607 loadTimeData.getString(messageToShow);
15608 }
15609 this.hasDownloads_ = hasDownloads;
15610
15611 if (loadTimeData.getBoolean('allowDeletingHistory'))
15612 this.$.toolbar.downloadsShowing = this.hasDownloads_;
15613
15614 this.$.panel.classList.remove('loading');
15615
15616 var allReady = this.items_.map(function(i) { return i.readyPromise; });
15617 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
15618 },
15619
15620 /**
15621 * @param {!downloads.Data} data
15622 * @private
15623 */
15624 updateItem_: function(data) {
15625 var item = this.idMap_[data.id];
15626
15627 var activeControl = this.shadowRoot.activeElement == item ?
15628 item.shadowRoot.activeElement : null;
15629
15630 item.update(data);
15631
15632 this.async(function() {
15633 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
15634 var focusRow = this.focusGrid_.getRowForRoot(item.content);
15635 focusRow.getEquivalentElement(activeControl).focus();
15636 }
15637 }.bind(this));
15638 },
15639 });
15640
15641 Manager.size = function() {
15642 return document.querySelector('downloads-manager').size_();
15643 };
15644
15645 Manager.updateAll = function(list) {
15646 document.querySelector('downloads-manager').updateAll_(list);
15647 };
15648
15649 Manager.updateItem = function(item) {
15650 document.querySelector('downloads-manager').updateItem_(item);
15651 };
15652
15653 Manager.onLoad = function() {
15654 document.querySelector('downloads-manager').onLoad_();
15655 };
15656
15657 return {Manager: Manager};
15658 });
15659
15660 window.addEventListener('load', downloads.Manager.onLoad);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698