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

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

Issue 2252963002: MD WebUI: Strip comments from vulcanized JS files (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@history_vulcanize
Patch Set: Fix comments ending in **/ Created 4 years, 4 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
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /**
6 * @fileoverview Assertion support.
7 */
8 5
9 /**
10 * Verify |condition| is truthy and return |condition| if so.
11 * @template T
12 * @param {T} condition A condition to check for truthiness. Note that this
13 * may be used to test whether a value is defined or not, and we don't want
14 * to force a cast to Boolean.
15 * @param {string=} opt_message A message to show on failure.
16 * @return {T} A non-null |condition|.
17 */
18 function assert(condition, opt_message) { 6 function assert(condition, opt_message) {
19 if (!condition) { 7 if (!condition) {
20 var message = 'Assertion failed'; 8 var message = 'Assertion failed';
21 if (opt_message) 9 if (opt_message)
22 message = message + ': ' + opt_message; 10 message = message + ': ' + opt_message;
23 var error = new Error(message); 11 var error = new Error(message);
24 var global = function() { return this; }(); 12 var global = function() { return this; }();
25 if (global.traceAssertionsForTesting) 13 if (global.traceAssertionsForTesting)
26 console.warn(error.stack); 14 console.warn(error.stack);
27 throw error; 15 throw error;
28 } 16 }
29 return condition; 17 return condition;
30 } 18 }
31 19
32 /**
33 * Call this from places in the code that should never be reached.
34 *
35 * For example, handling all the values of enum with a switch() like this:
36 *
37 * function getValueFromEnum(enum) {
38 * switch (enum) {
39 * case ENUM_FIRST_OF_TWO:
40 * return first
41 * case ENUM_LAST_OF_TWO:
42 * return last;
43 * }
44 * assertNotReached();
45 * return document;
46 * }
47 *
48 * This code should only be hit in the case of serious programmer error or
49 * unexpected input.
50 *
51 * @param {string=} opt_message A message to show when this is hit.
52 */
53 function assertNotReached(opt_message) { 20 function assertNotReached(opt_message) {
54 assert(false, opt_message || 'Unreachable code hit'); 21 assert(false, opt_message || 'Unreachable code hit');
55 } 22 }
56 23
57 /**
58 * @param {*} value The value to check.
59 * @param {function(new: T, ...)} type A user-defined constructor.
60 * @param {string=} opt_message A message to show when this is hit.
61 * @return {T}
62 * @template T
63 */
64 function assertInstanceof(value, type, opt_message) { 24 function assertInstanceof(value, type, opt_message) {
65 // We don't use assert immediately here so that we avoid constructing an error
66 // message if we don't have to.
67 if (!(value instanceof type)) { 25 if (!(value instanceof type)) {
68 assertNotReached(opt_message || 'Value ' + value + 26 assertNotReached(opt_message || 'Value ' + value +
69 ' is not a[n] ' + (type.name || typeof type)); 27 ' is not a[n] ' + (type.name || typeof type));
70 } 28 }
71 return value; 29 return value;
72 }; 30 };
73 // Copyright 2016 The Chromium Authors. All rights reserved. 31 // Copyright 2016 The Chromium Authors. All rights reserved.
74 // Use of this source code is governed by a BSD-style license that can be 32 // Use of this source code is governed by a BSD-style license that can be
75 // found in the LICENSE file. 33 // found in the LICENSE file.
76 34
77 /**
78 * @fileoverview PromiseResolver is a helper class that allows creating a
79 * Promise that will be fulfilled (resolved or rejected) some time later.
80 *
81 * Example:
82 * var resolver = new PromiseResolver();
83 * resolver.promise.then(function(result) {
84 * console.log('resolved with', result);
85 * });
86 * ...
87 * ...
88 * resolver.resolve({hello: 'world'});
89 */
90 35
91 /**
92 * @constructor @struct
93 * @template T
94 */
95 function PromiseResolver() { 36 function PromiseResolver() {
96 /** @private {function(T=): void} */ 37 /** @private {function(T=): void} */
97 this.resolve_; 38 this.resolve_;
98 39
99 /** @private {function(*=): void} */ 40 /** @private {function(*=): void} */
100 this.reject_; 41 this.reject_;
101 42
102 /** @private {!Promise<T>} */ 43 /** @private {!Promise<T>} */
103 this.promise_ = new Promise(function(resolve, reject) { 44 this.promise_ = new Promise(function(resolve, reject) {
104 this.resolve_ = resolve; 45 this.resolve_ = resolve;
(...skipping 11 matching lines...) Expand all
116 set resolve(r) { assertNotReached(); }, 57 set resolve(r) { assertNotReached(); },
117 58
118 /** @return {function(*=): void} */ 59 /** @return {function(*=): void} */
119 get reject() { return this.reject_; }, 60 get reject() { return this.reject_; },
120 set reject(s) { assertNotReached(); }, 61 set reject(s) { assertNotReached(); },
121 }; 62 };
122 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 63 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
123 // Use of this source code is governed by a BSD-style license that can be 64 // Use of this source code is governed by a BSD-style license that can be
124 // found in the LICENSE file. 65 // found in the LICENSE file.
125 66
126 /**
127 * The global object.
128 * @type {!Object}
129 * @const
130 */
131 var global = this; 67 var global = this;
132 68
133 /** @typedef {{eventName: string, uid: number}} */ 69 /** @typedef {{eventName: string, uid: number}} */
134 var WebUIListener; 70 var WebUIListener;
135 71
136 /** Platform, package, object property, and Event support. **/ 72 /** Platform, package, object property, and Event support. **/
137 var cr = cr || function() { 73 var cr = cr || function() {
138 'use strict'; 74 'use strict';
139 75
140 /**
141 * Builds an object structure for the provided namespace path,
142 * ensuring that names that already exist are not overwritten. For
143 * example:
144 * "a.b.c" -> a = {};a.b={};a.b.c={};
145 * @param {string} name Name of the object that this file defines.
146 * @param {*=} opt_object The object to expose at the end of the path.
147 * @param {Object=} opt_objectToExportTo The object to add the path to;
148 * default is {@code global}.
149 * @return {!Object} The last object exported (i.e. exportPath('cr.ui')
150 * returns a reference to the ui property of window.cr).
151 * @private
152 */
153 function exportPath(name, opt_object, opt_objectToExportTo) { 76 function exportPath(name, opt_object, opt_objectToExportTo) {
154 var parts = name.split('.'); 77 var parts = name.split('.');
155 var cur = opt_objectToExportTo || global; 78 var cur = opt_objectToExportTo || global;
156 79
157 for (var part; parts.length && (part = parts.shift());) { 80 for (var part; parts.length && (part = parts.shift());) {
158 if (!parts.length && opt_object !== undefined) { 81 if (!parts.length && opt_object !== undefined) {
159 // last part and we have an object; use it
160 cur[part] = opt_object; 82 cur[part] = opt_object;
161 } else if (part in cur) { 83 } else if (part in cur) {
162 cur = cur[part]; 84 cur = cur[part];
163 } else { 85 } else {
164 cur = cur[part] = {}; 86 cur = cur[part] = {};
165 } 87 }
166 } 88 }
167 return cur; 89 return cur;
168 } 90 }
169 91
170 /**
171 * Fires a property change event on the target.
172 * @param {EventTarget} target The target to dispatch the event on.
173 * @param {string} propertyName The name of the property that changed.
174 * @param {*} newValue The new value for the property.
175 * @param {*} oldValue The old value for the property.
176 */
177 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { 92 function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
178 var e = new Event(propertyName + 'Change'); 93 var e = new Event(propertyName + 'Change');
179 e.propertyName = propertyName; 94 e.propertyName = propertyName;
180 e.newValue = newValue; 95 e.newValue = newValue;
181 e.oldValue = oldValue; 96 e.oldValue = oldValue;
182 target.dispatchEvent(e); 97 target.dispatchEvent(e);
183 } 98 }
184 99
185 /**
186 * Converts a camelCase javascript property name to a hyphenated-lower-case
187 * attribute name.
188 * @param {string} jsName The javascript camelCase property name.
189 * @return {string} The equivalent hyphenated-lower-case attribute name.
190 */
191 function getAttributeName(jsName) { 100 function getAttributeName(jsName) {
192 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); 101 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
193 } 102 }
194 103
195 /**
196 * The kind of property to define in {@code defineProperty}.
197 * @enum {string}
198 * @const
199 */
200 var PropertyKind = { 104 var PropertyKind = {
201 /**
202 * Plain old JS property where the backing data is stored as a "private"
203 * field on the object.
204 * Use for properties of any type. Type will not be checked.
205 */
206 JS: 'js', 105 JS: 'js',
207 106
208 /**
209 * The property backing data is stored as an attribute on an element.
210 * Use only for properties of type {string}.
211 */
212 ATTR: 'attr', 107 ATTR: 'attr',
213 108
214 /**
215 * The property backing data is stored as an attribute on an element. If the
216 * element has the attribute then the value is true.
217 * Use only for properties of type {boolean}.
218 */
219 BOOL_ATTR: 'boolAttr' 109 BOOL_ATTR: 'boolAttr'
220 }; 110 };
221 111
222 /**
223 * Helper function for defineProperty that returns the getter to use for the
224 * property.
225 * @param {string} name The name of the property.
226 * @param {PropertyKind} kind The kind of the property.
227 * @return {function():*} The getter for the property.
228 */
229 function getGetter(name, kind) { 112 function getGetter(name, kind) {
230 switch (kind) { 113 switch (kind) {
231 case PropertyKind.JS: 114 case PropertyKind.JS:
232 var privateName = name + '_'; 115 var privateName = name + '_';
233 return function() { 116 return function() {
234 return this[privateName]; 117 return this[privateName];
235 }; 118 };
236 case PropertyKind.ATTR: 119 case PropertyKind.ATTR:
237 var attributeName = getAttributeName(name); 120 var attributeName = getAttributeName(name);
238 return function() { 121 return function() {
239 return this.getAttribute(attributeName); 122 return this.getAttribute(attributeName);
240 }; 123 };
241 case PropertyKind.BOOL_ATTR: 124 case PropertyKind.BOOL_ATTR:
242 var attributeName = getAttributeName(name); 125 var attributeName = getAttributeName(name);
243 return function() { 126 return function() {
244 return this.hasAttribute(attributeName); 127 return this.hasAttribute(attributeName);
245 }; 128 };
246 } 129 }
247 130
248 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
249 // the browser/unit tests to preprocess this file through grit.
250 throw 'not reached'; 131 throw 'not reached';
251 } 132 }
252 133
253 /**
254 * Helper function for defineProperty that returns the setter of the right
255 * kind.
256 * @param {string} name The name of the property we are defining the setter
257 * for.
258 * @param {PropertyKind} kind The kind of property we are getting the
259 * setter for.
260 * @param {function(*, *):void=} opt_setHook A function to run after the
261 * property is set, but before the propertyChange event is fired.
262 * @return {function(*):void} The function to use as a setter.
263 */
264 function getSetter(name, kind, opt_setHook) { 134 function getSetter(name, kind, opt_setHook) {
265 switch (kind) { 135 switch (kind) {
266 case PropertyKind.JS: 136 case PropertyKind.JS:
267 var privateName = name + '_'; 137 var privateName = name + '_';
268 return function(value) { 138 return function(value) {
269 var oldValue = this[name]; 139 var oldValue = this[name];
270 if (value !== oldValue) { 140 if (value !== oldValue) {
271 this[privateName] = value; 141 this[privateName] = value;
272 if (opt_setHook) 142 if (opt_setHook)
273 opt_setHook.call(this, value, oldValue); 143 opt_setHook.call(this, value, oldValue);
(...skipping 25 matching lines...) Expand all
299 this.setAttribute(attributeName, name); 169 this.setAttribute(attributeName, name);
300 else 170 else
301 this.removeAttribute(attributeName); 171 this.removeAttribute(attributeName);
302 if (opt_setHook) 172 if (opt_setHook)
303 opt_setHook.call(this, value, oldValue); 173 opt_setHook.call(this, value, oldValue);
304 dispatchPropertyChange(this, name, value, oldValue); 174 dispatchPropertyChange(this, name, value, oldValue);
305 } 175 }
306 }; 176 };
307 } 177 }
308 178
309 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
310 // the browser/unit tests to preprocess this file through grit.
311 throw 'not reached'; 179 throw 'not reached';
312 } 180 }
313 181
314 /**
315 * Defines a property on an object. When the setter changes the value a
316 * property change event with the type {@code name + 'Change'} is fired.
317 * @param {!Object} obj The object to define the property for.
318 * @param {string} name The name of the property.
319 * @param {PropertyKind=} opt_kind What kind of underlying storage to use.
320 * @param {function(*, *):void=} opt_setHook A function to run after the
321 * property is set, but before the propertyChange event is fired.
322 */
323 function defineProperty(obj, name, opt_kind, opt_setHook) { 182 function defineProperty(obj, name, opt_kind, opt_setHook) {
324 if (typeof obj == 'function') 183 if (typeof obj == 'function')
325 obj = obj.prototype; 184 obj = obj.prototype;
326 185
327 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); 186 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS);
328 187
329 if (!obj.__lookupGetter__(name)) 188 if (!obj.__lookupGetter__(name))
330 obj.__defineGetter__(name, getGetter(name, kind)); 189 obj.__defineGetter__(name, getGetter(name, kind));
331 190
332 if (!obj.__lookupSetter__(name)) 191 if (!obj.__lookupSetter__(name))
333 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); 192 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
334 } 193 }
335 194
336 /**
337 * Counter for use with createUid
338 */
339 var uidCounter = 1; 195 var uidCounter = 1;
340 196
341 /**
342 * @return {number} A new unique ID.
343 */
344 function createUid() { 197 function createUid() {
345 return uidCounter++; 198 return uidCounter++;
346 } 199 }
347 200
348 /**
349 * Returns a unique ID for the item. This mutates the item so it needs to be
350 * an object
351 * @param {!Object} item The item to get the unique ID for.
352 * @return {number} The unique ID for the item.
353 */
354 function getUid(item) { 201 function getUid(item) {
355 if (item.hasOwnProperty('uid')) 202 if (item.hasOwnProperty('uid'))
356 return item.uid; 203 return item.uid;
357 return item.uid = createUid(); 204 return item.uid = createUid();
358 } 205 }
359 206
360 /**
361 * Dispatches a simple event on an event target.
362 * @param {!EventTarget} target The event target to dispatch the event on.
363 * @param {string} type The type of the event.
364 * @param {boolean=} opt_bubbles Whether the event bubbles or not.
365 * @param {boolean=} opt_cancelable Whether the default action of the event
366 * can be prevented. Default is true.
367 * @return {boolean} If any of the listeners called {@code preventDefault}
368 * during the dispatch this will return false.
369 */
370 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { 207 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
371 var e = new Event(type, { 208 var e = new Event(type, {
372 bubbles: opt_bubbles, 209 bubbles: opt_bubbles,
373 cancelable: opt_cancelable === undefined || opt_cancelable 210 cancelable: opt_cancelable === undefined || opt_cancelable
374 }); 211 });
375 return target.dispatchEvent(e); 212 return target.dispatchEvent(e);
376 } 213 }
377 214
378 /**
379 * Calls |fun| and adds all the fields of the returned object to the object
380 * named by |name|. For example, cr.define('cr.ui', function() {
381 * function List() {
382 * ...
383 * }
384 * function ListItem() {
385 * ...
386 * }
387 * return {
388 * List: List,
389 * ListItem: ListItem,
390 * };
391 * });
392 * defines the functions cr.ui.List and cr.ui.ListItem.
393 * @param {string} name The name of the object that we are adding fields to.
394 * @param {!Function} fun The function that will return an object containing
395 * the names and values of the new fields.
396 */
397 function define(name, fun) { 215 function define(name, fun) {
398 var obj = exportPath(name); 216 var obj = exportPath(name);
399 var exports = fun(); 217 var exports = fun();
400 for (var propertyName in exports) { 218 for (var propertyName in exports) {
401 // Maybe we should check the prototype chain here? The current usage
402 // pattern is always using an object literal so we only care about own
403 // properties.
404 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, 219 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
405 propertyName); 220 propertyName);
406 if (propertyDescriptor) 221 if (propertyDescriptor)
407 Object.defineProperty(obj, propertyName, propertyDescriptor); 222 Object.defineProperty(obj, propertyName, propertyDescriptor);
408 } 223 }
409 } 224 }
410 225
411 /**
412 * Adds a {@code getInstance} static method that always return the same
413 * instance object.
414 * @param {!Function} ctor The constructor for the class to add the static
415 * method to.
416 */
417 function addSingletonGetter(ctor) { 226 function addSingletonGetter(ctor) {
418 ctor.getInstance = function() { 227 ctor.getInstance = function() {
419 return ctor.instance_ || (ctor.instance_ = new ctor()); 228 return ctor.instance_ || (ctor.instance_ = new ctor());
420 }; 229 };
421 } 230 }
422 231
423 /**
424 * Forwards public APIs to private implementations.
425 * @param {Function} ctor Constructor that have private implementations in its
426 * prototype.
427 * @param {Array<string>} methods List of public method names that have their
428 * underscored counterparts in constructor's prototype.
429 * @param {string=} opt_target Selector for target node.
430 */
431 function makePublic(ctor, methods, opt_target) { 232 function makePublic(ctor, methods, opt_target) {
432 methods.forEach(function(method) { 233 methods.forEach(function(method) {
433 ctor[method] = function() { 234 ctor[method] = function() {
434 var target = opt_target ? document.getElementById(opt_target) : 235 var target = opt_target ? document.getElementById(opt_target) :
435 ctor.getInstance(); 236 ctor.getInstance();
436 return target[method + '_'].apply(target, arguments); 237 return target[method + '_'].apply(target, arguments);
437 }; 238 };
438 }); 239 });
439 } 240 }
440 241
441 /**
442 * The mapping used by the sendWithPromise mechanism to tie the Promise
443 * returned to callers with the corresponding WebUI response. The mapping is
444 * from ID to the PromiseResolver helper; the ID is generated by
445 * sendWithPromise and is unique across all invocations of said method.
446 * @type {!Object<!PromiseResolver>}
447 */
448 var chromeSendResolverMap = {}; 242 var chromeSendResolverMap = {};
449 243
450 /**
451 * The named method the WebUI handler calls directly in response to a
452 * chrome.send call that expects a response. The handler requires no knowledge
453 * of the specific name of this method, as the name is passed to the handler
454 * as the first argument in the arguments list of chrome.send. The handler
455 * must pass the ID, also sent via the chrome.send arguments list, as the
456 * first argument of the JS invocation; additionally, the handler may
457 * supply any number of other arguments that will be included in the response.
458 * @param {string} id The unique ID identifying the Promise this response is
459 * tied to.
460 * @param {boolean} isSuccess Whether the request was successful.
461 * @param {*} response The response as sent from C++.
462 */
463 function webUIResponse(id, isSuccess, response) { 244 function webUIResponse(id, isSuccess, response) {
464 var resolver = chromeSendResolverMap[id]; 245 var resolver = chromeSendResolverMap[id];
465 delete chromeSendResolverMap[id]; 246 delete chromeSendResolverMap[id];
466 247
467 if (isSuccess) 248 if (isSuccess)
468 resolver.resolve(response); 249 resolver.resolve(response);
469 else 250 else
470 resolver.reject(response); 251 resolver.reject(response);
471 } 252 }
472 253
473 /**
474 * A variation of chrome.send, suitable for messages that expect a single
475 * response from C++.
476 * @param {string} methodName The name of the WebUI handler API.
477 * @param {...*} var_args Varibale number of arguments to be forwarded to the
478 * C++ call.
479 * @return {!Promise}
480 */
481 function sendWithPromise(methodName, var_args) { 254 function sendWithPromise(methodName, var_args) {
482 var args = Array.prototype.slice.call(arguments, 1); 255 var args = Array.prototype.slice.call(arguments, 1);
483 var promiseResolver = new PromiseResolver(); 256 var promiseResolver = new PromiseResolver();
484 var id = methodName + '_' + createUid(); 257 var id = methodName + '_' + createUid();
485 chromeSendResolverMap[id] = promiseResolver; 258 chromeSendResolverMap[id] = promiseResolver;
486 chrome.send(methodName, [id].concat(args)); 259 chrome.send(methodName, [id].concat(args));
487 return promiseResolver.promise; 260 return promiseResolver.promise;
488 } 261 }
489 262
490 /**
491 * A map of maps associating event names with listeners. The 2nd level map
492 * associates a listener ID with the callback function, such that individual
493 * listeners can be removed from an event without affecting other listeners of
494 * the same event.
495 * @type {!Object<!Object<!Function>>}
496 */
497 var webUIListenerMap = {}; 263 var webUIListenerMap = {};
498 264
499 /**
500 * The named method the WebUI handler calls directly when an event occurs.
501 * The WebUI handler must supply the name of the event as the first argument
502 * of the JS invocation; additionally, the handler may supply any number of
503 * other arguments that will be forwarded to the listener callbacks.
504 * @param {string} event The name of the event that has occurred.
505 * @param {...*} var_args Additional arguments passed from C++.
506 */
507 function webUIListenerCallback(event, var_args) { 265 function webUIListenerCallback(event, var_args) {
508 var eventListenersMap = webUIListenerMap[event]; 266 var eventListenersMap = webUIListenerMap[event];
509 if (!eventListenersMap) { 267 if (!eventListenersMap) {
510 // C++ event sent for an event that has no listeners.
511 // TODO(dpapad): Should a warning be displayed here?
512 return; 268 return;
513 } 269 }
514 270
515 var args = Array.prototype.slice.call(arguments, 1); 271 var args = Array.prototype.slice.call(arguments, 1);
516 for (var listenerId in eventListenersMap) { 272 for (var listenerId in eventListenersMap) {
517 eventListenersMap[listenerId].apply(null, args); 273 eventListenersMap[listenerId].apply(null, args);
518 } 274 }
519 } 275 }
520 276
521 /**
522 * Registers a listener for an event fired from WebUI handlers. Any number of
523 * listeners may register for a single event.
524 * @param {string} eventName The event to listen to.
525 * @param {!Function} callback The callback run when the event is fired.
526 * @return {!WebUIListener} An object to be used for removing a listener via
527 * cr.removeWebUIListener. Should be treated as read-only.
528 */
529 function addWebUIListener(eventName, callback) { 277 function addWebUIListener(eventName, callback) {
530 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {}; 278 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
531 var uid = createUid(); 279 var uid = createUid();
532 webUIListenerMap[eventName][uid] = callback; 280 webUIListenerMap[eventName][uid] = callback;
533 return {eventName: eventName, uid: uid}; 281 return {eventName: eventName, uid: uid};
534 } 282 }
535 283
536 /**
537 * Removes a listener. Does nothing if the specified listener is not found.
538 * @param {!WebUIListener} listener The listener to be removed (as returned by
539 * addWebUIListener).
540 * @return {boolean} Whether the given listener was found and actually
541 * removed.
542 */
543 function removeWebUIListener(listener) { 284 function removeWebUIListener(listener) {
544 var listenerExists = webUIListenerMap[listener.eventName] && 285 var listenerExists = webUIListenerMap[listener.eventName] &&
545 webUIListenerMap[listener.eventName][listener.uid]; 286 webUIListenerMap[listener.eventName][listener.uid];
546 if (listenerExists) { 287 if (listenerExists) {
547 delete webUIListenerMap[listener.eventName][listener.uid]; 288 delete webUIListenerMap[listener.eventName][listener.uid];
548 return true; 289 return true;
549 } 290 }
550 return false; 291 return false;
551 } 292 }
552 293
553 return { 294 return {
554 addSingletonGetter: addSingletonGetter, 295 addSingletonGetter: addSingletonGetter,
555 createUid: createUid, 296 createUid: createUid,
556 define: define, 297 define: define,
557 defineProperty: defineProperty, 298 defineProperty: defineProperty,
558 dispatchPropertyChange: dispatchPropertyChange, 299 dispatchPropertyChange: dispatchPropertyChange,
559 dispatchSimpleEvent: dispatchSimpleEvent, 300 dispatchSimpleEvent: dispatchSimpleEvent,
560 exportPath: exportPath, 301 exportPath: exportPath,
561 getUid: getUid, 302 getUid: getUid,
562 makePublic: makePublic, 303 makePublic: makePublic,
563 PropertyKind: PropertyKind, 304 PropertyKind: PropertyKind,
564 305
565 // C++ <-> JS communication related methods.
566 addWebUIListener: addWebUIListener, 306 addWebUIListener: addWebUIListener,
567 removeWebUIListener: removeWebUIListener, 307 removeWebUIListener: removeWebUIListener,
568 sendWithPromise: sendWithPromise, 308 sendWithPromise: sendWithPromise,
569 webUIListenerCallback: webUIListenerCallback, 309 webUIListenerCallback: webUIListenerCallback,
570 webUIResponse: webUIResponse, 310 webUIResponse: webUIResponse,
571 311
572 get doc() { 312 get doc() {
573 return document; 313 return document;
574 }, 314 },
575 315
(...skipping 27 matching lines...) Expand all
603 return /iPad|iPhone|iPod/.test(navigator.platform); 343 return /iPad|iPhone|iPod/.test(navigator.platform);
604 } 344 }
605 }; 345 };
606 }(); 346 }();
607 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 347 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
608 // Use of this source code is governed by a BSD-style license that can be 348 // Use of this source code is governed by a BSD-style license that can be
609 // found in the LICENSE file. 349 // found in the LICENSE file.
610 350
611 cr.define('cr.ui', function() { 351 cr.define('cr.ui', function() {
612 352
613 /**
614 * Decorates elements as an instance of a class.
615 * @param {string|!Element} source The way to find the element(s) to decorate.
616 * If this is a string then {@code querySeletorAll} is used to find the
617 * elements to decorate.
618 * @param {!Function} constr The constructor to decorate with. The constr
619 * needs to have a {@code decorate} function.
620 */
621 function decorate(source, constr) { 353 function decorate(source, constr) {
622 var elements; 354 var elements;
623 if (typeof source == 'string') 355 if (typeof source == 'string')
624 elements = cr.doc.querySelectorAll(source); 356 elements = cr.doc.querySelectorAll(source);
625 else 357 else
626 elements = [source]; 358 elements = [source];
627 359
628 for (var i = 0, el; el = elements[i]; i++) { 360 for (var i = 0, el; el = elements[i]; i++) {
629 if (!(el instanceof constr)) 361 if (!(el instanceof constr))
630 constr.decorate(el); 362 constr.decorate(el);
631 } 363 }
632 } 364 }
633 365
634 /**
635 * Helper function for creating new element for define.
636 */
637 function createElementHelper(tagName, opt_bag) { 366 function createElementHelper(tagName, opt_bag) {
638 // Allow passing in ownerDocument to create in a different document.
639 var doc; 367 var doc;
640 if (opt_bag && opt_bag.ownerDocument) 368 if (opt_bag && opt_bag.ownerDocument)
641 doc = opt_bag.ownerDocument; 369 doc = opt_bag.ownerDocument;
642 else 370 else
643 doc = cr.doc; 371 doc = cr.doc;
644 return doc.createElement(tagName); 372 return doc.createElement(tagName);
645 } 373 }
646 374
647 /**
648 * Creates the constructor for a UI element class.
649 *
650 * Usage:
651 * <pre>
652 * var List = cr.ui.define('list');
653 * List.prototype = {
654 * __proto__: HTMLUListElement.prototype,
655 * decorate: function() {
656 * ...
657 * },
658 * ...
659 * };
660 * </pre>
661 *
662 * @param {string|Function} tagNameOrFunction The tagName or
663 * function to use for newly created elements. If this is a function it
664 * needs to return a new element when called.
665 * @return {function(Object=):Element} The constructor function which takes
666 * an optional property bag. The function also has a static
667 * {@code decorate} method added to it.
668 */
669 function define(tagNameOrFunction) { 375 function define(tagNameOrFunction) {
670 var createFunction, tagName; 376 var createFunction, tagName;
671 if (typeof tagNameOrFunction == 'function') { 377 if (typeof tagNameOrFunction == 'function') {
672 createFunction = tagNameOrFunction; 378 createFunction = tagNameOrFunction;
673 tagName = ''; 379 tagName = '';
674 } else { 380 } else {
675 createFunction = createElementHelper; 381 createFunction = createElementHelper;
676 tagName = tagNameOrFunction; 382 tagName = tagNameOrFunction;
677 } 383 }
678 384
679 /**
680 * Creates a new UI element constructor.
681 * @param {Object=} opt_propertyBag Optional bag of properties to set on the
682 * object after created. The property {@code ownerDocument} is special
683 * cased and it allows you to create the element in a different
684 * document than the default.
685 * @constructor
686 */
687 function f(opt_propertyBag) { 385 function f(opt_propertyBag) {
688 var el = createFunction(tagName, opt_propertyBag); 386 var el = createFunction(tagName, opt_propertyBag);
689 f.decorate(el); 387 f.decorate(el);
690 for (var propertyName in opt_propertyBag) { 388 for (var propertyName in opt_propertyBag) {
691 el[propertyName] = opt_propertyBag[propertyName]; 389 el[propertyName] = opt_propertyBag[propertyName];
692 } 390 }
693 return el; 391 return el;
694 } 392 }
695 393
696 /**
697 * Decorates an element as a UI element class.
698 * @param {!Element} el The element to decorate.
699 */
700 f.decorate = function(el) { 394 f.decorate = function(el) {
701 el.__proto__ = f.prototype; 395 el.__proto__ = f.prototype;
702 el.decorate(); 396 el.decorate();
703 }; 397 };
704 398
705 return f; 399 return f;
706 } 400 }
707 401
708 /**
709 * Input elements do not grow and shrink with their content. This is a simple
710 * (and not very efficient) way of handling shrinking to content with support
711 * for min width and limited by the width of the parent element.
712 * @param {!HTMLElement} el The element to limit the width for.
713 * @param {!HTMLElement} parentEl The parent element that should limit the
714 * size.
715 * @param {number} min The minimum width.
716 * @param {number=} opt_scale Optional scale factor to apply to the width.
717 */
718 function limitInputWidth(el, parentEl, min, opt_scale) { 402 function limitInputWidth(el, parentEl, min, opt_scale) {
719 // Needs a size larger than borders
720 el.style.width = '10px'; 403 el.style.width = '10px';
721 var doc = el.ownerDocument; 404 var doc = el.ownerDocument;
722 var win = doc.defaultView; 405 var win = doc.defaultView;
723 var computedStyle = win.getComputedStyle(el); 406 var computedStyle = win.getComputedStyle(el);
724 var parentComputedStyle = win.getComputedStyle(parentEl); 407 var parentComputedStyle = win.getComputedStyle(parentEl);
725 var rtl = computedStyle.direction == 'rtl'; 408 var rtl = computedStyle.direction == 'rtl';
726 409
727 // To get the max width we get the width of the treeItem minus the position
728 // of the input.
729 var inputRect = el.getBoundingClientRect(); // box-sizing 410 var inputRect = el.getBoundingClientRect(); // box-sizing
730 var parentRect = parentEl.getBoundingClientRect(); 411 var parentRect = parentEl.getBoundingClientRect();
731 var startPos = rtl ? parentRect.right - inputRect.right : 412 var startPos = rtl ? parentRect.right - inputRect.right :
732 inputRect.left - parentRect.left; 413 inputRect.left - parentRect.left;
733 414
734 // Add up border and padding of the input.
735 var inner = parseInt(computedStyle.borderLeftWidth, 10) + 415 var inner = parseInt(computedStyle.borderLeftWidth, 10) +
736 parseInt(computedStyle.paddingLeft, 10) + 416 parseInt(computedStyle.paddingLeft, 10) +
737 parseInt(computedStyle.paddingRight, 10) + 417 parseInt(computedStyle.paddingRight, 10) +
738 parseInt(computedStyle.borderRightWidth, 10); 418 parseInt(computedStyle.borderRightWidth, 10);
739 419
740 // We also need to subtract the padding of parent to prevent it to overflow.
741 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : 420 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
742 parseInt(parentComputedStyle.paddingRight, 10); 421 parseInt(parentComputedStyle.paddingRight, 10);
743 422
744 var max = parentEl.clientWidth - startPos - inner - parentPadding; 423 var max = parentEl.clientWidth - startPos - inner - parentPadding;
745 if (opt_scale) 424 if (opt_scale)
746 max *= opt_scale; 425 max *= opt_scale;
747 426
748 function limit() { 427 function limit() {
749 if (el.scrollWidth > max) { 428 if (el.scrollWidth > max) {
750 el.style.width = max + 'px'; 429 el.style.width = max + 'px';
751 } else { 430 } else {
752 el.style.width = 0; 431 el.style.width = 0;
753 var sw = el.scrollWidth; 432 var sw = el.scrollWidth;
754 if (sw < min) { 433 if (sw < min) {
755 el.style.width = min + 'px'; 434 el.style.width = min + 'px';
756 } else { 435 } else {
757 el.style.width = sw + 'px'; 436 el.style.width = sw + 'px';
758 } 437 }
759 } 438 }
760 } 439 }
761 440
762 el.addEventListener('input', limit); 441 el.addEventListener('input', limit);
763 limit(); 442 limit();
764 } 443 }
765 444
766 /**
767 * Takes a number and spits out a value CSS will be happy with. To avoid
768 * subpixel layout issues, the value is rounded to the nearest integral value.
769 * @param {number} pixels The number of pixels.
770 * @return {string} e.g. '16px'.
771 */
772 function toCssPx(pixels) { 445 function toCssPx(pixels) {
773 if (!window.isFinite(pixels)) 446 if (!window.isFinite(pixels))
774 console.error('Pixel value is not a number: ' + pixels); 447 console.error('Pixel value is not a number: ' + pixels);
775 return Math.round(pixels) + 'px'; 448 return Math.round(pixels) + 'px';
776 } 449 }
777 450
778 /**
779 * Users complain they occasionaly use doubleclicks instead of clicks
780 * (http://crbug.com/140364). To fix it we freeze click handling for
781 * the doubleclick time interval.
782 * @param {MouseEvent} e Initial click event.
783 */
784 function swallowDoubleClick(e) { 451 function swallowDoubleClick(e) {
785 var doc = e.target.ownerDocument; 452 var doc = e.target.ownerDocument;
786 var counter = Math.min(1, e.detail); 453 var counter = Math.min(1, e.detail);
787 function swallow(e) { 454 function swallow(e) {
788 e.stopPropagation(); 455 e.stopPropagation();
789 e.preventDefault(); 456 e.preventDefault();
790 } 457 }
791 function onclick(e) { 458 function onclick(e) {
792 if (e.detail > counter) { 459 if (e.detail > counter) {
793 counter = e.detail; 460 counter = e.detail;
794 // Swallow the click since it's a click inside the doubleclick timeout.
795 swallow(e); 461 swallow(e);
796 } else { 462 } else {
797 // Stop tracking clicks and let regular handling.
798 doc.removeEventListener('dblclick', swallow, true); 463 doc.removeEventListener('dblclick', swallow, true);
799 doc.removeEventListener('click', onclick, true); 464 doc.removeEventListener('click', onclick, true);
800 } 465 }
801 } 466 }
802 // The following 'click' event (if e.type == 'mouseup') mustn't be taken
803 // into account (it mustn't stop tracking clicks). Start event listening
804 // after zero timeout.
805 setTimeout(function() { 467 setTimeout(function() {
806 doc.addEventListener('click', onclick, true); 468 doc.addEventListener('click', onclick, true);
807 doc.addEventListener('dblclick', swallow, true); 469 doc.addEventListener('dblclick', swallow, true);
808 }, 0); 470 }, 0);
809 } 471 }
810 472
811 return { 473 return {
812 decorate: decorate, 474 decorate: decorate,
813 define: define, 475 define: define,
814 limitInputWidth: limitInputWidth, 476 limitInputWidth: limitInputWidth,
815 toCssPx: toCssPx, 477 toCssPx: toCssPx,
816 swallowDoubleClick: swallowDoubleClick 478 swallowDoubleClick: swallowDoubleClick
817 }; 479 };
818 }); 480 });
819 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 481 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
820 // Use of this source code is governed by a BSD-style license that can be 482 // Use of this source code is governed by a BSD-style license that can be
821 // found in the LICENSE file. 483 // found in the LICENSE file.
822 484
823 /**
824 * @fileoverview A command is an abstraction of an action a user can do in the
825 * UI.
826 *
827 * When the focus changes in the document for each command a canExecute event
828 * is dispatched on the active element. By listening to this event you can
829 * enable and disable the command by setting the event.canExecute property.
830 *
831 * When a command is executed a command event is dispatched on the active
832 * element. Note that you should stop the propagation after you have handled the
833 * command if there might be other command listeners higher up in the DOM tree.
834 */
835 485
836 cr.define('cr.ui', function() { 486 cr.define('cr.ui', function() {
837 487
838 /**
839 * This is used to identify keyboard shortcuts.
840 * @param {string} shortcut The text used to describe the keys for this
841 * keyboard shortcut.
842 * @constructor
843 */
844 function KeyboardShortcut(shortcut) { 488 function KeyboardShortcut(shortcut) {
845 var mods = {}; 489 var mods = {};
846 var ident = ''; 490 var ident = '';
847 shortcut.split('|').forEach(function(part) { 491 shortcut.split('|').forEach(function(part) {
848 var partLc = part.toLowerCase(); 492 var partLc = part.toLowerCase();
849 switch (partLc) { 493 switch (partLc) {
850 case 'alt': 494 case 'alt':
851 case 'ctrl': 495 case 'ctrl':
852 case 'meta': 496 case 'meta':
853 case 'shift': 497 case 'shift':
854 mods[partLc + 'Key'] = true; 498 mods[partLc + 'Key'] = true;
855 break; 499 break;
856 default: 500 default:
857 if (ident) 501 if (ident)
858 throw Error('Invalid shortcut'); 502 throw Error('Invalid shortcut');
859 ident = part; 503 ident = part;
860 } 504 }
861 }); 505 });
862 506
863 this.ident_ = ident; 507 this.ident_ = ident;
864 this.mods_ = mods; 508 this.mods_ = mods;
865 } 509 }
866 510
867 KeyboardShortcut.prototype = { 511 KeyboardShortcut.prototype = {
868 /**
869 * Whether the keyboard shortcut object matches a keyboard event.
870 * @param {!Event} e The keyboard event object.
871 * @return {boolean} Whether we found a match or not.
872 */
873 matchesEvent: function(e) { 512 matchesEvent: function(e) {
874 if (e.key == this.ident_) { 513 if (e.key == this.ident_) {
875 // All keyboard modifiers needs to match.
876 var mods = this.mods_; 514 var mods = this.mods_;
877 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { 515 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) {
878 return e[k] == !!mods[k]; 516 return e[k] == !!mods[k];
879 }); 517 });
880 } 518 }
881 return false; 519 return false;
882 } 520 }
883 }; 521 };
884 522
885 /**
886 * Creates a new command element.
887 * @constructor
888 * @extends {HTMLElement}
889 */
890 var Command = cr.ui.define('command'); 523 var Command = cr.ui.define('command');
891 524
892 Command.prototype = { 525 Command.prototype = {
893 __proto__: HTMLElement.prototype, 526 __proto__: HTMLElement.prototype,
894 527
895 /**
896 * Initializes the command.
897 */
898 decorate: function() { 528 decorate: function() {
899 CommandManager.init(assert(this.ownerDocument)); 529 CommandManager.init(assert(this.ownerDocument));
900 530
901 if (this.hasAttribute('shortcut')) 531 if (this.hasAttribute('shortcut'))
902 this.shortcut = this.getAttribute('shortcut'); 532 this.shortcut = this.getAttribute('shortcut');
903 }, 533 },
904 534
905 /**
906 * Executes the command by dispatching a command event on the given element.
907 * If |element| isn't given, the active element is used instead.
908 * If the command is {@code disabled} this does nothing.
909 * @param {HTMLElement=} opt_element Optional element to dispatch event on.
910 */
911 execute: function(opt_element) { 535 execute: function(opt_element) {
912 if (this.disabled) 536 if (this.disabled)
913 return; 537 return;
914 var doc = this.ownerDocument; 538 var doc = this.ownerDocument;
915 if (doc.activeElement) { 539 if (doc.activeElement) {
916 var e = new Event('command', {bubbles: true}); 540 var e = new Event('command', {bubbles: true});
917 e.command = this; 541 e.command = this;
918 542
919 (opt_element || doc.activeElement).dispatchEvent(e); 543 (opt_element || doc.activeElement).dispatchEvent(e);
920 } 544 }
921 }, 545 },
922 546
923 /**
924 * Call this when there have been changes that might change whether the
925 * command can be executed or not.
926 * @param {Node=} opt_node Node for which to actuate command state.
927 */
928 canExecuteChange: function(opt_node) { 547 canExecuteChange: function(opt_node) {
929 dispatchCanExecuteEvent(this, 548 dispatchCanExecuteEvent(this,
930 opt_node || this.ownerDocument.activeElement); 549 opt_node || this.ownerDocument.activeElement);
931 }, 550 },
932 551
933 /**
934 * The keyboard shortcut that triggers the command. This is a string
935 * consisting of a key (as reported by WebKit in keydown) as
936 * well as optional key modifiers joinded with a '|'.
937 *
938 * Multiple keyboard shortcuts can be provided by separating them by
939 * whitespace.
940 *
941 * For example:
942 * "F1"
943 * "Backspace|Meta" for Apple command backspace.
944 * "a|Ctrl" for Control A
945 * "Delete Backspace|Meta" for Delete and Command Backspace
946 *
947 * @type {string}
948 */
949 shortcut_: '', 552 shortcut_: '',
950 get shortcut() { 553 get shortcut() {
951 return this.shortcut_; 554 return this.shortcut_;
952 }, 555 },
953 set shortcut(shortcut) { 556 set shortcut(shortcut) {
954 var oldShortcut = this.shortcut_; 557 var oldShortcut = this.shortcut_;
955 if (shortcut !== oldShortcut) { 558 if (shortcut !== oldShortcut) {
956 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { 559 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) {
957 return new KeyboardShortcut(shortcut); 560 return new KeyboardShortcut(shortcut);
958 }); 561 });
959 562
960 // Set this after the keyboardShortcuts_ since that might throw.
961 this.shortcut_ = shortcut; 563 this.shortcut_ = shortcut;
962 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, 564 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_,
963 oldShortcut); 565 oldShortcut);
964 } 566 }
965 }, 567 },
966 568
967 /**
968 * Whether the event object matches the shortcut for this command.
969 * @param {!Event} e The key event object.
970 * @return {boolean} Whether it matched or not.
971 */
972 matchesEvent: function(e) { 569 matchesEvent: function(e) {
973 if (!this.keyboardShortcuts_) 570 if (!this.keyboardShortcuts_)
974 return false; 571 return false;
975 572
976 return this.keyboardShortcuts_.some(function(keyboardShortcut) { 573 return this.keyboardShortcuts_.some(function(keyboardShortcut) {
977 return keyboardShortcut.matchesEvent(e); 574 return keyboardShortcut.matchesEvent(e);
978 }); 575 });
979 }, 576 },
980 }; 577 };
981 578
982 /**
983 * The label of the command.
984 */
985 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); 579 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR);
986 580
987 /**
988 * Whether the command is disabled or not.
989 */
990 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); 581 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR);
991 582
992 /**
993 * Whether the command is hidden or not.
994 */
995 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); 583 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR);
996 584
997 /**
998 * Whether the command is checked or not.
999 */
1000 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR); 585 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR);
1001 586
1002 /**
1003 * The flag that prevents the shortcut text from being displayed on menu.
1004 *
1005 * If false, the keyboard shortcut text (eg. "Ctrl+X" for the cut command)
1006 * is displayed in menu when the command is assosiated with a menu item.
1007 * Otherwise, no text is displayed.
1008 */
1009 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR); 587 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR);
1010 588
1011 /**
1012 * Dispatches a canExecute event on the target.
1013 * @param {!cr.ui.Command} command The command that we are testing for.
1014 * @param {EventTarget} target The target element to dispatch the event on.
1015 */
1016 function dispatchCanExecuteEvent(command, target) { 589 function dispatchCanExecuteEvent(command, target) {
1017 var e = new CanExecuteEvent(command); 590 var e = new CanExecuteEvent(command);
1018 target.dispatchEvent(e); 591 target.dispatchEvent(e);
1019 command.disabled = !e.canExecute; 592 command.disabled = !e.canExecute;
1020 } 593 }
1021 594
1022 /**
1023 * The command managers for different documents.
1024 */
1025 var commandManagers = {}; 595 var commandManagers = {};
1026 596
1027 /**
1028 * Keeps track of the focused element and updates the commands when the focus
1029 * changes.
1030 * @param {!Document} doc The document that we are managing the commands for.
1031 * @constructor
1032 */
1033 function CommandManager(doc) { 597 function CommandManager(doc) {
1034 doc.addEventListener('focus', this.handleFocus_.bind(this), true); 598 doc.addEventListener('focus', this.handleFocus_.bind(this), true);
1035 // Make sure we add the listener to the bubbling phase so that elements can
1036 // prevent the command.
1037 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false); 599 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false);
1038 } 600 }
1039 601
1040 /**
1041 * Initializes a command manager for the document as needed.
1042 * @param {!Document} doc The document to manage the commands for.
1043 */
1044 CommandManager.init = function(doc) { 602 CommandManager.init = function(doc) {
1045 var uid = cr.getUid(doc); 603 var uid = cr.getUid(doc);
1046 if (!(uid in commandManagers)) { 604 if (!(uid in commandManagers)) {
1047 commandManagers[uid] = new CommandManager(doc); 605 commandManagers[uid] = new CommandManager(doc);
1048 } 606 }
1049 }; 607 };
1050 608
1051 CommandManager.prototype = { 609 CommandManager.prototype = {
1052 610
1053 /**
1054 * Handles focus changes on the document.
1055 * @param {Event} e The focus event object.
1056 * @private
1057 * @suppress {checkTypes}
1058 * TODO(vitalyp): remove the suppression.
1059 */
1060 handleFocus_: function(e) { 611 handleFocus_: function(e) {
1061 var target = e.target; 612 var target = e.target;
1062 613
1063 // Ignore focus on a menu button or command item.
1064 if (target.menu || target.command) 614 if (target.menu || target.command)
1065 return; 615 return;
1066 616
1067 var commands = Array.prototype.slice.call( 617 var commands = Array.prototype.slice.call(
1068 target.ownerDocument.querySelectorAll('command')); 618 target.ownerDocument.querySelectorAll('command'));
1069 619
1070 commands.forEach(function(command) { 620 commands.forEach(function(command) {
1071 dispatchCanExecuteEvent(command, target); 621 dispatchCanExecuteEvent(command, target);
1072 }); 622 });
1073 }, 623 },
1074 624
1075 /**
1076 * Handles the keydown event and routes it to the right command.
1077 * @param {!Event} e The keydown event.
1078 */
1079 handleKeyDown_: function(e) { 625 handleKeyDown_: function(e) {
1080 var target = e.target; 626 var target = e.target;
1081 var commands = Array.prototype.slice.call( 627 var commands = Array.prototype.slice.call(
1082 target.ownerDocument.querySelectorAll('command')); 628 target.ownerDocument.querySelectorAll('command'));
1083 629
1084 for (var i = 0, command; command = commands[i]; i++) { 630 for (var i = 0, command; command = commands[i]; i++) {
1085 if (command.matchesEvent(e)) { 631 if (command.matchesEvent(e)) {
1086 // When invoking a command via a shortcut, we have to manually check
1087 // if it can be executed, since focus might not have been changed
1088 // what would have updated the command's state.
1089 command.canExecuteChange(); 632 command.canExecuteChange();
1090 633
1091 if (!command.disabled) { 634 if (!command.disabled) {
1092 e.preventDefault(); 635 e.preventDefault();
1093 // We do not want any other element to handle this.
1094 e.stopPropagation(); 636 e.stopPropagation();
1095 command.execute(); 637 command.execute();
1096 return; 638 return;
1097 } 639 }
1098 } 640 }
1099 } 641 }
1100 } 642 }
1101 }; 643 };
1102 644
1103 /**
1104 * The event type used for canExecute events.
1105 * @param {!cr.ui.Command} command The command that we are evaluating.
1106 * @extends {Event}
1107 * @constructor
1108 * @class
1109 */
1110 function CanExecuteEvent(command) { 645 function CanExecuteEvent(command) {
1111 var e = new Event('canExecute', {bubbles: true, cancelable: true}); 646 var e = new Event('canExecute', {bubbles: true, cancelable: true});
1112 e.__proto__ = CanExecuteEvent.prototype; 647 e.__proto__ = CanExecuteEvent.prototype;
1113 e.command = command; 648 e.command = command;
1114 return e; 649 return e;
1115 } 650 }
1116 651
1117 CanExecuteEvent.prototype = { 652 CanExecuteEvent.prototype = {
1118 __proto__: Event.prototype, 653 __proto__: Event.prototype,
1119 654
1120 /**
1121 * The current command
1122 * @type {cr.ui.Command}
1123 */
1124 command: null, 655 command: null,
1125 656
1126 /**
1127 * Whether the target can execute the command. Setting this also stops the
1128 * propagation and prevents the default. Callers can tell if an event has
1129 * been handled via |this.defaultPrevented|.
1130 * @type {boolean}
1131 */
1132 canExecute_: false, 657 canExecute_: false,
1133 get canExecute() { 658 get canExecute() {
1134 return this.canExecute_; 659 return this.canExecute_;
1135 }, 660 },
1136 set canExecute(canExecute) { 661 set canExecute(canExecute) {
1137 this.canExecute_ = !!canExecute; 662 this.canExecute_ = !!canExecute;
1138 this.stopPropagation(); 663 this.stopPropagation();
1139 this.preventDefault(); 664 this.preventDefault();
1140 } 665 }
1141 }; 666 };
1142 667
1143 // Export
1144 return { 668 return {
1145 Command: Command, 669 Command: Command,
1146 CanExecuteEvent: CanExecuteEvent 670 CanExecuteEvent: CanExecuteEvent
1147 }; 671 };
1148 }); 672 });
1149 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 673 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
1150 // Use of this source code is governed by a BSD-style license that can be 674 // Use of this source code is governed by a BSD-style license that can be
1151 // found in the LICENSE file. 675 // found in the LICENSE file.
1152 676
1153 // <include src-disabled="assert.js">
1154 677
1155 /**
1156 * Alias for document.getElementById. Found elements must be HTMLElements.
1157 * @param {string} id The ID of the element to find.
1158 * @return {HTMLElement} The found element or null if not found.
1159 */
1160 function $(id) { 678 function $(id) {
1161 var el = document.getElementById(id); 679 var el = document.getElementById(id);
1162 return el ? assertInstanceof(el, HTMLElement) : null; 680 return el ? assertInstanceof(el, HTMLElement) : null;
1163 } 681 }
1164 682
1165 // TODO(devlin): This should return SVGElement, but closure compiler is missing
1166 // those externs.
1167 /**
1168 * Alias for document.getElementById. Found elements must be SVGElements.
1169 * @param {string} id The ID of the element to find.
1170 * @return {Element} The found element or null if not found.
1171 */
1172 function getSVGElement(id) { 683 function getSVGElement(id) {
1173 var el = document.getElementById(id); 684 var el = document.getElementById(id);
1174 return el ? assertInstanceof(el, Element) : null; 685 return el ? assertInstanceof(el, Element) : null;
1175 } 686 }
1176 687
1177 /**
1178 * Add an accessible message to the page that will be announced to
1179 * users who have spoken feedback on, but will be invisible to all
1180 * other users. It's removed right away so it doesn't clutter the DOM.
1181 * @param {string} msg The text to be pronounced.
1182 */
1183 function announceAccessibleMessage(msg) { 688 function announceAccessibleMessage(msg) {
1184 var element = document.createElement('div'); 689 var element = document.createElement('div');
1185 element.setAttribute('aria-live', 'polite'); 690 element.setAttribute('aria-live', 'polite');
1186 element.style.position = 'relative'; 691 element.style.position = 'relative';
1187 element.style.left = '-9999px'; 692 element.style.left = '-9999px';
1188 element.style.height = '0px'; 693 element.style.height = '0px';
1189 element.innerText = msg; 694 element.innerText = msg;
1190 document.body.appendChild(element); 695 document.body.appendChild(element);
1191 window.setTimeout(function() { 696 window.setTimeout(function() {
1192 document.body.removeChild(element); 697 document.body.removeChild(element);
1193 }, 0); 698 }, 0);
1194 } 699 }
1195 700
1196 /**
1197 * Generates a CSS url string.
1198 * @param {string} s The URL to generate the CSS url for.
1199 * @return {string} The CSS url string.
1200 */
1201 function url(s) { 701 function url(s) {
1202 // http://www.w3.org/TR/css3-values/#uris
1203 // Parentheses, commas, whitespace characters, single quotes (') and double
1204 // quotes (") appearing in a URI must be escaped with a backslash
1205 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); 702 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
1206 // WebKit has a bug when it comes to URLs that end with \
1207 // https://bugs.webkit.org/show_bug.cgi?id=28885
1208 if (/\\\\$/.test(s2)) { 703 if (/\\\\$/.test(s2)) {
1209 // Add a space to work around the WebKit bug.
1210 s2 += ' '; 704 s2 += ' ';
1211 } 705 }
1212 return 'url("' + s2 + '")'; 706 return 'url("' + s2 + '")';
1213 } 707 }
1214 708
1215 /**
1216 * Parses query parameters from Location.
1217 * @param {Location} location The URL to generate the CSS url for.
1218 * @return {Object} Dictionary containing name value pairs for URL
1219 */
1220 function parseQueryParams(location) { 709 function parseQueryParams(location) {
1221 var params = {}; 710 var params = {};
1222 var query = unescape(location.search.substring(1)); 711 var query = unescape(location.search.substring(1));
1223 var vars = query.split('&'); 712 var vars = query.split('&');
1224 for (var i = 0; i < vars.length; i++) { 713 for (var i = 0; i < vars.length; i++) {
1225 var pair = vars[i].split('='); 714 var pair = vars[i].split('=');
1226 params[pair[0]] = pair[1]; 715 params[pair[0]] = pair[1];
1227 } 716 }
1228 return params; 717 return params;
1229 } 718 }
1230 719
1231 /**
1232 * Creates a new URL by appending or replacing the given query key and value.
1233 * Not supporting URL with username and password.
1234 * @param {Location} location The original URL.
1235 * @param {string} key The query parameter name.
1236 * @param {string} value The query parameter value.
1237 * @return {string} The constructed new URL.
1238 */
1239 function setQueryParam(location, key, value) { 720 function setQueryParam(location, key, value) {
1240 var query = parseQueryParams(location); 721 var query = parseQueryParams(location);
1241 query[encodeURIComponent(key)] = encodeURIComponent(value); 722 query[encodeURIComponent(key)] = encodeURIComponent(value);
1242 723
1243 var newQuery = ''; 724 var newQuery = '';
1244 for (var q in query) { 725 for (var q in query) {
1245 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; 726 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
1246 } 727 }
1247 728
1248 return location.origin + location.pathname + newQuery + location.hash; 729 return location.origin + location.pathname + newQuery + location.hash;
1249 } 730 }
1250 731
1251 /**
1252 * @param {Node} el A node to search for ancestors with |className|.
1253 * @param {string} className A class to search for.
1254 * @return {Element} A node with class of |className| or null if none is found.
1255 */
1256 function findAncestorByClass(el, className) { 732 function findAncestorByClass(el, className) {
1257 return /** @type {Element} */(findAncestor(el, function(el) { 733 return /** @type {Element} */(findAncestor(el, function(el) {
1258 return el.classList && el.classList.contains(className); 734 return el.classList && el.classList.contains(className);
1259 })); 735 }));
1260 } 736 }
1261 737
1262 /**
1263 * Return the first ancestor for which the {@code predicate} returns true.
1264 * @param {Node} node The node to check.
1265 * @param {function(Node):boolean} predicate The function that tests the
1266 * nodes.
1267 * @return {Node} The found ancestor or null if not found.
1268 */
1269 function findAncestor(node, predicate) { 738 function findAncestor(node, predicate) {
1270 var last = false; 739 var last = false;
1271 while (node != null && !(last = predicate(node))) { 740 while (node != null && !(last = predicate(node))) {
1272 node = node.parentNode; 741 node = node.parentNode;
1273 } 742 }
1274 return last ? node : null; 743 return last ? node : null;
1275 } 744 }
1276 745
1277 function swapDomNodes(a, b) { 746 function swapDomNodes(a, b) {
1278 var afterA = a.nextSibling; 747 var afterA = a.nextSibling;
1279 if (afterA == b) { 748 if (afterA == b) {
1280 swapDomNodes(b, a); 749 swapDomNodes(b, a);
1281 return; 750 return;
1282 } 751 }
1283 var aParent = a.parentNode; 752 var aParent = a.parentNode;
1284 b.parentNode.replaceChild(a, b); 753 b.parentNode.replaceChild(a, b);
1285 aParent.insertBefore(b, afterA); 754 aParent.insertBefore(b, afterA);
1286 } 755 }
1287 756
1288 /**
1289 * Disables text selection and dragging, with optional whitelist callbacks.
1290 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
1291 * is defined and returns true, the onselectionstart event will be
1292 * surpressed.
1293 * @param {function(Event):boolean=} opt_allowDragStart Unless this function
1294 * is defined and returns true, the ondragstart event will be surpressed.
1295 */
1296 function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { 757 function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
1297 // Disable text selection.
1298 document.onselectstart = function(e) { 758 document.onselectstart = function(e) {
1299 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) 759 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
1300 e.preventDefault(); 760 e.preventDefault();
1301 }; 761 };
1302 762
1303 // Disable dragging.
1304 document.ondragstart = function(e) { 763 document.ondragstart = function(e) {
1305 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) 764 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
1306 e.preventDefault(); 765 e.preventDefault();
1307 }; 766 };
1308 } 767 }
1309 768
1310 /**
1311 * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead.
1312 * Call this to stop clicks on <a href="#"> links from scrolling to the top of
1313 * the page (and possibly showing a # in the link).
1314 */
1315 function preventDefaultOnPoundLinkClicks() { 769 function preventDefaultOnPoundLinkClicks() {
1316 document.addEventListener('click', function(e) { 770 document.addEventListener('click', function(e) {
1317 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) { 771 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) {
1318 return el.tagName == 'A'; 772 return el.tagName == 'A';
1319 }); 773 });
1320 // Use getAttribute() to prevent URL normalization.
1321 if (anchor && anchor.getAttribute('href') == '#') 774 if (anchor && anchor.getAttribute('href') == '#')
1322 e.preventDefault(); 775 e.preventDefault();
1323 }); 776 });
1324 } 777 }
1325 778
1326 /**
1327 * Check the directionality of the page.
1328 * @return {boolean} True if Chrome is running an RTL UI.
1329 */
1330 function isRTL() { 779 function isRTL() {
1331 return document.documentElement.dir == 'rtl'; 780 return document.documentElement.dir == 'rtl';
1332 } 781 }
1333 782
1334 /**
1335 * Get an element that's known to exist by its ID. We use this instead of just
1336 * calling getElementById and not checking the result because this lets us
1337 * satisfy the JSCompiler type system.
1338 * @param {string} id The identifier name.
1339 * @return {!HTMLElement} the Element.
1340 */
1341 function getRequiredElement(id) { 783 function getRequiredElement(id) {
1342 return assertInstanceof($(id), HTMLElement, 784 return assertInstanceof($(id), HTMLElement,
1343 'Missing required element: ' + id); 785 'Missing required element: ' + id);
1344 } 786 }
1345 787
1346 /**
1347 * Query an element that's known to exist by a selector. We use this instead of
1348 * just calling querySelector and not checking the result because this lets us
1349 * satisfy the JSCompiler type system.
1350 * @param {string} selectors CSS selectors to query the element.
1351 * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional
1352 * context object for querySelector.
1353 * @return {!HTMLElement} the Element.
1354 */
1355 function queryRequiredElement(selectors, opt_context) { 788 function queryRequiredElement(selectors, opt_context) {
1356 var element = (opt_context || document).querySelector(selectors); 789 var element = (opt_context || document).querySelector(selectors);
1357 return assertInstanceof(element, HTMLElement, 790 return assertInstanceof(element, HTMLElement,
1358 'Missing required element: ' + selectors); 791 'Missing required element: ' + selectors);
1359 } 792 }
1360 793
1361 // Handle click on a link. If the link points to a chrome: or file: url, then
1362 // call into the browser to do the navigation.
1363 ['click', 'auxclick'].forEach(function(eventName) { 794 ['click', 'auxclick'].forEach(function(eventName) {
1364 document.addEventListener(eventName, function(e) { 795 document.addEventListener(eventName, function(e) {
1365 if (e.defaultPrevented) 796 if (e.defaultPrevented)
1366 return; 797 return;
1367 798
1368 var eventPath = e.path; 799 var eventPath = e.path;
1369 var anchor = null; 800 var anchor = null;
1370 if (eventPath) { 801 if (eventPath) {
1371 for (var i = 0; i < eventPath.length; i++) { 802 for (var i = 0; i < eventPath.length; i++) {
1372 var element = eventPath[i]; 803 var element = eventPath[i];
1373 if (element.tagName === 'A' && element.href) { 804 if (element.tagName === 'A' && element.href) {
1374 anchor = element; 805 anchor = element;
1375 break; 806 break;
1376 } 807 }
1377 } 808 }
1378 } 809 }
1379 810
1380 // Fallback if Event.path is not available.
1381 var el = e.target; 811 var el = e.target;
1382 if (!anchor && el.nodeType == Node.ELEMENT_NODE && 812 if (!anchor && el.nodeType == Node.ELEMENT_NODE &&
1383 el.webkitMatchesSelector('A, A *')) { 813 el.webkitMatchesSelector('A, A *')) {
1384 while (el.tagName != 'A') { 814 while (el.tagName != 'A') {
1385 el = el.parentElement; 815 el = el.parentElement;
1386 } 816 }
1387 anchor = el; 817 anchor = el;
1388 } 818 }
1389 819
1390 if (!anchor) 820 if (!anchor)
1391 return; 821 return;
1392 822
1393 anchor = /** @type {!HTMLAnchorElement} */(anchor); 823 anchor = /** @type {!HTMLAnchorElement} */(anchor);
1394 if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') && 824 if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') &&
1395 (e.button == 0 || e.button == 1)) { 825 (e.button == 0 || e.button == 1)) {
1396 chrome.send('navigateToUrl', [ 826 chrome.send('navigateToUrl', [
1397 anchor.href, 827 anchor.href,
1398 anchor.target, 828 anchor.target,
1399 e.button, 829 e.button,
1400 e.altKey, 830 e.altKey,
1401 e.ctrlKey, 831 e.ctrlKey,
1402 e.metaKey, 832 e.metaKey,
1403 e.shiftKey 833 e.shiftKey
1404 ]); 834 ]);
1405 e.preventDefault(); 835 e.preventDefault();
1406 } 836 }
1407 }); 837 });
1408 }); 838 });
1409 839
1410 /**
1411 * Creates a new URL which is the old URL with a GET param of key=value.
1412 * @param {string} url The base URL. There is not sanity checking on the URL so
1413 * it must be passed in a proper format.
1414 * @param {string} key The key of the param.
1415 * @param {string} value The value of the param.
1416 * @return {string} The new URL.
1417 */
1418 function appendParam(url, key, value) { 840 function appendParam(url, key, value) {
1419 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); 841 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
1420 842
1421 if (url.indexOf('?') == -1) 843 if (url.indexOf('?') == -1)
1422 return url + '?' + param; 844 return url + '?' + param;
1423 return url + '&' + param; 845 return url + '&' + param;
1424 } 846 }
1425 847
1426 /**
1427 * Creates an element of a specified type with a specified class name.
1428 * @param {string} type The node type.
1429 * @param {string} className The class name to use.
1430 * @return {Element} The created element.
1431 */
1432 function createElementWithClassName(type, className) { 848 function createElementWithClassName(type, className) {
1433 var elm = document.createElement(type); 849 var elm = document.createElement(type);
1434 elm.className = className; 850 elm.className = className;
1435 return elm; 851 return elm;
1436 } 852 }
1437 853
1438 /**
1439 * webkitTransitionEnd does not always fire (e.g. when animation is aborted
1440 * or when no paint happens during the animation). This function sets up
1441 * a timer and emulate the event if it is not fired when the timer expires.
1442 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
1443 * @param {number=} opt_timeOut The maximum wait time in milliseconds for the
1444 * webkitTransitionEnd to happen. If not specified, it is fetched from |el|
1445 * using the transitionDuration style value.
1446 */
1447 function ensureTransitionEndEvent(el, opt_timeOut) { 854 function ensureTransitionEndEvent(el, opt_timeOut) {
1448 if (opt_timeOut === undefined) { 855 if (opt_timeOut === undefined) {
1449 var style = getComputedStyle(el); 856 var style = getComputedStyle(el);
1450 opt_timeOut = parseFloat(style.transitionDuration) * 1000; 857 opt_timeOut = parseFloat(style.transitionDuration) * 1000;
1451 858
1452 // Give an additional 50ms buffer for the animation to complete.
1453 opt_timeOut += 50; 859 opt_timeOut += 50;
1454 } 860 }
1455 861
1456 var fired = false; 862 var fired = false;
1457 el.addEventListener('webkitTransitionEnd', function f(e) { 863 el.addEventListener('webkitTransitionEnd', function f(e) {
1458 el.removeEventListener('webkitTransitionEnd', f); 864 el.removeEventListener('webkitTransitionEnd', f);
1459 fired = true; 865 fired = true;
1460 }); 866 });
1461 window.setTimeout(function() { 867 window.setTimeout(function() {
1462 if (!fired) 868 if (!fired)
1463 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true); 869 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true);
1464 }, opt_timeOut); 870 }, opt_timeOut);
1465 } 871 }
1466 872
1467 /**
1468 * Alias for document.scrollTop getter.
1469 * @param {!HTMLDocument} doc The document node where information will be
1470 * queried from.
1471 * @return {number} The Y document scroll offset.
1472 */
1473 function scrollTopForDocument(doc) { 873 function scrollTopForDocument(doc) {
1474 return doc.documentElement.scrollTop || doc.body.scrollTop; 874 return doc.documentElement.scrollTop || doc.body.scrollTop;
1475 } 875 }
1476 876
1477 /**
1478 * Alias for document.scrollTop setter.
1479 * @param {!HTMLDocument} doc The document node where information will be
1480 * queried from.
1481 * @param {number} value The target Y scroll offset.
1482 */
1483 function setScrollTopForDocument(doc, value) { 877 function setScrollTopForDocument(doc, value) {
1484 doc.documentElement.scrollTop = doc.body.scrollTop = value; 878 doc.documentElement.scrollTop = doc.body.scrollTop = value;
1485 } 879 }
1486 880
1487 /**
1488 * Alias for document.scrollLeft getter.
1489 * @param {!HTMLDocument} doc The document node where information will be
1490 * queried from.
1491 * @return {number} The X document scroll offset.
1492 */
1493 function scrollLeftForDocument(doc) { 881 function scrollLeftForDocument(doc) {
1494 return doc.documentElement.scrollLeft || doc.body.scrollLeft; 882 return doc.documentElement.scrollLeft || doc.body.scrollLeft;
1495 } 883 }
1496 884
1497 /**
1498 * Alias for document.scrollLeft setter.
1499 * @param {!HTMLDocument} doc The document node where information will be
1500 * queried from.
1501 * @param {number} value The target X scroll offset.
1502 */
1503 function setScrollLeftForDocument(doc, value) { 885 function setScrollLeftForDocument(doc, value) {
1504 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; 886 doc.documentElement.scrollLeft = doc.body.scrollLeft = value;
1505 } 887 }
1506 888
1507 /**
1508 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
1509 * @param {string} original The original string.
1510 * @return {string} The string with all the characters mentioned above replaced.
1511 */
1512 function HTMLEscape(original) { 889 function HTMLEscape(original) {
1513 return original.replace(/&/g, '&amp;') 890 return original.replace(/&/g, '&amp;')
1514 .replace(/</g, '&lt;') 891 .replace(/</g, '&lt;')
1515 .replace(/>/g, '&gt;') 892 .replace(/>/g, '&gt;')
1516 .replace(/"/g, '&quot;') 893 .replace(/"/g, '&quot;')
1517 .replace(/'/g, '&#39;'); 894 .replace(/'/g, '&#39;');
1518 } 895 }
1519 896
1520 /**
1521 * Shortens the provided string (if necessary) to a string of length at most
1522 * |maxLength|.
1523 * @param {string} original The original string.
1524 * @param {number} maxLength The maximum length allowed for the string.
1525 * @return {string} The original string if its length does not exceed
1526 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
1527 * appended.
1528 */
1529 function elide(original, maxLength) { 897 function elide(original, maxLength) {
1530 if (original.length <= maxLength) 898 if (original.length <= maxLength)
1531 return original; 899 return original;
1532 return original.substring(0, maxLength - 1) + '\u2026'; 900 return original.substring(0, maxLength - 1) + '\u2026';
1533 } 901 }
1534 902
1535 /**
1536 * Quote a string so it can be used in a regular expression.
1537 * @param {string} str The source string.
1538 * @return {string} The escaped string.
1539 */
1540 function quoteString(str) { 903 function quoteString(str) {
1541 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); 904 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
1542 } 905 }
1543 906
1544 // <if expr="is_ios"> 907 // <if expr="is_ios">
1545 // Polyfill 'key' in KeyboardEvent for iOS.
1546 // This function is not intended to be complete but should
1547 // be sufficient enough to have iOS work correctly while
1548 // it does not support key yet.
1549 if (!('key' in KeyboardEvent.prototype)) { 908 if (!('key' in KeyboardEvent.prototype)) {
1550 Object.defineProperty(KeyboardEvent.prototype, 'key', { 909 Object.defineProperty(KeyboardEvent.prototype, 'key', {
1551 /** @this {KeyboardEvent} */ 910 /** @this {KeyboardEvent} */
1552 get: function () { 911 get: function () {
1553 // 0-9
1554 if (this.keyCode >= 0x30 && this.keyCode <= 0x39) 912 if (this.keyCode >= 0x30 && this.keyCode <= 0x39)
1555 return String.fromCharCode(this.keyCode); 913 return String.fromCharCode(this.keyCode);
1556 914
1557 // A-Z
1558 if (this.keyCode >= 0x41 && this.keyCode <= 0x5a) { 915 if (this.keyCode >= 0x41 && this.keyCode <= 0x5a) {
1559 var result = String.fromCharCode(this.keyCode).toLowerCase(); 916 var result = String.fromCharCode(this.keyCode).toLowerCase();
1560 if (this.shiftKey) 917 if (this.shiftKey)
1561 result = result.toUpperCase(); 918 result = result.toUpperCase();
1562 return result; 919 return result;
1563 } 920 }
1564 921
1565 // Special characters
1566 switch(this.keyCode) { 922 switch(this.keyCode) {
1567 case 0x08: return 'Backspace'; 923 case 0x08: return 'Backspace';
1568 case 0x09: return 'Tab'; 924 case 0x09: return 'Tab';
1569 case 0x0d: return 'Enter'; 925 case 0x0d: return 'Enter';
1570 case 0x10: return 'Shift'; 926 case 0x10: return 'Shift';
1571 case 0x11: return 'Control'; 927 case 0x11: return 'Control';
1572 case 0x12: return 'Alt'; 928 case 0x12: return 'Alt';
1573 case 0x1b: return 'Escape'; 929 case 0x1b: return 'Escape';
1574 case 0x20: return ' '; 930 case 0x20: return ' ';
1575 case 0x21: return 'PageUp'; 931 case 0x21: return 'PageUp';
(...skipping 24 matching lines...) Expand all
1600 case 0xdb: return '['; 956 case 0xdb: return '[';
1601 case 0xdd: return ']'; 957 case 0xdd: return ']';
1602 } 958 }
1603 return 'Unidentified'; 959 return 'Unidentified';
1604 } 960 }
1605 }); 961 });
1606 } else { 962 } else {
1607 window.console.log("KeyboardEvent.Key polyfill not required"); 963 window.console.log("KeyboardEvent.Key polyfill not required");
1608 } 964 }
1609 // </if> /* is_ios */ 965 // </if> /* is_ios */
1610 /**
1611 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
1612 * coordinate the flow of resize events between "resizers" (elements that cont rol the
1613 * size or hidden state of their children) and "resizables" (elements that nee d to be
1614 * notified when they are resized or un-hidden by their parents in order to ta ke
1615 * action on their new measurements).
1616 *
1617 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
1618 * their element definition and listen for the `iron-resize` event on themselv es.
1619 * This event will be fired when they become showing after having been hidden,
1620 * when they are resized explicitly by another resizable, or when the window h as been
1621 * resized.
1622 *
1623 * Note, the `iron-resize` event is non-bubbling.
1624 *
1625 * @polymerBehavior Polymer.IronResizableBehavior
1626 * @demo demo/index.html
1627 **/
1628 Polymer.IronResizableBehavior = { 966 Polymer.IronResizableBehavior = {
1629 properties: { 967 properties: {
1630 /**
1631 * The closest ancestor element that implements `IronResizableBehavior`.
1632 */
1633 _parentResizable: { 968 _parentResizable: {
1634 type: Object, 969 type: Object,
1635 observer: '_parentResizableChanged' 970 observer: '_parentResizableChanged'
1636 }, 971 },
1637 972
1638 /**
1639 * True if this element is currently notifying its descedant elements of
1640 * resize.
1641 */
1642 _notifyingDescendant: { 973 _notifyingDescendant: {
1643 type: Boolean, 974 type: Boolean,
1644 value: false 975 value: false
1645 } 976 }
1646 }, 977 },
1647 978
1648 listeners: { 979 listeners: {
1649 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' 980 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
1650 }, 981 },
1651 982
1652 created: function() { 983 created: function() {
1653 // We don't really need property effects on these, and also we want them
1654 // to be created before the `_parentResizable` observer fires:
1655 this._interestedResizables = []; 984 this._interestedResizables = [];
1656 this._boundNotifyResize = this.notifyResize.bind(this); 985 this._boundNotifyResize = this.notifyResize.bind(this);
1657 }, 986 },
1658 987
1659 attached: function() { 988 attached: function() {
1660 this.fire('iron-request-resize-notifications', null, { 989 this.fire('iron-request-resize-notifications', null, {
1661 node: this, 990 node: this,
1662 bubbles: true, 991 bubbles: true,
1663 cancelable: true 992 cancelable: true
1664 }); 993 });
1665 994
1666 if (!this._parentResizable) { 995 if (!this._parentResizable) {
1667 window.addEventListener('resize', this._boundNotifyResize); 996 window.addEventListener('resize', this._boundNotifyResize);
1668 this.notifyResize(); 997 this.notifyResize();
1669 } 998 }
1670 }, 999 },
1671 1000
1672 detached: function() { 1001 detached: function() {
1673 if (this._parentResizable) { 1002 if (this._parentResizable) {
1674 this._parentResizable.stopResizeNotificationsFor(this); 1003 this._parentResizable.stopResizeNotificationsFor(this);
1675 } else { 1004 } else {
1676 window.removeEventListener('resize', this._boundNotifyResize); 1005 window.removeEventListener('resize', this._boundNotifyResize);
1677 } 1006 }
1678 1007
1679 this._parentResizable = null; 1008 this._parentResizable = null;
1680 }, 1009 },
1681 1010
1682 /**
1683 * Can be called to manually notify a resizable and its descendant
1684 * resizables of a resize change.
1685 */
1686 notifyResize: function() { 1011 notifyResize: function() {
1687 if (!this.isAttached) { 1012 if (!this.isAttached) {
1688 return; 1013 return;
1689 } 1014 }
1690 1015
1691 this._interestedResizables.forEach(function(resizable) { 1016 this._interestedResizables.forEach(function(resizable) {
1692 if (this.resizerShouldNotify(resizable)) { 1017 if (this.resizerShouldNotify(resizable)) {
1693 this._notifyDescendant(resizable); 1018 this._notifyDescendant(resizable);
1694 } 1019 }
1695 }, this); 1020 }, this);
1696 1021
1697 this._fireResize(); 1022 this._fireResize();
1698 }, 1023 },
1699 1024
1700 /**
1701 * Used to assign the closest resizable ancestor to this resizable
1702 * if the ancestor detects a request for notifications.
1703 */
1704 assignParentResizable: function(parentResizable) { 1025 assignParentResizable: function(parentResizable) {
1705 this._parentResizable = parentResizable; 1026 this._parentResizable = parentResizable;
1706 }, 1027 },
1707 1028
1708 /**
1709 * Used to remove a resizable descendant from the list of descendants
1710 * that should be notified of a resize change.
1711 */
1712 stopResizeNotificationsFor: function(target) { 1029 stopResizeNotificationsFor: function(target) {
1713 var index = this._interestedResizables.indexOf(target); 1030 var index = this._interestedResizables.indexOf(target);
1714 1031
1715 if (index > -1) { 1032 if (index > -1) {
1716 this._interestedResizables.splice(index, 1); 1033 this._interestedResizables.splice(index, 1);
1717 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); 1034 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
1718 } 1035 }
1719 }, 1036 },
1720 1037
1721 /**
1722 * This method can be overridden to filter nested elements that should or
1723 * should not be notified by the current element. Return true if an element
1724 * should be notified, or false if it should not be notified.
1725 *
1726 * @param {HTMLElement} element A candidate descendant element that
1727 * implements `IronResizableBehavior`.
1728 * @return {boolean} True if the `element` should be notified of resize.
1729 */
1730 resizerShouldNotify: function(element) { return true; }, 1038 resizerShouldNotify: function(element) { return true; },
1731 1039
1732 _onDescendantIronResize: function(event) { 1040 _onDescendantIronResize: function(event) {
1733 if (this._notifyingDescendant) { 1041 if (this._notifyingDescendant) {
1734 event.stopPropagation(); 1042 event.stopPropagation();
1735 return; 1043 return;
1736 } 1044 }
1737 1045
1738 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
1739 // otherwise non-bubbling event "just work." We do it manually here for
1740 // the case where Polymer is not using shadow roots for whatever reason:
1741 if (!Polymer.Settings.useShadow) { 1046 if (!Polymer.Settings.useShadow) {
1742 this._fireResize(); 1047 this._fireResize();
1743 } 1048 }
1744 }, 1049 },
1745 1050
1746 _fireResize: function() { 1051 _fireResize: function() {
1747 this.fire('iron-resize', null, { 1052 this.fire('iron-resize', null, {
1748 node: this, 1053 node: this,
1749 bubbles: false 1054 bubbles: false
1750 }); 1055 });
(...skipping 17 matching lines...) Expand all
1768 event.stopPropagation(); 1073 event.stopPropagation();
1769 }, 1074 },
1770 1075
1771 _parentResizableChanged: function(parentResizable) { 1076 _parentResizableChanged: function(parentResizable) {
1772 if (parentResizable) { 1077 if (parentResizable) {
1773 window.removeEventListener('resize', this._boundNotifyResize); 1078 window.removeEventListener('resize', this._boundNotifyResize);
1774 } 1079 }
1775 }, 1080 },
1776 1081
1777 _notifyDescendant: function(descendant) { 1082 _notifyDescendant: function(descendant) {
1778 // NOTE(cdata): In IE10, attached is fired on children first, so it's
1779 // important not to notify them if the parent is not attached yet (or
1780 // else they will get redundantly notified when the parent attaches).
1781 if (!this.isAttached) { 1083 if (!this.isAttached) {
1782 return; 1084 return;
1783 } 1085 }
1784 1086
1785 this._notifyingDescendant = true; 1087 this._notifyingDescendant = true;
1786 descendant.notifyResize(); 1088 descendant.notifyResize();
1787 this._notifyingDescendant = false; 1089 this._notifyingDescendant = false;
1788 } 1090 }
1789 }; 1091 };
1790 (function() { 1092 (function() {
1791 'use strict'; 1093 'use strict';
1792 1094
1793 /**
1794 * Chrome uses an older version of DOM Level 3 Keyboard Events
1795 *
1796 * Most keys are labeled as text, but some are Unicode codepoints.
1797 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
1798 */
1799 var KEY_IDENTIFIER = { 1095 var KEY_IDENTIFIER = {
1800 'U+0008': 'backspace', 1096 'U+0008': 'backspace',
1801 'U+0009': 'tab', 1097 'U+0009': 'tab',
1802 'U+001B': 'esc', 1098 'U+001B': 'esc',
1803 'U+0020': 'space', 1099 'U+0020': 'space',
1804 'U+007F': 'del' 1100 'U+007F': 'del'
1805 }; 1101 };
1806 1102
1807 /**
1808 * Special table for KeyboardEvent.keyCode.
1809 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
1810 * than that.
1811 *
1812 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
1813 */
1814 var KEY_CODE = { 1103 var KEY_CODE = {
1815 8: 'backspace', 1104 8: 'backspace',
1816 9: 'tab', 1105 9: 'tab',
1817 13: 'enter', 1106 13: 'enter',
1818 27: 'esc', 1107 27: 'esc',
1819 33: 'pageup', 1108 33: 'pageup',
1820 34: 'pagedown', 1109 34: 'pagedown',
1821 35: 'end', 1110 35: 'end',
1822 36: 'home', 1111 36: 'home',
1823 32: 'space', 1112 32: 'space',
1824 37: 'left', 1113 37: 'left',
1825 38: 'up', 1114 38: 'up',
1826 39: 'right', 1115 39: 'right',
1827 40: 'down', 1116 40: 'down',
1828 46: 'del', 1117 46: 'del',
1829 106: '*' 1118 106: '*'
1830 }; 1119 };
1831 1120
1832 /**
1833 * MODIFIER_KEYS maps the short name for modifier keys used in a key
1834 * combo string to the property name that references those same keys
1835 * in a KeyboardEvent instance.
1836 */
1837 var MODIFIER_KEYS = { 1121 var MODIFIER_KEYS = {
1838 'shift': 'shiftKey', 1122 'shift': 'shiftKey',
1839 'ctrl': 'ctrlKey', 1123 'ctrl': 'ctrlKey',
1840 'alt': 'altKey', 1124 'alt': 'altKey',
1841 'meta': 'metaKey' 1125 'meta': 'metaKey'
1842 }; 1126 };
1843 1127
1844 /**
1845 * KeyboardEvent.key is mostly represented by printable character made by
1846 * the keyboard, with unprintable keys labeled nicely.
1847 *
1848 * However, on OS X, Alt+char can make a Unicode character that follows an
1849 * Apple-specific mapping. In this case, we fall back to .keyCode.
1850 */
1851 var KEY_CHAR = /[a-z0-9*]/; 1128 var KEY_CHAR = /[a-z0-9*]/;
1852 1129
1853 /**
1854 * Matches a keyIdentifier string.
1855 */
1856 var IDENT_CHAR = /U\+/; 1130 var IDENT_CHAR = /U\+/;
1857 1131
1858 /**
1859 * Matches arrow keys in Gecko 27.0+
1860 */
1861 var ARROW_KEY = /^arrow/; 1132 var ARROW_KEY = /^arrow/;
1862 1133
1863 /**
1864 * Matches space keys everywhere (notably including IE10's exceptional name
1865 * `spacebar`).
1866 */
1867 var SPACE_KEY = /^space(bar)?/; 1134 var SPACE_KEY = /^space(bar)?/;
1868 1135
1869 /**
1870 * Matches ESC key.
1871 *
1872 * Value from: http://w3c.github.io/uievents-key/#key-Escape
1873 */
1874 var ESC_KEY = /^escape$/; 1136 var ESC_KEY = /^escape$/;
1875 1137
1876 /**
1877 * Transforms the key.
1878 * @param {string} key The KeyBoardEvent.key
1879 * @param {Boolean} [noSpecialChars] Limits the transformation to
1880 * alpha-numeric characters.
1881 */
1882 function transformKey(key, noSpecialChars) { 1138 function transformKey(key, noSpecialChars) {
1883 var validKey = ''; 1139 var validKey = '';
1884 if (key) { 1140 if (key) {
1885 var lKey = key.toLowerCase(); 1141 var lKey = key.toLowerCase();
1886 if (lKey === ' ' || SPACE_KEY.test(lKey)) { 1142 if (lKey === ' ' || SPACE_KEY.test(lKey)) {
1887 validKey = 'space'; 1143 validKey = 'space';
1888 } else if (ESC_KEY.test(lKey)) { 1144 } else if (ESC_KEY.test(lKey)) {
1889 validKey = 'esc'; 1145 validKey = 'esc';
1890 } else if (lKey.length == 1) { 1146 } else if (lKey.length == 1) {
1891 if (!noSpecialChars || KEY_CHAR.test(lKey)) { 1147 if (!noSpecialChars || KEY_CHAR.test(lKey)) {
1892 validKey = lKey; 1148 validKey = lKey;
1893 } 1149 }
1894 } else if (ARROW_KEY.test(lKey)) { 1150 } else if (ARROW_KEY.test(lKey)) {
1895 validKey = lKey.replace('arrow', ''); 1151 validKey = lKey.replace('arrow', '');
1896 } else if (lKey == 'multiply') { 1152 } else if (lKey == 'multiply') {
1897 // numpad '*' can map to Multiply on IE/Windows
1898 validKey = '*'; 1153 validKey = '*';
1899 } else { 1154 } else {
1900 validKey = lKey; 1155 validKey = lKey;
1901 } 1156 }
1902 } 1157 }
1903 return validKey; 1158 return validKey;
1904 } 1159 }
1905 1160
1906 function transformKeyIdentifier(keyIdent) { 1161 function transformKeyIdentifier(keyIdent) {
1907 var validKey = ''; 1162 var validKey = '';
1908 if (keyIdent) { 1163 if (keyIdent) {
1909 if (keyIdent in KEY_IDENTIFIER) { 1164 if (keyIdent in KEY_IDENTIFIER) {
1910 validKey = KEY_IDENTIFIER[keyIdent]; 1165 validKey = KEY_IDENTIFIER[keyIdent];
1911 } else if (IDENT_CHAR.test(keyIdent)) { 1166 } else if (IDENT_CHAR.test(keyIdent)) {
1912 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); 1167 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
1913 validKey = String.fromCharCode(keyIdent).toLowerCase(); 1168 validKey = String.fromCharCode(keyIdent).toLowerCase();
1914 } else { 1169 } else {
1915 validKey = keyIdent.toLowerCase(); 1170 validKey = keyIdent.toLowerCase();
1916 } 1171 }
1917 } 1172 }
1918 return validKey; 1173 return validKey;
1919 } 1174 }
1920 1175
1921 function transformKeyCode(keyCode) { 1176 function transformKeyCode(keyCode) {
1922 var validKey = ''; 1177 var validKey = '';
1923 if (Number(keyCode)) { 1178 if (Number(keyCode)) {
1924 if (keyCode >= 65 && keyCode <= 90) { 1179 if (keyCode >= 65 && keyCode <= 90) {
1925 // ascii a-z
1926 // lowercase is 32 offset from uppercase
1927 validKey = String.fromCharCode(32 + keyCode); 1180 validKey = String.fromCharCode(32 + keyCode);
1928 } else if (keyCode >= 112 && keyCode <= 123) { 1181 } else if (keyCode >= 112 && keyCode <= 123) {
1929 // function keys f1-f12
1930 validKey = 'f' + (keyCode - 112); 1182 validKey = 'f' + (keyCode - 112);
1931 } else if (keyCode >= 48 && keyCode <= 57) { 1183 } else if (keyCode >= 48 && keyCode <= 57) {
1932 // top 0-9 keys
1933 validKey = String(keyCode - 48); 1184 validKey = String(keyCode - 48);
1934 } else if (keyCode >= 96 && keyCode <= 105) { 1185 } else if (keyCode >= 96 && keyCode <= 105) {
1935 // num pad 0-9
1936 validKey = String(keyCode - 96); 1186 validKey = String(keyCode - 96);
1937 } else { 1187 } else {
1938 validKey = KEY_CODE[keyCode]; 1188 validKey = KEY_CODE[keyCode];
1939 } 1189 }
1940 } 1190 }
1941 return validKey; 1191 return validKey;
1942 } 1192 }
1943 1193
1944 /**
1945 * Calculates the normalized key for a KeyboardEvent.
1946 * @param {KeyboardEvent} keyEvent
1947 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
1948 * transformation to alpha-numeric chars. This is useful with key
1949 * combinations like shift + 2, which on FF for MacOS produces
1950 * keyEvent.key = @
1951 * To get 2 returned, set noSpecialChars = true
1952 * To get @ returned, set noSpecialChars = false
1953 */
1954 function normalizedKeyForEvent(keyEvent, noSpecialChars) { 1194 function normalizedKeyForEvent(keyEvent, noSpecialChars) {
1955 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
1956 // .detail.key to support artificial keyboard events.
1957 return transformKey(keyEvent.key, noSpecialChars) || 1195 return transformKey(keyEvent.key, noSpecialChars) ||
1958 transformKeyIdentifier(keyEvent.keyIdentifier) || 1196 transformKeyIdentifier(keyEvent.keyIdentifier) ||
1959 transformKeyCode(keyEvent.keyCode) || 1197 transformKeyCode(keyEvent.keyCode) ||
1960 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no SpecialChars) || ''; 1198 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no SpecialChars) || '';
1961 } 1199 }
1962 1200
1963 function keyComboMatchesEvent(keyCombo, event) { 1201 function keyComboMatchesEvent(keyCombo, event) {
1964 // For combos with modifiers we support only alpha-numeric keys
1965 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); 1202 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
1966 return keyEvent === keyCombo.key && 1203 return keyEvent === keyCombo.key &&
1967 (!keyCombo.hasModifiers || ( 1204 (!keyCombo.hasModifiers || (
1968 !!event.shiftKey === !!keyCombo.shiftKey && 1205 !!event.shiftKey === !!keyCombo.shiftKey &&
1969 !!event.ctrlKey === !!keyCombo.ctrlKey && 1206 !!event.ctrlKey === !!keyCombo.ctrlKey &&
1970 !!event.altKey === !!keyCombo.altKey && 1207 !!event.altKey === !!keyCombo.altKey &&
1971 !!event.metaKey === !!keyCombo.metaKey) 1208 !!event.metaKey === !!keyCombo.metaKey)
1972 ); 1209 );
1973 } 1210 }
1974 1211
(...skipping 23 matching lines...) Expand all
1998 combo: keyComboString.split(':').shift() 1235 combo: keyComboString.split(':').shift()
1999 }); 1236 });
2000 } 1237 }
2001 1238
2002 function parseEventString(eventString) { 1239 function parseEventString(eventString) {
2003 return eventString.trim().split(' ').map(function(keyComboString) { 1240 return eventString.trim().split(' ').map(function(keyComboString) {
2004 return parseKeyComboString(keyComboString); 1241 return parseKeyComboString(keyComboString);
2005 }); 1242 });
2006 } 1243 }
2007 1244
2008 /**
2009 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
2010 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
2011 * The element takes care of browser differences with respect to Keyboard ev ents
2012 * and uses an expressive syntax to filter key presses.
2013 *
2014 * Use the `keyBindings` prototype property to express what combination of k eys
2015 * will trigger the callback. A key binding has the format
2016 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or
2017 * `"KEY:EVENT": "callback"` are valid as well). Some examples:
2018 *
2019 * keyBindings: {
2020 * 'space': '_onKeydown', // same as 'space:keydown'
2021 * 'shift+tab': '_onKeydown',
2022 * 'enter:keypress': '_onKeypress',
2023 * 'esc:keyup': '_onKeyup'
2024 * }
2025 *
2026 * The callback will receive with an event containing the following informat ion in `event.detail`:
2027 *
2028 * _onKeydown: function(event) {
2029 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab"
2030 * console.log(event.detail.key); // KEY only, e.g. "tab"
2031 * console.log(event.detail.event); // EVENT, e.g. "keydown"
2032 * console.log(event.detail.keyboardEvent); // the original KeyboardE vent
2033 * }
2034 *
2035 * Use the `keyEventTarget` attribute to set up event handlers on a specific
2036 * node.
2037 *
2038 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k eys-behavior/blob/master/demo/x-key-aware.html)
2039 * for an example.
2040 *
2041 * @demo demo/index.html
2042 * @polymerBehavior
2043 */
2044 Polymer.IronA11yKeysBehavior = { 1245 Polymer.IronA11yKeysBehavior = {
2045 properties: { 1246 properties: {
2046 /**
2047 * The EventTarget that will be firing relevant KeyboardEvents. Set it t o
2048 * `null` to disable the listeners.
2049 * @type {?EventTarget}
2050 */
2051 keyEventTarget: { 1247 keyEventTarget: {
2052 type: Object, 1248 type: Object,
2053 value: function() { 1249 value: function() {
2054 return this; 1250 return this;
2055 } 1251 }
2056 }, 1252 },
2057 1253
2058 /**
2059 * If true, this property will cause the implementing element to
2060 * automatically stop propagation on any handled KeyboardEvents.
2061 */
2062 stopKeyboardEventPropagation: { 1254 stopKeyboardEventPropagation: {
2063 type: Boolean, 1255 type: Boolean,
2064 value: false 1256 value: false
2065 }, 1257 },
2066 1258
2067 _boundKeyHandlers: { 1259 _boundKeyHandlers: {
2068 type: Array, 1260 type: Array,
2069 value: function() { 1261 value: function() {
2070 return []; 1262 return [];
2071 } 1263 }
2072 }, 1264 },
2073 1265
2074 // We use this due to a limitation in IE10 where instances will have
2075 // own properties of everything on the "prototype".
2076 _imperativeKeyBindings: { 1266 _imperativeKeyBindings: {
2077 type: Object, 1267 type: Object,
2078 value: function() { 1268 value: function() {
2079 return {}; 1269 return {};
2080 } 1270 }
2081 } 1271 }
2082 }, 1272 },
2083 1273
2084 observers: [ 1274 observers: [
2085 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' 1275 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
2086 ], 1276 ],
2087 1277
2088 1278
2089 /**
2090 * To be used to express what combination of keys will trigger the relati ve
2091 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}`
2092 * @type {Object}
2093 */
2094 keyBindings: {}, 1279 keyBindings: {},
2095 1280
2096 registered: function() { 1281 registered: function() {
2097 this._prepKeyBindings(); 1282 this._prepKeyBindings();
2098 }, 1283 },
2099 1284
2100 attached: function() { 1285 attached: function() {
2101 this._listenKeyEventListeners(); 1286 this._listenKeyEventListeners();
2102 }, 1287 },
2103 1288
2104 detached: function() { 1289 detached: function() {
2105 this._unlistenKeyEventListeners(); 1290 this._unlistenKeyEventListeners();
2106 }, 1291 },
2107 1292
2108 /**
2109 * Can be used to imperatively add a key binding to the implementing
2110 * element. This is the imperative equivalent of declaring a keybinding
2111 * in the `keyBindings` prototype property.
2112 */
2113 addOwnKeyBinding: function(eventString, handlerName) { 1293 addOwnKeyBinding: function(eventString, handlerName) {
2114 this._imperativeKeyBindings[eventString] = handlerName; 1294 this._imperativeKeyBindings[eventString] = handlerName;
2115 this._prepKeyBindings(); 1295 this._prepKeyBindings();
2116 this._resetKeyEventListeners(); 1296 this._resetKeyEventListeners();
2117 }, 1297 },
2118 1298
2119 /**
2120 * When called, will remove all imperatively-added key bindings.
2121 */
2122 removeOwnKeyBindings: function() { 1299 removeOwnKeyBindings: function() {
2123 this._imperativeKeyBindings = {}; 1300 this._imperativeKeyBindings = {};
2124 this._prepKeyBindings(); 1301 this._prepKeyBindings();
2125 this._resetKeyEventListeners(); 1302 this._resetKeyEventListeners();
2126 }, 1303 },
2127 1304
2128 /**
2129 * Returns true if a keyboard event matches `eventString`.
2130 *
2131 * @param {KeyboardEvent} event
2132 * @param {string} eventString
2133 * @return {boolean}
2134 */
2135 keyboardEventMatchesKeys: function(event, eventString) { 1305 keyboardEventMatchesKeys: function(event, eventString) {
2136 var keyCombos = parseEventString(eventString); 1306 var keyCombos = parseEventString(eventString);
2137 for (var i = 0; i < keyCombos.length; ++i) { 1307 for (var i = 0; i < keyCombos.length; ++i) {
2138 if (keyComboMatchesEvent(keyCombos[i], event)) { 1308 if (keyComboMatchesEvent(keyCombos[i], event)) {
2139 return true; 1309 return true;
2140 } 1310 }
2141 } 1311 }
2142 return false; 1312 return false;
2143 }, 1313 },
2144 1314
(...skipping 15 matching lines...) Expand all
2160 this._collectKeyBindings().forEach(function(keyBindings) { 1330 this._collectKeyBindings().forEach(function(keyBindings) {
2161 for (var eventString in keyBindings) { 1331 for (var eventString in keyBindings) {
2162 this._addKeyBinding(eventString, keyBindings[eventString]); 1332 this._addKeyBinding(eventString, keyBindings[eventString]);
2163 } 1333 }
2164 }, this); 1334 }, this);
2165 1335
2166 for (var eventString in this._imperativeKeyBindings) { 1336 for (var eventString in this._imperativeKeyBindings) {
2167 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]); 1337 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
2168 } 1338 }
2169 1339
2170 // Give precedence to combos with modifiers to be checked first.
2171 for (var eventName in this._keyBindings) { 1340 for (var eventName in this._keyBindings) {
2172 this._keyBindings[eventName].sort(function (kb1, kb2) { 1341 this._keyBindings[eventName].sort(function (kb1, kb2) {
2173 var b1 = kb1[0].hasModifiers; 1342 var b1 = kb1[0].hasModifiers;
2174 var b2 = kb2[0].hasModifiers; 1343 var b2 = kb2[0].hasModifiers;
2175 return (b1 === b2) ? 0 : b1 ? -1 : 1; 1344 return (b1 === b2) ? 0 : b1 ? -1 : 1;
2176 }) 1345 })
2177 } 1346 }
2178 }, 1347 },
2179 1348
2180 _addKeyBinding: function(eventString, handlerName) { 1349 _addKeyBinding: function(eventString, handlerName) {
(...skipping 30 matching lines...) Expand all
2211 }, this); 1380 }, this);
2212 }, 1381 },
2213 1382
2214 _unlistenKeyEventListeners: function() { 1383 _unlistenKeyEventListeners: function() {
2215 var keyHandlerTuple; 1384 var keyHandlerTuple;
2216 var keyEventTarget; 1385 var keyEventTarget;
2217 var eventName; 1386 var eventName;
2218 var boundKeyHandler; 1387 var boundKeyHandler;
2219 1388
2220 while (this._boundKeyHandlers.length) { 1389 while (this._boundKeyHandlers.length) {
2221 // My kingdom for block-scope binding and destructuring assignment..
2222 keyHandlerTuple = this._boundKeyHandlers.pop(); 1390 keyHandlerTuple = this._boundKeyHandlers.pop();
2223 keyEventTarget = keyHandlerTuple[0]; 1391 keyEventTarget = keyHandlerTuple[0];
2224 eventName = keyHandlerTuple[1]; 1392 eventName = keyHandlerTuple[1];
2225 boundKeyHandler = keyHandlerTuple[2]; 1393 boundKeyHandler = keyHandlerTuple[2];
2226 1394
2227 keyEventTarget.removeEventListener(eventName, boundKeyHandler); 1395 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
2228 } 1396 }
2229 }, 1397 },
2230 1398
2231 _onKeyBindingEvent: function(keyBindings, event) { 1399 _onKeyBindingEvent: function(keyBindings, event) {
2232 if (this.stopKeyboardEventPropagation) { 1400 if (this.stopKeyboardEventPropagation) {
2233 event.stopPropagation(); 1401 event.stopPropagation();
2234 } 1402 }
2235 1403
2236 // if event has been already prevented, don't do anything
2237 if (event.defaultPrevented) { 1404 if (event.defaultPrevented) {
2238 return; 1405 return;
2239 } 1406 }
2240 1407
2241 for (var i = 0; i < keyBindings.length; i++) { 1408 for (var i = 0; i < keyBindings.length; i++) {
2242 var keyCombo = keyBindings[i][0]; 1409 var keyCombo = keyBindings[i][0];
2243 var handlerName = keyBindings[i][1]; 1410 var handlerName = keyBindings[i][1];
2244 if (keyComboMatchesEvent(keyCombo, event)) { 1411 if (keyComboMatchesEvent(keyCombo, event)) {
2245 this._triggerKeyHandler(keyCombo, handlerName, event); 1412 this._triggerKeyHandler(keyCombo, handlerName, event);
2246 // exit the loop if eventDefault was prevented
2247 if (event.defaultPrevented) { 1413 if (event.defaultPrevented) {
2248 return; 1414 return;
2249 } 1415 }
2250 } 1416 }
2251 } 1417 }
2252 }, 1418 },
2253 1419
2254 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { 1420 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
2255 var detail = Object.create(keyCombo); 1421 var detail = Object.create(keyCombo);
2256 detail.keyboardEvent = keyboardEvent; 1422 detail.keyboardEvent = keyboardEvent;
2257 var event = new CustomEvent(keyCombo.event, { 1423 var event = new CustomEvent(keyCombo.event, {
2258 detail: detail, 1424 detail: detail,
2259 cancelable: true 1425 cancelable: true
2260 }); 1426 });
2261 this[handlerName].call(this, event); 1427 this[handlerName].call(this, event);
2262 if (event.defaultPrevented) { 1428 if (event.defaultPrevented) {
2263 keyboardEvent.preventDefault(); 1429 keyboardEvent.preventDefault();
2264 } 1430 }
2265 } 1431 }
2266 }; 1432 };
2267 })(); 1433 })();
2268 /**
2269 * `Polymer.IronScrollTargetBehavior` allows an element to respond to scroll e vents from a
2270 * designated scroll target.
2271 *
2272 * Elements that consume this behavior can override the `_scrollHandler`
2273 * method to add logic on the scroll event.
2274 *
2275 * @demo demo/scrolling-region.html Scrolling Region
2276 * @demo demo/document.html Document Element
2277 * @polymerBehavior
2278 */
2279 Polymer.IronScrollTargetBehavior = { 1434 Polymer.IronScrollTargetBehavior = {
2280 1435
2281 properties: { 1436 properties: {
2282 1437
2283 /**
2284 * Specifies the element that will handle the scroll event
2285 * on the behalf of the current element. This is typically a reference to an element,
2286 * but there are a few more posibilities:
2287 *
2288 * ### Elements id
2289 *
2290 *```html
2291 * <div id="scrollable-element" style="overflow: auto;">
2292 * <x-element scroll-target="scrollable-element">
2293 * \x3c!-- Content--\x3e
2294 * </x-element>
2295 * </div>
2296 *```
2297 * In this case, the `scrollTarget` will point to the outer div element.
2298 *
2299 * ### Document scrolling
2300 *
2301 * For document scrolling, you can use the reserved word `document`:
2302 *
2303 *```html
2304 * <x-element scroll-target="document">
2305 * \x3c!-- Content --\x3e
2306 * </x-element>
2307 *```
2308 *
2309 * ### Elements reference
2310 *
2311 *```js
2312 * appHeader.scrollTarget = document.querySelector('#scrollable-element');
2313 *```
2314 *
2315 * @type {HTMLElement}
2316 */
2317 scrollTarget: { 1438 scrollTarget: {
2318 type: HTMLElement, 1439 type: HTMLElement,
2319 value: function() { 1440 value: function() {
2320 return this._defaultScrollTarget; 1441 return this._defaultScrollTarget;
2321 } 1442 }
2322 } 1443 }
2323 }, 1444 },
2324 1445
2325 observers: [ 1446 observers: [
2326 '_scrollTargetChanged(scrollTarget, isAttached)' 1447 '_scrollTargetChanged(scrollTarget, isAttached)'
2327 ], 1448 ],
2328 1449
2329 _scrollTargetChanged: function(scrollTarget, isAttached) { 1450 _scrollTargetChanged: function(scrollTarget, isAttached) {
2330 var eventTarget; 1451 var eventTarget;
2331 1452
2332 if (this._oldScrollTarget) { 1453 if (this._oldScrollTarget) {
2333 eventTarget = this._oldScrollTarget === this._doc ? window : this._oldSc rollTarget; 1454 eventTarget = this._oldScrollTarget === this._doc ? window : this._oldSc rollTarget;
2334 eventTarget.removeEventListener('scroll', this._boundScrollHandler); 1455 eventTarget.removeEventListener('scroll', this._boundScrollHandler);
2335 this._oldScrollTarget = null; 1456 this._oldScrollTarget = null;
2336 } 1457 }
2337 1458
2338 if (!isAttached) { 1459 if (!isAttached) {
2339 return; 1460 return;
2340 } 1461 }
2341 // Support element id references
2342 if (scrollTarget === 'document') { 1462 if (scrollTarget === 'document') {
2343 1463
2344 this.scrollTarget = this._doc; 1464 this.scrollTarget = this._doc;
2345 1465
2346 } else if (typeof scrollTarget === 'string') { 1466 } else if (typeof scrollTarget === 'string') {
2347 1467
2348 this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] : 1468 this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] :
2349 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget); 1469 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget);
2350 1470
2351 } else if (this._isValidScrollTarget()) { 1471 } else if (this._isValidScrollTarget()) {
2352 1472
2353 eventTarget = scrollTarget === this._doc ? window : scrollTarget; 1473 eventTarget = scrollTarget === this._doc ? window : scrollTarget;
2354 this._boundScrollHandler = this._boundScrollHandler || this._scrollHandl er.bind(this); 1474 this._boundScrollHandler = this._boundScrollHandler || this._scrollHandl er.bind(this);
2355 this._oldScrollTarget = scrollTarget; 1475 this._oldScrollTarget = scrollTarget;
2356 1476
2357 eventTarget.addEventListener('scroll', this._boundScrollHandler); 1477 eventTarget.addEventListener('scroll', this._boundScrollHandler);
2358 } 1478 }
2359 }, 1479 },
2360 1480
2361 /**
2362 * Runs on every scroll event. Consumer of this behavior may override this m ethod.
2363 *
2364 * @protected
2365 */
2366 _scrollHandler: function scrollHandler() {}, 1481 _scrollHandler: function scrollHandler() {},
2367 1482
2368 /**
2369 * The default scroll target. Consumers of this behavior may want to customi ze
2370 * the default scroll target.
2371 *
2372 * @type {Element}
2373 */
2374 get _defaultScrollTarget() { 1483 get _defaultScrollTarget() {
2375 return this._doc; 1484 return this._doc;
2376 }, 1485 },
2377 1486
2378 /**
2379 * Shortcut for the document element
2380 *
2381 * @type {Element}
2382 */
2383 get _doc() { 1487 get _doc() {
2384 return this.ownerDocument.documentElement; 1488 return this.ownerDocument.documentElement;
2385 }, 1489 },
2386 1490
2387 /**
2388 * Gets the number of pixels that the content of an element is scrolled upwa rd.
2389 *
2390 * @type {number}
2391 */
2392 get _scrollTop() { 1491 get _scrollTop() {
2393 if (this._isValidScrollTarget()) { 1492 if (this._isValidScrollTarget()) {
2394 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol lTarget.scrollTop; 1493 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol lTarget.scrollTop;
2395 } 1494 }
2396 return 0; 1495 return 0;
2397 }, 1496 },
2398 1497
2399 /**
2400 * Gets the number of pixels that the content of an element is scrolled to t he left.
2401 *
2402 * @type {number}
2403 */
2404 get _scrollLeft() { 1498 get _scrollLeft() {
2405 if (this._isValidScrollTarget()) { 1499 if (this._isValidScrollTarget()) {
2406 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol lTarget.scrollLeft; 1500 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol lTarget.scrollLeft;
2407 } 1501 }
2408 return 0; 1502 return 0;
2409 }, 1503 },
2410 1504
2411 /**
2412 * Sets the number of pixels that the content of an element is scrolled upwa rd.
2413 *
2414 * @type {number}
2415 */
2416 set _scrollTop(top) { 1505 set _scrollTop(top) {
2417 if (this.scrollTarget === this._doc) { 1506 if (this.scrollTarget === this._doc) {
2418 window.scrollTo(window.pageXOffset, top); 1507 window.scrollTo(window.pageXOffset, top);
2419 } else if (this._isValidScrollTarget()) { 1508 } else if (this._isValidScrollTarget()) {
2420 this.scrollTarget.scrollTop = top; 1509 this.scrollTarget.scrollTop = top;
2421 } 1510 }
2422 }, 1511 },
2423 1512
2424 /**
2425 * Sets the number of pixels that the content of an element is scrolled to t he left.
2426 *
2427 * @type {number}
2428 */
2429 set _scrollLeft(left) { 1513 set _scrollLeft(left) {
2430 if (this.scrollTarget === this._doc) { 1514 if (this.scrollTarget === this._doc) {
2431 window.scrollTo(left, window.pageYOffset); 1515 window.scrollTo(left, window.pageYOffset);
2432 } else if (this._isValidScrollTarget()) { 1516 } else if (this._isValidScrollTarget()) {
2433 this.scrollTarget.scrollLeft = left; 1517 this.scrollTarget.scrollLeft = left;
2434 } 1518 }
2435 }, 1519 },
2436 1520
2437 /**
2438 * Scrolls the content to a particular place.
2439 *
2440 * @method scroll
2441 * @param {number} left The left position
2442 * @param {number} top The top position
2443 */
2444 scroll: function(left, top) { 1521 scroll: function(left, top) {
2445 if (this.scrollTarget === this._doc) { 1522 if (this.scrollTarget === this._doc) {
2446 window.scrollTo(left, top); 1523 window.scrollTo(left, top);
2447 } else if (this._isValidScrollTarget()) { 1524 } else if (this._isValidScrollTarget()) {
2448 this.scrollTarget.scrollLeft = left; 1525 this.scrollTarget.scrollLeft = left;
2449 this.scrollTarget.scrollTop = top; 1526 this.scrollTarget.scrollTop = top;
2450 } 1527 }
2451 }, 1528 },
2452 1529
2453 /**
2454 * Gets the width of the scroll target.
2455 *
2456 * @type {number}
2457 */
2458 get _scrollTargetWidth() { 1530 get _scrollTargetWidth() {
2459 if (this._isValidScrollTarget()) { 1531 if (this._isValidScrollTarget()) {
2460 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll Target.offsetWidth; 1532 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll Target.offsetWidth;
2461 } 1533 }
2462 return 0; 1534 return 0;
2463 }, 1535 },
2464 1536
2465 /**
2466 * Gets the height of the scroll target.
2467 *
2468 * @type {number}
2469 */
2470 get _scrollTargetHeight() { 1537 get _scrollTargetHeight() {
2471 if (this._isValidScrollTarget()) { 1538 if (this._isValidScrollTarget()) {
2472 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol lTarget.offsetHeight; 1539 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol lTarget.offsetHeight;
2473 } 1540 }
2474 return 0; 1541 return 0;
2475 }, 1542 },
2476 1543
2477 /**
2478 * Returns true if the scroll target is a valid HTMLElement.
2479 *
2480 * @return {boolean}
2481 */
2482 _isValidScrollTarget: function() { 1544 _isValidScrollTarget: function() {
2483 return this.scrollTarget instanceof HTMLElement; 1545 return this.scrollTarget instanceof HTMLElement;
2484 } 1546 }
2485 }; 1547 };
2486 (function() { 1548 (function() {
2487 1549
2488 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); 1550 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
2489 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; 1551 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
2490 var DEFAULT_PHYSICAL_COUNT = 3; 1552 var DEFAULT_PHYSICAL_COUNT = 3;
2491 var HIDDEN_Y = '-10000px'; 1553 var HIDDEN_Y = '-10000px';
2492 var DEFAULT_GRID_SIZE = 200; 1554 var DEFAULT_GRID_SIZE = 200;
2493 var SECRET_TABINDEX = -100; 1555 var SECRET_TABINDEX = -100;
2494 1556
2495 Polymer({ 1557 Polymer({
2496 1558
2497 is: 'iron-list', 1559 is: 'iron-list',
2498 1560
2499 properties: { 1561 properties: {
2500 1562
2501 /**
2502 * An array containing items determining how many instances of the templat e
2503 * to stamp and that that each template instance should bind to.
2504 */
2505 items: { 1563 items: {
2506 type: Array 1564 type: Array
2507 }, 1565 },
2508 1566
2509 /**
2510 * The max count of physical items the pool can extend to.
2511 */
2512 maxPhysicalCount: { 1567 maxPhysicalCount: {
2513 type: Number, 1568 type: Number,
2514 value: 500 1569 value: 500
2515 }, 1570 },
2516 1571
2517 /**
2518 * The name of the variable to add to the binding scope for the array
2519 * element associated with a given template instance.
2520 */
2521 as: { 1572 as: {
2522 type: String, 1573 type: String,
2523 value: 'item' 1574 value: 'item'
2524 }, 1575 },
2525 1576
2526 /**
2527 * The name of the variable to add to the binding scope with the index
2528 * for the row.
2529 */
2530 indexAs: { 1577 indexAs: {
2531 type: String, 1578 type: String,
2532 value: 'index' 1579 value: 'index'
2533 }, 1580 },
2534 1581
2535 /**
2536 * The name of the variable to add to the binding scope to indicate
2537 * if the row is selected.
2538 */
2539 selectedAs: { 1582 selectedAs: {
2540 type: String, 1583 type: String,
2541 value: 'selected' 1584 value: 'selected'
2542 }, 1585 },
2543 1586
2544 /**
2545 * When true, the list is rendered as a grid. Grid items must have
2546 * fixed width and height set via CSS. e.g.
2547 *
2548 * ```html
2549 * <iron-list grid>
2550 * <template>
2551 * <div style="width: 100px; height: 100px;"> 100x100 </div>
2552 * </template>
2553 * </iron-list>
2554 * ```
2555 */
2556 grid: { 1587 grid: {
2557 type: Boolean, 1588 type: Boolean,
2558 value: false, 1589 value: false,
2559 reflectToAttribute: true 1590 reflectToAttribute: true
2560 }, 1591 },
2561 1592
2562 /**
2563 * When true, tapping a row will select the item, placing its data model
2564 * in the set of selected items retrievable via the selection property.
2565 *
2566 * Note that tapping focusable elements within the list item will not
2567 * result in selection, since they are presumed to have their * own action .
2568 */
2569 selectionEnabled: { 1593 selectionEnabled: {
2570 type: Boolean, 1594 type: Boolean,
2571 value: false 1595 value: false
2572 }, 1596 },
2573 1597
2574 /**
2575 * When `multiSelection` is false, this is the currently selected item, or `null`
2576 * if no item is selected.
2577 */
2578 selectedItem: { 1598 selectedItem: {
2579 type: Object, 1599 type: Object,
2580 notify: true 1600 notify: true
2581 }, 1601 },
2582 1602
2583 /**
2584 * When `multiSelection` is true, this is an array that contains the selec ted items.
2585 */
2586 selectedItems: { 1603 selectedItems: {
2587 type: Object, 1604 type: Object,
2588 notify: true 1605 notify: true
2589 }, 1606 },
2590 1607
2591 /**
2592 * When `true`, multiple items may be selected at once (in this case,
2593 * `selected` is an array of currently selected items). When `false`,
2594 * only one item may be selected at a time.
2595 */
2596 multiSelection: { 1608 multiSelection: {
2597 type: Boolean, 1609 type: Boolean,
2598 value: false 1610 value: false
2599 } 1611 }
2600 }, 1612 },
2601 1613
2602 observers: [ 1614 observers: [
2603 '_itemsChanged(items.*)', 1615 '_itemsChanged(items.*)',
2604 '_selectionEnabledChanged(selectionEnabled)', 1616 '_selectionEnabledChanged(selectionEnabled)',
2605 '_multiSelectionChanged(multiSelection)', 1617 '_multiSelectionChanged(multiSelection)',
2606 '_setOverflow(scrollTarget)' 1618 '_setOverflow(scrollTarget)'
2607 ], 1619 ],
2608 1620
2609 behaviors: [ 1621 behaviors: [
2610 Polymer.Templatizer, 1622 Polymer.Templatizer,
2611 Polymer.IronResizableBehavior, 1623 Polymer.IronResizableBehavior,
2612 Polymer.IronA11yKeysBehavior, 1624 Polymer.IronA11yKeysBehavior,
2613 Polymer.IronScrollTargetBehavior 1625 Polymer.IronScrollTargetBehavior
2614 ], 1626 ],
2615 1627
2616 keyBindings: { 1628 keyBindings: {
2617 'up': '_didMoveUp', 1629 'up': '_didMoveUp',
2618 'down': '_didMoveDown', 1630 'down': '_didMoveDown',
2619 'enter': '_didEnter' 1631 'enter': '_didEnter'
2620 }, 1632 },
2621 1633
2622 /**
2623 * The ratio of hidden tiles that should remain in the scroll direction.
2624 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
2625 */
2626 _ratio: 0.5, 1634 _ratio: 0.5,
2627 1635
2628 /**
2629 * The padding-top value for the list.
2630 */
2631 _scrollerPaddingTop: 0, 1636 _scrollerPaddingTop: 0,
2632 1637
2633 /**
2634 * This value is the same as `scrollTop`.
2635 */
2636 _scrollPosition: 0, 1638 _scrollPosition: 0,
2637 1639
2638 /**
2639 * The sum of the heights of all the tiles in the DOM.
2640 */
2641 _physicalSize: 0, 1640 _physicalSize: 0,
2642 1641
2643 /**
2644 * The average `offsetHeight` of the tiles observed till now.
2645 */
2646 _physicalAverage: 0, 1642 _physicalAverage: 0,
2647 1643
2648 /**
2649 * The number of tiles which `offsetHeight` > 0 observed until now.
2650 */
2651 _physicalAverageCount: 0, 1644 _physicalAverageCount: 0,
2652 1645
2653 /**
2654 * The Y position of the item rendered in the `_physicalStart`
2655 * tile relative to the scrolling list.
2656 */
2657 _physicalTop: 0, 1646 _physicalTop: 0,
2658 1647
2659 /**
2660 * The number of items in the list.
2661 */
2662 _virtualCount: 0, 1648 _virtualCount: 0,
2663 1649
2664 /**
2665 * A map between an item key and its physical item index
2666 */
2667 _physicalIndexForKey: null, 1650 _physicalIndexForKey: null,
2668 1651
2669 /**
2670 * The estimated scroll height based on `_physicalAverage`
2671 */
2672 _estScrollHeight: 0, 1652 _estScrollHeight: 0,
2673 1653
2674 /**
2675 * The scroll height of the dom node
2676 */
2677 _scrollHeight: 0, 1654 _scrollHeight: 0,
2678 1655
2679 /**
2680 * The height of the list. This is referred as the viewport in the context o f list.
2681 */
2682 _viewportHeight: 0, 1656 _viewportHeight: 0,
2683 1657
2684 /**
2685 * The width of the list. This is referred as the viewport in the context of list.
2686 */
2687 _viewportWidth: 0, 1658 _viewportWidth: 0,
2688 1659
2689 /**
2690 * An array of DOM nodes that are currently in the tree
2691 * @type {?Array<!TemplatizerNode>}
2692 */
2693 _physicalItems: null, 1660 _physicalItems: null,
2694 1661
2695 /**
2696 * An array of heights for each item in `_physicalItems`
2697 * @type {?Array<number>}
2698 */
2699 _physicalSizes: null, 1662 _physicalSizes: null,
2700 1663
2701 /**
2702 * A cached value for the first visible index.
2703 * See `firstVisibleIndex`
2704 * @type {?number}
2705 */
2706 _firstVisibleIndexVal: null, 1664 _firstVisibleIndexVal: null,
2707 1665
2708 /**
2709 * A cached value for the last visible index.
2710 * See `lastVisibleIndex`
2711 * @type {?number}
2712 */
2713 _lastVisibleIndexVal: null, 1666 _lastVisibleIndexVal: null,
2714 1667
2715 /**
2716 * A Polymer collection for the items.
2717 * @type {?Polymer.Collection}
2718 */
2719 _collection: null, 1668 _collection: null,
2720 1669
2721 /**
2722 * True if the current item list was rendered for the first time
2723 * after attached.
2724 */
2725 _itemsRendered: false, 1670 _itemsRendered: false,
2726 1671
2727 /**
2728 * The page that is currently rendered.
2729 */
2730 _lastPage: null, 1672 _lastPage: null,
2731 1673
2732 /**
2733 * The max number of pages to render. One page is equivalent to the height o f the list.
2734 */
2735 _maxPages: 3, 1674 _maxPages: 3,
2736 1675
2737 /**
2738 * The currently focused physical item.
2739 */
2740 _focusedItem: null, 1676 _focusedItem: null,
2741 1677
2742 /**
2743 * The index of the `_focusedItem`.
2744 */
2745 _focusedIndex: -1, 1678 _focusedIndex: -1,
2746 1679
2747 /**
2748 * The the item that is focused if it is moved offscreen.
2749 * @private {?TemplatizerNode}
2750 */
2751 _offscreenFocusedItem: null, 1680 _offscreenFocusedItem: null,
2752 1681
2753 /**
2754 * The item that backfills the `_offscreenFocusedItem` in the physical items
2755 * list when that item is moved offscreen.
2756 */
2757 _focusBackfillItem: null, 1682 _focusBackfillItem: null,
2758 1683
2759 /**
2760 * The maximum items per row
2761 */
2762 _itemsPerRow: 1, 1684 _itemsPerRow: 1,
2763 1685
2764 /**
2765 * The width of each grid item
2766 */
2767 _itemWidth: 0, 1686 _itemWidth: 0,
2768 1687
2769 /**
2770 * The height of the row in grid layout.
2771 */
2772 _rowHeight: 0, 1688 _rowHeight: 0,
2773 1689
2774 /**
2775 * The bottom of the physical content.
2776 */
2777 get _physicalBottom() { 1690 get _physicalBottom() {
2778 return this._physicalTop + this._physicalSize; 1691 return this._physicalTop + this._physicalSize;
2779 }, 1692 },
2780 1693
2781 /**
2782 * The bottom of the scroll.
2783 */
2784 get _scrollBottom() { 1694 get _scrollBottom() {
2785 return this._scrollPosition + this._viewportHeight; 1695 return this._scrollPosition + this._viewportHeight;
2786 }, 1696 },
2787 1697
2788 /**
2789 * The n-th item rendered in the last physical item.
2790 */
2791 get _virtualEnd() { 1698 get _virtualEnd() {
2792 return this._virtualStart + this._physicalCount - 1; 1699 return this._virtualStart + this._physicalCount - 1;
2793 }, 1700 },
2794 1701
2795 /**
2796 * The height of the physical content that isn't on the screen.
2797 */
2798 get _hiddenContentSize() { 1702 get _hiddenContentSize() {
2799 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic alSize; 1703 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic alSize;
2800 return size - this._viewportHeight; 1704 return size - this._viewportHeight;
2801 }, 1705 },
2802 1706
2803 /**
2804 * The maximum scroll top value.
2805 */
2806 get _maxScrollTop() { 1707 get _maxScrollTop() {
2807 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin gTop; 1708 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin gTop;
2808 }, 1709 },
2809 1710
2810 /**
2811 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
2812 */
2813 _minVirtualStart: 0, 1711 _minVirtualStart: 0,
2814 1712
2815 /**
2816 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
2817 */
2818 get _maxVirtualStart() { 1713 get _maxVirtualStart() {
2819 return Math.max(0, this._virtualCount - this._physicalCount); 1714 return Math.max(0, this._virtualCount - this._physicalCount);
2820 }, 1715 },
2821 1716
2822 /**
2823 * The n-th item rendered in the `_physicalStart` tile.
2824 */
2825 _virtualStartVal: 0, 1717 _virtualStartVal: 0,
2826 1718
2827 set _virtualStart(val) { 1719 set _virtualStart(val) {
2828 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val)); 1720 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
2829 }, 1721 },
2830 1722
2831 get _virtualStart() { 1723 get _virtualStart() {
2832 return this._virtualStartVal || 0; 1724 return this._virtualStartVal || 0;
2833 }, 1725 },
2834 1726
2835 /**
2836 * The k-th tile that is at the top of the scrolling list.
2837 */
2838 _physicalStartVal: 0, 1727 _physicalStartVal: 0,
2839 1728
2840 set _physicalStart(val) { 1729 set _physicalStart(val) {
2841 this._physicalStartVal = val % this._physicalCount; 1730 this._physicalStartVal = val % this._physicalCount;
2842 if (this._physicalStartVal < 0) { 1731 if (this._physicalStartVal < 0) {
2843 this._physicalStartVal = this._physicalCount + this._physicalStartVal; 1732 this._physicalStartVal = this._physicalCount + this._physicalStartVal;
2844 } 1733 }
2845 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount; 1734 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
2846 }, 1735 },
2847 1736
2848 get _physicalStart() { 1737 get _physicalStart() {
2849 return this._physicalStartVal || 0; 1738 return this._physicalStartVal || 0;
2850 }, 1739 },
2851 1740
2852 /**
2853 * The number of tiles in the DOM.
2854 */
2855 _physicalCountVal: 0, 1741 _physicalCountVal: 0,
2856 1742
2857 set _physicalCount(val) { 1743 set _physicalCount(val) {
2858 this._physicalCountVal = val; 1744 this._physicalCountVal = val;
2859 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount; 1745 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
2860 }, 1746 },
2861 1747
2862 get _physicalCount() { 1748 get _physicalCount() {
2863 return this._physicalCountVal; 1749 return this._physicalCountVal;
2864 }, 1750 },
2865 1751
2866 /**
2867 * The k-th tile that is at the bottom of the scrolling list.
2868 */
2869 _physicalEnd: 0, 1752 _physicalEnd: 0,
2870 1753
2871 /**
2872 * An optimal physical size such that we will have enough physical items
2873 * to fill up the viewport and recycle when the user scrolls.
2874 *
2875 * This default value assumes that we will at least have the equivalent
2876 * to a viewport of physical items above and below the user's viewport.
2877 */
2878 get _optPhysicalSize() { 1754 get _optPhysicalSize() {
2879 if (this.grid) { 1755 if (this.grid) {
2880 return this._estRowsInView * this._rowHeight * this._maxPages; 1756 return this._estRowsInView * this._rowHeight * this._maxPages;
2881 } 1757 }
2882 return this._viewportHeight * this._maxPages; 1758 return this._viewportHeight * this._maxPages;
2883 }, 1759 },
2884 1760
2885 get _optPhysicalCount() { 1761 get _optPhysicalCount() {
2886 return this._estRowsInView * this._itemsPerRow * this._maxPages; 1762 return this._estRowsInView * this._itemsPerRow * this._maxPages;
2887 }, 1763 },
2888 1764
2889 /**
2890 * True if the current list is visible.
2891 */
2892 get _isVisible() { 1765 get _isVisible() {
2893 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight); 1766 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this. scrollTarget.offsetHeight);
2894 }, 1767 },
2895 1768
2896 /**
2897 * Gets the index of the first visible item in the viewport.
2898 *
2899 * @type {number}
2900 */
2901 get firstVisibleIndex() { 1769 get firstVisibleIndex() {
2902 if (this._firstVisibleIndexVal === null) { 1770 if (this._firstVisibleIndexVal === null) {
2903 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin gTop); 1771 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin gTop);
2904 1772
2905 this._firstVisibleIndexVal = this._iterateItems( 1773 this._firstVisibleIndexVal = this._iterateItems(
2906 function(pidx, vidx) { 1774 function(pidx, vidx) {
2907 physicalOffset += this._getPhysicalSizeIncrement(pidx); 1775 physicalOffset += this._getPhysicalSizeIncrement(pidx);
2908 1776
2909 if (physicalOffset > this._scrollPosition) { 1777 if (physicalOffset > this._scrollPosition) {
2910 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; 1778 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx;
2911 } 1779 }
2912 // Handle a partially rendered final row in grid mode
2913 if (this.grid && this._virtualCount - 1 === vidx) { 1780 if (this.grid && this._virtualCount - 1 === vidx) {
2914 return vidx - (vidx % this._itemsPerRow); 1781 return vidx - (vidx % this._itemsPerRow);
2915 } 1782 }
2916 }) || 0; 1783 }) || 0;
2917 } 1784 }
2918 return this._firstVisibleIndexVal; 1785 return this._firstVisibleIndexVal;
2919 }, 1786 },
2920 1787
2921 /**
2922 * Gets the index of the last visible item in the viewport.
2923 *
2924 * @type {number}
2925 */
2926 get lastVisibleIndex() { 1788 get lastVisibleIndex() {
2927 if (this._lastVisibleIndexVal === null) { 1789 if (this._lastVisibleIndexVal === null) {
2928 if (this.grid) { 1790 if (this.grid) {
2929 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i temsPerRow - 1; 1791 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i temsPerRow - 1;
2930 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex); 1792 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex);
2931 } else { 1793 } else {
2932 var physicalOffset = this._physicalTop; 1794 var physicalOffset = this._physicalTop;
2933 this._iterateItems(function(pidx, vidx) { 1795 this._iterateItems(function(pidx, vidx) {
2934 if (physicalOffset < this._scrollBottom) { 1796 if (physicalOffset < this._scrollBottom) {
2935 this._lastVisibleIndexVal = vidx; 1797 this._lastVisibleIndexVal = vidx;
2936 } else { 1798 } else {
2937 // Break _iterateItems
2938 return true; 1799 return true;
2939 } 1800 }
2940 physicalOffset += this._getPhysicalSizeIncrement(pidx); 1801 physicalOffset += this._getPhysicalSizeIncrement(pidx);
2941 }); 1802 });
2942 } 1803 }
2943 } 1804 }
2944 return this._lastVisibleIndexVal; 1805 return this._lastVisibleIndexVal;
2945 }, 1806 },
2946 1807
2947 get _defaultScrollTarget() { 1808 get _defaultScrollTarget() {
(...skipping 11 matching lines...) Expand all
2959 return Math.ceil(this._physicalCount / this._itemsPerRow); 1820 return Math.ceil(this._physicalCount / this._itemsPerRow);
2960 }, 1821 },
2961 1822
2962 ready: function() { 1823 ready: function() {
2963 this.addEventListener('focus', this._didFocus.bind(this), true); 1824 this.addEventListener('focus', this._didFocus.bind(this), true);
2964 }, 1825 },
2965 1826
2966 attached: function() { 1827 attached: function() {
2967 this.updateViewportBoundaries(); 1828 this.updateViewportBoundaries();
2968 this._render(); 1829 this._render();
2969 // `iron-resize` is fired when the list is attached if the event is added
2970 // before attached causing unnecessary work.
2971 this.listen(this, 'iron-resize', '_resizeHandler'); 1830 this.listen(this, 'iron-resize', '_resizeHandler');
2972 }, 1831 },
2973 1832
2974 detached: function() { 1833 detached: function() {
2975 this._itemsRendered = false; 1834 this._itemsRendered = false;
2976 this.unlisten(this, 'iron-resize', '_resizeHandler'); 1835 this.unlisten(this, 'iron-resize', '_resizeHandler');
2977 }, 1836 },
2978 1837
2979 /**
2980 * Set the overflow property if this element has its own scrolling region
2981 */
2982 _setOverflow: function(scrollTarget) { 1838 _setOverflow: function(scrollTarget) {
2983 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; 1839 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : '';
2984 this.style.overflow = scrollTarget === this ? 'auto' : ''; 1840 this.style.overflow = scrollTarget === this ? 'auto' : '';
2985 }, 1841 },
2986 1842
2987 /**
2988 * Invoke this method if you dynamically update the viewport's
2989 * size or CSS padding.
2990 *
2991 * @method updateViewportBoundaries
2992 */
2993 updateViewportBoundaries: function() { 1843 updateViewportBoundaries: function() {
2994 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : 1844 this._scrollerPaddingTop = this.scrollTarget === this ? 0 :
2995 parseInt(window.getComputedStyle(this)['padding-top'], 10); 1845 parseInt(window.getComputedStyle(this)['padding-top'], 10);
2996 1846
2997 this._viewportHeight = this._scrollTargetHeight; 1847 this._viewportHeight = this._scrollTargetHeight;
2998 if (this.grid) { 1848 if (this.grid) {
2999 this._updateGridMetrics(); 1849 this._updateGridMetrics();
3000 } 1850 }
3001 }, 1851 },
3002 1852
3003 /**
3004 * Update the models, the position of the
3005 * items in the viewport and recycle tiles as needed.
3006 */
3007 _scrollHandler: function() { 1853 _scrollHandler: function() {
3008 // clamp the `scrollTop` value
3009 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ; 1854 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop)) ;
3010 var delta = scrollTop - this._scrollPosition; 1855 var delta = scrollTop - this._scrollPosition;
3011 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m; 1856 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto m;
3012 var ratio = this._ratio; 1857 var ratio = this._ratio;
3013 var recycledTiles = 0; 1858 var recycledTiles = 0;
3014 var hiddenContentSize = this._hiddenContentSize; 1859 var hiddenContentSize = this._hiddenContentSize;
3015 var currentRatio = ratio; 1860 var currentRatio = ratio;
3016 var movingUp = []; 1861 var movingUp = [];
3017 1862
3018 // track the last `scrollTop`
3019 this._scrollPosition = scrollTop; 1863 this._scrollPosition = scrollTop;
3020 1864
3021 // clear cached visible indexes
3022 this._firstVisibleIndexVal = null; 1865 this._firstVisibleIndexVal = null;
3023 this._lastVisibleIndexVal = null; 1866 this._lastVisibleIndexVal = null;
3024 1867
3025 scrollBottom = this._scrollBottom; 1868 scrollBottom = this._scrollBottom;
3026 physicalBottom = this._physicalBottom; 1869 physicalBottom = this._physicalBottom;
3027 1870
3028 // random access
3029 if (Math.abs(delta) > this._physicalSize) { 1871 if (Math.abs(delta) > this._physicalSize) {
3030 this._physicalTop += delta; 1872 this._physicalTop += delta;
3031 recycledTiles = Math.round(delta / this._physicalAverage); 1873 recycledTiles = Math.round(delta / this._physicalAverage);
3032 } 1874 }
3033 // scroll up
3034 else if (delta < 0) { 1875 else if (delta < 0) {
3035 var topSpace = scrollTop - this._physicalTop; 1876 var topSpace = scrollTop - this._physicalTop;
3036 var virtualStart = this._virtualStart; 1877 var virtualStart = this._virtualStart;
3037 1878
3038 recycledTileSet = []; 1879 recycledTileSet = [];
3039 1880
3040 kth = this._physicalEnd; 1881 kth = this._physicalEnd;
3041 currentRatio = topSpace / hiddenContentSize; 1882 currentRatio = topSpace / hiddenContentSize;
3042 1883
3043 // move tiles from bottom to top
3044 while ( 1884 while (
3045 // approximate `currentRatio` to `ratio`
3046 currentRatio < ratio && 1885 currentRatio < ratio &&
3047 // recycle less physical items than the total
3048 recycledTiles < this._physicalCount && 1886 recycledTiles < this._physicalCount &&
3049 // ensure that these recycled tiles are needed
3050 virtualStart - recycledTiles > 0 && 1887 virtualStart - recycledTiles > 0 &&
3051 // ensure that the tile is not visible
3052 physicalBottom - this._getPhysicalSizeIncrement(kth) > scrollBottom 1888 physicalBottom - this._getPhysicalSizeIncrement(kth) > scrollBottom
3053 ) { 1889 ) {
3054 1890
3055 tileHeight = this._getPhysicalSizeIncrement(kth); 1891 tileHeight = this._getPhysicalSizeIncrement(kth);
3056 currentRatio += tileHeight / hiddenContentSize; 1892 currentRatio += tileHeight / hiddenContentSize;
3057 physicalBottom -= tileHeight; 1893 physicalBottom -= tileHeight;
3058 recycledTileSet.push(kth); 1894 recycledTileSet.push(kth);
3059 recycledTiles++; 1895 recycledTiles++;
3060 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; 1896 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1;
3061 } 1897 }
3062 1898
3063 movingUp = recycledTileSet; 1899 movingUp = recycledTileSet;
3064 recycledTiles = -recycledTiles; 1900 recycledTiles = -recycledTiles;
3065 } 1901 }
3066 // scroll down
3067 else if (delta > 0) { 1902 else if (delta > 0) {
3068 var bottomSpace = physicalBottom - scrollBottom; 1903 var bottomSpace = physicalBottom - scrollBottom;
3069 var virtualEnd = this._virtualEnd; 1904 var virtualEnd = this._virtualEnd;
3070 var lastVirtualItemIndex = this._virtualCount-1; 1905 var lastVirtualItemIndex = this._virtualCount-1;
3071 1906
3072 recycledTileSet = []; 1907 recycledTileSet = [];
3073 1908
3074 kth = this._physicalStart; 1909 kth = this._physicalStart;
3075 currentRatio = bottomSpace / hiddenContentSize; 1910 currentRatio = bottomSpace / hiddenContentSize;
3076 1911
3077 // move tiles from top to bottom
3078 while ( 1912 while (
3079 // approximate `currentRatio` to `ratio`
3080 currentRatio < ratio && 1913 currentRatio < ratio &&
3081 // recycle less physical items than the total
3082 recycledTiles < this._physicalCount && 1914 recycledTiles < this._physicalCount &&
3083 // ensure that these recycled tiles are needed
3084 virtualEnd + recycledTiles < lastVirtualItemIndex && 1915 virtualEnd + recycledTiles < lastVirtualItemIndex &&
3085 // ensure that the tile is not visible
3086 this._physicalTop + this._getPhysicalSizeIncrement(kth) < scrollTop 1916 this._physicalTop + this._getPhysicalSizeIncrement(kth) < scrollTop
3087 ) { 1917 ) {
3088 1918
3089 tileHeight = this._getPhysicalSizeIncrement(kth); 1919 tileHeight = this._getPhysicalSizeIncrement(kth);
3090 currentRatio += tileHeight / hiddenContentSize; 1920 currentRatio += tileHeight / hiddenContentSize;
3091 1921
3092 this._physicalTop += tileHeight; 1922 this._physicalTop += tileHeight;
3093 recycledTileSet.push(kth); 1923 recycledTileSet.push(kth);
3094 recycledTiles++; 1924 recycledTiles++;
3095 kth = (kth + 1) % this._physicalCount; 1925 kth = (kth + 1) % this._physicalCount;
3096 } 1926 }
3097 } 1927 }
3098 1928
3099 if (recycledTiles === 0) { 1929 if (recycledTiles === 0) {
3100 // Try to increase the pool if the list's client height isn't filled up with physical items
3101 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { 1930 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) {
3102 this._increasePoolIfNeeded(); 1931 this._increasePoolIfNeeded();
3103 } 1932 }
3104 } else { 1933 } else {
3105 this._virtualStart = this._virtualStart + recycledTiles; 1934 this._virtualStart = this._virtualStart + recycledTiles;
3106 this._physicalStart = this._physicalStart + recycledTiles; 1935 this._physicalStart = this._physicalStart + recycledTiles;
3107 this._update(recycledTileSet, movingUp); 1936 this._update(recycledTileSet, movingUp);
3108 } 1937 }
3109 }, 1938 },
3110 1939
3111 /**
3112 * Update the list of items, starting from the `_virtualStart` item.
3113 * @param {!Array<number>=} itemSet
3114 * @param {!Array<number>=} movingUp
3115 */
3116 _update: function(itemSet, movingUp) { 1940 _update: function(itemSet, movingUp) {
3117 // manage focus
3118 this._manageFocus(); 1941 this._manageFocus();
3119 // update models
3120 this._assignModels(itemSet); 1942 this._assignModels(itemSet);
3121 // measure heights
3122 this._updateMetrics(itemSet); 1943 this._updateMetrics(itemSet);
3123 // adjust offset after measuring
3124 if (movingUp) { 1944 if (movingUp) {
3125 while (movingUp.length) { 1945 while (movingUp.length) {
3126 var idx = movingUp.pop(); 1946 var idx = movingUp.pop();
3127 this._physicalTop -= this._getPhysicalSizeIncrement(idx); 1947 this._physicalTop -= this._getPhysicalSizeIncrement(idx);
3128 } 1948 }
3129 } 1949 }
3130 // update the position of the items
3131 this._positionItems(); 1950 this._positionItems();
3132 // set the scroller size
3133 this._updateScrollerSize(); 1951 this._updateScrollerSize();
3134 // increase the pool of physical items
3135 this._increasePoolIfNeeded(); 1952 this._increasePoolIfNeeded();
3136 }, 1953 },
3137 1954
3138 /**
3139 * Creates a pool of DOM elements and attaches them to the local dom.
3140 */
3141 _createPool: function(size) { 1955 _createPool: function(size) {
3142 var physicalItems = new Array(size); 1956 var physicalItems = new Array(size);
3143 1957
3144 this._ensureTemplatized(); 1958 this._ensureTemplatized();
3145 1959
3146 for (var i = 0; i < size; i++) { 1960 for (var i = 0; i < size; i++) {
3147 var inst = this.stamp(null); 1961 var inst = this.stamp(null);
3148 // First element child is item; Safari doesn't support children[0]
3149 // on a doc fragment
3150 physicalItems[i] = inst.root.querySelector('*'); 1962 physicalItems[i] = inst.root.querySelector('*');
3151 Polymer.dom(this).appendChild(inst.root); 1963 Polymer.dom(this).appendChild(inst.root);
3152 } 1964 }
3153 return physicalItems; 1965 return physicalItems;
3154 }, 1966 },
3155 1967
3156 /**
3157 * Increases the pool of physical items only if needed.
3158 *
3159 * @return {boolean} True if the pool was increased.
3160 */
3161 _increasePoolIfNeeded: function() { 1968 _increasePoolIfNeeded: function() {
3162 // Base case 1: the list has no height.
3163 if (this._viewportHeight === 0) { 1969 if (this._viewportHeight === 0) {
3164 return false; 1970 return false;
3165 } 1971 }
3166 // Base case 2: If the physical size is optimal and the list's client heig ht is full
3167 // with physical items, don't increase the pool.
3168 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi s._physicalTop <= this._scrollPosition; 1972 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi s._physicalTop <= this._scrollPosition;
3169 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { 1973 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) {
3170 return false; 1974 return false;
3171 } 1975 }
3172 // this value should range between [0 <= `currentPage` <= `_maxPages`]
3173 var currentPage = Math.floor(this._physicalSize / this._viewportHeight); 1976 var currentPage = Math.floor(this._physicalSize / this._viewportHeight);
3174 1977
3175 if (currentPage === 0) { 1978 if (currentPage === 0) {
3176 // fill the first page
3177 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph ysicalCount * 0.5))); 1979 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph ysicalCount * 0.5)));
3178 } else if (this._lastPage !== currentPage && isClientHeightFull) { 1980 } else if (this._lastPage !== currentPage && isClientHeightFull) {
3179 // paint the page and defer the next increase
3180 // wait 16ms which is rough enough to get paint cycle.
3181 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa sePool.bind(this, this._itemsPerRow), 16)); 1981 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa sePool.bind(this, this._itemsPerRow), 16));
3182 } else { 1982 } else {
3183 // fill the rest of the pages
3184 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow)) ; 1983 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow)) ;
3185 } 1984 }
3186 1985
3187 this._lastPage = currentPage; 1986 this._lastPage = currentPage;
3188 1987
3189 return true; 1988 return true;
3190 }, 1989 },
3191 1990
3192 /**
3193 * Increases the pool size.
3194 */
3195 _increasePool: function(missingItems) { 1991 _increasePool: function(missingItems) {
3196 var nextPhysicalCount = Math.min( 1992 var nextPhysicalCount = Math.min(
3197 this._physicalCount + missingItems, 1993 this._physicalCount + missingItems,
3198 this._virtualCount - this._virtualStart, 1994 this._virtualCount - this._virtualStart,
3199 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) 1995 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT)
3200 ); 1996 );
3201 var prevPhysicalCount = this._physicalCount; 1997 var prevPhysicalCount = this._physicalCount;
3202 var delta = nextPhysicalCount - prevPhysicalCount; 1998 var delta = nextPhysicalCount - prevPhysicalCount;
3203 1999
3204 if (delta <= 0) { 2000 if (delta <= 0) {
3205 return; 2001 return;
3206 } 2002 }
3207 2003
3208 [].push.apply(this._physicalItems, this._createPool(delta)); 2004 [].push.apply(this._physicalItems, this._createPool(delta));
3209 [].push.apply(this._physicalSizes, new Array(delta)); 2005 [].push.apply(this._physicalSizes, new Array(delta));
3210 2006
3211 this._physicalCount = prevPhysicalCount + delta; 2007 this._physicalCount = prevPhysicalCount + delta;
3212 2008
3213 // update the physical start if we need to preserve the model of the focus ed item.
3214 // In this situation, the focused item is currently rendered and its model would
3215 // have changed after increasing the pool if the physical start remained u nchanged.
3216 if (this._physicalStart > this._physicalEnd && 2009 if (this._physicalStart > this._physicalEnd &&
3217 this._isIndexRendered(this._focusedIndex) && 2010 this._isIndexRendered(this._focusedIndex) &&
3218 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { 2011 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) {
3219 this._physicalStart = this._physicalStart + delta; 2012 this._physicalStart = this._physicalStart + delta;
3220 } 2013 }
3221 this._update(); 2014 this._update();
3222 }, 2015 },
3223 2016
3224 /**
3225 * Render a new list of items. This method does exactly the same as `update` ,
3226 * but it also ensures that only one `update` cycle is created.
3227 */
3228 _render: function() { 2017 _render: function() {
3229 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; 2018 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
3230 2019
3231 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) { 2020 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
3232 this._lastPage = 0; 2021 this._lastPage = 0;
3233 this._update(); 2022 this._update();
3234 this._itemsRendered = true; 2023 this._itemsRendered = true;
3235 } 2024 }
3236 }, 2025 },
3237 2026
3238 /**
3239 * Templetizes the user template.
3240 */
3241 _ensureTemplatized: function() { 2027 _ensureTemplatized: function() {
3242 if (!this.ctor) { 2028 if (!this.ctor) {
3243 // Template instance props that should be excluded from forwarding
3244 var props = {}; 2029 var props = {};
3245 props.__key__ = true; 2030 props.__key__ = true;
3246 props[this.as] = true; 2031 props[this.as] = true;
3247 props[this.indexAs] = true; 2032 props[this.indexAs] = true;
3248 props[this.selectedAs] = true; 2033 props[this.selectedAs] = true;
3249 props.tabIndex = true; 2034 props.tabIndex = true;
3250 2035
3251 this._instanceProps = props; 2036 this._instanceProps = props;
3252 this._userTemplate = Polymer.dom(this).querySelector('template'); 2037 this._userTemplate = Polymer.dom(this).querySelector('template');
3253 2038
3254 if (this._userTemplate) { 2039 if (this._userTemplate) {
3255 this.templatize(this._userTemplate); 2040 this.templatize(this._userTemplate);
3256 } else { 2041 } else {
3257 console.warn('iron-list requires a template to be provided in light-do m'); 2042 console.warn('iron-list requires a template to be provided in light-do m');
3258 } 2043 }
3259 } 2044 }
3260 }, 2045 },
3261 2046
3262 /**
3263 * Implements extension point from Templatizer mixin.
3264 */
3265 _getStampedChildren: function() { 2047 _getStampedChildren: function() {
3266 return this._physicalItems; 2048 return this._physicalItems;
3267 }, 2049 },
3268 2050
3269 /**
3270 * Implements extension point from Templatizer
3271 * Called as a side effect of a template instance path change, responsible
3272 * for notifying items.<key-for-instance>.<path> change up to host.
3273 */
3274 _forwardInstancePath: function(inst, path, value) { 2051 _forwardInstancePath: function(inst, path, value) {
3275 if (path.indexOf(this.as + '.') === 0) { 2052 if (path.indexOf(this.as + '.') === 0) {
3276 this.notifyPath('items.' + inst.__key__ + '.' + 2053 this.notifyPath('items.' + inst.__key__ + '.' +
3277 path.slice(this.as.length + 1), value); 2054 path.slice(this.as.length + 1), value);
3278 } 2055 }
3279 }, 2056 },
3280 2057
3281 /**
3282 * Implements extension point from Templatizer mixin
3283 * Called as side-effect of a host property change, responsible for
3284 * notifying parent path change on each row.
3285 */
3286 _forwardParentProp: function(prop, value) { 2058 _forwardParentProp: function(prop, value) {
3287 if (this._physicalItems) { 2059 if (this._physicalItems) {
3288 this._physicalItems.forEach(function(item) { 2060 this._physicalItems.forEach(function(item) {
3289 item._templateInstance[prop] = value; 2061 item._templateInstance[prop] = value;
3290 }, this); 2062 }, this);
3291 } 2063 }
3292 }, 2064 },
3293 2065
3294 /**
3295 * Implements extension point from Templatizer
3296 * Called as side-effect of a host path change, responsible for
3297 * notifying parent.<path> path change on each row.
3298 */
3299 _forwardParentPath: function(path, value) { 2066 _forwardParentPath: function(path, value) {
3300 if (this._physicalItems) { 2067 if (this._physicalItems) {
3301 this._physicalItems.forEach(function(item) { 2068 this._physicalItems.forEach(function(item) {
3302 item._templateInstance.notifyPath(path, value, true); 2069 item._templateInstance.notifyPath(path, value, true);
3303 }, this); 2070 }, this);
3304 } 2071 }
3305 }, 2072 },
3306 2073
3307 /**
3308 * Called as a side effect of a host items.<key>.<path> path change,
3309 * responsible for notifying item.<path> changes.
3310 */
3311 _forwardItemPath: function(path, value) { 2074 _forwardItemPath: function(path, value) {
3312 if (!this._physicalIndexForKey) { 2075 if (!this._physicalIndexForKey) {
3313 return; 2076 return;
3314 } 2077 }
3315 var dot = path.indexOf('.'); 2078 var dot = path.indexOf('.');
3316 var key = path.substring(0, dot < 0 ? path.length : dot); 2079 var key = path.substring(0, dot < 0 ? path.length : dot);
3317 var idx = this._physicalIndexForKey[key]; 2080 var idx = this._physicalIndexForKey[key];
3318 var offscreenItem = this._offscreenFocusedItem; 2081 var offscreenItem = this._offscreenFocusedItem;
3319 var el = offscreenItem && offscreenItem._templateInstance.__key__ === key ? 2082 var el = offscreenItem && offscreenItem._templateInstance.__key__ === key ?
3320 offscreenItem : this._physicalItems[idx]; 2083 offscreenItem : this._physicalItems[idx];
3321 2084
3322 if (!el || el._templateInstance.__key__ !== key) { 2085 if (!el || el._templateInstance.__key__ !== key) {
3323 return; 2086 return;
3324 } 2087 }
3325 if (dot >= 0) { 2088 if (dot >= 0) {
3326 path = this.as + '.' + path.substring(dot+1); 2089 path = this.as + '.' + path.substring(dot+1);
3327 el._templateInstance.notifyPath(path, value, true); 2090 el._templateInstance.notifyPath(path, value, true);
3328 } else { 2091 } else {
3329 // Update selection if needed
3330 var currentItem = el._templateInstance[this.as]; 2092 var currentItem = el._templateInstance[this.as];
3331 if (Array.isArray(this.selectedItems)) { 2093 if (Array.isArray(this.selectedItems)) {
3332 for (var i = 0; i < this.selectedItems.length; i++) { 2094 for (var i = 0; i < this.selectedItems.length; i++) {
3333 if (this.selectedItems[i] === currentItem) { 2095 if (this.selectedItems[i] === currentItem) {
3334 this.set('selectedItems.' + i, value); 2096 this.set('selectedItems.' + i, value);
3335 break; 2097 break;
3336 } 2098 }
3337 } 2099 }
3338 } else if (this.selectedItem === currentItem) { 2100 } else if (this.selectedItem === currentItem) {
3339 this.set('selectedItem', value); 2101 this.set('selectedItem', value);
3340 } 2102 }
3341 el._templateInstance[this.as] = value; 2103 el._templateInstance[this.as] = value;
3342 } 2104 }
3343 }, 2105 },
3344 2106
3345 /**
3346 * Called when the items have changed. That is, ressignments
3347 * to `items`, splices or updates to a single item.
3348 */
3349 _itemsChanged: function(change) { 2107 _itemsChanged: function(change) {
3350 if (change.path === 'items') { 2108 if (change.path === 'items') {
3351 // reset items
3352 this._virtualStart = 0; 2109 this._virtualStart = 0;
3353 this._physicalTop = 0; 2110 this._physicalTop = 0;
3354 this._virtualCount = this.items ? this.items.length : 0; 2111 this._virtualCount = this.items ? this.items.length : 0;
3355 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l; 2112 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
3356 this._physicalIndexForKey = {}; 2113 this._physicalIndexForKey = {};
3357 this._firstVisibleIndexVal = null; 2114 this._firstVisibleIndexVal = null;
3358 this._lastVisibleIndexVal = null; 2115 this._lastVisibleIndexVal = null;
3359 2116
3360 this._resetScrollPosition(0); 2117 this._resetScrollPosition(0);
3361 this._removeFocusedItem(); 2118 this._removeFocusedItem();
3362 // create the initial physical items
3363 if (!this._physicalItems) { 2119 if (!this._physicalItems) {
3364 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount)); 2120 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
3365 this._physicalItems = this._createPool(this._physicalCount); 2121 this._physicalItems = this._createPool(this._physicalCount);
3366 this._physicalSizes = new Array(this._physicalCount); 2122 this._physicalSizes = new Array(this._physicalCount);
3367 } 2123 }
3368 2124
3369 this._physicalStart = 0; 2125 this._physicalStart = 0;
3370 2126
3371 } else if (change.path === 'items.splices') { 2127 } else if (change.path === 'items.splices') {
3372 2128
3373 this._adjustVirtualIndex(change.value.indexSplices); 2129 this._adjustVirtualIndex(change.value.indexSplices);
3374 this._virtualCount = this.items ? this.items.length : 0; 2130 this._virtualCount = this.items ? this.items.length : 0;
3375 2131
3376 } else { 2132 } else {
3377 // update a single item
3378 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value); 2133 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
3379 return; 2134 return;
3380 } 2135 }
3381 2136
3382 this._itemsRendered = false; 2137 this._itemsRendered = false;
3383 this._debounceTemplate(this._render); 2138 this._debounceTemplate(this._render);
3384 }, 2139 },
3385 2140
3386 /**
3387 * @param {!Array<!PolymerSplice>} splices
3388 */
3389 _adjustVirtualIndex: function(splices) { 2141 _adjustVirtualIndex: function(splices) {
3390 splices.forEach(function(splice) { 2142 splices.forEach(function(splice) {
3391 // deselect removed items
3392 splice.removed.forEach(this._removeItem, this); 2143 splice.removed.forEach(this._removeItem, this);
3393 // We only need to care about changes happening above the current positi on
3394 if (splice.index < this._virtualStart) { 2144 if (splice.index < this._virtualStart) {
3395 var delta = Math.max( 2145 var delta = Math.max(
3396 splice.addedCount - splice.removed.length, 2146 splice.addedCount - splice.removed.length,
3397 splice.index - this._virtualStart); 2147 splice.index - this._virtualStart);
3398 2148
3399 this._virtualStart = this._virtualStart + delta; 2149 this._virtualStart = this._virtualStart + delta;
3400 2150
3401 if (this._focusedIndex >= 0) { 2151 if (this._focusedIndex >= 0) {
3402 this._focusedIndex = this._focusedIndex + delta; 2152 this._focusedIndex = this._focusedIndex + delta;
3403 } 2153 }
3404 } 2154 }
3405 }, this); 2155 }, this);
3406 }, 2156 },
3407 2157
3408 _removeItem: function(item) { 2158 _removeItem: function(item) {
3409 this.$.selector.deselect(item); 2159 this.$.selector.deselect(item);
3410 // remove the current focused item
3411 if (this._focusedItem && this._focusedItem._templateInstance[this.as] === item) { 2160 if (this._focusedItem && this._focusedItem._templateInstance[this.as] === item) {
3412 this._removeFocusedItem(); 2161 this._removeFocusedItem();
3413 } 2162 }
3414 }, 2163 },
3415 2164
3416 /**
3417 * Executes a provided function per every physical index in `itemSet`
3418 * `itemSet` default value is equivalent to the entire set of physical index es.
3419 *
3420 * @param {!function(number, number)} fn
3421 * @param {!Array<number>=} itemSet
3422 */
3423 _iterateItems: function(fn, itemSet) { 2165 _iterateItems: function(fn, itemSet) {
3424 var pidx, vidx, rtn, i; 2166 var pidx, vidx, rtn, i;
3425 2167
3426 if (arguments.length === 2 && itemSet) { 2168 if (arguments.length === 2 && itemSet) {
3427 for (i = 0; i < itemSet.length; i++) { 2169 for (i = 0; i < itemSet.length; i++) {
3428 pidx = itemSet[i]; 2170 pidx = itemSet[i];
3429 vidx = this._computeVidx(pidx); 2171 vidx = this._computeVidx(pidx);
3430 if ((rtn = fn.call(this, pidx, vidx)) != null) { 2172 if ((rtn = fn.call(this, pidx, vidx)) != null) {
3431 return rtn; 2173 return rtn;
3432 } 2174 }
3433 } 2175 }
3434 } else { 2176 } else {
3435 pidx = this._physicalStart; 2177 pidx = this._physicalStart;
3436 vidx = this._virtualStart; 2178 vidx = this._virtualStart;
3437 2179
3438 for (; pidx < this._physicalCount; pidx++, vidx++) { 2180 for (; pidx < this._physicalCount; pidx++, vidx++) {
3439 if ((rtn = fn.call(this, pidx, vidx)) != null) { 2181 if ((rtn = fn.call(this, pidx, vidx)) != null) {
3440 return rtn; 2182 return rtn;
3441 } 2183 }
3442 } 2184 }
3443 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) { 2185 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) {
3444 if ((rtn = fn.call(this, pidx, vidx)) != null) { 2186 if ((rtn = fn.call(this, pidx, vidx)) != null) {
3445 return rtn; 2187 return rtn;
3446 } 2188 }
3447 } 2189 }
3448 } 2190 }
3449 }, 2191 },
3450 2192
3451 /**
3452 * Returns the virtual index for a given physical index
3453 *
3454 * @param {number} pidx Physical index
3455 * @return {number}
3456 */
3457 _computeVidx: function(pidx) { 2193 _computeVidx: function(pidx) {
3458 if (pidx >= this._physicalStart) { 2194 if (pidx >= this._physicalStart) {
3459 return this._virtualStart + (pidx - this._physicalStart); 2195 return this._virtualStart + (pidx - this._physicalStart);
3460 } 2196 }
3461 return this._virtualStart + (this._physicalCount - this._physicalStart) + pidx; 2197 return this._virtualStart + (this._physicalCount - this._physicalStart) + pidx;
3462 }, 2198 },
3463 2199
3464 /**
3465 * Assigns the data models to a given set of items.
3466 * @param {!Array<number>=} itemSet
3467 */
3468 _assignModels: function(itemSet) { 2200 _assignModels: function(itemSet) {
3469 this._iterateItems(function(pidx, vidx) { 2201 this._iterateItems(function(pidx, vidx) {
3470 var el = this._physicalItems[pidx]; 2202 var el = this._physicalItems[pidx];
3471 var inst = el._templateInstance; 2203 var inst = el._templateInstance;
3472 var item = this.items && this.items[vidx]; 2204 var item = this.items && this.items[vidx];
3473 2205
3474 if (item != null) { 2206 if (item != null) {
3475 inst[this.as] = item; 2207 inst[this.as] = item;
3476 inst.__key__ = this._collection.getKey(item); 2208 inst.__key__ = this._collection.getKey(item);
3477 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item); 2209 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s elector).isSelected(item);
3478 inst[this.indexAs] = vidx; 2210 inst[this.indexAs] = vidx;
3479 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1; 2211 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1;
3480 this._physicalIndexForKey[inst.__key__] = pidx; 2212 this._physicalIndexForKey[inst.__key__] = pidx;
3481 el.removeAttribute('hidden'); 2213 el.removeAttribute('hidden');
3482 } else { 2214 } else {
3483 inst.__key__ = null; 2215 inst.__key__ = null;
3484 el.setAttribute('hidden', ''); 2216 el.setAttribute('hidden', '');
3485 } 2217 }
3486 }, itemSet); 2218 }, itemSet);
3487 }, 2219 },
3488 2220
3489 /**
3490 * Updates the height for a given set of items.
3491 *
3492 * @param {!Array<number>=} itemSet
3493 */
3494 _updateMetrics: function(itemSet) { 2221 _updateMetrics: function(itemSet) {
3495 // Make sure we distributed all the physical items
3496 // so we can measure them
3497 Polymer.dom.flush(); 2222 Polymer.dom.flush();
3498 2223
3499 var newPhysicalSize = 0; 2224 var newPhysicalSize = 0;
3500 var oldPhysicalSize = 0; 2225 var oldPhysicalSize = 0;
3501 var prevAvgCount = this._physicalAverageCount; 2226 var prevAvgCount = this._physicalAverageCount;
3502 var prevPhysicalAvg = this._physicalAverage; 2227 var prevPhysicalAvg = this._physicalAverage;
3503 2228
3504 this._iterateItems(function(pidx, vidx) { 2229 this._iterateItems(function(pidx, vidx) {
3505 2230
3506 oldPhysicalSize += this._physicalSizes[pidx] || 0; 2231 oldPhysicalSize += this._physicalSizes[pidx] || 0;
3507 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; 2232 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight;
3508 newPhysicalSize += this._physicalSizes[pidx]; 2233 newPhysicalSize += this._physicalSizes[pidx];
3509 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; 2234 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
3510 2235
3511 }, itemSet); 2236 }, itemSet);
3512 2237
3513 this._viewportHeight = this._scrollTargetHeight; 2238 this._viewportHeight = this._scrollTargetHeight;
3514 if (this.grid) { 2239 if (this.grid) {
3515 this._updateGridMetrics(); 2240 this._updateGridMetrics();
3516 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow) * this._rowHeight; 2241 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow) * this._rowHeight;
3517 } else { 2242 } else {
3518 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS ize; 2243 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS ize;
3519 } 2244 }
3520 2245
3521 // update the average if we measured something
3522 if (this._physicalAverageCount !== prevAvgCount) { 2246 if (this._physicalAverageCount !== prevAvgCount) {
3523 this._physicalAverage = Math.round( 2247 this._physicalAverage = Math.round(
3524 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / 2248 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
3525 this._physicalAverageCount); 2249 this._physicalAverageCount);
3526 } 2250 }
3527 }, 2251 },
3528 2252
3529 _updateGridMetrics: function() { 2253 _updateGridMetrics: function() {
3530 this._viewportWidth = this.$.items.offsetWidth; 2254 this._viewportWidth = this.$.items.offsetWidth;
3531 // Set item width to the value of the _physicalItems offsetWidth
3532 this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoun dingClientRect().width : DEFAULT_GRID_SIZE; 2255 this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoun dingClientRect().width : DEFAULT_GRID_SIZE;
3533 // Set row height to the value of the _physicalItems offsetHeight
3534 this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetH eight : DEFAULT_GRID_SIZE; 2256 this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetH eight : DEFAULT_GRID_SIZE;
3535 // If in grid mode compute how many items with exist in each row
3536 this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / thi s._itemWidth) : this._itemsPerRow; 2257 this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / thi s._itemWidth) : this._itemsPerRow;
3537 }, 2258 },
3538 2259
3539 /**
3540 * Updates the position of the physical items.
3541 */
3542 _positionItems: function() { 2260 _positionItems: function() {
3543 this._adjustScrollPosition(); 2261 this._adjustScrollPosition();
3544 2262
3545 var y = this._physicalTop; 2263 var y = this._physicalTop;
3546 2264
3547 if (this.grid) { 2265 if (this.grid) {
3548 var totalItemWidth = this._itemsPerRow * this._itemWidth; 2266 var totalItemWidth = this._itemsPerRow * this._itemWidth;
3549 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; 2267 var rowOffset = (this._viewportWidth - totalItemWidth) / 2;
3550 2268
3551 this._iterateItems(function(pidx, vidx) { 2269 this._iterateItems(function(pidx, vidx) {
(...skipping 21 matching lines...) Expand all
3573 _getPhysicalSizeIncrement: function(pidx) { 2291 _getPhysicalSizeIncrement: function(pidx) {
3574 if (!this.grid) { 2292 if (!this.grid) {
3575 return this._physicalSizes[pidx]; 2293 return this._physicalSizes[pidx];
3576 } 2294 }
3577 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1) { 2295 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1) {
3578 return 0; 2296 return 0;
3579 } 2297 }
3580 return this._rowHeight; 2298 return this._rowHeight;
3581 }, 2299 },
3582 2300
3583 /**
3584 * Returns, based on the current index,
3585 * whether or not the next index will need
3586 * to be rendered on a new row.
3587 *
3588 * @param {number} vidx Virtual index
3589 * @return {boolean}
3590 */
3591 _shouldRenderNextRow: function(vidx) { 2301 _shouldRenderNextRow: function(vidx) {
3592 return vidx % this._itemsPerRow === this._itemsPerRow - 1; 2302 return vidx % this._itemsPerRow === this._itemsPerRow - 1;
3593 }, 2303 },
3594 2304
3595 /**
3596 * Adjusts the scroll position when it was overestimated.
3597 */
3598 _adjustScrollPosition: function() { 2305 _adjustScrollPosition: function() {
3599 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : 2306 var deltaHeight = this._virtualStart === 0 ? this._physicalTop :
3600 Math.min(this._scrollPosition + this._physicalTop, 0); 2307 Math.min(this._scrollPosition + this._physicalTop, 0);
3601 2308
3602 if (deltaHeight) { 2309 if (deltaHeight) {
3603 this._physicalTop = this._physicalTop - deltaHeight; 2310 this._physicalTop = this._physicalTop - deltaHeight;
3604 // juking scroll position during interial scrolling on iOS is no bueno
3605 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) { 2311 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) {
3606 this._resetScrollPosition(this._scrollTop - deltaHeight); 2312 this._resetScrollPosition(this._scrollTop - deltaHeight);
3607 } 2313 }
3608 } 2314 }
3609 }, 2315 },
3610 2316
3611 /**
3612 * Sets the position of the scroll.
3613 */
3614 _resetScrollPosition: function(pos) { 2317 _resetScrollPosition: function(pos) {
3615 if (this.scrollTarget) { 2318 if (this.scrollTarget) {
3616 this._scrollTop = pos; 2319 this._scrollTop = pos;
3617 this._scrollPosition = this._scrollTop; 2320 this._scrollPosition = this._scrollTop;
3618 } 2321 }
3619 }, 2322 },
3620 2323
3621 /**
3622 * Sets the scroll height, that's the height of the content,
3623 *
3624 * @param {boolean=} forceUpdate If true, updates the height no matter what.
3625 */
3626 _updateScrollerSize: function(forceUpdate) { 2324 _updateScrollerSize: function(forceUpdate) {
3627 if (this.grid) { 2325 if (this.grid) {
3628 this._estScrollHeight = this._virtualRowCount * this._rowHeight; 2326 this._estScrollHeight = this._virtualRowCount * this._rowHeight;
3629 } else { 2327 } else {
3630 this._estScrollHeight = (this._physicalBottom + 2328 this._estScrollHeight = (this._physicalBottom +
3631 Math.max(this._virtualCount - this._physicalCount - this._virtualSta rt, 0) * this._physicalAverage); 2329 Math.max(this._virtualCount - this._physicalCount - this._virtualSta rt, 0) * this._physicalAverage);
3632 } 2330 }
3633 2331
3634 forceUpdate = forceUpdate || this._scrollHeight === 0; 2332 forceUpdate = forceUpdate || this._scrollHeight === 0;
3635 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize; 2333 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
3636 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this ._estScrollHeight; 2334 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this ._estScrollHeight;
3637 2335
3638 // amortize height adjustment, so it won't trigger repaints very often
3639 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) { 2336 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
3640 this.$.items.style.height = this._estScrollHeight + 'px'; 2337 this.$.items.style.height = this._estScrollHeight + 'px';
3641 this._scrollHeight = this._estScrollHeight; 2338 this._scrollHeight = this._estScrollHeight;
3642 } 2339 }
3643 }, 2340 },
3644 2341
3645 /**
3646 * Scroll to a specific item in the virtual list regardless
3647 * of the physical items in the DOM tree.
3648 *
3649 * @method scrollToItem
3650 * @param {(Object)} item The item to be scrolled to
3651 */
3652 scrollToItem: function(item){ 2342 scrollToItem: function(item){
3653 return this.scrollToIndex(this.items.indexOf(item)); 2343 return this.scrollToIndex(this.items.indexOf(item));
3654 }, 2344 },
3655 2345
3656 /**
3657 * Scroll to a specific index in the virtual list regardless
3658 * of the physical items in the DOM tree.
3659 *
3660 * @method scrollToIndex
3661 * @param {number} idx The index of the item
3662 */
3663 scrollToIndex: function(idx) { 2346 scrollToIndex: function(idx) {
3664 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { 2347 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) {
3665 return; 2348 return;
3666 } 2349 }
3667 2350
3668 Polymer.dom.flush(); 2351 Polymer.dom.flush();
3669 2352
3670 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); 2353 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
3671 // update the virtual start only when needed
3672 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { 2354 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) {
3673 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx - 1); 2355 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx - 1);
3674 } 2356 }
3675 // manage focus
3676 this._manageFocus(); 2357 this._manageFocus();
3677 // assign new models
3678 this._assignModels(); 2358 this._assignModels();
3679 // measure the new sizes
3680 this._updateMetrics(); 2359 this._updateMetrics();
3681 2360
3682 // estimate new physical offset
3683 var estPhysicalTop = Math.floor(this._virtualStart / this._itemsPerRow) * this._physicalAverage; 2361 var estPhysicalTop = Math.floor(this._virtualStart / this._itemsPerRow) * this._physicalAverage;
3684 this._physicalTop = estPhysicalTop; 2362 this._physicalTop = estPhysicalTop;
3685 2363
3686 var currentTopItem = this._physicalStart; 2364 var currentTopItem = this._physicalStart;
3687 var currentVirtualItem = this._virtualStart; 2365 var currentVirtualItem = this._virtualStart;
3688 var targetOffsetTop = 0; 2366 var targetOffsetTop = 0;
3689 var hiddenContentSize = this._hiddenContentSize; 2367 var hiddenContentSize = this._hiddenContentSize;
3690 2368
3691 // scroll to the item as much as we can
3692 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { 2369 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) {
3693 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre ntTopItem); 2370 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre ntTopItem);
3694 currentTopItem = (currentTopItem + 1) % this._physicalCount; 2371 currentTopItem = (currentTopItem + 1) % this._physicalCount;
3695 currentVirtualItem++; 2372 currentVirtualItem++;
3696 } 2373 }
3697 // update the scroller size
3698 this._updateScrollerSize(true); 2374 this._updateScrollerSize(true);
3699 // update the position of the items
3700 this._positionItems(); 2375 this._positionItems();
3701 // set the new scroll position
3702 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t argetOffsetTop); 2376 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t argetOffsetTop);
3703 // increase the pool of physical items if needed
3704 this._increasePoolIfNeeded(); 2377 this._increasePoolIfNeeded();
3705 // clear cached visible index
3706 this._firstVisibleIndexVal = null; 2378 this._firstVisibleIndexVal = null;
3707 this._lastVisibleIndexVal = null; 2379 this._lastVisibleIndexVal = null;
3708 }, 2380 },
3709 2381
3710 /**
3711 * Reset the physical average and the average count.
3712 */
3713 _resetAverage: function() { 2382 _resetAverage: function() {
3714 this._physicalAverage = 0; 2383 this._physicalAverage = 0;
3715 this._physicalAverageCount = 0; 2384 this._physicalAverageCount = 0;
3716 }, 2385 },
3717 2386
3718 /**
3719 * A handler for the `iron-resize` event triggered by `IronResizableBehavior `
3720 * when the element is resized.
3721 */
3722 _resizeHandler: function() { 2387 _resizeHandler: function() {
3723 // iOS fires the resize event when the address bar slides up
3724 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100 ) { 2388 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100 ) {
3725 return; 2389 return;
3726 } 2390 }
3727 // In Desktop Safari 9.0.3, if the scroll bars are always shown,
3728 // changing the scroll position from a resize handler would result in
3729 // the scroll position being reset. Waiting 1ms fixes the issue.
3730 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { 2391 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() {
3731 this.updateViewportBoundaries(); 2392 this.updateViewportBoundaries();
3732 this._render(); 2393 this._render();
3733 2394
3734 if (this._itemsRendered && this._physicalItems && this._isVisible) { 2395 if (this._itemsRendered && this._physicalItems && this._isVisible) {
3735 this._resetAverage(); 2396 this._resetAverage();
3736 this.scrollToIndex(this.firstVisibleIndex); 2397 this.scrollToIndex(this.firstVisibleIndex);
3737 } 2398 }
3738 }.bind(this), 1)); 2399 }.bind(this), 1));
3739 }, 2400 },
3740 2401
3741 _getModelFromItem: function(item) { 2402 _getModelFromItem: function(item) {
3742 var key = this._collection.getKey(item); 2403 var key = this._collection.getKey(item);
3743 var pidx = this._physicalIndexForKey[key]; 2404 var pidx = this._physicalIndexForKey[key];
3744 2405
3745 if (pidx != null) { 2406 if (pidx != null) {
3746 return this._physicalItems[pidx]._templateInstance; 2407 return this._physicalItems[pidx]._templateInstance;
3747 } 2408 }
3748 return null; 2409 return null;
3749 }, 2410 },
3750 2411
3751 /**
3752 * Gets a valid item instance from its index or the object value.
3753 *
3754 * @param {(Object|number)} item The item object or its index
3755 */
3756 _getNormalizedItem: function(item) { 2412 _getNormalizedItem: function(item) {
3757 if (this._collection.getKey(item) === undefined) { 2413 if (this._collection.getKey(item) === undefined) {
3758 if (typeof item === 'number') { 2414 if (typeof item === 'number') {
3759 item = this.items[item]; 2415 item = this.items[item];
3760 if (!item) { 2416 if (!item) {
3761 throw new RangeError('<item> not found'); 2417 throw new RangeError('<item> not found');
3762 } 2418 }
3763 return item; 2419 return item;
3764 } 2420 }
3765 throw new TypeError('<item> should be a valid item'); 2421 throw new TypeError('<item> should be a valid item');
3766 } 2422 }
3767 return item; 2423 return item;
3768 }, 2424 },
3769 2425
3770 /**
3771 * Select the list item at the given index.
3772 *
3773 * @method selectItem
3774 * @param {(Object|number)} item The item object or its index
3775 */
3776 selectItem: function(item) { 2426 selectItem: function(item) {
3777 item = this._getNormalizedItem(item); 2427 item = this._getNormalizedItem(item);
3778 var model = this._getModelFromItem(item); 2428 var model = this._getModelFromItem(item);
3779 2429
3780 if (!this.multiSelection && this.selectedItem) { 2430 if (!this.multiSelection && this.selectedItem) {
3781 this.deselectItem(this.selectedItem); 2431 this.deselectItem(this.selectedItem);
3782 } 2432 }
3783 if (model) { 2433 if (model) {
3784 model[this.selectedAs] = true; 2434 model[this.selectedAs] = true;
3785 } 2435 }
3786 this.$.selector.select(item); 2436 this.$.selector.select(item);
3787 this.updateSizeForItem(item); 2437 this.updateSizeForItem(item);
3788 }, 2438 },
3789 2439
3790 /**
3791 * Deselects the given item list if it is already selected.
3792 *
3793
3794 * @method deselect
3795 * @param {(Object|number)} item The item object or its index
3796 */
3797 deselectItem: function(item) { 2440 deselectItem: function(item) {
3798 item = this._getNormalizedItem(item); 2441 item = this._getNormalizedItem(item);
3799 var model = this._getModelFromItem(item); 2442 var model = this._getModelFromItem(item);
3800 2443
3801 if (model) { 2444 if (model) {
3802 model[this.selectedAs] = false; 2445 model[this.selectedAs] = false;
3803 } 2446 }
3804 this.$.selector.deselect(item); 2447 this.$.selector.deselect(item);
3805 this.updateSizeForItem(item); 2448 this.updateSizeForItem(item);
3806 }, 2449 },
3807 2450
3808 /**
3809 * Select or deselect a given item depending on whether the item
3810 * has already been selected.
3811 *
3812 * @method toggleSelectionForItem
3813 * @param {(Object|number)} item The item object or its index
3814 */
3815 toggleSelectionForItem: function(item) { 2451 toggleSelectionForItem: function(item) {
3816 item = this._getNormalizedItem(item); 2452 item = this._getNormalizedItem(item);
3817 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item )) { 2453 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item )) {
3818 this.deselectItem(item); 2454 this.deselectItem(item);
3819 } else { 2455 } else {
3820 this.selectItem(item); 2456 this.selectItem(item);
3821 } 2457 }
3822 }, 2458 },
3823 2459
3824 /**
3825 * Clears the current selection state of the list.
3826 *
3827 * @method clearSelection
3828 */
3829 clearSelection: function() { 2460 clearSelection: function() {
3830 function unselect(item) { 2461 function unselect(item) {
3831 var model = this._getModelFromItem(item); 2462 var model = this._getModelFromItem(item);
3832 if (model) { 2463 if (model) {
3833 model[this.selectedAs] = false; 2464 model[this.selectedAs] = false;
3834 } 2465 }
3835 } 2466 }
3836 2467
3837 if (Array.isArray(this.selectedItems)) { 2468 if (Array.isArray(this.selectedItems)) {
3838 this.selectedItems.forEach(unselect, this); 2469 this.selectedItems.forEach(unselect, this);
3839 } else if (this.selectedItem) { 2470 } else if (this.selectedItem) {
3840 unselect.call(this, this.selectedItem); 2471 unselect.call(this, this.selectedItem);
3841 } 2472 }
3842 2473
3843 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); 2474 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection();
3844 }, 2475 },
3845 2476
3846 /**
3847 * Add an event listener to `tap` if `selectionEnabled` is true,
3848 * it will remove the listener otherwise.
3849 */
3850 _selectionEnabledChanged: function(selectionEnabled) { 2477 _selectionEnabledChanged: function(selectionEnabled) {
3851 var handler = selectionEnabled ? this.listen : this.unlisten; 2478 var handler = selectionEnabled ? this.listen : this.unlisten;
3852 handler.call(this, this, 'tap', '_selectionHandler'); 2479 handler.call(this, this, 'tap', '_selectionHandler');
3853 }, 2480 },
3854 2481
3855 /**
3856 * Select an item from an event object.
3857 */
3858 _selectionHandler: function(e) { 2482 _selectionHandler: function(e) {
3859 var model = this.modelForElement(e.target); 2483 var model = this.modelForElement(e.target);
3860 if (!model) { 2484 if (!model) {
3861 return; 2485 return;
3862 } 2486 }
3863 var modelTabIndex, activeElTabIndex; 2487 var modelTabIndex, activeElTabIndex;
3864 var target = Polymer.dom(e).path[0]; 2488 var target = Polymer.dom(e).path[0];
3865 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac tiveElement; 2489 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac tiveElement;
3866 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i ndexAs])]; 2490 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i ndexAs])];
3867 // Safari does not focus certain form controls via mouse
3868 // https://bugs.webkit.org/show_bug.cgi?id=118043
3869 if (target.localName === 'input' || 2491 if (target.localName === 'input' ||
3870 target.localName === 'button' || 2492 target.localName === 'button' ||
3871 target.localName === 'select') { 2493 target.localName === 'select') {
3872 return; 2494 return;
3873 } 2495 }
3874 // Set a temporary tabindex
3875 modelTabIndex = model.tabIndex; 2496 modelTabIndex = model.tabIndex;
3876 model.tabIndex = SECRET_TABINDEX; 2497 model.tabIndex = SECRET_TABINDEX;
3877 activeElTabIndex = activeEl ? activeEl.tabIndex : -1; 2498 activeElTabIndex = activeEl ? activeEl.tabIndex : -1;
3878 model.tabIndex = modelTabIndex; 2499 model.tabIndex = modelTabIndex;
3879 // Only select the item if the tap wasn't on a focusable child
3880 // or the element bound to `tabIndex`
3881 if (activeEl && physicalItem.contains(activeEl) && activeElTabIndex !== SE CRET_TABINDEX) { 2500 if (activeEl && physicalItem.contains(activeEl) && activeElTabIndex !== SE CRET_TABINDEX) {
3882 return; 2501 return;
3883 } 2502 }
3884 this.toggleSelectionForItem(model[this.as]); 2503 this.toggleSelectionForItem(model[this.as]);
3885 }, 2504 },
3886 2505
3887 _multiSelectionChanged: function(multiSelection) { 2506 _multiSelectionChanged: function(multiSelection) {
3888 this.clearSelection(); 2507 this.clearSelection();
3889 this.$.selector.multi = multiSelection; 2508 this.$.selector.multi = multiSelection;
3890 }, 2509 },
3891 2510
3892 /**
3893 * Updates the size of an item.
3894 *
3895 * @method updateSizeForItem
3896 * @param {(Object|number)} item The item object or its index
3897 */
3898 updateSizeForItem: function(item) { 2511 updateSizeForItem: function(item) {
3899 item = this._getNormalizedItem(item); 2512 item = this._getNormalizedItem(item);
3900 var key = this._collection.getKey(item); 2513 var key = this._collection.getKey(item);
3901 var pidx = this._physicalIndexForKey[key]; 2514 var pidx = this._physicalIndexForKey[key];
3902 2515
3903 if (pidx != null) { 2516 if (pidx != null) {
3904 this._updateMetrics([pidx]); 2517 this._updateMetrics([pidx]);
3905 this._positionItems(); 2518 this._positionItems();
3906 } 2519 }
3907 }, 2520 },
3908 2521
3909 /**
3910 * Creates a temporary backfill item in the rendered pool of physical items
3911 * to replace the main focused item. The focused item has tabIndex = 0
3912 * and might be currently focused by the user.
3913 *
3914 * This dynamic replacement helps to preserve the focus state.
3915 */
3916 _manageFocus: function() { 2522 _manageFocus: function() {
3917 var fidx = this._focusedIndex; 2523 var fidx = this._focusedIndex;
3918 2524
3919 if (fidx >= 0 && fidx < this._virtualCount) { 2525 if (fidx >= 0 && fidx < this._virtualCount) {
3920 // if it's a valid index, check if that index is rendered
3921 // in a physical item.
3922 if (this._isIndexRendered(fidx)) { 2526 if (this._isIndexRendered(fidx)) {
3923 this._restoreFocusedItem(); 2527 this._restoreFocusedItem();
3924 } else { 2528 } else {
3925 this._createFocusBackfillItem(); 2529 this._createFocusBackfillItem();
3926 } 2530 }
3927 } else if (this._virtualCount > 0 && this._physicalCount > 0) { 2531 } else if (this._virtualCount > 0 && this._physicalCount > 0) {
3928 // otherwise, assign the initial focused index.
3929 this._focusedIndex = this._virtualStart; 2532 this._focusedIndex = this._virtualStart;
3930 this._focusedItem = this._physicalItems[this._physicalStart]; 2533 this._focusedItem = this._physicalItems[this._physicalStart];
3931 } 2534 }
3932 }, 2535 },
3933 2536
3934 _isIndexRendered: function(idx) { 2537 _isIndexRendered: function(idx) {
3935 return idx >= this._virtualStart && idx <= this._virtualEnd; 2538 return idx >= this._virtualStart && idx <= this._virtualEnd;
3936 }, 2539 },
3937 2540
3938 _isIndexVisible: function(idx) { 2541 _isIndexVisible: function(idx) {
3939 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex; 2542 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex;
3940 }, 2543 },
3941 2544
3942 _getPhysicalIndex: function(idx) { 2545 _getPhysicalIndex: function(idx) {
3943 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz edItem(idx))]; 2546 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz edItem(idx))];
3944 }, 2547 },
3945 2548
3946 _focusPhysicalItem: function(idx) { 2549 _focusPhysicalItem: function(idx) {
3947 if (idx < 0 || idx >= this._virtualCount) { 2550 if (idx < 0 || idx >= this._virtualCount) {
3948 return; 2551 return;
3949 } 2552 }
3950 this._restoreFocusedItem(); 2553 this._restoreFocusedItem();
3951 // scroll to index to make sure it's rendered
3952 if (!this._isIndexRendered(idx)) { 2554 if (!this._isIndexRendered(idx)) {
3953 this.scrollToIndex(idx); 2555 this.scrollToIndex(idx);
3954 } 2556 }
3955 2557
3956 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)]; 2558 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)];
3957 var model = physicalItem._templateInstance; 2559 var model = physicalItem._templateInstance;
3958 var focusable; 2560 var focusable;
3959 2561
3960 // set a secret tab index
3961 model.tabIndex = SECRET_TABINDEX; 2562 model.tabIndex = SECRET_TABINDEX;
3962 // check if focusable element is the physical item
3963 if (physicalItem.tabIndex === SECRET_TABINDEX) { 2563 if (physicalItem.tabIndex === SECRET_TABINDEX) {
3964 focusable = physicalItem; 2564 focusable = physicalItem;
3965 } 2565 }
3966 // search for the element which tabindex is bound to the secret tab index
3967 if (!focusable) { 2566 if (!focusable) {
3968 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET_TABINDEX + '"]'); 2567 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR ET_TABINDEX + '"]');
3969 } 2568 }
3970 // restore the tab index
3971 model.tabIndex = 0; 2569 model.tabIndex = 0;
3972 // focus the focusable element
3973 this._focusedIndex = idx; 2570 this._focusedIndex = idx;
3974 focusable && focusable.focus(); 2571 focusable && focusable.focus();
3975 }, 2572 },
3976 2573
3977 _removeFocusedItem: function() { 2574 _removeFocusedItem: function() {
3978 if (this._offscreenFocusedItem) { 2575 if (this._offscreenFocusedItem) {
3979 Polymer.dom(this).removeChild(this._offscreenFocusedItem); 2576 Polymer.dom(this).removeChild(this._offscreenFocusedItem);
3980 } 2577 }
3981 this._offscreenFocusedItem = null; 2578 this._offscreenFocusedItem = null;
3982 this._focusBackfillItem = null; 2579 this._focusBackfillItem = null;
3983 this._focusedItem = null; 2580 this._focusedItem = null;
3984 this._focusedIndex = -1; 2581 this._focusedIndex = -1;
3985 }, 2582 },
3986 2583
3987 _createFocusBackfillItem: function() { 2584 _createFocusBackfillItem: function() {
3988 var pidx, fidx = this._focusedIndex; 2585 var pidx, fidx = this._focusedIndex;
3989 if (this._offscreenFocusedItem || fidx < 0) { 2586 if (this._offscreenFocusedItem || fidx < 0) {
3990 return; 2587 return;
3991 } 2588 }
3992 if (!this._focusBackfillItem) { 2589 if (!this._focusBackfillItem) {
3993 // create a physical item, so that it backfills the focused item.
3994 var stampedTemplate = this.stamp(null); 2590 var stampedTemplate = this.stamp(null);
3995 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); 2591 this._focusBackfillItem = stampedTemplate.root.querySelector('*');
3996 Polymer.dom(this).appendChild(stampedTemplate.root); 2592 Polymer.dom(this).appendChild(stampedTemplate.root);
3997 } 2593 }
3998 // get the physical index for the focused index
3999 pidx = this._getPhysicalIndex(fidx); 2594 pidx = this._getPhysicalIndex(fidx);
4000 2595
4001 if (pidx != null) { 2596 if (pidx != null) {
4002 // set the offcreen focused physical item
4003 this._offscreenFocusedItem = this._physicalItems[pidx]; 2597 this._offscreenFocusedItem = this._physicalItems[pidx];
4004 // backfill the focused physical item
4005 this._physicalItems[pidx] = this._focusBackfillItem; 2598 this._physicalItems[pidx] = this._focusBackfillItem;
4006 // hide the focused physical
4007 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); 2599 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem);
4008 } 2600 }
4009 }, 2601 },
4010 2602
4011 _restoreFocusedItem: function() { 2603 _restoreFocusedItem: function() {
4012 var pidx, fidx = this._focusedIndex; 2604 var pidx, fidx = this._focusedIndex;
4013 2605
4014 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { 2606 if (!this._offscreenFocusedItem || this._focusedIndex < 0) {
4015 return; 2607 return;
4016 } 2608 }
4017 // assign models to the focused index
4018 this._assignModels(); 2609 this._assignModels();
4019 // get the new physical index for the focused index
4020 pidx = this._getPhysicalIndex(fidx); 2610 pidx = this._getPhysicalIndex(fidx);
4021 2611
4022 if (pidx != null) { 2612 if (pidx != null) {
4023 // flip the focus backfill
4024 this._focusBackfillItem = this._physicalItems[pidx]; 2613 this._focusBackfillItem = this._physicalItems[pidx];
4025 // restore the focused physical item
4026 this._physicalItems[pidx] = this._offscreenFocusedItem; 2614 this._physicalItems[pidx] = this._offscreenFocusedItem;
4027 // reset the offscreen focused item
4028 this._offscreenFocusedItem = null; 2615 this._offscreenFocusedItem = null;
4029 // hide the physical item that backfills
4030 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); 2616 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem);
4031 } 2617 }
4032 }, 2618 },
4033 2619
4034 _didFocus: function(e) { 2620 _didFocus: function(e) {
4035 var targetModel = this.modelForElement(e.target); 2621 var targetModel = this.modelForElement(e.target);
4036 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance : null; 2622 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance : null;
4037 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; 2623 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null;
4038 var fidx = this._focusedIndex; 2624 var fidx = this._focusedIndex;
4039 2625
4040 if (!targetModel || !focusedModel) { 2626 if (!targetModel || !focusedModel) {
4041 return; 2627 return;
4042 } 2628 }
4043 if (focusedModel === targetModel) { 2629 if (focusedModel === targetModel) {
4044 // if the user focused the same item, then bring it into view if it's no t visible
4045 if (!this._isIndexVisible(fidx)) { 2630 if (!this._isIndexVisible(fidx)) {
4046 this.scrollToIndex(fidx); 2631 this.scrollToIndex(fidx);
4047 } 2632 }
4048 } else { 2633 } else {
4049 this._restoreFocusedItem(); 2634 this._restoreFocusedItem();
4050 // restore tabIndex for the currently focused item
4051 focusedModel.tabIndex = -1; 2635 focusedModel.tabIndex = -1;
4052 // set the tabIndex for the next focused item
4053 targetModel.tabIndex = 0; 2636 targetModel.tabIndex = 0;
4054 fidx = targetModel[this.indexAs]; 2637 fidx = targetModel[this.indexAs];
4055 this._focusedIndex = fidx; 2638 this._focusedIndex = fidx;
4056 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)]; 2639 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)];
4057 2640
4058 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) { 2641 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) {
4059 this._update(); 2642 this._update();
4060 } 2643 }
4061 } 2644 }
4062 }, 2645 },
4063 2646
4064 _didMoveUp: function() { 2647 _didMoveUp: function() {
4065 this._focusPhysicalItem(this._focusedIndex - 1); 2648 this._focusPhysicalItem(this._focusedIndex - 1);
4066 }, 2649 },
4067 2650
4068 _didMoveDown: function(e) { 2651 _didMoveDown: function(e) {
4069 // disable scroll when pressing the down key
4070 e.detail.keyboardEvent.preventDefault(); 2652 e.detail.keyboardEvent.preventDefault();
4071 this._focusPhysicalItem(this._focusedIndex + 1); 2653 this._focusPhysicalItem(this._focusedIndex + 1);
4072 }, 2654 },
4073 2655
4074 _didEnter: function(e) { 2656 _didEnter: function(e) {
4075 this._focusPhysicalItem(this._focusedIndex); 2657 this._focusPhysicalItem(this._focusedIndex);
4076 this._selectionHandler(e.detail.keyboardEvent); 2658 this._selectionHandler(e.detail.keyboardEvent);
4077 } 2659 }
4078 }); 2660 });
4079 2661
4080 })(); 2662 })();
4081 // Copyright 2015 The Chromium Authors. All rights reserved. 2663 // Copyright 2015 The Chromium Authors. All rights reserved.
4082 // Use of this source code is governed by a BSD-style license that can be 2664 // Use of this source code is governed by a BSD-style license that can be
4083 // found in the LICENSE file. 2665 // found in the LICENSE file.
4084 2666
4085 cr.define('downloads', function() { 2667 cr.define('downloads', function() {
4086 /**
4087 * @param {string} chromeSendName
4088 * @return {function(string):void} A chrome.send() callback with curried name.
4089 */
4090 function chromeSendWithId(chromeSendName) { 2668 function chromeSendWithId(chromeSendName) {
4091 return function(id) { chrome.send(chromeSendName, [id]); }; 2669 return function(id) { chrome.send(chromeSendName, [id]); };
4092 } 2670 }
4093 2671
4094 /** @constructor */ 2672 /** @constructor */
4095 function ActionService() { 2673 function ActionService() {
4096 /** @private {Array<string>} */ 2674 /** @private {Array<string>} */
4097 this.searchTerms_ = []; 2675 this.searchTerms_ = [];
4098 } 2676 }
4099 2677
4100 /**
4101 * @param {string} s
4102 * @return {string} |s| without whitespace at the beginning or end.
4103 */
4104 function trim(s) { return s.trim(); } 2678 function trim(s) { return s.trim(); }
4105 2679
4106 /**
4107 * @param {string|undefined} value
4108 * @return {boolean} Whether |value| is truthy.
4109 */
4110 function truthy(value) { return !!value; } 2680 function truthy(value) { return !!value; }
4111 2681
4112 /**
4113 * @param {string} searchText Input typed by the user into a search box.
4114 * @return {Array<string>} A list of terms extracted from |searchText|.
4115 */
4116 ActionService.splitTerms = function(searchText) { 2682 ActionService.splitTerms = function(searchText) {
4117 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']).
4118 return searchText.split(/"([^"]*)"/).map(trim).filter(truthy); 2683 return searchText.split(/"([^"]*)"/).map(trim).filter(truthy);
4119 }; 2684 };
4120 2685
4121 ActionService.prototype = { 2686 ActionService.prototype = {
4122 /** @param {string} id ID of the download to cancel. */ 2687 /** @param {string} id ID of the download to cancel. */
4123 cancel: chromeSendWithId('cancel'), 2688 cancel: chromeSendWithId('cancel'),
4124 2689
4125 /** Instructs the browser to clear all finished downloads. */ 2690 /** Instructs the browser to clear all finished downloads. */
4126 clearAll: function() { 2691 clearAll: function() {
4127 if (loadTimeData.getBoolean('allowDeletingHistory')) { 2692 if (loadTimeData.getBoolean('allowDeletingHistory')) {
(...skipping 14 matching lines...) Expand all
4142 }, 2707 },
4143 2708
4144 /** @param {string} id ID of the download that the user started dragging. */ 2709 /** @param {string} id ID of the download that the user started dragging. */
4145 drag: chromeSendWithId('drag'), 2710 drag: chromeSendWithId('drag'),
4146 2711
4147 /** Loads more downloads with the current search terms. */ 2712 /** Loads more downloads with the current search terms. */
4148 loadMore: function() { 2713 loadMore: function() {
4149 chrome.send('getDownloads', this.searchTerms_); 2714 chrome.send('getDownloads', this.searchTerms_);
4150 }, 2715 },
4151 2716
4152 /**
4153 * @return {boolean} Whether the user is currently searching for downloads
4154 * (i.e. has a non-empty search term).
4155 */
4156 isSearching: function() { 2717 isSearching: function() {
4157 return this.searchTerms_.length > 0; 2718 return this.searchTerms_.length > 0;
4158 }, 2719 },
4159 2720
4160 /** Opens the current local destination for downloads. */ 2721 /** Opens the current local destination for downloads. */
4161 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'), 2722 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'),
4162 2723
4163 /**
4164 * @param {string} id ID of the download to run locally on the user's box.
4165 */
4166 openFile: chromeSendWithId('openFile'), 2724 openFile: chromeSendWithId('openFile'),
4167 2725
4168 /** @param {string} id ID the of the progressing download to pause. */ 2726 /** @param {string} id ID the of the progressing download to pause. */
4169 pause: chromeSendWithId('pause'), 2727 pause: chromeSendWithId('pause'),
4170 2728
4171 /** @param {string} id ID of the finished download to remove. */ 2729 /** @param {string} id ID of the finished download to remove. */
4172 remove: chromeSendWithId('remove'), 2730 remove: chromeSendWithId('remove'),
4173 2731
4174 /** @param {string} id ID of the paused download to resume. */ 2732 /** @param {string} id ID of the paused download to resume. */
4175 resume: chromeSendWithId('resume'), 2733 resume: chromeSendWithId('resume'),
4176 2734
4177 /**
4178 * @param {string} id ID of the dangerous download to save despite
4179 * warnings.
4180 */
4181 saveDangerous: chromeSendWithId('saveDangerous'), 2735 saveDangerous: chromeSendWithId('saveDangerous'),
4182 2736
4183 /** @param {string} searchText What to search for. */ 2737 /** @param {string} searchText What to search for. */
4184 search: function(searchText) { 2738 search: function(searchText) {
4185 var searchTerms = ActionService.splitTerms(searchText); 2739 var searchTerms = ActionService.splitTerms(searchText);
4186 var sameTerms = searchTerms.length == this.searchTerms_.length; 2740 var sameTerms = searchTerms.length == this.searchTerms_.length;
4187 2741
4188 for (var i = 0; sameTerms && i < searchTerms.length; ++i) { 2742 for (var i = 0; sameTerms && i < searchTerms.length; ++i) {
4189 if (searchTerms[i] != this.searchTerms_[i]) 2743 if (searchTerms[i] != this.searchTerms_[i])
4190 sameTerms = false; 2744 sameTerms = false;
4191 } 2745 }
4192 2746
4193 if (sameTerms) 2747 if (sameTerms)
4194 return; 2748 return;
4195 2749
4196 this.searchTerms_ = searchTerms; 2750 this.searchTerms_ = searchTerms;
4197 this.loadMore(); 2751 this.loadMore();
4198 }, 2752 },
4199 2753
4200 /**
4201 * Shows the local folder a finished download resides in.
4202 * @param {string} id ID of the download to show.
4203 */
4204 show: chromeSendWithId('show'), 2754 show: chromeSendWithId('show'),
4205 2755
4206 /** Undo download removal. */ 2756 /** Undo download removal. */
4207 undo: chrome.send.bind(chrome, 'undo'), 2757 undo: chrome.send.bind(chrome, 'undo'),
4208 }; 2758 };
4209 2759
4210 cr.addSingletonGetter(ActionService); 2760 cr.addSingletonGetter(ActionService);
4211 2761
4212 return {ActionService: ActionService}; 2762 return {ActionService: ActionService};
4213 }); 2763 });
4214 // Copyright 2015 The Chromium Authors. All rights reserved. 2764 // Copyright 2015 The Chromium Authors. All rights reserved.
4215 // Use of this source code is governed by a BSD-style license that can be 2765 // Use of this source code is governed by a BSD-style license that can be
4216 // found in the LICENSE file. 2766 // found in the LICENSE file.
4217 2767
4218 cr.define('downloads', function() { 2768 cr.define('downloads', function() {
4219 /**
4220 * Explains why a download is in DANGEROUS state.
4221 * @enum {string}
4222 */
4223 var DangerType = { 2769 var DangerType = {
4224 NOT_DANGEROUS: 'NOT_DANGEROUS', 2770 NOT_DANGEROUS: 'NOT_DANGEROUS',
4225 DANGEROUS_FILE: 'DANGEROUS_FILE', 2771 DANGEROUS_FILE: 'DANGEROUS_FILE',
4226 DANGEROUS_URL: 'DANGEROUS_URL', 2772 DANGEROUS_URL: 'DANGEROUS_URL',
4227 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT', 2773 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
4228 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT', 2774 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
4229 DANGEROUS_HOST: 'DANGEROUS_HOST', 2775 DANGEROUS_HOST: 'DANGEROUS_HOST',
4230 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED', 2776 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
4231 }; 2777 };
4232 2778
4233 /**
4234 * The states a download can be in. These correspond to states defined in
4235 * DownloadsDOMHandler::CreateDownloadItemValue
4236 * @enum {string}
4237 */
4238 var States = { 2779 var States = {
4239 IN_PROGRESS: 'IN_PROGRESS', 2780 IN_PROGRESS: 'IN_PROGRESS',
4240 CANCELLED: 'CANCELLED', 2781 CANCELLED: 'CANCELLED',
4241 COMPLETE: 'COMPLETE', 2782 COMPLETE: 'COMPLETE',
4242 PAUSED: 'PAUSED', 2783 PAUSED: 'PAUSED',
4243 DANGEROUS: 'DANGEROUS', 2784 DANGEROUS: 'DANGEROUS',
4244 INTERRUPTED: 'INTERRUPTED', 2785 INTERRUPTED: 'INTERRUPTED',
4245 }; 2786 };
4246 2787
4247 return { 2788 return {
4248 DangerType: DangerType, 2789 DangerType: DangerType,
4249 States: States, 2790 States: States,
4250 }; 2791 };
4251 }); 2792 });
4252 // Copyright 2014 The Chromium Authors. All rights reserved. 2793 // Copyright 2014 The Chromium Authors. All rights reserved.
4253 // Use of this source code is governed by a BSD-style license that can be 2794 // Use of this source code is governed by a BSD-style license that can be
4254 // found in the LICENSE file. 2795 // found in the LICENSE file.
4255 2796
4256 // Action links are elements that are used to perform an in-page navigation or
4257 // action (e.g. showing a dialog).
4258 //
4259 // They look like normal anchor (<a>) tags as their text color is blue. However,
4260 // they're subtly different as they're not initially underlined (giving users a
4261 // clue that underlined links navigate while action links don't).
4262 //
4263 // Action links look very similar to normal links when hovered (hand cursor,
4264 // underlined). This gives the user an idea that clicking this link will do
4265 // something similar to navigation but in the same page.
4266 //
4267 // They can be created in JavaScript like this:
4268 //
4269 // var link = document.createElement('a', 'action-link'); // Note second arg.
4270 //
4271 // or with a constructor like this:
4272 //
4273 // var link = new ActionLink();
4274 //
4275 // They can be used easily from HTML as well, like so:
4276 //
4277 // <a is="action-link">Click me!</a>
4278 //
4279 // NOTE: <action-link> and document.createElement('action-link') don't work.
4280 2797
4281 /**
4282 * @constructor
4283 * @extends {HTMLAnchorElement}
4284 */
4285 var ActionLink = document.registerElement('action-link', { 2798 var ActionLink = document.registerElement('action-link', {
4286 prototype: { 2799 prototype: {
4287 __proto__: HTMLAnchorElement.prototype, 2800 __proto__: HTMLAnchorElement.prototype,
4288 2801
4289 /** @this {ActionLink} */ 2802 /** @this {ActionLink} */
4290 createdCallback: function() { 2803 createdCallback: function() {
4291 // Action links can start disabled (e.g. <a is="action-link" disabled>).
4292 this.tabIndex = this.disabled ? -1 : 0; 2804 this.tabIndex = this.disabled ? -1 : 0;
4293 2805
4294 if (!this.hasAttribute('role')) 2806 if (!this.hasAttribute('role'))
4295 this.setAttribute('role', 'link'); 2807 this.setAttribute('role', 'link');
4296 2808
4297 this.addEventListener('keydown', function(e) { 2809 this.addEventListener('keydown', function(e) {
4298 if (!this.disabled && e.key == 'Enter' && !this.href) { 2810 if (!this.disabled && e.key == 'Enter' && !this.href) {
4299 // Schedule a click asynchronously because other 'keydown' handlers
4300 // may still run later (e.g. document.addEventListener('keydown')).
4301 // Specifically options dialogs break when this timeout isn't here.
4302 // NOTE: this affects the "trusted" state of the ensuing click. I
4303 // haven't found anything that breaks because of this (yet).
4304 window.setTimeout(this.click.bind(this), 0); 2811 window.setTimeout(this.click.bind(this), 0);
4305 } 2812 }
4306 }); 2813 });
4307 2814
4308 function preventDefault(e) { 2815 function preventDefault(e) {
4309 e.preventDefault(); 2816 e.preventDefault();
4310 } 2817 }
4311 2818
4312 function removePreventDefault() { 2819 function removePreventDefault() {
4313 document.removeEventListener('selectstart', preventDefault); 2820 document.removeEventListener('selectstart', preventDefault);
4314 document.removeEventListener('mouseup', removePreventDefault); 2821 document.removeEventListener('mouseup', removePreventDefault);
4315 } 2822 }
4316 2823
4317 this.addEventListener('mousedown', function() { 2824 this.addEventListener('mousedown', function() {
4318 // This handlers strives to match the behavior of <a href="...">.
4319 2825
4320 // While the mouse is down, prevent text selection from dragging.
4321 document.addEventListener('selectstart', preventDefault); 2826 document.addEventListener('selectstart', preventDefault);
4322 document.addEventListener('mouseup', removePreventDefault); 2827 document.addEventListener('mouseup', removePreventDefault);
4323 2828
4324 // If focus started via mouse press, don't show an outline.
4325 if (document.activeElement != this) 2829 if (document.activeElement != this)
4326 this.classList.add('no-outline'); 2830 this.classList.add('no-outline');
4327 }); 2831 });
4328 2832
4329 this.addEventListener('blur', function() { 2833 this.addEventListener('blur', function() {
4330 this.classList.remove('no-outline'); 2834 this.classList.remove('no-outline');
4331 }); 2835 });
4332 }, 2836 },
4333 2837
4334 /** @type {boolean} */ 2838 /** @type {boolean} */
(...skipping 22 matching lines...) Expand all
4357 this.disabled = false; 2861 this.disabled = false;
4358 else 2862 else
4359 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); 2863 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
4360 }, 2864 },
4361 }, 2865 },
4362 2866
4363 extends: 'a', 2867 extends: 'a',
4364 }); 2868 });
4365 (function() { 2869 (function() {
4366 2870
4367 // monostate data
4368 var metaDatas = {}; 2871 var metaDatas = {};
4369 var metaArrays = {}; 2872 var metaArrays = {};
4370 var singleton = null; 2873 var singleton = null;
4371 2874
4372 Polymer.IronMeta = Polymer({ 2875 Polymer.IronMeta = Polymer({
4373 2876
4374 is: 'iron-meta', 2877 is: 'iron-meta',
4375 2878
4376 properties: { 2879 properties: {
4377 2880
4378 /**
4379 * The type of meta-data. All meta-data of the same type is stored
4380 * together.
4381 */
4382 type: { 2881 type: {
4383 type: String, 2882 type: String,
4384 value: 'default', 2883 value: 'default',
4385 observer: '_typeChanged' 2884 observer: '_typeChanged'
4386 }, 2885 },
4387 2886
4388 /**
4389 * The key used to store `value` under the `type` namespace.
4390 */
4391 key: { 2887 key: {
4392 type: String, 2888 type: String,
4393 observer: '_keyChanged' 2889 observer: '_keyChanged'
4394 }, 2890 },
4395 2891
4396 /**
4397 * The meta-data to store or retrieve.
4398 */
4399 value: { 2892 value: {
4400 type: Object, 2893 type: Object,
4401 notify: true, 2894 notify: true,
4402 observer: '_valueChanged' 2895 observer: '_valueChanged'
4403 }, 2896 },
4404 2897
4405 /**
4406 * If true, `value` is set to the iron-meta instance itself.
4407 */
4408 self: { 2898 self: {
4409 type: Boolean, 2899 type: Boolean,
4410 observer: '_selfChanged' 2900 observer: '_selfChanged'
4411 }, 2901 },
4412 2902
4413 /**
4414 * Array of all meta-data values for the given type.
4415 */
4416 list: { 2903 list: {
4417 type: Array, 2904 type: Array,
4418 notify: true 2905 notify: true
4419 } 2906 }
4420 2907
4421 }, 2908 },
4422 2909
4423 hostAttributes: { 2910 hostAttributes: {
4424 hidden: true 2911 hidden: true
4425 }, 2912 },
4426 2913
4427 /**
4428 * Only runs if someone invokes the factory/constructor directly
4429 * e.g. `new Polymer.IronMeta()`
4430 *
4431 * @param {{type: (string|undefined), key: (string|undefined), value}=} co nfig
4432 */
4433 factoryImpl: function(config) { 2914 factoryImpl: function(config) {
4434 if (config) { 2915 if (config) {
4435 for (var n in config) { 2916 for (var n in config) {
4436 switch(n) { 2917 switch(n) {
4437 case 'type': 2918 case 'type':
4438 case 'key': 2919 case 'key':
4439 case 'value': 2920 case 'value':
4440 this[n] = config[n]; 2921 this[n] = config[n];
4441 break; 2922 break;
4442 } 2923 }
4443 } 2924 }
4444 } 2925 }
4445 }, 2926 },
4446 2927
4447 created: function() { 2928 created: function() {
4448 // TODO(sjmiles): good for debugging?
4449 this._metaDatas = metaDatas; 2929 this._metaDatas = metaDatas;
4450 this._metaArrays = metaArrays; 2930 this._metaArrays = metaArrays;
4451 }, 2931 },
4452 2932
4453 _keyChanged: function(key, old) { 2933 _keyChanged: function(key, old) {
4454 this._resetRegistration(old); 2934 this._resetRegistration(old);
4455 }, 2935 },
4456 2936
4457 _valueChanged: function(value) { 2937 _valueChanged: function(value) {
4458 this._resetRegistration(this.key); 2938 this._resetRegistration(this.key);
(...skipping 11 matching lines...) Expand all
4470 metaDatas[type] = {}; 2950 metaDatas[type] = {};
4471 } 2951 }
4472 this._metaData = metaDatas[type]; 2952 this._metaData = metaDatas[type];
4473 if (!metaArrays[type]) { 2953 if (!metaArrays[type]) {
4474 metaArrays[type] = []; 2954 metaArrays[type] = [];
4475 } 2955 }
4476 this.list = metaArrays[type]; 2956 this.list = metaArrays[type];
4477 this._registerKeyValue(this.key, this.value); 2957 this._registerKeyValue(this.key, this.value);
4478 }, 2958 },
4479 2959
4480 /**
4481 * Retrieves meta data value by key.
4482 *
4483 * @method byKey
4484 * @param {string} key The key of the meta-data to be returned.
4485 * @return {*}
4486 */
4487 byKey: function(key) { 2960 byKey: function(key) {
4488 return this._metaData && this._metaData[key]; 2961 return this._metaData && this._metaData[key];
4489 }, 2962 },
4490 2963
4491 _resetRegistration: function(oldKey) { 2964 _resetRegistration: function(oldKey) {
4492 this._unregisterKey(oldKey); 2965 this._unregisterKey(oldKey);
4493 this._registerKeyValue(this.key, this.value); 2966 this._registerKeyValue(this.key, this.value);
4494 }, 2967 },
4495 2968
4496 _unregisterKey: function(key) { 2969 _unregisterKey: function(key) {
(...skipping 23 matching lines...) Expand all
4520 2993
4521 }); 2994 });
4522 2995
4523 Polymer.IronMeta.getIronMeta = function getIronMeta() { 2996 Polymer.IronMeta.getIronMeta = function getIronMeta() {
4524 if (singleton === null) { 2997 if (singleton === null) {
4525 singleton = new Polymer.IronMeta(); 2998 singleton = new Polymer.IronMeta();
4526 } 2999 }
4527 return singleton; 3000 return singleton;
4528 }; 3001 };
4529 3002
4530 /**
4531 `iron-meta-query` can be used to access infomation stored in `iron-meta`.
4532
4533 Examples:
4534
4535 If I create an instance like this:
4536
4537 <iron-meta key="info" value="foo/bar"></iron-meta>
4538
4539 Note that value="foo/bar" is the metadata I've defined. I could define more
4540 attributes or use child nodes to define additional metadata.
4541
4542 Now I can access that element (and it's metadata) from any `iron-meta-query` instance:
4543
4544 var value = new Polymer.IronMetaQuery({key: 'info'}).value;
4545
4546 @group Polymer Iron Elements
4547 @element iron-meta-query
4548 */
4549 Polymer.IronMetaQuery = Polymer({ 3003 Polymer.IronMetaQuery = Polymer({
4550 3004
4551 is: 'iron-meta-query', 3005 is: 'iron-meta-query',
4552 3006
4553 properties: { 3007 properties: {
4554 3008
4555 /**
4556 * The type of meta-data. All meta-data of the same type is stored
4557 * together.
4558 */
4559 type: { 3009 type: {
4560 type: String, 3010 type: String,
4561 value: 'default', 3011 value: 'default',
4562 observer: '_typeChanged' 3012 observer: '_typeChanged'
4563 }, 3013 },
4564 3014
4565 /**
4566 * Specifies a key to use for retrieving `value` from the `type`
4567 * namespace.
4568 */
4569 key: { 3015 key: {
4570 type: String, 3016 type: String,
4571 observer: '_keyChanged' 3017 observer: '_keyChanged'
4572 }, 3018 },
4573 3019
4574 /**
4575 * The meta-data to store or retrieve.
4576 */
4577 value: { 3020 value: {
4578 type: Object, 3021 type: Object,
4579 notify: true, 3022 notify: true,
4580 readOnly: true 3023 readOnly: true
4581 }, 3024 },
4582 3025
4583 /**
4584 * Array of all meta-data values for the given type.
4585 */
4586 list: { 3026 list: {
4587 type: Array, 3027 type: Array,
4588 notify: true 3028 notify: true
4589 } 3029 }
4590 3030
4591 }, 3031 },
4592 3032
4593 /**
4594 * Actually a factory method, not a true constructor. Only runs if
4595 * someone invokes it directly (via `new Polymer.IronMeta()`);
4596 *
4597 * @param {{type: (string|undefined), key: (string|undefined)}=} config
4598 */
4599 factoryImpl: function(config) { 3033 factoryImpl: function(config) {
4600 if (config) { 3034 if (config) {
4601 for (var n in config) { 3035 for (var n in config) {
4602 switch(n) { 3036 switch(n) {
4603 case 'type': 3037 case 'type':
4604 case 'key': 3038 case 'key':
4605 this[n] = config[n]; 3039 this[n] = config[n];
4606 break; 3040 break;
4607 } 3041 }
4608 } 3042 }
4609 } 3043 }
4610 }, 3044 },
4611 3045
4612 created: function() { 3046 created: function() {
4613 // TODO(sjmiles): good for debugging?
4614 this._metaDatas = metaDatas; 3047 this._metaDatas = metaDatas;
4615 this._metaArrays = metaArrays; 3048 this._metaArrays = metaArrays;
4616 }, 3049 },
4617 3050
4618 _keyChanged: function(key) { 3051 _keyChanged: function(key) {
4619 this._setValue(this._metaData && this._metaData[key]); 3052 this._setValue(this._metaData && this._metaData[key]);
4620 }, 3053 },
4621 3054
4622 _typeChanged: function(type) { 3055 _typeChanged: function(type) {
4623 this._metaData = metaDatas[type]; 3056 this._metaData = metaDatas[type];
4624 this.list = metaArrays[type]; 3057 this.list = metaArrays[type];
4625 if (this.key) { 3058 if (this.key) {
4626 this._keyChanged(this.key); 3059 this._keyChanged(this.key);
4627 } 3060 }
4628 }, 3061 },
4629 3062
4630 /**
4631 * Retrieves meta data value by key.
4632 * @param {string} key The key of the meta-data to be returned.
4633 * @return {*}
4634 */
4635 byKey: function(key) { 3063 byKey: function(key) {
4636 return this._metaData && this._metaData[key]; 3064 return this._metaData && this._metaData[key];
4637 } 3065 }
4638 3066
4639 }); 3067 });
4640 3068
4641 })(); 3069 })();
4642 Polymer({ 3070 Polymer({
4643 3071
4644 is: 'iron-icon', 3072 is: 'iron-icon',
4645 3073
4646 properties: { 3074 properties: {
4647 3075
4648 /**
4649 * The name of the icon to use. The name should be of the form:
4650 * `iconset_name:icon_name`.
4651 */
4652 icon: { 3076 icon: {
4653 type: String, 3077 type: String,
4654 observer: '_iconChanged' 3078 observer: '_iconChanged'
4655 }, 3079 },
4656 3080
4657 /**
4658 * The name of the theme to used, if one is specified by the
4659 * iconset.
4660 */
4661 theme: { 3081 theme: {
4662 type: String, 3082 type: String,
4663 observer: '_updateIcon' 3083 observer: '_updateIcon'
4664 }, 3084 },
4665 3085
4666 /**
4667 * If using iron-icon without an iconset, you can set the src to be
4668 * the URL of an individual icon image file. Note that this will take
4669 * precedence over a given icon attribute.
4670 */
4671 src: { 3086 src: {
4672 type: String, 3087 type: String,
4673 observer: '_srcChanged' 3088 observer: '_srcChanged'
4674 }, 3089 },
4675 3090
4676 /**
4677 * @type {!Polymer.IronMeta}
4678 */
4679 _meta: { 3091 _meta: {
4680 value: Polymer.Base.create('iron-meta', {type: 'iconset'}), 3092 value: Polymer.Base.create('iron-meta', {type: 'iconset'}),
4681 observer: '_updateIcon' 3093 observer: '_updateIcon'
4682 } 3094 }
4683 3095
4684 }, 3096 },
4685 3097
4686 _DEFAULT_ICONSET: 'icons', 3098 _DEFAULT_ICONSET: 'icons',
4687 3099
4688 _iconChanged: function(icon) { 3100 _iconChanged: function(icon) {
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
4729 this._img.style.width = '100%'; 3141 this._img.style.width = '100%';
4730 this._img.style.height = '100%'; 3142 this._img.style.height = '100%';
4731 this._img.draggable = false; 3143 this._img.draggable = false;
4732 } 3144 }
4733 this._img.src = this.src; 3145 this._img.src = this.src;
4734 Polymer.dom(this.root).appendChild(this._img); 3146 Polymer.dom(this.root).appendChild(this._img);
4735 } 3147 }
4736 } 3148 }
4737 3149
4738 }); 3150 });
4739 /**
4740 * @demo demo/index.html
4741 * @polymerBehavior
4742 */
4743 Polymer.IronControlState = { 3151 Polymer.IronControlState = {
4744 3152
4745 properties: { 3153 properties: {
4746 3154
4747 /**
4748 * If true, the element currently has focus.
4749 */
4750 focused: { 3155 focused: {
4751 type: Boolean, 3156 type: Boolean,
4752 value: false, 3157 value: false,
4753 notify: true, 3158 notify: true,
4754 readOnly: true, 3159 readOnly: true,
4755 reflectToAttribute: true 3160 reflectToAttribute: true
4756 }, 3161 },
4757 3162
4758 /**
4759 * If true, the user cannot interact with this element.
4760 */
4761 disabled: { 3163 disabled: {
4762 type: Boolean, 3164 type: Boolean,
4763 value: false, 3165 value: false,
4764 notify: true, 3166 notify: true,
4765 observer: '_disabledChanged', 3167 observer: '_disabledChanged',
4766 reflectToAttribute: true 3168 reflectToAttribute: true
4767 }, 3169 },
4768 3170
4769 _oldTabIndex: { 3171 _oldTabIndex: {
4770 type: Number 3172 type: Number
(...skipping 11 matching lines...) Expand all
4782 observers: [ 3184 observers: [
4783 '_changedControlState(focused, disabled)' 3185 '_changedControlState(focused, disabled)'
4784 ], 3186 ],
4785 3187
4786 ready: function() { 3188 ready: function() {
4787 this.addEventListener('focus', this._boundFocusBlurHandler, true); 3189 this.addEventListener('focus', this._boundFocusBlurHandler, true);
4788 this.addEventListener('blur', this._boundFocusBlurHandler, true); 3190 this.addEventListener('blur', this._boundFocusBlurHandler, true);
4789 }, 3191 },
4790 3192
4791 _focusBlurHandler: function(event) { 3193 _focusBlurHandler: function(event) {
4792 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will
4793 // eventually become `this` due to retargeting; if we are not in
4794 // ShadowDOM land, `event.target` will eventually become `this` due
4795 // to the second conditional which fires a synthetic event (that is also
4796 // handled). In either case, we can disregard `event.path`.
4797 3194
4798 if (event.target === this) { 3195 if (event.target === this) {
4799 this._setFocused(event.type === 'focus'); 3196 this._setFocused(event.type === 'focus');
4800 } else if (!this.shadowRoot) { 3197 } else if (!this.shadowRoot) {
4801 var target = /** @type {Node} */(Polymer.dom(event).localTarget); 3198 var target = /** @type {Node} */(Polymer.dom(event).localTarget);
4802 if (!this.isLightDescendant(target)) { 3199 if (!this.isLightDescendant(target)) {
4803 this.fire(event.type, {sourceEvent: event}, { 3200 this.fire(event.type, {sourceEvent: event}, {
4804 node: this, 3201 node: this,
4805 bubbles: event.bubbles, 3202 bubbles: event.bubbles,
4806 cancelable: event.cancelable 3203 cancelable: event.cancelable
4807 }); 3204 });
4808 } 3205 }
4809 } 3206 }
4810 }, 3207 },
4811 3208
4812 _disabledChanged: function(disabled, old) { 3209 _disabledChanged: function(disabled, old) {
4813 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); 3210 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
4814 this.style.pointerEvents = disabled ? 'none' : ''; 3211 this.style.pointerEvents = disabled ? 'none' : '';
4815 if (disabled) { 3212 if (disabled) {
4816 this._oldTabIndex = this.tabIndex; 3213 this._oldTabIndex = this.tabIndex;
4817 this._setFocused(false); 3214 this._setFocused(false);
4818 this.tabIndex = -1; 3215 this.tabIndex = -1;
4819 this.blur(); 3216 this.blur();
4820 } else if (this._oldTabIndex !== undefined) { 3217 } else if (this._oldTabIndex !== undefined) {
4821 this.tabIndex = this._oldTabIndex; 3218 this.tabIndex = this._oldTabIndex;
4822 } 3219 }
4823 }, 3220 },
4824 3221
4825 _changedControlState: function() { 3222 _changedControlState: function() {
4826 // _controlStateChanged is abstract, follow-on behaviors may implement it
4827 if (this._controlStateChanged) { 3223 if (this._controlStateChanged) {
4828 this._controlStateChanged(); 3224 this._controlStateChanged();
4829 } 3225 }
4830 } 3226 }
4831 3227
4832 }; 3228 };
4833 /**
4834 * @demo demo/index.html
4835 * @polymerBehavior Polymer.IronButtonState
4836 */
4837 Polymer.IronButtonStateImpl = { 3229 Polymer.IronButtonStateImpl = {
4838 3230
4839 properties: { 3231 properties: {
4840 3232
4841 /**
4842 * If true, the user is currently holding down the button.
4843 */
4844 pressed: { 3233 pressed: {
4845 type: Boolean, 3234 type: Boolean,
4846 readOnly: true, 3235 readOnly: true,
4847 value: false, 3236 value: false,
4848 reflectToAttribute: true, 3237 reflectToAttribute: true,
4849 observer: '_pressedChanged' 3238 observer: '_pressedChanged'
4850 }, 3239 },
4851 3240
4852 /**
4853 * If true, the button toggles the active state with each tap or press
4854 * of the spacebar.
4855 */
4856 toggles: { 3241 toggles: {
4857 type: Boolean, 3242 type: Boolean,
4858 value: false, 3243 value: false,
4859 reflectToAttribute: true 3244 reflectToAttribute: true
4860 }, 3245 },
4861 3246
4862 /**
4863 * If true, the button is a toggle and is currently in the active state.
4864 */
4865 active: { 3247 active: {
4866 type: Boolean, 3248 type: Boolean,
4867 value: false, 3249 value: false,
4868 notify: true, 3250 notify: true,
4869 reflectToAttribute: true 3251 reflectToAttribute: true
4870 }, 3252 },
4871 3253
4872 /**
4873 * True if the element is currently being pressed by a "pointer," which
4874 * is loosely defined as mouse or touch input (but specifically excluding
4875 * keyboard input).
4876 */
4877 pointerDown: { 3254 pointerDown: {
4878 type: Boolean, 3255 type: Boolean,
4879 readOnly: true, 3256 readOnly: true,
4880 value: false 3257 value: false
4881 }, 3258 },
4882 3259
4883 /**
4884 * True if the input device that caused the element to receive focus
4885 * was a keyboard.
4886 */
4887 receivedFocusFromKeyboard: { 3260 receivedFocusFromKeyboard: {
4888 type: Boolean, 3261 type: Boolean,
4889 readOnly: true 3262 readOnly: true
4890 }, 3263 },
4891 3264
4892 /**
4893 * The aria attribute to be set if the button is a toggle and in the
4894 * active state.
4895 */
4896 ariaActiveAttribute: { 3265 ariaActiveAttribute: {
4897 type: String, 3266 type: String,
4898 value: 'aria-pressed', 3267 value: 'aria-pressed',
4899 observer: '_ariaActiveAttributeChanged' 3268 observer: '_ariaActiveAttributeChanged'
4900 } 3269 }
4901 }, 3270 },
4902 3271
4903 listeners: { 3272 listeners: {
4904 down: '_downHandler', 3273 down: '_downHandler',
4905 up: '_upHandler', 3274 up: '_upHandler',
4906 tap: '_tapHandler' 3275 tap: '_tapHandler'
4907 }, 3276 },
4908 3277
4909 observers: [ 3278 observers: [
4910 '_detectKeyboardFocus(focused)', 3279 '_detectKeyboardFocus(focused)',
4911 '_activeChanged(active, ariaActiveAttribute)' 3280 '_activeChanged(active, ariaActiveAttribute)'
4912 ], 3281 ],
4913 3282
4914 keyBindings: { 3283 keyBindings: {
4915 'enter:keydown': '_asyncClick', 3284 'enter:keydown': '_asyncClick',
4916 'space:keydown': '_spaceKeyDownHandler', 3285 'space:keydown': '_spaceKeyDownHandler',
4917 'space:keyup': '_spaceKeyUpHandler', 3286 'space:keyup': '_spaceKeyUpHandler',
4918 }, 3287 },
4919 3288
4920 _mouseEventRe: /^mouse/, 3289 _mouseEventRe: /^mouse/,
4921 3290
4922 _tapHandler: function() { 3291 _tapHandler: function() {
4923 if (this.toggles) { 3292 if (this.toggles) {
4924 // a tap is needed to toggle the active state
4925 this._userActivate(!this.active); 3293 this._userActivate(!this.active);
4926 } else { 3294 } else {
4927 this.active = false; 3295 this.active = false;
4928 } 3296 }
4929 }, 3297 },
4930 3298
4931 _detectKeyboardFocus: function(focused) { 3299 _detectKeyboardFocus: function(focused) {
4932 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); 3300 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
4933 }, 3301 },
4934 3302
4935 // to emulate native checkbox, (de-)activations from a user interaction fire
4936 // 'change' events
4937 _userActivate: function(active) { 3303 _userActivate: function(active) {
4938 if (this.active !== active) { 3304 if (this.active !== active) {
4939 this.active = active; 3305 this.active = active;
4940 this.fire('change'); 3306 this.fire('change');
4941 } 3307 }
4942 }, 3308 },
4943 3309
4944 _downHandler: function(event) { 3310 _downHandler: function(event) {
4945 this._setPointerDown(true); 3311 this._setPointerDown(true);
4946 this._setPressed(true); 3312 this._setPressed(true);
4947 this._setReceivedFocusFromKeyboard(false); 3313 this._setReceivedFocusFromKeyboard(false);
4948 }, 3314 },
4949 3315
4950 _upHandler: function() { 3316 _upHandler: function() {
4951 this._setPointerDown(false); 3317 this._setPointerDown(false);
4952 this._setPressed(false); 3318 this._setPressed(false);
4953 }, 3319 },
4954 3320
4955 /**
4956 * @param {!KeyboardEvent} event .
4957 */
4958 _spaceKeyDownHandler: function(event) { 3321 _spaceKeyDownHandler: function(event) {
4959 var keyboardEvent = event.detail.keyboardEvent; 3322 var keyboardEvent = event.detail.keyboardEvent;
4960 var target = Polymer.dom(keyboardEvent).localTarget; 3323 var target = Polymer.dom(keyboardEvent).localTarget;
4961 3324
4962 // Ignore the event if this is coming from a focused light child, since th at
4963 // element will deal with it.
4964 if (this.isLightDescendant(/** @type {Node} */(target))) 3325 if (this.isLightDescendant(/** @type {Node} */(target)))
4965 return; 3326 return;
4966 3327
4967 keyboardEvent.preventDefault(); 3328 keyboardEvent.preventDefault();
4968 keyboardEvent.stopImmediatePropagation(); 3329 keyboardEvent.stopImmediatePropagation();
4969 this._setPressed(true); 3330 this._setPressed(true);
4970 }, 3331 },
4971 3332
4972 /**
4973 * @param {!KeyboardEvent} event .
4974 */
4975 _spaceKeyUpHandler: function(event) { 3333 _spaceKeyUpHandler: function(event) {
4976 var keyboardEvent = event.detail.keyboardEvent; 3334 var keyboardEvent = event.detail.keyboardEvent;
4977 var target = Polymer.dom(keyboardEvent).localTarget; 3335 var target = Polymer.dom(keyboardEvent).localTarget;
4978 3336
4979 // Ignore the event if this is coming from a focused light child, since th at
4980 // element will deal with it.
4981 if (this.isLightDescendant(/** @type {Node} */(target))) 3337 if (this.isLightDescendant(/** @type {Node} */(target)))
4982 return; 3338 return;
4983 3339
4984 if (this.pressed) { 3340 if (this.pressed) {
4985 this._asyncClick(); 3341 this._asyncClick();
4986 } 3342 }
4987 this._setPressed(false); 3343 this._setPressed(false);
4988 }, 3344 },
4989 3345
4990 // trigger click asynchronously, the asynchrony is useful to allow one
4991 // event handler to unwind before triggering another event
4992 _asyncClick: function() { 3346 _asyncClick: function() {
4993 this.async(function() { 3347 this.async(function() {
4994 this.click(); 3348 this.click();
4995 }, 1); 3349 }, 1);
4996 }, 3350 },
4997 3351
4998 // any of these changes are considered a change to button state
4999 3352
5000 _pressedChanged: function(pressed) { 3353 _pressedChanged: function(pressed) {
5001 this._changedButtonState(); 3354 this._changedButtonState();
5002 }, 3355 },
5003 3356
5004 _ariaActiveAttributeChanged: function(value, oldValue) { 3357 _ariaActiveAttributeChanged: function(value, oldValue) {
5005 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { 3358 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
5006 this.removeAttribute(oldValue); 3359 this.removeAttribute(oldValue);
5007 } 3360 }
5008 }, 3361 },
5009 3362
5010 _activeChanged: function(active, ariaActiveAttribute) { 3363 _activeChanged: function(active, ariaActiveAttribute) {
5011 if (this.toggles) { 3364 if (this.toggles) {
5012 this.setAttribute(this.ariaActiveAttribute, 3365 this.setAttribute(this.ariaActiveAttribute,
5013 active ? 'true' : 'false'); 3366 active ? 'true' : 'false');
5014 } else { 3367 } else {
5015 this.removeAttribute(this.ariaActiveAttribute); 3368 this.removeAttribute(this.ariaActiveAttribute);
5016 } 3369 }
5017 this._changedButtonState(); 3370 this._changedButtonState();
5018 }, 3371 },
5019 3372
5020 _controlStateChanged: function() { 3373 _controlStateChanged: function() {
5021 if (this.disabled) { 3374 if (this.disabled) {
5022 this._setPressed(false); 3375 this._setPressed(false);
5023 } else { 3376 } else {
5024 this._changedButtonState(); 3377 this._changedButtonState();
5025 } 3378 }
5026 }, 3379 },
5027 3380
5028 // provide hook for follow-on behaviors to react to button-state
5029 3381
5030 _changedButtonState: function() { 3382 _changedButtonState: function() {
5031 if (this._buttonStateChanged) { 3383 if (this._buttonStateChanged) {
5032 this._buttonStateChanged(); // abstract 3384 this._buttonStateChanged(); // abstract
5033 } 3385 }
5034 } 3386 }
5035 3387
5036 }; 3388 };
5037 3389
5038 /** @polymerBehavior */ 3390 /** @polymerBehavior */
5039 Polymer.IronButtonState = [ 3391 Polymer.IronButtonState = [
5040 Polymer.IronA11yKeysBehavior, 3392 Polymer.IronA11yKeysBehavior,
5041 Polymer.IronButtonStateImpl 3393 Polymer.IronButtonStateImpl
5042 ]; 3394 ];
5043 (function() { 3395 (function() {
5044 var Utility = { 3396 var Utility = {
5045 distance: function(x1, y1, x2, y2) { 3397 distance: function(x1, y1, x2, y2) {
5046 var xDelta = (x1 - x2); 3398 var xDelta = (x1 - x2);
5047 var yDelta = (y1 - y2); 3399 var yDelta = (y1 - y2);
5048 3400
5049 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); 3401 return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
5050 }, 3402 },
5051 3403
5052 now: window.performance && window.performance.now ? 3404 now: window.performance && window.performance.now ?
5053 window.performance.now.bind(window.performance) : Date.now 3405 window.performance.now.bind(window.performance) : Date.now
5054 }; 3406 };
5055 3407
5056 /**
5057 * @param {HTMLElement} element
5058 * @constructor
5059 */
5060 function ElementMetrics(element) { 3408 function ElementMetrics(element) {
5061 this.element = element; 3409 this.element = element;
5062 this.width = this.boundingRect.width; 3410 this.width = this.boundingRect.width;
5063 this.height = this.boundingRect.height; 3411 this.height = this.boundingRect.height;
5064 3412
5065 this.size = Math.max(this.width, this.height); 3413 this.size = Math.max(this.width, this.height);
5066 } 3414 }
5067 3415
5068 ElementMetrics.prototype = { 3416 ElementMetrics.prototype = {
5069 get boundingRect () { 3417 get boundingRect () {
5070 return this.element.getBoundingClientRect(); 3418 return this.element.getBoundingClientRect();
5071 }, 3419 },
5072 3420
5073 furthestCornerDistanceFrom: function(x, y) { 3421 furthestCornerDistanceFrom: function(x, y) {
5074 var topLeft = Utility.distance(x, y, 0, 0); 3422 var topLeft = Utility.distance(x, y, 0, 0);
5075 var topRight = Utility.distance(x, y, this.width, 0); 3423 var topRight = Utility.distance(x, y, this.width, 0);
5076 var bottomLeft = Utility.distance(x, y, 0, this.height); 3424 var bottomLeft = Utility.distance(x, y, 0, this.height);
5077 var bottomRight = Utility.distance(x, y, this.width, this.height); 3425 var bottomRight = Utility.distance(x, y, this.width, this.height);
5078 3426
5079 return Math.max(topLeft, topRight, bottomLeft, bottomRight); 3427 return Math.max(topLeft, topRight, bottomLeft, bottomRight);
5080 } 3428 }
5081 }; 3429 };
5082 3430
5083 /**
5084 * @param {HTMLElement} element
5085 * @constructor
5086 */
5087 function Ripple(element) { 3431 function Ripple(element) {
5088 this.element = element; 3432 this.element = element;
5089 this.color = window.getComputedStyle(element).color; 3433 this.color = window.getComputedStyle(element).color;
5090 3434
5091 this.wave = document.createElement('div'); 3435 this.wave = document.createElement('div');
5092 this.waveContainer = document.createElement('div'); 3436 this.waveContainer = document.createElement('div');
5093 this.wave.style.backgroundColor = this.color; 3437 this.wave.style.backgroundColor = this.color;
5094 this.wave.classList.add('wave'); 3438 this.wave.classList.add('wave');
5095 this.waveContainer.classList.add('wave-container'); 3439 this.waveContainer.classList.add('wave-container');
5096 Polymer.dom(this.waveContainer).appendChild(this.wave); 3440 Polymer.dom(this.waveContainer).appendChild(this.wave);
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
5170 return this.initialOpacity; 3514 return this.initialOpacity;
5171 } 3515 }
5172 3516
5173 return Math.max( 3517 return Math.max(
5174 0, 3518 0,
5175 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe locity 3519 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe locity
5176 ); 3520 );
5177 }, 3521 },
5178 3522
5179 get outerOpacity() { 3523 get outerOpacity() {
5180 // Linear increase in background opacity, capped at the opacity
5181 // of the wavefront (waveOpacity).
5182 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; 3524 var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
5183 var waveOpacity = this.opacity; 3525 var waveOpacity = this.opacity;
5184 3526
5185 return Math.max( 3527 return Math.max(
5186 0, 3528 0,
5187 Math.min(outerOpacity, waveOpacity) 3529 Math.min(outerOpacity, waveOpacity)
5188 ); 3530 );
5189 }, 3531 },
5190 3532
5191 get isOpacityFullyDecayed() { 3533 get isOpacityFullyDecayed() {
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
5250 var dx; 3592 var dx;
5251 var dy; 3593 var dy;
5252 3594
5253 this.wave.style.opacity = this.opacity; 3595 this.wave.style.opacity = this.opacity;
5254 3596
5255 scale = this.radius / (this.containerMetrics.size / 2); 3597 scale = this.radius / (this.containerMetrics.size / 2);
5256 dx = this.xNow - (this.containerMetrics.width / 2); 3598 dx = this.xNow - (this.containerMetrics.width / 2);
5257 dy = this.yNow - (this.containerMetrics.height / 2); 3599 dy = this.yNow - (this.containerMetrics.height / 2);
5258 3600
5259 3601
5260 // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
5261 // https://bugs.webkit.org/show_bug.cgi?id=98538
5262 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)'; 3602 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
5263 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)'; 3603 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
5264 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; 3604 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
5265 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; 3605 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
5266 }, 3606 },
5267 3607
5268 /** @param {Event=} event */ 3608 /** @param {Event=} event */
5269 downAction: function(event) { 3609 downAction: function(event) {
5270 var xCenter = this.containerMetrics.width / 2; 3610 var xCenter = this.containerMetrics.width / 2;
5271 var yCenter = this.containerMetrics.height / 2; 3611 var yCenter = this.containerMetrics.height / 2;
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
5327 }; 3667 };
5328 3668
5329 Polymer({ 3669 Polymer({
5330 is: 'paper-ripple', 3670 is: 'paper-ripple',
5331 3671
5332 behaviors: [ 3672 behaviors: [
5333 Polymer.IronA11yKeysBehavior 3673 Polymer.IronA11yKeysBehavior
5334 ], 3674 ],
5335 3675
5336 properties: { 3676 properties: {
5337 /**
5338 * The initial opacity set on the wave.
5339 *
5340 * @attribute initialOpacity
5341 * @type number
5342 * @default 0.25
5343 */
5344 initialOpacity: { 3677 initialOpacity: {
5345 type: Number, 3678 type: Number,
5346 value: 0.25 3679 value: 0.25
5347 }, 3680 },
5348 3681
5349 /**
5350 * How fast (opacity per second) the wave fades out.
5351 *
5352 * @attribute opacityDecayVelocity
5353 * @type number
5354 * @default 0.8
5355 */
5356 opacityDecayVelocity: { 3682 opacityDecayVelocity: {
5357 type: Number, 3683 type: Number,
5358 value: 0.8 3684 value: 0.8
5359 }, 3685 },
5360 3686
5361 /**
5362 * If true, ripples will exhibit a gravitational pull towards
5363 * the center of their container as they fade away.
5364 *
5365 * @attribute recenters
5366 * @type boolean
5367 * @default false
5368 */
5369 recenters: { 3687 recenters: {
5370 type: Boolean, 3688 type: Boolean,
5371 value: false 3689 value: false
5372 }, 3690 },
5373 3691
5374 /**
5375 * If true, ripples will center inside its container
5376 *
5377 * @attribute recenters
5378 * @type boolean
5379 * @default false
5380 */
5381 center: { 3692 center: {
5382 type: Boolean, 3693 type: Boolean,
5383 value: false 3694 value: false
5384 }, 3695 },
5385 3696
5386 /**
5387 * A list of the visual ripples.
5388 *
5389 * @attribute ripples
5390 * @type Array
5391 * @default []
5392 */
5393 ripples: { 3697 ripples: {
5394 type: Array, 3698 type: Array,
5395 value: function() { 3699 value: function() {
5396 return []; 3700 return [];
5397 } 3701 }
5398 }, 3702 },
5399 3703
5400 /**
5401 * True when there are visible ripples animating within the
5402 * element.
5403 */
5404 animating: { 3704 animating: {
5405 type: Boolean, 3705 type: Boolean,
5406 readOnly: true, 3706 readOnly: true,
5407 reflectToAttribute: true, 3707 reflectToAttribute: true,
5408 value: false 3708 value: false
5409 }, 3709 },
5410 3710
5411 /**
5412 * If true, the ripple will remain in the "down" state until `holdDown`
5413 * is set to false again.
5414 */
5415 holdDown: { 3711 holdDown: {
5416 type: Boolean, 3712 type: Boolean,
5417 value: false, 3713 value: false,
5418 observer: '_holdDownChanged' 3714 observer: '_holdDownChanged'
5419 }, 3715 },
5420 3716
5421 /**
5422 * If true, the ripple will not generate a ripple effect
5423 * via pointer interaction.
5424 * Calling ripple's imperative api like `simulatedRipple` will
5425 * still generate the ripple effect.
5426 */
5427 noink: { 3717 noink: {
5428 type: Boolean, 3718 type: Boolean,
5429 value: false 3719 value: false
5430 }, 3720 },
5431 3721
5432 _animating: { 3722 _animating: {
5433 type: Boolean 3723 type: Boolean
5434 }, 3724 },
5435 3725
5436 _boundAnimate: { 3726 _boundAnimate: {
5437 type: Function, 3727 type: Function,
5438 value: function() { 3728 value: function() {
5439 return this.animate.bind(this); 3729 return this.animate.bind(this);
5440 } 3730 }
5441 } 3731 }
5442 }, 3732 },
5443 3733
5444 get target () { 3734 get target () {
5445 return this.keyEventTarget; 3735 return this.keyEventTarget;
5446 }, 3736 },
5447 3737
5448 keyBindings: { 3738 keyBindings: {
5449 'enter:keydown': '_onEnterKeydown', 3739 'enter:keydown': '_onEnterKeydown',
5450 'space:keydown': '_onSpaceKeydown', 3740 'space:keydown': '_onSpaceKeydown',
5451 'space:keyup': '_onSpaceKeyup' 3741 'space:keyup': '_onSpaceKeyup'
5452 }, 3742 },
5453 3743
5454 attached: function() { 3744 attached: function() {
5455 // Set up a11yKeysBehavior to listen to key events on the target,
5456 // so that space and enter activate the ripple even if the target doesn' t
5457 // handle key events. The key handlers deal with `noink` themselves.
5458 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE 3745 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
5459 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host; 3746 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host;
5460 } else { 3747 } else {
5461 this.keyEventTarget = this.parentNode; 3748 this.keyEventTarget = this.parentNode;
5462 } 3749 }
5463 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); 3750 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget);
5464 this.listen(keyEventTarget, 'up', 'uiUpAction'); 3751 this.listen(keyEventTarget, 'up', 'uiUpAction');
5465 this.listen(keyEventTarget, 'down', 'uiDownAction'); 3752 this.listen(keyEventTarget, 'down', 'uiDownAction');
5466 }, 3753 },
5467 3754
5468 detached: function() { 3755 detached: function() {
5469 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); 3756 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction');
5470 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); 3757 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction');
5471 this.keyEventTarget = null; 3758 this.keyEventTarget = null;
5472 }, 3759 },
5473 3760
5474 get shouldKeepAnimating () { 3761 get shouldKeepAnimating () {
5475 for (var index = 0; index < this.ripples.length; ++index) { 3762 for (var index = 0; index < this.ripples.length; ++index) {
5476 if (!this.ripples[index].isAnimationComplete) { 3763 if (!this.ripples[index].isAnimationComplete) {
5477 return true; 3764 return true;
5478 } 3765 }
5479 } 3766 }
5480 3767
5481 return false; 3768 return false;
5482 }, 3769 },
5483 3770
5484 simulatedRipple: function() { 3771 simulatedRipple: function() {
5485 this.downAction(null); 3772 this.downAction(null);
5486 3773
5487 // Please see polymer/polymer#1305
5488 this.async(function() { 3774 this.async(function() {
5489 this.upAction(); 3775 this.upAction();
5490 }, 1); 3776 }, 1);
5491 }, 3777 },
5492 3778
5493 /**
5494 * Provokes a ripple down effect via a UI event,
5495 * respecting the `noink` property.
5496 * @param {Event=} event
5497 */
5498 uiDownAction: function(event) { 3779 uiDownAction: function(event) {
5499 if (!this.noink) { 3780 if (!this.noink) {
5500 this.downAction(event); 3781 this.downAction(event);
5501 } 3782 }
5502 }, 3783 },
5503 3784
5504 /**
5505 * Provokes a ripple down effect via a UI event,
5506 * *not* respecting the `noink` property.
5507 * @param {Event=} event
5508 */
5509 downAction: function(event) { 3785 downAction: function(event) {
5510 if (this.holdDown && this.ripples.length > 0) { 3786 if (this.holdDown && this.ripples.length > 0) {
5511 return; 3787 return;
5512 } 3788 }
5513 3789
5514 var ripple = this.addRipple(); 3790 var ripple = this.addRipple();
5515 3791
5516 ripple.downAction(event); 3792 ripple.downAction(event);
5517 3793
5518 if (!this._animating) { 3794 if (!this._animating) {
5519 this._animating = true; 3795 this._animating = true;
5520 this.animate(); 3796 this.animate();
5521 } 3797 }
5522 }, 3798 },
5523 3799
5524 /**
5525 * Provokes a ripple up effect via a UI event,
5526 * respecting the `noink` property.
5527 * @param {Event=} event
5528 */
5529 uiUpAction: function(event) { 3800 uiUpAction: function(event) {
5530 if (!this.noink) { 3801 if (!this.noink) {
5531 this.upAction(event); 3802 this.upAction(event);
5532 } 3803 }
5533 }, 3804 },
5534 3805
5535 /**
5536 * Provokes a ripple up effect via a UI event,
5537 * *not* respecting the `noink` property.
5538 * @param {Event=} event
5539 */
5540 upAction: function(event) { 3806 upAction: function(event) {
5541 if (this.holdDown) { 3807 if (this.holdDown) {
5542 return; 3808 return;
5543 } 3809 }
5544 3810
5545 this.ripples.forEach(function(ripple) { 3811 this.ripples.forEach(function(ripple) {
5546 ripple.upAction(event); 3812 ripple.upAction(event);
5547 }); 3813 });
5548 3814
5549 this._animating = true; 3815 this._animating = true;
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
5616 }, 3882 },
5617 3883
5618 _onSpaceKeydown: function() { 3884 _onSpaceKeydown: function() {
5619 this.uiDownAction(); 3885 this.uiDownAction();
5620 }, 3886 },
5621 3887
5622 _onSpaceKeyup: function() { 3888 _onSpaceKeyup: function() {
5623 this.uiUpAction(); 3889 this.uiUpAction();
5624 }, 3890 },
5625 3891
5626 // note: holdDown does not respect noink since it can be a focus based
5627 // effect.
5628 _holdDownChanged: function(newVal, oldVal) { 3892 _holdDownChanged: function(newVal, oldVal) {
5629 if (oldVal === undefined) { 3893 if (oldVal === undefined) {
5630 return; 3894 return;
5631 } 3895 }
5632 if (newVal) { 3896 if (newVal) {
5633 this.downAction(); 3897 this.downAction();
5634 } else { 3898 } else {
5635 this.upAction(); 3899 this.upAction();
5636 } 3900 }
5637 } 3901 }
5638 3902
5639 /**
5640 Fired when the animation finishes.
5641 This is useful if you want to wait until
5642 the ripple animation finishes to perform some action.
5643
5644 @event transitionend
5645 @param {{node: Object}} detail Contains the animated node.
5646 */
5647 }); 3903 });
5648 })(); 3904 })();
5649 /**
5650 * `Polymer.PaperRippleBehavior` dynamically implements a ripple
5651 * when the element has focus via pointer or keyboard.
5652 *
5653 * NOTE: This behavior is intended to be used in conjunction with and after
5654 * `Polymer.IronButtonState` and `Polymer.IronControlState`.
5655 *
5656 * @polymerBehavior Polymer.PaperRippleBehavior
5657 */
5658 Polymer.PaperRippleBehavior = { 3905 Polymer.PaperRippleBehavior = {
5659 properties: { 3906 properties: {
5660 /**
5661 * If true, the element will not produce a ripple effect when interacted
5662 * with via the pointer.
5663 */
5664 noink: { 3907 noink: {
5665 type: Boolean, 3908 type: Boolean,
5666 observer: '_noinkChanged' 3909 observer: '_noinkChanged'
5667 }, 3910 },
5668 3911
5669 /**
5670 * @type {Element|undefined}
5671 */
5672 _rippleContainer: { 3912 _rippleContainer: {
5673 type: Object, 3913 type: Object,
5674 } 3914 }
5675 }, 3915 },
5676 3916
5677 /**
5678 * Ensures a `<paper-ripple>` element is available when the element is
5679 * focused.
5680 */
5681 _buttonStateChanged: function() { 3917 _buttonStateChanged: function() {
5682 if (this.focused) { 3918 if (this.focused) {
5683 this.ensureRipple(); 3919 this.ensureRipple();
5684 } 3920 }
5685 }, 3921 },
5686 3922
5687 /**
5688 * In addition to the functionality provided in `IronButtonState`, ensures
5689 * a ripple effect is created when the element is in a `pressed` state.
5690 */
5691 _downHandler: function(event) { 3923 _downHandler: function(event) {
5692 Polymer.IronButtonStateImpl._downHandler.call(this, event); 3924 Polymer.IronButtonStateImpl._downHandler.call(this, event);
5693 if (this.pressed) { 3925 if (this.pressed) {
5694 this.ensureRipple(event); 3926 this.ensureRipple(event);
5695 } 3927 }
5696 }, 3928 },
5697 3929
5698 /**
5699 * Ensures this element contains a ripple effect. For startup efficiency
5700 * the ripple effect is dynamically on demand when needed.
5701 * @param {!Event=} optTriggeringEvent (optional) event that triggered the
5702 * ripple.
5703 */
5704 ensureRipple: function(optTriggeringEvent) { 3930 ensureRipple: function(optTriggeringEvent) {
5705 if (!this.hasRipple()) { 3931 if (!this.hasRipple()) {
5706 this._ripple = this._createRipple(); 3932 this._ripple = this._createRipple();
5707 this._ripple.noink = this.noink; 3933 this._ripple.noink = this.noink;
5708 var rippleContainer = this._rippleContainer || this.root; 3934 var rippleContainer = this._rippleContainer || this.root;
5709 if (rippleContainer) { 3935 if (rippleContainer) {
5710 Polymer.dom(rippleContainer).appendChild(this._ripple); 3936 Polymer.dom(rippleContainer).appendChild(this._ripple);
5711 } 3937 }
5712 if (optTriggeringEvent) { 3938 if (optTriggeringEvent) {
5713 // Check if the event happened inside of the ripple container
5714 // Fall back to host instead of the root because distributed text
5715 // nodes are not valid event targets
5716 var domContainer = Polymer.dom(this._rippleContainer || this); 3939 var domContainer = Polymer.dom(this._rippleContainer || this);
5717 var target = Polymer.dom(optTriggeringEvent).rootTarget; 3940 var target = Polymer.dom(optTriggeringEvent).rootTarget;
5718 if (domContainer.deepContains( /** @type {Node} */(target))) { 3941 if (domContainer.deepContains( /** @type {Node} */(target))) {
5719 this._ripple.uiDownAction(optTriggeringEvent); 3942 this._ripple.uiDownAction(optTriggeringEvent);
5720 } 3943 }
5721 } 3944 }
5722 } 3945 }
5723 }, 3946 },
5724 3947
5725 /**
5726 * Returns the `<paper-ripple>` element used by this element to create
5727 * ripple effects. The element's ripple is created on demand, when
5728 * necessary, and calling this method will force the
5729 * ripple to be created.
5730 */
5731 getRipple: function() { 3948 getRipple: function() {
5732 this.ensureRipple(); 3949 this.ensureRipple();
5733 return this._ripple; 3950 return this._ripple;
5734 }, 3951 },
5735 3952
5736 /**
5737 * Returns true if this element currently contains a ripple effect.
5738 * @return {boolean}
5739 */
5740 hasRipple: function() { 3953 hasRipple: function() {
5741 return Boolean(this._ripple); 3954 return Boolean(this._ripple);
5742 }, 3955 },
5743 3956
5744 /**
5745 * Create the element's ripple effect via creating a `<paper-ripple>`.
5746 * Override this method to customize the ripple element.
5747 * @return {!PaperRippleElement} Returns a `<paper-ripple>` element.
5748 */
5749 _createRipple: function() { 3957 _createRipple: function() {
5750 return /** @type {!PaperRippleElement} */ ( 3958 return /** @type {!PaperRippleElement} */ (
5751 document.createElement('paper-ripple')); 3959 document.createElement('paper-ripple'));
5752 }, 3960 },
5753 3961
5754 _noinkChanged: function(noink) { 3962 _noinkChanged: function(noink) {
5755 if (this.hasRipple()) { 3963 if (this.hasRipple()) {
5756 this._ripple.noink = noink; 3964 this._ripple.noink = noink;
5757 } 3965 }
5758 } 3966 }
5759 }; 3967 };
5760 /** @polymerBehavior Polymer.PaperButtonBehavior */ 3968 /** @polymerBehavior Polymer.PaperButtonBehavior */
5761 Polymer.PaperButtonBehaviorImpl = { 3969 Polymer.PaperButtonBehaviorImpl = {
5762 properties: { 3970 properties: {
5763 /**
5764 * The z-depth of this element, from 0-5. Setting to 0 will remove the
5765 * shadow, and each increasing number greater than 0 will be "deeper"
5766 * than the last.
5767 *
5768 * @attribute elevation
5769 * @type number
5770 * @default 1
5771 */
5772 elevation: { 3971 elevation: {
5773 type: Number, 3972 type: Number,
5774 reflectToAttribute: true, 3973 reflectToAttribute: true,
5775 readOnly: true 3974 readOnly: true
5776 } 3975 }
5777 }, 3976 },
5778 3977
5779 observers: [ 3978 observers: [
5780 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)', 3979 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)',
5781 '_computeKeyboardClass(receivedFocusFromKeyboard)' 3980 '_computeKeyboardClass(receivedFocusFromKeyboard)'
(...skipping 14 matching lines...) Expand all
5796 } else if (this.receivedFocusFromKeyboard) { 3995 } else if (this.receivedFocusFromKeyboard) {
5797 e = 3; 3996 e = 3;
5798 } 3997 }
5799 this._setElevation(e); 3998 this._setElevation(e);
5800 }, 3999 },
5801 4000
5802 _computeKeyboardClass: function(receivedFocusFromKeyboard) { 4001 _computeKeyboardClass: function(receivedFocusFromKeyboard) {
5803 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard); 4002 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard);
5804 }, 4003 },
5805 4004
5806 /**
5807 * In addition to `IronButtonState` behavior, when space key goes down,
5808 * create a ripple down effect.
5809 *
5810 * @param {!KeyboardEvent} event .
5811 */
5812 _spaceKeyDownHandler: function(event) { 4005 _spaceKeyDownHandler: function(event) {
5813 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); 4006 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event);
5814 // Ensure that there is at most one ripple when the space key is held down .
5815 if (this.hasRipple() && this.getRipple().ripples.length < 1) { 4007 if (this.hasRipple() && this.getRipple().ripples.length < 1) {
5816 this._ripple.uiDownAction(); 4008 this._ripple.uiDownAction();
5817 } 4009 }
5818 }, 4010 },
5819 4011
5820 /**
5821 * In addition to `IronButtonState` behavior, when space key goes up,
5822 * create a ripple up effect.
5823 *
5824 * @param {!KeyboardEvent} event .
5825 */
5826 _spaceKeyUpHandler: function(event) { 4012 _spaceKeyUpHandler: function(event) {
5827 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); 4013 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event);
5828 if (this.hasRipple()) { 4014 if (this.hasRipple()) {
5829 this._ripple.uiUpAction(); 4015 this._ripple.uiUpAction();
5830 } 4016 }
5831 } 4017 }
5832 }; 4018 };
5833 4019
5834 /** @polymerBehavior */ 4020 /** @polymerBehavior */
5835 Polymer.PaperButtonBehavior = [ 4021 Polymer.PaperButtonBehavior = [
5836 Polymer.IronButtonState, 4022 Polymer.IronButtonState,
5837 Polymer.IronControlState, 4023 Polymer.IronControlState,
5838 Polymer.PaperRippleBehavior, 4024 Polymer.PaperRippleBehavior,
5839 Polymer.PaperButtonBehaviorImpl 4025 Polymer.PaperButtonBehaviorImpl
5840 ]; 4026 ];
5841 Polymer({ 4027 Polymer({
5842 is: 'paper-button', 4028 is: 'paper-button',
5843 4029
5844 behaviors: [ 4030 behaviors: [
5845 Polymer.PaperButtonBehavior 4031 Polymer.PaperButtonBehavior
5846 ], 4032 ],
5847 4033
5848 properties: { 4034 properties: {
5849 /**
5850 * If true, the button should be styled with a shadow.
5851 */
5852 raised: { 4035 raised: {
5853 type: Boolean, 4036 type: Boolean,
5854 reflectToAttribute: true, 4037 reflectToAttribute: true,
5855 value: false, 4038 value: false,
5856 observer: '_calculateElevation' 4039 observer: '_calculateElevation'
5857 } 4040 }
5858 }, 4041 },
5859 4042
5860 _calculateElevation: function() { 4043 _calculateElevation: function() {
5861 if (!this.raised) { 4044 if (!this.raised) {
5862 this._setElevation(0); 4045 this._setElevation(0);
5863 } else { 4046 } else {
5864 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); 4047 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
5865 } 4048 }
5866 } 4049 }
5867 4050
5868 /**
5869 Fired when the animation finishes.
5870 This is useful if you want to wait until
5871 the ripple animation finishes to perform some action.
5872
5873 @event transitionend
5874 Event param: {{node: Object}} detail Contains the animated node.
5875 */
5876 }); 4051 });
5877 Polymer({ 4052 Polymer({
5878 is: 'paper-icon-button-light', 4053 is: 'paper-icon-button-light',
5879 extends: 'button', 4054 extends: 'button',
5880 4055
5881 behaviors: [ 4056 behaviors: [
5882 Polymer.PaperRippleBehavior 4057 Polymer.PaperRippleBehavior
5883 ], 4058 ],
5884 4059
5885 listeners: { 4060 listeners: {
5886 'down': '_rippleDown', 4061 'down': '_rippleDown',
5887 'up': '_rippleUp', 4062 'up': '_rippleUp',
5888 'focus': '_rippleDown', 4063 'focus': '_rippleDown',
5889 'blur': '_rippleUp', 4064 'blur': '_rippleUp',
5890 }, 4065 },
5891 4066
5892 _rippleDown: function() { 4067 _rippleDown: function() {
5893 this.getRipple().downAction(); 4068 this.getRipple().downAction();
5894 }, 4069 },
5895 4070
5896 _rippleUp: function() { 4071 _rippleUp: function() {
5897 this.getRipple().upAction(); 4072 this.getRipple().upAction();
5898 }, 4073 },
5899 4074
5900 /**
5901 * @param {...*} var_args
5902 */
5903 ensureRipple: function(var_args) { 4075 ensureRipple: function(var_args) {
5904 var lastRipple = this._ripple; 4076 var lastRipple = this._ripple;
5905 Polymer.PaperRippleBehavior.ensureRipple.apply(this, arguments); 4077 Polymer.PaperRippleBehavior.ensureRipple.apply(this, arguments);
5906 if (this._ripple && this._ripple !== lastRipple) { 4078 if (this._ripple && this._ripple !== lastRipple) {
5907 this._ripple.center = true; 4079 this._ripple.center = true;
5908 this._ripple.classList.add('circle'); 4080 this._ripple.classList.add('circle');
5909 } 4081 }
5910 } 4082 }
5911 }); 4083 });
5912 /**
5913 * `iron-range-behavior` provides the behavior for something with a minimum to m aximum range.
5914 *
5915 * @demo demo/index.html
5916 * @polymerBehavior
5917 */
5918 Polymer.IronRangeBehavior = { 4084 Polymer.IronRangeBehavior = {
5919 4085
5920 properties: { 4086 properties: {
5921 4087
5922 /**
5923 * The number that represents the current value.
5924 */
5925 value: { 4088 value: {
5926 type: Number, 4089 type: Number,
5927 value: 0, 4090 value: 0,
5928 notify: true, 4091 notify: true,
5929 reflectToAttribute: true 4092 reflectToAttribute: true
5930 }, 4093 },
5931 4094
5932 /**
5933 * The number that indicates the minimum value of the range.
5934 */
5935 min: { 4095 min: {
5936 type: Number, 4096 type: Number,
5937 value: 0, 4097 value: 0,
5938 notify: true 4098 notify: true
5939 }, 4099 },
5940 4100
5941 /**
5942 * The number that indicates the maximum value of the range.
5943 */
5944 max: { 4101 max: {
5945 type: Number, 4102 type: Number,
5946 value: 100, 4103 value: 100,
5947 notify: true 4104 notify: true
5948 }, 4105 },
5949 4106
5950 /**
5951 * Specifies the value granularity of the range's value.
5952 */
5953 step: { 4107 step: {
5954 type: Number, 4108 type: Number,
5955 value: 1, 4109 value: 1,
5956 notify: true 4110 notify: true
5957 }, 4111 },
5958 4112
5959 /**
5960 * Returns the ratio of the value.
5961 */
5962 ratio: { 4113 ratio: {
5963 type: Number, 4114 type: Number,
5964 value: 0, 4115 value: 0,
5965 readOnly: true, 4116 readOnly: true,
5966 notify: true 4117 notify: true
5967 }, 4118 },
5968 }, 4119 },
5969 4120
5970 observers: [ 4121 observers: [
5971 '_update(value, min, max, step)' 4122 '_update(value, min, max, step)'
5972 ], 4123 ],
5973 4124
5974 _calcRatio: function(value) { 4125 _calcRatio: function(value) {
5975 return (this._clampValue(value) - this.min) / (this.max - this.min); 4126 return (this._clampValue(value) - this.min) / (this.max - this.min);
5976 }, 4127 },
5977 4128
5978 _clampValue: function(value) { 4129 _clampValue: function(value) {
5979 return Math.min(this.max, Math.max(this.min, this._calcStep(value))); 4130 return Math.min(this.max, Math.max(this.min, this._calcStep(value)));
5980 }, 4131 },
5981 4132
5982 _calcStep: function(value) { 4133 _calcStep: function(value) {
5983 // polymer/issues/2493
5984 value = parseFloat(value); 4134 value = parseFloat(value);
5985 4135
5986 if (!this.step) { 4136 if (!this.step) {
5987 return value; 4137 return value;
5988 } 4138 }
5989 4139
5990 var numSteps = Math.round((value - this.min) / this.step); 4140 var numSteps = Math.round((value - this.min) / this.step);
5991 if (this.step < 1) { 4141 if (this.step < 1) {
5992 /**
5993 * For small values of this.step, if we calculate the step using
5994 * `Math.round(value / step) * step` we may hit a precision point issue
5995 * eg. 0.1 * 0.2 = 0.020000000000000004
5996 * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
5997 *
5998 * as a work around we can divide by the reciprocal of `step`
5999 */
6000 return numSteps / (1 / this.step) + this.min; 4142 return numSteps / (1 / this.step) + this.min;
6001 } else { 4143 } else {
6002 return numSteps * this.step + this.min; 4144 return numSteps * this.step + this.min;
6003 } 4145 }
6004 }, 4146 },
6005 4147
6006 _validateValue: function() { 4148 _validateValue: function() {
6007 var v = this._clampValue(this.value); 4149 var v = this._clampValue(this.value);
6008 this.value = this.oldValue = isNaN(v) ? this.oldValue : v; 4150 this.value = this.oldValue = isNaN(v) ? this.oldValue : v;
6009 return this.value !== v; 4151 return this.value !== v;
6010 }, 4152 },
6011 4153
6012 _update: function() { 4154 _update: function() {
6013 this._validateValue(); 4155 this._validateValue();
6014 this._setRatio(this._calcRatio(this.value) * 100); 4156 this._setRatio(this._calcRatio(this.value) * 100);
6015 } 4157 }
6016 4158
6017 }; 4159 };
6018 Polymer({ 4160 Polymer({
6019 is: 'paper-progress', 4161 is: 'paper-progress',
6020 4162
6021 behaviors: [ 4163 behaviors: [
6022 Polymer.IronRangeBehavior 4164 Polymer.IronRangeBehavior
6023 ], 4165 ],
6024 4166
6025 properties: { 4167 properties: {
6026 /**
6027 * The number that represents the current secondary progress.
6028 */
6029 secondaryProgress: { 4168 secondaryProgress: {
6030 type: Number, 4169 type: Number,
6031 value: 0 4170 value: 0
6032 }, 4171 },
6033 4172
6034 /**
6035 * The secondary ratio
6036 */
6037 secondaryRatio: { 4173 secondaryRatio: {
6038 type: Number, 4174 type: Number,
6039 value: 0, 4175 value: 0,
6040 readOnly: true 4176 readOnly: true
6041 }, 4177 },
6042 4178
6043 /**
6044 * Use an indeterminate progress indicator.
6045 */
6046 indeterminate: { 4179 indeterminate: {
6047 type: Boolean, 4180 type: Boolean,
6048 value: false, 4181 value: false,
6049 observer: '_toggleIndeterminate' 4182 observer: '_toggleIndeterminate'
6050 }, 4183 },
6051 4184
6052 /**
6053 * True if the progress is disabled.
6054 */
6055 disabled: { 4185 disabled: {
6056 type: Boolean, 4186 type: Boolean,
6057 value: false, 4187 value: false,
6058 reflectToAttribute: true, 4188 reflectToAttribute: true,
6059 observer: '_disabledChanged' 4189 observer: '_disabledChanged'
6060 } 4190 }
6061 }, 4191 },
6062 4192
6063 observers: [ 4193 observers: [
6064 '_progressChanged(secondaryProgress, value, min, max)' 4194 '_progressChanged(secondaryProgress, value, min, max)'
6065 ], 4195 ],
6066 4196
6067 hostAttributes: { 4197 hostAttributes: {
6068 role: 'progressbar' 4198 role: 'progressbar'
6069 }, 4199 },
6070 4200
6071 _toggleIndeterminate: function(indeterminate) { 4201 _toggleIndeterminate: function(indeterminate) {
6072 // If we use attribute/class binding, the animation sometimes doesn't tran slate properly
6073 // on Safari 7.1. So instead, we toggle the class here in the update metho d.
6074 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress); 4202 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress);
6075 }, 4203 },
6076 4204
6077 _transformProgress: function(progress, ratio) { 4205 _transformProgress: function(progress, ratio) {
6078 var transform = 'scaleX(' + (ratio / 100) + ')'; 4206 var transform = 'scaleX(' + (ratio / 100) + ')';
6079 progress.style.transform = progress.style.webkitTransform = transform; 4207 progress.style.transform = progress.style.webkitTransform = transform;
6080 }, 4208 },
6081 4209
6082 _mainRatioChanged: function(ratio) { 4210 _mainRatioChanged: function(ratio) {
6083 this._transformProgress(this.$.primaryProgress, ratio); 4211 this._transformProgress(this.$.primaryProgress, ratio);
(...skipping 18 matching lines...) Expand all
6102 }, 4230 },
6103 4231
6104 _disabledChanged: function(disabled) { 4232 _disabledChanged: function(disabled) {
6105 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); 4233 this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
6106 }, 4234 },
6107 4235
6108 _hideSecondaryProgress: function(secondaryRatio) { 4236 _hideSecondaryProgress: function(secondaryRatio) {
6109 return secondaryRatio === 0; 4237 return secondaryRatio === 0;
6110 } 4238 }
6111 }); 4239 });
6112 /**
6113 * The `iron-iconset-svg` element allows users to define their own icon sets
6114 * that contain svg icons. The svg icon elements should be children of the
6115 * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
6116 *
6117 * Using svg elements to create icons has a few advantages over traditional
6118 * bitmap graphics like jpg or png. Icons that use svg are vector based so
6119 * they are resolution independent and should look good on any device. They
6120 * are stylable via css. Icons can be themed, colorized, and even animated.
6121 *
6122 * Example:
6123 *
6124 * <iron-iconset-svg name="my-svg-icons" size="24">
6125 * <svg>
6126 * <defs>
6127 * <g id="shape">
6128 * <rect x="12" y="0" width="12" height="24" />
6129 * <circle cx="12" cy="12" r="12" />
6130 * </g>
6131 * </defs>
6132 * </svg>
6133 * </iron-iconset-svg>
6134 *
6135 * This will automatically register the icon set "my-svg-icons" to the iconset
6136 * database. To use these icons from within another element, make a
6137 * `iron-iconset` element and call the `byId` method
6138 * to retrieve a given iconset. To apply a particular icon inside an
6139 * element use the `applyIcon` method. For example:
6140 *
6141 * iconset.applyIcon(iconNode, 'car');
6142 *
6143 * @element iron-iconset-svg
6144 * @demo demo/index.html
6145 * @implements {Polymer.Iconset}
6146 */
6147 Polymer({ 4240 Polymer({
6148 is: 'iron-iconset-svg', 4241 is: 'iron-iconset-svg',
6149 4242
6150 properties: { 4243 properties: {
6151 4244
6152 /**
6153 * The name of the iconset.
6154 */
6155 name: { 4245 name: {
6156 type: String, 4246 type: String,
6157 observer: '_nameChanged' 4247 observer: '_nameChanged'
6158 }, 4248 },
6159 4249
6160 /**
6161 * The size of an individual icon. Note that icons must be square.
6162 */
6163 size: { 4250 size: {
6164 type: Number, 4251 type: Number,
6165 value: 24 4252 value: 24
6166 } 4253 }
6167 4254
6168 }, 4255 },
6169 4256
6170 attached: function() { 4257 attached: function() {
6171 this.style.display = 'none'; 4258 this.style.display = 'none';
6172 }, 4259 },
6173 4260
6174 /**
6175 * Construct an array of all icon names in this iconset.
6176 *
6177 * @return {!Array} Array of icon names.
6178 */
6179 getIconNames: function() { 4261 getIconNames: function() {
6180 this._icons = this._createIconMap(); 4262 this._icons = this._createIconMap();
6181 return Object.keys(this._icons).map(function(n) { 4263 return Object.keys(this._icons).map(function(n) {
6182 return this.name + ':' + n; 4264 return this.name + ':' + n;
6183 }, this); 4265 }, this);
6184 }, 4266 },
6185 4267
6186 /**
6187 * Applies an icon to the given element.
6188 *
6189 * An svg icon is prepended to the element's shadowRoot if it exists,
6190 * otherwise to the element itself.
6191 *
6192 * @method applyIcon
6193 * @param {Element} element Element to which the icon is applied.
6194 * @param {string} iconName Name of the icon to apply.
6195 * @return {?Element} The svg element which renders the icon.
6196 */
6197 applyIcon: function(element, iconName) { 4268 applyIcon: function(element, iconName) {
6198 // insert svg element into shadow root, if it exists
6199 element = element.root || element; 4269 element = element.root || element;
6200 // Remove old svg element
6201 this.removeIcon(element); 4270 this.removeIcon(element);
6202 // install new svg element
6203 var svg = this._cloneIcon(iconName); 4271 var svg = this._cloneIcon(iconName);
6204 if (svg) { 4272 if (svg) {
6205 var pde = Polymer.dom(element); 4273 var pde = Polymer.dom(element);
6206 pde.insertBefore(svg, pde.childNodes[0]); 4274 pde.insertBefore(svg, pde.childNodes[0]);
6207 return element._svgIcon = svg; 4275 return element._svgIcon = svg;
6208 } 4276 }
6209 return null; 4277 return null;
6210 }, 4278 },
6211 4279
6212 /**
6213 * Remove an icon from the given element by undoing the changes effected
6214 * by `applyIcon`.
6215 *
6216 * @param {Element} element The element from which the icon is removed.
6217 */
6218 removeIcon: function(element) { 4280 removeIcon: function(element) {
6219 // Remove old svg element
6220 if (element._svgIcon) { 4281 if (element._svgIcon) {
6221 Polymer.dom(element).removeChild(element._svgIcon); 4282 Polymer.dom(element).removeChild(element._svgIcon);
6222 element._svgIcon = null; 4283 element._svgIcon = null;
6223 } 4284 }
6224 }, 4285 },
6225 4286
6226 /**
6227 *
6228 * When name is changed, register iconset metadata
6229 *
6230 */
6231 _nameChanged: function() { 4287 _nameChanged: function() {
6232 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); 4288 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this});
6233 this.async(function() { 4289 this.async(function() {
6234 this.fire('iron-iconset-added', this, {node: window}); 4290 this.fire('iron-iconset-added', this, {node: window});
6235 }); 4291 });
6236 }, 4292 },
6237 4293
6238 /**
6239 * Create a map of child SVG elements by id.
6240 *
6241 * @return {!Object} Map of id's to SVG elements.
6242 */
6243 _createIconMap: function() { 4294 _createIconMap: function() {
6244 // Objects chained to Object.prototype (`{}`) have members. Specifically,
6245 // on FF there is a `watch` method that confuses the icon map, so we
6246 // need to use a null-based object here.
6247 var icons = Object.create(null); 4295 var icons = Object.create(null);
6248 Polymer.dom(this).querySelectorAll('[id]') 4296 Polymer.dom(this).querySelectorAll('[id]')
6249 .forEach(function(icon) { 4297 .forEach(function(icon) {
6250 icons[icon.id] = icon; 4298 icons[icon.id] = icon;
6251 }); 4299 });
6252 return icons; 4300 return icons;
6253 }, 4301 },
6254 4302
6255 /**
6256 * Produce installable clone of the SVG element matching `id` in this
6257 * iconset, or `undefined` if there is no matching element.
6258 *
6259 * @return {Element} Returns an installable clone of the SVG element
6260 * matching `id`.
6261 */
6262 _cloneIcon: function(id) { 4303 _cloneIcon: function(id) {
6263 // create the icon map on-demand, since the iconset itself has no discrete
6264 // signal to know when it's children are fully parsed
6265 this._icons = this._icons || this._createIconMap(); 4304 this._icons = this._icons || this._createIconMap();
6266 return this._prepareSvgClone(this._icons[id], this.size); 4305 return this._prepareSvgClone(this._icons[id], this.size);
6267 }, 4306 },
6268 4307
6269 /**
6270 * @param {Element} sourceSvg
6271 * @param {number} size
6272 * @return {Element}
6273 */
6274 _prepareSvgClone: function(sourceSvg, size) { 4308 _prepareSvgClone: function(sourceSvg, size) {
6275 if (sourceSvg) { 4309 if (sourceSvg) {
6276 var content = sourceSvg.cloneNode(true), 4310 var content = sourceSvg.cloneNode(true),
6277 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), 4311 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
6278 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s ize; 4312 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s ize;
6279 svg.setAttribute('viewBox', viewBox); 4313 svg.setAttribute('viewBox', viewBox);
6280 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); 4314 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
6281 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/ 370136
6282 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root
6283 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;'; 4315 svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;';
6284 svg.appendChild(content).removeAttribute('id'); 4316 svg.appendChild(content).removeAttribute('id');
6285 return svg; 4317 return svg;
6286 } 4318 }
6287 return null; 4319 return null;
6288 } 4320 }
6289 4321
6290 }); 4322 });
6291 // Copyright 2015 The Chromium Authors. All rights reserved. 4323 // Copyright 2015 The Chromium Authors. All rights reserved.
6292 // Use of this source code is governed by a BSD-style license that can be 4324 // Use of this source code is governed by a BSD-style license that can be
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
6351 }, 4383 },
6352 4384
6353 showProgress_: { 4385 showProgress_: {
6354 computed: 'computeShowProgress_(showCancel_, data.percent)', 4386 computed: 'computeShowProgress_(showCancel_, data.percent)',
6355 type: Boolean, 4387 type: Boolean,
6356 value: false, 4388 value: false,
6357 }, 4389 },
6358 }, 4390 },
6359 4391
6360 observers: [ 4392 observers: [
6361 // TODO(dbeam): this gets called way more when I observe data.by_ext_id
6362 // and data.by_ext_name directly. Why?
6363 'observeControlledBy_(controlledBy_)', 4393 'observeControlledBy_(controlledBy_)',
6364 'observeIsDangerous_(isDangerous_, data)', 4394 'observeIsDangerous_(isDangerous_, data)',
6365 ], 4395 ],
6366 4396
6367 ready: function() { 4397 ready: function() {
6368 this.content = this.$.content; 4398 this.content = this.$.content;
6369 }, 4399 },
6370 4400
6371 /** @private */ 4401 /** @private */
6372 computeClass_: function() { 4402 computeClass_: function() {
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after
6553 /** @private */ 4583 /** @private */
6554 onCancelTap_: function() { 4584 onCancelTap_: function() {
6555 downloads.ActionService.getInstance().cancel(this.data.id); 4585 downloads.ActionService.getInstance().cancel(this.data.id);
6556 }, 4586 },
6557 4587
6558 /** @private */ 4588 /** @private */
6559 onDiscardDangerousTap_: function() { 4589 onDiscardDangerousTap_: function() {
6560 downloads.ActionService.getInstance().discardDangerous(this.data.id); 4590 downloads.ActionService.getInstance().discardDangerous(this.data.id);
6561 }, 4591 },
6562 4592
6563 /**
6564 * @private
6565 * @param {Event} e
6566 */
6567 onDragStart_: function(e) { 4593 onDragStart_: function(e) {
6568 e.preventDefault(); 4594 e.preventDefault();
6569 downloads.ActionService.getInstance().drag(this.data.id); 4595 downloads.ActionService.getInstance().drag(this.data.id);
6570 }, 4596 },
6571 4597
6572 /**
6573 * @param {Event} e
6574 * @private
6575 */
6576 onFileLinkTap_: function(e) { 4598 onFileLinkTap_: function(e) {
6577 e.preventDefault(); 4599 e.preventDefault();
6578 downloads.ActionService.getInstance().openFile(this.data.id); 4600 downloads.ActionService.getInstance().openFile(this.data.id);
6579 }, 4601 },
6580 4602
6581 /** @private */ 4603 /** @private */
6582 onPauseOrResumeTap_: function() { 4604 onPauseOrResumeTap_: function() {
6583 if (this.isInProgress_) 4605 if (this.isInProgress_)
6584 downloads.ActionService.getInstance().pause(this.data.id); 4606 downloads.ActionService.getInstance().pause(this.data.id);
6585 else 4607 else
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
6623 Polymer.IronControlState, 4645 Polymer.IronControlState,
6624 Polymer.PaperItemBehaviorImpl 4646 Polymer.PaperItemBehaviorImpl
6625 ]; 4647 ];
6626 Polymer({ 4648 Polymer({
6627 is: 'paper-item', 4649 is: 'paper-item',
6628 4650
6629 behaviors: [ 4651 behaviors: [
6630 Polymer.PaperItemBehavior 4652 Polymer.PaperItemBehavior
6631 ] 4653 ]
6632 }); 4654 });
6633 /**
6634 * @param {!Function} selectCallback
6635 * @constructor
6636 */
6637 Polymer.IronSelection = function(selectCallback) { 4655 Polymer.IronSelection = function(selectCallback) {
6638 this.selection = []; 4656 this.selection = [];
6639 this.selectCallback = selectCallback; 4657 this.selectCallback = selectCallback;
6640 }; 4658 };
6641 4659
6642 Polymer.IronSelection.prototype = { 4660 Polymer.IronSelection.prototype = {
6643 4661
6644 /**
6645 * Retrieves the selected item(s).
6646 *
6647 * @method get
6648 * @returns Returns the selected item(s). If the multi property is true,
6649 * `get` will return an array, otherwise it will return
6650 * the selected item or undefined if there is no selection.
6651 */
6652 get: function() { 4662 get: function() {
6653 return this.multi ? this.selection.slice() : this.selection[0]; 4663 return this.multi ? this.selection.slice() : this.selection[0];
6654 }, 4664 },
6655 4665
6656 /**
6657 * Clears all the selection except the ones indicated.
6658 *
6659 * @method clear
6660 * @param {Array} excludes items to be excluded.
6661 */
6662 clear: function(excludes) { 4666 clear: function(excludes) {
6663 this.selection.slice().forEach(function(item) { 4667 this.selection.slice().forEach(function(item) {
6664 if (!excludes || excludes.indexOf(item) < 0) { 4668 if (!excludes || excludes.indexOf(item) < 0) {
6665 this.setItemSelected(item, false); 4669 this.setItemSelected(item, false);
6666 } 4670 }
6667 }, this); 4671 }, this);
6668 }, 4672 },
6669 4673
6670 /**
6671 * Indicates if a given item is selected.
6672 *
6673 * @method isSelected
6674 * @param {*} item The item whose selection state should be checked.
6675 * @returns Returns true if `item` is selected.
6676 */
6677 isSelected: function(item) { 4674 isSelected: function(item) {
6678 return this.selection.indexOf(item) >= 0; 4675 return this.selection.indexOf(item) >= 0;
6679 }, 4676 },
6680 4677
6681 /**
6682 * Sets the selection state for a given item to either selected or deselecte d.
6683 *
6684 * @method setItemSelected
6685 * @param {*} item The item to select.
6686 * @param {boolean} isSelected True for selected, false for deselected.
6687 */
6688 setItemSelected: function(item, isSelected) { 4678 setItemSelected: function(item, isSelected) {
6689 if (item != null) { 4679 if (item != null) {
6690 if (isSelected !== this.isSelected(item)) { 4680 if (isSelected !== this.isSelected(item)) {
6691 // proceed to update selection only if requested state differs from cu rrent
6692 if (isSelected) { 4681 if (isSelected) {
6693 this.selection.push(item); 4682 this.selection.push(item);
6694 } else { 4683 } else {
6695 var i = this.selection.indexOf(item); 4684 var i = this.selection.indexOf(item);
6696 if (i >= 0) { 4685 if (i >= 0) {
6697 this.selection.splice(i, 1); 4686 this.selection.splice(i, 1);
6698 } 4687 }
6699 } 4688 }
6700 if (this.selectCallback) { 4689 if (this.selectCallback) {
6701 this.selectCallback(item, isSelected); 4690 this.selectCallback(item, isSelected);
6702 } 4691 }
6703 } 4692 }
6704 } 4693 }
6705 }, 4694 },
6706 4695
6707 /**
6708 * Sets the selection state for a given item. If the `multi` property
6709 * is true, then the selected state of `item` will be toggled; otherwise
6710 * the `item` will be selected.
6711 *
6712 * @method select
6713 * @param {*} item The item to select.
6714 */
6715 select: function(item) { 4696 select: function(item) {
6716 if (this.multi) { 4697 if (this.multi) {
6717 this.toggle(item); 4698 this.toggle(item);
6718 } else if (this.get() !== item) { 4699 } else if (this.get() !== item) {
6719 this.setItemSelected(this.get(), false); 4700 this.setItemSelected(this.get(), false);
6720 this.setItemSelected(item, true); 4701 this.setItemSelected(item, true);
6721 } 4702 }
6722 }, 4703 },
6723 4704
6724 /**
6725 * Toggles the selection state for `item`.
6726 *
6727 * @method toggle
6728 * @param {*} item The item to toggle.
6729 */
6730 toggle: function(item) { 4705 toggle: function(item) {
6731 this.setItemSelected(item, !this.isSelected(item)); 4706 this.setItemSelected(item, !this.isSelected(item));
6732 } 4707 }
6733 4708
6734 }; 4709 };
6735 /** @polymerBehavior */ 4710 /** @polymerBehavior */
6736 Polymer.IronSelectableBehavior = { 4711 Polymer.IronSelectableBehavior = {
6737 4712
6738 /**
6739 * Fired when iron-selector is activated (selected or deselected).
6740 * It is fired before the selected items are changed.
6741 * Cancel the event to abort selection.
6742 *
6743 * @event iron-activate
6744 */
6745 4713
6746 /**
6747 * Fired when an item is selected
6748 *
6749 * @event iron-select
6750 */
6751 4714
6752 /**
6753 * Fired when an item is deselected
6754 *
6755 * @event iron-deselect
6756 */
6757 4715
6758 /**
6759 * Fired when the list of selectable items changes (e.g., items are
6760 * added or removed). The detail of the event is a mutation record that
6761 * describes what changed.
6762 *
6763 * @event iron-items-changed
6764 */
6765 4716
6766 properties: { 4717 properties: {
6767 4718
6768 /**
6769 * If you want to use an attribute value or property of an element for
6770 * `selected` instead of the index, set this to the name of the attribute
6771 * or property. Hyphenated values are converted to camel case when used to
6772 * look up the property of a selectable element. Camel cased values are
6773 * *not* converted to hyphenated values for attribute lookup. It's
6774 * recommended that you provide the hyphenated form of the name so that
6775 * selection works in both cases. (Use `attr-or-property-name` instead of
6776 * `attrOrPropertyName`.)
6777 */
6778 attrForSelected: { 4719 attrForSelected: {
6779 type: String, 4720 type: String,
6780 value: null 4721 value: null
6781 }, 4722 },
6782 4723
6783 /**
6784 * Gets or sets the selected element. The default is to use the index of t he item.
6785 * @type {string|number}
6786 */
6787 selected: { 4724 selected: {
6788 type: String, 4725 type: String,
6789 notify: true 4726 notify: true
6790 }, 4727 },
6791 4728
6792 /**
6793 * Returns the currently selected item.
6794 *
6795 * @type {?Object}
6796 */
6797 selectedItem: { 4729 selectedItem: {
6798 type: Object, 4730 type: Object,
6799 readOnly: true, 4731 readOnly: true,
6800 notify: true 4732 notify: true
6801 }, 4733 },
6802 4734
6803 /**
6804 * The event that fires from items when they are selected. Selectable
6805 * will listen for this event from items and update the selection state.
6806 * Set to empty string to listen to no events.
6807 */
6808 activateEvent: { 4735 activateEvent: {
6809 type: String, 4736 type: String,
6810 value: 'tap', 4737 value: 'tap',
6811 observer: '_activateEventChanged' 4738 observer: '_activateEventChanged'
6812 }, 4739 },
6813 4740
6814 /**
6815 * This is a CSS selector string. If this is set, only items that match t he CSS selector
6816 * are selectable.
6817 */
6818 selectable: String, 4741 selectable: String,
6819 4742
6820 /**
6821 * The class to set on elements when selected.
6822 */
6823 selectedClass: { 4743 selectedClass: {
6824 type: String, 4744 type: String,
6825 value: 'iron-selected' 4745 value: 'iron-selected'
6826 }, 4746 },
6827 4747
6828 /**
6829 * The attribute to set on elements when selected.
6830 */
6831 selectedAttribute: { 4748 selectedAttribute: {
6832 type: String, 4749 type: String,
6833 value: null 4750 value: null
6834 }, 4751 },
6835 4752
6836 /**
6837 * Default fallback if the selection based on selected with `attrForSelect ed`
6838 * is not found.
6839 */
6840 fallbackSelection: { 4753 fallbackSelection: {
6841 type: String, 4754 type: String,
6842 value: null 4755 value: null
6843 }, 4756 },
6844 4757
6845 /**
6846 * The list of items from which a selection can be made.
6847 */
6848 items: { 4758 items: {
6849 type: Array, 4759 type: Array,
6850 readOnly: true, 4760 readOnly: true,
6851 notify: true, 4761 notify: true,
6852 value: function() { 4762 value: function() {
6853 return []; 4763 return [];
6854 } 4764 }
6855 }, 4765 },
6856 4766
6857 /**
6858 * The set of excluded elements where the key is the `localName`
6859 * of the element that will be ignored from the item list.
6860 *
6861 * @default {template: 1}
6862 */
6863 _excludedLocalNames: { 4767 _excludedLocalNames: {
6864 type: Object, 4768 type: Object,
6865 value: function() { 4769 value: function() {
6866 return { 4770 return {
6867 'template': 1 4771 'template': 1
6868 }; 4772 };
6869 } 4773 }
6870 } 4774 }
6871 }, 4775 },
6872 4776
(...skipping 17 matching lines...) Expand all
6890 this._addListener(this.activateEvent); 4794 this._addListener(this.activateEvent);
6891 }, 4795 },
6892 4796
6893 detached: function() { 4797 detached: function() {
6894 if (this._observer) { 4798 if (this._observer) {
6895 Polymer.dom(this).unobserveNodes(this._observer); 4799 Polymer.dom(this).unobserveNodes(this._observer);
6896 } 4800 }
6897 this._removeListener(this.activateEvent); 4801 this._removeListener(this.activateEvent);
6898 }, 4802 },
6899 4803
6900 /**
6901 * Returns the index of the given item.
6902 *
6903 * @method indexOf
6904 * @param {Object} item
6905 * @returns Returns the index of the item
6906 */
6907 indexOf: function(item) { 4804 indexOf: function(item) {
6908 return this.items.indexOf(item); 4805 return this.items.indexOf(item);
6909 }, 4806 },
6910 4807
6911 /**
6912 * Selects the given value.
6913 *
6914 * @method select
6915 * @param {string|number} value the value to select.
6916 */
6917 select: function(value) { 4808 select: function(value) {
6918 this.selected = value; 4809 this.selected = value;
6919 }, 4810 },
6920 4811
6921 /**
6922 * Selects the previous item.
6923 *
6924 * @method selectPrevious
6925 */
6926 selectPrevious: function() { 4812 selectPrevious: function() {
6927 var length = this.items.length; 4813 var length = this.items.length;
6928 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len gth; 4814 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len gth;
6929 this.selected = this._indexToValue(index); 4815 this.selected = this._indexToValue(index);
6930 }, 4816 },
6931 4817
6932 /**
6933 * Selects the next item.
6934 *
6935 * @method selectNext
6936 */
6937 selectNext: function() { 4818 selectNext: function() {
6938 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l ength; 4819 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l ength;
6939 this.selected = this._indexToValue(index); 4820 this.selected = this._indexToValue(index);
6940 }, 4821 },
6941 4822
6942 /**
6943 * Selects the item at the given index.
6944 *
6945 * @method selectIndex
6946 */
6947 selectIndex: function(index) { 4823 selectIndex: function(index) {
6948 this.select(this._indexToValue(index)); 4824 this.select(this._indexToValue(index));
6949 }, 4825 },
6950 4826
6951 /**
6952 * Force a synchronous update of the `items` property.
6953 *
6954 * NOTE: Consider listening for the `iron-items-changed` event to respond to
6955 * updates to the set of selectable items after updates to the DOM list and
6956 * selection state have been made.
6957 *
6958 * WARNING: If you are using this method, you should probably consider an
6959 * alternate approach. Synchronously querying for items is potentially
6960 * slow for many use cases. The `items` property will update asynchronously
6961 * on its own to reflect selectable items in the DOM.
6962 */
6963 forceSynchronousItemUpdate: function() { 4827 forceSynchronousItemUpdate: function() {
6964 this._updateItems(); 4828 this._updateItems();
6965 }, 4829 },
6966 4830
6967 get _shouldUpdateSelection() { 4831 get _shouldUpdateSelection() {
6968 return this.selected != null; 4832 return this.selected != null;
6969 }, 4833 },
6970 4834
6971 _checkFallback: function() { 4835 _checkFallback: function() {
6972 if (this._shouldUpdateSelection) { 4836 if (this._shouldUpdateSelection) {
(...skipping 25 matching lines...) Expand all
6998 this.selected = this._indexToValue(this.indexOf(this.selectedItem)); 4862 this.selected = this._indexToValue(this.indexOf(this.selectedItem));
6999 } 4863 }
7000 }, 4864 },
7001 4865
7002 _updateSelected: function() { 4866 _updateSelected: function() {
7003 this._selectSelected(this.selected); 4867 this._selectSelected(this.selected);
7004 }, 4868 },
7005 4869
7006 _selectSelected: function(selected) { 4870 _selectSelected: function(selected) {
7007 this._selection.select(this._valueToItem(this.selected)); 4871 this._selection.select(this._valueToItem(this.selected));
7008 // Check for items, since this array is populated only when attached
7009 // Since Number(0) is falsy, explicitly check for undefined
7010 if (this.fallbackSelection && this.items.length && (this._selection.get() === undefined)) { 4872 if (this.fallbackSelection && this.items.length && (this._selection.get() === undefined)) {
7011 this.selected = this.fallbackSelection; 4873 this.selected = this.fallbackSelection;
7012 } 4874 }
7013 }, 4875 },
7014 4876
7015 _filterItem: function(node) { 4877 _filterItem: function(node) {
7016 return !this._excludedLocalNames[node.localName]; 4878 return !this._excludedLocalNames[node.localName];
7017 }, 4879 },
7018 4880
7019 _valueToItem: function(value) { 4881 _valueToItem: function(value) {
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
7056 this.toggleAttribute(this.selectedAttribute, isSelected, item); 4918 this.toggleAttribute(this.selectedAttribute, isSelected, item);
7057 } 4919 }
7058 this._selectionChange(); 4920 this._selectionChange();
7059 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); 4921 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
7060 }, 4922 },
7061 4923
7062 _selectionChange: function() { 4924 _selectionChange: function() {
7063 this._setSelectedItem(this._selection.get()); 4925 this._setSelectedItem(this._selection.get());
7064 }, 4926 },
7065 4927
7066 // observe items change under the given node.
7067 _observeItems: function(node) { 4928 _observeItems: function(node) {
7068 return Polymer.dom(node).observeNodes(function(mutation) { 4929 return Polymer.dom(node).observeNodes(function(mutation) {
7069 this._updateItems(); 4930 this._updateItems();
7070 4931
7071 if (this._shouldUpdateSelection) { 4932 if (this._shouldUpdateSelection) {
7072 this._updateSelected(); 4933 this._updateSelected();
7073 } 4934 }
7074 4935
7075 // Let other interested parties know about the change so that
7076 // we don't have to recreate mutation observers everywhere.
7077 this.fire('iron-items-changed', mutation, { 4936 this.fire('iron-items-changed', mutation, {
7078 bubbles: false, 4937 bubbles: false,
7079 cancelable: false 4938 cancelable: false
7080 }); 4939 });
7081 }); 4940 });
7082 }, 4941 },
7083 4942
7084 _activateHandler: function(e) { 4943 _activateHandler: function(e) {
7085 var t = e.target; 4944 var t = e.target;
7086 var items = this.items; 4945 var items = this.items;
(...skipping 13 matching lines...) Expand all
7100 {selected: value, item: item}, {cancelable: true}).defaultPrevented) { 4959 {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
7101 this.select(value); 4960 this.select(value);
7102 } 4961 }
7103 } 4962 }
7104 4963
7105 }; 4964 };
7106 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ 4965 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */
7107 Polymer.IronMultiSelectableBehaviorImpl = { 4966 Polymer.IronMultiSelectableBehaviorImpl = {
7108 properties: { 4967 properties: {
7109 4968
7110 /**
7111 * If true, multiple selections are allowed.
7112 */
7113 multi: { 4969 multi: {
7114 type: Boolean, 4970 type: Boolean,
7115 value: false, 4971 value: false,
7116 observer: 'multiChanged' 4972 observer: 'multiChanged'
7117 }, 4973 },
7118 4974
7119 /**
7120 * Gets or sets the selected elements. This is used instead of `selected` when `multi`
7121 * is true.
7122 */
7123 selectedValues: { 4975 selectedValues: {
7124 type: Array, 4976 type: Array,
7125 notify: true 4977 notify: true
7126 }, 4978 },
7127 4979
7128 /**
7129 * Returns an array of currently selected items.
7130 */
7131 selectedItems: { 4980 selectedItems: {
7132 type: Array, 4981 type: Array,
7133 readOnly: true, 4982 readOnly: true,
7134 notify: true 4983 notify: true
7135 }, 4984 },
7136 4985
7137 }, 4986 },
7138 4987
7139 observers: [ 4988 observers: [
7140 '_updateSelected(selectedValues.splices)' 4989 '_updateSelected(selectedValues.splices)'
7141 ], 4990 ],
7142 4991
7143 /**
7144 * Selects the given value. If the `multi` property is true, then the select ed state of the
7145 * `value` will be toggled; otherwise the `value` will be selected.
7146 *
7147 * @method select
7148 * @param {string|number} value the value to select.
7149 */
7150 select: function(value) { 4992 select: function(value) {
7151 if (this.multi) { 4993 if (this.multi) {
7152 if (this.selectedValues) { 4994 if (this.selectedValues) {
7153 this._toggleSelected(value); 4995 this._toggleSelected(value);
7154 } else { 4996 } else {
7155 this.selectedValues = [value]; 4997 this.selectedValues = [value];
7156 } 4998 }
7157 } else { 4999 } else {
7158 this.selected = value; 5000 this.selected = value;
7159 } 5001 }
(...skipping 24 matching lines...) Expand all
7184 if (this.multi) { 5026 if (this.multi) {
7185 this._selectMulti(this.selectedValues); 5027 this._selectMulti(this.selectedValues);
7186 } else { 5028 } else {
7187 this._selectSelected(this.selected); 5029 this._selectSelected(this.selected);
7188 } 5030 }
7189 }, 5031 },
7190 5032
7191 _selectMulti: function(values) { 5033 _selectMulti: function(values) {
7192 if (values) { 5034 if (values) {
7193 var selectedItems = this._valuesToItems(values); 5035 var selectedItems = this._valuesToItems(values);
7194 // clear all but the current selected items
7195 this._selection.clear(selectedItems); 5036 this._selection.clear(selectedItems);
7196 // select only those not selected yet
7197 for (var i = 0; i < selectedItems.length; i++) { 5037 for (var i = 0; i < selectedItems.length; i++) {
7198 this._selection.setItemSelected(selectedItems[i], true); 5038 this._selection.setItemSelected(selectedItems[i], true);
7199 } 5039 }
7200 // Check for items, since this array is populated only when attached
7201 if (this.fallbackSelection && this.items.length && !this._selection.get( ).length) { 5040 if (this.fallbackSelection && this.items.length && !this._selection.get( ).length) {
7202 var fallback = this._valueToItem(this.fallbackSelection); 5041 var fallback = this._valueToItem(this.fallbackSelection);
7203 if (fallback) { 5042 if (fallback) {
7204 this.selectedValues = [this.fallbackSelection]; 5043 this.selectedValues = [this.fallbackSelection];
7205 } 5044 }
7206 } 5045 }
7207 } else { 5046 } else {
7208 this._selection.clear(); 5047 this._selection.clear();
7209 } 5048 }
7210 }, 5049 },
(...skipping 23 matching lines...) Expand all
7234 return this._valueToItem(value); 5073 return this._valueToItem(value);
7235 }, this); 5074 }, this);
7236 } 5075 }
7237 }; 5076 };
7238 5077
7239 /** @polymerBehavior */ 5078 /** @polymerBehavior */
7240 Polymer.IronMultiSelectableBehavior = [ 5079 Polymer.IronMultiSelectableBehavior = [
7241 Polymer.IronSelectableBehavior, 5080 Polymer.IronSelectableBehavior,
7242 Polymer.IronMultiSelectableBehaviorImpl 5081 Polymer.IronMultiSelectableBehaviorImpl
7243 ]; 5082 ];
7244 /**
7245 * `Polymer.IronMenuBehavior` implements accessible menu behavior.
7246 *
7247 * @demo demo/index.html
7248 * @polymerBehavior Polymer.IronMenuBehavior
7249 */
7250 Polymer.IronMenuBehaviorImpl = { 5083 Polymer.IronMenuBehaviorImpl = {
7251 5084
7252 properties: { 5085 properties: {
7253 5086
7254 /**
7255 * Returns the currently focused item.
7256 * @type {?Object}
7257 */
7258 focusedItem: { 5087 focusedItem: {
7259 observer: '_focusedItemChanged', 5088 observer: '_focusedItemChanged',
7260 readOnly: true, 5089 readOnly: true,
7261 type: Object 5090 type: Object
7262 }, 5091 },
7263 5092
7264 /**
7265 * The attribute to use on menu items to look up the item title. Typing th e first
7266 * letter of an item when the menu is open focuses that item. If unset, `t extContent`
7267 * will be used.
7268 */
7269 attrForItemTitle: { 5093 attrForItemTitle: {
7270 type: String 5094 type: String
7271 } 5095 }
7272 }, 5096 },
7273 5097
7274 hostAttributes: { 5098 hostAttributes: {
7275 'role': 'menu', 5099 'role': 'menu',
7276 'tabindex': '0' 5100 'tabindex': '0'
7277 }, 5101 },
7278 5102
(...skipping 11 matching lines...) Expand all
7290 'up': '_onUpKey', 5114 'up': '_onUpKey',
7291 'down': '_onDownKey', 5115 'down': '_onDownKey',
7292 'esc': '_onEscKey', 5116 'esc': '_onEscKey',
7293 'shift+tab:keydown': '_onShiftTabDown' 5117 'shift+tab:keydown': '_onShiftTabDown'
7294 }, 5118 },
7295 5119
7296 attached: function() { 5120 attached: function() {
7297 this._resetTabindices(); 5121 this._resetTabindices();
7298 }, 5122 },
7299 5123
7300 /**
7301 * Selects the given value. If the `multi` property is true, then the select ed state of the
7302 * `value` will be toggled; otherwise the `value` will be selected.
7303 *
7304 * @param {string|number} value the value to select.
7305 */
7306 select: function(value) { 5124 select: function(value) {
7307 // Cancel automatically focusing a default item if the menu received focus
7308 // through a user action selecting a particular item.
7309 if (this._defaultFocusAsync) { 5125 if (this._defaultFocusAsync) {
7310 this.cancelAsync(this._defaultFocusAsync); 5126 this.cancelAsync(this._defaultFocusAsync);
7311 this._defaultFocusAsync = null; 5127 this._defaultFocusAsync = null;
7312 } 5128 }
7313 var item = this._valueToItem(value); 5129 var item = this._valueToItem(value);
7314 if (item && item.hasAttribute('disabled')) return; 5130 if (item && item.hasAttribute('disabled')) return;
7315 this._setFocusedItem(item); 5131 this._setFocusedItem(item);
7316 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); 5132 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
7317 }, 5133 },
7318 5134
7319 /**
7320 * Resets all tabindex attributes to the appropriate value based on the
7321 * current selection state. The appropriate value is `0` (focusable) for
7322 * the default selected item, and `-1` (not keyboard focusable) for all
7323 * other items.
7324 */
7325 _resetTabindices: function() { 5135 _resetTabindices: function() {
7326 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[ 0]) : this.selectedItem; 5136 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[ 0]) : this.selectedItem;
7327 5137
7328 this.items.forEach(function(item) { 5138 this.items.forEach(function(item) {
7329 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); 5139 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
7330 }, this); 5140 }, this);
7331 }, 5141 },
7332 5142
7333 /**
7334 * Sets appropriate ARIA based on whether or not the menu is meant to be
7335 * multi-selectable.
7336 *
7337 * @param {boolean} multi True if the menu should be multi-selectable.
7338 */
7339 _updateMultiselectable: function(multi) { 5143 _updateMultiselectable: function(multi) {
7340 if (multi) { 5144 if (multi) {
7341 this.setAttribute('aria-multiselectable', 'true'); 5145 this.setAttribute('aria-multiselectable', 'true');
7342 } else { 5146 } else {
7343 this.removeAttribute('aria-multiselectable'); 5147 this.removeAttribute('aria-multiselectable');
7344 } 5148 }
7345 }, 5149 },
7346 5150
7347 /**
7348 * Given a KeyboardEvent, this method will focus the appropriate item in the
7349 * menu (if there is a relevant item, and it is possible to focus it).
7350 *
7351 * @param {KeyboardEvent} event A KeyboardEvent.
7352 */
7353 _focusWithKeyboardEvent: function(event) { 5151 _focusWithKeyboardEvent: function(event) {
7354 for (var i = 0, item; item = this.items[i]; i++) { 5152 for (var i = 0, item; item = this.items[i]; i++) {
7355 var attr = this.attrForItemTitle || 'textContent'; 5153 var attr = this.attrForItemTitle || 'textContent';
7356 var title = item[attr] || item.getAttribute(attr); 5154 var title = item[attr] || item.getAttribute(attr);
7357 5155
7358 if (!item.hasAttribute('disabled') && title && 5156 if (!item.hasAttribute('disabled') && title &&
7359 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k eyCode).toLowerCase()) { 5157 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k eyCode).toLowerCase()) {
7360 this._setFocusedItem(item); 5158 this._setFocusedItem(item);
7361 break; 5159 break;
7362 } 5160 }
7363 } 5161 }
7364 }, 5162 },
7365 5163
7366 /**
7367 * Focuses the previous item (relative to the currently focused item) in the
7368 * menu, disabled items will be skipped.
7369 * Loop until length + 1 to handle case of single item in menu.
7370 */
7371 _focusPrevious: function() { 5164 _focusPrevious: function() {
7372 var length = this.items.length; 5165 var length = this.items.length;
7373 var curFocusIndex = Number(this.indexOf(this.focusedItem)); 5166 var curFocusIndex = Number(this.indexOf(this.focusedItem));
7374 for (var i = 1; i < length + 1; i++) { 5167 for (var i = 1; i < length + 1; i++) {
7375 var item = this.items[(curFocusIndex - i + length) % length]; 5168 var item = this.items[(curFocusIndex - i + length) % length];
7376 if (!item.hasAttribute('disabled')) { 5169 if (!item.hasAttribute('disabled')) {
7377 this._setFocusedItem(item); 5170 this._setFocusedItem(item);
7378 return; 5171 return;
7379 } 5172 }
7380 } 5173 }
7381 }, 5174 },
7382 5175
7383 /**
7384 * Focuses the next item (relative to the currently focused item) in the
7385 * menu, disabled items will be skipped.
7386 * Loop until length + 1 to handle case of single item in menu.
7387 */
7388 _focusNext: function() { 5176 _focusNext: function() {
7389 var length = this.items.length; 5177 var length = this.items.length;
7390 var curFocusIndex = Number(this.indexOf(this.focusedItem)); 5178 var curFocusIndex = Number(this.indexOf(this.focusedItem));
7391 for (var i = 1; i < length + 1; i++) { 5179 for (var i = 1; i < length + 1; i++) {
7392 var item = this.items[(curFocusIndex + i) % length]; 5180 var item = this.items[(curFocusIndex + i) % length];
7393 if (!item.hasAttribute('disabled')) { 5181 if (!item.hasAttribute('disabled')) {
7394 this._setFocusedItem(item); 5182 this._setFocusedItem(item);
7395 return; 5183 return;
7396 } 5184 }
7397 } 5185 }
7398 }, 5186 },
7399 5187
7400 /**
7401 * Mutates items in the menu based on provided selection details, so that
7402 * all items correctly reflect selection state.
7403 *
7404 * @param {Element} item An item in the menu.
7405 * @param {boolean} isSelected True if the item should be shown in a
7406 * selected state, otherwise false.
7407 */
7408 _applySelection: function(item, isSelected) { 5188 _applySelection: function(item, isSelected) {
7409 if (isSelected) { 5189 if (isSelected) {
7410 item.setAttribute('aria-selected', 'true'); 5190 item.setAttribute('aria-selected', 'true');
7411 } else { 5191 } else {
7412 item.removeAttribute('aria-selected'); 5192 item.removeAttribute('aria-selected');
7413 } 5193 }
7414 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); 5194 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
7415 }, 5195 },
7416 5196
7417 /**
7418 * Discretely updates tabindex values among menu items as the focused item
7419 * changes.
7420 *
7421 * @param {Element} focusedItem The element that is currently focused.
7422 * @param {?Element} old The last element that was considered focused, if
7423 * applicable.
7424 */
7425 _focusedItemChanged: function(focusedItem, old) { 5197 _focusedItemChanged: function(focusedItem, old) {
7426 old && old.setAttribute('tabindex', '-1'); 5198 old && old.setAttribute('tabindex', '-1');
7427 if (focusedItem) { 5199 if (focusedItem) {
7428 focusedItem.setAttribute('tabindex', '0'); 5200 focusedItem.setAttribute('tabindex', '0');
7429 focusedItem.focus(); 5201 focusedItem.focus();
7430 } 5202 }
7431 }, 5203 },
7432 5204
7433 /**
7434 * A handler that responds to mutation changes related to the list of items
7435 * in the menu.
7436 *
7437 * @param {CustomEvent} event An event containing mutation records as its
7438 * detail.
7439 */
7440 _onIronItemsChanged: function(event) { 5205 _onIronItemsChanged: function(event) {
7441 if (event.detail.addedNodes.length) { 5206 if (event.detail.addedNodes.length) {
7442 this._resetTabindices(); 5207 this._resetTabindices();
7443 } 5208 }
7444 }, 5209 },
7445 5210
7446 /**
7447 * Handler that is called when a shift+tab keypress is detected by the menu.
7448 *
7449 * @param {CustomEvent} event A key combination event.
7450 */
7451 _onShiftTabDown: function(event) { 5211 _onShiftTabDown: function(event) {
7452 var oldTabIndex = this.getAttribute('tabindex'); 5212 var oldTabIndex = this.getAttribute('tabindex');
7453 5213
7454 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; 5214 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;
7455 5215
7456 this._setFocusedItem(null); 5216 this._setFocusedItem(null);
7457 5217
7458 this.setAttribute('tabindex', '-1'); 5218 this.setAttribute('tabindex', '-1');
7459 5219
7460 this.async(function() { 5220 this.async(function() {
7461 this.setAttribute('tabindex', oldTabIndex); 5221 this.setAttribute('tabindex', oldTabIndex);
7462 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; 5222 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
7463 // NOTE(cdata): polymer/polymer#1305
7464 }, 1); 5223 }, 1);
7465 }, 5224 },
7466 5225
7467 /**
7468 * Handler that is called when the menu receives focus.
7469 *
7470 * @param {FocusEvent} event A focus event.
7471 */
7472 _onFocus: function(event) { 5226 _onFocus: function(event) {
7473 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { 5227 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
7474 // do not focus the menu itself
7475 return; 5228 return;
7476 } 5229 }
7477 5230
7478 // Do not focus the selected tab if the deepest target is part of the
7479 // menu element's local DOM and is focusable.
7480 var rootTarget = /** @type {?HTMLElement} */( 5231 var rootTarget = /** @type {?HTMLElement} */(
7481 Polymer.dom(event).rootTarget); 5232 Polymer.dom(event).rootTarget);
7482 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && ! this.isLightDescendant(rootTarget)) { 5233 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && ! this.isLightDescendant(rootTarget)) {
7483 return; 5234 return;
7484 } 5235 }
7485 5236
7486 // clear the cached focus item
7487 this._defaultFocusAsync = this.async(function() { 5237 this._defaultFocusAsync = this.async(function() {
7488 // focus the selected item when the menu receives focus, or the first it em
7489 // if no item is selected
7490 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem; 5238 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem;
7491 5239
7492 this._setFocusedItem(null); 5240 this._setFocusedItem(null);
7493 5241
7494 if (selectedItem) { 5242 if (selectedItem) {
7495 this._setFocusedItem(selectedItem); 5243 this._setFocusedItem(selectedItem);
7496 } else if (this.items[0]) { 5244 } else if (this.items[0]) {
7497 // We find the first none-disabled item (if one exists)
7498 this._focusNext(); 5245 this._focusNext();
7499 } 5246 }
7500 }); 5247 });
7501 }, 5248 },
7502 5249
7503 /**
7504 * Handler that is called when the up key is pressed.
7505 *
7506 * @param {CustomEvent} event A key combination event.
7507 */
7508 _onUpKey: function(event) { 5250 _onUpKey: function(event) {
7509 // up and down arrows moves the focus
7510 this._focusPrevious(); 5251 this._focusPrevious();
7511 event.detail.keyboardEvent.preventDefault(); 5252 event.detail.keyboardEvent.preventDefault();
7512 }, 5253 },
7513 5254
7514 /**
7515 * Handler that is called when the down key is pressed.
7516 *
7517 * @param {CustomEvent} event A key combination event.
7518 */
7519 _onDownKey: function(event) { 5255 _onDownKey: function(event) {
7520 this._focusNext(); 5256 this._focusNext();
7521 event.detail.keyboardEvent.preventDefault(); 5257 event.detail.keyboardEvent.preventDefault();
7522 }, 5258 },
7523 5259
7524 /**
7525 * Handler that is called when the esc key is pressed.
7526 *
7527 * @param {CustomEvent} event A key combination event.
7528 */
7529 _onEscKey: function(event) { 5260 _onEscKey: function(event) {
7530 // esc blurs the control
7531 this.focusedItem.blur(); 5261 this.focusedItem.blur();
7532 }, 5262 },
7533 5263
7534 /**
7535 * Handler that is called when a keydown event is detected.
7536 *
7537 * @param {KeyboardEvent} event A keyboard event.
7538 */
7539 _onKeydown: function(event) { 5264 _onKeydown: function(event) {
7540 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) { 5265 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) {
7541 // all other keys focus the menu item starting with that character
7542 this._focusWithKeyboardEvent(event); 5266 this._focusWithKeyboardEvent(event);
7543 } 5267 }
7544 event.stopPropagation(); 5268 event.stopPropagation();
7545 }, 5269 },
7546 5270
7547 // override _activateHandler
7548 _activateHandler: function(event) { 5271 _activateHandler: function(event) {
7549 Polymer.IronSelectableBehavior._activateHandler.call(this, event); 5272 Polymer.IronSelectableBehavior._activateHandler.call(this, event);
7550 event.stopPropagation(); 5273 event.stopPropagation();
7551 } 5274 }
7552 }; 5275 };
7553 5276
7554 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; 5277 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
7555 5278
7556 /** @polymerBehavior Polymer.IronMenuBehavior */ 5279 /** @polymerBehavior Polymer.IronMenuBehavior */
7557 Polymer.IronMenuBehavior = [ 5280 Polymer.IronMenuBehavior = [
7558 Polymer.IronMultiSelectableBehavior, 5281 Polymer.IronMultiSelectableBehavior,
7559 Polymer.IronA11yKeysBehavior, 5282 Polymer.IronA11yKeysBehavior,
7560 Polymer.IronMenuBehaviorImpl 5283 Polymer.IronMenuBehaviorImpl
7561 ]; 5284 ];
7562 (function() { 5285 (function() {
7563 Polymer({ 5286 Polymer({
7564 is: 'paper-menu', 5287 is: 'paper-menu',
7565 5288
7566 behaviors: [ 5289 behaviors: [
7567 Polymer.IronMenuBehavior 5290 Polymer.IronMenuBehavior
7568 ] 5291 ]
7569 }); 5292 });
7570 })(); 5293 })();
7571 /**
7572 `Polymer.IronFitBehavior` fits an element in another element using `max-height` and `max-width`, and
7573 optionally centers it in the window or another element.
7574
7575 The element will only be sized and/or positioned if it has not already been size d and/or positioned
7576 by CSS.
7577
7578 CSS properties | Action
7579 -----------------------------|-------------------------------------------
7580 `position` set | Element is not centered horizontally or verticall y
7581 `top` or `bottom` set | Element is not vertically centered
7582 `left` or `right` set | Element is not horizontally centered
7583 `max-height` set | Element respects `max-height`
7584 `max-width` set | Element respects `max-width`
7585
7586 `Polymer.IronFitBehavior` can position an element into another element using
7587 `verticalAlign` and `horizontalAlign`. This will override the element's css posi tion.
7588
7589 <div class="container">
7590 <iron-fit-impl vertical-align="top" horizontal-align="auto">
7591 Positioned into the container
7592 </iron-fit-impl>
7593 </div>
7594
7595 Use `noOverlap` to position the element around another element without overlappi ng it.
7596
7597 <div class="container">
7598 <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto">
7599 Positioned around the container
7600 </iron-fit-impl>
7601 </div>
7602
7603 @demo demo/index.html
7604 @polymerBehavior
7605 */
7606 5294
7607 Polymer.IronFitBehavior = { 5295 Polymer.IronFitBehavior = {
7608 5296
7609 properties: { 5297 properties: {
7610 5298
7611 /**
7612 * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
7613 * but it can be set to a child element. This is useful, for example, for implementing a
7614 * scrolling region inside the element.
7615 * @type {!Element}
7616 */
7617 sizingTarget: { 5299 sizingTarget: {
7618 type: Object, 5300 type: Object,
7619 value: function() { 5301 value: function() {
7620 return this; 5302 return this;
7621 } 5303 }
7622 }, 5304 },
7623 5305
7624 /**
7625 * The element to fit `this` into.
7626 */
7627 fitInto: { 5306 fitInto: {
7628 type: Object, 5307 type: Object,
7629 value: window 5308 value: window
7630 }, 5309 },
7631 5310
7632 /**
7633 * Will position the element around the positionTarget without overlapping it.
7634 */
7635 noOverlap: { 5311 noOverlap: {
7636 type: Boolean 5312 type: Boolean
7637 }, 5313 },
7638 5314
7639 /**
7640 * The element that should be used to position the element. If not set, it will
7641 * default to the parent node.
7642 * @type {!Element}
7643 */
7644 positionTarget: { 5315 positionTarget: {
7645 type: Element 5316 type: Element
7646 }, 5317 },
7647 5318
7648 /**
7649 * The orientation against which to align the element horizontally
7650 * relative to the `positionTarget`. Possible values are "left", "right", "auto".
7651 */
7652 horizontalAlign: { 5319 horizontalAlign: {
7653 type: String 5320 type: String
7654 }, 5321 },
7655 5322
7656 /**
7657 * The orientation against which to align the element vertically
7658 * relative to the `positionTarget`. Possible values are "top", "bottom", "auto".
7659 */
7660 verticalAlign: { 5323 verticalAlign: {
7661 type: String 5324 type: String
7662 }, 5325 },
7663 5326
7664 /**
7665 * If true, it will use `horizontalAlign` and `verticalAlign` values as pr eferred alignment
7666 * and if there's not enough space, it will pick the values which minimize the cropping.
7667 */
7668 dynamicAlign: { 5327 dynamicAlign: {
7669 type: Boolean 5328 type: Boolean
7670 }, 5329 },
7671 5330
7672 /**
7673 * The same as setting margin-left and margin-right css properties.
7674 * @deprecated
7675 */
7676 horizontalOffset: { 5331 horizontalOffset: {
7677 type: Number, 5332 type: Number,
7678 value: 0, 5333 value: 0,
7679 notify: true 5334 notify: true
7680 }, 5335 },
7681 5336
7682 /**
7683 * The same as setting margin-top and margin-bottom css properties.
7684 * @deprecated
7685 */
7686 verticalOffset: { 5337 verticalOffset: {
7687 type: Number, 5338 type: Number,
7688 value: 0, 5339 value: 0,
7689 notify: true 5340 notify: true
7690 }, 5341 },
7691 5342
7692 /**
7693 * Set to true to auto-fit on attach.
7694 */
7695 autoFitOnAttach: { 5343 autoFitOnAttach: {
7696 type: Boolean, 5344 type: Boolean,
7697 value: false 5345 value: false
7698 }, 5346 },
7699 5347
7700 /** @type {?Object} */ 5348 /** @type {?Object} */
7701 _fitInfo: { 5349 _fitInfo: {
7702 type: Object 5350 type: Object
7703 } 5351 }
7704 }, 5352 },
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
7736 get _fitTop() { 5384 get _fitTop() {
7737 var fitTop; 5385 var fitTop;
7738 if (this.fitInto === window) { 5386 if (this.fitInto === window) {
7739 fitTop = 0; 5387 fitTop = 0;
7740 } else { 5388 } else {
7741 fitTop = this.fitInto.getBoundingClientRect().top; 5389 fitTop = this.fitInto.getBoundingClientRect().top;
7742 } 5390 }
7743 return fitTop; 5391 return fitTop;
7744 }, 5392 },
7745 5393
7746 /**
7747 * The element that should be used to position the element,
7748 * if no position target is configured.
7749 */
7750 get _defaultPositionTarget() { 5394 get _defaultPositionTarget() {
7751 var parent = Polymer.dom(this).parentNode; 5395 var parent = Polymer.dom(this).parentNode;
7752 5396
7753 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { 5397 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
7754 parent = parent.host; 5398 parent = parent.host;
7755 } 5399 }
7756 5400
7757 return parent; 5401 return parent;
7758 }, 5402 },
7759 5403
7760 /**
7761 * The horizontal align value, accounting for the RTL/LTR text direction.
7762 */
7763 get _localeHorizontalAlign() { 5404 get _localeHorizontalAlign() {
7764 if (this._isRTL) { 5405 if (this._isRTL) {
7765 // In RTL, "left" becomes "right".
7766 if (this.horizontalAlign === 'right') { 5406 if (this.horizontalAlign === 'right') {
7767 return 'left'; 5407 return 'left';
7768 } 5408 }
7769 if (this.horizontalAlign === 'left') { 5409 if (this.horizontalAlign === 'left') {
7770 return 'right'; 5410 return 'right';
7771 } 5411 }
7772 } 5412 }
7773 return this.horizontalAlign; 5413 return this.horizontalAlign;
7774 }, 5414 },
7775 5415
7776 attached: function() { 5416 attached: function() {
7777 // Memoize this to avoid expensive calculations & relayouts.
7778 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; 5417 this._isRTL = window.getComputedStyle(this).direction == 'rtl';
7779 this.positionTarget = this.positionTarget || this._defaultPositionTarget; 5418 this.positionTarget = this.positionTarget || this._defaultPositionTarget;
7780 if (this.autoFitOnAttach) { 5419 if (this.autoFitOnAttach) {
7781 if (window.getComputedStyle(this).display === 'none') { 5420 if (window.getComputedStyle(this).display === 'none') {
7782 setTimeout(function() { 5421 setTimeout(function() {
7783 this.fit(); 5422 this.fit();
7784 }.bind(this)); 5423 }.bind(this));
7785 } else { 5424 } else {
7786 this.fit(); 5425 this.fit();
7787 } 5426 }
7788 } 5427 }
7789 }, 5428 },
7790 5429
7791 /**
7792 * Positions and fits the element into the `fitInto` element.
7793 */
7794 fit: function() { 5430 fit: function() {
7795 this.position(); 5431 this.position();
7796 this.constrain(); 5432 this.constrain();
7797 this.center(); 5433 this.center();
7798 }, 5434 },
7799 5435
7800 /**
7801 * Memoize information needed to position and size the target element.
7802 * @suppress {deprecated}
7803 */
7804 _discoverInfo: function() { 5436 _discoverInfo: function() {
7805 if (this._fitInfo) { 5437 if (this._fitInfo) {
7806 return; 5438 return;
7807 } 5439 }
7808 var target = window.getComputedStyle(this); 5440 var target = window.getComputedStyle(this);
7809 var sizer = window.getComputedStyle(this.sizingTarget); 5441 var sizer = window.getComputedStyle(this.sizingTarget);
7810 5442
7811 this._fitInfo = { 5443 this._fitInfo = {
7812 inlineStyle: { 5444 inlineStyle: {
7813 top: this.style.top || '', 5445 top: this.style.top || '',
(...skipping 18 matching lines...) Expand all
7832 minHeight: parseInt(sizer.minHeight, 10) || 0 5464 minHeight: parseInt(sizer.minHeight, 10) || 0
7833 }, 5465 },
7834 margin: { 5466 margin: {
7835 top: parseInt(target.marginTop, 10) || 0, 5467 top: parseInt(target.marginTop, 10) || 0,
7836 right: parseInt(target.marginRight, 10) || 0, 5468 right: parseInt(target.marginRight, 10) || 0,
7837 bottom: parseInt(target.marginBottom, 10) || 0, 5469 bottom: parseInt(target.marginBottom, 10) || 0,
7838 left: parseInt(target.marginLeft, 10) || 0 5470 left: parseInt(target.marginLeft, 10) || 0
7839 } 5471 }
7840 }; 5472 };
7841 5473
7842 // Support these properties until they are removed.
7843 if (this.verticalOffset) { 5474 if (this.verticalOffset) {
7844 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf fset; 5475 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf fset;
7845 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || ''; 5476 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || '';
7846 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || ''; 5477 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || '';
7847 this.style.marginTop = this.style.marginBottom = this.verticalOffset + ' px'; 5478 this.style.marginTop = this.style.marginBottom = this.verticalOffset + ' px';
7848 } 5479 }
7849 if (this.horizontalOffset) { 5480 if (this.horizontalOffset) {
7850 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal Offset; 5481 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal Offset;
7851 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || ''; 5482 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || '';
7852 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || ''; 5483 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || '';
7853 this.style.marginLeft = this.style.marginRight = this.horizontalOffset + 'px'; 5484 this.style.marginLeft = this.style.marginRight = this.horizontalOffset + 'px';
7854 } 5485 }
7855 }, 5486 },
7856 5487
7857 /**
7858 * Resets the target element's position and size constraints, and clear
7859 * the memoized data.
7860 */
7861 resetFit: function() { 5488 resetFit: function() {
7862 var info = this._fitInfo || {}; 5489 var info = this._fitInfo || {};
7863 for (var property in info.sizerInlineStyle) { 5490 for (var property in info.sizerInlineStyle) {
7864 this.sizingTarget.style[property] = info.sizerInlineStyle[property]; 5491 this.sizingTarget.style[property] = info.sizerInlineStyle[property];
7865 } 5492 }
7866 for (var property in info.inlineStyle) { 5493 for (var property in info.inlineStyle) {
7867 this.style[property] = info.inlineStyle[property]; 5494 this.style[property] = info.inlineStyle[property];
7868 } 5495 }
7869 5496
7870 this._fitInfo = null; 5497 this._fitInfo = null;
7871 }, 5498 },
7872 5499
7873 /**
7874 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
7875 * the element or the `fitInto` element has been resized, or if any of the
7876 * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated .
7877 * It preserves the scroll position of the sizingTarget.
7878 */
7879 refit: function() { 5500 refit: function() {
7880 var scrollLeft = this.sizingTarget.scrollLeft; 5501 var scrollLeft = this.sizingTarget.scrollLeft;
7881 var scrollTop = this.sizingTarget.scrollTop; 5502 var scrollTop = this.sizingTarget.scrollTop;
7882 this.resetFit(); 5503 this.resetFit();
7883 this.fit(); 5504 this.fit();
7884 this.sizingTarget.scrollLeft = scrollLeft; 5505 this.sizingTarget.scrollLeft = scrollLeft;
7885 this.sizingTarget.scrollTop = scrollTop; 5506 this.sizingTarget.scrollTop = scrollTop;
7886 }, 5507 },
7887 5508
7888 /**
7889 * Positions the element according to `horizontalAlign, verticalAlign`.
7890 */
7891 position: function() { 5509 position: function() {
7892 if (!this.horizontalAlign && !this.verticalAlign) { 5510 if (!this.horizontalAlign && !this.verticalAlign) {
7893 // needs to be centered, and it is done after constrain.
7894 return; 5511 return;
7895 } 5512 }
7896 this._discoverInfo(); 5513 this._discoverInfo();
7897 5514
7898 this.style.position = 'fixed'; 5515 this.style.position = 'fixed';
7899 // Need border-box for margin/padding.
7900 this.sizingTarget.style.boxSizing = 'border-box'; 5516 this.sizingTarget.style.boxSizing = 'border-box';
7901 // Set to 0, 0 in order to discover any offset caused by parent stacking c ontexts.
7902 this.style.left = '0px'; 5517 this.style.left = '0px';
7903 this.style.top = '0px'; 5518 this.style.top = '0px';
7904 5519
7905 var rect = this.getBoundingClientRect(); 5520 var rect = this.getBoundingClientRect();
7906 var positionRect = this.__getNormalizedRect(this.positionTarget); 5521 var positionRect = this.__getNormalizedRect(this.positionTarget);
7907 var fitRect = this.__getNormalizedRect(this.fitInto); 5522 var fitRect = this.__getNormalizedRect(this.fitInto);
7908 5523
7909 var margin = this._fitInfo.margin; 5524 var margin = this._fitInfo.margin;
7910 5525
7911 // Consider the margin as part of the size for position calculations.
7912 var size = { 5526 var size = {
7913 width: rect.width + margin.left + margin.right, 5527 width: rect.width + margin.left + margin.right,
7914 height: rect.height + margin.top + margin.bottom 5528 height: rect.height + margin.top + margin.bottom
7915 }; 5529 };
7916 5530
7917 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic alAlign, size, positionRect, fitRect); 5531 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic alAlign, size, positionRect, fitRect);
7918 5532
7919 var left = position.left + margin.left; 5533 var left = position.left + margin.left;
7920 var top = position.top + margin.top; 5534 var top = position.top + margin.top;
7921 5535
7922 // Use original size (without margin).
7923 var right = Math.min(fitRect.right - margin.right, left + rect.width); 5536 var right = Math.min(fitRect.right - margin.right, left + rect.width);
7924 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height); 5537 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height);
7925 5538
7926 var minWidth = this._fitInfo.sizedBy.minWidth; 5539 var minWidth = this._fitInfo.sizedBy.minWidth;
7927 var minHeight = this._fitInfo.sizedBy.minHeight; 5540 var minHeight = this._fitInfo.sizedBy.minHeight;
7928 if (left < margin.left) { 5541 if (left < margin.left) {
7929 left = margin.left; 5542 left = margin.left;
7930 if (right - left < minWidth) { 5543 if (right - left < minWidth) {
7931 left = right - minWidth; 5544 left = right - minWidth;
7932 } 5545 }
7933 } 5546 }
7934 if (top < margin.top) { 5547 if (top < margin.top) {
7935 top = margin.top; 5548 top = margin.top;
7936 if (bottom - top < minHeight) { 5549 if (bottom - top < minHeight) {
7937 top = bottom - minHeight; 5550 top = bottom - minHeight;
7938 } 5551 }
7939 } 5552 }
7940 5553
7941 this.sizingTarget.style.maxWidth = (right - left) + 'px'; 5554 this.sizingTarget.style.maxWidth = (right - left) + 'px';
7942 this.sizingTarget.style.maxHeight = (bottom - top) + 'px'; 5555 this.sizingTarget.style.maxHeight = (bottom - top) + 'px';
7943 5556
7944 // Remove the offset caused by any stacking context.
7945 this.style.left = (left - rect.left) + 'px'; 5557 this.style.left = (left - rect.left) + 'px';
7946 this.style.top = (top - rect.top) + 'px'; 5558 this.style.top = (top - rect.top) + 'px';
7947 }, 5559 },
7948 5560
7949 /**
7950 * Constrains the size of the element to `fitInto` by setting `max-height`
7951 * and/or `max-width`.
7952 */
7953 constrain: function() { 5561 constrain: function() {
7954 if (this.horizontalAlign || this.verticalAlign) { 5562 if (this.horizontalAlign || this.verticalAlign) {
7955 return; 5563 return;
7956 } 5564 }
7957 this._discoverInfo(); 5565 this._discoverInfo();
7958 5566
7959 var info = this._fitInfo; 5567 var info = this._fitInfo;
7960 // position at (0px, 0px) if not already positioned, so we can measure the natural size.
7961 if (!info.positionedBy.vertically) { 5568 if (!info.positionedBy.vertically) {
7962 this.style.position = 'fixed'; 5569 this.style.position = 'fixed';
7963 this.style.top = '0px'; 5570 this.style.top = '0px';
7964 } 5571 }
7965 if (!info.positionedBy.horizontally) { 5572 if (!info.positionedBy.horizontally) {
7966 this.style.position = 'fixed'; 5573 this.style.position = 'fixed';
7967 this.style.left = '0px'; 5574 this.style.left = '0px';
7968 } 5575 }
7969 5576
7970 // need border-box for margin/padding
7971 this.sizingTarget.style.boxSizing = 'border-box'; 5577 this.sizingTarget.style.boxSizing = 'border-box';
7972 // constrain the width and height if not already set
7973 var rect = this.getBoundingClientRect(); 5578 var rect = this.getBoundingClientRect();
7974 if (!info.sizedBy.height) { 5579 if (!info.sizedBy.height) {
7975 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom' , 'Height'); 5580 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom' , 'Height');
7976 } 5581 }
7977 if (!info.sizedBy.width) { 5582 if (!info.sizedBy.width) {
7978 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ t', 'Width'); 5583 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ t', 'Width');
7979 } 5584 }
7980 }, 5585 },
7981 5586
7982 /**
7983 * @protected
7984 * @deprecated
7985 */
7986 _sizeDimension: function(rect, positionedBy, start, end, extent) { 5587 _sizeDimension: function(rect, positionedBy, start, end, extent) {
7987 this.__sizeDimension(rect, positionedBy, start, end, extent); 5588 this.__sizeDimension(rect, positionedBy, start, end, extent);
7988 }, 5589 },
7989 5590
7990 /**
7991 * @private
7992 */
7993 __sizeDimension: function(rect, positionedBy, start, end, extent) { 5591 __sizeDimension: function(rect, positionedBy, start, end, extent) {
7994 var info = this._fitInfo; 5592 var info = this._fitInfo;
7995 var fitRect = this.__getNormalizedRect(this.fitInto); 5593 var fitRect = this.__getNormalizedRect(this.fitInto);
7996 var max = extent === 'Width' ? fitRect.width : fitRect.height; 5594 var max = extent === 'Width' ? fitRect.width : fitRect.height;
7997 var flip = (positionedBy === end); 5595 var flip = (positionedBy === end);
7998 var offset = flip ? max - rect[end] : rect[start]; 5596 var offset = flip ? max - rect[end] : rect[start];
7999 var margin = info.margin[flip ? start : end]; 5597 var margin = info.margin[flip ? start : end];
8000 var offsetExtent = 'offset' + extent; 5598 var offsetExtent = 'offset' + extent;
8001 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; 5599 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
8002 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px'; 5600 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px';
8003 }, 5601 },
8004 5602
8005 /**
8006 * Centers horizontally and vertically if not already positioned. This also sets
8007 * `position:fixed`.
8008 */
8009 center: function() { 5603 center: function() {
8010 if (this.horizontalAlign || this.verticalAlign) { 5604 if (this.horizontalAlign || this.verticalAlign) {
8011 return; 5605 return;
8012 } 5606 }
8013 this._discoverInfo(); 5607 this._discoverInfo();
8014 5608
8015 var positionedBy = this._fitInfo.positionedBy; 5609 var positionedBy = this._fitInfo.positionedBy;
8016 if (positionedBy.vertically && positionedBy.horizontally) { 5610 if (positionedBy.vertically && positionedBy.horizontally) {
8017 // Already positioned.
8018 return; 5611 return;
8019 } 5612 }
8020 // Need position:fixed to center
8021 this.style.position = 'fixed'; 5613 this.style.position = 'fixed';
8022 // Take into account the offset caused by parents that create stacking
8023 // contexts (e.g. with transform: translate3d). Translate to 0,0 and
8024 // measure the bounding rect.
8025 if (!positionedBy.vertically) { 5614 if (!positionedBy.vertically) {
8026 this.style.top = '0px'; 5615 this.style.top = '0px';
8027 } 5616 }
8028 if (!positionedBy.horizontally) { 5617 if (!positionedBy.horizontally) {
8029 this.style.left = '0px'; 5618 this.style.left = '0px';
8030 } 5619 }
8031 // It will take in consideration margins and transforms
8032 var rect = this.getBoundingClientRect(); 5620 var rect = this.getBoundingClientRect();
8033 var fitRect = this.__getNormalizedRect(this.fitInto); 5621 var fitRect = this.__getNormalizedRect(this.fitInto);
8034 if (!positionedBy.vertically) { 5622 if (!positionedBy.vertically) {
8035 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2; 5623 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2;
8036 this.style.top = top + 'px'; 5624 this.style.top = top + 'px';
8037 } 5625 }
8038 if (!positionedBy.horizontally) { 5626 if (!positionedBy.horizontally) {
8039 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2; 5627 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2;
8040 this.style.left = left + 'px'; 5628 this.style.left = left + 'px';
8041 } 5629 }
(...skipping 14 matching lines...) Expand all
8056 }, 5644 },
8057 5645
8058 __getCroppedArea: function(position, size, fitRect) { 5646 __getCroppedArea: function(position, size, fitRect) {
8059 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom - (position.top + size.height)); 5647 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom - (position.top + size.height));
8060 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ t - (position.left + size.width)); 5648 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ t - (position.left + size.width));
8061 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si ze.height; 5649 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si ze.height;
8062 }, 5650 },
8063 5651
8064 5652
8065 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) { 5653 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) {
8066 // All the possible configurations.
8067 // Ordered as top-left, top-right, bottom-left, bottom-right.
8068 var positions = [{ 5654 var positions = [{
8069 verticalAlign: 'top', 5655 verticalAlign: 'top',
8070 horizontalAlign: 'left', 5656 horizontalAlign: 'left',
8071 top: positionRect.top, 5657 top: positionRect.top,
8072 left: positionRect.left 5658 left: positionRect.left
8073 }, { 5659 }, {
8074 verticalAlign: 'top', 5660 verticalAlign: 'top',
8075 horizontalAlign: 'right', 5661 horizontalAlign: 'right',
8076 top: positionRect.top, 5662 top: positionRect.top,
8077 left: positionRect.right - size.width 5663 left: positionRect.right - size.width
8078 }, { 5664 }, {
8079 verticalAlign: 'bottom', 5665 verticalAlign: 'bottom',
8080 horizontalAlign: 'left', 5666 horizontalAlign: 'left',
8081 top: positionRect.bottom - size.height, 5667 top: positionRect.bottom - size.height,
8082 left: positionRect.left 5668 left: positionRect.left
8083 }, { 5669 }, {
8084 verticalAlign: 'bottom', 5670 verticalAlign: 'bottom',
8085 horizontalAlign: 'right', 5671 horizontalAlign: 'right',
8086 top: positionRect.bottom - size.height, 5672 top: positionRect.bottom - size.height,
8087 left: positionRect.right - size.width 5673 left: positionRect.right - size.width
8088 }]; 5674 }];
8089 5675
8090 if (this.noOverlap) { 5676 if (this.noOverlap) {
8091 // Duplicate.
8092 for (var i = 0, l = positions.length; i < l; i++) { 5677 for (var i = 0, l = positions.length; i < l; i++) {
8093 var copy = {}; 5678 var copy = {};
8094 for (var key in positions[i]) { 5679 for (var key in positions[i]) {
8095 copy[key] = positions[i][key]; 5680 copy[key] = positions[i][key];
8096 } 5681 }
8097 positions.push(copy); 5682 positions.push(copy);
8098 } 5683 }
8099 // Horizontal overlap only.
8100 positions[0].top = positions[1].top += positionRect.height; 5684 positions[0].top = positions[1].top += positionRect.height;
8101 positions[2].top = positions[3].top -= positionRect.height; 5685 positions[2].top = positions[3].top -= positionRect.height;
8102 // Vertical overlap only.
8103 positions[4].left = positions[6].left += positionRect.width; 5686 positions[4].left = positions[6].left += positionRect.width;
8104 positions[5].left = positions[7].left -= positionRect.width; 5687 positions[5].left = positions[7].left -= positionRect.width;
8105 } 5688 }
8106 5689
8107 // Consider auto as null for coding convenience.
8108 vAlign = vAlign === 'auto' ? null : vAlign; 5690 vAlign = vAlign === 'auto' ? null : vAlign;
8109 hAlign = hAlign === 'auto' ? null : hAlign; 5691 hAlign = hAlign === 'auto' ? null : hAlign;
8110 5692
8111 var position; 5693 var position;
8112 for (var i = 0; i < positions.length; i++) { 5694 for (var i = 0; i < positions.length; i++) {
8113 var pos = positions[i]; 5695 var pos = positions[i];
8114 5696
8115 // If both vAlign and hAlign are defined, return exact match.
8116 // For dynamicAlign and noOverlap we'll have more than one candidate, so
8117 // we'll have to check the croppedArea to make the best choice.
8118 if (!this.dynamicAlign && !this.noOverlap && 5697 if (!this.dynamicAlign && !this.noOverlap &&
8119 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) { 5698 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) {
8120 position = pos; 5699 position = pos;
8121 break; 5700 break;
8122 } 5701 }
8123 5702
8124 // Align is ok if alignment preferences are respected. If no preferences ,
8125 // it is considered ok.
8126 var alignOk = (!vAlign || pos.verticalAlign === vAlign) && 5703 var alignOk = (!vAlign || pos.verticalAlign === vAlign) &&
8127 (!hAlign || pos.horizontalAlign === hAlign); 5704 (!hAlign || pos.horizontalAlign === hAlign);
8128 5705
8129 // Filter out elements that don't match the alignment (if defined).
8130 // With dynamicAlign, we need to consider all the positions to find the
8131 // one that minimizes the cropped area.
8132 if (!this.dynamicAlign && !alignOk) { 5706 if (!this.dynamicAlign && !alignOk) {
8133 continue; 5707 continue;
8134 } 5708 }
8135 5709
8136 position = position || pos; 5710 position = position || pos;
8137 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect); 5711 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect);
8138 var diff = pos.croppedArea - position.croppedArea; 5712 var diff = pos.croppedArea - position.croppedArea;
8139 // Check which crops less. If it crops equally, check if align is ok.
8140 if (diff < 0 || (diff === 0 && alignOk)) { 5713 if (diff < 0 || (diff === 0 && alignOk)) {
8141 position = pos; 5714 position = pos;
8142 } 5715 }
8143 // If not cropped and respects the align requirements, keep it.
8144 // This allows to prefer positions overlapping horizontally over the
8145 // ones overlapping vertically.
8146 if (position.croppedArea === 0 && alignOk) { 5716 if (position.croppedArea === 0 && alignOk) {
8147 break; 5717 break;
8148 } 5718 }
8149 } 5719 }
8150 5720
8151 return position; 5721 return position;
8152 } 5722 }
8153 5723
8154 }; 5724 };
8155 (function() { 5725 (function() {
8156 'use strict'; 5726 'use strict';
8157 5727
8158 Polymer({ 5728 Polymer({
8159 5729
8160 is: 'iron-overlay-backdrop', 5730 is: 'iron-overlay-backdrop',
8161 5731
8162 properties: { 5732 properties: {
8163 5733
8164 /**
8165 * Returns true if the backdrop is opened.
8166 */
8167 opened: { 5734 opened: {
8168 reflectToAttribute: true, 5735 reflectToAttribute: true,
8169 type: Boolean, 5736 type: Boolean,
8170 value: false, 5737 value: false,
8171 observer: '_openedChanged' 5738 observer: '_openedChanged'
8172 } 5739 }
8173 5740
8174 }, 5741 },
8175 5742
8176 listeners: { 5743 listeners: {
8177 'transitionend': '_onTransitionend' 5744 'transitionend': '_onTransitionend'
8178 }, 5745 },
8179 5746
8180 created: function() { 5747 created: function() {
8181 // Used to cancel previous requestAnimationFrame calls when opened changes .
8182 this.__openedRaf = null; 5748 this.__openedRaf = null;
8183 }, 5749 },
8184 5750
8185 attached: function() { 5751 attached: function() {
8186 this.opened && this._openedChanged(this.opened); 5752 this.opened && this._openedChanged(this.opened);
8187 }, 5753 },
8188 5754
8189 /**
8190 * Appends the backdrop to document body if needed.
8191 */
8192 prepare: function() { 5755 prepare: function() {
8193 if (this.opened && !this.parentNode) { 5756 if (this.opened && !this.parentNode) {
8194 Polymer.dom(document.body).appendChild(this); 5757 Polymer.dom(document.body).appendChild(this);
8195 } 5758 }
8196 }, 5759 },
8197 5760
8198 /**
8199 * Shows the backdrop.
8200 */
8201 open: function() { 5761 open: function() {
8202 this.opened = true; 5762 this.opened = true;
8203 }, 5763 },
8204 5764
8205 /**
8206 * Hides the backdrop.
8207 */
8208 close: function() { 5765 close: function() {
8209 this.opened = false; 5766 this.opened = false;
8210 }, 5767 },
8211 5768
8212 /**
8213 * Removes the backdrop from document body if needed.
8214 */
8215 complete: function() { 5769 complete: function() {
8216 if (!this.opened && this.parentNode === document.body) { 5770 if (!this.opened && this.parentNode === document.body) {
8217 Polymer.dom(this.parentNode).removeChild(this); 5771 Polymer.dom(this.parentNode).removeChild(this);
8218 } 5772 }
8219 }, 5773 },
8220 5774
8221 _onTransitionend: function(event) { 5775 _onTransitionend: function(event) {
8222 if (event && event.target === this) { 5776 if (event && event.target === this) {
8223 this.complete(); 5777 this.complete();
8224 } 5778 }
8225 }, 5779 },
8226 5780
8227 /**
8228 * @param {boolean} opened
8229 * @private
8230 */
8231 _openedChanged: function(opened) { 5781 _openedChanged: function(opened) {
8232 if (opened) { 5782 if (opened) {
8233 // Auto-attach.
8234 this.prepare(); 5783 this.prepare();
8235 } else { 5784 } else {
8236 // Animation might be disabled via the mixin or opacity custom property.
8237 // If it is disabled in other ways, it's up to the user to call complete .
8238 var cs = window.getComputedStyle(this); 5785 var cs = window.getComputedStyle(this);
8239 if (cs.transitionDuration === '0s' || cs.opacity == 0) { 5786 if (cs.transitionDuration === '0s' || cs.opacity == 0) {
8240 this.complete(); 5787 this.complete();
8241 } 5788 }
8242 } 5789 }
8243 5790
8244 if (!this.isAttached) { 5791 if (!this.isAttached) {
8245 return; 5792 return;
8246 } 5793 }
8247 5794
8248 // Always cancel previous requestAnimationFrame.
8249 if (this.__openedRaf) { 5795 if (this.__openedRaf) {
8250 window.cancelAnimationFrame(this.__openedRaf); 5796 window.cancelAnimationFrame(this.__openedRaf);
8251 this.__openedRaf = null; 5797 this.__openedRaf = null;
8252 } 5798 }
8253 // Force relayout to ensure proper transitions.
8254 this.scrollTop = this.scrollTop; 5799 this.scrollTop = this.scrollTop;
8255 this.__openedRaf = window.requestAnimationFrame(function() { 5800 this.__openedRaf = window.requestAnimationFrame(function() {
8256 this.__openedRaf = null; 5801 this.__openedRaf = null;
8257 this.toggleClass('opened', this.opened); 5802 this.toggleClass('opened', this.opened);
8258 }.bind(this)); 5803 }.bind(this));
8259 } 5804 }
8260 }); 5805 });
8261 5806
8262 })(); 5807 })();
8263 /**
8264 * @struct
8265 * @constructor
8266 * @private
8267 */
8268 Polymer.IronOverlayManagerClass = function() { 5808 Polymer.IronOverlayManagerClass = function() {
8269 /**
8270 * Used to keep track of the opened overlays.
8271 * @private {Array<Element>}
8272 */
8273 this._overlays = []; 5809 this._overlays = [];
8274 5810
8275 /**
8276 * iframes have a default z-index of 100,
8277 * so this default should be at least that.
8278 * @private {number}
8279 */
8280 this._minimumZ = 101; 5811 this._minimumZ = 101;
8281 5812
8282 /**
8283 * Memoized backdrop element.
8284 * @private {Element|null}
8285 */
8286 this._backdropElement = null; 5813 this._backdropElement = null;
8287 5814
8288 // Enable document-wide tap recognizer.
8289 Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this)); 5815 Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this));
8290 5816
8291 document.addEventListener('focus', this._onCaptureFocus.bind(this), true); 5817 document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
8292 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true ); 5818 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true );
8293 }; 5819 };
8294 5820
8295 Polymer.IronOverlayManagerClass.prototype = { 5821 Polymer.IronOverlayManagerClass.prototype = {
8296 5822
8297 constructor: Polymer.IronOverlayManagerClass, 5823 constructor: Polymer.IronOverlayManagerClass,
8298 5824
8299 /**
8300 * The shared backdrop element.
8301 * @type {!Element} backdropElement
8302 */
8303 get backdropElement() { 5825 get backdropElement() {
8304 if (!this._backdropElement) { 5826 if (!this._backdropElement) {
8305 this._backdropElement = document.createElement('iron-overlay-backdrop'); 5827 this._backdropElement = document.createElement('iron-overlay-backdrop');
8306 } 5828 }
8307 return this._backdropElement; 5829 return this._backdropElement;
8308 }, 5830 },
8309 5831
8310 /**
8311 * The deepest active element.
8312 * @type {!Element} activeElement the active element
8313 */
8314 get deepActiveElement() { 5832 get deepActiveElement() {
8315 // document.activeElement can be null
8316 // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
8317 // In case of null, default it to document.body.
8318 var active = document.activeElement || document.body; 5833 var active = document.activeElement || document.body;
8319 while (active.root && Polymer.dom(active.root).activeElement) { 5834 while (active.root && Polymer.dom(active.root).activeElement) {
8320 active = Polymer.dom(active.root).activeElement; 5835 active = Polymer.dom(active.root).activeElement;
8321 } 5836 }
8322 return active; 5837 return active;
8323 }, 5838 },
8324 5839
8325 /**
8326 * Brings the overlay at the specified index to the front.
8327 * @param {number} i
8328 * @private
8329 */
8330 _bringOverlayAtIndexToFront: function(i) { 5840 _bringOverlayAtIndexToFront: function(i) {
8331 var overlay = this._overlays[i]; 5841 var overlay = this._overlays[i];
8332 if (!overlay) { 5842 if (!overlay) {
8333 return; 5843 return;
8334 } 5844 }
8335 var lastI = this._overlays.length - 1; 5845 var lastI = this._overlays.length - 1;
8336 var currentOverlay = this._overlays[lastI]; 5846 var currentOverlay = this._overlays[lastI];
8337 // Ensure always-on-top overlay stays on top.
8338 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay) ) { 5847 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay) ) {
8339 lastI--; 5848 lastI--;
8340 } 5849 }
8341 // If already the top element, return.
8342 if (i >= lastI) { 5850 if (i >= lastI) {
8343 return; 5851 return;
8344 } 5852 }
8345 // Update z-index to be on top.
8346 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); 5853 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
8347 if (this._getZ(overlay) <= minimumZ) { 5854 if (this._getZ(overlay) <= minimumZ) {
8348 this._applyOverlayZ(overlay, minimumZ); 5855 this._applyOverlayZ(overlay, minimumZ);
8349 } 5856 }
8350 5857
8351 // Shift other overlays behind the new on top.
8352 while (i < lastI) { 5858 while (i < lastI) {
8353 this._overlays[i] = this._overlays[i + 1]; 5859 this._overlays[i] = this._overlays[i + 1];
8354 i++; 5860 i++;
8355 } 5861 }
8356 this._overlays[lastI] = overlay; 5862 this._overlays[lastI] = overlay;
8357 }, 5863 },
8358 5864
8359 /**
8360 * Adds the overlay and updates its z-index if it's opened, or removes it if it's closed.
8361 * Also updates the backdrop z-index.
8362 * @param {!Element} overlay
8363 */
8364 addOrRemoveOverlay: function(overlay) { 5865 addOrRemoveOverlay: function(overlay) {
8365 if (overlay.opened) { 5866 if (overlay.opened) {
8366 this.addOverlay(overlay); 5867 this.addOverlay(overlay);
8367 } else { 5868 } else {
8368 this.removeOverlay(overlay); 5869 this.removeOverlay(overlay);
8369 } 5870 }
8370 }, 5871 },
8371 5872
8372 /**
8373 * Tracks overlays for z-index and focus management.
8374 * Ensures the last added overlay with always-on-top remains on top.
8375 * @param {!Element} overlay
8376 */
8377 addOverlay: function(overlay) { 5873 addOverlay: function(overlay) {
8378 var i = this._overlays.indexOf(overlay); 5874 var i = this._overlays.indexOf(overlay);
8379 if (i >= 0) { 5875 if (i >= 0) {
8380 this._bringOverlayAtIndexToFront(i); 5876 this._bringOverlayAtIndexToFront(i);
8381 this.trackBackdrop(); 5877 this.trackBackdrop();
8382 return; 5878 return;
8383 } 5879 }
8384 var insertionIndex = this._overlays.length; 5880 var insertionIndex = this._overlays.length;
8385 var currentOverlay = this._overlays[insertionIndex - 1]; 5881 var currentOverlay = this._overlays[insertionIndex - 1];
8386 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); 5882 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ);
8387 var newZ = this._getZ(overlay); 5883 var newZ = this._getZ(overlay);
8388 5884
8389 // Ensure always-on-top overlay stays on top.
8390 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay) ) { 5885 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay) ) {
8391 // This bumps the z-index of +2.
8392 this._applyOverlayZ(currentOverlay, minimumZ); 5886 this._applyOverlayZ(currentOverlay, minimumZ);
8393 insertionIndex--; 5887 insertionIndex--;
8394 // Update minimumZ to match previous overlay's z-index.
8395 var previousOverlay = this._overlays[insertionIndex - 1]; 5888 var previousOverlay = this._overlays[insertionIndex - 1];
8396 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); 5889 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ);
8397 } 5890 }
8398 5891
8399 // Update z-index and insert overlay.
8400 if (newZ <= minimumZ) { 5892 if (newZ <= minimumZ) {
8401 this._applyOverlayZ(overlay, minimumZ); 5893 this._applyOverlayZ(overlay, minimumZ);
8402 } 5894 }
8403 this._overlays.splice(insertionIndex, 0, overlay); 5895 this._overlays.splice(insertionIndex, 0, overlay);
8404 5896
8405 this.trackBackdrop(); 5897 this.trackBackdrop();
8406 }, 5898 },
8407 5899
8408 /**
8409 * @param {!Element} overlay
8410 */
8411 removeOverlay: function(overlay) { 5900 removeOverlay: function(overlay) {
8412 var i = this._overlays.indexOf(overlay); 5901 var i = this._overlays.indexOf(overlay);
8413 if (i === -1) { 5902 if (i === -1) {
8414 return; 5903 return;
8415 } 5904 }
8416 this._overlays.splice(i, 1); 5905 this._overlays.splice(i, 1);
8417 5906
8418 this.trackBackdrop(); 5907 this.trackBackdrop();
8419 }, 5908 },
8420 5909
8421 /**
8422 * Returns the current overlay.
8423 * @return {Element|undefined}
8424 */
8425 currentOverlay: function() { 5910 currentOverlay: function() {
8426 var i = this._overlays.length - 1; 5911 var i = this._overlays.length - 1;
8427 return this._overlays[i]; 5912 return this._overlays[i];
8428 }, 5913 },
8429 5914
8430 /**
8431 * Returns the current overlay z-index.
8432 * @return {number}
8433 */
8434 currentOverlayZ: function() { 5915 currentOverlayZ: function() {
8435 return this._getZ(this.currentOverlay()); 5916 return this._getZ(this.currentOverlay());
8436 }, 5917 },
8437 5918
8438 /**
8439 * Ensures that the minimum z-index of new overlays is at least `minimumZ`.
8440 * This does not effect the z-index of any existing overlays.
8441 * @param {number} minimumZ
8442 */
8443 ensureMinimumZ: function(minimumZ) { 5919 ensureMinimumZ: function(minimumZ) {
8444 this._minimumZ = Math.max(this._minimumZ, minimumZ); 5920 this._minimumZ = Math.max(this._minimumZ, minimumZ);
8445 }, 5921 },
8446 5922
8447 focusOverlay: function() { 5923 focusOverlay: function() {
8448 var current = /** @type {?} */ (this.currentOverlay()); 5924 var current = /** @type {?} */ (this.currentOverlay());
8449 if (current) { 5925 if (current) {
8450 current._applyFocus(); 5926 current._applyFocus();
8451 } 5927 }
8452 }, 5928 },
8453 5929
8454 /**
8455 * Updates the backdrop z-index.
8456 */
8457 trackBackdrop: function() { 5930 trackBackdrop: function() {
8458 var overlay = this._overlayWithBackdrop(); 5931 var overlay = this._overlayWithBackdrop();
8459 // Avoid creating the backdrop if there is no overlay with backdrop.
8460 if (!overlay && !this._backdropElement) { 5932 if (!overlay && !this._backdropElement) {
8461 return; 5933 return;
8462 } 5934 }
8463 this.backdropElement.style.zIndex = this._getZ(overlay) - 1; 5935 this.backdropElement.style.zIndex = this._getZ(overlay) - 1;
8464 this.backdropElement.opened = !!overlay; 5936 this.backdropElement.opened = !!overlay;
8465 }, 5937 },
8466 5938
8467 /**
8468 * @return {Array<Element>}
8469 */
8470 getBackdrops: function() { 5939 getBackdrops: function() {
8471 var backdrops = []; 5940 var backdrops = [];
8472 for (var i = 0; i < this._overlays.length; i++) { 5941 for (var i = 0; i < this._overlays.length; i++) {
8473 if (this._overlays[i].withBackdrop) { 5942 if (this._overlays[i].withBackdrop) {
8474 backdrops.push(this._overlays[i]); 5943 backdrops.push(this._overlays[i]);
8475 } 5944 }
8476 } 5945 }
8477 return backdrops; 5946 return backdrops;
8478 }, 5947 },
8479 5948
8480 /**
8481 * Returns the z-index for the backdrop.
8482 * @return {number}
8483 */
8484 backdropZ: function() { 5949 backdropZ: function() {
8485 return this._getZ(this._overlayWithBackdrop()) - 1; 5950 return this._getZ(this._overlayWithBackdrop()) - 1;
8486 }, 5951 },
8487 5952
8488 /**
8489 * Returns the first opened overlay that has a backdrop.
8490 * @return {Element|undefined}
8491 * @private
8492 */
8493 _overlayWithBackdrop: function() { 5953 _overlayWithBackdrop: function() {
8494 for (var i = 0; i < this._overlays.length; i++) { 5954 for (var i = 0; i < this._overlays.length; i++) {
8495 if (this._overlays[i].withBackdrop) { 5955 if (this._overlays[i].withBackdrop) {
8496 return this._overlays[i]; 5956 return this._overlays[i];
8497 } 5957 }
8498 } 5958 }
8499 }, 5959 },
8500 5960
8501 /**
8502 * Calculates the minimum z-index for the overlay.
8503 * @param {Element=} overlay
8504 * @private
8505 */
8506 _getZ: function(overlay) { 5961 _getZ: function(overlay) {
8507 var z = this._minimumZ; 5962 var z = this._minimumZ;
8508 if (overlay) { 5963 if (overlay) {
8509 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay) .zIndex); 5964 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay) .zIndex);
8510 // Check if is a number
8511 // Number.isNaN not supported in IE 10+
8512 if (z1 === z1) { 5965 if (z1 === z1) {
8513 z = z1; 5966 z = z1;
8514 } 5967 }
8515 } 5968 }
8516 return z; 5969 return z;
8517 }, 5970 },
8518 5971
8519 /**
8520 * @param {!Element} element
8521 * @param {number|string} z
8522 * @private
8523 */
8524 _setZ: function(element, z) { 5972 _setZ: function(element, z) {
8525 element.style.zIndex = z; 5973 element.style.zIndex = z;
8526 }, 5974 },
8527 5975
8528 /**
8529 * @param {!Element} overlay
8530 * @param {number} aboveZ
8531 * @private
8532 */
8533 _applyOverlayZ: function(overlay, aboveZ) { 5976 _applyOverlayZ: function(overlay, aboveZ) {
8534 this._setZ(overlay, aboveZ + 2); 5977 this._setZ(overlay, aboveZ + 2);
8535 }, 5978 },
8536 5979
8537 /**
8538 * Returns the deepest overlay in the path.
8539 * @param {Array<Element>=} path
8540 * @return {Element|undefined}
8541 * @suppress {missingProperties}
8542 * @private
8543 */
8544 _overlayInPath: function(path) { 5980 _overlayInPath: function(path) {
8545 path = path || []; 5981 path = path || [];
8546 for (var i = 0; i < path.length; i++) { 5982 for (var i = 0; i < path.length; i++) {
8547 if (path[i]._manager === this) { 5983 if (path[i]._manager === this) {
8548 return path[i]; 5984 return path[i];
8549 } 5985 }
8550 } 5986 }
8551 }, 5987 },
8552 5988
8553 /**
8554 * Ensures the click event is delegated to the right overlay.
8555 * @param {!Event} event
8556 * @private
8557 */
8558 _onCaptureClick: function(event) { 5989 _onCaptureClick: function(event) {
8559 var overlay = /** @type {?} */ (this.currentOverlay()); 5990 var overlay = /** @type {?} */ (this.currentOverlay());
8560 // Check if clicked outside of top overlay.
8561 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { 5991 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) {
8562 overlay._onCaptureClick(event); 5992 overlay._onCaptureClick(event);
8563 } 5993 }
8564 }, 5994 },
8565 5995
8566 /**
8567 * Ensures the focus event is delegated to the right overlay.
8568 * @param {!Event} event
8569 * @private
8570 */
8571 _onCaptureFocus: function(event) { 5996 _onCaptureFocus: function(event) {
8572 var overlay = /** @type {?} */ (this.currentOverlay()); 5997 var overlay = /** @type {?} */ (this.currentOverlay());
8573 if (overlay) { 5998 if (overlay) {
8574 overlay._onCaptureFocus(event); 5999 overlay._onCaptureFocus(event);
8575 } 6000 }
8576 }, 6001 },
8577 6002
8578 /**
8579 * Ensures TAB and ESC keyboard events are delegated to the right overlay.
8580 * @param {!Event} event
8581 * @private
8582 */
8583 _onCaptureKeyDown: function(event) { 6003 _onCaptureKeyDown: function(event) {
8584 var overlay = /** @type {?} */ (this.currentOverlay()); 6004 var overlay = /** @type {?} */ (this.currentOverlay());
8585 if (overlay) { 6005 if (overlay) {
8586 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) { 6006 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
8587 overlay._onCaptureEsc(event); 6007 overlay._onCaptureEsc(event);
8588 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) { 6008 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) {
8589 overlay._onCaptureTab(event); 6009 overlay._onCaptureTab(event);
8590 } 6010 }
8591 } 6011 }
8592 }, 6012 },
8593 6013
8594 /**
8595 * Returns if the overlay1 should be behind overlay2.
8596 * @param {!Element} overlay1
8597 * @param {!Element} overlay2
8598 * @return {boolean}
8599 * @suppress {missingProperties}
8600 * @private
8601 */
8602 _shouldBeBehindOverlay: function(overlay1, overlay2) { 6014 _shouldBeBehindOverlay: function(overlay1, overlay2) {
8603 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; 6015 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop;
8604 } 6016 }
8605 }; 6017 };
8606 6018
8607 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); 6019 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass();
8608 (function() { 6020 (function() {
8609 'use strict'; 6021 'use strict';
8610 6022
8611 /**
8612 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
8613 on top of other content. It includes an optional backdrop, and can be used to im plement a variety
8614 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once.
8615
8616 See the [demo source code](https://github.com/PolymerElements/iron-overlay-behav ior/blob/master/demo/simple-overlay.html)
8617 for an example.
8618
8619 ### Closing and canceling
8620
8621 An overlay may be hidden by closing or canceling. The difference between close a nd cancel is user
8622 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
8623 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
8624 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click ` properties.
8625 `close()` should be called explicitly by the implementer when the user interacts with a control
8626 in the overlay element. When the dialog is canceled, the overlay fires an 'iron- overlay-canceled'
8627 event. Call `preventDefault` on this event to prevent the overlay from closing.
8628
8629 ### Positioning
8630
8631 By default the element is sized and positioned to fit and centered inside the wi ndow. You can
8632 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
8633
8634 ### Backdrop
8635
8636 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
8637 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
8638 options.
8639
8640 In addition, `with-backdrop` will wrap the focus within the content in the light DOM.
8641 Override the [`_focusableNodes` getter](#Polymer.IronOverlayBehavior:property-_f ocusableNodes)
8642 to achieve a different behavior.
8643
8644 ### Limitations
8645
8646 The element is styled to appear on top of other content by setting its `z-index` property. You
8647 must ensure no element has a stacking context with a higher `z-index` than its p arent stacking
8648 context. You should place this element as a child of `<body>` whenever possible.
8649
8650 @demo demo/index.html
8651 @polymerBehavior Polymer.IronOverlayBehavior
8652 */
8653 6023
8654 Polymer.IronOverlayBehaviorImpl = { 6024 Polymer.IronOverlayBehaviorImpl = {
8655 6025
8656 properties: { 6026 properties: {
8657 6027
8658 /**
8659 * True if the overlay is currently displayed.
8660 */
8661 opened: { 6028 opened: {
8662 observer: '_openedChanged', 6029 observer: '_openedChanged',
8663 type: Boolean, 6030 type: Boolean,
8664 value: false, 6031 value: false,
8665 notify: true 6032 notify: true
8666 }, 6033 },
8667 6034
8668 /**
8669 * True if the overlay was canceled when it was last closed.
8670 */
8671 canceled: { 6035 canceled: {
8672 observer: '_canceledChanged', 6036 observer: '_canceledChanged',
8673 readOnly: true, 6037 readOnly: true,
8674 type: Boolean, 6038 type: Boolean,
8675 value: false 6039 value: false
8676 }, 6040 },
8677 6041
8678 /**
8679 * Set to true to display a backdrop behind the overlay. It traps the focu s
8680 * within the light DOM of the overlay.
8681 */
8682 withBackdrop: { 6042 withBackdrop: {
8683 observer: '_withBackdropChanged', 6043 observer: '_withBackdropChanged',
8684 type: Boolean 6044 type: Boolean
8685 }, 6045 },
8686 6046
8687 /**
8688 * Set to true to disable auto-focusing the overlay or child nodes with
8689 * the `autofocus` attribute` when the overlay is opened.
8690 */
8691 noAutoFocus: { 6047 noAutoFocus: {
8692 type: Boolean, 6048 type: Boolean,
8693 value: false 6049 value: false
8694 }, 6050 },
8695 6051
8696 /**
8697 * Set to true to disable canceling the overlay with the ESC key.
8698 */
8699 noCancelOnEscKey: { 6052 noCancelOnEscKey: {
8700 type: Boolean, 6053 type: Boolean,
8701 value: false 6054 value: false
8702 }, 6055 },
8703 6056
8704 /**
8705 * Set to true to disable canceling the overlay by clicking outside it.
8706 */
8707 noCancelOnOutsideClick: { 6057 noCancelOnOutsideClick: {
8708 type: Boolean, 6058 type: Boolean,
8709 value: false 6059 value: false
8710 }, 6060 },
8711 6061
8712 /**
8713 * Contains the reason(s) this overlay was last closed (see `iron-overlay- closed`).
8714 * `IronOverlayBehavior` provides the `canceled` reason; implementers of t he
8715 * behavior can provide other reasons in addition to `canceled`.
8716 */
8717 closingReason: { 6062 closingReason: {
8718 // was a getter before, but needs to be a property so other
8719 // behaviors can override this.
8720 type: Object 6063 type: Object
8721 }, 6064 },
8722 6065
8723 /**
8724 * Set to true to enable restoring of focus when overlay is closed.
8725 */
8726 restoreFocusOnClose: { 6066 restoreFocusOnClose: {
8727 type: Boolean, 6067 type: Boolean,
8728 value: false 6068 value: false
8729 }, 6069 },
8730 6070
8731 /**
8732 * Set to true to keep overlay always on top.
8733 */
8734 alwaysOnTop: { 6071 alwaysOnTop: {
8735 type: Boolean 6072 type: Boolean
8736 }, 6073 },
8737 6074
8738 /**
8739 * Shortcut to access to the overlay manager.
8740 * @private
8741 * @type {Polymer.IronOverlayManagerClass}
8742 */
8743 _manager: { 6075 _manager: {
8744 type: Object, 6076 type: Object,
8745 value: Polymer.IronOverlayManager 6077 value: Polymer.IronOverlayManager
8746 }, 6078 },
8747 6079
8748 /**
8749 * The node being focused.
8750 * @type {?Node}
8751 */
8752 _focusedChild: { 6080 _focusedChild: {
8753 type: Object 6081 type: Object
8754 } 6082 }
8755 6083
8756 }, 6084 },
8757 6085
8758 listeners: { 6086 listeners: {
8759 'iron-resize': '_onIronResize' 6087 'iron-resize': '_onIronResize'
8760 }, 6088 },
8761 6089
8762 /**
8763 * The backdrop element.
8764 * @type {Element}
8765 */
8766 get backdropElement() { 6090 get backdropElement() {
8767 return this._manager.backdropElement; 6091 return this._manager.backdropElement;
8768 }, 6092 },
8769 6093
8770 /**
8771 * Returns the node to give focus to.
8772 * @type {Node}
8773 */
8774 get _focusNode() { 6094 get _focusNode() {
8775 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this; 6095 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this;
8776 }, 6096 },
8777 6097
8778 /**
8779 * Array of nodes that can receive focus (overlay included), ordered by `tab index`.
8780 * This is used to retrieve which is the first and last focusable nodes in o rder
8781 * to wrap the focus for overlays `with-backdrop`.
8782 *
8783 * If you know what is your content (specifically the first and last focusab le children),
8784 * you can override this method to return only `[firstFocusable, lastFocusab le];`
8785 * @type {Array<Node>}
8786 * @protected
8787 */
8788 get _focusableNodes() { 6098 get _focusableNodes() {
8789 // Elements that can be focused even if they have [disabled] attribute.
8790 var FOCUSABLE_WITH_DISABLED = [ 6099 var FOCUSABLE_WITH_DISABLED = [
8791 'a[href]', 6100 'a[href]',
8792 'area[href]', 6101 'area[href]',
8793 'iframe', 6102 'iframe',
8794 '[tabindex]', 6103 '[tabindex]',
8795 '[contentEditable=true]' 6104 '[contentEditable=true]'
8796 ]; 6105 ];
8797 6106
8798 // Elements that cannot be focused if they have [disabled] attribute.
8799 var FOCUSABLE_WITHOUT_DISABLED = [ 6107 var FOCUSABLE_WITHOUT_DISABLED = [
8800 'input', 6108 'input',
8801 'select', 6109 'select',
8802 'textarea', 6110 'textarea',
8803 'button' 6111 'button'
8804 ]; 6112 ];
8805 6113
8806 // Discard elements with tabindex=-1 (makes them not focusable).
8807 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') + 6114 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') +
8808 ':not([tabindex="-1"]),' + 6115 ':not([tabindex="-1"]),' +
8809 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),' ) + 6116 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),' ) +
8810 ':not([disabled]):not([tabindex="-1"])'; 6117 ':not([disabled]):not([tabindex="-1"])';
8811 6118
8812 var focusables = Polymer.dom(this).querySelectorAll(selector); 6119 var focusables = Polymer.dom(this).querySelectorAll(selector);
8813 if (this.tabIndex >= 0) { 6120 if (this.tabIndex >= 0) {
8814 // Insert at the beginning because we might have all elements with tabIn dex = 0,
8815 // and the overlay should be the first of the list.
8816 focusables.splice(0, 0, this); 6121 focusables.splice(0, 0, this);
8817 } 6122 }
8818 // Sort by tabindex.
8819 return focusables.sort(function (a, b) { 6123 return focusables.sort(function (a, b) {
8820 if (a.tabIndex === b.tabIndex) { 6124 if (a.tabIndex === b.tabIndex) {
8821 return 0; 6125 return 0;
8822 } 6126 }
8823 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) { 6127 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) {
8824 return 1; 6128 return 1;
8825 } 6129 }
8826 return -1; 6130 return -1;
8827 }); 6131 });
8828 }, 6132 },
8829 6133
8830 ready: function() { 6134 ready: function() {
8831 // Used to skip calls to notifyResize and refit while the overlay is anima ting.
8832 this.__isAnimating = false; 6135 this.__isAnimating = false;
8833 // with-backdrop needs tabindex to be set in order to trap the focus.
8834 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false.
8835 this.__shouldRemoveTabIndex = false; 6136 this.__shouldRemoveTabIndex = false;
8836 // Used for wrapping the focus on TAB / Shift+TAB.
8837 this.__firstFocusableNode = this.__lastFocusableNode = null; 6137 this.__firstFocusableNode = this.__lastFocusableNode = null;
8838 // Used by __onNextAnimationFrame to cancel any previous callback.
8839 this.__raf = null; 6138 this.__raf = null;
8840 // Focused node before overlay gets opened. Can be restored on close.
8841 this.__restoreFocusNode = null; 6139 this.__restoreFocusNode = null;
8842 this._ensureSetup(); 6140 this._ensureSetup();
8843 }, 6141 },
8844 6142
8845 attached: function() { 6143 attached: function() {
8846 // Call _openedChanged here so that position can be computed correctly.
8847 if (this.opened) { 6144 if (this.opened) {
8848 this._openedChanged(this.opened); 6145 this._openedChanged(this.opened);
8849 } 6146 }
8850 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange); 6147 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
8851 }, 6148 },
8852 6149
8853 detached: function() { 6150 detached: function() {
8854 Polymer.dom(this).unobserveNodes(this._observer); 6151 Polymer.dom(this).unobserveNodes(this._observer);
8855 this._observer = null; 6152 this._observer = null;
8856 if (this.__raf) { 6153 if (this.__raf) {
8857 window.cancelAnimationFrame(this.__raf); 6154 window.cancelAnimationFrame(this.__raf);
8858 this.__raf = null; 6155 this.__raf = null;
8859 } 6156 }
8860 this._manager.removeOverlay(this); 6157 this._manager.removeOverlay(this);
8861 }, 6158 },
8862 6159
8863 /**
8864 * Toggle the opened state of the overlay.
8865 */
8866 toggle: function() { 6160 toggle: function() {
8867 this._setCanceled(false); 6161 this._setCanceled(false);
8868 this.opened = !this.opened; 6162 this.opened = !this.opened;
8869 }, 6163 },
8870 6164
8871 /**
8872 * Open the overlay.
8873 */
8874 open: function() { 6165 open: function() {
8875 this._setCanceled(false); 6166 this._setCanceled(false);
8876 this.opened = true; 6167 this.opened = true;
8877 }, 6168 },
8878 6169
8879 /**
8880 * Close the overlay.
8881 */
8882 close: function() { 6170 close: function() {
8883 this._setCanceled(false); 6171 this._setCanceled(false);
8884 this.opened = false; 6172 this.opened = false;
8885 }, 6173 },
8886 6174
8887 /**
8888 * Cancels the overlay.
8889 * @param {Event=} event The original event
8890 */
8891 cancel: function(event) { 6175 cancel: function(event) {
8892 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t rue}); 6176 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t rue});
8893 if (cancelEvent.defaultPrevented) { 6177 if (cancelEvent.defaultPrevented) {
8894 return; 6178 return;
8895 } 6179 }
8896 6180
8897 this._setCanceled(true); 6181 this._setCanceled(true);
8898 this.opened = false; 6182 this.opened = false;
8899 }, 6183 },
8900 6184
8901 _ensureSetup: function() { 6185 _ensureSetup: function() {
8902 if (this._overlaySetup) { 6186 if (this._overlaySetup) {
8903 return; 6187 return;
8904 } 6188 }
8905 this._overlaySetup = true; 6189 this._overlaySetup = true;
8906 this.style.outline = 'none'; 6190 this.style.outline = 'none';
8907 this.style.display = 'none'; 6191 this.style.display = 'none';
8908 }, 6192 },
8909 6193
8910 /**
8911 * Called when `opened` changes.
8912 * @param {boolean=} opened
8913 * @protected
8914 */
8915 _openedChanged: function(opened) { 6194 _openedChanged: function(opened) {
8916 if (opened) { 6195 if (opened) {
8917 this.removeAttribute('aria-hidden'); 6196 this.removeAttribute('aria-hidden');
8918 } else { 6197 } else {
8919 this.setAttribute('aria-hidden', 'true'); 6198 this.setAttribute('aria-hidden', 'true');
8920 } 6199 }
8921 6200
8922 // Defer any animation-related code on attached
8923 // (_openedChanged gets called again on attached).
8924 if (!this.isAttached) { 6201 if (!this.isAttached) {
8925 return; 6202 return;
8926 } 6203 }
8927 6204
8928 this.__isAnimating = true; 6205 this.__isAnimating = true;
8929 6206
8930 // Use requestAnimationFrame for non-blocking rendering.
8931 this.__onNextAnimationFrame(this.__openedChanged); 6207 this.__onNextAnimationFrame(this.__openedChanged);
8932 }, 6208 },
8933 6209
8934 _canceledChanged: function() { 6210 _canceledChanged: function() {
8935 this.closingReason = this.closingReason || {}; 6211 this.closingReason = this.closingReason || {};
8936 this.closingReason.canceled = this.canceled; 6212 this.closingReason.canceled = this.canceled;
8937 }, 6213 },
8938 6214
8939 _withBackdropChanged: function() { 6215 _withBackdropChanged: function() {
8940 // If tabindex is already set, no need to override it.
8941 if (this.withBackdrop && !this.hasAttribute('tabindex')) { 6216 if (this.withBackdrop && !this.hasAttribute('tabindex')) {
8942 this.setAttribute('tabindex', '-1'); 6217 this.setAttribute('tabindex', '-1');
8943 this.__shouldRemoveTabIndex = true; 6218 this.__shouldRemoveTabIndex = true;
8944 } else if (this.__shouldRemoveTabIndex) { 6219 } else if (this.__shouldRemoveTabIndex) {
8945 this.removeAttribute('tabindex'); 6220 this.removeAttribute('tabindex');
8946 this.__shouldRemoveTabIndex = false; 6221 this.__shouldRemoveTabIndex = false;
8947 } 6222 }
8948 if (this.opened && this.isAttached) { 6223 if (this.opened && this.isAttached) {
8949 this._manager.trackBackdrop(); 6224 this._manager.trackBackdrop();
8950 } 6225 }
8951 }, 6226 },
8952 6227
8953 /**
8954 * tasks which must occur before opening; e.g. making the element visible.
8955 * @protected
8956 */
8957 _prepareRenderOpened: function() { 6228 _prepareRenderOpened: function() {
8958 // Store focused node.
8959 this.__restoreFocusNode = this._manager.deepActiveElement; 6229 this.__restoreFocusNode = this._manager.deepActiveElement;
8960 6230
8961 // Needed to calculate the size of the overlay so that transitions on its size
8962 // will have the correct starting points.
8963 this._preparePositioning(); 6231 this._preparePositioning();
8964 this.refit(); 6232 this.refit();
8965 this._finishPositioning(); 6233 this._finishPositioning();
8966 6234
8967 // Safari will apply the focus to the autofocus element when displayed
8968 // for the first time, so we make sure to return the focus where it was.
8969 if (this.noAutoFocus && document.activeElement === this._focusNode) { 6235 if (this.noAutoFocus && document.activeElement === this._focusNode) {
8970 this._focusNode.blur(); 6236 this._focusNode.blur();
8971 this.__restoreFocusNode.focus(); 6237 this.__restoreFocusNode.focus();
8972 } 6238 }
8973 }, 6239 },
8974 6240
8975 /**
8976 * Tasks which cause the overlay to actually open; typically play an animati on.
8977 * @protected
8978 */
8979 _renderOpened: function() { 6241 _renderOpened: function() {
8980 this._finishRenderOpened(); 6242 this._finishRenderOpened();
8981 }, 6243 },
8982 6244
8983 /**
8984 * Tasks which cause the overlay to actually close; typically play an animat ion.
8985 * @protected
8986 */
8987 _renderClosed: function() { 6245 _renderClosed: function() {
8988 this._finishRenderClosed(); 6246 this._finishRenderClosed();
8989 }, 6247 },
8990 6248
8991 /**
8992 * Tasks to be performed at the end of open action. Will fire `iron-overlay- opened`.
8993 * @protected
8994 */
8995 _finishRenderOpened: function() { 6249 _finishRenderOpened: function() {
8996 this.notifyResize(); 6250 this.notifyResize();
8997 this.__isAnimating = false; 6251 this.__isAnimating = false;
8998 6252
8999 // Store it so we don't query too much.
9000 var focusableNodes = this._focusableNodes; 6253 var focusableNodes = this._focusableNodes;
9001 this.__firstFocusableNode = focusableNodes[0]; 6254 this.__firstFocusableNode = focusableNodes[0];
9002 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1]; 6255 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1];
9003 6256
9004 this.fire('iron-overlay-opened'); 6257 this.fire('iron-overlay-opened');
9005 }, 6258 },
9006 6259
9007 /**
9008 * Tasks to be performed at the end of close action. Will fire `iron-overlay -closed`.
9009 * @protected
9010 */
9011 _finishRenderClosed: function() { 6260 _finishRenderClosed: function() {
9012 // Hide the overlay.
9013 this.style.display = 'none'; 6261 this.style.display = 'none';
9014 // Reset z-index only at the end of the animation.
9015 this.style.zIndex = ''; 6262 this.style.zIndex = '';
9016 this.notifyResize(); 6263 this.notifyResize();
9017 this.__isAnimating = false; 6264 this.__isAnimating = false;
9018 this.fire('iron-overlay-closed', this.closingReason); 6265 this.fire('iron-overlay-closed', this.closingReason);
9019 }, 6266 },
9020 6267
9021 _preparePositioning: function() { 6268 _preparePositioning: function() {
9022 this.style.transition = this.style.webkitTransition = 'none'; 6269 this.style.transition = this.style.webkitTransition = 'none';
9023 this.style.transform = this.style.webkitTransform = 'none'; 6270 this.style.transform = this.style.webkitTransform = 'none';
9024 this.style.display = ''; 6271 this.style.display = '';
9025 }, 6272 },
9026 6273
9027 _finishPositioning: function() { 6274 _finishPositioning: function() {
9028 // First, make it invisible & reactivate animations.
9029 this.style.display = 'none'; 6275 this.style.display = 'none';
9030 // Force reflow before re-enabling animations so that they don't start.
9031 // Set scrollTop to itself so that Closure Compiler doesn't remove this.
9032 this.scrollTop = this.scrollTop; 6276 this.scrollTop = this.scrollTop;
9033 this.style.transition = this.style.webkitTransition = ''; 6277 this.style.transition = this.style.webkitTransition = '';
9034 this.style.transform = this.style.webkitTransform = ''; 6278 this.style.transform = this.style.webkitTransform = '';
9035 // Now that animations are enabled, make it visible again
9036 this.style.display = ''; 6279 this.style.display = '';
9037 // Force reflow, so that following animations are properly started.
9038 // Set scrollTop to itself so that Closure Compiler doesn't remove this.
9039 this.scrollTop = this.scrollTop; 6280 this.scrollTop = this.scrollTop;
9040 }, 6281 },
9041 6282
9042 /**
9043 * Applies focus according to the opened state.
9044 * @protected
9045 */
9046 _applyFocus: function() { 6283 _applyFocus: function() {
9047 if (this.opened) { 6284 if (this.opened) {
9048 if (!this.noAutoFocus) { 6285 if (!this.noAutoFocus) {
9049 this._focusNode.focus(); 6286 this._focusNode.focus();
9050 } 6287 }
9051 } 6288 }
9052 else { 6289 else {
9053 this._focusNode.blur(); 6290 this._focusNode.blur();
9054 this._focusedChild = null; 6291 this._focusedChild = null;
9055 // Restore focus.
9056 if (this.restoreFocusOnClose && this.__restoreFocusNode) { 6292 if (this.restoreFocusOnClose && this.__restoreFocusNode) {
9057 this.__restoreFocusNode.focus(); 6293 this.__restoreFocusNode.focus();
9058 } 6294 }
9059 this.__restoreFocusNode = null; 6295 this.__restoreFocusNode = null;
9060 // If many overlays get closed at the same time, one of them would still
9061 // be the currentOverlay even if already closed, and would call _applyFo cus
9062 // infinitely, so we check for this not to be the current overlay.
9063 var currentOverlay = this._manager.currentOverlay(); 6296 var currentOverlay = this._manager.currentOverlay();
9064 if (currentOverlay && this !== currentOverlay) { 6297 if (currentOverlay && this !== currentOverlay) {
9065 currentOverlay._applyFocus(); 6298 currentOverlay._applyFocus();
9066 } 6299 }
9067 } 6300 }
9068 }, 6301 },
9069 6302
9070 /**
9071 * Cancels (closes) the overlay. Call when click happens outside the overlay .
9072 * @param {!Event} event
9073 * @protected
9074 */
9075 _onCaptureClick: function(event) { 6303 _onCaptureClick: function(event) {
9076 if (!this.noCancelOnOutsideClick) { 6304 if (!this.noCancelOnOutsideClick) {
9077 this.cancel(event); 6305 this.cancel(event);
9078 } 6306 }
9079 }, 6307 },
9080 6308
9081 /**
9082 * Keeps track of the focused child. If withBackdrop, traps focus within ove rlay.
9083 * @param {!Event} event
9084 * @protected
9085 */
9086 _onCaptureFocus: function (event) { 6309 _onCaptureFocus: function (event) {
9087 if (!this.withBackdrop) { 6310 if (!this.withBackdrop) {
9088 return; 6311 return;
9089 } 6312 }
9090 var path = Polymer.dom(event).path; 6313 var path = Polymer.dom(event).path;
9091 if (path.indexOf(this) === -1) { 6314 if (path.indexOf(this) === -1) {
9092 event.stopPropagation(); 6315 event.stopPropagation();
9093 this._applyFocus(); 6316 this._applyFocus();
9094 } else { 6317 } else {
9095 this._focusedChild = path[0]; 6318 this._focusedChild = path[0];
9096 } 6319 }
9097 }, 6320 },
9098 6321
9099 /**
9100 * Handles the ESC key event and cancels (closes) the overlay.
9101 * @param {!Event} event
9102 * @protected
9103 */
9104 _onCaptureEsc: function(event) { 6322 _onCaptureEsc: function(event) {
9105 if (!this.noCancelOnEscKey) { 6323 if (!this.noCancelOnEscKey) {
9106 this.cancel(event); 6324 this.cancel(event);
9107 } 6325 }
9108 }, 6326 },
9109 6327
9110 /**
9111 * Handles TAB key events to track focus changes.
9112 * Will wrap focus for overlays withBackdrop.
9113 * @param {!Event} event
9114 * @protected
9115 */
9116 _onCaptureTab: function(event) { 6328 _onCaptureTab: function(event) {
9117 if (!this.withBackdrop) { 6329 if (!this.withBackdrop) {
9118 return; 6330 return;
9119 } 6331 }
9120 // TAB wraps from last to first focusable.
9121 // Shift + TAB wraps from first to last focusable.
9122 var shift = event.shiftKey; 6332 var shift = event.shiftKey;
9123 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable Node; 6333 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable Node;
9124 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo de; 6334 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo de;
9125 var shouldWrap = false; 6335 var shouldWrap = false;
9126 if (nodeToCheck === nodeToSet) { 6336 if (nodeToCheck === nodeToSet) {
9127 // If nodeToCheck is the same as nodeToSet, it means we have an overlay
9128 // with 0 or 1 focusables; in either case we still need to trap the
9129 // focus within the overlay.
9130 shouldWrap = true; 6337 shouldWrap = true;
9131 } else { 6338 } else {
9132 // In dom=shadow, the manager will receive focus changes on the main
9133 // root but not the ones within other shadow roots, so we can't rely on
9134 // _focusedChild, but we should check the deepest active element.
9135 var focusedNode = this._manager.deepActiveElement; 6339 var focusedNode = this._manager.deepActiveElement;
9136 // If the active element is not the nodeToCheck but the overlay itself,
9137 // it means the focus is about to go outside the overlay, hence we
9138 // should prevent that (e.g. user opens the overlay and hit Shift+TAB).
9139 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this); 6340 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this);
9140 } 6341 }
9141 6342
9142 if (shouldWrap) { 6343 if (shouldWrap) {
9143 // When the overlay contains the last focusable element of the document
9144 // and it's already focused, pressing TAB would move the focus outside
9145 // the document (e.g. to the browser search bar). Similarly, when the
9146 // overlay contains the first focusable element of the document and it's
9147 // already focused, pressing Shift+TAB would move the focus outside the
9148 // document (e.g. to the browser search bar).
9149 // In both cases, we would not receive a focus event, but only a blur.
9150 // In order to achieve focus wrapping, we prevent this TAB event and
9151 // force the focus. This will also prevent the focus to temporarily move
9152 // outside the overlay, which might cause scrolling.
9153 event.preventDefault(); 6344 event.preventDefault();
9154 this._focusedChild = nodeToSet; 6345 this._focusedChild = nodeToSet;
9155 this._applyFocus(); 6346 this._applyFocus();
9156 } 6347 }
9157 }, 6348 },
9158 6349
9159 /**
9160 * Refits if the overlay is opened and not animating.
9161 * @protected
9162 */
9163 _onIronResize: function() { 6350 _onIronResize: function() {
9164 if (this.opened && !this.__isAnimating) { 6351 if (this.opened && !this.__isAnimating) {
9165 this.__onNextAnimationFrame(this.refit); 6352 this.__onNextAnimationFrame(this.refit);
9166 } 6353 }
9167 }, 6354 },
9168 6355
9169 /**
9170 * Will call notifyResize if overlay is opened.
9171 * Can be overridden in order to avoid multiple observers on the same node.
9172 * @protected
9173 */
9174 _onNodesChange: function() { 6356 _onNodesChange: function() {
9175 if (this.opened && !this.__isAnimating) { 6357 if (this.opened && !this.__isAnimating) {
9176 this.notifyResize(); 6358 this.notifyResize();
9177 } 6359 }
9178 }, 6360 },
9179 6361
9180 /**
9181 * Tasks executed when opened changes: prepare for the opening, move the
9182 * focus, update the manager, render opened/closed.
9183 * @private
9184 */
9185 __openedChanged: function() { 6362 __openedChanged: function() {
9186 if (this.opened) { 6363 if (this.opened) {
9187 // Make overlay visible, then add it to the manager.
9188 this._prepareRenderOpened(); 6364 this._prepareRenderOpened();
9189 this._manager.addOverlay(this); 6365 this._manager.addOverlay(this);
9190 // Move the focus to the child node with [autofocus].
9191 this._applyFocus(); 6366 this._applyFocus();
9192 6367
9193 this._renderOpened(); 6368 this._renderOpened();
9194 } else { 6369 } else {
9195 // Remove overlay, then restore the focus before actually closing.
9196 this._manager.removeOverlay(this); 6370 this._manager.removeOverlay(this);
9197 this._applyFocus(); 6371 this._applyFocus();
9198 6372
9199 this._renderClosed(); 6373 this._renderClosed();
9200 } 6374 }
9201 }, 6375 },
9202 6376
9203 /**
9204 * Executes a callback on the next animation frame, overriding any previous
9205 * callback awaiting for the next animation frame. e.g.
9206 * `__onNextAnimationFrame(callback1) && __onNextAnimationFrame(callback2)`;
9207 * `callback1` will never be invoked.
9208 * @param {!Function} callback Its `this` parameter is the overlay itself.
9209 * @private
9210 */
9211 __onNextAnimationFrame: function(callback) { 6377 __onNextAnimationFrame: function(callback) {
9212 if (this.__raf) { 6378 if (this.__raf) {
9213 window.cancelAnimationFrame(this.__raf); 6379 window.cancelAnimationFrame(this.__raf);
9214 } 6380 }
9215 var self = this; 6381 var self = this;
9216 this.__raf = window.requestAnimationFrame(function nextAnimationFrame() { 6382 this.__raf = window.requestAnimationFrame(function nextAnimationFrame() {
9217 self.__raf = null; 6383 self.__raf = null;
9218 callback.call(self); 6384 callback.call(self);
9219 }); 6385 });
9220 } 6386 }
9221 6387
9222 }; 6388 };
9223 6389
9224 /** @polymerBehavior */ 6390 /** @polymerBehavior */
9225 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl]; 6391 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl];
9226 6392
9227 /**
9228 * Fired after the overlay opens.
9229 * @event iron-overlay-opened
9230 */
9231 6393
9232 /**
9233 * Fired when the overlay is canceled, but before it is closed.
9234 * @event iron-overlay-canceled
9235 * @param {Event} event The closing of the overlay can be prevented
9236 * by calling `event.preventDefault()`. The `event.detail` is the original eve nt that
9237 * originated the canceling (e.g. ESC keyboard event or click event outside th e overlay).
9238 */
9239 6394
9240 /**
9241 * Fired after the overlay closes.
9242 * @event iron-overlay-closed
9243 * @param {Event} event The `event.detail` is the `closingReason` property
9244 * (contains `canceled`, whether the overlay was canceled).
9245 */
9246 6395
9247 })(); 6396 })();
9248 /**
9249 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim ations for use with
9250 * elements implementing `Polymer.NeonAnimationRunnerBehavior`.
9251 * @polymerBehavior
9252 */
9253 Polymer.NeonAnimatableBehavior = { 6397 Polymer.NeonAnimatableBehavior = {
9254 6398
9255 properties: { 6399 properties: {
9256 6400
9257 /**
9258 * Animation configuration. See README for more info.
9259 */
9260 animationConfig: { 6401 animationConfig: {
9261 type: Object 6402 type: Object
9262 }, 6403 },
9263 6404
9264 /**
9265 * Convenience property for setting an 'entry' animation. Do not set `anim ationConfig.entry`
9266 * manually if using this. The animated node is set to `this` if using thi s property.
9267 */
9268 entryAnimation: { 6405 entryAnimation: {
9269 observer: '_entryAnimationChanged', 6406 observer: '_entryAnimationChanged',
9270 type: String 6407 type: String
9271 }, 6408 },
9272 6409
9273 /**
9274 * Convenience property for setting an 'exit' animation. Do not set `anima tionConfig.exit`
9275 * manually if using this. The animated node is set to `this` if using thi s property.
9276 */
9277 exitAnimation: { 6410 exitAnimation: {
9278 observer: '_exitAnimationChanged', 6411 observer: '_exitAnimationChanged',
9279 type: String 6412 type: String
9280 } 6413 }
9281 6414
9282 }, 6415 },
9283 6416
9284 _entryAnimationChanged: function() { 6417 _entryAnimationChanged: function() {
9285 this.animationConfig = this.animationConfig || {}; 6418 this.animationConfig = this.animationConfig || {};
9286 this.animationConfig['entry'] = [{ 6419 this.animationConfig['entry'] = [{
9287 name: this.entryAnimation, 6420 name: this.entryAnimation,
9288 node: this 6421 node: this
9289 }]; 6422 }];
9290 }, 6423 },
9291 6424
9292 _exitAnimationChanged: function() { 6425 _exitAnimationChanged: function() {
9293 this.animationConfig = this.animationConfig || {}; 6426 this.animationConfig = this.animationConfig || {};
9294 this.animationConfig['exit'] = [{ 6427 this.animationConfig['exit'] = [{
9295 name: this.exitAnimation, 6428 name: this.exitAnimation,
9296 node: this 6429 node: this
9297 }]; 6430 }];
9298 }, 6431 },
9299 6432
9300 _copyProperties: function(config1, config2) { 6433 _copyProperties: function(config1, config2) {
9301 // shallowly copy properties from config2 to config1
9302 for (var property in config2) { 6434 for (var property in config2) {
9303 config1[property] = config2[property]; 6435 config1[property] = config2[property];
9304 } 6436 }
9305 }, 6437 },
9306 6438
9307 _cloneConfig: function(config) { 6439 _cloneConfig: function(config) {
9308 var clone = { 6440 var clone = {
9309 isClone: true 6441 isClone: true
9310 }; 6442 };
9311 this._copyProperties(clone, config); 6443 this._copyProperties(clone, config);
9312 return clone; 6444 return clone;
9313 }, 6445 },
9314 6446
9315 _getAnimationConfigRecursive: function(type, map, allConfigs) { 6447 _getAnimationConfigRecursive: function(type, map, allConfigs) {
9316 if (!this.animationConfig) { 6448 if (!this.animationConfig) {
9317 return; 6449 return;
9318 } 6450 }
9319 6451
9320 if(this.animationConfig.value && typeof this.animationConfig.value === 'fu nction') { 6452 if(this.animationConfig.value && typeof this.animationConfig.value === 'fu nction') {
9321 this._warn(this._logf('playAnimation', "Please put 'animationConfig' ins ide of your components 'properties' object instead of outside of it.")); 6453 this._warn(this._logf('playAnimation', "Please put 'animationConfig' ins ide of your components 'properties' object instead of outside of it."));
9322 return; 6454 return;
9323 } 6455 }
9324 6456
9325 // type is optional
9326 var thisConfig; 6457 var thisConfig;
9327 if (type) { 6458 if (type) {
9328 thisConfig = this.animationConfig[type]; 6459 thisConfig = this.animationConfig[type];
9329 } else { 6460 } else {
9330 thisConfig = this.animationConfig; 6461 thisConfig = this.animationConfig;
9331 } 6462 }
9332 6463
9333 if (!Array.isArray(thisConfig)) { 6464 if (!Array.isArray(thisConfig)) {
9334 thisConfig = [thisConfig]; 6465 thisConfig = [thisConfig];
9335 } 6466 }
9336 6467
9337 // iterate animations and recurse to process configurations from child nod es
9338 if (thisConfig) { 6468 if (thisConfig) {
9339 for (var config, index = 0; config = thisConfig[index]; index++) { 6469 for (var config, index = 0; config = thisConfig[index]; index++) {
9340 if (config.animatable) { 6470 if (config.animatable) {
9341 config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs); 6471 config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs);
9342 } else { 6472 } else {
9343 if (config.id) { 6473 if (config.id) {
9344 var cachedConfig = map[config.id]; 6474 var cachedConfig = map[config.id];
9345 if (cachedConfig) { 6475 if (cachedConfig) {
9346 // merge configurations with the same id, making a clone lazily
9347 if (!cachedConfig.isClone) { 6476 if (!cachedConfig.isClone) {
9348 map[config.id] = this._cloneConfig(cachedConfig) 6477 map[config.id] = this._cloneConfig(cachedConfig)
9349 cachedConfig = map[config.id]; 6478 cachedConfig = map[config.id];
9350 } 6479 }
9351 this._copyProperties(cachedConfig, config); 6480 this._copyProperties(cachedConfig, config);
9352 } else { 6481 } else {
9353 // put any configs with an id into a map
9354 map[config.id] = config; 6482 map[config.id] = config;
9355 } 6483 }
9356 } else { 6484 } else {
9357 allConfigs.push(config); 6485 allConfigs.push(config);
9358 } 6486 }
9359 } 6487 }
9360 } 6488 }
9361 } 6489 }
9362 }, 6490 },
9363 6491
9364 /**
9365 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure
9366 * an animation with an optional type. Elements implementing `Polymer.NeonAn imatableBehavior`
9367 * should define the property `animationConfig`, which is either a configura tion object
9368 * or a map of animation type to array of configuration objects.
9369 */
9370 getAnimationConfig: function(type) { 6492 getAnimationConfig: function(type) {
9371 var map = {}; 6493 var map = {};
9372 var allConfigs = []; 6494 var allConfigs = [];
9373 this._getAnimationConfigRecursive(type, map, allConfigs); 6495 this._getAnimationConfigRecursive(type, map, allConfigs);
9374 // append the configurations saved in the map to the array
9375 for (var key in map) { 6496 for (var key in map) {
9376 allConfigs.push(map[key]); 6497 allConfigs.push(map[key]);
9377 } 6498 }
9378 return allConfigs; 6499 return allConfigs;
9379 } 6500 }
9380 6501
9381 }; 6502 };
9382 /**
9383 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations.
9384 *
9385 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior
9386 */
9387 Polymer.NeonAnimationRunnerBehaviorImpl = { 6503 Polymer.NeonAnimationRunnerBehaviorImpl = {
9388 6504
9389 _configureAnimations: function(configs) { 6505 _configureAnimations: function(configs) {
9390 var results = []; 6506 var results = [];
9391 if (configs.length > 0) { 6507 if (configs.length > 0) {
9392 for (var config, index = 0; config = configs[index]; index++) { 6508 for (var config, index = 0; config = configs[index]; index++) {
9393 var neonAnimation = document.createElement(config.name); 6509 var neonAnimation = document.createElement(config.name);
9394 // is this element actually a neon animation?
9395 if (neonAnimation.isNeonAnimation) { 6510 if (neonAnimation.isNeonAnimation) {
9396 var result = null; 6511 var result = null;
9397 // configuration or play could fail if polyfills aren't loaded
9398 try { 6512 try {
9399 result = neonAnimation.configure(config); 6513 result = neonAnimation.configure(config);
9400 // Check if we have an Effect rather than an Animation
9401 if (typeof result.cancel != 'function') { 6514 if (typeof result.cancel != 'function') {
9402 result = document.timeline.play(result); 6515 result = document.timeline.play(result);
9403 } 6516 }
9404 } catch (e) { 6517 } catch (e) {
9405 result = null; 6518 result = null;
9406 console.warn('Couldnt play', '(', config.name, ').', e); 6519 console.warn('Couldnt play', '(', config.name, ').', e);
9407 } 6520 }
9408 if (result) { 6521 if (result) {
9409 results.push({ 6522 results.push({
9410 neonAnimation: neonAnimation, 6523 neonAnimation: neonAnimation,
(...skipping 22 matching lines...) Expand all
9433 6546
9434 _complete: function(activeEntries) { 6547 _complete: function(activeEntries) {
9435 for (var i = 0; i < activeEntries.length; i++) { 6548 for (var i = 0; i < activeEntries.length; i++) {
9436 activeEntries[i].neonAnimation.complete(activeEntries[i].config); 6549 activeEntries[i].neonAnimation.complete(activeEntries[i].config);
9437 } 6550 }
9438 for (var i = 0; i < activeEntries.length; i++) { 6551 for (var i = 0; i < activeEntries.length; i++) {
9439 activeEntries[i].animation.cancel(); 6552 activeEntries[i].animation.cancel();
9440 } 6553 }
9441 }, 6554 },
9442 6555
9443 /**
9444 * Plays an animation with an optional `type`.
9445 * @param {string=} type
9446 * @param {!Object=} cookie
9447 */
9448 playAnimation: function(type, cookie) { 6556 playAnimation: function(type, cookie) {
9449 var configs = this.getAnimationConfig(type); 6557 var configs = this.getAnimationConfig(type);
9450 if (!configs) { 6558 if (!configs) {
9451 return; 6559 return;
9452 } 6560 }
9453 this._active = this._active || {}; 6561 this._active = this._active || {};
9454 if (this._active[type]) { 6562 if (this._active[type]) {
9455 this._complete(this._active[type]); 6563 this._complete(this._active[type]);
9456 delete this._active[type]; 6564 delete this._active[type];
9457 } 6565 }
(...skipping 11 matching lines...) Expand all
9469 activeEntries[i].animation.onfinish = function() { 6577 activeEntries[i].animation.onfinish = function() {
9470 if (this._shouldComplete(activeEntries)) { 6578 if (this._shouldComplete(activeEntries)) {
9471 this._complete(activeEntries); 6579 this._complete(activeEntries);
9472 delete this._active[type]; 6580 delete this._active[type];
9473 this.fire('neon-animation-finish', cookie, {bubbles: false}); 6581 this.fire('neon-animation-finish', cookie, {bubbles: false});
9474 } 6582 }
9475 }.bind(this); 6583 }.bind(this);
9476 } 6584 }
9477 }, 6585 },
9478 6586
9479 /**
9480 * Cancels the currently running animations.
9481 */
9482 cancelAnimation: function() { 6587 cancelAnimation: function() {
9483 for (var k in this._animations) { 6588 for (var k in this._animations) {
9484 this._animations[k].cancel(); 6589 this._animations[k].cancel();
9485 } 6590 }
9486 this._animations = {}; 6591 this._animations = {};
9487 } 6592 }
9488 }; 6593 };
9489 6594
9490 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ 6595 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */
9491 Polymer.NeonAnimationRunnerBehavior = [ 6596 Polymer.NeonAnimationRunnerBehavior = [
9492 Polymer.NeonAnimatableBehavior, 6597 Polymer.NeonAnimatableBehavior,
9493 Polymer.NeonAnimationRunnerBehaviorImpl 6598 Polymer.NeonAnimationRunnerBehaviorImpl
9494 ]; 6599 ];
9495 /**
9496 * Use `Polymer.NeonAnimationBehavior` to implement an animation.
9497 * @polymerBehavior
9498 */
9499 Polymer.NeonAnimationBehavior = { 6600 Polymer.NeonAnimationBehavior = {
9500 6601
9501 properties: { 6602 properties: {
9502 6603
9503 /**
9504 * Defines the animation timing.
9505 */
9506 animationTiming: { 6604 animationTiming: {
9507 type: Object, 6605 type: Object,
9508 value: function() { 6606 value: function() {
9509 return { 6607 return {
9510 duration: 500, 6608 duration: 500,
9511 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', 6609 easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
9512 fill: 'both' 6610 fill: 'both'
9513 } 6611 }
9514 } 6612 }
9515 } 6613 }
9516 6614
9517 }, 6615 },
9518 6616
9519 /**
9520 * Can be used to determine that elements implement this behavior.
9521 */
9522 isNeonAnimation: true, 6617 isNeonAnimation: true,
9523 6618
9524 /**
9525 * Do any animation configuration here.
9526 */
9527 // configure: function(config) {
9528 // },
9529 6619
9530 /**
9531 * Returns the animation timing by mixing in properties from `config` to the defaults defined
9532 * by the animation.
9533 */
9534 timingFromConfig: function(config) { 6620 timingFromConfig: function(config) {
9535 if (config.timing) { 6621 if (config.timing) {
9536 for (var property in config.timing) { 6622 for (var property in config.timing) {
9537 this.animationTiming[property] = config.timing[property]; 6623 this.animationTiming[property] = config.timing[property];
9538 } 6624 }
9539 } 6625 }
9540 return this.animationTiming; 6626 return this.animationTiming;
9541 }, 6627 },
9542 6628
9543 /**
9544 * Sets `transform` and `transformOrigin` properties along with the prefixed versions.
9545 */
9546 setPrefixedProperty: function(node, property, value) { 6629 setPrefixedProperty: function(node, property, value) {
9547 var map = { 6630 var map = {
9548 'transform': ['webkitTransform'], 6631 'transform': ['webkitTransform'],
9549 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] 6632 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin']
9550 }; 6633 };
9551 var prefixes = map[property]; 6634 var prefixes = map[property];
9552 for (var prefix, index = 0; prefix = prefixes[index]; index++) { 6635 for (var prefix, index = 0; prefix = prefixes[index]; index++) {
9553 node.style[prefix] = value; 6636 node.style[prefix] = value;
9554 } 6637 }
9555 node.style[property] = value; 6638 node.style[property] = value;
9556 }, 6639 },
9557 6640
9558 /**
9559 * Called when the animation finishes.
9560 */
9561 complete: function() {} 6641 complete: function() {}
9562 6642
9563 }; 6643 };
9564 Polymer({ 6644 Polymer({
9565 6645
9566 is: 'opaque-animation', 6646 is: 'opaque-animation',
9567 6647
9568 behaviors: [ 6648 behaviors: [
9569 Polymer.NeonAnimationBehavior 6649 Polymer.NeonAnimationBehavior
9570 ], 6650 ],
9571 6651
9572 configure: function(config) { 6652 configure: function(config) {
9573 var node = config.node; 6653 var node = config.node;
9574 this._effect = new KeyframeEffect(node, [ 6654 this._effect = new KeyframeEffect(node, [
9575 {'opacity': '1'}, 6655 {'opacity': '1'},
9576 {'opacity': '1'} 6656 {'opacity': '1'}
9577 ], this.timingFromConfig(config)); 6657 ], this.timingFromConfig(config));
9578 node.style.opacity = '0'; 6658 node.style.opacity = '0';
9579 return this._effect; 6659 return this._effect;
9580 }, 6660 },
9581 6661
9582 complete: function(config) { 6662 complete: function(config) {
9583 config.node.style.opacity = ''; 6663 config.node.style.opacity = '';
9584 } 6664 }
9585 6665
9586 }); 6666 });
9587 (function() { 6667 (function() {
9588 'use strict'; 6668 'use strict';
9589 // Used to calculate the scroll direction during touch events.
9590 var LAST_TOUCH_POSITION = { 6669 var LAST_TOUCH_POSITION = {
9591 pageX: 0, 6670 pageX: 0,
9592 pageY: 0 6671 pageY: 0
9593 }; 6672 };
9594 // Used to avoid computing event.path and filter scrollable nodes (better pe rf).
9595 var ROOT_TARGET = null; 6673 var ROOT_TARGET = null;
9596 var SCROLLABLE_NODES = []; 6674 var SCROLLABLE_NODES = [];
9597 6675
9598 /**
9599 * The IronDropdownScrollManager is intended to provide a central source
9600 * of authority and control over which elements in a document are currently
9601 * allowed to scroll.
9602 */
9603 6676
9604 Polymer.IronDropdownScrollManager = { 6677 Polymer.IronDropdownScrollManager = {
9605 6678
9606 /**
9607 * The current element that defines the DOM boundaries of the
9608 * scroll lock. This is always the most recently locking element.
9609 */
9610 get currentLockingElement() { 6679 get currentLockingElement() {
9611 return this._lockingElements[this._lockingElements.length - 1]; 6680 return this._lockingElements[this._lockingElements.length - 1];
9612 }, 6681 },
9613 6682
9614 /**
9615 * Returns true if the provided element is "scroll locked", which is to
9616 * say that it cannot be scrolled via pointer or keyboard interactions.
9617 *
9618 * @param {HTMLElement} element An HTML element instance which may or may
9619 * not be scroll locked.
9620 */
9621 elementIsScrollLocked: function(element) { 6683 elementIsScrollLocked: function(element) {
9622 var currentLockingElement = this.currentLockingElement; 6684 var currentLockingElement = this.currentLockingElement;
9623 6685
9624 if (currentLockingElement === undefined) 6686 if (currentLockingElement === undefined)
9625 return false; 6687 return false;
9626 6688
9627 var scrollLocked; 6689 var scrollLocked;
9628 6690
9629 if (this._hasCachedLockedElement(element)) { 6691 if (this._hasCachedLockedElement(element)) {
9630 return true; 6692 return true;
9631 } 6693 }
9632 6694
9633 if (this._hasCachedUnlockedElement(element)) { 6695 if (this._hasCachedUnlockedElement(element)) {
9634 return false; 6696 return false;
9635 } 6697 }
9636 6698
9637 scrollLocked = !!currentLockingElement && 6699 scrollLocked = !!currentLockingElement &&
9638 currentLockingElement !== element && 6700 currentLockingElement !== element &&
9639 !this._composedTreeContains(currentLockingElement, element); 6701 !this._composedTreeContains(currentLockingElement, element);
9640 6702
9641 if (scrollLocked) { 6703 if (scrollLocked) {
9642 this._lockedElementCache.push(element); 6704 this._lockedElementCache.push(element);
9643 } else { 6705 } else {
9644 this._unlockedElementCache.push(element); 6706 this._unlockedElementCache.push(element);
9645 } 6707 }
9646 6708
9647 return scrollLocked; 6709 return scrollLocked;
9648 }, 6710 },
9649 6711
9650 /**
9651 * Push an element onto the current scroll lock stack. The most recently
9652 * pushed element and its children will be considered scrollable. All
9653 * other elements will not be scrollable.
9654 *
9655 * Scroll locking is implemented as a stack so that cases such as
9656 * dropdowns within dropdowns are handled well.
9657 *
9658 * @param {HTMLElement} element The element that should lock scroll.
9659 */
9660 pushScrollLock: function(element) { 6712 pushScrollLock: function(element) {
9661 // Prevent pushing the same element twice
9662 if (this._lockingElements.indexOf(element) >= 0) { 6713 if (this._lockingElements.indexOf(element) >= 0) {
9663 return; 6714 return;
9664 } 6715 }
9665 6716
9666 if (this._lockingElements.length === 0) { 6717 if (this._lockingElements.length === 0) {
9667 this._lockScrollInteractions(); 6718 this._lockScrollInteractions();
9668 } 6719 }
9669 6720
9670 this._lockingElements.push(element); 6721 this._lockingElements.push(element);
9671 6722
9672 this._lockedElementCache = []; 6723 this._lockedElementCache = [];
9673 this._unlockedElementCache = []; 6724 this._unlockedElementCache = [];
9674 }, 6725 },
9675 6726
9676 /**
9677 * Remove an element from the scroll lock stack. The element being
9678 * removed does not need to be the most recently pushed element. However,
9679 * the scroll lock constraints only change when the most recently pushed
9680 * element is removed.
9681 *
9682 * @param {HTMLElement} element The element to remove from the scroll
9683 * lock stack.
9684 */
9685 removeScrollLock: function(element) { 6727 removeScrollLock: function(element) {
9686 var index = this._lockingElements.indexOf(element); 6728 var index = this._lockingElements.indexOf(element);
9687 6729
9688 if (index === -1) { 6730 if (index === -1) {
9689 return; 6731 return;
9690 } 6732 }
9691 6733
9692 this._lockingElements.splice(index, 1); 6734 this._lockingElements.splice(index, 1);
9693 6735
9694 this._lockedElementCache = []; 6736 this._lockedElementCache = [];
(...skipping 12 matching lines...) Expand all
9707 6749
9708 _hasCachedLockedElement: function(element) { 6750 _hasCachedLockedElement: function(element) {
9709 return this._lockedElementCache.indexOf(element) > -1; 6751 return this._lockedElementCache.indexOf(element) > -1;
9710 }, 6752 },
9711 6753
9712 _hasCachedUnlockedElement: function(element) { 6754 _hasCachedUnlockedElement: function(element) {
9713 return this._unlockedElementCache.indexOf(element) > -1; 6755 return this._unlockedElementCache.indexOf(element) > -1;
9714 }, 6756 },
9715 6757
9716 _composedTreeContains: function(element, child) { 6758 _composedTreeContains: function(element, child) {
9717 // NOTE(cdata): This method iterates over content elements and their
9718 // corresponding distributed nodes to implement a contains-like method
9719 // that pierces through the composed tree of the ShadowDOM. Results of
9720 // this operation are cached (elsewhere) on a per-scroll-lock basis, to
9721 // guard against potentially expensive lookups happening repeatedly as
9722 // a user scrolls / touchmoves.
9723 var contentElements; 6759 var contentElements;
9724 var distributedNodes; 6760 var distributedNodes;
9725 var contentIndex; 6761 var contentIndex;
9726 var nodeIndex; 6762 var nodeIndex;
9727 6763
9728 if (element.contains(child)) { 6764 if (element.contains(child)) {
9729 return true; 6765 return true;
9730 } 6766 }
9731 6767
9732 contentElements = Polymer.dom(element).querySelectorAll('content'); 6768 contentElements = Polymer.dom(element).querySelectorAll('content');
9733 6769
9734 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI ndex) { 6770 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI ndex) {
9735 6771
9736 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr ibutedNodes(); 6772 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr ibutedNodes();
9737 6773
9738 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) { 6774 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
9739 6775
9740 if (this._composedTreeContains(distributedNodes[nodeIndex], child)) { 6776 if (this._composedTreeContains(distributedNodes[nodeIndex], child)) {
9741 return true; 6777 return true;
9742 } 6778 }
9743 } 6779 }
9744 } 6780 }
9745 6781
9746 return false; 6782 return false;
9747 }, 6783 },
9748 6784
9749 _scrollInteractionHandler: function(event) { 6785 _scrollInteractionHandler: function(event) {
9750 // Avoid canceling an event with cancelable=false, e.g. scrolling is in
9751 // progress and cannot be interrupted.
9752 if (event.cancelable && this._shouldPreventScrolling(event)) { 6786 if (event.cancelable && this._shouldPreventScrolling(event)) {
9753 event.preventDefault(); 6787 event.preventDefault();
9754 } 6788 }
9755 // If event has targetTouches (touch event), update last touch position.
9756 if (event.targetTouches) { 6789 if (event.targetTouches) {
9757 var touch = event.targetTouches[0]; 6790 var touch = event.targetTouches[0];
9758 LAST_TOUCH_POSITION.pageX = touch.pageX; 6791 LAST_TOUCH_POSITION.pageX = touch.pageX;
9759 LAST_TOUCH_POSITION.pageY = touch.pageY; 6792 LAST_TOUCH_POSITION.pageY = touch.pageY;
9760 } 6793 }
9761 }, 6794 },
9762 6795
9763 _lockScrollInteractions: function() { 6796 _lockScrollInteractions: function() {
9764 this._boundScrollHandler = this._boundScrollHandler || 6797 this._boundScrollHandler = this._boundScrollHandler ||
9765 this._scrollInteractionHandler.bind(this); 6798 this._scrollInteractionHandler.bind(this);
9766 // Modern `wheel` event for mouse wheel scrolling:
9767 document.addEventListener('wheel', this._boundScrollHandler, true); 6799 document.addEventListener('wheel', this._boundScrollHandler, true);
9768 // Older, non-standard `mousewheel` event for some FF:
9769 document.addEventListener('mousewheel', this._boundScrollHandler, true); 6800 document.addEventListener('mousewheel', this._boundScrollHandler, true);
9770 // IE:
9771 document.addEventListener('DOMMouseScroll', this._boundScrollHandler, tr ue); 6801 document.addEventListener('DOMMouseScroll', this._boundScrollHandler, tr ue);
9772 // Save the SCROLLABLE_NODES on touchstart, to be used on touchmove.
9773 document.addEventListener('touchstart', this._boundScrollHandler, true); 6802 document.addEventListener('touchstart', this._boundScrollHandler, true);
9774 // Mobile devices can scroll on touch move:
9775 document.addEventListener('touchmove', this._boundScrollHandler, true); 6803 document.addEventListener('touchmove', this._boundScrollHandler, true);
9776 }, 6804 },
9777 6805
9778 _unlockScrollInteractions: function() { 6806 _unlockScrollInteractions: function() {
9779 document.removeEventListener('wheel', this._boundScrollHandler, true); 6807 document.removeEventListener('wheel', this._boundScrollHandler, true);
9780 document.removeEventListener('mousewheel', this._boundScrollHandler, tru e); 6808 document.removeEventListener('mousewheel', this._boundScrollHandler, tru e);
9781 document.removeEventListener('DOMMouseScroll', this._boundScrollHandler, true); 6809 document.removeEventListener('DOMMouseScroll', this._boundScrollHandler, true);
9782 document.removeEventListener('touchstart', this._boundScrollHandler, tru e); 6810 document.removeEventListener('touchstart', this._boundScrollHandler, tru e);
9783 document.removeEventListener('touchmove', this._boundScrollHandler, true ); 6811 document.removeEventListener('touchmove', this._boundScrollHandler, true );
9784 }, 6812 },
9785 6813
9786 /**
9787 * Returns true if the event causes scroll outside the current locking
9788 * element, e.g. pointer/keyboard interactions, or scroll "leaking"
9789 * outside the locking element when it is already at its scroll boundaries .
9790 * @param {!Event} event
9791 * @return {boolean}
9792 * @private
9793 */
9794 _shouldPreventScrolling: function(event) { 6814 _shouldPreventScrolling: function(event) {
9795 6815
9796 // Update if root target changed. For touch events, ensure we don't
9797 // update during touchmove.
9798 var target = Polymer.dom(event).rootTarget; 6816 var target = Polymer.dom(event).rootTarget;
9799 if (event.type !== 'touchmove' && ROOT_TARGET !== target) { 6817 if (event.type !== 'touchmove' && ROOT_TARGET !== target) {
9800 ROOT_TARGET = target; 6818 ROOT_TARGET = target;
9801 SCROLLABLE_NODES = this._getScrollableNodes(Polymer.dom(event).path); 6819 SCROLLABLE_NODES = this._getScrollableNodes(Polymer.dom(event).path);
9802 } 6820 }
9803 6821
9804 // Prevent event if no scrollable nodes.
9805 if (!SCROLLABLE_NODES.length) { 6822 if (!SCROLLABLE_NODES.length) {
9806 return true; 6823 return true;
9807 } 6824 }
9808 // Don't prevent touchstart event inside the locking element when it has
9809 // scrollable nodes.
9810 if (event.type === 'touchstart') { 6825 if (event.type === 'touchstart') {
9811 return false; 6826 return false;
9812 } 6827 }
9813 // Get deltaX/Y.
9814 var info = this._getScrollInfo(event); 6828 var info = this._getScrollInfo(event);
9815 // Prevent if there is no child that can scroll.
9816 return !this._getScrollingNode(SCROLLABLE_NODES, info.deltaX, info.delta Y); 6829 return !this._getScrollingNode(SCROLLABLE_NODES, info.deltaX, info.delta Y);
9817 }, 6830 },
9818 6831
9819 /**
9820 * Returns an array of scrollable nodes up to the current locking element,
9821 * which is included too if scrollable.
9822 * @param {!Array<Node>} nodes
9823 * @return {Array<Node>} scrollables
9824 * @private
9825 */
9826 _getScrollableNodes: function(nodes) { 6832 _getScrollableNodes: function(nodes) {
9827 var scrollables = []; 6833 var scrollables = [];
9828 var lockingIndex = nodes.indexOf(this.currentLockingElement); 6834 var lockingIndex = nodes.indexOf(this.currentLockingElement);
9829 // Loop from root target to locking element (included).
9830 for (var i = 0; i <= lockingIndex; i++) { 6835 for (var i = 0; i <= lockingIndex; i++) {
9831 var node = nodes[i]; 6836 var node = nodes[i];
9832 // Skip document fragments.
9833 if (node.nodeType === 11) { 6837 if (node.nodeType === 11) {
9834 continue; 6838 continue;
9835 } 6839 }
9836 // Check inline style before checking computed style.
9837 var style = node.style; 6840 var style = node.style;
9838 if (style.overflow !== 'scroll' && style.overflow !== 'auto') { 6841 if (style.overflow !== 'scroll' && style.overflow !== 'auto') {
9839 style = window.getComputedStyle(node); 6842 style = window.getComputedStyle(node);
9840 } 6843 }
9841 if (style.overflow === 'scroll' || style.overflow === 'auto') { 6844 if (style.overflow === 'scroll' || style.overflow === 'auto') {
9842 scrollables.push(node); 6845 scrollables.push(node);
9843 } 6846 }
9844 } 6847 }
9845 return scrollables; 6848 return scrollables;
9846 }, 6849 },
9847 6850
9848 /**
9849 * Returns the node that is scrolling. If there is no scrolling,
9850 * returns undefined.
9851 * @param {!Array<Node>} nodes
9852 * @param {number} deltaX Scroll delta on the x-axis
9853 * @param {number} deltaY Scroll delta on the y-axis
9854 * @return {Node|undefined}
9855 * @private
9856 */
9857 _getScrollingNode: function(nodes, deltaX, deltaY) { 6851 _getScrollingNode: function(nodes, deltaX, deltaY) {
9858 // No scroll.
9859 if (!deltaX && !deltaY) { 6852 if (!deltaX && !deltaY) {
9860 return; 6853 return;
9861 } 6854 }
9862 // Check only one axis according to where there is more scroll.
9863 // Prefer vertical to horizontal.
9864 var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX); 6855 var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX);
9865 for (var i = 0; i < nodes.length; i++) { 6856 for (var i = 0; i < nodes.length; i++) {
9866 var node = nodes[i]; 6857 var node = nodes[i];
9867 var canScroll = false; 6858 var canScroll = false;
9868 if (verticalScroll) { 6859 if (verticalScroll) {
9869 // delta < 0 is scroll up, delta > 0 is scroll down.
9870 canScroll = deltaY < 0 ? node.scrollTop > 0 : 6860 canScroll = deltaY < 0 ? node.scrollTop > 0 :
9871 node.scrollTop < node.scrollHeight - node.clientHeight; 6861 node.scrollTop < node.scrollHeight - node.clientHeight;
9872 } else { 6862 } else {
9873 // delta < 0 is scroll left, delta > 0 is scroll right.
9874 canScroll = deltaX < 0 ? node.scrollLeft > 0 : 6863 canScroll = deltaX < 0 ? node.scrollLeft > 0 :
9875 node.scrollLeft < node.scrollWidth - node.clientWidth; 6864 node.scrollLeft < node.scrollWidth - node.clientWidth;
9876 } 6865 }
9877 if (canScroll) { 6866 if (canScroll) {
9878 return node; 6867 return node;
9879 } 6868 }
9880 } 6869 }
9881 }, 6870 },
9882 6871
9883 /**
9884 * Returns scroll `deltaX` and `deltaY`.
9885 * @param {!Event} event The scroll event
9886 * @return {{
9887 * deltaX: number The x-axis scroll delta (positive: scroll right,
9888 * negative: scroll left, 0: no scroll),
9889 * deltaY: number The y-axis scroll delta (positive: scroll down,
9890 * negative: scroll up, 0: no scroll)
9891 * }} info
9892 * @private
9893 */
9894 _getScrollInfo: function(event) { 6872 _getScrollInfo: function(event) {
9895 var info = { 6873 var info = {
9896 deltaX: event.deltaX, 6874 deltaX: event.deltaX,
9897 deltaY: event.deltaY 6875 deltaY: event.deltaY
9898 }; 6876 };
9899 // Already available.
9900 if ('deltaX' in event) { 6877 if ('deltaX' in event) {
9901 // do nothing, values are already good.
9902 } 6878 }
9903 // Safari has scroll info in `wheelDeltaX/Y`.
9904 else if ('wheelDeltaX' in event) { 6879 else if ('wheelDeltaX' in event) {
9905 info.deltaX = -event.wheelDeltaX; 6880 info.deltaX = -event.wheelDeltaX;
9906 info.deltaY = -event.wheelDeltaY; 6881 info.deltaY = -event.wheelDeltaY;
9907 } 6882 }
9908 // Firefox has scroll info in `detail` and `axis`.
9909 else if ('axis' in event) { 6883 else if ('axis' in event) {
9910 info.deltaX = event.axis === 1 ? event.detail : 0; 6884 info.deltaX = event.axis === 1 ? event.detail : 0;
9911 info.deltaY = event.axis === 2 ? event.detail : 0; 6885 info.deltaY = event.axis === 2 ? event.detail : 0;
9912 } 6886 }
9913 // On mobile devices, calculate scroll direction.
9914 else if (event.targetTouches) { 6887 else if (event.targetTouches) {
9915 var touch = event.targetTouches[0]; 6888 var touch = event.targetTouches[0];
9916 // Touch moves from right to left => scrolling goes right.
9917 info.deltaX = LAST_TOUCH_POSITION.pageX - touch.pageX; 6889 info.deltaX = LAST_TOUCH_POSITION.pageX - touch.pageX;
9918 // Touch moves from down to up => scrolling goes down.
9919 info.deltaY = LAST_TOUCH_POSITION.pageY - touch.pageY; 6890 info.deltaY = LAST_TOUCH_POSITION.pageY - touch.pageY;
9920 } 6891 }
9921 return info; 6892 return info;
9922 } 6893 }
9923 }; 6894 };
9924 })(); 6895 })();
9925 (function() { 6896 (function() {
9926 'use strict'; 6897 'use strict';
9927 6898
9928 Polymer({ 6899 Polymer({
9929 is: 'iron-dropdown', 6900 is: 'iron-dropdown',
9930 6901
9931 behaviors: [ 6902 behaviors: [
9932 Polymer.IronControlState, 6903 Polymer.IronControlState,
9933 Polymer.IronA11yKeysBehavior, 6904 Polymer.IronA11yKeysBehavior,
9934 Polymer.IronOverlayBehavior, 6905 Polymer.IronOverlayBehavior,
9935 Polymer.NeonAnimationRunnerBehavior 6906 Polymer.NeonAnimationRunnerBehavior
9936 ], 6907 ],
9937 6908
9938 properties: { 6909 properties: {
9939 /**
9940 * The orientation against which to align the dropdown content
9941 * horizontally relative to the dropdown trigger.
9942 * Overridden from `Polymer.IronFitBehavior`.
9943 */
9944 horizontalAlign: { 6910 horizontalAlign: {
9945 type: String, 6911 type: String,
9946 value: 'left', 6912 value: 'left',
9947 reflectToAttribute: true 6913 reflectToAttribute: true
9948 }, 6914 },
9949 6915
9950 /**
9951 * The orientation against which to align the dropdown content
9952 * vertically relative to the dropdown trigger.
9953 * Overridden from `Polymer.IronFitBehavior`.
9954 */
9955 verticalAlign: { 6916 verticalAlign: {
9956 type: String, 6917 type: String,
9957 value: 'top', 6918 value: 'top',
9958 reflectToAttribute: true 6919 reflectToAttribute: true
9959 }, 6920 },
9960 6921
9961 /**
9962 * An animation config. If provided, this will be used to animate the
9963 * opening of the dropdown.
9964 */
9965 openAnimationConfig: { 6922 openAnimationConfig: {
9966 type: Object 6923 type: Object
9967 }, 6924 },
9968 6925
9969 /**
9970 * An animation config. If provided, this will be used to animate the
9971 * closing of the dropdown.
9972 */
9973 closeAnimationConfig: { 6926 closeAnimationConfig: {
9974 type: Object 6927 type: Object
9975 }, 6928 },
9976 6929
9977 /**
9978 * If provided, this will be the element that will be focused when
9979 * the dropdown opens.
9980 */
9981 focusTarget: { 6930 focusTarget: {
9982 type: Object 6931 type: Object
9983 }, 6932 },
9984 6933
9985 /**
9986 * Set to true to disable animations when opening and closing the
9987 * dropdown.
9988 */
9989 noAnimations: { 6934 noAnimations: {
9990 type: Boolean, 6935 type: Boolean,
9991 value: false 6936 value: false
9992 }, 6937 },
9993 6938
9994 /**
9995 * By default, the dropdown will constrain scrolling on the page
9996 * to itself when opened.
9997 * Set to true in order to prevent scroll from being constrained
9998 * to the dropdown when it opens.
9999 */
10000 allowOutsideScroll: { 6939 allowOutsideScroll: {
10001 type: Boolean, 6940 type: Boolean,
10002 value: false 6941 value: false
10003 }, 6942 },
10004 6943
10005 /**
10006 * Callback for scroll events.
10007 * @type {Function}
10008 * @private
10009 */
10010 _boundOnCaptureScroll: { 6944 _boundOnCaptureScroll: {
10011 type: Function, 6945 type: Function,
10012 value: function() { 6946 value: function() {
10013 return this._onCaptureScroll.bind(this); 6947 return this._onCaptureScroll.bind(this);
10014 } 6948 }
10015 } 6949 }
10016 }, 6950 },
10017 6951
10018 listeners: { 6952 listeners: {
10019 'neon-animation-finish': '_onNeonAnimationFinish' 6953 'neon-animation-finish': '_onNeonAnimationFinish'
10020 }, 6954 },
10021 6955
10022 observers: [ 6956 observers: [
10023 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign , verticalOffset, horizontalOffset)' 6957 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign , verticalOffset, horizontalOffset)'
10024 ], 6958 ],
10025 6959
10026 /**
10027 * The element that is contained by the dropdown, if any.
10028 */
10029 get containedElement() { 6960 get containedElement() {
10030 return Polymer.dom(this.$.content).getDistributedNodes()[0]; 6961 return Polymer.dom(this.$.content).getDistributedNodes()[0];
10031 }, 6962 },
10032 6963
10033 /**
10034 * The element that should be focused when the dropdown opens.
10035 * @deprecated
10036 */
10037 get _focusTarget() { 6964 get _focusTarget() {
10038 return this.focusTarget || this.containedElement; 6965 return this.focusTarget || this.containedElement;
10039 }, 6966 },
10040 6967
10041 ready: function() { 6968 ready: function() {
10042 // Memoized scrolling position, used to block scrolling outside.
10043 this._scrollTop = 0; 6969 this._scrollTop = 0;
10044 this._scrollLeft = 0; 6970 this._scrollLeft = 0;
10045 // Used to perform a non-blocking refit on scroll.
10046 this._refitOnScrollRAF = null; 6971 this._refitOnScrollRAF = null;
10047 }, 6972 },
10048 6973
10049 detached: function() { 6974 detached: function() {
10050 this.cancelAnimation(); 6975 this.cancelAnimation();
10051 Polymer.IronDropdownScrollManager.removeScrollLock(this); 6976 Polymer.IronDropdownScrollManager.removeScrollLock(this);
10052 }, 6977 },
10053 6978
10054 /**
10055 * Called when the value of `opened` changes.
10056 * Overridden from `IronOverlayBehavior`
10057 */
10058 _openedChanged: function() { 6979 _openedChanged: function() {
10059 if (this.opened && this.disabled) { 6980 if (this.opened && this.disabled) {
10060 this.cancel(); 6981 this.cancel();
10061 } else { 6982 } else {
10062 this.cancelAnimation(); 6983 this.cancelAnimation();
10063 this.sizingTarget = this.containedElement || this.sizingTarget; 6984 this.sizingTarget = this.containedElement || this.sizingTarget;
10064 this._updateAnimationConfig(); 6985 this._updateAnimationConfig();
10065 this._saveScrollPosition(); 6986 this._saveScrollPosition();
10066 if (this.opened) { 6987 if (this.opened) {
10067 document.addEventListener('scroll', this._boundOnCaptureScroll); 6988 document.addEventListener('scroll', this._boundOnCaptureScroll);
10068 !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.push ScrollLock(this); 6989 !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.push ScrollLock(this);
10069 } else { 6990 } else {
10070 document.removeEventListener('scroll', this._boundOnCaptureScroll) ; 6991 document.removeEventListener('scroll', this._boundOnCaptureScroll) ;
10071 Polymer.IronDropdownScrollManager.removeScrollLock(this); 6992 Polymer.IronDropdownScrollManager.removeScrollLock(this);
10072 } 6993 }
10073 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments ); 6994 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments );
10074 } 6995 }
10075 }, 6996 },
10076 6997
10077 /**
10078 * Overridden from `IronOverlayBehavior`.
10079 */
10080 _renderOpened: function() { 6998 _renderOpened: function() {
10081 if (!this.noAnimations && this.animationConfig.open) { 6999 if (!this.noAnimations && this.animationConfig.open) {
10082 this.$.contentWrapper.classList.add('animating'); 7000 this.$.contentWrapper.classList.add('animating');
10083 this.playAnimation('open'); 7001 this.playAnimation('open');
10084 } else { 7002 } else {
10085 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments) ; 7003 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments) ;
10086 } 7004 }
10087 }, 7005 },
10088 7006
10089 /**
10090 * Overridden from `IronOverlayBehavior`.
10091 */
10092 _renderClosed: function() { 7007 _renderClosed: function() {
10093 7008
10094 if (!this.noAnimations && this.animationConfig.close) { 7009 if (!this.noAnimations && this.animationConfig.close) {
10095 this.$.contentWrapper.classList.add('animating'); 7010 this.$.contentWrapper.classList.add('animating');
10096 this.playAnimation('close'); 7011 this.playAnimation('close');
10097 } else { 7012 } else {
10098 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments) ; 7013 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments) ;
10099 } 7014 }
10100 }, 7015 },
10101 7016
10102 /**
10103 * Called when animation finishes on the dropdown (when opening or
10104 * closing). Responsible for "completing" the process of opening or
10105 * closing the dropdown by positioning it or setting its display to
10106 * none.
10107 */
10108 _onNeonAnimationFinish: function() { 7017 _onNeonAnimationFinish: function() {
10109 this.$.contentWrapper.classList.remove('animating'); 7018 this.$.contentWrapper.classList.remove('animating');
10110 if (this.opened) { 7019 if (this.opened) {
10111 this._finishRenderOpened(); 7020 this._finishRenderOpened();
10112 } else { 7021 } else {
10113 this._finishRenderClosed(); 7022 this._finishRenderClosed();
10114 } 7023 }
10115 }, 7024 },
10116 7025
10117 _onCaptureScroll: function() { 7026 _onCaptureScroll: function() {
10118 if (!this.allowOutsideScroll) { 7027 if (!this.allowOutsideScroll) {
10119 this._restoreScrollPosition(); 7028 this._restoreScrollPosition();
10120 } else { 7029 } else {
10121 this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnS crollRAF); 7030 this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnS crollRAF);
10122 this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bin d(this)); 7031 this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bin d(this));
10123 } 7032 }
10124 }, 7033 },
10125 7034
10126 /**
10127 * Memoizes the scroll position of the outside scrolling element.
10128 * @private
10129 */
10130 _saveScrollPosition: function() { 7035 _saveScrollPosition: function() {
10131 if (document.scrollingElement) { 7036 if (document.scrollingElement) {
10132 this._scrollTop = document.scrollingElement.scrollTop; 7037 this._scrollTop = document.scrollingElement.scrollTop;
10133 this._scrollLeft = document.scrollingElement.scrollLeft; 7038 this._scrollLeft = document.scrollingElement.scrollLeft;
10134 } else { 7039 } else {
10135 // Since we don't know if is the body or html, get max.
10136 this._scrollTop = Math.max(document.documentElement.scrollTop, docum ent.body.scrollTop); 7040 this._scrollTop = Math.max(document.documentElement.scrollTop, docum ent.body.scrollTop);
10137 this._scrollLeft = Math.max(document.documentElement.scrollLeft, doc ument.body.scrollLeft); 7041 this._scrollLeft = Math.max(document.documentElement.scrollLeft, doc ument.body.scrollLeft);
10138 } 7042 }
10139 }, 7043 },
10140 7044
10141 /**
10142 * Resets the scroll position of the outside scrolling element.
10143 * @private
10144 */
10145 _restoreScrollPosition: function() { 7045 _restoreScrollPosition: function() {
10146 if (document.scrollingElement) { 7046 if (document.scrollingElement) {
10147 document.scrollingElement.scrollTop = this._scrollTop; 7047 document.scrollingElement.scrollTop = this._scrollTop;
10148 document.scrollingElement.scrollLeft = this._scrollLeft; 7048 document.scrollingElement.scrollLeft = this._scrollLeft;
10149 } else { 7049 } else {
10150 // Since we don't know if is the body or html, set both.
10151 document.documentElement.scrollTop = this._scrollTop; 7050 document.documentElement.scrollTop = this._scrollTop;
10152 document.documentElement.scrollLeft = this._scrollLeft; 7051 document.documentElement.scrollLeft = this._scrollLeft;
10153 document.body.scrollTop = this._scrollTop; 7052 document.body.scrollTop = this._scrollTop;
10154 document.body.scrollLeft = this._scrollLeft; 7053 document.body.scrollLeft = this._scrollLeft;
10155 } 7054 }
10156 }, 7055 },
10157 7056
10158 /**
10159 * Constructs the final animation config from different properties used
10160 * to configure specific parts of the opening and closing animations.
10161 */
10162 _updateAnimationConfig: function() { 7057 _updateAnimationConfig: function() {
10163 var animations = (this.openAnimationConfig || []).concat(this.closeAni mationConfig || []); 7058 var animations = (this.openAnimationConfig || []).concat(this.closeAni mationConfig || []);
10164 for (var i = 0; i < animations.length; i++) { 7059 for (var i = 0; i < animations.length; i++) {
10165 animations[i].node = this.containedElement; 7060 animations[i].node = this.containedElement;
10166 } 7061 }
10167 this.animationConfig = { 7062 this.animationConfig = {
10168 open: this.openAnimationConfig, 7063 open: this.openAnimationConfig,
10169 close: this.closeAnimationConfig 7064 close: this.closeAnimationConfig
10170 }; 7065 };
10171 }, 7066 },
10172 7067
10173 /**
10174 * Updates the overlay position based on configured horizontal
10175 * and vertical alignment.
10176 */
10177 _updateOverlayPosition: function() { 7068 _updateOverlayPosition: function() {
10178 if (this.isAttached) { 7069 if (this.isAttached) {
10179 // This triggers iron-resize, and iron-overlay-behavior will call re fit if needed.
10180 this.notifyResize(); 7070 this.notifyResize();
10181 } 7071 }
10182 }, 7072 },
10183 7073
10184 /**
10185 * Apply focus to focusTarget or containedElement
10186 */
10187 _applyFocus: function () { 7074 _applyFocus: function () {
10188 var focusTarget = this.focusTarget || this.containedElement; 7075 var focusTarget = this.focusTarget || this.containedElement;
10189 if (focusTarget && this.opened && !this.noAutoFocus) { 7076 if (focusTarget && this.opened && !this.noAutoFocus) {
10190 focusTarget.focus(); 7077 focusTarget.focus();
10191 } else { 7078 } else {
10192 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); 7079 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments);
10193 } 7080 }
10194 } 7081 }
10195 }); 7082 });
10196 })(); 7083 })();
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
10326 'use strict'; 7213 'use strict';
10327 7214
10328 var config = { 7215 var config = {
10329 ANIMATION_CUBIC_BEZIER: 'cubic-bezier(.3,.95,.5,1)', 7216 ANIMATION_CUBIC_BEZIER: 'cubic-bezier(.3,.95,.5,1)',
10330 MAX_ANIMATION_TIME_MS: 400 7217 MAX_ANIMATION_TIME_MS: 400
10331 }; 7218 };
10332 7219
10333 var PaperMenuButton = Polymer({ 7220 var PaperMenuButton = Polymer({
10334 is: 'paper-menu-button', 7221 is: 'paper-menu-button',
10335 7222
10336 /**
10337 * Fired when the dropdown opens.
10338 *
10339 * @event paper-dropdown-open
10340 */
10341 7223
10342 /**
10343 * Fired when the dropdown closes.
10344 *
10345 * @event paper-dropdown-close
10346 */
10347 7224
10348 behaviors: [ 7225 behaviors: [
10349 Polymer.IronA11yKeysBehavior, 7226 Polymer.IronA11yKeysBehavior,
10350 Polymer.IronControlState 7227 Polymer.IronControlState
10351 ], 7228 ],
10352 7229
10353 properties: { 7230 properties: {
10354 /**
10355 * True if the content is currently displayed.
10356 */
10357 opened: { 7231 opened: {
10358 type: Boolean, 7232 type: Boolean,
10359 value: false, 7233 value: false,
10360 notify: true, 7234 notify: true,
10361 observer: '_openedChanged' 7235 observer: '_openedChanged'
10362 }, 7236 },
10363 7237
10364 /**
10365 * The orientation against which to align the menu dropdown
10366 * horizontally relative to the dropdown trigger.
10367 */
10368 horizontalAlign: { 7238 horizontalAlign: {
10369 type: String, 7239 type: String,
10370 value: 'left', 7240 value: 'left',
10371 reflectToAttribute: true 7241 reflectToAttribute: true
10372 }, 7242 },
10373 7243
10374 /**
10375 * The orientation against which to align the menu dropdown
10376 * vertically relative to the dropdown trigger.
10377 */
10378 verticalAlign: { 7244 verticalAlign: {
10379 type: String, 7245 type: String,
10380 value: 'top', 7246 value: 'top',
10381 reflectToAttribute: true 7247 reflectToAttribute: true
10382 }, 7248 },
10383 7249
10384 /**
10385 * If true, the `horizontalAlign` and `verticalAlign` properties will
10386 * be considered preferences instead of strict requirements when
10387 * positioning the dropdown and may be changed if doing so reduces
10388 * the area of the dropdown falling outside of `fitInto`.
10389 */
10390 dynamicAlign: { 7250 dynamicAlign: {
10391 type: Boolean 7251 type: Boolean
10392 }, 7252 },
10393 7253
10394 /**
10395 * A pixel value that will be added to the position calculated for the
10396 * given `horizontalAlign`. Use a negative value to offset to the
10397 * left, or a positive value to offset to the right.
10398 */
10399 horizontalOffset: { 7254 horizontalOffset: {
10400 type: Number, 7255 type: Number,
10401 value: 0, 7256 value: 0,
10402 notify: true 7257 notify: true
10403 }, 7258 },
10404 7259
10405 /**
10406 * A pixel value that will be added to the position calculated for the
10407 * given `verticalAlign`. Use a negative value to offset towards the
10408 * top, or a positive value to offset towards the bottom.
10409 */
10410 verticalOffset: { 7260 verticalOffset: {
10411 type: Number, 7261 type: Number,
10412 value: 0, 7262 value: 0,
10413 notify: true 7263 notify: true
10414 }, 7264 },
10415 7265
10416 /**
10417 * If true, the dropdown will be positioned so that it doesn't overlap
10418 * the button.
10419 */
10420 noOverlap: { 7266 noOverlap: {
10421 type: Boolean 7267 type: Boolean
10422 }, 7268 },
10423 7269
10424 /**
10425 * Set to true to disable animations when opening and closing the
10426 * dropdown.
10427 */
10428 noAnimations: { 7270 noAnimations: {
10429 type: Boolean, 7271 type: Boolean,
10430 value: false 7272 value: false
10431 }, 7273 },
10432 7274
10433 /**
10434 * Set to true to disable automatically closing the dropdown after
10435 * a selection has been made.
10436 */
10437 ignoreSelect: { 7275 ignoreSelect: {
10438 type: Boolean, 7276 type: Boolean,
10439 value: false 7277 value: false
10440 }, 7278 },
10441 7279
10442 /**
10443 * Set to true to enable automatically closing the dropdown after an
10444 * item has been activated, even if the selection did not change.
10445 */
10446 closeOnActivate: { 7280 closeOnActivate: {
10447 type: Boolean, 7281 type: Boolean,
10448 value: false 7282 value: false
10449 }, 7283 },
10450 7284
10451 /**
10452 * An animation config. If provided, this will be used to animate the
10453 * opening of the dropdown.
10454 */
10455 openAnimationConfig: { 7285 openAnimationConfig: {
10456 type: Object, 7286 type: Object,
10457 value: function() { 7287 value: function() {
10458 return [{ 7288 return [{
10459 name: 'fade-in-animation', 7289 name: 'fade-in-animation',
10460 timing: { 7290 timing: {
10461 delay: 100, 7291 delay: 100,
10462 duration: 200 7292 duration: 200
10463 } 7293 }
10464 }, { 7294 }, {
10465 name: 'paper-menu-grow-width-animation', 7295 name: 'paper-menu-grow-width-animation',
10466 timing: { 7296 timing: {
10467 delay: 100, 7297 delay: 100,
10468 duration: 150, 7298 duration: 150,
10469 easing: config.ANIMATION_CUBIC_BEZIER 7299 easing: config.ANIMATION_CUBIC_BEZIER
10470 } 7300 }
10471 }, { 7301 }, {
10472 name: 'paper-menu-grow-height-animation', 7302 name: 'paper-menu-grow-height-animation',
10473 timing: { 7303 timing: {
10474 delay: 100, 7304 delay: 100,
10475 duration: 275, 7305 duration: 275,
10476 easing: config.ANIMATION_CUBIC_BEZIER 7306 easing: config.ANIMATION_CUBIC_BEZIER
10477 } 7307 }
10478 }]; 7308 }];
10479 } 7309 }
10480 }, 7310 },
10481 7311
10482 /**
10483 * An animation config. If provided, this will be used to animate the
10484 * closing of the dropdown.
10485 */
10486 closeAnimationConfig: { 7312 closeAnimationConfig: {
10487 type: Object, 7313 type: Object,
10488 value: function() { 7314 value: function() {
10489 return [{ 7315 return [{
10490 name: 'fade-out-animation', 7316 name: 'fade-out-animation',
10491 timing: { 7317 timing: {
10492 duration: 150 7318 duration: 150
10493 } 7319 }
10494 }, { 7320 }, {
10495 name: 'paper-menu-shrink-width-animation', 7321 name: 'paper-menu-shrink-width-animation',
10496 timing: { 7322 timing: {
10497 delay: 100, 7323 delay: 100,
10498 duration: 50, 7324 duration: 50,
10499 easing: config.ANIMATION_CUBIC_BEZIER 7325 easing: config.ANIMATION_CUBIC_BEZIER
10500 } 7326 }
10501 }, { 7327 }, {
10502 name: 'paper-menu-shrink-height-animation', 7328 name: 'paper-menu-shrink-height-animation',
10503 timing: { 7329 timing: {
10504 duration: 200, 7330 duration: 200,
10505 easing: 'ease-in' 7331 easing: 'ease-in'
10506 } 7332 }
10507 }]; 7333 }];
10508 } 7334 }
10509 }, 7335 },
10510 7336
10511 /**
10512 * By default, the dropdown will constrain scrolling on the page
10513 * to itself when opened.
10514 * Set to true in order to prevent scroll from being constrained
10515 * to the dropdown when it opens.
10516 */
10517 allowOutsideScroll: { 7337 allowOutsideScroll: {
10518 type: Boolean, 7338 type: Boolean,
10519 value: false 7339 value: false
10520 }, 7340 },
10521 7341
10522 /**
10523 * Whether focus should be restored to the button when the menu closes .
10524 */
10525 restoreFocusOnClose: { 7342 restoreFocusOnClose: {
10526 type: Boolean, 7343 type: Boolean,
10527 value: true 7344 value: true
10528 }, 7345 },
10529 7346
10530 /**
10531 * This is the element intended to be bound as the focus target
10532 * for the `iron-dropdown` contained by `paper-menu-button`.
10533 */
10534 _dropdownContent: { 7347 _dropdownContent: {
10535 type: Object 7348 type: Object
10536 } 7349 }
10537 }, 7350 },
10538 7351
10539 hostAttributes: { 7352 hostAttributes: {
10540 role: 'group', 7353 role: 'group',
10541 'aria-haspopup': 'true' 7354 'aria-haspopup': 'true'
10542 }, 7355 },
10543 7356
10544 listeners: { 7357 listeners: {
10545 'iron-activate': '_onIronActivate', 7358 'iron-activate': '_onIronActivate',
10546 'iron-select': '_onIronSelect' 7359 'iron-select': '_onIronSelect'
10547 }, 7360 },
10548 7361
10549 /**
10550 * The content element that is contained by the menu button, if any.
10551 */
10552 get contentElement() { 7362 get contentElement() {
10553 return Polymer.dom(this.$.content).getDistributedNodes()[0]; 7363 return Polymer.dom(this.$.content).getDistributedNodes()[0];
10554 }, 7364 },
10555 7365
10556 /**
10557 * Toggles the drowpdown content between opened and closed.
10558 */
10559 toggle: function() { 7366 toggle: function() {
10560 if (this.opened) { 7367 if (this.opened) {
10561 this.close(); 7368 this.close();
10562 } else { 7369 } else {
10563 this.open(); 7370 this.open();
10564 } 7371 }
10565 }, 7372 },
10566 7373
10567 /**
10568 * Make the dropdown content appear as an overlay positioned relative
10569 * to the dropdown trigger.
10570 */
10571 open: function() { 7374 open: function() {
10572 if (this.disabled) { 7375 if (this.disabled) {
10573 return; 7376 return;
10574 } 7377 }
10575 7378
10576 this.$.dropdown.open(); 7379 this.$.dropdown.open();
10577 }, 7380 },
10578 7381
10579 /**
10580 * Hide the dropdown content.
10581 */
10582 close: function() { 7382 close: function() {
10583 this.$.dropdown.close(); 7383 this.$.dropdown.close();
10584 }, 7384 },
10585 7385
10586 /**
10587 * When an `iron-select` event is received, the dropdown should
10588 * automatically close on the assumption that a value has been chosen.
10589 *
10590 * @param {CustomEvent} event A CustomEvent instance with type
10591 * set to `"iron-select"`.
10592 */
10593 _onIronSelect: function(event) { 7386 _onIronSelect: function(event) {
10594 if (!this.ignoreSelect) { 7387 if (!this.ignoreSelect) {
10595 this.close(); 7388 this.close();
10596 } 7389 }
10597 }, 7390 },
10598 7391
10599 /**
10600 * Closes the dropdown when an `iron-activate` event is received if
10601 * `closeOnActivate` is true.
10602 *
10603 * @param {CustomEvent} event A CustomEvent of type 'iron-activate'.
10604 */
10605 _onIronActivate: function(event) { 7392 _onIronActivate: function(event) {
10606 if (this.closeOnActivate) { 7393 if (this.closeOnActivate) {
10607 this.close(); 7394 this.close();
10608 } 7395 }
10609 }, 7396 },
10610 7397
10611 /**
10612 * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
10613 * When the dropdown closes, the `paper-menu-button` fires `paper-close` .
10614 *
10615 * @param {boolean} opened True if the dropdown is opened, otherwise fal se.
10616 * @param {boolean} oldOpened The previous value of `opened`.
10617 */
10618 _openedChanged: function(opened, oldOpened) { 7398 _openedChanged: function(opened, oldOpened) {
10619 if (opened) { 7399 if (opened) {
10620 // TODO(cdata): Update this when we can measure changes in distribut ed
10621 // children in an idiomatic way.
10622 // We poke this property in case the element has changed. This will
10623 // cause the focus target for the `iron-dropdown` to be updated as
10624 // necessary:
10625 this._dropdownContent = this.contentElement; 7400 this._dropdownContent = this.contentElement;
10626 this.fire('paper-dropdown-open'); 7401 this.fire('paper-dropdown-open');
10627 } else if (oldOpened != null) { 7402 } else if (oldOpened != null) {
10628 this.fire('paper-dropdown-close'); 7403 this.fire('paper-dropdown-close');
10629 } 7404 }
10630 }, 7405 },
10631 7406
10632 /**
10633 * If the dropdown is open when disabled becomes true, close the
10634 * dropdown.
10635 *
10636 * @param {boolean} disabled True if disabled, otherwise false.
10637 */
10638 _disabledChanged: function(disabled) { 7407 _disabledChanged: function(disabled) {
10639 Polymer.IronControlState._disabledChanged.apply(this, arguments); 7408 Polymer.IronControlState._disabledChanged.apply(this, arguments);
10640 if (disabled && this.opened) { 7409 if (disabled && this.opened) {
10641 this.close(); 7410 this.close();
10642 } 7411 }
10643 }, 7412 },
10644 7413
10645 __onIronOverlayCanceled: function(event) { 7414 __onIronOverlayCanceled: function(event) {
10646 var uiEvent = event.detail; 7415 var uiEvent = event.detail;
10647 var target = Polymer.dom(uiEvent).rootTarget; 7416 var target = Polymer.dom(uiEvent).rootTarget;
10648 var trigger = this.$.trigger; 7417 var trigger = this.$.trigger;
10649 var path = Polymer.dom(uiEvent).path; 7418 var path = Polymer.dom(uiEvent).path;
10650 7419
10651 if (path.indexOf(trigger) > -1) { 7420 if (path.indexOf(trigger) > -1) {
10652 event.preventDefault(); 7421 event.preventDefault();
10653 } 7422 }
10654 } 7423 }
10655 }); 7424 });
10656 7425
10657 Object.keys(config).forEach(function (key) { 7426 Object.keys(config).forEach(function (key) {
10658 PaperMenuButton[key] = config[key]; 7427 PaperMenuButton[key] = config[key];
10659 }); 7428 });
10660 7429
10661 Polymer.PaperMenuButton = PaperMenuButton; 7430 Polymer.PaperMenuButton = PaperMenuButton;
10662 })(); 7431 })();
10663 /**
10664 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k eyboard focus.
10665 *
10666 * @polymerBehavior Polymer.PaperInkyFocusBehavior
10667 */
10668 Polymer.PaperInkyFocusBehaviorImpl = { 7432 Polymer.PaperInkyFocusBehaviorImpl = {
10669 observers: [ 7433 observers: [
10670 '_focusedChanged(receivedFocusFromKeyboard)' 7434 '_focusedChanged(receivedFocusFromKeyboard)'
10671 ], 7435 ],
10672 7436
10673 _focusedChanged: function(receivedFocusFromKeyboard) { 7437 _focusedChanged: function(receivedFocusFromKeyboard) {
10674 if (receivedFocusFromKeyboard) { 7438 if (receivedFocusFromKeyboard) {
10675 this.ensureRipple(); 7439 this.ensureRipple();
10676 } 7440 }
10677 if (this.hasRipple()) { 7441 if (this.hasRipple()) {
(...skipping 23 matching lines...) Expand all
10701 hostAttributes: { 7465 hostAttributes: {
10702 role: 'button', 7466 role: 'button',
10703 tabindex: '0' 7467 tabindex: '0'
10704 }, 7468 },
10705 7469
10706 behaviors: [ 7470 behaviors: [
10707 Polymer.PaperInkyFocusBehavior 7471 Polymer.PaperInkyFocusBehavior
10708 ], 7472 ],
10709 7473
10710 properties: { 7474 properties: {
10711 /**
10712 * The URL of an image for the icon. If the src property is specified,
10713 * the icon property should not be.
10714 */
10715 src: { 7475 src: {
10716 type: String 7476 type: String
10717 }, 7477 },
10718 7478
10719 /**
10720 * Specifies the icon name or index in the set of icons available in
10721 * the icon's icon set. If the icon property is specified,
10722 * the src property should not be.
10723 */
10724 icon: { 7479 icon: {
10725 type: String 7480 type: String
10726 }, 7481 },
10727 7482
10728 /**
10729 * Specifies the alternate text for the button, for accessibility.
10730 */
10731 alt: { 7483 alt: {
10732 type: String, 7484 type: String,
10733 observer: "_altChanged" 7485 observer: "_altChanged"
10734 } 7486 }
10735 }, 7487 },
10736 7488
10737 _altChanged: function(newValue, oldValue) { 7489 _altChanged: function(newValue, oldValue) {
10738 var label = this.getAttribute('aria-label'); 7490 var label = this.getAttribute('aria-label');
10739 7491
10740 // Don't stomp over a user-set aria-label.
10741 if (!label || oldValue == label) { 7492 if (!label || oldValue == label) {
10742 this.setAttribute('aria-label', newValue); 7493 this.setAttribute('aria-label', newValue);
10743 } 7494 }
10744 } 7495 }
10745 }); 7496 });
10746 // Copyright 2016 The Chromium Authors. All rights reserved. 7497 // Copyright 2016 The Chromium Authors. All rights reserved.
10747 // Use of this source code is governed by a BSD-style license that can be 7498 // Use of this source code is governed by a BSD-style license that can be
10748 // found in the LICENSE file. 7499 // found in the LICENSE file.
10749 7500
10750 /**
10751 * Implements an incremental search field which can be shown and hidden.
10752 * Canonical implementation is <cr-search-field>.
10753 * @polymerBehavior
10754 */
10755 var CrSearchFieldBehavior = { 7501 var CrSearchFieldBehavior = {
10756 properties: { 7502 properties: {
10757 label: { 7503 label: {
10758 type: String, 7504 type: String,
10759 value: '', 7505 value: '',
10760 }, 7506 },
10761 7507
10762 clearLabel: { 7508 clearLabel: {
10763 type: String, 7509 type: String,
10764 value: '', 7510 value: '',
10765 }, 7511 },
10766 7512
10767 showingSearch: { 7513 showingSearch: {
10768 type: Boolean, 7514 type: Boolean,
10769 value: false, 7515 value: false,
10770 notify: true, 7516 notify: true,
10771 observer: 'showingSearchChanged_', 7517 observer: 'showingSearchChanged_',
10772 reflectToAttribute: true 7518 reflectToAttribute: true
10773 }, 7519 },
10774 7520
10775 /** @private */ 7521 /** @private */
10776 lastValue_: { 7522 lastValue_: {
10777 type: String, 7523 type: String,
10778 value: '', 7524 value: '',
10779 }, 7525 },
10780 }, 7526 },
10781 7527
10782 /**
10783 * @abstract
10784 * @return {!HTMLInputElement} The input field element the behavior should
10785 * use.
10786 */
10787 getSearchInput: function() {}, 7528 getSearchInput: function() {},
10788 7529
10789 /**
10790 * @return {string} The value of the search field.
10791 */
10792 getValue: function() { 7530 getValue: function() {
10793 return this.getSearchInput().value; 7531 return this.getSearchInput().value;
10794 }, 7532 },
10795 7533
10796 /**
10797 * Sets the value of the search field.
10798 * @param {string} value
10799 */
10800 setValue: function(value) { 7534 setValue: function(value) {
10801 // Use bindValue when setting the input value so that changes propagate
10802 // correctly.
10803 this.getSearchInput().bindValue = value; 7535 this.getSearchInput().bindValue = value;
10804 this.onValueChanged_(value); 7536 this.onValueChanged_(value);
10805 }, 7537 },
10806 7538
10807 showAndFocus: function() { 7539 showAndFocus: function() {
10808 this.showingSearch = true; 7540 this.showingSearch = true;
10809 this.focus_(); 7541 this.focus_();
10810 }, 7542 },
10811 7543
10812 /** @private */ 7544 /** @private */
10813 focus_: function() { 7545 focus_: function() {
10814 this.getSearchInput().focus(); 7546 this.getSearchInput().focus();
10815 }, 7547 },
10816 7548
10817 onSearchTermSearch: function() { 7549 onSearchTermSearch: function() {
10818 this.onValueChanged_(this.getValue()); 7550 this.onValueChanged_(this.getValue());
10819 }, 7551 },
10820 7552
10821 /**
10822 * Updates the internal state of the search field based on a change that has
10823 * already happened.
10824 * @param {string} newValue
10825 * @private
10826 */
10827 onValueChanged_: function(newValue) { 7553 onValueChanged_: function(newValue) {
10828 if (newValue == this.lastValue_) 7554 if (newValue == this.lastValue_)
10829 return; 7555 return;
10830 7556
10831 this.fire('search-changed', newValue); 7557 this.fire('search-changed', newValue);
10832 this.lastValue_ = newValue; 7558 this.lastValue_ = newValue;
10833 }, 7559 },
10834 7560
10835 onSearchTermKeydown: function(e) { 7561 onSearchTermKeydown: function(e) {
10836 if (e.key == 'Escape') 7562 if (e.key == 'Escape')
(...skipping 12 matching lines...) Expand all
10849 } 7575 }
10850 }; 7576 };
10851 (function() { 7577 (function() {
10852 'use strict'; 7578 'use strict';
10853 7579
10854 Polymer.IronA11yAnnouncer = Polymer({ 7580 Polymer.IronA11yAnnouncer = Polymer({
10855 is: 'iron-a11y-announcer', 7581 is: 'iron-a11y-announcer',
10856 7582
10857 properties: { 7583 properties: {
10858 7584
10859 /**
10860 * The value of mode is used to set the `aria-live` attribute
10861 * for the element that will be announced. Valid values are: `off`,
10862 * `polite` and `assertive`.
10863 */
10864 mode: { 7585 mode: {
10865 type: String, 7586 type: String,
10866 value: 'polite' 7587 value: 'polite'
10867 }, 7588 },
10868 7589
10869 _text: { 7590 _text: {
10870 type: String, 7591 type: String,
10871 value: '' 7592 value: ''
10872 } 7593 }
10873 }, 7594 },
10874 7595
10875 created: function() { 7596 created: function() {
10876 if (!Polymer.IronA11yAnnouncer.instance) { 7597 if (!Polymer.IronA11yAnnouncer.instance) {
10877 Polymer.IronA11yAnnouncer.instance = this; 7598 Polymer.IronA11yAnnouncer.instance = this;
10878 } 7599 }
10879 7600
10880 document.body.addEventListener('iron-announce', this._onIronAnnounce.b ind(this)); 7601 document.body.addEventListener('iron-announce', this._onIronAnnounce.b ind(this));
10881 }, 7602 },
10882 7603
10883 /**
10884 * Cause a text string to be announced by screen readers.
10885 *
10886 * @param {string} text The text that should be announced.
10887 */
10888 announce: function(text) { 7604 announce: function(text) {
10889 this._text = ''; 7605 this._text = '';
10890 this.async(function() { 7606 this.async(function() {
10891 this._text = text; 7607 this._text = text;
10892 }, 100); 7608 }, 100);
10893 }, 7609 },
10894 7610
10895 _onIronAnnounce: function(event) { 7611 _onIronAnnounce: function(event) {
10896 if (event.detail && event.detail.text) { 7612 if (event.detail && event.detail.text) {
10897 this.announce(event.detail.text); 7613 this.announce(event.detail.text);
10898 } 7614 }
10899 } 7615 }
10900 }); 7616 });
10901 7617
10902 Polymer.IronA11yAnnouncer.instance = null; 7618 Polymer.IronA11yAnnouncer.instance = null;
10903 7619
10904 Polymer.IronA11yAnnouncer.requestAvailability = function() { 7620 Polymer.IronA11yAnnouncer.requestAvailability = function() {
10905 if (!Polymer.IronA11yAnnouncer.instance) { 7621 if (!Polymer.IronA11yAnnouncer.instance) {
10906 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y -announcer'); 7622 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y -announcer');
10907 } 7623 }
10908 7624
10909 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); 7625 document.body.appendChild(Polymer.IronA11yAnnouncer.instance);
10910 }; 7626 };
10911 })(); 7627 })();
10912 /**
10913 * Singleton IronMeta instance.
10914 */
10915 Polymer.IronValidatableBehaviorMeta = null; 7628 Polymer.IronValidatableBehaviorMeta = null;
10916 7629
10917 /**
10918 * `Use Polymer.IronValidatableBehavior` to implement an element that validate s user input.
10919 * Use the related `Polymer.IronValidatorBehavior` to add custom validation lo gic to an iron-input.
10920 *
10921 * By default, an `<iron-form>` element validates its fields when the user pre sses the submit button.
10922 * To validate a form imperatively, call the form's `validate()` method, which in turn will
10923 * call `validate()` on all its children. By using `Polymer.IronValidatableBeh avior`, your
10924 * custom element will get a public `validate()`, which
10925 * will return the validity of the element, and a corresponding `invalid` attr ibute,
10926 * which can be used for styling.
10927 *
10928 * To implement the custom validation logic of your element, you must override
10929 * the protected `_getValidity()` method of this behaviour, rather than `valid ate()`.
10930 * See [this](https://github.com/PolymerElements/iron-form/blob/master/demo/si mple-element.html)
10931 * for an example.
10932 *
10933 * ### Accessibility
10934 *
10935 * Changing the `invalid` property, either manually or by calling `validate()` will update the
10936 * `aria-invalid` attribute.
10937 *
10938 * @demo demo/index.html
10939 * @polymerBehavior
10940 */
10941 Polymer.IronValidatableBehavior = { 7630 Polymer.IronValidatableBehavior = {
10942 7631
10943 properties: { 7632 properties: {
10944 7633
10945 /**
10946 * Name of the validator to use.
10947 */
10948 validator: { 7634 validator: {
10949 type: String 7635 type: String
10950 }, 7636 },
10951 7637
10952 /**
10953 * True if the last call to `validate` is invalid.
10954 */
10955 invalid: { 7638 invalid: {
10956 notify: true, 7639 notify: true,
10957 reflectToAttribute: true, 7640 reflectToAttribute: true,
10958 type: Boolean, 7641 type: Boolean,
10959 value: false 7642 value: false
10960 }, 7643 },
10961 7644
10962 /**
10963 * This property is deprecated and should not be used. Use the global
10964 * validator meta singleton, `Polymer.IronValidatableBehaviorMeta` instead .
10965 */
10966 _validatorMeta: { 7645 _validatorMeta: {
10967 type: Object 7646 type: Object
10968 }, 7647 },
10969 7648
10970 /**
10971 * Namespace for this validator. This property is deprecated and should
10972 * not be used. For all intents and purposes, please consider it a
10973 * read-only, config-time property.
10974 */
10975 validatorType: { 7649 validatorType: {
10976 type: String, 7650 type: String,
10977 value: 'validator' 7651 value: 'validator'
10978 }, 7652 },
10979 7653
10980 _validator: { 7654 _validator: {
10981 type: Object, 7655 type: Object,
10982 computed: '__computeValidator(validator)' 7656 computed: '__computeValidator(validator)'
10983 } 7657 }
10984 }, 7658 },
10985 7659
10986 observers: [ 7660 observers: [
10987 '_invalidChanged(invalid)' 7661 '_invalidChanged(invalid)'
10988 ], 7662 ],
10989 7663
10990 registered: function() { 7664 registered: function() {
10991 Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validat or'}); 7665 Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validat or'});
10992 }, 7666 },
10993 7667
10994 _invalidChanged: function() { 7668 _invalidChanged: function() {
10995 if (this.invalid) { 7669 if (this.invalid) {
10996 this.setAttribute('aria-invalid', 'true'); 7670 this.setAttribute('aria-invalid', 'true');
10997 } else { 7671 } else {
10998 this.removeAttribute('aria-invalid'); 7672 this.removeAttribute('aria-invalid');
10999 } 7673 }
11000 }, 7674 },
11001 7675
11002 /**
11003 * @return {boolean} True if the validator `validator` exists.
11004 */
11005 hasValidator: function() { 7676 hasValidator: function() {
11006 return this._validator != null; 7677 return this._validator != null;
11007 }, 7678 },
11008 7679
11009 /**
11010 * Returns true if the `value` is valid, and updates `invalid`. If you want
11011 * your element to have custom validation logic, do not override this method ;
11012 * override `_getValidity(value)` instead.
11013
11014 * @param {Object} value The value to be validated. By default, it is passed
11015 * to the validator's `validate()` function, if a validator is set.
11016 * @return {boolean} True if `value` is valid.
11017 */
11018 validate: function(value) { 7680 validate: function(value) {
11019 this.invalid = !this._getValidity(value); 7681 this.invalid = !this._getValidity(value);
11020 return !this.invalid; 7682 return !this.invalid;
11021 }, 7683 },
11022 7684
11023 /**
11024 * Returns true if `value` is valid. By default, it is passed
11025 * to the validator's `validate()` function, if a validator is set. You
11026 * should override this method if you want to implement custom validity
11027 * logic for your element.
11028 *
11029 * @param {Object} value The value to be validated.
11030 * @return {boolean} True if `value` is valid.
11031 */
11032 7685
11033 _getValidity: function(value) { 7686 _getValidity: function(value) {
11034 if (this.hasValidator()) { 7687 if (this.hasValidator()) {
11035 return this._validator.validate(value); 7688 return this._validator.validate(value);
11036 } 7689 }
11037 return true; 7690 return true;
11038 }, 7691 },
11039 7692
11040 __computeValidator: function() { 7693 __computeValidator: function() {
11041 return Polymer.IronValidatableBehaviorMeta && 7694 return Polymer.IronValidatableBehaviorMeta &&
11042 Polymer.IronValidatableBehaviorMeta.byKey(this.validator); 7695 Polymer.IronValidatableBehaviorMeta.byKey(this.validator);
11043 } 7696 }
11044 }; 7697 };
11045 /*
11046 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal idatorBehavior`
11047 to `<input>`.
11048
11049 ### Two-way binding
11050
11051 By default you can only get notified of changes to an `input`'s `value` due to u ser input:
11052
11053 <input value="{{myValue::input}}">
11054
11055 `iron-input` adds the `bind-value` property that mirrors the `value` property, a nd can be used
11056 for two-way data binding. `bind-value` will notify if it is changed either by us er input or by script.
11057
11058 <input is="iron-input" bind-value="{{myValue}}">
11059
11060 ### Custom validators
11061
11062 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit h `<iron-input>`.
11063
11064 <input is="iron-input" validator="my-custom-validator">
11065
11066 ### Stopping invalid input
11067
11068 It may be desirable to only allow users to enter certain characters. You can use the
11069 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature
11070 is separate from validation, and `allowed-pattern` does not affect how the input is validated.
11071
11072 \x3c!-- only allow characters that match [0-9] --\x3e
11073 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]">
11074
11075 @hero hero.svg
11076 @demo demo/index.html
11077 */
11078 7698
11079 Polymer({ 7699 Polymer({
11080 7700
11081 is: 'iron-input', 7701 is: 'iron-input',
11082 7702
11083 extends: 'input', 7703 extends: 'input',
11084 7704
11085 behaviors: [ 7705 behaviors: [
11086 Polymer.IronValidatableBehavior 7706 Polymer.IronValidatableBehavior
11087 ], 7707 ],
11088 7708
11089 properties: { 7709 properties: {
11090 7710
11091 /**
11092 * Use this property instead of `value` for two-way data binding.
11093 */
11094 bindValue: { 7711 bindValue: {
11095 observer: '_bindValueChanged', 7712 observer: '_bindValueChanged',
11096 type: String 7713 type: String
11097 }, 7714 },
11098 7715
11099 /**
11100 * Set to true to prevent the user from entering invalid input. If `allowe dPattern` is set,
11101 * any character typed by the user will be matched against that pattern, a nd rejected if it's not a match.
11102 * Pasted input will have each character checked individually; if any char acter
11103 * doesn't match `allowedPattern`, the entire pasted string will be reject ed.
11104 * If `allowedPattern` is not set, it will use the `type` attribute (only supported for `type=number`).
11105 */
11106 preventInvalidInput: { 7716 preventInvalidInput: {
11107 type: Boolean 7717 type: Boolean
11108 }, 7718 },
11109 7719
11110 /**
11111 * Regular expression that list the characters allowed as input.
11112 * This pattern represents the allowed characters for the field; as the us er inputs text,
11113 * each individual character will be checked against the pattern (rather t han checking
11114 * the entire value as a whole). The recommended format should be a list o f allowed characters;
11115 * for example, `[a-zA-Z0-9.+-!;:]`
11116 */
11117 allowedPattern: { 7720 allowedPattern: {
11118 type: String, 7721 type: String,
11119 observer: "_allowedPatternChanged" 7722 observer: "_allowedPatternChanged"
11120 }, 7723 },
11121 7724
11122 _previousValidInput: { 7725 _previousValidInput: {
11123 type: String, 7726 type: String,
11124 value: '' 7727 value: ''
11125 }, 7728 },
11126 7729
11127 _patternAlreadyChecked: { 7730 _patternAlreadyChecked: {
11128 type: Boolean, 7731 type: Boolean,
11129 value: false 7732 value: false
11130 } 7733 }
11131 7734
11132 }, 7735 },
11133 7736
11134 listeners: { 7737 listeners: {
11135 'input': '_onInput', 7738 'input': '_onInput',
11136 'keypress': '_onKeypress' 7739 'keypress': '_onKeypress'
11137 }, 7740 },
11138 7741
11139 /** @suppress {checkTypes} */ 7742 /** @suppress {checkTypes} */
11140 registered: function() { 7743 registered: function() {
11141 // Feature detect whether we need to patch dispatchEvent (i.e. on FF and I E).
11142 if (!this._canDispatchEventOnDisabled()) { 7744 if (!this._canDispatchEventOnDisabled()) {
11143 this._origDispatchEvent = this.dispatchEvent; 7745 this._origDispatchEvent = this.dispatchEvent;
11144 this.dispatchEvent = this._dispatchEventFirefoxIE; 7746 this.dispatchEvent = this._dispatchEventFirefoxIE;
11145 } 7747 }
11146 }, 7748 },
11147 7749
11148 created: function() { 7750 created: function() {
11149 Polymer.IronA11yAnnouncer.requestAvailability(); 7751 Polymer.IronA11yAnnouncer.requestAvailability();
11150 }, 7752 },
11151 7753
11152 _canDispatchEventOnDisabled: function() { 7754 _canDispatchEventOnDisabled: function() {
11153 var input = document.createElement('input'); 7755 var input = document.createElement('input');
11154 var canDispatch = false; 7756 var canDispatch = false;
11155 input.disabled = true; 7757 input.disabled = true;
11156 7758
11157 input.addEventListener('feature-check-dispatch-event', function() { 7759 input.addEventListener('feature-check-dispatch-event', function() {
11158 canDispatch = true; 7760 canDispatch = true;
11159 }); 7761 });
11160 7762
11161 try { 7763 try {
11162 input.dispatchEvent(new Event('feature-check-dispatch-event')); 7764 input.dispatchEvent(new Event('feature-check-dispatch-event'));
11163 } catch(e) {} 7765 } catch(e) {}
11164 7766
11165 return canDispatch; 7767 return canDispatch;
11166 }, 7768 },
11167 7769
11168 _dispatchEventFirefoxIE: function() { 7770 _dispatchEventFirefoxIE: function() {
11169 // Due to Firefox bug, events fired on disabled form controls can throw
11170 // errors; furthermore, neither IE nor Firefox will actually dispatch
11171 // events from disabled form controls; as such, we toggle disable around
11172 // the dispatch to allow notifying properties to notify
11173 // See issue #47 for details
11174 var disabled = this.disabled; 7771 var disabled = this.disabled;
11175 this.disabled = false; 7772 this.disabled = false;
11176 this._origDispatchEvent.apply(this, arguments); 7773 this._origDispatchEvent.apply(this, arguments);
11177 this.disabled = disabled; 7774 this.disabled = disabled;
11178 }, 7775 },
11179 7776
11180 get _patternRegExp() { 7777 get _patternRegExp() {
11181 var pattern; 7778 var pattern;
11182 if (this.allowedPattern) { 7779 if (this.allowedPattern) {
11183 pattern = new RegExp(this.allowedPattern); 7780 pattern = new RegExp(this.allowedPattern);
11184 } else { 7781 } else {
11185 switch (this.type) { 7782 switch (this.type) {
11186 case 'number': 7783 case 'number':
11187 pattern = /[0-9.,e-]/; 7784 pattern = /[0-9.,e-]/;
11188 break; 7785 break;
11189 } 7786 }
11190 } 7787 }
11191 return pattern; 7788 return pattern;
11192 }, 7789 },
11193 7790
11194 ready: function() { 7791 ready: function() {
11195 this.bindValue = this.value; 7792 this.bindValue = this.value;
11196 }, 7793 },
11197 7794
11198 /**
11199 * @suppress {checkTypes}
11200 */
11201 _bindValueChanged: function() { 7795 _bindValueChanged: function() {
11202 if (this.value !== this.bindValue) { 7796 if (this.value !== this.bindValue) {
11203 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue === false) ? '' : this.bindValue; 7797 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue === false) ? '' : this.bindValue;
11204 } 7798 }
11205 // manually notify because we don't want to notify until after setting val ue
11206 this.fire('bind-value-changed', {value: this.bindValue}); 7799 this.fire('bind-value-changed', {value: this.bindValue});
11207 }, 7800 },
11208 7801
11209 _allowedPatternChanged: function() { 7802 _allowedPatternChanged: function() {
11210 // Force to prevent invalid input when an `allowed-pattern` is set
11211 this.preventInvalidInput = this.allowedPattern ? true : false; 7803 this.preventInvalidInput = this.allowedPattern ? true : false;
11212 }, 7804 },
11213 7805
11214 _onInput: function() { 7806 _onInput: function() {
11215 // Need to validate each of the characters pasted if they haven't
11216 // been validated inside `_onKeypress` already.
11217 if (this.preventInvalidInput && !this._patternAlreadyChecked) { 7807 if (this.preventInvalidInput && !this._patternAlreadyChecked) {
11218 var valid = this._checkPatternValidity(); 7808 var valid = this._checkPatternValidity();
11219 if (!valid) { 7809 if (!valid) {
11220 this._announceInvalidCharacter('Invalid string of characters not enter ed.'); 7810 this._announceInvalidCharacter('Invalid string of characters not enter ed.');
11221 this.value = this._previousValidInput; 7811 this.value = this._previousValidInput;
11222 } 7812 }
11223 } 7813 }
11224 7814
11225 this.bindValue = this.value; 7815 this.bindValue = this.value;
11226 this._previousValidInput = this.value; 7816 this._previousValidInput = this.value;
11227 this._patternAlreadyChecked = false; 7817 this._patternAlreadyChecked = false;
11228 }, 7818 },
11229 7819
11230 _isPrintable: function(event) { 7820 _isPrintable: function(event) {
11231 // What a control/printable character is varies wildly based on the browse r.
11232 // - most control characters (arrows, backspace) do not send a `keypress` event
11233 // in Chrome, but the *do* on Firefox
11234 // - in Firefox, when they do send a `keypress` event, control chars have
11235 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
11236 // - printable characters always send a keypress event.
11237 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
11238 // always matches the charCode.
11239 // None of this makes any sense.
11240 7821
11241 // For these keys, ASCII code == browser keycode.
11242 var anyNonPrintable = 7822 var anyNonPrintable =
11243 (event.keyCode == 8) || // backspace 7823 (event.keyCode == 8) || // backspace
11244 (event.keyCode == 9) || // tab 7824 (event.keyCode == 9) || // tab
11245 (event.keyCode == 13) || // enter 7825 (event.keyCode == 13) || // enter
11246 (event.keyCode == 27); // escape 7826 (event.keyCode == 27); // escape
11247 7827
11248 // For these keys, make sure it's a browser keycode and not an ASCII code.
11249 var mozNonPrintable = 7828 var mozNonPrintable =
11250 (event.keyCode == 19) || // pause 7829 (event.keyCode == 19) || // pause
11251 (event.keyCode == 20) || // caps lock 7830 (event.keyCode == 20) || // caps lock
11252 (event.keyCode == 45) || // insert 7831 (event.keyCode == 45) || // insert
11253 (event.keyCode == 46) || // delete 7832 (event.keyCode == 46) || // delete
11254 (event.keyCode == 144) || // num lock 7833 (event.keyCode == 144) || // num lock
11255 (event.keyCode == 145) || // scroll lock 7834 (event.keyCode == 145) || // scroll lock
11256 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho me, arrows 7835 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho me, arrows
11257 (event.keyCode > 111 && event.keyCode < 124); // fn keys 7836 (event.keyCode > 111 && event.keyCode < 124); // fn keys
11258 7837
11259 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); 7838 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
11260 }, 7839 },
11261 7840
11262 _onKeypress: function(event) { 7841 _onKeypress: function(event) {
11263 if (!this.preventInvalidInput && this.type !== 'number') { 7842 if (!this.preventInvalidInput && this.type !== 'number') {
11264 return; 7843 return;
11265 } 7844 }
11266 var regexp = this._patternRegExp; 7845 var regexp = this._patternRegExp;
11267 if (!regexp) { 7846 if (!regexp) {
11268 return; 7847 return;
11269 } 7848 }
11270 7849
11271 // Handle special keys and backspace
11272 if (event.metaKey || event.ctrlKey || event.altKey) 7850 if (event.metaKey || event.ctrlKey || event.altKey)
11273 return; 7851 return;
11274 7852
11275 // Check the pattern either here or in `_onInput`, but not in both.
11276 this._patternAlreadyChecked = true; 7853 this._patternAlreadyChecked = true;
11277 7854
11278 var thisChar = String.fromCharCode(event.charCode); 7855 var thisChar = String.fromCharCode(event.charCode);
11279 if (this._isPrintable(event) && !regexp.test(thisChar)) { 7856 if (this._isPrintable(event) && !regexp.test(thisChar)) {
11280 event.preventDefault(); 7857 event.preventDefault();
11281 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not e ntered.'); 7858 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not e ntered.');
11282 } 7859 }
11283 }, 7860 },
11284 7861
11285 _checkPatternValidity: function() { 7862 _checkPatternValidity: function() {
11286 var regexp = this._patternRegExp; 7863 var regexp = this._patternRegExp;
11287 if (!regexp) { 7864 if (!regexp) {
11288 return true; 7865 return true;
11289 } 7866 }
11290 for (var i = 0; i < this.value.length; i++) { 7867 for (var i = 0; i < this.value.length; i++) {
11291 if (!regexp.test(this.value[i])) { 7868 if (!regexp.test(this.value[i])) {
11292 return false; 7869 return false;
11293 } 7870 }
11294 } 7871 }
11295 return true; 7872 return true;
11296 }, 7873 },
11297 7874
11298 /**
11299 * Returns true if `value` is valid. The validator provided in `validator` w ill be used first,
11300 * then any constraints.
11301 * @return {boolean} True if the value is valid.
11302 */
11303 validate: function() { 7875 validate: function() {
11304 // First, check what the browser thinks. Some inputs (like type=number)
11305 // behave weirdly and will set the value to "" if something invalid is
11306 // entered, but will set the validity correctly.
11307 var valid = this.checkValidity(); 7876 var valid = this.checkValidity();
11308 7877
11309 // Only do extra checking if the browser thought this was valid.
11310 if (valid) { 7878 if (valid) {
11311 // Empty, required input is invalid
11312 if (this.required && this.value === '') { 7879 if (this.required && this.value === '') {
11313 valid = false; 7880 valid = false;
11314 } else if (this.hasValidator()) { 7881 } else if (this.hasValidator()) {
11315 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value ); 7882 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value );
11316 } 7883 }
11317 } 7884 }
11318 7885
11319 this.invalid = !valid; 7886 this.invalid = !valid;
11320 this.fire('iron-input-validate'); 7887 this.fire('iron-input-validate');
11321 return valid; 7888 return valid;
11322 }, 7889 },
11323 7890
11324 _announceInvalidCharacter: function(message) { 7891 _announceInvalidCharacter: function(message) {
11325 this.fire('iron-announce', { text: message }); 7892 this.fire('iron-announce', { text: message });
11326 } 7893 }
11327 }); 7894 });
11328 7895
11329 /*
11330 The `iron-input-validate` event is fired whenever `validate()` is called.
11331 @event iron-input-validate
11332 */
11333 Polymer({ 7896 Polymer({
11334 is: 'paper-input-container', 7897 is: 'paper-input-container',
11335 7898
11336 properties: { 7899 properties: {
11337 /**
11338 * Set to true to disable the floating label. The label disappears when th e input value is
11339 * not null.
11340 */
11341 noLabelFloat: { 7900 noLabelFloat: {
11342 type: Boolean, 7901 type: Boolean,
11343 value: false 7902 value: false
11344 }, 7903 },
11345 7904
11346 /**
11347 * Set to true to always float the floating label.
11348 */
11349 alwaysFloatLabel: { 7905 alwaysFloatLabel: {
11350 type: Boolean, 7906 type: Boolean,
11351 value: false 7907 value: false
11352 }, 7908 },
11353 7909
11354 /**
11355 * The attribute to listen for value changes on.
11356 */
11357 attrForValue: { 7910 attrForValue: {
11358 type: String, 7911 type: String,
11359 value: 'bind-value' 7912 value: 'bind-value'
11360 }, 7913 },
11361 7914
11362 /**
11363 * Set to true to auto-validate the input value when it changes.
11364 */
11365 autoValidate: { 7915 autoValidate: {
11366 type: Boolean, 7916 type: Boolean,
11367 value: false 7917 value: false
11368 }, 7918 },
11369 7919
11370 /**
11371 * True if the input is invalid. This property is set automatically when t he input value
11372 * changes if auto-validating, or when the `iron-input-validate` event is heard from a child.
11373 */
11374 invalid: { 7920 invalid: {
11375 observer: '_invalidChanged', 7921 observer: '_invalidChanged',
11376 type: Boolean, 7922 type: Boolean,
11377 value: false 7923 value: false
11378 }, 7924 },
11379 7925
11380 /**
11381 * True if the input has focus.
11382 */
11383 focused: { 7926 focused: {
11384 readOnly: true, 7927 readOnly: true,
11385 type: Boolean, 7928 type: Boolean,
11386 value: false, 7929 value: false,
11387 notify: true 7930 notify: true
11388 }, 7931 },
11389 7932
11390 _addons: { 7933 _addons: {
11391 type: Array 7934 type: Array
11392 // do not set a default value here intentionally - it will be initialize d lazily when a
11393 // distributed child is attached, which may occur before configuration f or this element
11394 // in polyfill.
11395 }, 7935 },
11396 7936
11397 _inputHasContent: { 7937 _inputHasContent: {
11398 type: Boolean, 7938 type: Boolean,
11399 value: false 7939 value: false
11400 }, 7940 },
11401 7941
11402 _inputSelector: { 7942 _inputSelector: {
11403 type: String, 7943 type: String,
11404 value: 'input,textarea,.paper-input-input' 7944 value: 'input,textarea,.paper-input-input'
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
11462 this.addEventListener('blur', this._boundOnBlur, true); 8002 this.addEventListener('blur', this._boundOnBlur, true);
11463 }, 8003 },
11464 8004
11465 attached: function() { 8005 attached: function() {
11466 if (this.attrForValue) { 8006 if (this.attrForValue) {
11467 this._inputElement.addEventListener(this._valueChangedEvent, this._bound ValueChanged); 8007 this._inputElement.addEventListener(this._valueChangedEvent, this._bound ValueChanged);
11468 } else { 8008 } else {
11469 this.addEventListener('input', this._onInput); 8009 this.addEventListener('input', this._onInput);
11470 } 8010 }
11471 8011
11472 // Only validate when attached if the input already has a value.
11473 if (this._inputElementValue != '') { 8012 if (this._inputElementValue != '') {
11474 this._handleValueAndAutoValidate(this._inputElement); 8013 this._handleValueAndAutoValidate(this._inputElement);
11475 } else { 8014 } else {
11476 this._handleValue(this._inputElement); 8015 this._handleValue(this._inputElement);
11477 } 8016 }
11478 }, 8017 },
11479 8018
11480 _onAddonAttached: function(event) { 8019 _onAddonAttached: function(event) {
11481 if (!this._addons) { 8020 if (!this._addons) {
11482 this._addons = []; 8021 this._addons = [];
(...skipping 20 matching lines...) Expand all
11503 this._handleValueAndAutoValidate(event.target); 8042 this._handleValueAndAutoValidate(event.target);
11504 }, 8043 },
11505 8044
11506 _onValueChanged: function(event) { 8045 _onValueChanged: function(event) {
11507 this._handleValueAndAutoValidate(event.target); 8046 this._handleValueAndAutoValidate(event.target);
11508 }, 8047 },
11509 8048
11510 _handleValue: function(inputElement) { 8049 _handleValue: function(inputElement) {
11511 var value = this._inputElementValue; 8050 var value = this._inputElementValue;
11512 8051
11513 // type="number" hack needed because this.value is empty until it's valid
11514 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme nt.checkValidity())) { 8052 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme nt.checkValidity())) {
11515 this._inputHasContent = true; 8053 this._inputHasContent = true;
11516 } else { 8054 } else {
11517 this._inputHasContent = false; 8055 this._inputHasContent = false;
11518 } 8056 }
11519 8057
11520 this.updateAddons({ 8058 this.updateAddons({
11521 inputElement: inputElement, 8059 inputElement: inputElement,
11522 value: value, 8060 value: value,
11523 invalid: this.invalid 8061 invalid: this.invalid
11524 }); 8062 });
11525 }, 8063 },
11526 8064
11527 _handleValueAndAutoValidate: function(inputElement) { 8065 _handleValueAndAutoValidate: function(inputElement) {
11528 if (this.autoValidate) { 8066 if (this.autoValidate) {
11529 var valid; 8067 var valid;
11530 if (inputElement.validate) { 8068 if (inputElement.validate) {
11531 valid = inputElement.validate(this._inputElementValue); 8069 valid = inputElement.validate(this._inputElementValue);
11532 } else { 8070 } else {
11533 valid = inputElement.checkValidity(); 8071 valid = inputElement.checkValidity();
11534 } 8072 }
11535 this.invalid = !valid; 8073 this.invalid = !valid;
11536 } 8074 }
11537 8075
11538 // Call this last to notify the add-ons.
11539 this._handleValue(inputElement); 8076 this._handleValue(inputElement);
11540 }, 8077 },
11541 8078
11542 _onIronInputValidate: function(event) { 8079 _onIronInputValidate: function(event) {
11543 this.invalid = this._inputElement.invalid; 8080 this.invalid = this._inputElement.invalid;
11544 }, 8081 },
11545 8082
11546 _invalidChanged: function() { 8083 _invalidChanged: function() {
11547 if (this._addons) { 8084 if (this._addons) {
11548 this.updateAddons({invalid: this.invalid}); 8085 this.updateAddons({invalid: this.invalid});
11549 } 8086 }
11550 }, 8087 },
11551 8088
11552 /**
11553 * Call this to update the state of add-ons.
11554 * @param {Object} state Add-on state.
11555 */
11556 updateAddons: function(state) { 8089 updateAddons: function(state) {
11557 for (var addon, index = 0; addon = this._addons[index]; index++) { 8090 for (var addon, index = 0; addon = this._addons[index]; index++) {
11558 addon.update(state); 8091 addon.update(state);
11559 } 8092 }
11560 }, 8093 },
11561 8094
11562 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) { 8095 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) {
11563 var cls = 'input-content'; 8096 var cls = 'input-content';
11564 if (!noLabelFloat) { 8097 if (!noLabelFloat) {
11565 var label = this.querySelector('label'); 8098 var label = this.querySelector('label');
11566 8099
11567 if (alwaysFloatLabel || _inputHasContent) { 8100 if (alwaysFloatLabel || _inputHasContent) {
11568 cls += ' label-is-floating'; 8101 cls += ' label-is-floating';
11569 // If the label is floating, ignore any offsets that may have been
11570 // applied from a prefix element.
11571 this.$.labelAndInputContainer.style.position = 'static'; 8102 this.$.labelAndInputContainer.style.position = 'static';
11572 8103
11573 if (invalid) { 8104 if (invalid) {
11574 cls += ' is-invalid'; 8105 cls += ' is-invalid';
11575 } else if (focused) { 8106 } else if (focused) {
11576 cls += " label-is-highlighted"; 8107 cls += " label-is-highlighted";
11577 } 8108 }
11578 } else { 8109 } else {
11579 // When the label is not floating, it should overlap the input element .
11580 if (label) { 8110 if (label) {
11581 this.$.labelAndInputContainer.style.position = 'relative'; 8111 this.$.labelAndInputContainer.style.position = 'relative';
11582 } 8112 }
11583 } 8113 }
11584 } else { 8114 } else {
11585 if (_inputHasContent) { 8115 if (_inputHasContent) {
11586 cls += ' label-is-hidden'; 8116 cls += ' label-is-hidden';
11587 } 8117 }
11588 } 8118 }
11589 return cls; 8119 return cls;
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
11640 }); 8170 });
11641 // Copyright 2015 The Chromium Authors. All rights reserved. 8171 // Copyright 2015 The Chromium Authors. All rights reserved.
11642 // Use of this source code is governed by a BSD-style license that can be 8172 // Use of this source code is governed by a BSD-style license that can be
11643 // found in the LICENSE file. 8173 // found in the LICENSE file.
11644 8174
11645 cr.define('downloads', function() { 8175 cr.define('downloads', function() {
11646 var Toolbar = Polymer({ 8176 var Toolbar = Polymer({
11647 is: 'downloads-toolbar', 8177 is: 'downloads-toolbar',
11648 8178
11649 attached: function() { 8179 attached: function() {
11650 // isRTL() only works after i18n_template.js runs to set <html dir>.
11651 this.overflowAlign_ = isRTL() ? 'left' : 'right'; 8180 this.overflowAlign_ = isRTL() ? 'left' : 'right';
11652 }, 8181 },
11653 8182
11654 properties: { 8183 properties: {
11655 downloadsShowing: { 8184 downloadsShowing: {
11656 reflectToAttribute: true, 8185 reflectToAttribute: true,
11657 type: Boolean, 8186 type: Boolean,
11658 value: false, 8187 value: false,
11659 observer: 'downloadsShowingChanged_', 8188 observer: 'downloadsShowingChanged_',
11660 }, 8189 },
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
11698 onClearAllTap_: function() { 8227 onClearAllTap_: function() {
11699 assert(this.canClearAll()); 8228 assert(this.canClearAll());
11700 downloads.ActionService.getInstance().clearAll(); 8229 downloads.ActionService.getInstance().clearAll();
11701 }, 8230 },
11702 8231
11703 /** @private */ 8232 /** @private */
11704 onPaperDropdownClose_: function() { 8233 onPaperDropdownClose_: function() {
11705 window.removeEventListener('resize', assert(this.boundClose_)); 8234 window.removeEventListener('resize', assert(this.boundClose_));
11706 }, 8235 },
11707 8236
11708 /**
11709 * @param {!Event} e
11710 * @private
11711 */
11712 onItemBlur_: function(e) { 8237 onItemBlur_: function(e) {
11713 var menu = /** @type {PaperMenuElement} */(this.$$('paper-menu')); 8238 var menu = /** @type {PaperMenuElement} */(this.$$('paper-menu'));
11714 if (menu.items.indexOf(e.relatedTarget) >= 0) 8239 if (menu.items.indexOf(e.relatedTarget) >= 0)
11715 return; 8240 return;
11716 8241
11717 this.$.more.restoreFocusOnClose = false; 8242 this.$.more.restoreFocusOnClose = false;
11718 this.closeMoreActions_(); 8243 this.closeMoreActions_();
11719 this.$.more.restoreFocusOnClose = true; 8244 this.$.more.restoreFocusOnClose = true;
11720 }, 8245 },
11721 8246
11722 /** @private */ 8247 /** @private */
11723 onPaperDropdownOpen_: function() { 8248 onPaperDropdownOpen_: function() {
11724 this.boundClose_ = this.boundClose_ || this.closeMoreActions_.bind(this); 8249 this.boundClose_ = this.boundClose_ || this.closeMoreActions_.bind(this);
11725 window.addEventListener('resize', this.boundClose_); 8250 window.addEventListener('resize', this.boundClose_);
11726 }, 8251 },
11727 8252
11728 /**
11729 * @param {!CustomEvent} event
11730 * @private
11731 */
11732 onSearchChanged_: function(event) { 8253 onSearchChanged_: function(event) {
11733 downloads.ActionService.getInstance().search( 8254 downloads.ActionService.getInstance().search(
11734 /** @type {string} */ (event.detail)); 8255 /** @type {string} */ (event.detail));
11735 this.updateClearAll_(); 8256 this.updateClearAll_();
11736 }, 8257 },
11737 8258
11738 /** @private */ 8259 /** @private */
11739 onOpenDownloadsFolderTap_: function() { 8260 onOpenDownloadsFolderTap_: function() {
11740 downloads.ActionService.getInstance().openDownloadsFolder(); 8261 downloads.ActionService.getInstance().openDownloadsFolder();
11741 }, 8262 },
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
11794 if (this.hasDownloads_) { 8315 if (this.hasDownloads_) {
11795 this.$['downloads-list'].fire('iron-resize'); 8316 this.$['downloads-list'].fire('iron-resize');
11796 } else { 8317 } else {
11797 var isSearching = downloads.ActionService.getInstance().isSearching(); 8318 var isSearching = downloads.ActionService.getInstance().isSearching();
11798 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; 8319 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
11799 this.$['no-downloads'].querySelector('span').textContent = 8320 this.$['no-downloads'].querySelector('span').textContent =
11800 loadTimeData.getString(messageToShow); 8321 loadTimeData.getString(messageToShow);
11801 } 8322 }
11802 }, 8323 },
11803 8324
11804 /**
11805 * @param {number} index
11806 * @param {!Array<!downloads.Data>} list
11807 * @private
11808 */
11809 insertItems_: function(index, list) { 8325 insertItems_: function(index, list) {
11810 this.splice.apply(this, ['items_', index, 0].concat(list)); 8326 this.splice.apply(this, ['items_', index, 0].concat(list));
11811 this.updateHideDates_(index, index + list.length); 8327 this.updateHideDates_(index, index + list.length);
11812 this.removeAttribute('loading'); 8328 this.removeAttribute('loading');
11813 }, 8329 },
11814 8330
11815 /** @private */ 8331 /** @private */
11816 itemsChanged_: function() { 8332 itemsChanged_: function() {
11817 this.hasDownloads_ = this.items_.length > 0; 8333 this.hasDownloads_ = this.items_.length > 0;
11818 }, 8334 },
11819 8335
11820 /**
11821 * @param {Event} e
11822 * @private
11823 */
11824 onCanExecute_: function(e) { 8336 onCanExecute_: function(e) {
11825 e = /** @type {cr.ui.CanExecuteEvent} */(e); 8337 e = /** @type {cr.ui.CanExecuteEvent} */(e);
11826 switch (e.command.id) { 8338 switch (e.command.id) {
11827 case 'undo-command': 8339 case 'undo-command':
11828 e.canExecute = this.$.toolbar.canUndo(); 8340 e.canExecute = this.$.toolbar.canUndo();
11829 break; 8341 break;
11830 case 'clear-all-command': 8342 case 'clear-all-command':
11831 e.canExecute = this.$.toolbar.canClearAll(); 8343 e.canExecute = this.$.toolbar.canClearAll();
11832 break; 8344 break;
11833 case 'find-command': 8345 case 'find-command':
11834 e.canExecute = true; 8346 e.canExecute = true;
11835 break; 8347 break;
11836 } 8348 }
11837 }, 8349 },
11838 8350
11839 /**
11840 * @param {Event} e
11841 * @private
11842 */
11843 onCommand_: function(e) { 8351 onCommand_: function(e) {
11844 if (e.command.id == 'clear-all-command') 8352 if (e.command.id == 'clear-all-command')
11845 downloads.ActionService.getInstance().clearAll(); 8353 downloads.ActionService.getInstance().clearAll();
11846 else if (e.command.id == 'undo-command') 8354 else if (e.command.id == 'undo-command')
11847 downloads.ActionService.getInstance().undo(); 8355 downloads.ActionService.getInstance().undo();
11848 else if (e.command.id == 'find-command') 8356 else if (e.command.id == 'find-command')
11849 this.$.toolbar.onFindCommand(); 8357 this.$.toolbar.onFindCommand();
11850 }, 8358 },
11851 8359
11852 /** @private */ 8360 /** @private */
11853 onListScroll_: function() { 8361 onListScroll_: function() {
11854 var list = this.$['downloads-list']; 8362 var list = this.$['downloads-list'];
11855 if (list.scrollHeight - list.scrollTop - list.offsetHeight <= 100) { 8363 if (list.scrollHeight - list.scrollTop - list.offsetHeight <= 100) {
11856 // Approaching the end of the scrollback. Attempt to load more items.
11857 downloads.ActionService.getInstance().loadMore(); 8364 downloads.ActionService.getInstance().loadMore();
11858 } 8365 }
11859 }, 8366 },
11860 8367
11861 /** @private */ 8368 /** @private */
11862 onLoad_: function() { 8369 onLoad_: function() {
11863 cr.ui.decorate('command', cr.ui.Command); 8370 cr.ui.decorate('command', cr.ui.Command);
11864 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); 8371 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
11865 document.addEventListener('command', this.onCommand_.bind(this)); 8372 document.addEventListener('command', this.onCommand_.bind(this));
11866 8373
11867 downloads.ActionService.getInstance().loadMore(); 8374 downloads.ActionService.getInstance().loadMore();
11868 }, 8375 },
11869 8376
11870 /**
11871 * @param {number} index
11872 * @private
11873 */
11874 removeItem_: function(index) { 8377 removeItem_: function(index) {
11875 this.splice('items_', index, 1); 8378 this.splice('items_', index, 1);
11876 this.updateHideDates_(index, index); 8379 this.updateHideDates_(index, index);
11877 this.onListScroll_(); 8380 this.onListScroll_();
11878 }, 8381 },
11879 8382
11880 /**
11881 * @param {number} start
11882 * @param {number} end
11883 * @private
11884 */
11885 updateHideDates_: function(start, end) { 8383 updateHideDates_: function(start, end) {
11886 for (var i = start; i <= end; ++i) { 8384 for (var i = start; i <= end; ++i) {
11887 var current = this.items_[i]; 8385 var current = this.items_[i];
11888 if (!current) 8386 if (!current)
11889 continue; 8387 continue;
11890 var prev = this.items_[i - 1]; 8388 var prev = this.items_[i - 1];
11891 current.hideDate = !!prev && prev.date_string == current.date_string; 8389 current.hideDate = !!prev && prev.date_string == current.date_string;
11892 } 8390 }
11893 }, 8391 },
11894 8392
11895 /**
11896 * @param {number} index
11897 * @param {!downloads.Data} data
11898 * @private
11899 */
11900 updateItem_: function(index, data) { 8393 updateItem_: function(index, data) {
11901 this.set('items_.' + index, data); 8394 this.set('items_.' + index, data);
11902 this.updateHideDates_(index, index); 8395 this.updateHideDates_(index, index);
11903 var list = /** @type {!IronListElement} */(this.$['downloads-list']); 8396 var list = /** @type {!IronListElement} */(this.$['downloads-list']);
11904 list.updateSizeForItem(index); 8397 list.updateSizeForItem(index);
11905 }, 8398 },
11906 }); 8399 });
11907 8400
11908 Manager.clearAll = function() { 8401 Manager.clearAll = function() {
11909 Manager.get().clearAll_(); 8402 Manager.get().clearAll_();
(...skipping 21 matching lines...) Expand all
11931 Manager.get().updateItem_(index, data); 8424 Manager.get().updateItem_(index, data);
11932 }; 8425 };
11933 8426
11934 return {Manager: Manager}; 8427 return {Manager: Manager};
11935 }); 8428 });
11936 // Copyright 2015 The Chromium Authors. All rights reserved. 8429 // Copyright 2015 The Chromium Authors. All rights reserved.
11937 // Use of this source code is governed by a BSD-style license that can be 8430 // Use of this source code is governed by a BSD-style license that can be
11938 // found in the LICENSE file. 8431 // found in the LICENSE file.
11939 8432
11940 window.addEventListener('load', downloads.Manager.onLoad); 8433 window.addEventListener('load', downloads.Manager.onLoad);
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/md_history/app.crisper.js » ('j') | chrome/browser/resources/vulcanize.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698