OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 PromiseResolver is a helper class that allows creating a | |
7 * Promise that will be fulfilled (resolved or rejected) some time later. | |
8 * | |
9 * Example: | |
10 * var resolver = new PromiseResolver(); | |
11 * resolver.promise.then(function(result) { | |
12 * console.log('resolved with', result); | |
13 * }); | |
14 * ... | |
15 * ... | |
16 * resolver.resolve({hello: 'world'}); | |
17 */ | |
18 | 5 |
19 /** | |
20 * @constructor @struct | |
21 * @template T | |
22 */ | |
23 function PromiseResolver() { | 6 function PromiseResolver() { |
24 /** @private {function(T=): void} */ | 7 /** @private {function(T=): void} */ |
25 this.resolve_; | 8 this.resolve_; |
26 | 9 |
27 /** @private {function(*=): void} */ | 10 /** @private {function(*=): void} */ |
28 this.reject_; | 11 this.reject_; |
29 | 12 |
30 /** @private {!Promise<T>} */ | 13 /** @private {!Promise<T>} */ |
31 this.promise_ = new Promise(function(resolve, reject) { | 14 this.promise_ = new Promise(function(resolve, reject) { |
32 this.resolve_ = resolve; | 15 this.resolve_ = resolve; |
(...skipping 11 matching lines...) Expand all Loading... |
44 set resolve(r) { assertNotReached(); }, | 27 set resolve(r) { assertNotReached(); }, |
45 | 28 |
46 /** @return {function(*=): void} */ | 29 /** @return {function(*=): void} */ |
47 get reject() { return this.reject_; }, | 30 get reject() { return this.reject_; }, |
48 set reject(s) { assertNotReached(); }, | 31 set reject(s) { assertNotReached(); }, |
49 }; | 32 }; |
50 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 33 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
51 // Use of this source code is governed by a BSD-style license that can be | 34 // Use of this source code is governed by a BSD-style license that can be |
52 // found in the LICENSE file. | 35 // found in the LICENSE file. |
53 | 36 |
54 /** | |
55 * The global object. | |
56 * @type {!Object} | |
57 * @const | |
58 */ | |
59 var global = this; | 37 var global = this; |
60 | 38 |
61 /** @typedef {{eventName: string, uid: number}} */ | 39 /** @typedef {{eventName: string, uid: number}} */ |
62 var WebUIListener; | 40 var WebUIListener; |
63 | 41 |
64 /** Platform, package, object property, and Event support. **/ | 42 /** Platform, package, object property, and Event support. **/ |
65 var cr = cr || function() { | 43 var cr = cr || function() { |
66 'use strict'; | 44 'use strict'; |
67 | 45 |
68 /** | |
69 * Builds an object structure for the provided namespace path, | |
70 * ensuring that names that already exist are not overwritten. For | |
71 * example: | |
72 * "a.b.c" -> a = {};a.b={};a.b.c={}; | |
73 * @param {string} name Name of the object that this file defines. | |
74 * @param {*=} opt_object The object to expose at the end of the path. | |
75 * @param {Object=} opt_objectToExportTo The object to add the path to; | |
76 * default is {@code global}. | |
77 * @return {!Object} The last object exported (i.e. exportPath('cr.ui') | |
78 * returns a reference to the ui property of window.cr). | |
79 * @private | |
80 */ | |
81 function exportPath(name, opt_object, opt_objectToExportTo) { | 46 function exportPath(name, opt_object, opt_objectToExportTo) { |
82 var parts = name.split('.'); | 47 var parts = name.split('.'); |
83 var cur = opt_objectToExportTo || global; | 48 var cur = opt_objectToExportTo || global; |
84 | 49 |
85 for (var part; parts.length && (part = parts.shift());) { | 50 for (var part; parts.length && (part = parts.shift());) { |
86 if (!parts.length && opt_object !== undefined) { | 51 if (!parts.length && opt_object !== undefined) { |
87 // last part and we have an object; use it | |
88 cur[part] = opt_object; | 52 cur[part] = opt_object; |
89 } else if (part in cur) { | 53 } else if (part in cur) { |
90 cur = cur[part]; | 54 cur = cur[part]; |
91 } else { | 55 } else { |
92 cur = cur[part] = {}; | 56 cur = cur[part] = {}; |
93 } | 57 } |
94 } | 58 } |
95 return cur; | 59 return cur; |
96 } | 60 } |
97 | 61 |
98 /** | |
99 * Fires a property change event on the target. | |
100 * @param {EventTarget} target The target to dispatch the event on. | |
101 * @param {string} propertyName The name of the property that changed. | |
102 * @param {*} newValue The new value for the property. | |
103 * @param {*} oldValue The old value for the property. | |
104 */ | |
105 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { | 62 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { |
106 var e = new Event(propertyName + 'Change'); | 63 var e = new Event(propertyName + 'Change'); |
107 e.propertyName = propertyName; | 64 e.propertyName = propertyName; |
108 e.newValue = newValue; | 65 e.newValue = newValue; |
109 e.oldValue = oldValue; | 66 e.oldValue = oldValue; |
110 target.dispatchEvent(e); | 67 target.dispatchEvent(e); |
111 } | 68 } |
112 | 69 |
113 /** | |
114 * Converts a camelCase javascript property name to a hyphenated-lower-case | |
115 * attribute name. | |
116 * @param {string} jsName The javascript camelCase property name. | |
117 * @return {string} The equivalent hyphenated-lower-case attribute name. | |
118 */ | |
119 function getAttributeName(jsName) { | 70 function getAttributeName(jsName) { |
120 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); | 71 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); |
121 } | 72 } |
122 | 73 |
123 /** | |
124 * The kind of property to define in {@code defineProperty}. | |
125 * @enum {string} | |
126 * @const | |
127 */ | |
128 var PropertyKind = { | 74 var PropertyKind = { |
129 /** | |
130 * Plain old JS property where the backing data is stored as a "private" | |
131 * field on the object. | |
132 * Use for properties of any type. Type will not be checked. | |
133 */ | |
134 JS: 'js', | 75 JS: 'js', |
135 | 76 |
136 /** | |
137 * The property backing data is stored as an attribute on an element. | |
138 * Use only for properties of type {string}. | |
139 */ | |
140 ATTR: 'attr', | 77 ATTR: 'attr', |
141 | 78 |
142 /** | |
143 * The property backing data is stored as an attribute on an element. If the | |
144 * element has the attribute then the value is true. | |
145 * Use only for properties of type {boolean}. | |
146 */ | |
147 BOOL_ATTR: 'boolAttr' | 79 BOOL_ATTR: 'boolAttr' |
148 }; | 80 }; |
149 | 81 |
150 /** | |
151 * Helper function for defineProperty that returns the getter to use for the | |
152 * property. | |
153 * @param {string} name The name of the property. | |
154 * @param {PropertyKind} kind The kind of the property. | |
155 * @return {function():*} The getter for the property. | |
156 */ | |
157 function getGetter(name, kind) { | 82 function getGetter(name, kind) { |
158 switch (kind) { | 83 switch (kind) { |
159 case PropertyKind.JS: | 84 case PropertyKind.JS: |
160 var privateName = name + '_'; | 85 var privateName = name + '_'; |
161 return function() { | 86 return function() { |
162 return this[privateName]; | 87 return this[privateName]; |
163 }; | 88 }; |
164 case PropertyKind.ATTR: | 89 case PropertyKind.ATTR: |
165 var attributeName = getAttributeName(name); | 90 var attributeName = getAttributeName(name); |
166 return function() { | 91 return function() { |
167 return this.getAttribute(attributeName); | 92 return this.getAttribute(attributeName); |
168 }; | 93 }; |
169 case PropertyKind.BOOL_ATTR: | 94 case PropertyKind.BOOL_ATTR: |
170 var attributeName = getAttributeName(name); | 95 var attributeName = getAttributeName(name); |
171 return function() { | 96 return function() { |
172 return this.hasAttribute(attributeName); | 97 return this.hasAttribute(attributeName); |
173 }; | 98 }; |
174 } | 99 } |
175 | 100 |
176 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax | |
177 // the browser/unit tests to preprocess this file through grit. | |
178 throw 'not reached'; | 101 throw 'not reached'; |
179 } | 102 } |
180 | 103 |
181 /** | |
182 * Helper function for defineProperty that returns the setter of the right | |
183 * kind. | |
184 * @param {string} name The name of the property we are defining the setter | |
185 * for. | |
186 * @param {PropertyKind} kind The kind of property we are getting the | |
187 * setter for. | |
188 * @param {function(*, *):void=} opt_setHook A function to run after the | |
189 * property is set, but before the propertyChange event is fired. | |
190 * @return {function(*):void} The function to use as a setter. | |
191 */ | |
192 function getSetter(name, kind, opt_setHook) { | 104 function getSetter(name, kind, opt_setHook) { |
193 switch (kind) { | 105 switch (kind) { |
194 case PropertyKind.JS: | 106 case PropertyKind.JS: |
195 var privateName = name + '_'; | 107 var privateName = name + '_'; |
196 return function(value) { | 108 return function(value) { |
197 var oldValue = this[name]; | 109 var oldValue = this[name]; |
198 if (value !== oldValue) { | 110 if (value !== oldValue) { |
199 this[privateName] = value; | 111 this[privateName] = value; |
200 if (opt_setHook) | 112 if (opt_setHook) |
201 opt_setHook.call(this, value, oldValue); | 113 opt_setHook.call(this, value, oldValue); |
(...skipping 25 matching lines...) Expand all Loading... |
227 this.setAttribute(attributeName, name); | 139 this.setAttribute(attributeName, name); |
228 else | 140 else |
229 this.removeAttribute(attributeName); | 141 this.removeAttribute(attributeName); |
230 if (opt_setHook) | 142 if (opt_setHook) |
231 opt_setHook.call(this, value, oldValue); | 143 opt_setHook.call(this, value, oldValue); |
232 dispatchPropertyChange(this, name, value, oldValue); | 144 dispatchPropertyChange(this, name, value, oldValue); |
233 } | 145 } |
234 }; | 146 }; |
235 } | 147 } |
236 | 148 |
237 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax | |
238 // the browser/unit tests to preprocess this file through grit. | |
239 throw 'not reached'; | 149 throw 'not reached'; |
240 } | 150 } |
241 | 151 |
242 /** | |
243 * Defines a property on an object. When the setter changes the value a | |
244 * property change event with the type {@code name + 'Change'} is fired. | |
245 * @param {!Object} obj The object to define the property for. | |
246 * @param {string} name The name of the property. | |
247 * @param {PropertyKind=} opt_kind What kind of underlying storage to use. | |
248 * @param {function(*, *):void=} opt_setHook A function to run after the | |
249 * property is set, but before the propertyChange event is fired. | |
250 */ | |
251 function defineProperty(obj, name, opt_kind, opt_setHook) { | 152 function defineProperty(obj, name, opt_kind, opt_setHook) { |
252 if (typeof obj == 'function') | 153 if (typeof obj == 'function') |
253 obj = obj.prototype; | 154 obj = obj.prototype; |
254 | 155 |
255 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); | 156 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); |
256 | 157 |
257 if (!obj.__lookupGetter__(name)) | 158 if (!obj.__lookupGetter__(name)) |
258 obj.__defineGetter__(name, getGetter(name, kind)); | 159 obj.__defineGetter__(name, getGetter(name, kind)); |
259 | 160 |
260 if (!obj.__lookupSetter__(name)) | 161 if (!obj.__lookupSetter__(name)) |
261 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); | 162 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); |
262 } | 163 } |
263 | 164 |
264 /** | |
265 * Counter for use with createUid | |
266 */ | |
267 var uidCounter = 1; | 165 var uidCounter = 1; |
268 | 166 |
269 /** | |
270 * @return {number} A new unique ID. | |
271 */ | |
272 function createUid() { | 167 function createUid() { |
273 return uidCounter++; | 168 return uidCounter++; |
274 } | 169 } |
275 | 170 |
276 /** | |
277 * Returns a unique ID for the item. This mutates the item so it needs to be | |
278 * an object | |
279 * @param {!Object} item The item to get the unique ID for. | |
280 * @return {number} The unique ID for the item. | |
281 */ | |
282 function getUid(item) { | 171 function getUid(item) { |
283 if (item.hasOwnProperty('uid')) | 172 if (item.hasOwnProperty('uid')) |
284 return item.uid; | 173 return item.uid; |
285 return item.uid = createUid(); | 174 return item.uid = createUid(); |
286 } | 175 } |
287 | 176 |
288 /** | |
289 * Dispatches a simple event on an event target. | |
290 * @param {!EventTarget} target The event target to dispatch the event on. | |
291 * @param {string} type The type of the event. | |
292 * @param {boolean=} opt_bubbles Whether the event bubbles or not. | |
293 * @param {boolean=} opt_cancelable Whether the default action of the event | |
294 * can be prevented. Default is true. | |
295 * @return {boolean} If any of the listeners called {@code preventDefault} | |
296 * during the dispatch this will return false. | |
297 */ | |
298 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { | 177 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { |
299 var e = new Event(type, { | 178 var e = new Event(type, { |
300 bubbles: opt_bubbles, | 179 bubbles: opt_bubbles, |
301 cancelable: opt_cancelable === undefined || opt_cancelable | 180 cancelable: opt_cancelable === undefined || opt_cancelable |
302 }); | 181 }); |
303 return target.dispatchEvent(e); | 182 return target.dispatchEvent(e); |
304 } | 183 } |
305 | 184 |
306 /** | |
307 * Calls |fun| and adds all the fields of the returned object to the object | |
308 * named by |name|. For example, cr.define('cr.ui', function() { | |
309 * function List() { | |
310 * ... | |
311 * } | |
312 * function ListItem() { | |
313 * ... | |
314 * } | |
315 * return { | |
316 * List: List, | |
317 * ListItem: ListItem, | |
318 * }; | |
319 * }); | |
320 * defines the functions cr.ui.List and cr.ui.ListItem. | |
321 * @param {string} name The name of the object that we are adding fields to. | |
322 * @param {!Function} fun The function that will return an object containing | |
323 * the names and values of the new fields. | |
324 */ | |
325 function define(name, fun) { | 185 function define(name, fun) { |
326 var obj = exportPath(name); | 186 var obj = exportPath(name); |
327 var exports = fun(); | 187 var exports = fun(); |
328 for (var propertyName in exports) { | 188 for (var propertyName in exports) { |
329 // Maybe we should check the prototype chain here? The current usage | |
330 // pattern is always using an object literal so we only care about own | |
331 // properties. | |
332 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, | 189 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, |
333 propertyName); | 190 propertyName); |
334 if (propertyDescriptor) | 191 if (propertyDescriptor) |
335 Object.defineProperty(obj, propertyName, propertyDescriptor); | 192 Object.defineProperty(obj, propertyName, propertyDescriptor); |
336 } | 193 } |
337 } | 194 } |
338 | 195 |
339 /** | |
340 * Adds a {@code getInstance} static method that always return the same | |
341 * instance object. | |
342 * @param {!Function} ctor The constructor for the class to add the static | |
343 * method to. | |
344 */ | |
345 function addSingletonGetter(ctor) { | 196 function addSingletonGetter(ctor) { |
346 ctor.getInstance = function() { | 197 ctor.getInstance = function() { |
347 return ctor.instance_ || (ctor.instance_ = new ctor()); | 198 return ctor.instance_ || (ctor.instance_ = new ctor()); |
348 }; | 199 }; |
349 } | 200 } |
350 | 201 |
351 /** | |
352 * Forwards public APIs to private implementations. | |
353 * @param {Function} ctor Constructor that have private implementations in its | |
354 * prototype. | |
355 * @param {Array<string>} methods List of public method names that have their | |
356 * underscored counterparts in constructor's prototype. | |
357 * @param {string=} opt_target Selector for target node. | |
358 */ | |
359 function makePublic(ctor, methods, opt_target) { | 202 function makePublic(ctor, methods, opt_target) { |
360 methods.forEach(function(method) { | 203 methods.forEach(function(method) { |
361 ctor[method] = function() { | 204 ctor[method] = function() { |
362 var target = opt_target ? document.getElementById(opt_target) : | 205 var target = opt_target ? document.getElementById(opt_target) : |
363 ctor.getInstance(); | 206 ctor.getInstance(); |
364 return target[method + '_'].apply(target, arguments); | 207 return target[method + '_'].apply(target, arguments); |
365 }; | 208 }; |
366 }); | 209 }); |
367 } | 210 } |
368 | 211 |
369 /** | |
370 * The mapping used by the sendWithPromise mechanism to tie the Promise | |
371 * returned to callers with the corresponding WebUI response. The mapping is | |
372 * from ID to the PromiseResolver helper; the ID is generated by | |
373 * sendWithPromise and is unique across all invocations of said method. | |
374 * @type {!Object<!PromiseResolver>} | |
375 */ | |
376 var chromeSendResolverMap = {}; | 212 var chromeSendResolverMap = {}; |
377 | 213 |
378 /** | |
379 * The named method the WebUI handler calls directly in response to a | |
380 * chrome.send call that expects a response. The handler requires no knowledge | |
381 * of the specific name of this method, as the name is passed to the handler | |
382 * as the first argument in the arguments list of chrome.send. The handler | |
383 * must pass the ID, also sent via the chrome.send arguments list, as the | |
384 * first argument of the JS invocation; additionally, the handler may | |
385 * supply any number of other arguments that will be included in the response. | |
386 * @param {string} id The unique ID identifying the Promise this response is | |
387 * tied to. | |
388 * @param {boolean} isSuccess Whether the request was successful. | |
389 * @param {*} response The response as sent from C++. | |
390 */ | |
391 function webUIResponse(id, isSuccess, response) { | 214 function webUIResponse(id, isSuccess, response) { |
392 var resolver = chromeSendResolverMap[id]; | 215 var resolver = chromeSendResolverMap[id]; |
393 delete chromeSendResolverMap[id]; | 216 delete chromeSendResolverMap[id]; |
394 | 217 |
395 if (isSuccess) | 218 if (isSuccess) |
396 resolver.resolve(response); | 219 resolver.resolve(response); |
397 else | 220 else |
398 resolver.reject(response); | 221 resolver.reject(response); |
399 } | 222 } |
400 | 223 |
401 /** | |
402 * A variation of chrome.send, suitable for messages that expect a single | |
403 * response from C++. | |
404 * @param {string} methodName The name of the WebUI handler API. | |
405 * @param {...*} var_args Varibale number of arguments to be forwarded to the | |
406 * C++ call. | |
407 * @return {!Promise} | |
408 */ | |
409 function sendWithPromise(methodName, var_args) { | 224 function sendWithPromise(methodName, var_args) { |
410 var args = Array.prototype.slice.call(arguments, 1); | 225 var args = Array.prototype.slice.call(arguments, 1); |
411 var promiseResolver = new PromiseResolver(); | 226 var promiseResolver = new PromiseResolver(); |
412 var id = methodName + '_' + createUid(); | 227 var id = methodName + '_' + createUid(); |
413 chromeSendResolverMap[id] = promiseResolver; | 228 chromeSendResolverMap[id] = promiseResolver; |
414 chrome.send(methodName, [id].concat(args)); | 229 chrome.send(methodName, [id].concat(args)); |
415 return promiseResolver.promise; | 230 return promiseResolver.promise; |
416 } | 231 } |
417 | 232 |
418 /** | |
419 * A map of maps associating event names with listeners. The 2nd level map | |
420 * associates a listener ID with the callback function, such that individual | |
421 * listeners can be removed from an event without affecting other listeners of | |
422 * the same event. | |
423 * @type {!Object<!Object<!Function>>} | |
424 */ | |
425 var webUIListenerMap = {}; | 233 var webUIListenerMap = {}; |
426 | 234 |
427 /** | |
428 * The named method the WebUI handler calls directly when an event occurs. | |
429 * The WebUI handler must supply the name of the event as the first argument | |
430 * of the JS invocation; additionally, the handler may supply any number of | |
431 * other arguments that will be forwarded to the listener callbacks. | |
432 * @param {string} event The name of the event that has occurred. | |
433 * @param {...*} var_args Additional arguments passed from C++. | |
434 */ | |
435 function webUIListenerCallback(event, var_args) { | 235 function webUIListenerCallback(event, var_args) { |
436 var eventListenersMap = webUIListenerMap[event]; | 236 var eventListenersMap = webUIListenerMap[event]; |
437 if (!eventListenersMap) { | 237 if (!eventListenersMap) { |
438 // C++ event sent for an event that has no listeners. | |
439 // TODO(dpapad): Should a warning be displayed here? | |
440 return; | 238 return; |
441 } | 239 } |
442 | 240 |
443 var args = Array.prototype.slice.call(arguments, 1); | 241 var args = Array.prototype.slice.call(arguments, 1); |
444 for (var listenerId in eventListenersMap) { | 242 for (var listenerId in eventListenersMap) { |
445 eventListenersMap[listenerId].apply(null, args); | 243 eventListenersMap[listenerId].apply(null, args); |
446 } | 244 } |
447 } | 245 } |
448 | 246 |
449 /** | |
450 * Registers a listener for an event fired from WebUI handlers. Any number of | |
451 * listeners may register for a single event. | |
452 * @param {string} eventName The event to listen to. | |
453 * @param {!Function} callback The callback run when the event is fired. | |
454 * @return {!WebUIListener} An object to be used for removing a listener via | |
455 * cr.removeWebUIListener. Should be treated as read-only. | |
456 */ | |
457 function addWebUIListener(eventName, callback) { | 247 function addWebUIListener(eventName, callback) { |
458 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {}; | 248 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {}; |
459 var uid = createUid(); | 249 var uid = createUid(); |
460 webUIListenerMap[eventName][uid] = callback; | 250 webUIListenerMap[eventName][uid] = callback; |
461 return {eventName: eventName, uid: uid}; | 251 return {eventName: eventName, uid: uid}; |
462 } | 252 } |
463 | 253 |
464 /** | |
465 * Removes a listener. Does nothing if the specified listener is not found. | |
466 * @param {!WebUIListener} listener The listener to be removed (as returned by | |
467 * addWebUIListener). | |
468 * @return {boolean} Whether the given listener was found and actually | |
469 * removed. | |
470 */ | |
471 function removeWebUIListener(listener) { | 254 function removeWebUIListener(listener) { |
472 var listenerExists = webUIListenerMap[listener.eventName] && | 255 var listenerExists = webUIListenerMap[listener.eventName] && |
473 webUIListenerMap[listener.eventName][listener.uid]; | 256 webUIListenerMap[listener.eventName][listener.uid]; |
474 if (listenerExists) { | 257 if (listenerExists) { |
475 delete webUIListenerMap[listener.eventName][listener.uid]; | 258 delete webUIListenerMap[listener.eventName][listener.uid]; |
476 return true; | 259 return true; |
477 } | 260 } |
478 return false; | 261 return false; |
479 } | 262 } |
480 | 263 |
481 return { | 264 return { |
482 addSingletonGetter: addSingletonGetter, | 265 addSingletonGetter: addSingletonGetter, |
483 createUid: createUid, | 266 createUid: createUid, |
484 define: define, | 267 define: define, |
485 defineProperty: defineProperty, | 268 defineProperty: defineProperty, |
486 dispatchPropertyChange: dispatchPropertyChange, | 269 dispatchPropertyChange: dispatchPropertyChange, |
487 dispatchSimpleEvent: dispatchSimpleEvent, | 270 dispatchSimpleEvent: dispatchSimpleEvent, |
488 exportPath: exportPath, | 271 exportPath: exportPath, |
489 getUid: getUid, | 272 getUid: getUid, |
490 makePublic: makePublic, | 273 makePublic: makePublic, |
491 PropertyKind: PropertyKind, | 274 PropertyKind: PropertyKind, |
492 | 275 |
493 // C++ <-> JS communication related methods. | |
494 addWebUIListener: addWebUIListener, | 276 addWebUIListener: addWebUIListener, |
495 removeWebUIListener: removeWebUIListener, | 277 removeWebUIListener: removeWebUIListener, |
496 sendWithPromise: sendWithPromise, | 278 sendWithPromise: sendWithPromise, |
497 webUIListenerCallback: webUIListenerCallback, | 279 webUIListenerCallback: webUIListenerCallback, |
498 webUIResponse: webUIResponse, | 280 webUIResponse: webUIResponse, |
499 | 281 |
500 get doc() { | 282 get doc() { |
501 return document; | 283 return document; |
502 }, | 284 }, |
503 | 285 |
(...skipping 27 matching lines...) Expand all Loading... |
531 return /iPad|iPhone|iPod/.test(navigator.platform); | 313 return /iPad|iPhone|iPod/.test(navigator.platform); |
532 } | 314 } |
533 }; | 315 }; |
534 }(); | 316 }(); |
535 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 317 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
536 // Use of this source code is governed by a BSD-style license that can be | 318 // Use of this source code is governed by a BSD-style license that can be |
537 // found in the LICENSE file. | 319 // found in the LICENSE file. |
538 | 320 |
539 cr.define('cr.ui', function() { | 321 cr.define('cr.ui', function() { |
540 | 322 |
541 /** | |
542 * Decorates elements as an instance of a class. | |
543 * @param {string|!Element} source The way to find the element(s) to decorate. | |
544 * If this is a string then {@code querySeletorAll} is used to find the | |
545 * elements to decorate. | |
546 * @param {!Function} constr The constructor to decorate with. The constr | |
547 * needs to have a {@code decorate} function. | |
548 */ | |
549 function decorate(source, constr) { | 323 function decorate(source, constr) { |
550 var elements; | 324 var elements; |
551 if (typeof source == 'string') | 325 if (typeof source == 'string') |
552 elements = cr.doc.querySelectorAll(source); | 326 elements = cr.doc.querySelectorAll(source); |
553 else | 327 else |
554 elements = [source]; | 328 elements = [source]; |
555 | 329 |
556 for (var i = 0, el; el = elements[i]; i++) { | 330 for (var i = 0, el; el = elements[i]; i++) { |
557 if (!(el instanceof constr)) | 331 if (!(el instanceof constr)) |
558 constr.decorate(el); | 332 constr.decorate(el); |
559 } | 333 } |
560 } | 334 } |
561 | 335 |
562 /** | |
563 * Helper function for creating new element for define. | |
564 */ | |
565 function createElementHelper(tagName, opt_bag) { | 336 function createElementHelper(tagName, opt_bag) { |
566 // Allow passing in ownerDocument to create in a different document. | |
567 var doc; | 337 var doc; |
568 if (opt_bag && opt_bag.ownerDocument) | 338 if (opt_bag && opt_bag.ownerDocument) |
569 doc = opt_bag.ownerDocument; | 339 doc = opt_bag.ownerDocument; |
570 else | 340 else |
571 doc = cr.doc; | 341 doc = cr.doc; |
572 return doc.createElement(tagName); | 342 return doc.createElement(tagName); |
573 } | 343 } |
574 | 344 |
575 /** | |
576 * Creates the constructor for a UI element class. | |
577 * | |
578 * Usage: | |
579 * <pre> | |
580 * var List = cr.ui.define('list'); | |
581 * List.prototype = { | |
582 * __proto__: HTMLUListElement.prototype, | |
583 * decorate: function() { | |
584 * ... | |
585 * }, | |
586 * ... | |
587 * }; | |
588 * </pre> | |
589 * | |
590 * @param {string|Function} tagNameOrFunction The tagName or | |
591 * function to use for newly created elements. If this is a function it | |
592 * needs to return a new element when called. | |
593 * @return {function(Object=):Element} The constructor function which takes | |
594 * an optional property bag. The function also has a static | |
595 * {@code decorate} method added to it. | |
596 */ | |
597 function define(tagNameOrFunction) { | 345 function define(tagNameOrFunction) { |
598 var createFunction, tagName; | 346 var createFunction, tagName; |
599 if (typeof tagNameOrFunction == 'function') { | 347 if (typeof tagNameOrFunction == 'function') { |
600 createFunction = tagNameOrFunction; | 348 createFunction = tagNameOrFunction; |
601 tagName = ''; | 349 tagName = ''; |
602 } else { | 350 } else { |
603 createFunction = createElementHelper; | 351 createFunction = createElementHelper; |
604 tagName = tagNameOrFunction; | 352 tagName = tagNameOrFunction; |
605 } | 353 } |
606 | 354 |
607 /** | |
608 * Creates a new UI element constructor. | |
609 * @param {Object=} opt_propertyBag Optional bag of properties to set on the | |
610 * object after created. The property {@code ownerDocument} is special | |
611 * cased and it allows you to create the element in a different | |
612 * document than the default. | |
613 * @constructor | |
614 */ | |
615 function f(opt_propertyBag) { | 355 function f(opt_propertyBag) { |
616 var el = createFunction(tagName, opt_propertyBag); | 356 var el = createFunction(tagName, opt_propertyBag); |
617 f.decorate(el); | 357 f.decorate(el); |
618 for (var propertyName in opt_propertyBag) { | 358 for (var propertyName in opt_propertyBag) { |
619 el[propertyName] = opt_propertyBag[propertyName]; | 359 el[propertyName] = opt_propertyBag[propertyName]; |
620 } | 360 } |
621 return el; | 361 return el; |
622 } | 362 } |
623 | 363 |
624 /** | |
625 * Decorates an element as a UI element class. | |
626 * @param {!Element} el The element to decorate. | |
627 */ | |
628 f.decorate = function(el) { | 364 f.decorate = function(el) { |
629 el.__proto__ = f.prototype; | 365 el.__proto__ = f.prototype; |
630 el.decorate(); | 366 el.decorate(); |
631 }; | 367 }; |
632 | 368 |
633 return f; | 369 return f; |
634 } | 370 } |
635 | 371 |
636 /** | |
637 * Input elements do not grow and shrink with their content. This is a simple | |
638 * (and not very efficient) way of handling shrinking to content with support | |
639 * for min width and limited by the width of the parent element. | |
640 * @param {!HTMLElement} el The element to limit the width for. | |
641 * @param {!HTMLElement} parentEl The parent element that should limit the | |
642 * size. | |
643 * @param {number} min The minimum width. | |
644 * @param {number=} opt_scale Optional scale factor to apply to the width. | |
645 */ | |
646 function limitInputWidth(el, parentEl, min, opt_scale) { | 372 function limitInputWidth(el, parentEl, min, opt_scale) { |
647 // Needs a size larger than borders | |
648 el.style.width = '10px'; | 373 el.style.width = '10px'; |
649 var doc = el.ownerDocument; | 374 var doc = el.ownerDocument; |
650 var win = doc.defaultView; | 375 var win = doc.defaultView; |
651 var computedStyle = win.getComputedStyle(el); | 376 var computedStyle = win.getComputedStyle(el); |
652 var parentComputedStyle = win.getComputedStyle(parentEl); | 377 var parentComputedStyle = win.getComputedStyle(parentEl); |
653 var rtl = computedStyle.direction == 'rtl'; | 378 var rtl = computedStyle.direction == 'rtl'; |
654 | 379 |
655 // To get the max width we get the width of the treeItem minus the position | |
656 // of the input. | |
657 var inputRect = el.getBoundingClientRect(); // box-sizing | 380 var inputRect = el.getBoundingClientRect(); // box-sizing |
658 var parentRect = parentEl.getBoundingClientRect(); | 381 var parentRect = parentEl.getBoundingClientRect(); |
659 var startPos = rtl ? parentRect.right - inputRect.right : | 382 var startPos = rtl ? parentRect.right - inputRect.right : |
660 inputRect.left - parentRect.left; | 383 inputRect.left - parentRect.left; |
661 | 384 |
662 // Add up border and padding of the input. | |
663 var inner = parseInt(computedStyle.borderLeftWidth, 10) + | 385 var inner = parseInt(computedStyle.borderLeftWidth, 10) + |
664 parseInt(computedStyle.paddingLeft, 10) + | 386 parseInt(computedStyle.paddingLeft, 10) + |
665 parseInt(computedStyle.paddingRight, 10) + | 387 parseInt(computedStyle.paddingRight, 10) + |
666 parseInt(computedStyle.borderRightWidth, 10); | 388 parseInt(computedStyle.borderRightWidth, 10); |
667 | 389 |
668 // We also need to subtract the padding of parent to prevent it to overflow. | |
669 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : | 390 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : |
670 parseInt(parentComputedStyle.paddingRight, 10); | 391 parseInt(parentComputedStyle.paddingRight, 10); |
671 | 392 |
672 var max = parentEl.clientWidth - startPos - inner - parentPadding; | 393 var max = parentEl.clientWidth - startPos - inner - parentPadding; |
673 if (opt_scale) | 394 if (opt_scale) |
674 max *= opt_scale; | 395 max *= opt_scale; |
675 | 396 |
676 function limit() { | 397 function limit() { |
677 if (el.scrollWidth > max) { | 398 if (el.scrollWidth > max) { |
678 el.style.width = max + 'px'; | 399 el.style.width = max + 'px'; |
679 } else { | 400 } else { |
680 el.style.width = 0; | 401 el.style.width = 0; |
681 var sw = el.scrollWidth; | 402 var sw = el.scrollWidth; |
682 if (sw < min) { | 403 if (sw < min) { |
683 el.style.width = min + 'px'; | 404 el.style.width = min + 'px'; |
684 } else { | 405 } else { |
685 el.style.width = sw + 'px'; | 406 el.style.width = sw + 'px'; |
686 } | 407 } |
687 } | 408 } |
688 } | 409 } |
689 | 410 |
690 el.addEventListener('input', limit); | 411 el.addEventListener('input', limit); |
691 limit(); | 412 limit(); |
692 } | 413 } |
693 | 414 |
694 /** | |
695 * Takes a number and spits out a value CSS will be happy with. To avoid | |
696 * subpixel layout issues, the value is rounded to the nearest integral value. | |
697 * @param {number} pixels The number of pixels. | |
698 * @return {string} e.g. '16px'. | |
699 */ | |
700 function toCssPx(pixels) { | 415 function toCssPx(pixels) { |
701 if (!window.isFinite(pixels)) | 416 if (!window.isFinite(pixels)) |
702 console.error('Pixel value is not a number: ' + pixels); | 417 console.error('Pixel value is not a number: ' + pixels); |
703 return Math.round(pixels) + 'px'; | 418 return Math.round(pixels) + 'px'; |
704 } | 419 } |
705 | 420 |
706 /** | |
707 * Users complain they occasionaly use doubleclicks instead of clicks | |
708 * (http://crbug.com/140364). To fix it we freeze click handling for | |
709 * the doubleclick time interval. | |
710 * @param {MouseEvent} e Initial click event. | |
711 */ | |
712 function swallowDoubleClick(e) { | 421 function swallowDoubleClick(e) { |
713 var doc = e.target.ownerDocument; | 422 var doc = e.target.ownerDocument; |
714 var counter = Math.min(1, e.detail); | 423 var counter = Math.min(1, e.detail); |
715 function swallow(e) { | 424 function swallow(e) { |
716 e.stopPropagation(); | 425 e.stopPropagation(); |
717 e.preventDefault(); | 426 e.preventDefault(); |
718 } | 427 } |
719 function onclick(e) { | 428 function onclick(e) { |
720 if (e.detail > counter) { | 429 if (e.detail > counter) { |
721 counter = e.detail; | 430 counter = e.detail; |
722 // Swallow the click since it's a click inside the doubleclick timeout. | |
723 swallow(e); | 431 swallow(e); |
724 } else { | 432 } else { |
725 // Stop tracking clicks and let regular handling. | |
726 doc.removeEventListener('dblclick', swallow, true); | 433 doc.removeEventListener('dblclick', swallow, true); |
727 doc.removeEventListener('click', onclick, true); | 434 doc.removeEventListener('click', onclick, true); |
728 } | 435 } |
729 } | 436 } |
730 // The following 'click' event (if e.type == 'mouseup') mustn't be taken | |
731 // into account (it mustn't stop tracking clicks). Start event listening | |
732 // after zero timeout. | |
733 setTimeout(function() { | 437 setTimeout(function() { |
734 doc.addEventListener('click', onclick, true); | 438 doc.addEventListener('click', onclick, true); |
735 doc.addEventListener('dblclick', swallow, true); | 439 doc.addEventListener('dblclick', swallow, true); |
736 }, 0); | 440 }, 0); |
737 } | 441 } |
738 | 442 |
739 return { | 443 return { |
740 decorate: decorate, | 444 decorate: decorate, |
741 define: define, | 445 define: define, |
742 limitInputWidth: limitInputWidth, | 446 limitInputWidth: limitInputWidth, |
743 toCssPx: toCssPx, | 447 toCssPx: toCssPx, |
744 swallowDoubleClick: swallowDoubleClick | 448 swallowDoubleClick: swallowDoubleClick |
745 }; | 449 }; |
746 }); | 450 }); |
747 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 451 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
748 // Use of this source code is governed by a BSD-style license that can be | 452 // Use of this source code is governed by a BSD-style license that can be |
749 // found in the LICENSE file. | 453 // found in the LICENSE file. |
750 | 454 |
751 /** | |
752 * @fileoverview A command is an abstraction of an action a user can do in the | |
753 * UI. | |
754 * | |
755 * When the focus changes in the document for each command a canExecute event | |
756 * is dispatched on the active element. By listening to this event you can | |
757 * enable and disable the command by setting the event.canExecute property. | |
758 * | |
759 * When a command is executed a command event is dispatched on the active | |
760 * element. Note that you should stop the propagation after you have handled the | |
761 * command if there might be other command listeners higher up in the DOM tree. | |
762 */ | |
763 | 455 |
764 cr.define('cr.ui', function() { | 456 cr.define('cr.ui', function() { |
765 | 457 |
766 /** | |
767 * This is used to identify keyboard shortcuts. | |
768 * @param {string} shortcut The text used to describe the keys for this | |
769 * keyboard shortcut. | |
770 * @constructor | |
771 */ | |
772 function KeyboardShortcut(shortcut) { | 458 function KeyboardShortcut(shortcut) { |
773 var mods = {}; | 459 var mods = {}; |
774 var ident = ''; | 460 var ident = ''; |
775 shortcut.split('|').forEach(function(part) { | 461 shortcut.split('|').forEach(function(part) { |
776 var partLc = part.toLowerCase(); | 462 var partLc = part.toLowerCase(); |
777 switch (partLc) { | 463 switch (partLc) { |
778 case 'alt': | 464 case 'alt': |
779 case 'ctrl': | 465 case 'ctrl': |
780 case 'meta': | 466 case 'meta': |
781 case 'shift': | 467 case 'shift': |
782 mods[partLc + 'Key'] = true; | 468 mods[partLc + 'Key'] = true; |
783 break; | 469 break; |
784 default: | 470 default: |
785 if (ident) | 471 if (ident) |
786 throw Error('Invalid shortcut'); | 472 throw Error('Invalid shortcut'); |
787 ident = part; | 473 ident = part; |
788 } | 474 } |
789 }); | 475 }); |
790 | 476 |
791 this.ident_ = ident; | 477 this.ident_ = ident; |
792 this.mods_ = mods; | 478 this.mods_ = mods; |
793 } | 479 } |
794 | 480 |
795 KeyboardShortcut.prototype = { | 481 KeyboardShortcut.prototype = { |
796 /** | |
797 * Whether the keyboard shortcut object matches a keyboard event. | |
798 * @param {!Event} e The keyboard event object. | |
799 * @return {boolean} Whether we found a match or not. | |
800 */ | |
801 matchesEvent: function(e) { | 482 matchesEvent: function(e) { |
802 if (e.key == this.ident_) { | 483 if (e.key == this.ident_) { |
803 // All keyboard modifiers needs to match. | |
804 var mods = this.mods_; | 484 var mods = this.mods_; |
805 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { | 485 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { |
806 return e[k] == !!mods[k]; | 486 return e[k] == !!mods[k]; |
807 }); | 487 }); |
808 } | 488 } |
809 return false; | 489 return false; |
810 } | 490 } |
811 }; | 491 }; |
812 | 492 |
813 /** | |
814 * Creates a new command element. | |
815 * @constructor | |
816 * @extends {HTMLElement} | |
817 */ | |
818 var Command = cr.ui.define('command'); | 493 var Command = cr.ui.define('command'); |
819 | 494 |
820 Command.prototype = { | 495 Command.prototype = { |
821 __proto__: HTMLElement.prototype, | 496 __proto__: HTMLElement.prototype, |
822 | 497 |
823 /** | |
824 * Initializes the command. | |
825 */ | |
826 decorate: function() { | 498 decorate: function() { |
827 CommandManager.init(assert(this.ownerDocument)); | 499 CommandManager.init(assert(this.ownerDocument)); |
828 | 500 |
829 if (this.hasAttribute('shortcut')) | 501 if (this.hasAttribute('shortcut')) |
830 this.shortcut = this.getAttribute('shortcut'); | 502 this.shortcut = this.getAttribute('shortcut'); |
831 }, | 503 }, |
832 | 504 |
833 /** | |
834 * Executes the command by dispatching a command event on the given element. | |
835 * If |element| isn't given, the active element is used instead. | |
836 * If the command is {@code disabled} this does nothing. | |
837 * @param {HTMLElement=} opt_element Optional element to dispatch event on. | |
838 */ | |
839 execute: function(opt_element) { | 505 execute: function(opt_element) { |
840 if (this.disabled) | 506 if (this.disabled) |
841 return; | 507 return; |
842 var doc = this.ownerDocument; | 508 var doc = this.ownerDocument; |
843 if (doc.activeElement) { | 509 if (doc.activeElement) { |
844 var e = new Event('command', {bubbles: true}); | 510 var e = new Event('command', {bubbles: true}); |
845 e.command = this; | 511 e.command = this; |
846 | 512 |
847 (opt_element || doc.activeElement).dispatchEvent(e); | 513 (opt_element || doc.activeElement).dispatchEvent(e); |
848 } | 514 } |
849 }, | 515 }, |
850 | 516 |
851 /** | |
852 * Call this when there have been changes that might change whether the | |
853 * command can be executed or not. | |
854 * @param {Node=} opt_node Node for which to actuate command state. | |
855 */ | |
856 canExecuteChange: function(opt_node) { | 517 canExecuteChange: function(opt_node) { |
857 dispatchCanExecuteEvent(this, | 518 dispatchCanExecuteEvent(this, |
858 opt_node || this.ownerDocument.activeElement); | 519 opt_node || this.ownerDocument.activeElement); |
859 }, | 520 }, |
860 | 521 |
861 /** | |
862 * The keyboard shortcut that triggers the command. This is a string | |
863 * consisting of a key (as reported by WebKit in keydown) as | |
864 * well as optional key modifiers joinded with a '|'. | |
865 * | |
866 * Multiple keyboard shortcuts can be provided by separating them by | |
867 * whitespace. | |
868 * | |
869 * For example: | |
870 * "F1" | |
871 * "Backspace|Meta" for Apple command backspace. | |
872 * "a|Ctrl" for Control A | |
873 * "Delete Backspace|Meta" for Delete and Command Backspace | |
874 * | |
875 * @type {string} | |
876 */ | |
877 shortcut_: '', | 522 shortcut_: '', |
878 get shortcut() { | 523 get shortcut() { |
879 return this.shortcut_; | 524 return this.shortcut_; |
880 }, | 525 }, |
881 set shortcut(shortcut) { | 526 set shortcut(shortcut) { |
882 var oldShortcut = this.shortcut_; | 527 var oldShortcut = this.shortcut_; |
883 if (shortcut !== oldShortcut) { | 528 if (shortcut !== oldShortcut) { |
884 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { | 529 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { |
885 return new KeyboardShortcut(shortcut); | 530 return new KeyboardShortcut(shortcut); |
886 }); | 531 }); |
887 | 532 |
888 // Set this after the keyboardShortcuts_ since that might throw. | |
889 this.shortcut_ = shortcut; | 533 this.shortcut_ = shortcut; |
890 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, | 534 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, |
891 oldShortcut); | 535 oldShortcut); |
892 } | 536 } |
893 }, | 537 }, |
894 | 538 |
895 /** | |
896 * Whether the event object matches the shortcut for this command. | |
897 * @param {!Event} e The key event object. | |
898 * @return {boolean} Whether it matched or not. | |
899 */ | |
900 matchesEvent: function(e) { | 539 matchesEvent: function(e) { |
901 if (!this.keyboardShortcuts_) | 540 if (!this.keyboardShortcuts_) |
902 return false; | 541 return false; |
903 | 542 |
904 return this.keyboardShortcuts_.some(function(keyboardShortcut) { | 543 return this.keyboardShortcuts_.some(function(keyboardShortcut) { |
905 return keyboardShortcut.matchesEvent(e); | 544 return keyboardShortcut.matchesEvent(e); |
906 }); | 545 }); |
907 }, | 546 }, |
908 }; | 547 }; |
909 | 548 |
910 /** | |
911 * The label of the command. | |
912 */ | |
913 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); | 549 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); |
914 | 550 |
915 /** | |
916 * Whether the command is disabled or not. | |
917 */ | |
918 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); | 551 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); |
919 | 552 |
920 /** | |
921 * Whether the command is hidden or not. | |
922 */ | |
923 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); | 553 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); |
924 | 554 |
925 /** | |
926 * Whether the command is checked or not. | |
927 */ | |
928 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR); | 555 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR); |
929 | 556 |
930 /** | |
931 * The flag that prevents the shortcut text from being displayed on menu. | |
932 * | |
933 * If false, the keyboard shortcut text (eg. "Ctrl+X" for the cut command) | |
934 * is displayed in menu when the command is assosiated with a menu item. | |
935 * Otherwise, no text is displayed. | |
936 */ | |
937 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR); | 557 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR); |
938 | 558 |
939 /** | |
940 * Dispatches a canExecute event on the target. | |
941 * @param {!cr.ui.Command} command The command that we are testing for. | |
942 * @param {EventTarget} target The target element to dispatch the event on. | |
943 */ | |
944 function dispatchCanExecuteEvent(command, target) { | 559 function dispatchCanExecuteEvent(command, target) { |
945 var e = new CanExecuteEvent(command); | 560 var e = new CanExecuteEvent(command); |
946 target.dispatchEvent(e); | 561 target.dispatchEvent(e); |
947 command.disabled = !e.canExecute; | 562 command.disabled = !e.canExecute; |
948 } | 563 } |
949 | 564 |
950 /** | |
951 * The command managers for different documents. | |
952 */ | |
953 var commandManagers = {}; | 565 var commandManagers = {}; |
954 | 566 |
955 /** | |
956 * Keeps track of the focused element and updates the commands when the focus | |
957 * changes. | |
958 * @param {!Document} doc The document that we are managing the commands for. | |
959 * @constructor | |
960 */ | |
961 function CommandManager(doc) { | 567 function CommandManager(doc) { |
962 doc.addEventListener('focus', this.handleFocus_.bind(this), true); | 568 doc.addEventListener('focus', this.handleFocus_.bind(this), true); |
963 // Make sure we add the listener to the bubbling phase so that elements can | |
964 // prevent the command. | |
965 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false); | 569 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false); |
966 } | 570 } |
967 | 571 |
968 /** | |
969 * Initializes a command manager for the document as needed. | |
970 * @param {!Document} doc The document to manage the commands for. | |
971 */ | |
972 CommandManager.init = function(doc) { | 572 CommandManager.init = function(doc) { |
973 var uid = cr.getUid(doc); | 573 var uid = cr.getUid(doc); |
974 if (!(uid in commandManagers)) { | 574 if (!(uid in commandManagers)) { |
975 commandManagers[uid] = new CommandManager(doc); | 575 commandManagers[uid] = new CommandManager(doc); |
976 } | 576 } |
977 }; | 577 }; |
978 | 578 |
979 CommandManager.prototype = { | 579 CommandManager.prototype = { |
980 | 580 |
981 /** | |
982 * Handles focus changes on the document. | |
983 * @param {Event} e The focus event object. | |
984 * @private | |
985 * @suppress {checkTypes} | |
986 * TODO(vitalyp): remove the suppression. | |
987 */ | |
988 handleFocus_: function(e) { | 581 handleFocus_: function(e) { |
989 var target = e.target; | 582 var target = e.target; |
990 | 583 |
991 // Ignore focus on a menu button or command item. | |
992 if (target.menu || target.command) | 584 if (target.menu || target.command) |
993 return; | 585 return; |
994 | 586 |
995 var commands = Array.prototype.slice.call( | 587 var commands = Array.prototype.slice.call( |
996 target.ownerDocument.querySelectorAll('command')); | 588 target.ownerDocument.querySelectorAll('command')); |
997 | 589 |
998 commands.forEach(function(command) { | 590 commands.forEach(function(command) { |
999 dispatchCanExecuteEvent(command, target); | 591 dispatchCanExecuteEvent(command, target); |
1000 }); | 592 }); |
1001 }, | 593 }, |
1002 | 594 |
1003 /** | |
1004 * Handles the keydown event and routes it to the right command. | |
1005 * @param {!Event} e The keydown event. | |
1006 */ | |
1007 handleKeyDown_: function(e) { | 595 handleKeyDown_: function(e) { |
1008 var target = e.target; | 596 var target = e.target; |
1009 var commands = Array.prototype.slice.call( | 597 var commands = Array.prototype.slice.call( |
1010 target.ownerDocument.querySelectorAll('command')); | 598 target.ownerDocument.querySelectorAll('command')); |
1011 | 599 |
1012 for (var i = 0, command; command = commands[i]; i++) { | 600 for (var i = 0, command; command = commands[i]; i++) { |
1013 if (command.matchesEvent(e)) { | 601 if (command.matchesEvent(e)) { |
1014 // When invoking a command via a shortcut, we have to manually check | |
1015 // if it can be executed, since focus might not have been changed | |
1016 // what would have updated the command's state. | |
1017 command.canExecuteChange(); | 602 command.canExecuteChange(); |
1018 | 603 |
1019 if (!command.disabled) { | 604 if (!command.disabled) { |
1020 e.preventDefault(); | 605 e.preventDefault(); |
1021 // We do not want any other element to handle this. | |
1022 e.stopPropagation(); | 606 e.stopPropagation(); |
1023 command.execute(); | 607 command.execute(); |
1024 return; | 608 return; |
1025 } | 609 } |
1026 } | 610 } |
1027 } | 611 } |
1028 } | 612 } |
1029 }; | 613 }; |
1030 | 614 |
1031 /** | |
1032 * The event type used for canExecute events. | |
1033 * @param {!cr.ui.Command} command The command that we are evaluating. | |
1034 * @extends {Event} | |
1035 * @constructor | |
1036 * @class | |
1037 */ | |
1038 function CanExecuteEvent(command) { | 615 function CanExecuteEvent(command) { |
1039 var e = new Event('canExecute', {bubbles: true, cancelable: true}); | 616 var e = new Event('canExecute', {bubbles: true, cancelable: true}); |
1040 e.__proto__ = CanExecuteEvent.prototype; | 617 e.__proto__ = CanExecuteEvent.prototype; |
1041 e.command = command; | 618 e.command = command; |
1042 return e; | 619 return e; |
1043 } | 620 } |
1044 | 621 |
1045 CanExecuteEvent.prototype = { | 622 CanExecuteEvent.prototype = { |
1046 __proto__: Event.prototype, | 623 __proto__: Event.prototype, |
1047 | 624 |
1048 /** | |
1049 * The current command | |
1050 * @type {cr.ui.Command} | |
1051 */ | |
1052 command: null, | 625 command: null, |
1053 | 626 |
1054 /** | |
1055 * Whether the target can execute the command. Setting this also stops the | |
1056 * propagation and prevents the default. Callers can tell if an event has | |
1057 * been handled via |this.defaultPrevented|. | |
1058 * @type {boolean} | |
1059 */ | |
1060 canExecute_: false, | 627 canExecute_: false, |
1061 get canExecute() { | 628 get canExecute() { |
1062 return this.canExecute_; | 629 return this.canExecute_; |
1063 }, | 630 }, |
1064 set canExecute(canExecute) { | 631 set canExecute(canExecute) { |
1065 this.canExecute_ = !!canExecute; | 632 this.canExecute_ = !!canExecute; |
1066 this.stopPropagation(); | 633 this.stopPropagation(); |
1067 this.preventDefault(); | 634 this.preventDefault(); |
1068 } | 635 } |
1069 }; | 636 }; |
1070 | 637 |
1071 // Export | |
1072 return { | 638 return { |
1073 Command: Command, | 639 Command: Command, |
1074 CanExecuteEvent: CanExecuteEvent | 640 CanExecuteEvent: CanExecuteEvent |
1075 }; | 641 }; |
1076 }); | 642 }); |
1077 Polymer({ | 643 Polymer({ |
1078 is: 'app-drawer', | 644 is: 'app-drawer', |
1079 | 645 |
1080 properties: { | 646 properties: { |
1081 /** | |
1082 * The opened state of the drawer. | |
1083 */ | |
1084 opened: { | 647 opened: { |
1085 type: Boolean, | 648 type: Boolean, |
1086 value: false, | 649 value: false, |
1087 notify: true, | 650 notify: true, |
1088 reflectToAttribute: true | 651 reflectToAttribute: true |
1089 }, | 652 }, |
1090 | 653 |
1091 /** | |
1092 * The drawer does not have a scrim and cannot be swiped close. | |
1093 */ | |
1094 persistent: { | 654 persistent: { |
1095 type: Boolean, | 655 type: Boolean, |
1096 value: false, | 656 value: false, |
1097 reflectToAttribute: true | 657 reflectToAttribute: true |
1098 }, | 658 }, |
1099 | 659 |
1100 /** | |
1101 * The alignment of the drawer on the screen ('left', 'right', 'start' o
r 'end'). | |
1102 * 'start' computes to left and 'end' to right in LTR layout and vice ve
rsa in RTL | |
1103 * layout. | |
1104 */ | |
1105 align: { | 660 align: { |
1106 type: String, | 661 type: String, |
1107 value: 'left' | 662 value: 'left' |
1108 }, | 663 }, |
1109 | 664 |
1110 /** | |
1111 * The computed, read-only position of the drawer on the screen ('left'
or 'right'). | |
1112 */ | |
1113 position: { | 665 position: { |
1114 type: String, | 666 type: String, |
1115 readOnly: true, | 667 readOnly: true, |
1116 value: 'left', | 668 value: 'left', |
1117 reflectToAttribute: true | 669 reflectToAttribute: true |
1118 }, | 670 }, |
1119 | 671 |
1120 /** | |
1121 * Create an area at the edge of the screen to swipe open the drawer. | |
1122 */ | |
1123 swipeOpen: { | 672 swipeOpen: { |
1124 type: Boolean, | 673 type: Boolean, |
1125 value: false, | 674 value: false, |
1126 reflectToAttribute: true | 675 reflectToAttribute: true |
1127 }, | 676 }, |
1128 | 677 |
1129 /** | |
1130 * Trap keyboard focus when the drawer is opened and not persistent. | |
1131 */ | |
1132 noFocusTrap: { | 678 noFocusTrap: { |
1133 type: Boolean, | 679 type: Boolean, |
1134 value: false | 680 value: false |
1135 } | 681 } |
1136 }, | 682 }, |
1137 | 683 |
1138 observers: [ | 684 observers: [ |
1139 'resetLayout(position)', | 685 'resetLayout(position)', |
1140 '_resetPosition(align, isAttached)' | 686 '_resetPosition(align, isAttached)' |
1141 ], | 687 ], |
1142 | 688 |
1143 _translateOffset: 0, | 689 _translateOffset: 0, |
1144 | 690 |
1145 _trackDetails: null, | 691 _trackDetails: null, |
1146 | 692 |
1147 _drawerState: 0, | 693 _drawerState: 0, |
1148 | 694 |
1149 _boundEscKeydownHandler: null, | 695 _boundEscKeydownHandler: null, |
1150 | 696 |
1151 _firstTabStop: null, | 697 _firstTabStop: null, |
1152 | 698 |
1153 _lastTabStop: null, | 699 _lastTabStop: null, |
1154 | 700 |
1155 ready: function() { | 701 ready: function() { |
1156 // Set the scroll direction so you can vertically scroll inside the draw
er. | |
1157 this.setScrollDirection('y'); | 702 this.setScrollDirection('y'); |
1158 | 703 |
1159 // Only transition the drawer after its first render (e.g. app-drawer-la
yout | |
1160 // may need to set the initial opened state which should not be transiti
oned). | |
1161 this._setTransitionDuration('0s'); | 704 this._setTransitionDuration('0s'); |
1162 }, | 705 }, |
1163 | 706 |
1164 attached: function() { | 707 attached: function() { |
1165 // Only transition the drawer after its first render (e.g. app-drawer-la
yout | |
1166 // may need to set the initial opened state which should not be transiti
oned). | |
1167 Polymer.RenderStatus.afterNextRender(this, function() { | 708 Polymer.RenderStatus.afterNextRender(this, function() { |
1168 this._setTransitionDuration(''); | 709 this._setTransitionDuration(''); |
1169 this._boundEscKeydownHandler = this._escKeydownHandler.bind(this); | 710 this._boundEscKeydownHandler = this._escKeydownHandler.bind(this); |
1170 this._resetDrawerState(); | 711 this._resetDrawerState(); |
1171 | 712 |
1172 this.listen(this, 'track', '_track'); | 713 this.listen(this, 'track', '_track'); |
1173 this.addEventListener('transitionend', this._transitionend.bind(this))
; | 714 this.addEventListener('transitionend', this._transitionend.bind(this))
; |
1174 this.addEventListener('keydown', this._tabKeydownHandler.bind(this)) | 715 this.addEventListener('keydown', this._tabKeydownHandler.bind(this)) |
1175 }); | 716 }); |
1176 }, | 717 }, |
1177 | 718 |
1178 detached: function() { | 719 detached: function() { |
1179 document.removeEventListener('keydown', this._boundEscKeydownHandler); | 720 document.removeEventListener('keydown', this._boundEscKeydownHandler); |
1180 }, | 721 }, |
1181 | 722 |
1182 /** | |
1183 * Opens the drawer. | |
1184 */ | |
1185 open: function() { | 723 open: function() { |
1186 this.opened = true; | 724 this.opened = true; |
1187 }, | 725 }, |
1188 | 726 |
1189 /** | |
1190 * Closes the drawer. | |
1191 */ | |
1192 close: function() { | 727 close: function() { |
1193 this.opened = false; | 728 this.opened = false; |
1194 }, | 729 }, |
1195 | 730 |
1196 /** | |
1197 * Toggles the drawer open and close. | |
1198 */ | |
1199 toggle: function() { | 731 toggle: function() { |
1200 this.opened = !this.opened; | 732 this.opened = !this.opened; |
1201 }, | 733 }, |
1202 | 734 |
1203 /** | |
1204 * Gets the width of the drawer. | |
1205 * | |
1206 * @return {number} The width of the drawer in pixels. | |
1207 */ | |
1208 getWidth: function() { | 735 getWidth: function() { |
1209 return this.$.contentContainer.offsetWidth; | 736 return this.$.contentContainer.offsetWidth; |
1210 }, | 737 }, |
1211 | 738 |
1212 /** | |
1213 * Resets the layout. If you changed the size of app-header via CSS | |
1214 * you can notify the changes by either firing the `iron-resize` event | |
1215 * or calling `resetLayout` directly. | |
1216 * | |
1217 * @method resetLayout | |
1218 */ | |
1219 resetLayout: function() { | 739 resetLayout: function() { |
1220 this.debounce('_resetLayout', function() { | 740 this.debounce('_resetLayout', function() { |
1221 this.fire('app-drawer-reset-layout'); | 741 this.fire('app-drawer-reset-layout'); |
1222 }, 1); | 742 }, 1); |
1223 }, | 743 }, |
1224 | 744 |
1225 _isRTL: function() { | 745 _isRTL: function() { |
1226 return window.getComputedStyle(this).direction === 'rtl'; | 746 return window.getComputedStyle(this).direction === 'rtl'; |
1227 }, | 747 }, |
1228 | 748 |
1229 _resetPosition: function() { | 749 _resetPosition: function() { |
1230 switch (this.align) { | 750 switch (this.align) { |
1231 case 'start': | 751 case 'start': |
1232 this._setPosition(this._isRTL() ? 'right' : 'left'); | 752 this._setPosition(this._isRTL() ? 'right' : 'left'); |
1233 return; | 753 return; |
1234 case 'end': | 754 case 'end': |
1235 this._setPosition(this._isRTL() ? 'left' : 'right'); | 755 this._setPosition(this._isRTL() ? 'left' : 'right'); |
1236 return; | 756 return; |
1237 } | 757 } |
1238 this._setPosition(this.align); | 758 this._setPosition(this.align); |
1239 }, | 759 }, |
1240 | 760 |
1241 _escKeydownHandler: function(event) { | 761 _escKeydownHandler: function(event) { |
1242 var ESC_KEYCODE = 27; | 762 var ESC_KEYCODE = 27; |
1243 if (event.keyCode === ESC_KEYCODE) { | 763 if (event.keyCode === ESC_KEYCODE) { |
1244 // Prevent any side effects if app-drawer closes. | |
1245 event.preventDefault(); | 764 event.preventDefault(); |
1246 this.close(); | 765 this.close(); |
1247 } | 766 } |
1248 }, | 767 }, |
1249 | 768 |
1250 _track: function(event) { | 769 _track: function(event) { |
1251 if (this.persistent) { | 770 if (this.persistent) { |
1252 return; | 771 return; |
1253 } | 772 } |
1254 | 773 |
1255 // Disable user selection on desktop. | |
1256 event.preventDefault(); | 774 event.preventDefault(); |
1257 | 775 |
1258 switch (event.detail.state) { | 776 switch (event.detail.state) { |
1259 case 'start': | 777 case 'start': |
1260 this._trackStart(event); | 778 this._trackStart(event); |
1261 break; | 779 break; |
1262 case 'track': | 780 case 'track': |
1263 this._trackMove(event); | 781 this._trackMove(event); |
1264 break; | 782 break; |
1265 case 'end': | 783 case 'end': |
1266 this._trackEnd(event); | 784 this._trackEnd(event); |
1267 break; | 785 break; |
1268 } | 786 } |
1269 }, | 787 }, |
1270 | 788 |
1271 _trackStart: function(event) { | 789 _trackStart: function(event) { |
1272 this._drawerState = this._DRAWER_STATE.TRACKING; | 790 this._drawerState = this._DRAWER_STATE.TRACKING; |
1273 | 791 |
1274 // Disable transitions since style attributes will reflect user track ev
ents. | |
1275 this._setTransitionDuration('0s'); | 792 this._setTransitionDuration('0s'); |
1276 this.style.visibility = 'visible'; | 793 this.style.visibility = 'visible'; |
1277 | 794 |
1278 var rect = this.$.contentContainer.getBoundingClientRect(); | 795 var rect = this.$.contentContainer.getBoundingClientRect(); |
1279 if (this.position === 'left') { | 796 if (this.position === 'left') { |
1280 this._translateOffset = rect.left; | 797 this._translateOffset = rect.left; |
1281 } else { | 798 } else { |
1282 this._translateOffset = rect.right - window.innerWidth; | 799 this._translateOffset = rect.right - window.innerWidth; |
1283 } | 800 } |
1284 | 801 |
1285 this._trackDetails = []; | 802 this._trackDetails = []; |
1286 }, | 803 }, |
1287 | 804 |
1288 _trackMove: function(event) { | 805 _trackMove: function(event) { |
1289 this._translateDrawer(event.detail.dx + this._translateOffset); | 806 this._translateDrawer(event.detail.dx + this._translateOffset); |
1290 | 807 |
1291 // Use Date.now() since event.timeStamp is inconsistent across browsers
(e.g. most | |
1292 // browsers use milliseconds but FF 44 uses microseconds). | |
1293 this._trackDetails.push({ | 808 this._trackDetails.push({ |
1294 dx: event.detail.dx, | 809 dx: event.detail.dx, |
1295 timeStamp: Date.now() | 810 timeStamp: Date.now() |
1296 }); | 811 }); |
1297 }, | 812 }, |
1298 | 813 |
1299 _trackEnd: function(event) { | 814 _trackEnd: function(event) { |
1300 var x = event.detail.dx + this._translateOffset; | 815 var x = event.detail.dx + this._translateOffset; |
1301 var drawerWidth = this.getWidth(); | 816 var drawerWidth = this.getWidth(); |
1302 var isPositionLeft = this.position === 'left'; | 817 var isPositionLeft = this.position === 'left'; |
1303 var isInEndState = isPositionLeft ? (x >= 0 || x <= -drawerWidth) : | 818 var isInEndState = isPositionLeft ? (x >= 0 || x <= -drawerWidth) : |
1304 (x <= 0 || x >= drawerWidth); | 819 (x <= 0 || x >= drawerWidth); |
1305 | 820 |
1306 if (!isInEndState) { | 821 if (!isInEndState) { |
1307 // No longer need the track events after this method returns - allow t
hem to be GC'd. | |
1308 var trackDetails = this._trackDetails; | 822 var trackDetails = this._trackDetails; |
1309 this._trackDetails = null; | 823 this._trackDetails = null; |
1310 | 824 |
1311 this._flingDrawer(event, trackDetails); | 825 this._flingDrawer(event, trackDetails); |
1312 if (this._drawerState === this._DRAWER_STATE.FLINGING) { | 826 if (this._drawerState === this._DRAWER_STATE.FLINGING) { |
1313 return; | 827 return; |
1314 } | 828 } |
1315 } | 829 } |
1316 | 830 |
1317 // If the drawer is not flinging, toggle the opened state based on the p
osition of | |
1318 // the drawer. | |
1319 var halfWidth = drawerWidth / 2; | 831 var halfWidth = drawerWidth / 2; |
1320 if (event.detail.dx < -halfWidth) { | 832 if (event.detail.dx < -halfWidth) { |
1321 this.opened = this.position === 'right'; | 833 this.opened = this.position === 'right'; |
1322 } else if (event.detail.dx > halfWidth) { | 834 } else if (event.detail.dx > halfWidth) { |
1323 this.opened = this.position === 'left'; | 835 this.opened = this.position === 'left'; |
1324 } | 836 } |
1325 | 837 |
1326 // Trigger app-drawer-transitioned now since there will be no transition
end event. | |
1327 if (isInEndState) { | 838 if (isInEndState) { |
1328 this._resetDrawerState(); | 839 this._resetDrawerState(); |
1329 } | 840 } |
1330 | 841 |
1331 this._setTransitionDuration(''); | 842 this._setTransitionDuration(''); |
1332 this._resetDrawerTranslate(); | 843 this._resetDrawerTranslate(); |
1333 this.style.visibility = ''; | 844 this.style.visibility = ''; |
1334 }, | 845 }, |
1335 | 846 |
1336 _calculateVelocity: function(event, trackDetails) { | 847 _calculateVelocity: function(event, trackDetails) { |
1337 // Find the oldest track event that is within 100ms using binary search. | |
1338 var now = Date.now(); | 848 var now = Date.now(); |
1339 var timeLowerBound = now - 100; | 849 var timeLowerBound = now - 100; |
1340 var trackDetail; | 850 var trackDetail; |
1341 var min = 0; | 851 var min = 0; |
1342 var max = trackDetails.length - 1; | 852 var max = trackDetails.length - 1; |
1343 | 853 |
1344 while (min <= max) { | 854 while (min <= max) { |
1345 // Floor of average of min and max. | |
1346 var mid = (min + max) >> 1; | 855 var mid = (min + max) >> 1; |
1347 var d = trackDetails[mid]; | 856 var d = trackDetails[mid]; |
1348 if (d.timeStamp >= timeLowerBound) { | 857 if (d.timeStamp >= timeLowerBound) { |
1349 trackDetail = d; | 858 trackDetail = d; |
1350 max = mid - 1; | 859 max = mid - 1; |
1351 } else { | 860 } else { |
1352 min = mid + 1; | 861 min = mid + 1; |
1353 } | 862 } |
1354 } | 863 } |
1355 | 864 |
1356 if (trackDetail) { | 865 if (trackDetail) { |
1357 var dx = event.detail.dx - trackDetail.dx; | 866 var dx = event.detail.dx - trackDetail.dx; |
1358 var dt = (now - trackDetail.timeStamp) || 1; | 867 var dt = (now - trackDetail.timeStamp) || 1; |
1359 return dx / dt; | 868 return dx / dt; |
1360 } | 869 } |
1361 return 0; | 870 return 0; |
1362 }, | 871 }, |
1363 | 872 |
1364 _flingDrawer: function(event, trackDetails) { | 873 _flingDrawer: function(event, trackDetails) { |
1365 var velocity = this._calculateVelocity(event, trackDetails); | 874 var velocity = this._calculateVelocity(event, trackDetails); |
1366 | 875 |
1367 // Do not fling if velocity is not above a threshold. | |
1368 if (Math.abs(velocity) < this._MIN_FLING_THRESHOLD) { | 876 if (Math.abs(velocity) < this._MIN_FLING_THRESHOLD) { |
1369 return; | 877 return; |
1370 } | 878 } |
1371 | 879 |
1372 this._drawerState = this._DRAWER_STATE.FLINGING; | 880 this._drawerState = this._DRAWER_STATE.FLINGING; |
1373 | 881 |
1374 var x = event.detail.dx + this._translateOffset; | 882 var x = event.detail.dx + this._translateOffset; |
1375 var drawerWidth = this.getWidth(); | 883 var drawerWidth = this.getWidth(); |
1376 var isPositionLeft = this.position === 'left'; | 884 var isPositionLeft = this.position === 'left'; |
1377 var isVelocityPositive = velocity > 0; | 885 var isVelocityPositive = velocity > 0; |
1378 var isClosingLeft = !isVelocityPositive && isPositionLeft; | 886 var isClosingLeft = !isVelocityPositive && isPositionLeft; |
1379 var isClosingRight = isVelocityPositive && !isPositionLeft; | 887 var isClosingRight = isVelocityPositive && !isPositionLeft; |
1380 var dx; | 888 var dx; |
1381 if (isClosingLeft) { | 889 if (isClosingLeft) { |
1382 dx = -(x + drawerWidth); | 890 dx = -(x + drawerWidth); |
1383 } else if (isClosingRight) { | 891 } else if (isClosingRight) { |
1384 dx = (drawerWidth - x); | 892 dx = (drawerWidth - x); |
1385 } else { | 893 } else { |
1386 dx = -x; | 894 dx = -x; |
1387 } | 895 } |
1388 | 896 |
1389 // Enforce a minimum transition velocity to make the drawer feel snappy. | |
1390 if (isVelocityPositive) { | 897 if (isVelocityPositive) { |
1391 velocity = Math.max(velocity, this._MIN_TRANSITION_VELOCITY); | 898 velocity = Math.max(velocity, this._MIN_TRANSITION_VELOCITY); |
1392 this.opened = this.position === 'left'; | 899 this.opened = this.position === 'left'; |
1393 } else { | 900 } else { |
1394 velocity = Math.min(velocity, -this._MIN_TRANSITION_VELOCITY); | 901 velocity = Math.min(velocity, -this._MIN_TRANSITION_VELOCITY); |
1395 this.opened = this.position === 'right'; | 902 this.opened = this.position === 'right'; |
1396 } | 903 } |
1397 | 904 |
1398 // Calculate the amount of time needed to finish the transition based on
the | |
1399 // initial slope of the timing function. | |
1400 this._setTransitionDuration((this._FLING_INITIAL_SLOPE * dx / velocity)
+ 'ms'); | 905 this._setTransitionDuration((this._FLING_INITIAL_SLOPE * dx / velocity)
+ 'ms'); |
1401 this._setTransitionTimingFunction(this._FLING_TIMING_FUNCTION); | 906 this._setTransitionTimingFunction(this._FLING_TIMING_FUNCTION); |
1402 | 907 |
1403 this._resetDrawerTranslate(); | 908 this._resetDrawerTranslate(); |
1404 }, | 909 }, |
1405 | 910 |
1406 _transitionend: function(event) { | 911 _transitionend: function(event) { |
1407 // contentContainer will transition on opened state changed, and scrim w
ill | |
1408 // transition on persistent state changed when opened - these are the | |
1409 // transitions we are interested in. | |
1410 var target = Polymer.dom(event).rootTarget; | 912 var target = Polymer.dom(event).rootTarget; |
1411 if (target === this.$.contentContainer || target === this.$.scrim) { | 913 if (target === this.$.contentContainer || target === this.$.scrim) { |
1412 | 914 |
1413 // If the drawer was flinging, we need to reset the style attributes. | |
1414 if (this._drawerState === this._DRAWER_STATE.FLINGING) { | 915 if (this._drawerState === this._DRAWER_STATE.FLINGING) { |
1415 this._setTransitionDuration(''); | 916 this._setTransitionDuration(''); |
1416 this._setTransitionTimingFunction(''); | 917 this._setTransitionTimingFunction(''); |
1417 this.style.visibility = ''; | 918 this.style.visibility = ''; |
1418 } | 919 } |
1419 | 920 |
1420 this._resetDrawerState(); | 921 this._resetDrawerState(); |
1421 } | 922 } |
1422 }, | 923 }, |
1423 | 924 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1462 if (oldState !== this._drawerState) { | 963 if (oldState !== this._drawerState) { |
1463 if (this._drawerState === this._DRAWER_STATE.OPENED) { | 964 if (this._drawerState === this._DRAWER_STATE.OPENED) { |
1464 this._setKeyboardFocusTrap(); | 965 this._setKeyboardFocusTrap(); |
1465 document.addEventListener('keydown', this._boundEscKeydownHandler); | 966 document.addEventListener('keydown', this._boundEscKeydownHandler); |
1466 document.body.style.overflow = 'hidden'; | 967 document.body.style.overflow = 'hidden'; |
1467 } else { | 968 } else { |
1468 document.removeEventListener('keydown', this._boundEscKeydownHandler
); | 969 document.removeEventListener('keydown', this._boundEscKeydownHandler
); |
1469 document.body.style.overflow = ''; | 970 document.body.style.overflow = ''; |
1470 } | 971 } |
1471 | 972 |
1472 // Don't fire the event on initial load. | |
1473 if (oldState !== this._DRAWER_STATE.INIT) { | 973 if (oldState !== this._DRAWER_STATE.INIT) { |
1474 this.fire('app-drawer-transitioned'); | 974 this.fire('app-drawer-transitioned'); |
1475 } | 975 } |
1476 } | 976 } |
1477 }, | 977 }, |
1478 | 978 |
1479 _setKeyboardFocusTrap: function() { | 979 _setKeyboardFocusTrap: function() { |
1480 if (this.noFocusTrap) { | 980 if (this.noFocusTrap) { |
1481 return; | 981 return; |
1482 } | 982 } |
1483 | 983 |
1484 // NOTE: Unless we use /deep/ (which we shouldn't since it's deprecated)
, this will | |
1485 // not select focusable elements inside shadow roots. | |
1486 var focusableElementsSelector = [ | 984 var focusableElementsSelector = [ |
1487 'a[href]:not([tabindex="-1"])', | 985 'a[href]:not([tabindex="-1"])', |
1488 'area[href]:not([tabindex="-1"])', | 986 'area[href]:not([tabindex="-1"])', |
1489 'input:not([disabled]):not([tabindex="-1"])', | 987 'input:not([disabled]):not([tabindex="-1"])', |
1490 'select:not([disabled]):not([tabindex="-1"])', | 988 'select:not([disabled]):not([tabindex="-1"])', |
1491 'textarea:not([disabled]):not([tabindex="-1"])', | 989 'textarea:not([disabled]):not([tabindex="-1"])', |
1492 'button:not([disabled]):not([tabindex="-1"])', | 990 'button:not([disabled]):not([tabindex="-1"])', |
1493 'iframe:not([tabindex="-1"])', | 991 'iframe:not([tabindex="-1"])', |
1494 '[tabindex]:not([tabindex="-1"])', | 992 '[tabindex]:not([tabindex="-1"])', |
1495 '[contentEditable=true]:not([tabindex="-1"])' | 993 '[contentEditable=true]:not([tabindex="-1"])' |
1496 ].join(','); | 994 ].join(','); |
1497 var focusableElements = Polymer.dom(this).querySelectorAll(focusableElem
entsSelector); | 995 var focusableElements = Polymer.dom(this).querySelectorAll(focusableElem
entsSelector); |
1498 | 996 |
1499 if (focusableElements.length > 0) { | 997 if (focusableElements.length > 0) { |
1500 this._firstTabStop = focusableElements[0]; | 998 this._firstTabStop = focusableElements[0]; |
1501 this._lastTabStop = focusableElements[focusableElements.length - 1]; | 999 this._lastTabStop = focusableElements[focusableElements.length - 1]; |
1502 } else { | 1000 } else { |
1503 // Reset saved tab stops when there are no focusable elements in the d
rawer. | |
1504 this._firstTabStop = null; | 1001 this._firstTabStop = null; |
1505 this._lastTabStop = null; | 1002 this._lastTabStop = null; |
1506 } | 1003 } |
1507 | 1004 |
1508 // Focus on app-drawer if it has non-zero tabindex. Otherwise, focus the
first focusable | |
1509 // element in the drawer, if it exists. Use the tabindex attribute since
the this.tabIndex | |
1510 // property in IE/Edge returns 0 (instead of -1) when the attribute is n
ot set. | |
1511 var tabindex = this.getAttribute('tabindex'); | 1005 var tabindex = this.getAttribute('tabindex'); |
1512 if (tabindex && parseInt(tabindex, 10) > -1) { | 1006 if (tabindex && parseInt(tabindex, 10) > -1) { |
1513 this.focus(); | 1007 this.focus(); |
1514 } else if (this._firstTabStop) { | 1008 } else if (this._firstTabStop) { |
1515 this._firstTabStop.focus(); | 1009 this._firstTabStop.focus(); |
1516 } | 1010 } |
1517 }, | 1011 }, |
1518 | 1012 |
1519 _tabKeydownHandler: function(event) { | 1013 _tabKeydownHandler: function(event) { |
1520 if (this.noFocusTrap) { | 1014 if (this.noFocusTrap) { |
(...skipping 26 matching lines...) Expand all Loading... |
1547 | 1041 |
1548 _DRAWER_STATE: { | 1042 _DRAWER_STATE: { |
1549 INIT: 0, | 1043 INIT: 0, |
1550 OPENED: 1, | 1044 OPENED: 1, |
1551 OPENED_PERSISTENT: 2, | 1045 OPENED_PERSISTENT: 2, |
1552 CLOSED: 3, | 1046 CLOSED: 3, |
1553 TRACKING: 4, | 1047 TRACKING: 4, |
1554 FLINGING: 5 | 1048 FLINGING: 5 |
1555 } | 1049 } |
1556 | 1050 |
1557 /** | |
1558 * Fired when the layout of app-drawer has changed. | |
1559 * | |
1560 * @event app-drawer-reset-layout | |
1561 */ | |
1562 | 1051 |
1563 /** | |
1564 * Fired when app-drawer has finished transitioning. | |
1565 * | |
1566 * @event app-drawer-transitioned | |
1567 */ | |
1568 }); | 1052 }); |
1569 (function() { | 1053 (function() { |
1570 'use strict'; | 1054 'use strict'; |
1571 | 1055 |
1572 Polymer({ | 1056 Polymer({ |
1573 is: 'iron-location', | 1057 is: 'iron-location', |
1574 properties: { | 1058 properties: { |
1575 /** | |
1576 * The pathname component of the URL. | |
1577 */ | |
1578 path: { | 1059 path: { |
1579 type: String, | 1060 type: String, |
1580 notify: true, | 1061 notify: true, |
1581 value: function() { | 1062 value: function() { |
1582 return window.decodeURIComponent(window.location.pathname); | 1063 return window.decodeURIComponent(window.location.pathname); |
1583 } | 1064 } |
1584 }, | 1065 }, |
1585 /** | |
1586 * The query string portion of the URL. | |
1587 */ | |
1588 query: { | 1066 query: { |
1589 type: String, | 1067 type: String, |
1590 notify: true, | 1068 notify: true, |
1591 value: function() { | 1069 value: function() { |
1592 return window.decodeURIComponent(window.location.search.slice(1)); | 1070 return window.decodeURIComponent(window.location.search.slice(1)); |
1593 } | 1071 } |
1594 }, | 1072 }, |
1595 /** | |
1596 * The hash component of the URL. | |
1597 */ | |
1598 hash: { | 1073 hash: { |
1599 type: String, | 1074 type: String, |
1600 notify: true, | 1075 notify: true, |
1601 value: function() { | 1076 value: function() { |
1602 return window.decodeURIComponent(window.location.hash.slice(1)); | 1077 return window.decodeURIComponent(window.location.hash.slice(1)); |
1603 } | 1078 } |
1604 }, | 1079 }, |
1605 /** | |
1606 * If the user was on a URL for less than `dwellTime` milliseconds, it | |
1607 * won't be added to the browser's history, but instead will be replaced | |
1608 * by the next entry. | |
1609 * | |
1610 * This is to prevent large numbers of entries from clogging up the user
's | |
1611 * browser history. Disable by setting to a negative number. | |
1612 */ | |
1613 dwellTime: { | 1080 dwellTime: { |
1614 type: Number, | 1081 type: Number, |
1615 value: 2000 | 1082 value: 2000 |
1616 }, | 1083 }, |
1617 | 1084 |
1618 /** | |
1619 * A regexp that defines the set of URLs that should be considered part | |
1620 * of this web app. | |
1621 * | |
1622 * Clicking on a link that matches this regex won't result in a full pag
e | |
1623 * navigation, but will instead just update the URL state in place. | |
1624 * | |
1625 * This regexp is given everything after the origin in an absolute | |
1626 * URL. So to match just URLs that start with /search/ do: | |
1627 * url-space-regex="^/search/" | |
1628 * | |
1629 * @type {string|RegExp} | |
1630 */ | |
1631 urlSpaceRegex: { | 1085 urlSpaceRegex: { |
1632 type: String, | 1086 type: String, |
1633 value: '' | 1087 value: '' |
1634 }, | 1088 }, |
1635 | 1089 |
1636 /** | |
1637 * urlSpaceRegex, but coerced into a regexp. | |
1638 * | |
1639 * @type {RegExp} | |
1640 */ | |
1641 _urlSpaceRegExp: { | 1090 _urlSpaceRegExp: { |
1642 computed: '_makeRegExp(urlSpaceRegex)' | 1091 computed: '_makeRegExp(urlSpaceRegex)' |
1643 }, | 1092 }, |
1644 | 1093 |
1645 _lastChangedAt: { | 1094 _lastChangedAt: { |
1646 type: Number | 1095 type: Number |
1647 }, | 1096 }, |
1648 | 1097 |
1649 _initialized: { | 1098 _initialized: { |
1650 type: Boolean, | 1099 type: Boolean, |
1651 value: false | 1100 value: false |
1652 } | 1101 } |
1653 }, | 1102 }, |
1654 hostAttributes: { | 1103 hostAttributes: { |
1655 hidden: true | 1104 hidden: true |
1656 }, | 1105 }, |
1657 observers: [ | 1106 observers: [ |
1658 '_updateUrl(path, query, hash)' | 1107 '_updateUrl(path, query, hash)' |
1659 ], | 1108 ], |
1660 attached: function() { | 1109 attached: function() { |
1661 this.listen(window, 'hashchange', '_hashChanged'); | 1110 this.listen(window, 'hashchange', '_hashChanged'); |
1662 this.listen(window, 'location-changed', '_urlChanged'); | 1111 this.listen(window, 'location-changed', '_urlChanged'); |
1663 this.listen(window, 'popstate', '_urlChanged'); | 1112 this.listen(window, 'popstate', '_urlChanged'); |
1664 this.listen(/** @type {!HTMLBodyElement} */(document.body), 'click', '_g
lobalOnClick'); | 1113 this.listen(/** @type {!HTMLBodyElement} */(document.body), 'click', '_g
lobalOnClick'); |
1665 // Give a 200ms grace period to make initial redirects without any | |
1666 // additions to the user's history. | |
1667 this._lastChangedAt = window.performance.now() - (this.dwellTime - 200); | 1114 this._lastChangedAt = window.performance.now() - (this.dwellTime - 200); |
1668 | 1115 |
1669 this._initialized = true; | 1116 this._initialized = true; |
1670 this._urlChanged(); | 1117 this._urlChanged(); |
1671 }, | 1118 }, |
1672 detached: function() { | 1119 detached: function() { |
1673 this.unlisten(window, 'hashchange', '_hashChanged'); | 1120 this.unlisten(window, 'hashchange', '_hashChanged'); |
1674 this.unlisten(window, 'location-changed', '_urlChanged'); | 1121 this.unlisten(window, 'location-changed', '_urlChanged'); |
1675 this.unlisten(window, 'popstate', '_urlChanged'); | 1122 this.unlisten(window, 'popstate', '_urlChanged'); |
1676 this.unlisten(/** @type {!HTMLBodyElement} */(document.body), 'click', '
_globalOnClick'); | 1123 this.unlisten(/** @type {!HTMLBodyElement} */(document.body), 'click', '
_globalOnClick'); |
1677 this._initialized = false; | 1124 this._initialized = false; |
1678 }, | 1125 }, |
1679 _hashChanged: function() { | 1126 _hashChanged: function() { |
1680 this.hash = window.decodeURIComponent(window.location.hash.substring(1))
; | 1127 this.hash = window.decodeURIComponent(window.location.hash.substring(1))
; |
1681 }, | 1128 }, |
1682 _urlChanged: function() { | 1129 _urlChanged: function() { |
1683 // We want to extract all info out of the updated URL before we | |
1684 // try to write anything back into it. | |
1685 // | |
1686 // i.e. without _dontUpdateUrl we'd overwrite the new path with the old | |
1687 // one when we set this.hash. Likewise for query. | |
1688 this._dontUpdateUrl = true; | 1130 this._dontUpdateUrl = true; |
1689 this._hashChanged(); | 1131 this._hashChanged(); |
1690 this.path = window.decodeURIComponent(window.location.pathname); | 1132 this.path = window.decodeURIComponent(window.location.pathname); |
1691 this.query = window.decodeURIComponent( | 1133 this.query = window.decodeURIComponent( |
1692 window.location.search.substring(1)); | 1134 window.location.search.substring(1)); |
1693 this._dontUpdateUrl = false; | 1135 this._dontUpdateUrl = false; |
1694 this._updateUrl(); | 1136 this._updateUrl(); |
1695 }, | 1137 }, |
1696 _getUrl: function() { | 1138 _getUrl: function() { |
1697 var partiallyEncodedPath = window.encodeURI( | 1139 var partiallyEncodedPath = window.encodeURI( |
(...skipping 12 matching lines...) Expand all Loading... |
1710 }, | 1152 }, |
1711 _updateUrl: function() { | 1153 _updateUrl: function() { |
1712 if (this._dontUpdateUrl || !this._initialized) { | 1154 if (this._dontUpdateUrl || !this._initialized) { |
1713 return; | 1155 return; |
1714 } | 1156 } |
1715 if (this.path === window.decodeURIComponent(window.location.pathname) && | 1157 if (this.path === window.decodeURIComponent(window.location.pathname) && |
1716 this.query === window.decodeURIComponent( | 1158 this.query === window.decodeURIComponent( |
1717 window.location.search.substring(1)) && | 1159 window.location.search.substring(1)) && |
1718 this.hash === window.decodeURIComponent( | 1160 this.hash === window.decodeURIComponent( |
1719 window.location.hash.substring(1))) { | 1161 window.location.hash.substring(1))) { |
1720 // Nothing to do, the current URL is a representation of our propertie
s. | |
1721 return; | 1162 return; |
1722 } | 1163 } |
1723 var newUrl = this._getUrl(); | 1164 var newUrl = this._getUrl(); |
1724 // Need to use a full URL in case the containing page has a base URI. | |
1725 var fullNewUrl = new URL( | 1165 var fullNewUrl = new URL( |
1726 newUrl, window.location.protocol + '//' + window.location.host).href
; | 1166 newUrl, window.location.protocol + '//' + window.location.host).href
; |
1727 var now = window.performance.now(); | 1167 var now = window.performance.now(); |
1728 var shouldReplace = | 1168 var shouldReplace = |
1729 this._lastChangedAt + this.dwellTime > now; | 1169 this._lastChangedAt + this.dwellTime > now; |
1730 this._lastChangedAt = now; | 1170 this._lastChangedAt = now; |
1731 if (shouldReplace) { | 1171 if (shouldReplace) { |
1732 window.history.replaceState({}, '', fullNewUrl); | 1172 window.history.replaceState({}, '', fullNewUrl); |
1733 } else { | 1173 } else { |
1734 window.history.pushState({}, '', fullNewUrl); | 1174 window.history.pushState({}, '', fullNewUrl); |
1735 } | 1175 } |
1736 this.fire('location-changed', {}, {node: window}); | 1176 this.fire('location-changed', {}, {node: window}); |
1737 }, | 1177 }, |
1738 /** | |
1739 * A necessary evil so that links work as expected. Does its best to | |
1740 * bail out early if possible. | |
1741 * | |
1742 * @param {MouseEvent} event . | |
1743 */ | |
1744 _globalOnClick: function(event) { | 1178 _globalOnClick: function(event) { |
1745 // If another event handler has stopped this event then there's nothing | |
1746 // for us to do. This can happen e.g. when there are multiple | |
1747 // iron-location elements in a page. | |
1748 if (event.defaultPrevented) { | 1179 if (event.defaultPrevented) { |
1749 return; | 1180 return; |
1750 } | 1181 } |
1751 var href = this._getSameOriginLinkHref(event); | 1182 var href = this._getSameOriginLinkHref(event); |
1752 if (!href) { | 1183 if (!href) { |
1753 return; | 1184 return; |
1754 } | 1185 } |
1755 event.preventDefault(); | 1186 event.preventDefault(); |
1756 // If the navigation is to the current page we shouldn't add a history | |
1757 // entry or fire a change event. | |
1758 if (href === window.location.href) { | 1187 if (href === window.location.href) { |
1759 return; | 1188 return; |
1760 } | 1189 } |
1761 window.history.pushState({}, '', href); | 1190 window.history.pushState({}, '', href); |
1762 this.fire('location-changed', {}, {node: window}); | 1191 this.fire('location-changed', {}, {node: window}); |
1763 }, | 1192 }, |
1764 /** | |
1765 * Returns the absolute URL of the link (if any) that this click event | |
1766 * is clicking on, if we can and should override the resulting full | |
1767 * page navigation. Returns null otherwise. | |
1768 * | |
1769 * @param {MouseEvent} event . | |
1770 * @return {string?} . | |
1771 */ | |
1772 _getSameOriginLinkHref: function(event) { | 1193 _getSameOriginLinkHref: function(event) { |
1773 // We only care about left-clicks. | |
1774 if (event.button !== 0) { | 1194 if (event.button !== 0) { |
1775 return null; | 1195 return null; |
1776 } | 1196 } |
1777 // We don't want modified clicks, where the intent is to open the page | |
1778 // in a new tab. | |
1779 if (event.metaKey || event.ctrlKey) { | 1197 if (event.metaKey || event.ctrlKey) { |
1780 return null; | 1198 return null; |
1781 } | 1199 } |
1782 var eventPath = Polymer.dom(event).path; | 1200 var eventPath = Polymer.dom(event).path; |
1783 var anchor = null; | 1201 var anchor = null; |
1784 for (var i = 0; i < eventPath.length; i++) { | 1202 for (var i = 0; i < eventPath.length; i++) { |
1785 var element = eventPath[i]; | 1203 var element = eventPath[i]; |
1786 if (element.tagName === 'A' && element.href) { | 1204 if (element.tagName === 'A' && element.href) { |
1787 anchor = element; | 1205 anchor = element; |
1788 break; | 1206 break; |
1789 } | 1207 } |
1790 } | 1208 } |
1791 | 1209 |
1792 // If there's no link there's nothing to do. | |
1793 if (!anchor) { | 1210 if (!anchor) { |
1794 return null; | 1211 return null; |
1795 } | 1212 } |
1796 | 1213 |
1797 // Target blank is a new tab, don't intercept. | |
1798 if (anchor.target === '_blank') { | 1214 if (anchor.target === '_blank') { |
1799 return null; | 1215 return null; |
1800 } | 1216 } |
1801 // If the link is for an existing parent frame, don't intercept. | |
1802 if ((anchor.target === '_top' || | 1217 if ((anchor.target === '_top' || |
1803 anchor.target === '_parent') && | 1218 anchor.target === '_parent') && |
1804 window.top !== window) { | 1219 window.top !== window) { |
1805 return null; | 1220 return null; |
1806 } | 1221 } |
1807 | 1222 |
1808 var href = anchor.href; | 1223 var href = anchor.href; |
1809 | 1224 |
1810 // It only makes sense for us to intercept same-origin navigations. | |
1811 // pushState/replaceState don't work with cross-origin links. | |
1812 var url; | 1225 var url; |
1813 if (document.baseURI != null) { | 1226 if (document.baseURI != null) { |
1814 url = new URL(href, /** @type {string} */(document.baseURI)); | 1227 url = new URL(href, /** @type {string} */(document.baseURI)); |
1815 } else { | 1228 } else { |
1816 url = new URL(href); | 1229 url = new URL(href); |
1817 } | 1230 } |
1818 | 1231 |
1819 var origin; | 1232 var origin; |
1820 | 1233 |
1821 // IE Polyfill | |
1822 if (window.location.origin) { | 1234 if (window.location.origin) { |
1823 origin = window.location.origin; | 1235 origin = window.location.origin; |
1824 } else { | 1236 } else { |
1825 origin = window.location.protocol + '//' + window.location.hostname; | 1237 origin = window.location.protocol + '//' + window.location.hostname; |
1826 | 1238 |
1827 if (window.location.port) { | 1239 if (window.location.port) { |
1828 origin += ':' + window.location.port; | 1240 origin += ':' + window.location.port; |
1829 } | 1241 } |
1830 } | 1242 } |
1831 | 1243 |
1832 if (url.origin !== origin) { | 1244 if (url.origin !== origin) { |
1833 return null; | 1245 return null; |
1834 } | 1246 } |
1835 var normalizedHref = url.pathname + url.search + url.hash; | 1247 var normalizedHref = url.pathname + url.search + url.hash; |
1836 | 1248 |
1837 // If we've been configured not to handle this url... don't handle it! | |
1838 if (this._urlSpaceRegExp && | 1249 if (this._urlSpaceRegExp && |
1839 !this._urlSpaceRegExp.test(normalizedHref)) { | 1250 !this._urlSpaceRegExp.test(normalizedHref)) { |
1840 return null; | 1251 return null; |
1841 } | 1252 } |
1842 // Need to use a full URL in case the containing page has a base URI. | |
1843 var fullNormalizedHref = new URL( | 1253 var fullNormalizedHref = new URL( |
1844 normalizedHref, window.location.href).href; | 1254 normalizedHref, window.location.href).href; |
1845 return fullNormalizedHref; | 1255 return fullNormalizedHref; |
1846 }, | 1256 }, |
1847 _makeRegExp: function(urlSpaceRegex) { | 1257 _makeRegExp: function(urlSpaceRegex) { |
1848 return RegExp(urlSpaceRegex); | 1258 return RegExp(urlSpaceRegex); |
1849 } | 1259 } |
1850 }); | 1260 }); |
1851 })(); | 1261 })(); |
1852 'use strict'; | 1262 'use strict'; |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1900 '=' + | 1310 '=' + |
1901 encodeURIComponent(value.toString()) | 1311 encodeURIComponent(value.toString()) |
1902 ); | 1312 ); |
1903 } | 1313 } |
1904 } | 1314 } |
1905 return encodedParams.join('&'); | 1315 return encodedParams.join('&'); |
1906 }, | 1316 }, |
1907 _decodeParams: function(paramString) { | 1317 _decodeParams: function(paramString) { |
1908 var params = {}; | 1318 var params = {}; |
1909 | 1319 |
1910 // Work around a bug in decodeURIComponent where + is not | |
1911 // converted to spaces: | |
1912 paramString = (paramString || '').replace(/\+/g, '%20'); | 1320 paramString = (paramString || '').replace(/\+/g, '%20'); |
1913 | 1321 |
1914 var paramList = paramString.split('&'); | 1322 var paramList = paramString.split('&'); |
1915 for (var i = 0; i < paramList.length; i++) { | 1323 for (var i = 0; i < paramList.length; i++) { |
1916 var param = paramList[i].split('='); | 1324 var param = paramList[i].split('='); |
1917 if (param[0]) { | 1325 if (param[0]) { |
1918 params[decodeURIComponent(param[0])] = | 1326 params[decodeURIComponent(param[0])] = |
1919 decodeURIComponent(param[1] || ''); | 1327 decodeURIComponent(param[1] || ''); |
1920 } | 1328 } |
1921 } | 1329 } |
1922 return params; | 1330 return params; |
1923 } | 1331 } |
1924 }); | 1332 }); |
1925 'use strict'; | 1333 'use strict'; |
1926 | 1334 |
1927 /** | |
1928 * Provides bidirectional mapping between `path` and `queryParams` and a | |
1929 * app-route compatible `route` object. | |
1930 * | |
1931 * For more information, see the docs for `app-route-converter`. | |
1932 * | |
1933 * @polymerBehavior | |
1934 */ | |
1935 Polymer.AppRouteConverterBehavior = { | 1335 Polymer.AppRouteConverterBehavior = { |
1936 properties: { | 1336 properties: { |
1937 /** | |
1938 * A model representing the deserialized path through the route tree, as | |
1939 * well as the current queryParams. | |
1940 * | |
1941 * A route object is the kernel of the routing system. It is intended to | |
1942 * be fed into consuming elements such as `app-route`. | |
1943 * | |
1944 * @type {?Object} | |
1945 */ | |
1946 route: { | 1337 route: { |
1947 type: Object, | 1338 type: Object, |
1948 notify: true | 1339 notify: true |
1949 }, | 1340 }, |
1950 | 1341 |
1951 /** | |
1952 * A set of key/value pairs that are universally accessible to branches of | |
1953 * the route tree. | |
1954 * | |
1955 * @type {?Object} | |
1956 */ | |
1957 queryParams: { | 1342 queryParams: { |
1958 type: Object, | 1343 type: Object, |
1959 notify: true | 1344 notify: true |
1960 }, | 1345 }, |
1961 | 1346 |
1962 /** | |
1963 * The serialized path through the route tree. This corresponds to the | |
1964 * `window.location.pathname` value, and will update to reflect changes | |
1965 * to that value. | |
1966 */ | |
1967 path: { | 1347 path: { |
1968 type: String, | 1348 type: String, |
1969 notify: true, | 1349 notify: true, |
1970 } | 1350 } |
1971 }, | 1351 }, |
1972 | 1352 |
1973 observers: [ | 1353 observers: [ |
1974 '_locationChanged(path, queryParams)', | 1354 '_locationChanged(path, queryParams)', |
1975 '_routeChanged(route.prefix, route.path)', | 1355 '_routeChanged(route.prefix, route.path)', |
1976 '_routeQueryParamsChanged(route.__queryParams)' | 1356 '_routeQueryParamsChanged(route.__queryParams)' |
1977 ], | 1357 ], |
1978 | 1358 |
1979 created: function() { | 1359 created: function() { |
1980 this.linkPaths('route.__queryParams', 'queryParams'); | 1360 this.linkPaths('route.__queryParams', 'queryParams'); |
1981 this.linkPaths('queryParams', 'route.__queryParams'); | 1361 this.linkPaths('queryParams', 'route.__queryParams'); |
1982 }, | 1362 }, |
1983 | 1363 |
1984 /** | |
1985 * Handler called when the path or queryParams change. | |
1986 */ | |
1987 _locationChanged: function() { | 1364 _locationChanged: function() { |
1988 if (this.route && | 1365 if (this.route && |
1989 this.route.path === this.path && | 1366 this.route.path === this.path && |
1990 this.queryParams === this.route.__queryParams) { | 1367 this.queryParams === this.route.__queryParams) { |
1991 return; | 1368 return; |
1992 } | 1369 } |
1993 this.route = { | 1370 this.route = { |
1994 prefix: '', | 1371 prefix: '', |
1995 path: this.path, | 1372 path: this.path, |
1996 __queryParams: this.queryParams | 1373 __queryParams: this.queryParams |
1997 }; | 1374 }; |
1998 }, | 1375 }, |
1999 | 1376 |
2000 /** | |
2001 * Handler called when the route prefix and route path change. | |
2002 */ | |
2003 _routeChanged: function() { | 1377 _routeChanged: function() { |
2004 if (!this.route) { | 1378 if (!this.route) { |
2005 return; | 1379 return; |
2006 } | 1380 } |
2007 | 1381 |
2008 this.path = this.route.prefix + this.route.path; | 1382 this.path = this.route.prefix + this.route.path; |
2009 }, | 1383 }, |
2010 | 1384 |
2011 /** | |
2012 * Handler called when the route queryParams change. | |
2013 * | |
2014 * @param {Object} queryParams A set of key/value pairs that are | |
2015 * universally accessible to branches of the route tree. | |
2016 */ | |
2017 _routeQueryParamsChanged: function(queryParams) { | 1385 _routeQueryParamsChanged: function(queryParams) { |
2018 if (!this.route) { | 1386 if (!this.route) { |
2019 return; | 1387 return; |
2020 } | 1388 } |
2021 this.queryParams = queryParams; | 1389 this.queryParams = queryParams; |
2022 } | 1390 } |
2023 }; | 1391 }; |
2024 'use strict'; | 1392 'use strict'; |
2025 | 1393 |
2026 Polymer({ | 1394 Polymer({ |
2027 is: 'app-location', | 1395 is: 'app-location', |
2028 | 1396 |
2029 properties: { | 1397 properties: { |
2030 /** | |
2031 * A model representing the deserialized path through the route tree, as | |
2032 * well as the current queryParams. | |
2033 */ | |
2034 route: { | 1398 route: { |
2035 type: Object, | 1399 type: Object, |
2036 notify: true | 1400 notify: true |
2037 }, | 1401 }, |
2038 | 1402 |
2039 /** | |
2040 * In many scenarios, it is convenient to treat the `hash` as a stand-in | |
2041 * alternative to the `path`. For example, if deploying an app to a stat
ic | |
2042 * web server (e.g., Github Pages) - where one does not have control ove
r | |
2043 * server-side routing - it is usually a better experience to use the ha
sh | |
2044 * to represent paths through one's app. | |
2045 * | |
2046 * When this property is set to true, the `hash` will be used in place o
f | |
2047 | |
2048 * the `path` for generating a `route`. | |
2049 */ | |
2050 useHashAsPath: { | 1403 useHashAsPath: { |
2051 type: Boolean, | 1404 type: Boolean, |
2052 value: false | 1405 value: false |
2053 }, | 1406 }, |
2054 | 1407 |
2055 /** | |
2056 * A regexp that defines the set of URLs that should be considered part | |
2057 * of this web app. | |
2058 * | |
2059 * Clicking on a link that matches this regex won't result in a full pag
e | |
2060 * navigation, but will instead just update the URL state in place. | |
2061 * | |
2062 * This regexp is given everything after the origin in an absolute | |
2063 * URL. So to match just URLs that start with /search/ do: | |
2064 * url-space-regex="^/search/" | |
2065 * | |
2066 * @type {string|RegExp} | |
2067 */ | |
2068 urlSpaceRegex: { | 1408 urlSpaceRegex: { |
2069 type: String, | 1409 type: String, |
2070 notify: true | 1410 notify: true |
2071 }, | 1411 }, |
2072 | 1412 |
2073 /** | |
2074 * A set of key/value pairs that are universally accessible to branches | |
2075 * of the route tree. | |
2076 */ | |
2077 __queryParams: { | 1413 __queryParams: { |
2078 type: Object | 1414 type: Object |
2079 }, | 1415 }, |
2080 | 1416 |
2081 /** | |
2082 * The pathname component of the current URL. | |
2083 */ | |
2084 __path: { | 1417 __path: { |
2085 type: String | 1418 type: String |
2086 }, | 1419 }, |
2087 | 1420 |
2088 /** | |
2089 * The query string portion of the current URL. | |
2090 */ | |
2091 __query: { | 1421 __query: { |
2092 type: String | 1422 type: String |
2093 }, | 1423 }, |
2094 | 1424 |
2095 /** | |
2096 * The hash portion of the current URL. | |
2097 */ | |
2098 __hash: { | 1425 __hash: { |
2099 type: String | 1426 type: String |
2100 }, | 1427 }, |
2101 | 1428 |
2102 /** | |
2103 * The route path, which will be either the hash or the path, depending | |
2104 * on useHashAsPath. | |
2105 */ | |
2106 path: { | 1429 path: { |
2107 type: String, | 1430 type: String, |
2108 observer: '__onPathChanged' | 1431 observer: '__onPathChanged' |
2109 } | 1432 } |
2110 }, | 1433 }, |
2111 | 1434 |
2112 behaviors: [Polymer.AppRouteConverterBehavior], | 1435 behaviors: [Polymer.AppRouteConverterBehavior], |
2113 | 1436 |
2114 observers: [ | 1437 observers: [ |
2115 '__computeRoutePath(useHashAsPath, __hash, __path)' | 1438 '__computeRoutePath(useHashAsPath, __hash, __path)' |
(...skipping 14 matching lines...) Expand all Loading... |
2130 this.__path = this.path; | 1453 this.__path = this.path; |
2131 } | 1454 } |
2132 } | 1455 } |
2133 }); | 1456 }); |
2134 'use strict'; | 1457 'use strict'; |
2135 | 1458 |
2136 Polymer({ | 1459 Polymer({ |
2137 is: 'app-route', | 1460 is: 'app-route', |
2138 | 1461 |
2139 properties: { | 1462 properties: { |
2140 /** | |
2141 * The URL component managed by this element. | |
2142 */ | |
2143 route: { | 1463 route: { |
2144 type: Object, | 1464 type: Object, |
2145 notify: true | 1465 notify: true |
2146 }, | 1466 }, |
2147 | 1467 |
2148 /** | |
2149 * The pattern of slash-separated segments to match `path` against. | |
2150 * | |
2151 * For example the pattern "/foo" will match "/foo" or "/foo/bar" | |
2152 * but not "/foobar". | |
2153 * | |
2154 * Path segments like `/:named` are mapped to properties on the `data` obj
ect. | |
2155 */ | |
2156 pattern: { | 1468 pattern: { |
2157 type: String | 1469 type: String |
2158 }, | 1470 }, |
2159 | 1471 |
2160 /** | |
2161 * The parameterized values that are extracted from the route as | |
2162 * described by `pattern`. | |
2163 */ | |
2164 data: { | 1472 data: { |
2165 type: Object, | 1473 type: Object, |
2166 value: function() {return {};}, | 1474 value: function() {return {};}, |
2167 notify: true | 1475 notify: true |
2168 }, | 1476 }, |
2169 | 1477 |
2170 /** | |
2171 * @type {?Object} | |
2172 */ | |
2173 queryParams: { | 1478 queryParams: { |
2174 type: Object, | 1479 type: Object, |
2175 value: function() { | 1480 value: function() { |
2176 return {}; | 1481 return {}; |
2177 }, | 1482 }, |
2178 notify: true | 1483 notify: true |
2179 }, | 1484 }, |
2180 | 1485 |
2181 /** | |
2182 * The part of `path` NOT consumed by `pattern`. | |
2183 */ | |
2184 tail: { | 1486 tail: { |
2185 type: Object, | 1487 type: Object, |
2186 value: function() {return {path: null, prefix: null, __queryParams: null
};}, | 1488 value: function() {return {path: null, prefix: null, __queryParams: null
};}, |
2187 notify: true | 1489 notify: true |
2188 }, | 1490 }, |
2189 | 1491 |
2190 active: { | 1492 active: { |
2191 type: Boolean, | 1493 type: Boolean, |
2192 notify: true, | 1494 notify: true, |
2193 readOnly: true | 1495 readOnly: true |
2194 }, | 1496 }, |
2195 | 1497 |
2196 _queryParamsUpdating: { | 1498 _queryParamsUpdating: { |
2197 type: Boolean, | 1499 type: Boolean, |
2198 value: false | 1500 value: false |
2199 }, | 1501 }, |
2200 /** | |
2201 * @type {?string} | |
2202 */ | |
2203 _matched: { | 1502 _matched: { |
2204 type: String, | 1503 type: String, |
2205 value: '' | 1504 value: '' |
2206 } | 1505 } |
2207 }, | 1506 }, |
2208 | 1507 |
2209 observers: [ | 1508 observers: [ |
2210 '__tryToMatch(route.path, pattern)', | 1509 '__tryToMatch(route.path, pattern)', |
2211 '__updatePathOnDataChange(data.*)', | 1510 '__updatePathOnDataChange(data.*)', |
2212 '__tailPathChanged(tail.path)', | 1511 '__tailPathChanged(tail.path)', |
2213 '__routeQueryParamsChanged(route.__queryParams)', | 1512 '__routeQueryParamsChanged(route.__queryParams)', |
2214 '__tailQueryParamsChanged(tail.__queryParams)', | 1513 '__tailQueryParamsChanged(tail.__queryParams)', |
2215 '__queryParamsChanged(queryParams.*)' | 1514 '__queryParamsChanged(queryParams.*)' |
2216 ], | 1515 ], |
2217 | 1516 |
2218 created: function() { | 1517 created: function() { |
2219 this.linkPaths('route.__queryParams', 'tail.__queryParams'); | 1518 this.linkPaths('route.__queryParams', 'tail.__queryParams'); |
2220 this.linkPaths('tail.__queryParams', 'route.__queryParams'); | 1519 this.linkPaths('tail.__queryParams', 'route.__queryParams'); |
2221 }, | 1520 }, |
2222 | 1521 |
2223 /** | |
2224 * Deal with the query params object being assigned to wholesale. | |
2225 * @export | |
2226 */ | |
2227 __routeQueryParamsChanged: function(queryParams) { | 1522 __routeQueryParamsChanged: function(queryParams) { |
2228 if (queryParams && this.tail) { | 1523 if (queryParams && this.tail) { |
2229 this.set('tail.__queryParams', queryParams); | 1524 this.set('tail.__queryParams', queryParams); |
2230 | 1525 |
2231 if (!this.active || this._queryParamsUpdating) { | 1526 if (!this.active || this._queryParamsUpdating) { |
2232 return; | 1527 return; |
2233 } | 1528 } |
2234 | 1529 |
2235 // Copy queryParams and track whether there are any differences compared | |
2236 // to the existing query params. | |
2237 var copyOfQueryParams = {}; | 1530 var copyOfQueryParams = {}; |
2238 var anythingChanged = false; | 1531 var anythingChanged = false; |
2239 for (var key in queryParams) { | 1532 for (var key in queryParams) { |
2240 copyOfQueryParams[key] = queryParams[key]; | 1533 copyOfQueryParams[key] = queryParams[key]; |
2241 if (anythingChanged || | 1534 if (anythingChanged || |
2242 !this.queryParams || | 1535 !this.queryParams || |
2243 queryParams[key] !== this.queryParams[key]) { | 1536 queryParams[key] !== this.queryParams[key]) { |
2244 anythingChanged = true; | 1537 anythingChanged = true; |
2245 } | 1538 } |
2246 } | 1539 } |
2247 // Need to check whether any keys were deleted | |
2248 for (var key in this.queryParams) { | 1540 for (var key in this.queryParams) { |
2249 if (anythingChanged || !(key in queryParams)) { | 1541 if (anythingChanged || !(key in queryParams)) { |
2250 anythingChanged = true; | 1542 anythingChanged = true; |
2251 break; | 1543 break; |
2252 } | 1544 } |
2253 } | 1545 } |
2254 | 1546 |
2255 if (!anythingChanged) { | 1547 if (!anythingChanged) { |
2256 return; | 1548 return; |
2257 } | 1549 } |
2258 this._queryParamsUpdating = true; | 1550 this._queryParamsUpdating = true; |
2259 this.set('queryParams', copyOfQueryParams); | 1551 this.set('queryParams', copyOfQueryParams); |
2260 this._queryParamsUpdating = false; | 1552 this._queryParamsUpdating = false; |
2261 } | 1553 } |
2262 }, | 1554 }, |
2263 | 1555 |
2264 /** | |
2265 * @export | |
2266 */ | |
2267 __tailQueryParamsChanged: function(queryParams) { | 1556 __tailQueryParamsChanged: function(queryParams) { |
2268 if (queryParams && this.route) { | 1557 if (queryParams && this.route) { |
2269 this.set('route.__queryParams', queryParams); | 1558 this.set('route.__queryParams', queryParams); |
2270 } | 1559 } |
2271 }, | 1560 }, |
2272 | 1561 |
2273 /** | |
2274 * @export | |
2275 */ | |
2276 __queryParamsChanged: function(changes) { | 1562 __queryParamsChanged: function(changes) { |
2277 if (!this.active || this._queryParamsUpdating) { | 1563 if (!this.active || this._queryParamsUpdating) { |
2278 return; | 1564 return; |
2279 } | 1565 } |
2280 | 1566 |
2281 this.set('route.__' + changes.path, changes.value); | 1567 this.set('route.__' + changes.path, changes.value); |
2282 }, | 1568 }, |
2283 | 1569 |
2284 __resetProperties: function() { | 1570 __resetProperties: function() { |
2285 this._setActive(false); | 1571 this._setActive(false); |
2286 this._matched = null; | 1572 this._matched = null; |
2287 //this.tail = { path: null, prefix: null, queryParams: null }; | |
2288 //this.data = {}; | |
2289 }, | 1573 }, |
2290 | 1574 |
2291 /** | |
2292 * @export | |
2293 */ | |
2294 __tryToMatch: function() { | 1575 __tryToMatch: function() { |
2295 if (!this.route) { | 1576 if (!this.route) { |
2296 return; | 1577 return; |
2297 } | 1578 } |
2298 var path = this.route.path; | 1579 var path = this.route.path; |
2299 var pattern = this.pattern; | 1580 var pattern = this.pattern; |
2300 if (!pattern) { | 1581 if (!pattern) { |
2301 return; | 1582 return; |
2302 } | 1583 } |
2303 | 1584 |
2304 if (!path) { | 1585 if (!path) { |
2305 this.__resetProperties(); | 1586 this.__resetProperties(); |
2306 return; | 1587 return; |
2307 } | 1588 } |
2308 | 1589 |
2309 var remainingPieces = path.split('/'); | 1590 var remainingPieces = path.split('/'); |
2310 var patternPieces = pattern.split('/'); | 1591 var patternPieces = pattern.split('/'); |
2311 | 1592 |
2312 var matched = []; | 1593 var matched = []; |
2313 var namedMatches = {}; | 1594 var namedMatches = {}; |
2314 | 1595 |
2315 for (var i=0; i < patternPieces.length; i++) { | 1596 for (var i=0; i < patternPieces.length; i++) { |
2316 var patternPiece = patternPieces[i]; | 1597 var patternPiece = patternPieces[i]; |
2317 if (!patternPiece && patternPiece !== '') { | 1598 if (!patternPiece && patternPiece !== '') { |
2318 break; | 1599 break; |
2319 } | 1600 } |
2320 var pathPiece = remainingPieces.shift(); | 1601 var pathPiece = remainingPieces.shift(); |
2321 | 1602 |
2322 // We don't match this path. | |
2323 if (!pathPiece && pathPiece !== '') { | 1603 if (!pathPiece && pathPiece !== '') { |
2324 this.__resetProperties(); | 1604 this.__resetProperties(); |
2325 return; | 1605 return; |
2326 } | 1606 } |
2327 matched.push(pathPiece); | 1607 matched.push(pathPiece); |
2328 | 1608 |
2329 if (patternPiece.charAt(0) == ':') { | 1609 if (patternPiece.charAt(0) == ':') { |
2330 namedMatches[patternPiece.slice(1)] = pathPiece; | 1610 namedMatches[patternPiece.slice(1)] = pathPiece; |
2331 } else if (patternPiece !== pathPiece) { | 1611 } else if (patternPiece !== pathPiece) { |
2332 this.__resetProperties(); | 1612 this.__resetProperties(); |
2333 return; | 1613 return; |
2334 } | 1614 } |
2335 } | 1615 } |
2336 | 1616 |
2337 this._matched = matched.join('/'); | 1617 this._matched = matched.join('/'); |
2338 | 1618 |
2339 // Properties that must be updated atomically. | |
2340 var propertyUpdates = {}; | 1619 var propertyUpdates = {}; |
2341 | 1620 |
2342 //this.active | |
2343 if (!this.active) { | 1621 if (!this.active) { |
2344 propertyUpdates.active = true; | 1622 propertyUpdates.active = true; |
2345 } | 1623 } |
2346 | 1624 |
2347 // this.tail | |
2348 var tailPrefix = this.route.prefix + this._matched; | 1625 var tailPrefix = this.route.prefix + this._matched; |
2349 var tailPath = remainingPieces.join('/'); | 1626 var tailPath = remainingPieces.join('/'); |
2350 if (remainingPieces.length > 0) { | 1627 if (remainingPieces.length > 0) { |
2351 tailPath = '/' + tailPath; | 1628 tailPath = '/' + tailPath; |
2352 } | 1629 } |
2353 if (!this.tail || | 1630 if (!this.tail || |
2354 this.tail.prefix !== tailPrefix || | 1631 this.tail.prefix !== tailPrefix || |
2355 this.tail.path !== tailPath) { | 1632 this.tail.path !== tailPath) { |
2356 propertyUpdates.tail = { | 1633 propertyUpdates.tail = { |
2357 prefix: tailPrefix, | 1634 prefix: tailPrefix, |
2358 path: tailPath, | 1635 path: tailPath, |
2359 __queryParams: this.route.__queryParams | 1636 __queryParams: this.route.__queryParams |
2360 }; | 1637 }; |
2361 } | 1638 } |
2362 | 1639 |
2363 // this.data | |
2364 propertyUpdates.data = namedMatches; | 1640 propertyUpdates.data = namedMatches; |
2365 this._dataInUrl = {}; | 1641 this._dataInUrl = {}; |
2366 for (var key in namedMatches) { | 1642 for (var key in namedMatches) { |
2367 this._dataInUrl[key] = namedMatches[key]; | 1643 this._dataInUrl[key] = namedMatches[key]; |
2368 } | 1644 } |
2369 | 1645 |
2370 this.__setMulti(propertyUpdates); | 1646 this.__setMulti(propertyUpdates); |
2371 }, | 1647 }, |
2372 | 1648 |
2373 /** | |
2374 * @export | |
2375 */ | |
2376 __tailPathChanged: function() { | 1649 __tailPathChanged: function() { |
2377 if (!this.active) { | 1650 if (!this.active) { |
2378 return; | 1651 return; |
2379 } | 1652 } |
2380 var tailPath = this.tail.path; | 1653 var tailPath = this.tail.path; |
2381 var newPath = this._matched; | 1654 var newPath = this._matched; |
2382 if (tailPath) { | 1655 if (tailPath) { |
2383 if (tailPath.charAt(0) !== '/') { | 1656 if (tailPath.charAt(0) !== '/') { |
2384 tailPath = '/' + tailPath; | 1657 tailPath = '/' + tailPath; |
2385 } | 1658 } |
2386 newPath += tailPath; | 1659 newPath += tailPath; |
2387 } | 1660 } |
2388 this.set('route.path', newPath); | 1661 this.set('route.path', newPath); |
2389 }, | 1662 }, |
2390 | 1663 |
2391 /** | |
2392 * @export | |
2393 */ | |
2394 __updatePathOnDataChange: function() { | 1664 __updatePathOnDataChange: function() { |
2395 if (!this.route || !this.active) { | 1665 if (!this.route || !this.active) { |
2396 return; | 1666 return; |
2397 } | 1667 } |
2398 var newPath = this.__getLink({}); | 1668 var newPath = this.__getLink({}); |
2399 var oldPath = this.__getLink(this._dataInUrl); | 1669 var oldPath = this.__getLink(this._dataInUrl); |
2400 if (newPath === oldPath) { | 1670 if (newPath === oldPath) { |
2401 return; | 1671 return; |
2402 } | 1672 } |
2403 this.set('route.path', newPath); | 1673 this.set('route.path', newPath); |
(...skipping 18 matching lines...) Expand all Loading... |
2422 if (interp.length > 0 && values.tail.path.charAt(0) === '/') { | 1692 if (interp.length > 0 && values.tail.path.charAt(0) === '/') { |
2423 interp.push(values.tail.path.slice(1)); | 1693 interp.push(values.tail.path.slice(1)); |
2424 } else { | 1694 } else { |
2425 interp.push(values.tail.path); | 1695 interp.push(values.tail.path); |
2426 } | 1696 } |
2427 } | 1697 } |
2428 return interp.join('/'); | 1698 return interp.join('/'); |
2429 }, | 1699 }, |
2430 | 1700 |
2431 __setMulti: function(setObj) { | 1701 __setMulti: function(setObj) { |
2432 // HACK(rictic): skirting around 1.0's lack of a setMulti by poking at | |
2433 // internal data structures. I would not advise that you copy this | |
2434 // example. | |
2435 // | |
2436 // In the future this will be a feature of Polymer itself. | |
2437 // See: https://github.com/Polymer/polymer/issues/3640 | |
2438 // | |
2439 // Hacking around with private methods like this is juggling footguns, | |
2440 // and is likely to have unexpected and unsupported rough edges. | |
2441 // | |
2442 // Be ye so warned. | |
2443 for (var property in setObj) { | 1702 for (var property in setObj) { |
2444 this._propertySetter(property, setObj[property]); | 1703 this._propertySetter(property, setObj[property]); |
2445 } | 1704 } |
2446 | 1705 |
2447 for (var property in setObj) { | 1706 for (var property in setObj) { |
2448 this._pathEffector(property, this[property]); | 1707 this._pathEffector(property, this[property]); |
2449 this._notifyPathUp(property, this[property]); | 1708 this._notifyPathUp(property, this[property]); |
2450 } | 1709 } |
2451 } | 1710 } |
2452 }); | 1711 }); |
2453 Polymer({ | 1712 Polymer({ |
2454 | 1713 |
2455 is: 'iron-media-query', | 1714 is: 'iron-media-query', |
2456 | 1715 |
2457 properties: { | 1716 properties: { |
2458 | 1717 |
2459 /** | |
2460 * The Boolean return value of the media query. | |
2461 */ | |
2462 queryMatches: { | 1718 queryMatches: { |
2463 type: Boolean, | 1719 type: Boolean, |
2464 value: false, | 1720 value: false, |
2465 readOnly: true, | 1721 readOnly: true, |
2466 notify: true | 1722 notify: true |
2467 }, | 1723 }, |
2468 | 1724 |
2469 /** | |
2470 * The CSS media query to evaluate. | |
2471 */ | |
2472 query: { | 1725 query: { |
2473 type: String, | 1726 type: String, |
2474 observer: 'queryChanged' | 1727 observer: 'queryChanged' |
2475 }, | 1728 }, |
2476 | 1729 |
2477 /** | |
2478 * If true, the query attribute is assumed to be a complete media query | |
2479 * string rather than a single media feature. | |
2480 */ | |
2481 full: { | 1730 full: { |
2482 type: Boolean, | 1731 type: Boolean, |
2483 value: false | 1732 value: false |
2484 }, | 1733 }, |
2485 | 1734 |
2486 /** | |
2487 * @type {function(MediaQueryList)} | |
2488 */ | |
2489 _boundMQHandler: { | 1735 _boundMQHandler: { |
2490 value: function() { | 1736 value: function() { |
2491 return this.queryHandler.bind(this); | 1737 return this.queryHandler.bind(this); |
2492 } | 1738 } |
2493 }, | 1739 }, |
2494 | 1740 |
2495 /** | |
2496 * @type {MediaQueryList} | |
2497 */ | |
2498 _mq: { | 1741 _mq: { |
2499 value: null | 1742 value: null |
2500 } | 1743 } |
2501 }, | 1744 }, |
2502 | 1745 |
2503 attached: function() { | 1746 attached: function() { |
2504 this.style.display = 'none'; | 1747 this.style.display = 'none'; |
2505 this.queryChanged(); | 1748 this.queryChanged(); |
2506 }, | 1749 }, |
2507 | 1750 |
(...skipping 26 matching lines...) Expand all Loading... |
2534 this._mq = window.matchMedia(query); | 1777 this._mq = window.matchMedia(query); |
2535 this._add(); | 1778 this._add(); |
2536 this.queryHandler(this._mq); | 1779 this.queryHandler(this._mq); |
2537 }, | 1780 }, |
2538 | 1781 |
2539 queryHandler: function(mq) { | 1782 queryHandler: function(mq) { |
2540 this._setQueryMatches(mq.matches); | 1783 this._setQueryMatches(mq.matches); |
2541 } | 1784 } |
2542 | 1785 |
2543 }); | 1786 }); |
2544 /** | |
2545 * `IronResizableBehavior` is a behavior that can be used in Polymer elements
to | |
2546 * coordinate the flow of resize events between "resizers" (elements that cont
rol the | |
2547 * size or hidden state of their children) and "resizables" (elements that nee
d to be | |
2548 * notified when they are resized or un-hidden by their parents in order to ta
ke | |
2549 * action on their new measurements). | |
2550 * | |
2551 * Elements that perform measurement should add the `IronResizableBehavior` be
havior to | |
2552 * their element definition and listen for the `iron-resize` event on themselv
es. | |
2553 * This event will be fired when they become showing after having been hidden, | |
2554 * when they are resized explicitly by another resizable, or when the window h
as been | |
2555 * resized. | |
2556 * | |
2557 * Note, the `iron-resize` event is non-bubbling. | |
2558 * | |
2559 * @polymerBehavior Polymer.IronResizableBehavior | |
2560 * @demo demo/index.html | |
2561 **/ | |
2562 Polymer.IronResizableBehavior = { | 1787 Polymer.IronResizableBehavior = { |
2563 properties: { | 1788 properties: { |
2564 /** | |
2565 * The closest ancestor element that implements `IronResizableBehavior`. | |
2566 */ | |
2567 _parentResizable: { | 1789 _parentResizable: { |
2568 type: Object, | 1790 type: Object, |
2569 observer: '_parentResizableChanged' | 1791 observer: '_parentResizableChanged' |
2570 }, | 1792 }, |
2571 | 1793 |
2572 /** | |
2573 * True if this element is currently notifying its descedant elements of | |
2574 * resize. | |
2575 */ | |
2576 _notifyingDescendant: { | 1794 _notifyingDescendant: { |
2577 type: Boolean, | 1795 type: Boolean, |
2578 value: false | 1796 value: false |
2579 } | 1797 } |
2580 }, | 1798 }, |
2581 | 1799 |
2582 listeners: { | 1800 listeners: { |
2583 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' | 1801 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' |
2584 }, | 1802 }, |
2585 | 1803 |
2586 created: function() { | 1804 created: function() { |
2587 // We don't really need property effects on these, and also we want them | |
2588 // to be created before the `_parentResizable` observer fires: | |
2589 this._interestedResizables = []; | 1805 this._interestedResizables = []; |
2590 this._boundNotifyResize = this.notifyResize.bind(this); | 1806 this._boundNotifyResize = this.notifyResize.bind(this); |
2591 }, | 1807 }, |
2592 | 1808 |
2593 attached: function() { | 1809 attached: function() { |
2594 this.fire('iron-request-resize-notifications', null, { | 1810 this.fire('iron-request-resize-notifications', null, { |
2595 node: this, | 1811 node: this, |
2596 bubbles: true, | 1812 bubbles: true, |
2597 cancelable: true | 1813 cancelable: true |
2598 }); | 1814 }); |
2599 | 1815 |
2600 if (!this._parentResizable) { | 1816 if (!this._parentResizable) { |
2601 window.addEventListener('resize', this._boundNotifyResize); | 1817 window.addEventListener('resize', this._boundNotifyResize); |
2602 this.notifyResize(); | 1818 this.notifyResize(); |
2603 } | 1819 } |
2604 }, | 1820 }, |
2605 | 1821 |
2606 detached: function() { | 1822 detached: function() { |
2607 if (this._parentResizable) { | 1823 if (this._parentResizable) { |
2608 this._parentResizable.stopResizeNotificationsFor(this); | 1824 this._parentResizable.stopResizeNotificationsFor(this); |
2609 } else { | 1825 } else { |
2610 window.removeEventListener('resize', this._boundNotifyResize); | 1826 window.removeEventListener('resize', this._boundNotifyResize); |
2611 } | 1827 } |
2612 | 1828 |
2613 this._parentResizable = null; | 1829 this._parentResizable = null; |
2614 }, | 1830 }, |
2615 | 1831 |
2616 /** | |
2617 * Can be called to manually notify a resizable and its descendant | |
2618 * resizables of a resize change. | |
2619 */ | |
2620 notifyResize: function() { | 1832 notifyResize: function() { |
2621 if (!this.isAttached) { | 1833 if (!this.isAttached) { |
2622 return; | 1834 return; |
2623 } | 1835 } |
2624 | 1836 |
2625 this._interestedResizables.forEach(function(resizable) { | 1837 this._interestedResizables.forEach(function(resizable) { |
2626 if (this.resizerShouldNotify(resizable)) { | 1838 if (this.resizerShouldNotify(resizable)) { |
2627 this._notifyDescendant(resizable); | 1839 this._notifyDescendant(resizable); |
2628 } | 1840 } |
2629 }, this); | 1841 }, this); |
2630 | 1842 |
2631 this._fireResize(); | 1843 this._fireResize(); |
2632 }, | 1844 }, |
2633 | 1845 |
2634 /** | |
2635 * Used to assign the closest resizable ancestor to this resizable | |
2636 * if the ancestor detects a request for notifications. | |
2637 */ | |
2638 assignParentResizable: function(parentResizable) { | 1846 assignParentResizable: function(parentResizable) { |
2639 this._parentResizable = parentResizable; | 1847 this._parentResizable = parentResizable; |
2640 }, | 1848 }, |
2641 | 1849 |
2642 /** | |
2643 * Used to remove a resizable descendant from the list of descendants | |
2644 * that should be notified of a resize change. | |
2645 */ | |
2646 stopResizeNotificationsFor: function(target) { | 1850 stopResizeNotificationsFor: function(target) { |
2647 var index = this._interestedResizables.indexOf(target); | 1851 var index = this._interestedResizables.indexOf(target); |
2648 | 1852 |
2649 if (index > -1) { | 1853 if (index > -1) { |
2650 this._interestedResizables.splice(index, 1); | 1854 this._interestedResizables.splice(index, 1); |
2651 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); | 1855 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); |
2652 } | 1856 } |
2653 }, | 1857 }, |
2654 | 1858 |
2655 /** | |
2656 * This method can be overridden to filter nested elements that should or | |
2657 * should not be notified by the current element. Return true if an element | |
2658 * should be notified, or false if it should not be notified. | |
2659 * | |
2660 * @param {HTMLElement} element A candidate descendant element that | |
2661 * implements `IronResizableBehavior`. | |
2662 * @return {boolean} True if the `element` should be notified of resize. | |
2663 */ | |
2664 resizerShouldNotify: function(element) { return true; }, | 1859 resizerShouldNotify: function(element) { return true; }, |
2665 | 1860 |
2666 _onDescendantIronResize: function(event) { | 1861 _onDescendantIronResize: function(event) { |
2667 if (this._notifyingDescendant) { | 1862 if (this._notifyingDescendant) { |
2668 event.stopPropagation(); | 1863 event.stopPropagation(); |
2669 return; | 1864 return; |
2670 } | 1865 } |
2671 | 1866 |
2672 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the | |
2673 // otherwise non-bubbling event "just work." We do it manually here for | |
2674 // the case where Polymer is not using shadow roots for whatever reason: | |
2675 if (!Polymer.Settings.useShadow) { | 1867 if (!Polymer.Settings.useShadow) { |
2676 this._fireResize(); | 1868 this._fireResize(); |
2677 } | 1869 } |
2678 }, | 1870 }, |
2679 | 1871 |
2680 _fireResize: function() { | 1872 _fireResize: function() { |
2681 this.fire('iron-resize', null, { | 1873 this.fire('iron-resize', null, { |
2682 node: this, | 1874 node: this, |
2683 bubbles: false | 1875 bubbles: false |
2684 }); | 1876 }); |
(...skipping 17 matching lines...) Expand all Loading... |
2702 event.stopPropagation(); | 1894 event.stopPropagation(); |
2703 }, | 1895 }, |
2704 | 1896 |
2705 _parentResizableChanged: function(parentResizable) { | 1897 _parentResizableChanged: function(parentResizable) { |
2706 if (parentResizable) { | 1898 if (parentResizable) { |
2707 window.removeEventListener('resize', this._boundNotifyResize); | 1899 window.removeEventListener('resize', this._boundNotifyResize); |
2708 } | 1900 } |
2709 }, | 1901 }, |
2710 | 1902 |
2711 _notifyDescendant: function(descendant) { | 1903 _notifyDescendant: function(descendant) { |
2712 // NOTE(cdata): In IE10, attached is fired on children first, so it's | |
2713 // important not to notify them if the parent is not attached yet (or | |
2714 // else they will get redundantly notified when the parent attaches). | |
2715 if (!this.isAttached) { | 1904 if (!this.isAttached) { |
2716 return; | 1905 return; |
2717 } | 1906 } |
2718 | 1907 |
2719 this._notifyingDescendant = true; | 1908 this._notifyingDescendant = true; |
2720 descendant.notifyResize(); | 1909 descendant.notifyResize(); |
2721 this._notifyingDescendant = false; | 1910 this._notifyingDescendant = false; |
2722 } | 1911 } |
2723 }; | 1912 }; |
2724 /** | |
2725 * @param {!Function} selectCallback | |
2726 * @constructor | |
2727 */ | |
2728 Polymer.IronSelection = function(selectCallback) { | 1913 Polymer.IronSelection = function(selectCallback) { |
2729 this.selection = []; | 1914 this.selection = []; |
2730 this.selectCallback = selectCallback; | 1915 this.selectCallback = selectCallback; |
2731 }; | 1916 }; |
2732 | 1917 |
2733 Polymer.IronSelection.prototype = { | 1918 Polymer.IronSelection.prototype = { |
2734 | 1919 |
2735 /** | |
2736 * Retrieves the selected item(s). | |
2737 * | |
2738 * @method get | |
2739 * @returns Returns the selected item(s). If the multi property is true, | |
2740 * `get` will return an array, otherwise it will return | |
2741 * the selected item or undefined if there is no selection. | |
2742 */ | |
2743 get: function() { | 1920 get: function() { |
2744 return this.multi ? this.selection.slice() : this.selection[0]; | 1921 return this.multi ? this.selection.slice() : this.selection[0]; |
2745 }, | 1922 }, |
2746 | 1923 |
2747 /** | |
2748 * Clears all the selection except the ones indicated. | |
2749 * | |
2750 * @method clear | |
2751 * @param {Array} excludes items to be excluded. | |
2752 */ | |
2753 clear: function(excludes) { | 1924 clear: function(excludes) { |
2754 this.selection.slice().forEach(function(item) { | 1925 this.selection.slice().forEach(function(item) { |
2755 if (!excludes || excludes.indexOf(item) < 0) { | 1926 if (!excludes || excludes.indexOf(item) < 0) { |
2756 this.setItemSelected(item, false); | 1927 this.setItemSelected(item, false); |
2757 } | 1928 } |
2758 }, this); | 1929 }, this); |
2759 }, | 1930 }, |
2760 | 1931 |
2761 /** | |
2762 * Indicates if a given item is selected. | |
2763 * | |
2764 * @method isSelected | |
2765 * @param {*} item The item whose selection state should be checked. | |
2766 * @returns Returns true if `item` is selected. | |
2767 */ | |
2768 isSelected: function(item) { | 1932 isSelected: function(item) { |
2769 return this.selection.indexOf(item) >= 0; | 1933 return this.selection.indexOf(item) >= 0; |
2770 }, | 1934 }, |
2771 | 1935 |
2772 /** | |
2773 * Sets the selection state for a given item to either selected or deselecte
d. | |
2774 * | |
2775 * @method setItemSelected | |
2776 * @param {*} item The item to select. | |
2777 * @param {boolean} isSelected True for selected, false for deselected. | |
2778 */ | |
2779 setItemSelected: function(item, isSelected) { | 1936 setItemSelected: function(item, isSelected) { |
2780 if (item != null) { | 1937 if (item != null) { |
2781 if (isSelected !== this.isSelected(item)) { | 1938 if (isSelected !== this.isSelected(item)) { |
2782 // proceed to update selection only if requested state differs from cu
rrent | |
2783 if (isSelected) { | 1939 if (isSelected) { |
2784 this.selection.push(item); | 1940 this.selection.push(item); |
2785 } else { | 1941 } else { |
2786 var i = this.selection.indexOf(item); | 1942 var i = this.selection.indexOf(item); |
2787 if (i >= 0) { | 1943 if (i >= 0) { |
2788 this.selection.splice(i, 1); | 1944 this.selection.splice(i, 1); |
2789 } | 1945 } |
2790 } | 1946 } |
2791 if (this.selectCallback) { | 1947 if (this.selectCallback) { |
2792 this.selectCallback(item, isSelected); | 1948 this.selectCallback(item, isSelected); |
2793 } | 1949 } |
2794 } | 1950 } |
2795 } | 1951 } |
2796 }, | 1952 }, |
2797 | 1953 |
2798 /** | |
2799 * Sets the selection state for a given item. If the `multi` property | |
2800 * is true, then the selected state of `item` will be toggled; otherwise | |
2801 * the `item` will be selected. | |
2802 * | |
2803 * @method select | |
2804 * @param {*} item The item to select. | |
2805 */ | |
2806 select: function(item) { | 1954 select: function(item) { |
2807 if (this.multi) { | 1955 if (this.multi) { |
2808 this.toggle(item); | 1956 this.toggle(item); |
2809 } else if (this.get() !== item) { | 1957 } else if (this.get() !== item) { |
2810 this.setItemSelected(this.get(), false); | 1958 this.setItemSelected(this.get(), false); |
2811 this.setItemSelected(item, true); | 1959 this.setItemSelected(item, true); |
2812 } | 1960 } |
2813 }, | 1961 }, |
2814 | 1962 |
2815 /** | |
2816 * Toggles the selection state for `item`. | |
2817 * | |
2818 * @method toggle | |
2819 * @param {*} item The item to toggle. | |
2820 */ | |
2821 toggle: function(item) { | 1963 toggle: function(item) { |
2822 this.setItemSelected(item, !this.isSelected(item)); | 1964 this.setItemSelected(item, !this.isSelected(item)); |
2823 } | 1965 } |
2824 | 1966 |
2825 }; | 1967 }; |
2826 /** @polymerBehavior */ | 1968 /** @polymerBehavior */ |
2827 Polymer.IronSelectableBehavior = { | 1969 Polymer.IronSelectableBehavior = { |
2828 | 1970 |
2829 /** | |
2830 * Fired when iron-selector is activated (selected or deselected). | |
2831 * It is fired before the selected items are changed. | |
2832 * Cancel the event to abort selection. | |
2833 * | |
2834 * @event iron-activate | |
2835 */ | |
2836 | 1971 |
2837 /** | |
2838 * Fired when an item is selected | |
2839 * | |
2840 * @event iron-select | |
2841 */ | |
2842 | 1972 |
2843 /** | |
2844 * Fired when an item is deselected | |
2845 * | |
2846 * @event iron-deselect | |
2847 */ | |
2848 | 1973 |
2849 /** | |
2850 * Fired when the list of selectable items changes (e.g., items are | |
2851 * added or removed). The detail of the event is a mutation record that | |
2852 * describes what changed. | |
2853 * | |
2854 * @event iron-items-changed | |
2855 */ | |
2856 | 1974 |
2857 properties: { | 1975 properties: { |
2858 | 1976 |
2859 /** | |
2860 * If you want to use an attribute value or property of an element for | |
2861 * `selected` instead of the index, set this to the name of the attribute | |
2862 * or property. Hyphenated values are converted to camel case when used to | |
2863 * look up the property of a selectable element. Camel cased values are | |
2864 * *not* converted to hyphenated values for attribute lookup. It's | |
2865 * recommended that you provide the hyphenated form of the name so that | |
2866 * selection works in both cases. (Use `attr-or-property-name` instead of | |
2867 * `attrOrPropertyName`.) | |
2868 */ | |
2869 attrForSelected: { | 1977 attrForSelected: { |
2870 type: String, | 1978 type: String, |
2871 value: null | 1979 value: null |
2872 }, | 1980 }, |
2873 | 1981 |
2874 /** | |
2875 * Gets or sets the selected element. The default is to use the index of t
he item. | |
2876 * @type {string|number} | |
2877 */ | |
2878 selected: { | 1982 selected: { |
2879 type: String, | 1983 type: String, |
2880 notify: true | 1984 notify: true |
2881 }, | 1985 }, |
2882 | 1986 |
2883 /** | |
2884 * Returns the currently selected item. | |
2885 * | |
2886 * @type {?Object} | |
2887 */ | |
2888 selectedItem: { | 1987 selectedItem: { |
2889 type: Object, | 1988 type: Object, |
2890 readOnly: true, | 1989 readOnly: true, |
2891 notify: true | 1990 notify: true |
2892 }, | 1991 }, |
2893 | 1992 |
2894 /** | |
2895 * The event that fires from items when they are selected. Selectable | |
2896 * will listen for this event from items and update the selection state. | |
2897 * Set to empty string to listen to no events. | |
2898 */ | |
2899 activateEvent: { | 1993 activateEvent: { |
2900 type: String, | 1994 type: String, |
2901 value: 'tap', | 1995 value: 'tap', |
2902 observer: '_activateEventChanged' | 1996 observer: '_activateEventChanged' |
2903 }, | 1997 }, |
2904 | 1998 |
2905 /** | |
2906 * This is a CSS selector string. If this is set, only items that match t
he CSS selector | |
2907 * are selectable. | |
2908 */ | |
2909 selectable: String, | 1999 selectable: String, |
2910 | 2000 |
2911 /** | |
2912 * The class to set on elements when selected. | |
2913 */ | |
2914 selectedClass: { | 2001 selectedClass: { |
2915 type: String, | 2002 type: String, |
2916 value: 'iron-selected' | 2003 value: 'iron-selected' |
2917 }, | 2004 }, |
2918 | 2005 |
2919 /** | |
2920 * The attribute to set on elements when selected. | |
2921 */ | |
2922 selectedAttribute: { | 2006 selectedAttribute: { |
2923 type: String, | 2007 type: String, |
2924 value: null | 2008 value: null |
2925 }, | 2009 }, |
2926 | 2010 |
2927 /** | |
2928 * Default fallback if the selection based on selected with `attrForSelect
ed` | |
2929 * is not found. | |
2930 */ | |
2931 fallbackSelection: { | 2011 fallbackSelection: { |
2932 type: String, | 2012 type: String, |
2933 value: null | 2013 value: null |
2934 }, | 2014 }, |
2935 | 2015 |
2936 /** | |
2937 * The list of items from which a selection can be made. | |
2938 */ | |
2939 items: { | 2016 items: { |
2940 type: Array, | 2017 type: Array, |
2941 readOnly: true, | 2018 readOnly: true, |
2942 notify: true, | 2019 notify: true, |
2943 value: function() { | 2020 value: function() { |
2944 return []; | 2021 return []; |
2945 } | 2022 } |
2946 }, | 2023 }, |
2947 | 2024 |
2948 /** | |
2949 * The set of excluded elements where the key is the `localName` | |
2950 * of the element that will be ignored from the item list. | |
2951 * | |
2952 * @default {template: 1} | |
2953 */ | |
2954 _excludedLocalNames: { | 2025 _excludedLocalNames: { |
2955 type: Object, | 2026 type: Object, |
2956 value: function() { | 2027 value: function() { |
2957 return { | 2028 return { |
2958 'template': 1 | 2029 'template': 1 |
2959 }; | 2030 }; |
2960 } | 2031 } |
2961 } | 2032 } |
2962 }, | 2033 }, |
2963 | 2034 |
(...skipping 17 matching lines...) Expand all Loading... |
2981 this._addListener(this.activateEvent); | 2052 this._addListener(this.activateEvent); |
2982 }, | 2053 }, |
2983 | 2054 |
2984 detached: function() { | 2055 detached: function() { |
2985 if (this._observer) { | 2056 if (this._observer) { |
2986 Polymer.dom(this).unobserveNodes(this._observer); | 2057 Polymer.dom(this).unobserveNodes(this._observer); |
2987 } | 2058 } |
2988 this._removeListener(this.activateEvent); | 2059 this._removeListener(this.activateEvent); |
2989 }, | 2060 }, |
2990 | 2061 |
2991 /** | |
2992 * Returns the index of the given item. | |
2993 * | |
2994 * @method indexOf | |
2995 * @param {Object} item | |
2996 * @returns Returns the index of the item | |
2997 */ | |
2998 indexOf: function(item) { | 2062 indexOf: function(item) { |
2999 return this.items.indexOf(item); | 2063 return this.items.indexOf(item); |
3000 }, | 2064 }, |
3001 | 2065 |
3002 /** | |
3003 * Selects the given value. | |
3004 * | |
3005 * @method select | |
3006 * @param {string|number} value the value to select. | |
3007 */ | |
3008 select: function(value) { | 2066 select: function(value) { |
3009 this.selected = value; | 2067 this.selected = value; |
3010 }, | 2068 }, |
3011 | 2069 |
3012 /** | |
3013 * Selects the previous item. | |
3014 * | |
3015 * @method selectPrevious | |
3016 */ | |
3017 selectPrevious: function() { | 2070 selectPrevious: function() { |
3018 var length = this.items.length; | 2071 var length = this.items.length; |
3019 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len
gth; | 2072 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len
gth; |
3020 this.selected = this._indexToValue(index); | 2073 this.selected = this._indexToValue(index); |
3021 }, | 2074 }, |
3022 | 2075 |
3023 /** | |
3024 * Selects the next item. | |
3025 * | |
3026 * @method selectNext | |
3027 */ | |
3028 selectNext: function() { | 2076 selectNext: function() { |
3029 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l
ength; | 2077 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l
ength; |
3030 this.selected = this._indexToValue(index); | 2078 this.selected = this._indexToValue(index); |
3031 }, | 2079 }, |
3032 | 2080 |
3033 /** | |
3034 * Selects the item at the given index. | |
3035 * | |
3036 * @method selectIndex | |
3037 */ | |
3038 selectIndex: function(index) { | 2081 selectIndex: function(index) { |
3039 this.select(this._indexToValue(index)); | 2082 this.select(this._indexToValue(index)); |
3040 }, | 2083 }, |
3041 | 2084 |
3042 /** | |
3043 * Force a synchronous update of the `items` property. | |
3044 * | |
3045 * NOTE: Consider listening for the `iron-items-changed` event to respond to | |
3046 * updates to the set of selectable items after updates to the DOM list and | |
3047 * selection state have been made. | |
3048 * | |
3049 * WARNING: If you are using this method, you should probably consider an | |
3050 * alternate approach. Synchronously querying for items is potentially | |
3051 * slow for many use cases. The `items` property will update asynchronously | |
3052 * on its own to reflect selectable items in the DOM. | |
3053 */ | |
3054 forceSynchronousItemUpdate: function() { | 2085 forceSynchronousItemUpdate: function() { |
3055 this._updateItems(); | 2086 this._updateItems(); |
3056 }, | 2087 }, |
3057 | 2088 |
3058 get _shouldUpdateSelection() { | 2089 get _shouldUpdateSelection() { |
3059 return this.selected != null; | 2090 return this.selected != null; |
3060 }, | 2091 }, |
3061 | 2092 |
3062 _checkFallback: function() { | 2093 _checkFallback: function() { |
3063 if (this._shouldUpdateSelection) { | 2094 if (this._shouldUpdateSelection) { |
(...skipping 25 matching lines...) Expand all Loading... |
3089 this.selected = this._indexToValue(this.indexOf(this.selectedItem)); | 2120 this.selected = this._indexToValue(this.indexOf(this.selectedItem)); |
3090 } | 2121 } |
3091 }, | 2122 }, |
3092 | 2123 |
3093 _updateSelected: function() { | 2124 _updateSelected: function() { |
3094 this._selectSelected(this.selected); | 2125 this._selectSelected(this.selected); |
3095 }, | 2126 }, |
3096 | 2127 |
3097 _selectSelected: function(selected) { | 2128 _selectSelected: function(selected) { |
3098 this._selection.select(this._valueToItem(this.selected)); | 2129 this._selection.select(this._valueToItem(this.selected)); |
3099 // Check for items, since this array is populated only when attached | |
3100 // Since Number(0) is falsy, explicitly check for undefined | |
3101 if (this.fallbackSelection && this.items.length && (this._selection.get()
=== undefined)) { | 2130 if (this.fallbackSelection && this.items.length && (this._selection.get()
=== undefined)) { |
3102 this.selected = this.fallbackSelection; | 2131 this.selected = this.fallbackSelection; |
3103 } | 2132 } |
3104 }, | 2133 }, |
3105 | 2134 |
3106 _filterItem: function(node) { | 2135 _filterItem: function(node) { |
3107 return !this._excludedLocalNames[node.localName]; | 2136 return !this._excludedLocalNames[node.localName]; |
3108 }, | 2137 }, |
3109 | 2138 |
3110 _valueToItem: function(value) { | 2139 _valueToItem: function(value) { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3147 this.toggleAttribute(this.selectedAttribute, isSelected, item); | 2176 this.toggleAttribute(this.selectedAttribute, isSelected, item); |
3148 } | 2177 } |
3149 this._selectionChange(); | 2178 this._selectionChange(); |
3150 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); | 2179 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); |
3151 }, | 2180 }, |
3152 | 2181 |
3153 _selectionChange: function() { | 2182 _selectionChange: function() { |
3154 this._setSelectedItem(this._selection.get()); | 2183 this._setSelectedItem(this._selection.get()); |
3155 }, | 2184 }, |
3156 | 2185 |
3157 // observe items change under the given node. | |
3158 _observeItems: function(node) { | 2186 _observeItems: function(node) { |
3159 return Polymer.dom(node).observeNodes(function(mutation) { | 2187 return Polymer.dom(node).observeNodes(function(mutation) { |
3160 this._updateItems(); | 2188 this._updateItems(); |
3161 | 2189 |
3162 if (this._shouldUpdateSelection) { | 2190 if (this._shouldUpdateSelection) { |
3163 this._updateSelected(); | 2191 this._updateSelected(); |
3164 } | 2192 } |
3165 | 2193 |
3166 // Let other interested parties know about the change so that | |
3167 // we don't have to recreate mutation observers everywhere. | |
3168 this.fire('iron-items-changed', mutation, { | 2194 this.fire('iron-items-changed', mutation, { |
3169 bubbles: false, | 2195 bubbles: false, |
3170 cancelable: false | 2196 cancelable: false |
3171 }); | 2197 }); |
3172 }); | 2198 }); |
3173 }, | 2199 }, |
3174 | 2200 |
3175 _activateHandler: function(e) { | 2201 _activateHandler: function(e) { |
3176 var t = e.target; | 2202 var t = e.target; |
3177 var items = this.items; | 2203 var items = this.items; |
(...skipping 20 matching lines...) Expand all Loading... |
3198 | 2224 |
3199 is: 'iron-pages', | 2225 is: 'iron-pages', |
3200 | 2226 |
3201 behaviors: [ | 2227 behaviors: [ |
3202 Polymer.IronResizableBehavior, | 2228 Polymer.IronResizableBehavior, |
3203 Polymer.IronSelectableBehavior | 2229 Polymer.IronSelectableBehavior |
3204 ], | 2230 ], |
3205 | 2231 |
3206 properties: { | 2232 properties: { |
3207 | 2233 |
3208 // as the selected page is the only one visible, activateEvent | |
3209 // is both non-sensical and problematic; e.g. in cases where a user | |
3210 // handler attempts to change the page and the activateEvent | |
3211 // handler immediately changes it back | |
3212 activateEvent: { | 2234 activateEvent: { |
3213 type: String, | 2235 type: String, |
3214 value: null | 2236 value: null |
3215 } | 2237 } |
3216 | 2238 |
3217 }, | 2239 }, |
3218 | 2240 |
3219 observers: [ | 2241 observers: [ |
3220 '_selectedPageChanged(selected)' | 2242 '_selectedPageChanged(selected)' |
3221 ], | 2243 ], |
3222 | 2244 |
3223 _selectedPageChanged: function(selected, old) { | 2245 _selectedPageChanged: function(selected, old) { |
3224 this.async(this.notifyResize); | 2246 this.async(this.notifyResize); |
3225 } | 2247 } |
3226 }); | 2248 }); |
3227 (function() { | 2249 (function() { |
3228 'use strict'; | 2250 'use strict'; |
3229 | 2251 |
3230 /** | |
3231 * Chrome uses an older version of DOM Level 3 Keyboard Events | |
3232 * | |
3233 * Most keys are labeled as text, but some are Unicode codepoints. | |
3234 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set | |
3235 */ | |
3236 var KEY_IDENTIFIER = { | 2252 var KEY_IDENTIFIER = { |
3237 'U+0008': 'backspace', | 2253 'U+0008': 'backspace', |
3238 'U+0009': 'tab', | 2254 'U+0009': 'tab', |
3239 'U+001B': 'esc', | 2255 'U+001B': 'esc', |
3240 'U+0020': 'space', | 2256 'U+0020': 'space', |
3241 'U+007F': 'del' | 2257 'U+007F': 'del' |
3242 }; | 2258 }; |
3243 | 2259 |
3244 /** | |
3245 * Special table for KeyboardEvent.keyCode. | |
3246 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er | |
3247 * than that. | |
3248 * | |
3249 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode | |
3250 */ | |
3251 var KEY_CODE = { | 2260 var KEY_CODE = { |
3252 8: 'backspace', | 2261 8: 'backspace', |
3253 9: 'tab', | 2262 9: 'tab', |
3254 13: 'enter', | 2263 13: 'enter', |
3255 27: 'esc', | 2264 27: 'esc', |
3256 33: 'pageup', | 2265 33: 'pageup', |
3257 34: 'pagedown', | 2266 34: 'pagedown', |
3258 35: 'end', | 2267 35: 'end', |
3259 36: 'home', | 2268 36: 'home', |
3260 32: 'space', | 2269 32: 'space', |
3261 37: 'left', | 2270 37: 'left', |
3262 38: 'up', | 2271 38: 'up', |
3263 39: 'right', | 2272 39: 'right', |
3264 40: 'down', | 2273 40: 'down', |
3265 46: 'del', | 2274 46: 'del', |
3266 106: '*' | 2275 106: '*' |
3267 }; | 2276 }; |
3268 | 2277 |
3269 /** | |
3270 * MODIFIER_KEYS maps the short name for modifier keys used in a key | |
3271 * combo string to the property name that references those same keys | |
3272 * in a KeyboardEvent instance. | |
3273 */ | |
3274 var MODIFIER_KEYS = { | 2278 var MODIFIER_KEYS = { |
3275 'shift': 'shiftKey', | 2279 'shift': 'shiftKey', |
3276 'ctrl': 'ctrlKey', | 2280 'ctrl': 'ctrlKey', |
3277 'alt': 'altKey', | 2281 'alt': 'altKey', |
3278 'meta': 'metaKey' | 2282 'meta': 'metaKey' |
3279 }; | 2283 }; |
3280 | 2284 |
3281 /** | |
3282 * KeyboardEvent.key is mostly represented by printable character made by | |
3283 * the keyboard, with unprintable keys labeled nicely. | |
3284 * | |
3285 * However, on OS X, Alt+char can make a Unicode character that follows an | |
3286 * Apple-specific mapping. In this case, we fall back to .keyCode. | |
3287 */ | |
3288 var KEY_CHAR = /[a-z0-9*]/; | 2285 var KEY_CHAR = /[a-z0-9*]/; |
3289 | 2286 |
3290 /** | |
3291 * Matches a keyIdentifier string. | |
3292 */ | |
3293 var IDENT_CHAR = /U\+/; | 2287 var IDENT_CHAR = /U\+/; |
3294 | 2288 |
3295 /** | |
3296 * Matches arrow keys in Gecko 27.0+ | |
3297 */ | |
3298 var ARROW_KEY = /^arrow/; | 2289 var ARROW_KEY = /^arrow/; |
3299 | 2290 |
3300 /** | |
3301 * Matches space keys everywhere (notably including IE10's exceptional name | |
3302 * `spacebar`). | |
3303 */ | |
3304 var SPACE_KEY = /^space(bar)?/; | 2291 var SPACE_KEY = /^space(bar)?/; |
3305 | 2292 |
3306 /** | |
3307 * Matches ESC key. | |
3308 * | |
3309 * Value from: http://w3c.github.io/uievents-key/#key-Escape | |
3310 */ | |
3311 var ESC_KEY = /^escape$/; | 2293 var ESC_KEY = /^escape$/; |
3312 | 2294 |
3313 /** | |
3314 * Transforms the key. | |
3315 * @param {string} key The KeyBoardEvent.key | |
3316 * @param {Boolean} [noSpecialChars] Limits the transformation to | |
3317 * alpha-numeric characters. | |
3318 */ | |
3319 function transformKey(key, noSpecialChars) { | 2295 function transformKey(key, noSpecialChars) { |
3320 var validKey = ''; | 2296 var validKey = ''; |
3321 if (key) { | 2297 if (key) { |
3322 var lKey = key.toLowerCase(); | 2298 var lKey = key.toLowerCase(); |
3323 if (lKey === ' ' || SPACE_KEY.test(lKey)) { | 2299 if (lKey === ' ' || SPACE_KEY.test(lKey)) { |
3324 validKey = 'space'; | 2300 validKey = 'space'; |
3325 } else if (ESC_KEY.test(lKey)) { | 2301 } else if (ESC_KEY.test(lKey)) { |
3326 validKey = 'esc'; | 2302 validKey = 'esc'; |
3327 } else if (lKey.length == 1) { | 2303 } else if (lKey.length == 1) { |
3328 if (!noSpecialChars || KEY_CHAR.test(lKey)) { | 2304 if (!noSpecialChars || KEY_CHAR.test(lKey)) { |
3329 validKey = lKey; | 2305 validKey = lKey; |
3330 } | 2306 } |
3331 } else if (ARROW_KEY.test(lKey)) { | 2307 } else if (ARROW_KEY.test(lKey)) { |
3332 validKey = lKey.replace('arrow', ''); | 2308 validKey = lKey.replace('arrow', ''); |
3333 } else if (lKey == 'multiply') { | 2309 } else if (lKey == 'multiply') { |
3334 // numpad '*' can map to Multiply on IE/Windows | |
3335 validKey = '*'; | 2310 validKey = '*'; |
3336 } else { | 2311 } else { |
3337 validKey = lKey; | 2312 validKey = lKey; |
3338 } | 2313 } |
3339 } | 2314 } |
3340 return validKey; | 2315 return validKey; |
3341 } | 2316 } |
3342 | 2317 |
3343 function transformKeyIdentifier(keyIdent) { | 2318 function transformKeyIdentifier(keyIdent) { |
3344 var validKey = ''; | 2319 var validKey = ''; |
3345 if (keyIdent) { | 2320 if (keyIdent) { |
3346 if (keyIdent in KEY_IDENTIFIER) { | 2321 if (keyIdent in KEY_IDENTIFIER) { |
3347 validKey = KEY_IDENTIFIER[keyIdent]; | 2322 validKey = KEY_IDENTIFIER[keyIdent]; |
3348 } else if (IDENT_CHAR.test(keyIdent)) { | 2323 } else if (IDENT_CHAR.test(keyIdent)) { |
3349 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); | 2324 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); |
3350 validKey = String.fromCharCode(keyIdent).toLowerCase(); | 2325 validKey = String.fromCharCode(keyIdent).toLowerCase(); |
3351 } else { | 2326 } else { |
3352 validKey = keyIdent.toLowerCase(); | 2327 validKey = keyIdent.toLowerCase(); |
3353 } | 2328 } |
3354 } | 2329 } |
3355 return validKey; | 2330 return validKey; |
3356 } | 2331 } |
3357 | 2332 |
3358 function transformKeyCode(keyCode) { | 2333 function transformKeyCode(keyCode) { |
3359 var validKey = ''; | 2334 var validKey = ''; |
3360 if (Number(keyCode)) { | 2335 if (Number(keyCode)) { |
3361 if (keyCode >= 65 && keyCode <= 90) { | 2336 if (keyCode >= 65 && keyCode <= 90) { |
3362 // ascii a-z | |
3363 // lowercase is 32 offset from uppercase | |
3364 validKey = String.fromCharCode(32 + keyCode); | 2337 validKey = String.fromCharCode(32 + keyCode); |
3365 } else if (keyCode >= 112 && keyCode <= 123) { | 2338 } else if (keyCode >= 112 && keyCode <= 123) { |
3366 // function keys f1-f12 | |
3367 validKey = 'f' + (keyCode - 112); | 2339 validKey = 'f' + (keyCode - 112); |
3368 } else if (keyCode >= 48 && keyCode <= 57) { | 2340 } else if (keyCode >= 48 && keyCode <= 57) { |
3369 // top 0-9 keys | |
3370 validKey = String(keyCode - 48); | 2341 validKey = String(keyCode - 48); |
3371 } else if (keyCode >= 96 && keyCode <= 105) { | 2342 } else if (keyCode >= 96 && keyCode <= 105) { |
3372 // num pad 0-9 | |
3373 validKey = String(keyCode - 96); | 2343 validKey = String(keyCode - 96); |
3374 } else { | 2344 } else { |
3375 validKey = KEY_CODE[keyCode]; | 2345 validKey = KEY_CODE[keyCode]; |
3376 } | 2346 } |
3377 } | 2347 } |
3378 return validKey; | 2348 return validKey; |
3379 } | 2349 } |
3380 | 2350 |
3381 /** | |
3382 * Calculates the normalized key for a KeyboardEvent. | |
3383 * @param {KeyboardEvent} keyEvent | |
3384 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key | |
3385 * transformation to alpha-numeric chars. This is useful with key | |
3386 * combinations like shift + 2, which on FF for MacOS produces | |
3387 * keyEvent.key = @ | |
3388 * To get 2 returned, set noSpecialChars = true | |
3389 * To get @ returned, set noSpecialChars = false | |
3390 */ | |
3391 function normalizedKeyForEvent(keyEvent, noSpecialChars) { | 2351 function normalizedKeyForEvent(keyEvent, noSpecialChars) { |
3392 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to | |
3393 // .detail.key to support artificial keyboard events. | |
3394 return transformKey(keyEvent.key, noSpecialChars) || | 2352 return transformKey(keyEvent.key, noSpecialChars) || |
3395 transformKeyIdentifier(keyEvent.keyIdentifier) || | 2353 transformKeyIdentifier(keyEvent.keyIdentifier) || |
3396 transformKeyCode(keyEvent.keyCode) || | 2354 transformKeyCode(keyEvent.keyCode) || |
3397 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no
SpecialChars) || ''; | 2355 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no
SpecialChars) || ''; |
3398 } | 2356 } |
3399 | 2357 |
3400 function keyComboMatchesEvent(keyCombo, event) { | 2358 function keyComboMatchesEvent(keyCombo, event) { |
3401 // For combos with modifiers we support only alpha-numeric keys | |
3402 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); | 2359 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); |
3403 return keyEvent === keyCombo.key && | 2360 return keyEvent === keyCombo.key && |
3404 (!keyCombo.hasModifiers || ( | 2361 (!keyCombo.hasModifiers || ( |
3405 !!event.shiftKey === !!keyCombo.shiftKey && | 2362 !!event.shiftKey === !!keyCombo.shiftKey && |
3406 !!event.ctrlKey === !!keyCombo.ctrlKey && | 2363 !!event.ctrlKey === !!keyCombo.ctrlKey && |
3407 !!event.altKey === !!keyCombo.altKey && | 2364 !!event.altKey === !!keyCombo.altKey && |
3408 !!event.metaKey === !!keyCombo.metaKey) | 2365 !!event.metaKey === !!keyCombo.metaKey) |
3409 ); | 2366 ); |
3410 } | 2367 } |
3411 | 2368 |
(...skipping 23 matching lines...) Expand all Loading... |
3435 combo: keyComboString.split(':').shift() | 2392 combo: keyComboString.split(':').shift() |
3436 }); | 2393 }); |
3437 } | 2394 } |
3438 | 2395 |
3439 function parseEventString(eventString) { | 2396 function parseEventString(eventString) { |
3440 return eventString.trim().split(' ').map(function(keyComboString) { | 2397 return eventString.trim().split(' ').map(function(keyComboString) { |
3441 return parseKeyComboString(keyComboString); | 2398 return parseKeyComboString(keyComboString); |
3442 }); | 2399 }); |
3443 } | 2400 } |
3444 | 2401 |
3445 /** | |
3446 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces
sing | |
3447 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3
.org/TR/wai-aria-practices/#kbd_general_binding). | |
3448 * The element takes care of browser differences with respect to Keyboard ev
ents | |
3449 * and uses an expressive syntax to filter key presses. | |
3450 * | |
3451 * Use the `keyBindings` prototype property to express what combination of k
eys | |
3452 * will trigger the callback. A key binding has the format | |
3453 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or | |
3454 * `"KEY:EVENT": "callback"` are valid as well). Some examples: | |
3455 * | |
3456 * keyBindings: { | |
3457 * 'space': '_onKeydown', // same as 'space:keydown' | |
3458 * 'shift+tab': '_onKeydown', | |
3459 * 'enter:keypress': '_onKeypress', | |
3460 * 'esc:keyup': '_onKeyup' | |
3461 * } | |
3462 * | |
3463 * The callback will receive with an event containing the following informat
ion in `event.detail`: | |
3464 * | |
3465 * _onKeydown: function(event) { | |
3466 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab" | |
3467 * console.log(event.detail.key); // KEY only, e.g. "tab" | |
3468 * console.log(event.detail.event); // EVENT, e.g. "keydown" | |
3469 * console.log(event.detail.keyboardEvent); // the original KeyboardE
vent | |
3470 * } | |
3471 * | |
3472 * Use the `keyEventTarget` attribute to set up event handlers on a specific | |
3473 * node. | |
3474 * | |
3475 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k
eys-behavior/blob/master/demo/x-key-aware.html) | |
3476 * for an example. | |
3477 * | |
3478 * @demo demo/index.html | |
3479 * @polymerBehavior | |
3480 */ | |
3481 Polymer.IronA11yKeysBehavior = { | 2402 Polymer.IronA11yKeysBehavior = { |
3482 properties: { | 2403 properties: { |
3483 /** | |
3484 * The EventTarget that will be firing relevant KeyboardEvents. Set it t
o | |
3485 * `null` to disable the listeners. | |
3486 * @type {?EventTarget} | |
3487 */ | |
3488 keyEventTarget: { | 2404 keyEventTarget: { |
3489 type: Object, | 2405 type: Object, |
3490 value: function() { | 2406 value: function() { |
3491 return this; | 2407 return this; |
3492 } | 2408 } |
3493 }, | 2409 }, |
3494 | 2410 |
3495 /** | |
3496 * If true, this property will cause the implementing element to | |
3497 * automatically stop propagation on any handled KeyboardEvents. | |
3498 */ | |
3499 stopKeyboardEventPropagation: { | 2411 stopKeyboardEventPropagation: { |
3500 type: Boolean, | 2412 type: Boolean, |
3501 value: false | 2413 value: false |
3502 }, | 2414 }, |
3503 | 2415 |
3504 _boundKeyHandlers: { | 2416 _boundKeyHandlers: { |
3505 type: Array, | 2417 type: Array, |
3506 value: function() { | 2418 value: function() { |
3507 return []; | 2419 return []; |
3508 } | 2420 } |
3509 }, | 2421 }, |
3510 | 2422 |
3511 // We use this due to a limitation in IE10 where instances will have | |
3512 // own properties of everything on the "prototype". | |
3513 _imperativeKeyBindings: { | 2423 _imperativeKeyBindings: { |
3514 type: Object, | 2424 type: Object, |
3515 value: function() { | 2425 value: function() { |
3516 return {}; | 2426 return {}; |
3517 } | 2427 } |
3518 } | 2428 } |
3519 }, | 2429 }, |
3520 | 2430 |
3521 observers: [ | 2431 observers: [ |
3522 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' | 2432 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' |
3523 ], | 2433 ], |
3524 | 2434 |
3525 | 2435 |
3526 /** | |
3527 * To be used to express what combination of keys will trigger the relati
ve | |
3528 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}` | |
3529 * @type {Object} | |
3530 */ | |
3531 keyBindings: {}, | 2436 keyBindings: {}, |
3532 | 2437 |
3533 registered: function() { | 2438 registered: function() { |
3534 this._prepKeyBindings(); | 2439 this._prepKeyBindings(); |
3535 }, | 2440 }, |
3536 | 2441 |
3537 attached: function() { | 2442 attached: function() { |
3538 this._listenKeyEventListeners(); | 2443 this._listenKeyEventListeners(); |
3539 }, | 2444 }, |
3540 | 2445 |
3541 detached: function() { | 2446 detached: function() { |
3542 this._unlistenKeyEventListeners(); | 2447 this._unlistenKeyEventListeners(); |
3543 }, | 2448 }, |
3544 | 2449 |
3545 /** | |
3546 * Can be used to imperatively add a key binding to the implementing | |
3547 * element. This is the imperative equivalent of declaring a keybinding | |
3548 * in the `keyBindings` prototype property. | |
3549 */ | |
3550 addOwnKeyBinding: function(eventString, handlerName) { | 2450 addOwnKeyBinding: function(eventString, handlerName) { |
3551 this._imperativeKeyBindings[eventString] = handlerName; | 2451 this._imperativeKeyBindings[eventString] = handlerName; |
3552 this._prepKeyBindings(); | 2452 this._prepKeyBindings(); |
3553 this._resetKeyEventListeners(); | 2453 this._resetKeyEventListeners(); |
3554 }, | 2454 }, |
3555 | 2455 |
3556 /** | |
3557 * When called, will remove all imperatively-added key bindings. | |
3558 */ | |
3559 removeOwnKeyBindings: function() { | 2456 removeOwnKeyBindings: function() { |
3560 this._imperativeKeyBindings = {}; | 2457 this._imperativeKeyBindings = {}; |
3561 this._prepKeyBindings(); | 2458 this._prepKeyBindings(); |
3562 this._resetKeyEventListeners(); | 2459 this._resetKeyEventListeners(); |
3563 }, | 2460 }, |
3564 | 2461 |
3565 /** | |
3566 * Returns true if a keyboard event matches `eventString`. | |
3567 * | |
3568 * @param {KeyboardEvent} event | |
3569 * @param {string} eventString | |
3570 * @return {boolean} | |
3571 */ | |
3572 keyboardEventMatchesKeys: function(event, eventString) { | 2462 keyboardEventMatchesKeys: function(event, eventString) { |
3573 var keyCombos = parseEventString(eventString); | 2463 var keyCombos = parseEventString(eventString); |
3574 for (var i = 0; i < keyCombos.length; ++i) { | 2464 for (var i = 0; i < keyCombos.length; ++i) { |
3575 if (keyComboMatchesEvent(keyCombos[i], event)) { | 2465 if (keyComboMatchesEvent(keyCombos[i], event)) { |
3576 return true; | 2466 return true; |
3577 } | 2467 } |
3578 } | 2468 } |
3579 return false; | 2469 return false; |
3580 }, | 2470 }, |
3581 | 2471 |
(...skipping 15 matching lines...) Expand all Loading... |
3597 this._collectKeyBindings().forEach(function(keyBindings) { | 2487 this._collectKeyBindings().forEach(function(keyBindings) { |
3598 for (var eventString in keyBindings) { | 2488 for (var eventString in keyBindings) { |
3599 this._addKeyBinding(eventString, keyBindings[eventString]); | 2489 this._addKeyBinding(eventString, keyBindings[eventString]); |
3600 } | 2490 } |
3601 }, this); | 2491 }, this); |
3602 | 2492 |
3603 for (var eventString in this._imperativeKeyBindings) { | 2493 for (var eventString in this._imperativeKeyBindings) { |
3604 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); | 2494 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); |
3605 } | 2495 } |
3606 | 2496 |
3607 // Give precedence to combos with modifiers to be checked first. | |
3608 for (var eventName in this._keyBindings) { | 2497 for (var eventName in this._keyBindings) { |
3609 this._keyBindings[eventName].sort(function (kb1, kb2) { | 2498 this._keyBindings[eventName].sort(function (kb1, kb2) { |
3610 var b1 = kb1[0].hasModifiers; | 2499 var b1 = kb1[0].hasModifiers; |
3611 var b2 = kb2[0].hasModifiers; | 2500 var b2 = kb2[0].hasModifiers; |
3612 return (b1 === b2) ? 0 : b1 ? -1 : 1; | 2501 return (b1 === b2) ? 0 : b1 ? -1 : 1; |
3613 }) | 2502 }) |
3614 } | 2503 } |
3615 }, | 2504 }, |
3616 | 2505 |
3617 _addKeyBinding: function(eventString, handlerName) { | 2506 _addKeyBinding: function(eventString, handlerName) { |
(...skipping 30 matching lines...) Expand all Loading... |
3648 }, this); | 2537 }, this); |
3649 }, | 2538 }, |
3650 | 2539 |
3651 _unlistenKeyEventListeners: function() { | 2540 _unlistenKeyEventListeners: function() { |
3652 var keyHandlerTuple; | 2541 var keyHandlerTuple; |
3653 var keyEventTarget; | 2542 var keyEventTarget; |
3654 var eventName; | 2543 var eventName; |
3655 var boundKeyHandler; | 2544 var boundKeyHandler; |
3656 | 2545 |
3657 while (this._boundKeyHandlers.length) { | 2546 while (this._boundKeyHandlers.length) { |
3658 // My kingdom for block-scope binding and destructuring assignment.. | |
3659 keyHandlerTuple = this._boundKeyHandlers.pop(); | 2547 keyHandlerTuple = this._boundKeyHandlers.pop(); |
3660 keyEventTarget = keyHandlerTuple[0]; | 2548 keyEventTarget = keyHandlerTuple[0]; |
3661 eventName = keyHandlerTuple[1]; | 2549 eventName = keyHandlerTuple[1]; |
3662 boundKeyHandler = keyHandlerTuple[2]; | 2550 boundKeyHandler = keyHandlerTuple[2]; |
3663 | 2551 |
3664 keyEventTarget.removeEventListener(eventName, boundKeyHandler); | 2552 keyEventTarget.removeEventListener(eventName, boundKeyHandler); |
3665 } | 2553 } |
3666 }, | 2554 }, |
3667 | 2555 |
3668 _onKeyBindingEvent: function(keyBindings, event) { | 2556 _onKeyBindingEvent: function(keyBindings, event) { |
3669 if (this.stopKeyboardEventPropagation) { | 2557 if (this.stopKeyboardEventPropagation) { |
3670 event.stopPropagation(); | 2558 event.stopPropagation(); |
3671 } | 2559 } |
3672 | 2560 |
3673 // if event has been already prevented, don't do anything | |
3674 if (event.defaultPrevented) { | 2561 if (event.defaultPrevented) { |
3675 return; | 2562 return; |
3676 } | 2563 } |
3677 | 2564 |
3678 for (var i = 0; i < keyBindings.length; i++) { | 2565 for (var i = 0; i < keyBindings.length; i++) { |
3679 var keyCombo = keyBindings[i][0]; | 2566 var keyCombo = keyBindings[i][0]; |
3680 var handlerName = keyBindings[i][1]; | 2567 var handlerName = keyBindings[i][1]; |
3681 if (keyComboMatchesEvent(keyCombo, event)) { | 2568 if (keyComboMatchesEvent(keyCombo, event)) { |
3682 this._triggerKeyHandler(keyCombo, handlerName, event); | 2569 this._triggerKeyHandler(keyCombo, handlerName, event); |
3683 // exit the loop if eventDefault was prevented | |
3684 if (event.defaultPrevented) { | 2570 if (event.defaultPrevented) { |
3685 return; | 2571 return; |
3686 } | 2572 } |
3687 } | 2573 } |
3688 } | 2574 } |
3689 }, | 2575 }, |
3690 | 2576 |
3691 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { | 2577 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { |
3692 var detail = Object.create(keyCombo); | 2578 var detail = Object.create(keyCombo); |
3693 detail.keyboardEvent = keyboardEvent; | 2579 detail.keyboardEvent = keyboardEvent; |
3694 var event = new CustomEvent(keyCombo.event, { | 2580 var event = new CustomEvent(keyCombo.event, { |
3695 detail: detail, | 2581 detail: detail, |
3696 cancelable: true | 2582 cancelable: true |
3697 }); | 2583 }); |
3698 this[handlerName].call(this, event); | 2584 this[handlerName].call(this, event); |
3699 if (event.defaultPrevented) { | 2585 if (event.defaultPrevented) { |
3700 keyboardEvent.preventDefault(); | 2586 keyboardEvent.preventDefault(); |
3701 } | 2587 } |
3702 } | 2588 } |
3703 }; | 2589 }; |
3704 })(); | 2590 })(); |
3705 /** | |
3706 * @demo demo/index.html | |
3707 * @polymerBehavior | |
3708 */ | |
3709 Polymer.IronControlState = { | 2591 Polymer.IronControlState = { |
3710 | 2592 |
3711 properties: { | 2593 properties: { |
3712 | 2594 |
3713 /** | |
3714 * If true, the element currently has focus. | |
3715 */ | |
3716 focused: { | 2595 focused: { |
3717 type: Boolean, | 2596 type: Boolean, |
3718 value: false, | 2597 value: false, |
3719 notify: true, | 2598 notify: true, |
3720 readOnly: true, | 2599 readOnly: true, |
3721 reflectToAttribute: true | 2600 reflectToAttribute: true |
3722 }, | 2601 }, |
3723 | 2602 |
3724 /** | |
3725 * If true, the user cannot interact with this element. | |
3726 */ | |
3727 disabled: { | 2603 disabled: { |
3728 type: Boolean, | 2604 type: Boolean, |
3729 value: false, | 2605 value: false, |
3730 notify: true, | 2606 notify: true, |
3731 observer: '_disabledChanged', | 2607 observer: '_disabledChanged', |
3732 reflectToAttribute: true | 2608 reflectToAttribute: true |
3733 }, | 2609 }, |
3734 | 2610 |
3735 _oldTabIndex: { | 2611 _oldTabIndex: { |
3736 type: Number | 2612 type: Number |
(...skipping 11 matching lines...) Expand all Loading... |
3748 observers: [ | 2624 observers: [ |
3749 '_changedControlState(focused, disabled)' | 2625 '_changedControlState(focused, disabled)' |
3750 ], | 2626 ], |
3751 | 2627 |
3752 ready: function() { | 2628 ready: function() { |
3753 this.addEventListener('focus', this._boundFocusBlurHandler, true); | 2629 this.addEventListener('focus', this._boundFocusBlurHandler, true); |
3754 this.addEventListener('blur', this._boundFocusBlurHandler, true); | 2630 this.addEventListener('blur', this._boundFocusBlurHandler, true); |
3755 }, | 2631 }, |
3756 | 2632 |
3757 _focusBlurHandler: function(event) { | 2633 _focusBlurHandler: function(event) { |
3758 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will | |
3759 // eventually become `this` due to retargeting; if we are not in | |
3760 // ShadowDOM land, `event.target` will eventually become `this` due | |
3761 // to the second conditional which fires a synthetic event (that is also | |
3762 // handled). In either case, we can disregard `event.path`. | |
3763 | 2634 |
3764 if (event.target === this) { | 2635 if (event.target === this) { |
3765 this._setFocused(event.type === 'focus'); | 2636 this._setFocused(event.type === 'focus'); |
3766 } else if (!this.shadowRoot) { | 2637 } else if (!this.shadowRoot) { |
3767 var target = /** @type {Node} */(Polymer.dom(event).localTarget); | 2638 var target = /** @type {Node} */(Polymer.dom(event).localTarget); |
3768 if (!this.isLightDescendant(target)) { | 2639 if (!this.isLightDescendant(target)) { |
3769 this.fire(event.type, {sourceEvent: event}, { | 2640 this.fire(event.type, {sourceEvent: event}, { |
3770 node: this, | 2641 node: this, |
3771 bubbles: event.bubbles, | 2642 bubbles: event.bubbles, |
3772 cancelable: event.cancelable | 2643 cancelable: event.cancelable |
3773 }); | 2644 }); |
3774 } | 2645 } |
3775 } | 2646 } |
3776 }, | 2647 }, |
3777 | 2648 |
3778 _disabledChanged: function(disabled, old) { | 2649 _disabledChanged: function(disabled, old) { |
3779 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); | 2650 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
3780 this.style.pointerEvents = disabled ? 'none' : ''; | 2651 this.style.pointerEvents = disabled ? 'none' : ''; |
3781 if (disabled) { | 2652 if (disabled) { |
3782 this._oldTabIndex = this.tabIndex; | 2653 this._oldTabIndex = this.tabIndex; |
3783 this._setFocused(false); | 2654 this._setFocused(false); |
3784 this.tabIndex = -1; | 2655 this.tabIndex = -1; |
3785 this.blur(); | 2656 this.blur(); |
3786 } else if (this._oldTabIndex !== undefined) { | 2657 } else if (this._oldTabIndex !== undefined) { |
3787 this.tabIndex = this._oldTabIndex; | 2658 this.tabIndex = this._oldTabIndex; |
3788 } | 2659 } |
3789 }, | 2660 }, |
3790 | 2661 |
3791 _changedControlState: function() { | 2662 _changedControlState: function() { |
3792 // _controlStateChanged is abstract, follow-on behaviors may implement it | |
3793 if (this._controlStateChanged) { | 2663 if (this._controlStateChanged) { |
3794 this._controlStateChanged(); | 2664 this._controlStateChanged(); |
3795 } | 2665 } |
3796 } | 2666 } |
3797 | 2667 |
3798 }; | 2668 }; |
3799 /** | |
3800 * @demo demo/index.html | |
3801 * @polymerBehavior Polymer.IronButtonState | |
3802 */ | |
3803 Polymer.IronButtonStateImpl = { | 2669 Polymer.IronButtonStateImpl = { |
3804 | 2670 |
3805 properties: { | 2671 properties: { |
3806 | 2672 |
3807 /** | |
3808 * If true, the user is currently holding down the button. | |
3809 */ | |
3810 pressed: { | 2673 pressed: { |
3811 type: Boolean, | 2674 type: Boolean, |
3812 readOnly: true, | 2675 readOnly: true, |
3813 value: false, | 2676 value: false, |
3814 reflectToAttribute: true, | 2677 reflectToAttribute: true, |
3815 observer: '_pressedChanged' | 2678 observer: '_pressedChanged' |
3816 }, | 2679 }, |
3817 | 2680 |
3818 /** | |
3819 * If true, the button toggles the active state with each tap or press | |
3820 * of the spacebar. | |
3821 */ | |
3822 toggles: { | 2681 toggles: { |
3823 type: Boolean, | 2682 type: Boolean, |
3824 value: false, | 2683 value: false, |
3825 reflectToAttribute: true | 2684 reflectToAttribute: true |
3826 }, | 2685 }, |
3827 | 2686 |
3828 /** | |
3829 * If true, the button is a toggle and is currently in the active state. | |
3830 */ | |
3831 active: { | 2687 active: { |
3832 type: Boolean, | 2688 type: Boolean, |
3833 value: false, | 2689 value: false, |
3834 notify: true, | 2690 notify: true, |
3835 reflectToAttribute: true | 2691 reflectToAttribute: true |
3836 }, | 2692 }, |
3837 | 2693 |
3838 /** | |
3839 * True if the element is currently being pressed by a "pointer," which | |
3840 * is loosely defined as mouse or touch input (but specifically excluding | |
3841 * keyboard input). | |
3842 */ | |
3843 pointerDown: { | 2694 pointerDown: { |
3844 type: Boolean, | 2695 type: Boolean, |
3845 readOnly: true, | 2696 readOnly: true, |
3846 value: false | 2697 value: false |
3847 }, | 2698 }, |
3848 | 2699 |
3849 /** | |
3850 * True if the input device that caused the element to receive focus | |
3851 * was a keyboard. | |
3852 */ | |
3853 receivedFocusFromKeyboard: { | 2700 receivedFocusFromKeyboard: { |
3854 type: Boolean, | 2701 type: Boolean, |
3855 readOnly: true | 2702 readOnly: true |
3856 }, | 2703 }, |
3857 | 2704 |
3858 /** | |
3859 * The aria attribute to be set if the button is a toggle and in the | |
3860 * active state. | |
3861 */ | |
3862 ariaActiveAttribute: { | 2705 ariaActiveAttribute: { |
3863 type: String, | 2706 type: String, |
3864 value: 'aria-pressed', | 2707 value: 'aria-pressed', |
3865 observer: '_ariaActiveAttributeChanged' | 2708 observer: '_ariaActiveAttributeChanged' |
3866 } | 2709 } |
3867 }, | 2710 }, |
3868 | 2711 |
3869 listeners: { | 2712 listeners: { |
3870 down: '_downHandler', | 2713 down: '_downHandler', |
3871 up: '_upHandler', | 2714 up: '_upHandler', |
3872 tap: '_tapHandler' | 2715 tap: '_tapHandler' |
3873 }, | 2716 }, |
3874 | 2717 |
3875 observers: [ | 2718 observers: [ |
3876 '_detectKeyboardFocus(focused)', | 2719 '_detectKeyboardFocus(focused)', |
3877 '_activeChanged(active, ariaActiveAttribute)' | 2720 '_activeChanged(active, ariaActiveAttribute)' |
3878 ], | 2721 ], |
3879 | 2722 |
3880 keyBindings: { | 2723 keyBindings: { |
3881 'enter:keydown': '_asyncClick', | 2724 'enter:keydown': '_asyncClick', |
3882 'space:keydown': '_spaceKeyDownHandler', | 2725 'space:keydown': '_spaceKeyDownHandler', |
3883 'space:keyup': '_spaceKeyUpHandler', | 2726 'space:keyup': '_spaceKeyUpHandler', |
3884 }, | 2727 }, |
3885 | 2728 |
3886 _mouseEventRe: /^mouse/, | 2729 _mouseEventRe: /^mouse/, |
3887 | 2730 |
3888 _tapHandler: function() { | 2731 _tapHandler: function() { |
3889 if (this.toggles) { | 2732 if (this.toggles) { |
3890 // a tap is needed to toggle the active state | |
3891 this._userActivate(!this.active); | 2733 this._userActivate(!this.active); |
3892 } else { | 2734 } else { |
3893 this.active = false; | 2735 this.active = false; |
3894 } | 2736 } |
3895 }, | 2737 }, |
3896 | 2738 |
3897 _detectKeyboardFocus: function(focused) { | 2739 _detectKeyboardFocus: function(focused) { |
3898 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); | 2740 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); |
3899 }, | 2741 }, |
3900 | 2742 |
3901 // to emulate native checkbox, (de-)activations from a user interaction fire | |
3902 // 'change' events | |
3903 _userActivate: function(active) { | 2743 _userActivate: function(active) { |
3904 if (this.active !== active) { | 2744 if (this.active !== active) { |
3905 this.active = active; | 2745 this.active = active; |
3906 this.fire('change'); | 2746 this.fire('change'); |
3907 } | 2747 } |
3908 }, | 2748 }, |
3909 | 2749 |
3910 _downHandler: function(event) { | 2750 _downHandler: function(event) { |
3911 this._setPointerDown(true); | 2751 this._setPointerDown(true); |
3912 this._setPressed(true); | 2752 this._setPressed(true); |
3913 this._setReceivedFocusFromKeyboard(false); | 2753 this._setReceivedFocusFromKeyboard(false); |
3914 }, | 2754 }, |
3915 | 2755 |
3916 _upHandler: function() { | 2756 _upHandler: function() { |
3917 this._setPointerDown(false); | 2757 this._setPointerDown(false); |
3918 this._setPressed(false); | 2758 this._setPressed(false); |
3919 }, | 2759 }, |
3920 | 2760 |
3921 /** | |
3922 * @param {!KeyboardEvent} event . | |
3923 */ | |
3924 _spaceKeyDownHandler: function(event) { | 2761 _spaceKeyDownHandler: function(event) { |
3925 var keyboardEvent = event.detail.keyboardEvent; | 2762 var keyboardEvent = event.detail.keyboardEvent; |
3926 var target = Polymer.dom(keyboardEvent).localTarget; | 2763 var target = Polymer.dom(keyboardEvent).localTarget; |
3927 | 2764 |
3928 // Ignore the event if this is coming from a focused light child, since th
at | |
3929 // element will deal with it. | |
3930 if (this.isLightDescendant(/** @type {Node} */(target))) | 2765 if (this.isLightDescendant(/** @type {Node} */(target))) |
3931 return; | 2766 return; |
3932 | 2767 |
3933 keyboardEvent.preventDefault(); | 2768 keyboardEvent.preventDefault(); |
3934 keyboardEvent.stopImmediatePropagation(); | 2769 keyboardEvent.stopImmediatePropagation(); |
3935 this._setPressed(true); | 2770 this._setPressed(true); |
3936 }, | 2771 }, |
3937 | 2772 |
3938 /** | |
3939 * @param {!KeyboardEvent} event . | |
3940 */ | |
3941 _spaceKeyUpHandler: function(event) { | 2773 _spaceKeyUpHandler: function(event) { |
3942 var keyboardEvent = event.detail.keyboardEvent; | 2774 var keyboardEvent = event.detail.keyboardEvent; |
3943 var target = Polymer.dom(keyboardEvent).localTarget; | 2775 var target = Polymer.dom(keyboardEvent).localTarget; |
3944 | 2776 |
3945 // Ignore the event if this is coming from a focused light child, since th
at | |
3946 // element will deal with it. | |
3947 if (this.isLightDescendant(/** @type {Node} */(target))) | 2777 if (this.isLightDescendant(/** @type {Node} */(target))) |
3948 return; | 2778 return; |
3949 | 2779 |
3950 if (this.pressed) { | 2780 if (this.pressed) { |
3951 this._asyncClick(); | 2781 this._asyncClick(); |
3952 } | 2782 } |
3953 this._setPressed(false); | 2783 this._setPressed(false); |
3954 }, | 2784 }, |
3955 | 2785 |
3956 // trigger click asynchronously, the asynchrony is useful to allow one | |
3957 // event handler to unwind before triggering another event | |
3958 _asyncClick: function() { | 2786 _asyncClick: function() { |
3959 this.async(function() { | 2787 this.async(function() { |
3960 this.click(); | 2788 this.click(); |
3961 }, 1); | 2789 }, 1); |
3962 }, | 2790 }, |
3963 | 2791 |
3964 // any of these changes are considered a change to button state | |
3965 | 2792 |
3966 _pressedChanged: function(pressed) { | 2793 _pressedChanged: function(pressed) { |
3967 this._changedButtonState(); | 2794 this._changedButtonState(); |
3968 }, | 2795 }, |
3969 | 2796 |
3970 _ariaActiveAttributeChanged: function(value, oldValue) { | 2797 _ariaActiveAttributeChanged: function(value, oldValue) { |
3971 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { | 2798 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { |
3972 this.removeAttribute(oldValue); | 2799 this.removeAttribute(oldValue); |
3973 } | 2800 } |
3974 }, | 2801 }, |
3975 | 2802 |
3976 _activeChanged: function(active, ariaActiveAttribute) { | 2803 _activeChanged: function(active, ariaActiveAttribute) { |
3977 if (this.toggles) { | 2804 if (this.toggles) { |
3978 this.setAttribute(this.ariaActiveAttribute, | 2805 this.setAttribute(this.ariaActiveAttribute, |
3979 active ? 'true' : 'false'); | 2806 active ? 'true' : 'false'); |
3980 } else { | 2807 } else { |
3981 this.removeAttribute(this.ariaActiveAttribute); | 2808 this.removeAttribute(this.ariaActiveAttribute); |
3982 } | 2809 } |
3983 this._changedButtonState(); | 2810 this._changedButtonState(); |
3984 }, | 2811 }, |
3985 | 2812 |
3986 _controlStateChanged: function() { | 2813 _controlStateChanged: function() { |
3987 if (this.disabled) { | 2814 if (this.disabled) { |
3988 this._setPressed(false); | 2815 this._setPressed(false); |
3989 } else { | 2816 } else { |
3990 this._changedButtonState(); | 2817 this._changedButtonState(); |
3991 } | 2818 } |
3992 }, | 2819 }, |
3993 | 2820 |
3994 // provide hook for follow-on behaviors to react to button-state | |
3995 | 2821 |
3996 _changedButtonState: function() { | 2822 _changedButtonState: function() { |
3997 if (this._buttonStateChanged) { | 2823 if (this._buttonStateChanged) { |
3998 this._buttonStateChanged(); // abstract | 2824 this._buttonStateChanged(); // abstract |
3999 } | 2825 } |
4000 } | 2826 } |
4001 | 2827 |
4002 }; | 2828 }; |
4003 | 2829 |
4004 /** @polymerBehavior */ | 2830 /** @polymerBehavior */ |
4005 Polymer.IronButtonState = [ | 2831 Polymer.IronButtonState = [ |
4006 Polymer.IronA11yKeysBehavior, | 2832 Polymer.IronA11yKeysBehavior, |
4007 Polymer.IronButtonStateImpl | 2833 Polymer.IronButtonStateImpl |
4008 ]; | 2834 ]; |
4009 (function() { | 2835 (function() { |
4010 var Utility = { | 2836 var Utility = { |
4011 distance: function(x1, y1, x2, y2) { | 2837 distance: function(x1, y1, x2, y2) { |
4012 var xDelta = (x1 - x2); | 2838 var xDelta = (x1 - x2); |
4013 var yDelta = (y1 - y2); | 2839 var yDelta = (y1 - y2); |
4014 | 2840 |
4015 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); | 2841 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); |
4016 }, | 2842 }, |
4017 | 2843 |
4018 now: window.performance && window.performance.now ? | 2844 now: window.performance && window.performance.now ? |
4019 window.performance.now.bind(window.performance) : Date.now | 2845 window.performance.now.bind(window.performance) : Date.now |
4020 }; | 2846 }; |
4021 | 2847 |
4022 /** | |
4023 * @param {HTMLElement} element | |
4024 * @constructor | |
4025 */ | |
4026 function ElementMetrics(element) { | 2848 function ElementMetrics(element) { |
4027 this.element = element; | 2849 this.element = element; |
4028 this.width = this.boundingRect.width; | 2850 this.width = this.boundingRect.width; |
4029 this.height = this.boundingRect.height; | 2851 this.height = this.boundingRect.height; |
4030 | 2852 |
4031 this.size = Math.max(this.width, this.height); | 2853 this.size = Math.max(this.width, this.height); |
4032 } | 2854 } |
4033 | 2855 |
4034 ElementMetrics.prototype = { | 2856 ElementMetrics.prototype = { |
4035 get boundingRect () { | 2857 get boundingRect () { |
4036 return this.element.getBoundingClientRect(); | 2858 return this.element.getBoundingClientRect(); |
4037 }, | 2859 }, |
4038 | 2860 |
4039 furthestCornerDistanceFrom: function(x, y) { | 2861 furthestCornerDistanceFrom: function(x, y) { |
4040 var topLeft = Utility.distance(x, y, 0, 0); | 2862 var topLeft = Utility.distance(x, y, 0, 0); |
4041 var topRight = Utility.distance(x, y, this.width, 0); | 2863 var topRight = Utility.distance(x, y, this.width, 0); |
4042 var bottomLeft = Utility.distance(x, y, 0, this.height); | 2864 var bottomLeft = Utility.distance(x, y, 0, this.height); |
4043 var bottomRight = Utility.distance(x, y, this.width, this.height); | 2865 var bottomRight = Utility.distance(x, y, this.width, this.height); |
4044 | 2866 |
4045 return Math.max(topLeft, topRight, bottomLeft, bottomRight); | 2867 return Math.max(topLeft, topRight, bottomLeft, bottomRight); |
4046 } | 2868 } |
4047 }; | 2869 }; |
4048 | 2870 |
4049 /** | |
4050 * @param {HTMLElement} element | |
4051 * @constructor | |
4052 */ | |
4053 function Ripple(element) { | 2871 function Ripple(element) { |
4054 this.element = element; | 2872 this.element = element; |
4055 this.color = window.getComputedStyle(element).color; | 2873 this.color = window.getComputedStyle(element).color; |
4056 | 2874 |
4057 this.wave = document.createElement('div'); | 2875 this.wave = document.createElement('div'); |
4058 this.waveContainer = document.createElement('div'); | 2876 this.waveContainer = document.createElement('div'); |
4059 this.wave.style.backgroundColor = this.color; | 2877 this.wave.style.backgroundColor = this.color; |
4060 this.wave.classList.add('wave'); | 2878 this.wave.classList.add('wave'); |
4061 this.waveContainer.classList.add('wave-container'); | 2879 this.waveContainer.classList.add('wave-container'); |
4062 Polymer.dom(this.waveContainer).appendChild(this.wave); | 2880 Polymer.dom(this.waveContainer).appendChild(this.wave); |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4136 return this.initialOpacity; | 2954 return this.initialOpacity; |
4137 } | 2955 } |
4138 | 2956 |
4139 return Math.max( | 2957 return Math.max( |
4140 0, | 2958 0, |
4141 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe
locity | 2959 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe
locity |
4142 ); | 2960 ); |
4143 }, | 2961 }, |
4144 | 2962 |
4145 get outerOpacity() { | 2963 get outerOpacity() { |
4146 // Linear increase in background opacity, capped at the opacity | |
4147 // of the wavefront (waveOpacity). | |
4148 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; | 2964 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; |
4149 var waveOpacity = this.opacity; | 2965 var waveOpacity = this.opacity; |
4150 | 2966 |
4151 return Math.max( | 2967 return Math.max( |
4152 0, | 2968 0, |
4153 Math.min(outerOpacity, waveOpacity) | 2969 Math.min(outerOpacity, waveOpacity) |
4154 ); | 2970 ); |
4155 }, | 2971 }, |
4156 | 2972 |
4157 get isOpacityFullyDecayed() { | 2973 get isOpacityFullyDecayed() { |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4216 var dx; | 3032 var dx; |
4217 var dy; | 3033 var dy; |
4218 | 3034 |
4219 this.wave.style.opacity = this.opacity; | 3035 this.wave.style.opacity = this.opacity; |
4220 | 3036 |
4221 scale = this.radius / (this.containerMetrics.size / 2); | 3037 scale = this.radius / (this.containerMetrics.size / 2); |
4222 dx = this.xNow - (this.containerMetrics.width / 2); | 3038 dx = this.xNow - (this.containerMetrics.width / 2); |
4223 dy = this.yNow - (this.containerMetrics.height / 2); | 3039 dy = this.yNow - (this.containerMetrics.height / 2); |
4224 | 3040 |
4225 | 3041 |
4226 // 2d transform for safari because of border-radius and overflow:hidden
clipping bug. | |
4227 // https://bugs.webkit.org/show_bug.cgi?id=98538 | |
4228 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' +
dy + 'px)'; | 3042 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' +
dy + 'px)'; |
4229 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy +
'px, 0)'; | 3043 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy +
'px, 0)'; |
4230 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; | 3044 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; |
4231 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; | 3045 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; |
4232 }, | 3046 }, |
4233 | 3047 |
4234 /** @param {Event=} event */ | 3048 /** @param {Event=} event */ |
4235 downAction: function(event) { | 3049 downAction: function(event) { |
4236 var xCenter = this.containerMetrics.width / 2; | 3050 var xCenter = this.containerMetrics.width / 2; |
4237 var yCenter = this.containerMetrics.height / 2; | 3051 var yCenter = this.containerMetrics.height / 2; |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4293 }; | 3107 }; |
4294 | 3108 |
4295 Polymer({ | 3109 Polymer({ |
4296 is: 'paper-ripple', | 3110 is: 'paper-ripple', |
4297 | 3111 |
4298 behaviors: [ | 3112 behaviors: [ |
4299 Polymer.IronA11yKeysBehavior | 3113 Polymer.IronA11yKeysBehavior |
4300 ], | 3114 ], |
4301 | 3115 |
4302 properties: { | 3116 properties: { |
4303 /** | |
4304 * The initial opacity set on the wave. | |
4305 * | |
4306 * @attribute initialOpacity | |
4307 * @type number | |
4308 * @default 0.25 | |
4309 */ | |
4310 initialOpacity: { | 3117 initialOpacity: { |
4311 type: Number, | 3118 type: Number, |
4312 value: 0.25 | 3119 value: 0.25 |
4313 }, | 3120 }, |
4314 | 3121 |
4315 /** | |
4316 * How fast (opacity per second) the wave fades out. | |
4317 * | |
4318 * @attribute opacityDecayVelocity | |
4319 * @type number | |
4320 * @default 0.8 | |
4321 */ | |
4322 opacityDecayVelocity: { | 3122 opacityDecayVelocity: { |
4323 type: Number, | 3123 type: Number, |
4324 value: 0.8 | 3124 value: 0.8 |
4325 }, | 3125 }, |
4326 | 3126 |
4327 /** | |
4328 * If true, ripples will exhibit a gravitational pull towards | |
4329 * the center of their container as they fade away. | |
4330 * | |
4331 * @attribute recenters | |
4332 * @type boolean | |
4333 * @default false | |
4334 */ | |
4335 recenters: { | 3127 recenters: { |
4336 type: Boolean, | 3128 type: Boolean, |
4337 value: false | 3129 value: false |
4338 }, | 3130 }, |
4339 | 3131 |
4340 /** | |
4341 * If true, ripples will center inside its container | |
4342 * | |
4343 * @attribute recenters | |
4344 * @type boolean | |
4345 * @default false | |
4346 */ | |
4347 center: { | 3132 center: { |
4348 type: Boolean, | 3133 type: Boolean, |
4349 value: false | 3134 value: false |
4350 }, | 3135 }, |
4351 | 3136 |
4352 /** | |
4353 * A list of the visual ripples. | |
4354 * | |
4355 * @attribute ripples | |
4356 * @type Array | |
4357 * @default [] | |
4358 */ | |
4359 ripples: { | 3137 ripples: { |
4360 type: Array, | 3138 type: Array, |
4361 value: function() { | 3139 value: function() { |
4362 return []; | 3140 return []; |
4363 } | 3141 } |
4364 }, | 3142 }, |
4365 | 3143 |
4366 /** | |
4367 * True when there are visible ripples animating within the | |
4368 * element. | |
4369 */ | |
4370 animating: { | 3144 animating: { |
4371 type: Boolean, | 3145 type: Boolean, |
4372 readOnly: true, | 3146 readOnly: true, |
4373 reflectToAttribute: true, | 3147 reflectToAttribute: true, |
4374 value: false | 3148 value: false |
4375 }, | 3149 }, |
4376 | 3150 |
4377 /** | |
4378 * If true, the ripple will remain in the "down" state until `holdDown` | |
4379 * is set to false again. | |
4380 */ | |
4381 holdDown: { | 3151 holdDown: { |
4382 type: Boolean, | 3152 type: Boolean, |
4383 value: false, | 3153 value: false, |
4384 observer: '_holdDownChanged' | 3154 observer: '_holdDownChanged' |
4385 }, | 3155 }, |
4386 | 3156 |
4387 /** | |
4388 * If true, the ripple will not generate a ripple effect | |
4389 * via pointer interaction. | |
4390 * Calling ripple's imperative api like `simulatedRipple` will | |
4391 * still generate the ripple effect. | |
4392 */ | |
4393 noink: { | 3157 noink: { |
4394 type: Boolean, | 3158 type: Boolean, |
4395 value: false | 3159 value: false |
4396 }, | 3160 }, |
4397 | 3161 |
4398 _animating: { | 3162 _animating: { |
4399 type: Boolean | 3163 type: Boolean |
4400 }, | 3164 }, |
4401 | 3165 |
4402 _boundAnimate: { | 3166 _boundAnimate: { |
4403 type: Function, | 3167 type: Function, |
4404 value: function() { | 3168 value: function() { |
4405 return this.animate.bind(this); | 3169 return this.animate.bind(this); |
4406 } | 3170 } |
4407 } | 3171 } |
4408 }, | 3172 }, |
4409 | 3173 |
4410 get target () { | 3174 get target () { |
4411 return this.keyEventTarget; | 3175 return this.keyEventTarget; |
4412 }, | 3176 }, |
4413 | 3177 |
4414 keyBindings: { | 3178 keyBindings: { |
4415 'enter:keydown': '_onEnterKeydown', | 3179 'enter:keydown': '_onEnterKeydown', |
4416 'space:keydown': '_onSpaceKeydown', | 3180 'space:keydown': '_onSpaceKeydown', |
4417 'space:keyup': '_onSpaceKeyup' | 3181 'space:keyup': '_onSpaceKeyup' |
4418 }, | 3182 }, |
4419 | 3183 |
4420 attached: function() { | 3184 attached: function() { |
4421 // Set up a11yKeysBehavior to listen to key events on the target, | |
4422 // so that space and enter activate the ripple even if the target doesn'
t | |
4423 // handle key events. The key handlers deal with `noink` themselves. | |
4424 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE | 3185 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE |
4425 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host; | 3186 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host; |
4426 } else { | 3187 } else { |
4427 this.keyEventTarget = this.parentNode; | 3188 this.keyEventTarget = this.parentNode; |
4428 } | 3189 } |
4429 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); | 3190 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); |
4430 this.listen(keyEventTarget, 'up', 'uiUpAction'); | 3191 this.listen(keyEventTarget, 'up', 'uiUpAction'); |
4431 this.listen(keyEventTarget, 'down', 'uiDownAction'); | 3192 this.listen(keyEventTarget, 'down', 'uiDownAction'); |
4432 }, | 3193 }, |
4433 | 3194 |
4434 detached: function() { | 3195 detached: function() { |
4435 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); | 3196 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); |
4436 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); | 3197 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); |
4437 this.keyEventTarget = null; | 3198 this.keyEventTarget = null; |
4438 }, | 3199 }, |
4439 | 3200 |
4440 get shouldKeepAnimating () { | 3201 get shouldKeepAnimating () { |
4441 for (var index = 0; index < this.ripples.length; ++index) { | 3202 for (var index = 0; index < this.ripples.length; ++index) { |
4442 if (!this.ripples[index].isAnimationComplete) { | 3203 if (!this.ripples[index].isAnimationComplete) { |
4443 return true; | 3204 return true; |
4444 } | 3205 } |
4445 } | 3206 } |
4446 | 3207 |
4447 return false; | 3208 return false; |
4448 }, | 3209 }, |
4449 | 3210 |
4450 simulatedRipple: function() { | 3211 simulatedRipple: function() { |
4451 this.downAction(null); | 3212 this.downAction(null); |
4452 | 3213 |
4453 // Please see polymer/polymer#1305 | |
4454 this.async(function() { | 3214 this.async(function() { |
4455 this.upAction(); | 3215 this.upAction(); |
4456 }, 1); | 3216 }, 1); |
4457 }, | 3217 }, |
4458 | 3218 |
4459 /** | |
4460 * Provokes a ripple down effect via a UI event, | |
4461 * respecting the `noink` property. | |
4462 * @param {Event=} event | |
4463 */ | |
4464 uiDownAction: function(event) { | 3219 uiDownAction: function(event) { |
4465 if (!this.noink) { | 3220 if (!this.noink) { |
4466 this.downAction(event); | 3221 this.downAction(event); |
4467 } | 3222 } |
4468 }, | 3223 }, |
4469 | 3224 |
4470 /** | |
4471 * Provokes a ripple down effect via a UI event, | |
4472 * *not* respecting the `noink` property. | |
4473 * @param {Event=} event | |
4474 */ | |
4475 downAction: function(event) { | 3225 downAction: function(event) { |
4476 if (this.holdDown && this.ripples.length > 0) { | 3226 if (this.holdDown && this.ripples.length > 0) { |
4477 return; | 3227 return; |
4478 } | 3228 } |
4479 | 3229 |
4480 var ripple = this.addRipple(); | 3230 var ripple = this.addRipple(); |
4481 | 3231 |
4482 ripple.downAction(event); | 3232 ripple.downAction(event); |
4483 | 3233 |
4484 if (!this._animating) { | 3234 if (!this._animating) { |
4485 this._animating = true; | 3235 this._animating = true; |
4486 this.animate(); | 3236 this.animate(); |
4487 } | 3237 } |
4488 }, | 3238 }, |
4489 | 3239 |
4490 /** | |
4491 * Provokes a ripple up effect via a UI event, | |
4492 * respecting the `noink` property. | |
4493 * @param {Event=} event | |
4494 */ | |
4495 uiUpAction: function(event) { | 3240 uiUpAction: function(event) { |
4496 if (!this.noink) { | 3241 if (!this.noink) { |
4497 this.upAction(event); | 3242 this.upAction(event); |
4498 } | 3243 } |
4499 }, | 3244 }, |
4500 | 3245 |
4501 /** | |
4502 * Provokes a ripple up effect via a UI event, | |
4503 * *not* respecting the `noink` property. | |
4504 * @param {Event=} event | |
4505 */ | |
4506 upAction: function(event) { | 3246 upAction: function(event) { |
4507 if (this.holdDown) { | 3247 if (this.holdDown) { |
4508 return; | 3248 return; |
4509 } | 3249 } |
4510 | 3250 |
4511 this.ripples.forEach(function(ripple) { | 3251 this.ripples.forEach(function(ripple) { |
4512 ripple.upAction(event); | 3252 ripple.upAction(event); |
4513 }); | 3253 }); |
4514 | 3254 |
4515 this._animating = true; | 3255 this._animating = true; |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4582 }, | 3322 }, |
4583 | 3323 |
4584 _onSpaceKeydown: function() { | 3324 _onSpaceKeydown: function() { |
4585 this.uiDownAction(); | 3325 this.uiDownAction(); |
4586 }, | 3326 }, |
4587 | 3327 |
4588 _onSpaceKeyup: function() { | 3328 _onSpaceKeyup: function() { |
4589 this.uiUpAction(); | 3329 this.uiUpAction(); |
4590 }, | 3330 }, |
4591 | 3331 |
4592 // note: holdDown does not respect noink since it can be a focus based | |
4593 // effect. | |
4594 _holdDownChanged: function(newVal, oldVal) { | 3332 _holdDownChanged: function(newVal, oldVal) { |
4595 if (oldVal === undefined) { | 3333 if (oldVal === undefined) { |
4596 return; | 3334 return; |
4597 } | 3335 } |
4598 if (newVal) { | 3336 if (newVal) { |
4599 this.downAction(); | 3337 this.downAction(); |
4600 } else { | 3338 } else { |
4601 this.upAction(); | 3339 this.upAction(); |
4602 } | 3340 } |
4603 } | 3341 } |
4604 | 3342 |
4605 /** | |
4606 Fired when the animation finishes. | |
4607 This is useful if you want to wait until | |
4608 the ripple animation finishes to perform some action. | |
4609 | |
4610 @event transitionend | |
4611 @param {{node: Object}} detail Contains the animated node. | |
4612 */ | |
4613 }); | 3343 }); |
4614 })(); | 3344 })(); |
4615 /** | |
4616 * `Polymer.PaperRippleBehavior` dynamically implements a ripple | |
4617 * when the element has focus via pointer or keyboard. | |
4618 * | |
4619 * NOTE: This behavior is intended to be used in conjunction with and after | |
4620 * `Polymer.IronButtonState` and `Polymer.IronControlState`. | |
4621 * | |
4622 * @polymerBehavior Polymer.PaperRippleBehavior | |
4623 */ | |
4624 Polymer.PaperRippleBehavior = { | 3345 Polymer.PaperRippleBehavior = { |
4625 properties: { | 3346 properties: { |
4626 /** | |
4627 * If true, the element will not produce a ripple effect when interacted | |
4628 * with via the pointer. | |
4629 */ | |
4630 noink: { | 3347 noink: { |
4631 type: Boolean, | 3348 type: Boolean, |
4632 observer: '_noinkChanged' | 3349 observer: '_noinkChanged' |
4633 }, | 3350 }, |
4634 | 3351 |
4635 /** | |
4636 * @type {Element|undefined} | |
4637 */ | |
4638 _rippleContainer: { | 3352 _rippleContainer: { |
4639 type: Object, | 3353 type: Object, |
4640 } | 3354 } |
4641 }, | 3355 }, |
4642 | 3356 |
4643 /** | |
4644 * Ensures a `<paper-ripple>` element is available when the element is | |
4645 * focused. | |
4646 */ | |
4647 _buttonStateChanged: function() { | 3357 _buttonStateChanged: function() { |
4648 if (this.focused) { | 3358 if (this.focused) { |
4649 this.ensureRipple(); | 3359 this.ensureRipple(); |
4650 } | 3360 } |
4651 }, | 3361 }, |
4652 | 3362 |
4653 /** | |
4654 * In addition to the functionality provided in `IronButtonState`, ensures | |
4655 * a ripple effect is created when the element is in a `pressed` state. | |
4656 */ | |
4657 _downHandler: function(event) { | 3363 _downHandler: function(event) { |
4658 Polymer.IronButtonStateImpl._downHandler.call(this, event); | 3364 Polymer.IronButtonStateImpl._downHandler.call(this, event); |
4659 if (this.pressed) { | 3365 if (this.pressed) { |
4660 this.ensureRipple(event); | 3366 this.ensureRipple(event); |
4661 } | 3367 } |
4662 }, | 3368 }, |
4663 | 3369 |
4664 /** | |
4665 * Ensures this element contains a ripple effect. For startup efficiency | |
4666 * the ripple effect is dynamically on demand when needed. | |
4667 * @param {!Event=} optTriggeringEvent (optional) event that triggered the | |
4668 * ripple. | |
4669 */ | |
4670 ensureRipple: function(optTriggeringEvent) { | 3370 ensureRipple: function(optTriggeringEvent) { |
4671 if (!this.hasRipple()) { | 3371 if (!this.hasRipple()) { |
4672 this._ripple = this._createRipple(); | 3372 this._ripple = this._createRipple(); |
4673 this._ripple.noink = this.noink; | 3373 this._ripple.noink = this.noink; |
4674 var rippleContainer = this._rippleContainer || this.root; | 3374 var rippleContainer = this._rippleContainer || this.root; |
4675 if (rippleContainer) { | 3375 if (rippleContainer) { |
4676 Polymer.dom(rippleContainer).appendChild(this._ripple); | 3376 Polymer.dom(rippleContainer).appendChild(this._ripple); |
4677 } | 3377 } |
4678 if (optTriggeringEvent) { | 3378 if (optTriggeringEvent) { |
4679 // Check if the event happened inside of the ripple container | |
4680 // Fall back to host instead of the root because distributed text | |
4681 // nodes are not valid event targets | |
4682 var domContainer = Polymer.dom(this._rippleContainer || this); | 3379 var domContainer = Polymer.dom(this._rippleContainer || this); |
4683 var target = Polymer.dom(optTriggeringEvent).rootTarget; | 3380 var target = Polymer.dom(optTriggeringEvent).rootTarget; |
4684 if (domContainer.deepContains( /** @type {Node} */(target))) { | 3381 if (domContainer.deepContains( /** @type {Node} */(target))) { |
4685 this._ripple.uiDownAction(optTriggeringEvent); | 3382 this._ripple.uiDownAction(optTriggeringEvent); |
4686 } | 3383 } |
4687 } | 3384 } |
4688 } | 3385 } |
4689 }, | 3386 }, |
4690 | 3387 |
4691 /** | |
4692 * Returns the `<paper-ripple>` element used by this element to create | |
4693 * ripple effects. The element's ripple is created on demand, when | |
4694 * necessary, and calling this method will force the | |
4695 * ripple to be created. | |
4696 */ | |
4697 getRipple: function() { | 3388 getRipple: function() { |
4698 this.ensureRipple(); | 3389 this.ensureRipple(); |
4699 return this._ripple; | 3390 return this._ripple; |
4700 }, | 3391 }, |
4701 | 3392 |
4702 /** | |
4703 * Returns true if this element currently contains a ripple effect. | |
4704 * @return {boolean} | |
4705 */ | |
4706 hasRipple: function() { | 3393 hasRipple: function() { |
4707 return Boolean(this._ripple); | 3394 return Boolean(this._ripple); |
4708 }, | 3395 }, |
4709 | 3396 |
4710 /** | |
4711 * Create the element's ripple effect via creating a `<paper-ripple>`. | |
4712 * Override this method to customize the ripple element. | |
4713 * @return {!PaperRippleElement} Returns a `<paper-ripple>` element. | |
4714 */ | |
4715 _createRipple: function() { | 3397 _createRipple: function() { |
4716 return /** @type {!PaperRippleElement} */ ( | 3398 return /** @type {!PaperRippleElement} */ ( |
4717 document.createElement('paper-ripple')); | 3399 document.createElement('paper-ripple')); |
4718 }, | 3400 }, |
4719 | 3401 |
4720 _noinkChanged: function(noink) { | 3402 _noinkChanged: function(noink) { |
4721 if (this.hasRipple()) { | 3403 if (this.hasRipple()) { |
4722 this._ripple.noink = noink; | 3404 this._ripple.noink = noink; |
4723 } | 3405 } |
4724 } | 3406 } |
4725 }; | 3407 }; |
4726 /** @polymerBehavior Polymer.PaperButtonBehavior */ | 3408 /** @polymerBehavior Polymer.PaperButtonBehavior */ |
4727 Polymer.PaperButtonBehaviorImpl = { | 3409 Polymer.PaperButtonBehaviorImpl = { |
4728 properties: { | 3410 properties: { |
4729 /** | |
4730 * The z-depth of this element, from 0-5. Setting to 0 will remove the | |
4731 * shadow, and each increasing number greater than 0 will be "deeper" | |
4732 * than the last. | |
4733 * | |
4734 * @attribute elevation | |
4735 * @type number | |
4736 * @default 1 | |
4737 */ | |
4738 elevation: { | 3411 elevation: { |
4739 type: Number, | 3412 type: Number, |
4740 reflectToAttribute: true, | 3413 reflectToAttribute: true, |
4741 readOnly: true | 3414 readOnly: true |
4742 } | 3415 } |
4743 }, | 3416 }, |
4744 | 3417 |
4745 observers: [ | 3418 observers: [ |
4746 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom
Keyboard)', | 3419 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom
Keyboard)', |
4747 '_computeKeyboardClass(receivedFocusFromKeyboard)' | 3420 '_computeKeyboardClass(receivedFocusFromKeyboard)' |
(...skipping 14 matching lines...) Expand all Loading... |
4762 } else if (this.receivedFocusFromKeyboard) { | 3435 } else if (this.receivedFocusFromKeyboard) { |
4763 e = 3; | 3436 e = 3; |
4764 } | 3437 } |
4765 this._setElevation(e); | 3438 this._setElevation(e); |
4766 }, | 3439 }, |
4767 | 3440 |
4768 _computeKeyboardClass: function(receivedFocusFromKeyboard) { | 3441 _computeKeyboardClass: function(receivedFocusFromKeyboard) { |
4769 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard); | 3442 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard); |
4770 }, | 3443 }, |
4771 | 3444 |
4772 /** | |
4773 * In addition to `IronButtonState` behavior, when space key goes down, | |
4774 * create a ripple down effect. | |
4775 * | |
4776 * @param {!KeyboardEvent} event . | |
4777 */ | |
4778 _spaceKeyDownHandler: function(event) { | 3445 _spaceKeyDownHandler: function(event) { |
4779 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); | 3446 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); |
4780 // Ensure that there is at most one ripple when the space key is held down
. | |
4781 if (this.hasRipple() && this.getRipple().ripples.length < 1) { | 3447 if (this.hasRipple() && this.getRipple().ripples.length < 1) { |
4782 this._ripple.uiDownAction(); | 3448 this._ripple.uiDownAction(); |
4783 } | 3449 } |
4784 }, | 3450 }, |
4785 | 3451 |
4786 /** | |
4787 * In addition to `IronButtonState` behavior, when space key goes up, | |
4788 * create a ripple up effect. | |
4789 * | |
4790 * @param {!KeyboardEvent} event . | |
4791 */ | |
4792 _spaceKeyUpHandler: function(event) { | 3452 _spaceKeyUpHandler: function(event) { |
4793 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); | 3453 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); |
4794 if (this.hasRipple()) { | 3454 if (this.hasRipple()) { |
4795 this._ripple.uiUpAction(); | 3455 this._ripple.uiUpAction(); |
4796 } | 3456 } |
4797 } | 3457 } |
4798 }; | 3458 }; |
4799 | 3459 |
4800 /** @polymerBehavior */ | 3460 /** @polymerBehavior */ |
4801 Polymer.PaperButtonBehavior = [ | 3461 Polymer.PaperButtonBehavior = [ |
4802 Polymer.IronButtonState, | 3462 Polymer.IronButtonState, |
4803 Polymer.IronControlState, | 3463 Polymer.IronControlState, |
4804 Polymer.PaperRippleBehavior, | 3464 Polymer.PaperRippleBehavior, |
4805 Polymer.PaperButtonBehaviorImpl | 3465 Polymer.PaperButtonBehaviorImpl |
4806 ]; | 3466 ]; |
4807 Polymer({ | 3467 Polymer({ |
4808 is: 'paper-button', | 3468 is: 'paper-button', |
4809 | 3469 |
4810 behaviors: [ | 3470 behaviors: [ |
4811 Polymer.PaperButtonBehavior | 3471 Polymer.PaperButtonBehavior |
4812 ], | 3472 ], |
4813 | 3473 |
4814 properties: { | 3474 properties: { |
4815 /** | |
4816 * If true, the button should be styled with a shadow. | |
4817 */ | |
4818 raised: { | 3475 raised: { |
4819 type: Boolean, | 3476 type: Boolean, |
4820 reflectToAttribute: true, | 3477 reflectToAttribute: true, |
4821 value: false, | 3478 value: false, |
4822 observer: '_calculateElevation' | 3479 observer: '_calculateElevation' |
4823 } | 3480 } |
4824 }, | 3481 }, |
4825 | 3482 |
4826 _calculateElevation: function() { | 3483 _calculateElevation: function() { |
4827 if (!this.raised) { | 3484 if (!this.raised) { |
4828 this._setElevation(0); | 3485 this._setElevation(0); |
4829 } else { | 3486 } else { |
4830 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); | 3487 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); |
4831 } | 3488 } |
4832 } | 3489 } |
4833 | 3490 |
4834 /** | |
4835 Fired when the animation finishes. | |
4836 This is useful if you want to wait until | |
4837 the ripple animation finishes to perform some action. | |
4838 | |
4839 @event transitionend | |
4840 Event param: {{node: Object}} detail Contains the animated node. | |
4841 */ | |
4842 }); | 3491 }); |
4843 (function() { | 3492 (function() { |
4844 | 3493 |
4845 // monostate data | |
4846 var metaDatas = {}; | 3494 var metaDatas = {}; |
4847 var metaArrays = {}; | 3495 var metaArrays = {}; |
4848 var singleton = null; | 3496 var singleton = null; |
4849 | 3497 |
4850 Polymer.IronMeta = Polymer({ | 3498 Polymer.IronMeta = Polymer({ |
4851 | 3499 |
4852 is: 'iron-meta', | 3500 is: 'iron-meta', |
4853 | 3501 |
4854 properties: { | 3502 properties: { |
4855 | 3503 |
4856 /** | |
4857 * The type of meta-data. All meta-data of the same type is stored | |
4858 * together. | |
4859 */ | |
4860 type: { | 3504 type: { |
4861 type: String, | 3505 type: String, |
4862 value: 'default', | 3506 value: 'default', |
4863 observer: '_typeChanged' | 3507 observer: '_typeChanged' |
4864 }, | 3508 }, |
4865 | 3509 |
4866 /** | |
4867 * The key used to store `value` under the `type` namespace. | |
4868 */ | |
4869 key: { | 3510 key: { |
4870 type: String, | 3511 type: String, |
4871 observer: '_keyChanged' | 3512 observer: '_keyChanged' |
4872 }, | 3513 }, |
4873 | 3514 |
4874 /** | |
4875 * The meta-data to store or retrieve. | |
4876 */ | |
4877 value: { | 3515 value: { |
4878 type: Object, | 3516 type: Object, |
4879 notify: true, | 3517 notify: true, |
4880 observer: '_valueChanged' | 3518 observer: '_valueChanged' |
4881 }, | 3519 }, |
4882 | 3520 |
4883 /** | |
4884 * If true, `value` is set to the iron-meta instance itself. | |
4885 */ | |
4886 self: { | 3521 self: { |
4887 type: Boolean, | 3522 type: Boolean, |
4888 observer: '_selfChanged' | 3523 observer: '_selfChanged' |
4889 }, | 3524 }, |
4890 | 3525 |
4891 /** | |
4892 * Array of all meta-data values for the given type. | |
4893 */ | |
4894 list: { | 3526 list: { |
4895 type: Array, | 3527 type: Array, |
4896 notify: true | 3528 notify: true |
4897 } | 3529 } |
4898 | 3530 |
4899 }, | 3531 }, |
4900 | 3532 |
4901 hostAttributes: { | 3533 hostAttributes: { |
4902 hidden: true | 3534 hidden: true |
4903 }, | 3535 }, |
4904 | 3536 |
4905 /** | |
4906 * Only runs if someone invokes the factory/constructor directly | |
4907 * e.g. `new Polymer.IronMeta()` | |
4908 * | |
4909 * @param {{type: (string|undefined), key: (string|undefined), value}=} co
nfig | |
4910 */ | |
4911 factoryImpl: function(config) { | 3537 factoryImpl: function(config) { |
4912 if (config) { | 3538 if (config) { |
4913 for (var n in config) { | 3539 for (var n in config) { |
4914 switch(n) { | 3540 switch(n) { |
4915 case 'type': | 3541 case 'type': |
4916 case 'key': | 3542 case 'key': |
4917 case 'value': | 3543 case 'value': |
4918 this[n] = config[n]; | 3544 this[n] = config[n]; |
4919 break; | 3545 break; |
4920 } | 3546 } |
4921 } | 3547 } |
4922 } | 3548 } |
4923 }, | 3549 }, |
4924 | 3550 |
4925 created: function() { | 3551 created: function() { |
4926 // TODO(sjmiles): good for debugging? | |
4927 this._metaDatas = metaDatas; | 3552 this._metaDatas = metaDatas; |
4928 this._metaArrays = metaArrays; | 3553 this._metaArrays = metaArrays; |
4929 }, | 3554 }, |
4930 | 3555 |
4931 _keyChanged: function(key, old) { | 3556 _keyChanged: function(key, old) { |
4932 this._resetRegistration(old); | 3557 this._resetRegistration(old); |
4933 }, | 3558 }, |
4934 | 3559 |
4935 _valueChanged: function(value) { | 3560 _valueChanged: function(value) { |
4936 this._resetRegistration(this.key); | 3561 this._resetRegistration(this.key); |
(...skipping 11 matching lines...) Expand all Loading... |
4948 metaDatas[type] = {}; | 3573 metaDatas[type] = {}; |
4949 } | 3574 } |
4950 this._metaData = metaDatas[type]; | 3575 this._metaData = metaDatas[type]; |
4951 if (!metaArrays[type]) { | 3576 if (!metaArrays[type]) { |
4952 metaArrays[type] = []; | 3577 metaArrays[type] = []; |
4953 } | 3578 } |
4954 this.list = metaArrays[type]; | 3579 this.list = metaArrays[type]; |
4955 this._registerKeyValue(this.key, this.value); | 3580 this._registerKeyValue(this.key, this.value); |
4956 }, | 3581 }, |
4957 | 3582 |
4958 /** | |
4959 * Retrieves meta data value by key. | |
4960 * | |
4961 * @method byKey | |
4962 * @param {string} key The key of the meta-data to be returned. | |
4963 * @return {*} | |
4964 */ | |
4965 byKey: function(key) { | 3583 byKey: function(key) { |
4966 return this._metaData && this._metaData[key]; | 3584 return this._metaData && this._metaData[key]; |
4967 }, | 3585 }, |
4968 | 3586 |
4969 _resetRegistration: function(oldKey) { | 3587 _resetRegistration: function(oldKey) { |
4970 this._unregisterKey(oldKey); | 3588 this._unregisterKey(oldKey); |
4971 this._registerKeyValue(this.key, this.value); | 3589 this._registerKeyValue(this.key, this.value); |
4972 }, | 3590 }, |
4973 | 3591 |
4974 _unregisterKey: function(key) { | 3592 _unregisterKey: function(key) { |
(...skipping 23 matching lines...) Expand all Loading... |
4998 | 3616 |
4999 }); | 3617 }); |
5000 | 3618 |
5001 Polymer.IronMeta.getIronMeta = function getIronMeta() { | 3619 Polymer.IronMeta.getIronMeta = function getIronMeta() { |
5002 if (singleton === null) { | 3620 if (singleton === null) { |
5003 singleton = new Polymer.IronMeta(); | 3621 singleton = new Polymer.IronMeta(); |
5004 } | 3622 } |
5005 return singleton; | 3623 return singleton; |
5006 }; | 3624 }; |
5007 | 3625 |
5008 /** | |
5009 `iron-meta-query` can be used to access infomation stored in `iron-meta`. | |
5010 | |
5011 Examples: | |
5012 | |
5013 If I create an instance like this: | |
5014 | |
5015 <iron-meta key="info" value="foo/bar"></iron-meta> | |
5016 | |
5017 Note that value="foo/bar" is the metadata I've defined. I could define more | |
5018 attributes or use child nodes to define additional metadata. | |
5019 | |
5020 Now I can access that element (and it's metadata) from any `iron-meta-query`
instance: | |
5021 | |
5022 var value = new Polymer.IronMetaQuery({key: 'info'}).value; | |
5023 | |
5024 @group Polymer Iron Elements | |
5025 @element iron-meta-query | |
5026 */ | |
5027 Polymer.IronMetaQuery = Polymer({ | 3626 Polymer.IronMetaQuery = Polymer({ |
5028 | 3627 |
5029 is: 'iron-meta-query', | 3628 is: 'iron-meta-query', |
5030 | 3629 |
5031 properties: { | 3630 properties: { |
5032 | 3631 |
5033 /** | |
5034 * The type of meta-data. All meta-data of the same type is stored | |
5035 * together. | |
5036 */ | |
5037 type: { | 3632 type: { |
5038 type: String, | 3633 type: String, |
5039 value: 'default', | 3634 value: 'default', |
5040 observer: '_typeChanged' | 3635 observer: '_typeChanged' |
5041 }, | 3636 }, |
5042 | 3637 |
5043 /** | |
5044 * Specifies a key to use for retrieving `value` from the `type` | |
5045 * namespace. | |
5046 */ | |
5047 key: { | 3638 key: { |
5048 type: String, | 3639 type: String, |
5049 observer: '_keyChanged' | 3640 observer: '_keyChanged' |
5050 }, | 3641 }, |
5051 | 3642 |
5052 /** | |
5053 * The meta-data to store or retrieve. | |
5054 */ | |
5055 value: { | 3643 value: { |
5056 type: Object, | 3644 type: Object, |
5057 notify: true, | 3645 notify: true, |
5058 readOnly: true | 3646 readOnly: true |
5059 }, | 3647 }, |
5060 | 3648 |
5061 /** | |
5062 * Array of all meta-data values for the given type. | |
5063 */ | |
5064 list: { | 3649 list: { |
5065 type: Array, | 3650 type: Array, |
5066 notify: true | 3651 notify: true |
5067 } | 3652 } |
5068 | 3653 |
5069 }, | 3654 }, |
5070 | 3655 |
5071 /** | |
5072 * Actually a factory method, not a true constructor. Only runs if | |
5073 * someone invokes it directly (via `new Polymer.IronMeta()`); | |
5074 * | |
5075 * @param {{type: (string|undefined), key: (string|undefined)}=} config | |
5076 */ | |
5077 factoryImpl: function(config) { | 3656 factoryImpl: function(config) { |
5078 if (config) { | 3657 if (config) { |
5079 for (var n in config) { | 3658 for (var n in config) { |
5080 switch(n) { | 3659 switch(n) { |
5081 case 'type': | 3660 case 'type': |
5082 case 'key': | 3661 case 'key': |
5083 this[n] = config[n]; | 3662 this[n] = config[n]; |
5084 break; | 3663 break; |
5085 } | 3664 } |
5086 } | 3665 } |
5087 } | 3666 } |
5088 }, | 3667 }, |
5089 | 3668 |
5090 created: function() { | 3669 created: function() { |
5091 // TODO(sjmiles): good for debugging? | |
5092 this._metaDatas = metaDatas; | 3670 this._metaDatas = metaDatas; |
5093 this._metaArrays = metaArrays; | 3671 this._metaArrays = metaArrays; |
5094 }, | 3672 }, |
5095 | 3673 |
5096 _keyChanged: function(key) { | 3674 _keyChanged: function(key) { |
5097 this._setValue(this._metaData && this._metaData[key]); | 3675 this._setValue(this._metaData && this._metaData[key]); |
5098 }, | 3676 }, |
5099 | 3677 |
5100 _typeChanged: function(type) { | 3678 _typeChanged: function(type) { |
5101 this._metaData = metaDatas[type]; | 3679 this._metaData = metaDatas[type]; |
5102 this.list = metaArrays[type]; | 3680 this.list = metaArrays[type]; |
5103 if (this.key) { | 3681 if (this.key) { |
5104 this._keyChanged(this.key); | 3682 this._keyChanged(this.key); |
5105 } | 3683 } |
5106 }, | 3684 }, |
5107 | 3685 |
5108 /** | |
5109 * Retrieves meta data value by key. | |
5110 * @param {string} key The key of the meta-data to be returned. | |
5111 * @return {*} | |
5112 */ | |
5113 byKey: function(key) { | 3686 byKey: function(key) { |
5114 return this._metaData && this._metaData[key]; | 3687 return this._metaData && this._metaData[key]; |
5115 } | 3688 } |
5116 | 3689 |
5117 }); | 3690 }); |
5118 | 3691 |
5119 })(); | 3692 })(); |
5120 Polymer({ | 3693 Polymer({ |
5121 | 3694 |
5122 is: 'iron-icon', | 3695 is: 'iron-icon', |
5123 | 3696 |
5124 properties: { | 3697 properties: { |
5125 | 3698 |
5126 /** | |
5127 * The name of the icon to use. The name should be of the form: | |
5128 * `iconset_name:icon_name`. | |
5129 */ | |
5130 icon: { | 3699 icon: { |
5131 type: String, | 3700 type: String, |
5132 observer: '_iconChanged' | 3701 observer: '_iconChanged' |
5133 }, | 3702 }, |
5134 | 3703 |
5135 /** | |
5136 * The name of the theme to used, if one is specified by the | |
5137 * iconset. | |
5138 */ | |
5139 theme: { | 3704 theme: { |
5140 type: String, | 3705 type: String, |
5141 observer: '_updateIcon' | 3706 observer: '_updateIcon' |
5142 }, | 3707 }, |
5143 | 3708 |
5144 /** | |
5145 * If using iron-icon without an iconset, you can set the src to be | |
5146 * the URL of an individual icon image file. Note that this will take | |
5147 * precedence over a given icon attribute. | |
5148 */ | |
5149 src: { | 3709 src: { |
5150 type: String, | 3710 type: String, |
5151 observer: '_srcChanged' | 3711 observer: '_srcChanged' |
5152 }, | 3712 }, |
5153 | 3713 |
5154 /** | |
5155 * @type {!Polymer.IronMeta} | |
5156 */ | |
5157 _meta: { | 3714 _meta: { |
5158 value: Polymer.Base.create('iron-meta', {type: 'iconset'}), | 3715 value: Polymer.Base.create('iron-meta', {type: 'iconset'}), |
5159 observer: '_updateIcon' | 3716 observer: '_updateIcon' |
5160 } | 3717 } |
5161 | 3718 |
5162 }, | 3719 }, |
5163 | 3720 |
5164 _DEFAULT_ICONSET: 'icons', | 3721 _DEFAULT_ICONSET: 'icons', |
5165 | 3722 |
5166 _iconChanged: function(icon) { | 3723 _iconChanged: function(icon) { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5207 this._img.style.width = '100%'; | 3764 this._img.style.width = '100%'; |
5208 this._img.style.height = '100%'; | 3765 this._img.style.height = '100%'; |
5209 this._img.draggable = false; | 3766 this._img.draggable = false; |
5210 } | 3767 } |
5211 this._img.src = this.src; | 3768 this._img.src = this.src; |
5212 Polymer.dom(this.root).appendChild(this._img); | 3769 Polymer.dom(this.root).appendChild(this._img); |
5213 } | 3770 } |
5214 } | 3771 } |
5215 | 3772 |
5216 }); | 3773 }); |
5217 /** | |
5218 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k
eyboard focus. | |
5219 * | |
5220 * @polymerBehavior Polymer.PaperInkyFocusBehavior | |
5221 */ | |
5222 Polymer.PaperInkyFocusBehaviorImpl = { | 3774 Polymer.PaperInkyFocusBehaviorImpl = { |
5223 observers: [ | 3775 observers: [ |
5224 '_focusedChanged(receivedFocusFromKeyboard)' | 3776 '_focusedChanged(receivedFocusFromKeyboard)' |
5225 ], | 3777 ], |
5226 | 3778 |
5227 _focusedChanged: function(receivedFocusFromKeyboard) { | 3779 _focusedChanged: function(receivedFocusFromKeyboard) { |
5228 if (receivedFocusFromKeyboard) { | 3780 if (receivedFocusFromKeyboard) { |
5229 this.ensureRipple(); | 3781 this.ensureRipple(); |
5230 } | 3782 } |
5231 if (this.hasRipple()) { | 3783 if (this.hasRipple()) { |
(...skipping 23 matching lines...) Expand all Loading... |
5255 hostAttributes: { | 3807 hostAttributes: { |
5256 role: 'button', | 3808 role: 'button', |
5257 tabindex: '0' | 3809 tabindex: '0' |
5258 }, | 3810 }, |
5259 | 3811 |
5260 behaviors: [ | 3812 behaviors: [ |
5261 Polymer.PaperInkyFocusBehavior | 3813 Polymer.PaperInkyFocusBehavior |
5262 ], | 3814 ], |
5263 | 3815 |
5264 properties: { | 3816 properties: { |
5265 /** | |
5266 * The URL of an image for the icon. If the src property is specified, | |
5267 * the icon property should not be. | |
5268 */ | |
5269 src: { | 3817 src: { |
5270 type: String | 3818 type: String |
5271 }, | 3819 }, |
5272 | 3820 |
5273 /** | |
5274 * Specifies the icon name or index in the set of icons available in | |
5275 * the icon's icon set. If the icon property is specified, | |
5276 * the src property should not be. | |
5277 */ | |
5278 icon: { | 3821 icon: { |
5279 type: String | 3822 type: String |
5280 }, | 3823 }, |
5281 | 3824 |
5282 /** | |
5283 * Specifies the alternate text for the button, for accessibility. | |
5284 */ | |
5285 alt: { | 3825 alt: { |
5286 type: String, | 3826 type: String, |
5287 observer: "_altChanged" | 3827 observer: "_altChanged" |
5288 } | 3828 } |
5289 }, | 3829 }, |
5290 | 3830 |
5291 _altChanged: function(newValue, oldValue) { | 3831 _altChanged: function(newValue, oldValue) { |
5292 var label = this.getAttribute('aria-label'); | 3832 var label = this.getAttribute('aria-label'); |
5293 | 3833 |
5294 // Don't stomp over a user-set aria-label. | |
5295 if (!label || oldValue == label) { | 3834 if (!label || oldValue == label) { |
5296 this.setAttribute('aria-label', newValue); | 3835 this.setAttribute('aria-label', newValue); |
5297 } | 3836 } |
5298 } | 3837 } |
5299 }); | 3838 }); |
5300 Polymer({ | 3839 Polymer({ |
5301 is: 'paper-tab', | 3840 is: 'paper-tab', |
5302 | 3841 |
5303 behaviors: [ | 3842 behaviors: [ |
5304 Polymer.IronControlState, | 3843 Polymer.IronControlState, |
5305 Polymer.IronButtonState, | 3844 Polymer.IronButtonState, |
5306 Polymer.PaperRippleBehavior | 3845 Polymer.PaperRippleBehavior |
5307 ], | 3846 ], |
5308 | 3847 |
5309 properties: { | 3848 properties: { |
5310 | 3849 |
5311 /** | |
5312 * If true, the tab will forward keyboard clicks (enter/space) to | |
5313 * the first anchor element found in its descendants | |
5314 */ | |
5315 link: { | 3850 link: { |
5316 type: Boolean, | 3851 type: Boolean, |
5317 value: false, | 3852 value: false, |
5318 reflectToAttribute: true | 3853 reflectToAttribute: true |
5319 } | 3854 } |
5320 | 3855 |
5321 }, | 3856 }, |
5322 | 3857 |
5323 hostAttributes: { | 3858 hostAttributes: { |
5324 role: 'tab' | 3859 role: 'tab' |
(...skipping 18 matching lines...) Expand all Loading... |
5343 }, | 3878 }, |
5344 | 3879 |
5345 _onTap: function(event) { | 3880 _onTap: function(event) { |
5346 if (this.link) { | 3881 if (this.link) { |
5347 var anchor = this.queryEffectiveChildren('a'); | 3882 var anchor = this.queryEffectiveChildren('a'); |
5348 | 3883 |
5349 if (!anchor) { | 3884 if (!anchor) { |
5350 return; | 3885 return; |
5351 } | 3886 } |
5352 | 3887 |
5353 // Don't get stuck in a loop delegating | |
5354 // the listener from the child anchor | |
5355 if (event.target === anchor) { | 3888 if (event.target === anchor) { |
5356 return; | 3889 return; |
5357 } | 3890 } |
5358 | 3891 |
5359 anchor.click(); | 3892 anchor.click(); |
5360 } | 3893 } |
5361 } | 3894 } |
5362 | 3895 |
5363 }); | 3896 }); |
5364 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ | 3897 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ |
5365 Polymer.IronMultiSelectableBehaviorImpl = { | 3898 Polymer.IronMultiSelectableBehaviorImpl = { |
5366 properties: { | 3899 properties: { |
5367 | 3900 |
5368 /** | |
5369 * If true, multiple selections are allowed. | |
5370 */ | |
5371 multi: { | 3901 multi: { |
5372 type: Boolean, | 3902 type: Boolean, |
5373 value: false, | 3903 value: false, |
5374 observer: 'multiChanged' | 3904 observer: 'multiChanged' |
5375 }, | 3905 }, |
5376 | 3906 |
5377 /** | |
5378 * Gets or sets the selected elements. This is used instead of `selected`
when `multi` | |
5379 * is true. | |
5380 */ | |
5381 selectedValues: { | 3907 selectedValues: { |
5382 type: Array, | 3908 type: Array, |
5383 notify: true | 3909 notify: true |
5384 }, | 3910 }, |
5385 | 3911 |
5386 /** | |
5387 * Returns an array of currently selected items. | |
5388 */ | |
5389 selectedItems: { | 3912 selectedItems: { |
5390 type: Array, | 3913 type: Array, |
5391 readOnly: true, | 3914 readOnly: true, |
5392 notify: true | 3915 notify: true |
5393 }, | 3916 }, |
5394 | 3917 |
5395 }, | 3918 }, |
5396 | 3919 |
5397 observers: [ | 3920 observers: [ |
5398 '_updateSelected(selectedValues.splices)' | 3921 '_updateSelected(selectedValues.splices)' |
5399 ], | 3922 ], |
5400 | 3923 |
5401 /** | |
5402 * Selects the given value. If the `multi` property is true, then the select
ed state of the | |
5403 * `value` will be toggled; otherwise the `value` will be selected. | |
5404 * | |
5405 * @method select | |
5406 * @param {string|number} value the value to select. | |
5407 */ | |
5408 select: function(value) { | 3924 select: function(value) { |
5409 if (this.multi) { | 3925 if (this.multi) { |
5410 if (this.selectedValues) { | 3926 if (this.selectedValues) { |
5411 this._toggleSelected(value); | 3927 this._toggleSelected(value); |
5412 } else { | 3928 } else { |
5413 this.selectedValues = [value]; | 3929 this.selectedValues = [value]; |
5414 } | 3930 } |
5415 } else { | 3931 } else { |
5416 this.selected = value; | 3932 this.selected = value; |
5417 } | 3933 } |
(...skipping 24 matching lines...) Expand all Loading... |
5442 if (this.multi) { | 3958 if (this.multi) { |
5443 this._selectMulti(this.selectedValues); | 3959 this._selectMulti(this.selectedValues); |
5444 } else { | 3960 } else { |
5445 this._selectSelected(this.selected); | 3961 this._selectSelected(this.selected); |
5446 } | 3962 } |
5447 }, | 3963 }, |
5448 | 3964 |
5449 _selectMulti: function(values) { | 3965 _selectMulti: function(values) { |
5450 if (values) { | 3966 if (values) { |
5451 var selectedItems = this._valuesToItems(values); | 3967 var selectedItems = this._valuesToItems(values); |
5452 // clear all but the current selected items | |
5453 this._selection.clear(selectedItems); | 3968 this._selection.clear(selectedItems); |
5454 // select only those not selected yet | |
5455 for (var i = 0; i < selectedItems.length; i++) { | 3969 for (var i = 0; i < selectedItems.length; i++) { |
5456 this._selection.setItemSelected(selectedItems[i], true); | 3970 this._selection.setItemSelected(selectedItems[i], true); |
5457 } | 3971 } |
5458 // Check for items, since this array is populated only when attached | |
5459 if (this.fallbackSelection && this.items.length && !this._selection.get(
).length) { | 3972 if (this.fallbackSelection && this.items.length && !this._selection.get(
).length) { |
5460 var fallback = this._valueToItem(this.fallbackSelection); | 3973 var fallback = this._valueToItem(this.fallbackSelection); |
5461 if (fallback) { | 3974 if (fallback) { |
5462 this.selectedValues = [this.fallbackSelection]; | 3975 this.selectedValues = [this.fallbackSelection]; |
5463 } | 3976 } |
5464 } | 3977 } |
5465 } else { | 3978 } else { |
5466 this._selection.clear(); | 3979 this._selection.clear(); |
5467 } | 3980 } |
5468 }, | 3981 }, |
(...skipping 23 matching lines...) Expand all Loading... |
5492 return this._valueToItem(value); | 4005 return this._valueToItem(value); |
5493 }, this); | 4006 }, this); |
5494 } | 4007 } |
5495 }; | 4008 }; |
5496 | 4009 |
5497 /** @polymerBehavior */ | 4010 /** @polymerBehavior */ |
5498 Polymer.IronMultiSelectableBehavior = [ | 4011 Polymer.IronMultiSelectableBehavior = [ |
5499 Polymer.IronSelectableBehavior, | 4012 Polymer.IronSelectableBehavior, |
5500 Polymer.IronMultiSelectableBehaviorImpl | 4013 Polymer.IronMultiSelectableBehaviorImpl |
5501 ]; | 4014 ]; |
5502 /** | |
5503 * `Polymer.IronMenuBehavior` implements accessible menu behavior. | |
5504 * | |
5505 * @demo demo/index.html | |
5506 * @polymerBehavior Polymer.IronMenuBehavior | |
5507 */ | |
5508 Polymer.IronMenuBehaviorImpl = { | 4015 Polymer.IronMenuBehaviorImpl = { |
5509 | 4016 |
5510 properties: { | 4017 properties: { |
5511 | 4018 |
5512 /** | |
5513 * Returns the currently focused item. | |
5514 * @type {?Object} | |
5515 */ | |
5516 focusedItem: { | 4019 focusedItem: { |
5517 observer: '_focusedItemChanged', | 4020 observer: '_focusedItemChanged', |
5518 readOnly: true, | 4021 readOnly: true, |
5519 type: Object | 4022 type: Object |
5520 }, | 4023 }, |
5521 | 4024 |
5522 /** | |
5523 * The attribute to use on menu items to look up the item title. Typing th
e first | |
5524 * letter of an item when the menu is open focuses that item. If unset, `t
extContent` | |
5525 * will be used. | |
5526 */ | |
5527 attrForItemTitle: { | 4025 attrForItemTitle: { |
5528 type: String | 4026 type: String |
5529 } | 4027 } |
5530 }, | 4028 }, |
5531 | 4029 |
5532 hostAttributes: { | 4030 hostAttributes: { |
5533 'role': 'menu', | 4031 'role': 'menu', |
5534 'tabindex': '0' | 4032 'tabindex': '0' |
5535 }, | 4033 }, |
5536 | 4034 |
(...skipping 11 matching lines...) Expand all Loading... |
5548 'up': '_onUpKey', | 4046 'up': '_onUpKey', |
5549 'down': '_onDownKey', | 4047 'down': '_onDownKey', |
5550 'esc': '_onEscKey', | 4048 'esc': '_onEscKey', |
5551 'shift+tab:keydown': '_onShiftTabDown' | 4049 'shift+tab:keydown': '_onShiftTabDown' |
5552 }, | 4050 }, |
5553 | 4051 |
5554 attached: function() { | 4052 attached: function() { |
5555 this._resetTabindices(); | 4053 this._resetTabindices(); |
5556 }, | 4054 }, |
5557 | 4055 |
5558 /** | |
5559 * Selects the given value. If the `multi` property is true, then the select
ed state of the | |
5560 * `value` will be toggled; otherwise the `value` will be selected. | |
5561 * | |
5562 * @param {string|number} value the value to select. | |
5563 */ | |
5564 select: function(value) { | 4056 select: function(value) { |
5565 // Cancel automatically focusing a default item if the menu received focus | |
5566 // through a user action selecting a particular item. | |
5567 if (this._defaultFocusAsync) { | 4057 if (this._defaultFocusAsync) { |
5568 this.cancelAsync(this._defaultFocusAsync); | 4058 this.cancelAsync(this._defaultFocusAsync); |
5569 this._defaultFocusAsync = null; | 4059 this._defaultFocusAsync = null; |
5570 } | 4060 } |
5571 var item = this._valueToItem(value); | 4061 var item = this._valueToItem(value); |
5572 if (item && item.hasAttribute('disabled')) return; | 4062 if (item && item.hasAttribute('disabled')) return; |
5573 this._setFocusedItem(item); | 4063 this._setFocusedItem(item); |
5574 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); | 4064 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); |
5575 }, | 4065 }, |
5576 | 4066 |
5577 /** | |
5578 * Resets all tabindex attributes to the appropriate value based on the | |
5579 * current selection state. The appropriate value is `0` (focusable) for | |
5580 * the default selected item, and `-1` (not keyboard focusable) for all | |
5581 * other items. | |
5582 */ | |
5583 _resetTabindices: function() { | 4067 _resetTabindices: function() { |
5584 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[
0]) : this.selectedItem; | 4068 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[
0]) : this.selectedItem; |
5585 | 4069 |
5586 this.items.forEach(function(item) { | 4070 this.items.forEach(function(item) { |
5587 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); | 4071 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); |
5588 }, this); | 4072 }, this); |
5589 }, | 4073 }, |
5590 | 4074 |
5591 /** | |
5592 * Sets appropriate ARIA based on whether or not the menu is meant to be | |
5593 * multi-selectable. | |
5594 * | |
5595 * @param {boolean} multi True if the menu should be multi-selectable. | |
5596 */ | |
5597 _updateMultiselectable: function(multi) { | 4075 _updateMultiselectable: function(multi) { |
5598 if (multi) { | 4076 if (multi) { |
5599 this.setAttribute('aria-multiselectable', 'true'); | 4077 this.setAttribute('aria-multiselectable', 'true'); |
5600 } else { | 4078 } else { |
5601 this.removeAttribute('aria-multiselectable'); | 4079 this.removeAttribute('aria-multiselectable'); |
5602 } | 4080 } |
5603 }, | 4081 }, |
5604 | 4082 |
5605 /** | |
5606 * Given a KeyboardEvent, this method will focus the appropriate item in the | |
5607 * menu (if there is a relevant item, and it is possible to focus it). | |
5608 * | |
5609 * @param {KeyboardEvent} event A KeyboardEvent. | |
5610 */ | |
5611 _focusWithKeyboardEvent: function(event) { | 4083 _focusWithKeyboardEvent: function(event) { |
5612 for (var i = 0, item; item = this.items[i]; i++) { | 4084 for (var i = 0, item; item = this.items[i]; i++) { |
5613 var attr = this.attrForItemTitle || 'textContent'; | 4085 var attr = this.attrForItemTitle || 'textContent'; |
5614 var title = item[attr] || item.getAttribute(attr); | 4086 var title = item[attr] || item.getAttribute(attr); |
5615 | 4087 |
5616 if (!item.hasAttribute('disabled') && title && | 4088 if (!item.hasAttribute('disabled') && title && |
5617 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k
eyCode).toLowerCase()) { | 4089 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k
eyCode).toLowerCase()) { |
5618 this._setFocusedItem(item); | 4090 this._setFocusedItem(item); |
5619 break; | 4091 break; |
5620 } | 4092 } |
5621 } | 4093 } |
5622 }, | 4094 }, |
5623 | 4095 |
5624 /** | |
5625 * Focuses the previous item (relative to the currently focused item) in the | |
5626 * menu, disabled items will be skipped. | |
5627 * Loop until length + 1 to handle case of single item in menu. | |
5628 */ | |
5629 _focusPrevious: function() { | 4096 _focusPrevious: function() { |
5630 var length = this.items.length; | 4097 var length = this.items.length; |
5631 var curFocusIndex = Number(this.indexOf(this.focusedItem)); | 4098 var curFocusIndex = Number(this.indexOf(this.focusedItem)); |
5632 for (var i = 1; i < length + 1; i++) { | 4099 for (var i = 1; i < length + 1; i++) { |
5633 var item = this.items[(curFocusIndex - i + length) % length]; | 4100 var item = this.items[(curFocusIndex - i + length) % length]; |
5634 if (!item.hasAttribute('disabled')) { | 4101 if (!item.hasAttribute('disabled')) { |
5635 this._setFocusedItem(item); | 4102 this._setFocusedItem(item); |
5636 return; | 4103 return; |
5637 } | 4104 } |
5638 } | 4105 } |
5639 }, | 4106 }, |
5640 | 4107 |
5641 /** | |
5642 * Focuses the next item (relative to the currently focused item) in the | |
5643 * menu, disabled items will be skipped. | |
5644 * Loop until length + 1 to handle case of single item in menu. | |
5645 */ | |
5646 _focusNext: function() { | 4108 _focusNext: function() { |
5647 var length = this.items.length; | 4109 var length = this.items.length; |
5648 var curFocusIndex = Number(this.indexOf(this.focusedItem)); | 4110 var curFocusIndex = Number(this.indexOf(this.focusedItem)); |
5649 for (var i = 1; i < length + 1; i++) { | 4111 for (var i = 1; i < length + 1; i++) { |
5650 var item = this.items[(curFocusIndex + i) % length]; | 4112 var item = this.items[(curFocusIndex + i) % length]; |
5651 if (!item.hasAttribute('disabled')) { | 4113 if (!item.hasAttribute('disabled')) { |
5652 this._setFocusedItem(item); | 4114 this._setFocusedItem(item); |
5653 return; | 4115 return; |
5654 } | 4116 } |
5655 } | 4117 } |
5656 }, | 4118 }, |
5657 | 4119 |
5658 /** | |
5659 * Mutates items in the menu based on provided selection details, so that | |
5660 * all items correctly reflect selection state. | |
5661 * | |
5662 * @param {Element} item An item in the menu. | |
5663 * @param {boolean} isSelected True if the item should be shown in a | |
5664 * selected state, otherwise false. | |
5665 */ | |
5666 _applySelection: function(item, isSelected) { | 4120 _applySelection: function(item, isSelected) { |
5667 if (isSelected) { | 4121 if (isSelected) { |
5668 item.setAttribute('aria-selected', 'true'); | 4122 item.setAttribute('aria-selected', 'true'); |
5669 } else { | 4123 } else { |
5670 item.removeAttribute('aria-selected'); | 4124 item.removeAttribute('aria-selected'); |
5671 } | 4125 } |
5672 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); | 4126 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); |
5673 }, | 4127 }, |
5674 | 4128 |
5675 /** | |
5676 * Discretely updates tabindex values among menu items as the focused item | |
5677 * changes. | |
5678 * | |
5679 * @param {Element} focusedItem The element that is currently focused. | |
5680 * @param {?Element} old The last element that was considered focused, if | |
5681 * applicable. | |
5682 */ | |
5683 _focusedItemChanged: function(focusedItem, old) { | 4129 _focusedItemChanged: function(focusedItem, old) { |
5684 old && old.setAttribute('tabindex', '-1'); | 4130 old && old.setAttribute('tabindex', '-1'); |
5685 if (focusedItem) { | 4131 if (focusedItem) { |
5686 focusedItem.setAttribute('tabindex', '0'); | 4132 focusedItem.setAttribute('tabindex', '0'); |
5687 focusedItem.focus(); | 4133 focusedItem.focus(); |
5688 } | 4134 } |
5689 }, | 4135 }, |
5690 | 4136 |
5691 /** | |
5692 * A handler that responds to mutation changes related to the list of items | |
5693 * in the menu. | |
5694 * | |
5695 * @param {CustomEvent} event An event containing mutation records as its | |
5696 * detail. | |
5697 */ | |
5698 _onIronItemsChanged: function(event) { | 4137 _onIronItemsChanged: function(event) { |
5699 if (event.detail.addedNodes.length) { | 4138 if (event.detail.addedNodes.length) { |
5700 this._resetTabindices(); | 4139 this._resetTabindices(); |
5701 } | 4140 } |
5702 }, | 4141 }, |
5703 | 4142 |
5704 /** | |
5705 * Handler that is called when a shift+tab keypress is detected by the menu. | |
5706 * | |
5707 * @param {CustomEvent} event A key combination event. | |
5708 */ | |
5709 _onShiftTabDown: function(event) { | 4143 _onShiftTabDown: function(event) { |
5710 var oldTabIndex = this.getAttribute('tabindex'); | 4144 var oldTabIndex = this.getAttribute('tabindex'); |
5711 | 4145 |
5712 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; | 4146 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; |
5713 | 4147 |
5714 this._setFocusedItem(null); | 4148 this._setFocusedItem(null); |
5715 | 4149 |
5716 this.setAttribute('tabindex', '-1'); | 4150 this.setAttribute('tabindex', '-1'); |
5717 | 4151 |
5718 this.async(function() { | 4152 this.async(function() { |
5719 this.setAttribute('tabindex', oldTabIndex); | 4153 this.setAttribute('tabindex', oldTabIndex); |
5720 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; | 4154 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
5721 // NOTE(cdata): polymer/polymer#1305 | |
5722 }, 1); | 4155 }, 1); |
5723 }, | 4156 }, |
5724 | 4157 |
5725 /** | |
5726 * Handler that is called when the menu receives focus. | |
5727 * | |
5728 * @param {FocusEvent} event A focus event. | |
5729 */ | |
5730 _onFocus: function(event) { | 4158 _onFocus: function(event) { |
5731 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { | 4159 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { |
5732 // do not focus the menu itself | |
5733 return; | 4160 return; |
5734 } | 4161 } |
5735 | 4162 |
5736 // Do not focus the selected tab if the deepest target is part of the | |
5737 // menu element's local DOM and is focusable. | |
5738 var rootTarget = /** @type {?HTMLElement} */( | 4163 var rootTarget = /** @type {?HTMLElement} */( |
5739 Polymer.dom(event).rootTarget); | 4164 Polymer.dom(event).rootTarget); |
5740 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && !
this.isLightDescendant(rootTarget)) { | 4165 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && !
this.isLightDescendant(rootTarget)) { |
5741 return; | 4166 return; |
5742 } | 4167 } |
5743 | 4168 |
5744 // clear the cached focus item | |
5745 this._defaultFocusAsync = this.async(function() { | 4169 this._defaultFocusAsync = this.async(function() { |
5746 // focus the selected item when the menu receives focus, or the first it
em | |
5747 // if no item is selected | |
5748 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem
s[0]) : this.selectedItem; | 4170 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem
s[0]) : this.selectedItem; |
5749 | 4171 |
5750 this._setFocusedItem(null); | 4172 this._setFocusedItem(null); |
5751 | 4173 |
5752 if (selectedItem) { | 4174 if (selectedItem) { |
5753 this._setFocusedItem(selectedItem); | 4175 this._setFocusedItem(selectedItem); |
5754 } else if (this.items[0]) { | 4176 } else if (this.items[0]) { |
5755 // We find the first none-disabled item (if one exists) | |
5756 this._focusNext(); | 4177 this._focusNext(); |
5757 } | 4178 } |
5758 }); | 4179 }); |
5759 }, | 4180 }, |
5760 | 4181 |
5761 /** | |
5762 * Handler that is called when the up key is pressed. | |
5763 * | |
5764 * @param {CustomEvent} event A key combination event. | |
5765 */ | |
5766 _onUpKey: function(event) { | 4182 _onUpKey: function(event) { |
5767 // up and down arrows moves the focus | |
5768 this._focusPrevious(); | 4183 this._focusPrevious(); |
5769 event.detail.keyboardEvent.preventDefault(); | 4184 event.detail.keyboardEvent.preventDefault(); |
5770 }, | 4185 }, |
5771 | 4186 |
5772 /** | |
5773 * Handler that is called when the down key is pressed. | |
5774 * | |
5775 * @param {CustomEvent} event A key combination event. | |
5776 */ | |
5777 _onDownKey: function(event) { | 4187 _onDownKey: function(event) { |
5778 this._focusNext(); | 4188 this._focusNext(); |
5779 event.detail.keyboardEvent.preventDefault(); | 4189 event.detail.keyboardEvent.preventDefault(); |
5780 }, | 4190 }, |
5781 | 4191 |
5782 /** | |
5783 * Handler that is called when the esc key is pressed. | |
5784 * | |
5785 * @param {CustomEvent} event A key combination event. | |
5786 */ | |
5787 _onEscKey: function(event) { | 4192 _onEscKey: function(event) { |
5788 // esc blurs the control | |
5789 this.focusedItem.blur(); | 4193 this.focusedItem.blur(); |
5790 }, | 4194 }, |
5791 | 4195 |
5792 /** | |
5793 * Handler that is called when a keydown event is detected. | |
5794 * | |
5795 * @param {KeyboardEvent} event A keyboard event. | |
5796 */ | |
5797 _onKeydown: function(event) { | 4196 _onKeydown: function(event) { |
5798 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) { | 4197 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) { |
5799 // all other keys focus the menu item starting with that character | |
5800 this._focusWithKeyboardEvent(event); | 4198 this._focusWithKeyboardEvent(event); |
5801 } | 4199 } |
5802 event.stopPropagation(); | 4200 event.stopPropagation(); |
5803 }, | 4201 }, |
5804 | 4202 |
5805 // override _activateHandler | |
5806 _activateHandler: function(event) { | 4203 _activateHandler: function(event) { |
5807 Polymer.IronSelectableBehavior._activateHandler.call(this, event); | 4204 Polymer.IronSelectableBehavior._activateHandler.call(this, event); |
5808 event.stopPropagation(); | 4205 event.stopPropagation(); |
5809 } | 4206 } |
5810 }; | 4207 }; |
5811 | 4208 |
5812 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; | 4209 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
5813 | 4210 |
5814 /** @polymerBehavior Polymer.IronMenuBehavior */ | 4211 /** @polymerBehavior Polymer.IronMenuBehavior */ |
5815 Polymer.IronMenuBehavior = [ | 4212 Polymer.IronMenuBehavior = [ |
5816 Polymer.IronMultiSelectableBehavior, | 4213 Polymer.IronMultiSelectableBehavior, |
5817 Polymer.IronA11yKeysBehavior, | 4214 Polymer.IronA11yKeysBehavior, |
5818 Polymer.IronMenuBehaviorImpl | 4215 Polymer.IronMenuBehaviorImpl |
5819 ]; | 4216 ]; |
5820 /** | |
5821 * `Polymer.IronMenubarBehavior` implements accessible menubar behavior. | |
5822 * | |
5823 * @polymerBehavior Polymer.IronMenubarBehavior | |
5824 */ | |
5825 Polymer.IronMenubarBehaviorImpl = { | 4217 Polymer.IronMenubarBehaviorImpl = { |
5826 | 4218 |
5827 hostAttributes: { | 4219 hostAttributes: { |
5828 'role': 'menubar' | 4220 'role': 'menubar' |
5829 }, | 4221 }, |
5830 | 4222 |
5831 keyBindings: { | 4223 keyBindings: { |
5832 'left': '_onLeftKey', | 4224 'left': '_onLeftKey', |
5833 'right': '_onRightKey' | 4225 'right': '_onRightKey' |
5834 }, | 4226 }, |
(...skipping 28 matching lines...) Expand all Loading... |
5863 this._focusNext(); | 4255 this._focusNext(); |
5864 } | 4256 } |
5865 event.detail.keyboardEvent.preventDefault(); | 4257 event.detail.keyboardEvent.preventDefault(); |
5866 }, | 4258 }, |
5867 | 4259 |
5868 _onKeydown: function(event) { | 4260 _onKeydown: function(event) { |
5869 if (this.keyboardEventMatchesKeys(event, 'up down left right esc')) { | 4261 if (this.keyboardEventMatchesKeys(event, 'up down left right esc')) { |
5870 return; | 4262 return; |
5871 } | 4263 } |
5872 | 4264 |
5873 // all other keys focus the menu item starting with that character | |
5874 this._focusWithKeyboardEvent(event); | 4265 this._focusWithKeyboardEvent(event); |
5875 } | 4266 } |
5876 | 4267 |
5877 }; | 4268 }; |
5878 | 4269 |
5879 /** @polymerBehavior Polymer.IronMenubarBehavior */ | 4270 /** @polymerBehavior Polymer.IronMenubarBehavior */ |
5880 Polymer.IronMenubarBehavior = [ | 4271 Polymer.IronMenubarBehavior = [ |
5881 Polymer.IronMenuBehavior, | 4272 Polymer.IronMenuBehavior, |
5882 Polymer.IronMenubarBehaviorImpl | 4273 Polymer.IronMenubarBehaviorImpl |
5883 ]; | 4274 ]; |
5884 /** | |
5885 * The `iron-iconset-svg` element allows users to define their own icon sets | |
5886 * that contain svg icons. The svg icon elements should be children of the | |
5887 * `iron-iconset-svg` element. Multiple icons should be given distinct id's. | |
5888 * | |
5889 * Using svg elements to create icons has a few advantages over traditional | |
5890 * bitmap graphics like jpg or png. Icons that use svg are vector based so | |
5891 * they are resolution independent and should look good on any device. They | |
5892 * are stylable via css. Icons can be themed, colorized, and even animated. | |
5893 * | |
5894 * Example: | |
5895 * | |
5896 * <iron-iconset-svg name="my-svg-icons" size="24"> | |
5897 * <svg> | |
5898 * <defs> | |
5899 * <g id="shape"> | |
5900 * <rect x="12" y="0" width="12" height="24" /> | |
5901 * <circle cx="12" cy="12" r="12" /> | |
5902 * </g> | |
5903 * </defs> | |
5904 * </svg> | |
5905 * </iron-iconset-svg> | |
5906 * | |
5907 * This will automatically register the icon set "my-svg-icons" to the iconset | |
5908 * database. To use these icons from within another element, make a | |
5909 * `iron-iconset` element and call the `byId` method | |
5910 * to retrieve a given iconset. To apply a particular icon inside an | |
5911 * element use the `applyIcon` method. For example: | |
5912 * | |
5913 * iconset.applyIcon(iconNode, 'car'); | |
5914 * | |
5915 * @element iron-iconset-svg | |
5916 * @demo demo/index.html | |
5917 * @implements {Polymer.Iconset} | |
5918 */ | |
5919 Polymer({ | 4275 Polymer({ |
5920 is: 'iron-iconset-svg', | 4276 is: 'iron-iconset-svg', |
5921 | 4277 |
5922 properties: { | 4278 properties: { |
5923 | 4279 |
5924 /** | |
5925 * The name of the iconset. | |
5926 */ | |
5927 name: { | 4280 name: { |
5928 type: String, | 4281 type: String, |
5929 observer: '_nameChanged' | 4282 observer: '_nameChanged' |
5930 }, | 4283 }, |
5931 | 4284 |
5932 /** | |
5933 * The size of an individual icon. Note that icons must be square. | |
5934 */ | |
5935 size: { | 4285 size: { |
5936 type: Number, | 4286 type: Number, |
5937 value: 24 | 4287 value: 24 |
5938 } | 4288 } |
5939 | 4289 |
5940 }, | 4290 }, |
5941 | 4291 |
5942 attached: function() { | 4292 attached: function() { |
5943 this.style.display = 'none'; | 4293 this.style.display = 'none'; |
5944 }, | 4294 }, |
5945 | 4295 |
5946 /** | |
5947 * Construct an array of all icon names in this iconset. | |
5948 * | |
5949 * @return {!Array} Array of icon names. | |
5950 */ | |
5951 getIconNames: function() { | 4296 getIconNames: function() { |
5952 this._icons = this._createIconMap(); | 4297 this._icons = this._createIconMap(); |
5953 return Object.keys(this._icons).map(function(n) { | 4298 return Object.keys(this._icons).map(function(n) { |
5954 return this.name + ':' + n; | 4299 return this.name + ':' + n; |
5955 }, this); | 4300 }, this); |
5956 }, | 4301 }, |
5957 | 4302 |
5958 /** | |
5959 * Applies an icon to the given element. | |
5960 * | |
5961 * An svg icon is prepended to the element's shadowRoot if it exists, | |
5962 * otherwise to the element itself. | |
5963 * | |
5964 * @method applyIcon | |
5965 * @param {Element} element Element to which the icon is applied. | |
5966 * @param {string} iconName Name of the icon to apply. | |
5967 * @return {?Element} The svg element which renders the icon. | |
5968 */ | |
5969 applyIcon: function(element, iconName) { | 4303 applyIcon: function(element, iconName) { |
5970 // insert svg element into shadow root, if it exists | |
5971 element = element.root || element; | 4304 element = element.root || element; |
5972 // Remove old svg element | |
5973 this.removeIcon(element); | 4305 this.removeIcon(element); |
5974 // install new svg element | |
5975 var svg = this._cloneIcon(iconName); | 4306 var svg = this._cloneIcon(iconName); |
5976 if (svg) { | 4307 if (svg) { |
5977 var pde = Polymer.dom(element); | 4308 var pde = Polymer.dom(element); |
5978 pde.insertBefore(svg, pde.childNodes[0]); | 4309 pde.insertBefore(svg, pde.childNodes[0]); |
5979 return element._svgIcon = svg; | 4310 return element._svgIcon = svg; |
5980 } | 4311 } |
5981 return null; | 4312 return null; |
5982 }, | 4313 }, |
5983 | 4314 |
5984 /** | |
5985 * Remove an icon from the given element by undoing the changes effected | |
5986 * by `applyIcon`. | |
5987 * | |
5988 * @param {Element} element The element from which the icon is removed. | |
5989 */ | |
5990 removeIcon: function(element) { | 4315 removeIcon: function(element) { |
5991 // Remove old svg element | |
5992 if (element._svgIcon) { | 4316 if (element._svgIcon) { |
5993 Polymer.dom(element).removeChild(element._svgIcon); | 4317 Polymer.dom(element).removeChild(element._svgIcon); |
5994 element._svgIcon = null; | 4318 element._svgIcon = null; |
5995 } | 4319 } |
5996 }, | 4320 }, |
5997 | 4321 |
5998 /** | |
5999 * | |
6000 * When name is changed, register iconset metadata | |
6001 * | |
6002 */ | |
6003 _nameChanged: function() { | 4322 _nameChanged: function() { |
6004 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); | 4323 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); |
6005 this.async(function() { | 4324 this.async(function() { |
6006 this.fire('iron-iconset-added', this, {node: window}); | 4325 this.fire('iron-iconset-added', this, {node: window}); |
6007 }); | 4326 }); |
6008 }, | 4327 }, |
6009 | 4328 |
6010 /** | |
6011 * Create a map of child SVG elements by id. | |
6012 * | |
6013 * @return {!Object} Map of id's to SVG elements. | |
6014 */ | |
6015 _createIconMap: function() { | 4329 _createIconMap: function() { |
6016 // Objects chained to Object.prototype (`{}`) have members. Specifically, | |
6017 // on FF there is a `watch` method that confuses the icon map, so we | |
6018 // need to use a null-based object here. | |
6019 var icons = Object.create(null); | 4330 var icons = Object.create(null); |
6020 Polymer.dom(this).querySelectorAll('[id]') | 4331 Polymer.dom(this).querySelectorAll('[id]') |
6021 .forEach(function(icon) { | 4332 .forEach(function(icon) { |
6022 icons[icon.id] = icon; | 4333 icons[icon.id] = icon; |
6023 }); | 4334 }); |
6024 return icons; | 4335 return icons; |
6025 }, | 4336 }, |
6026 | 4337 |
6027 /** | |
6028 * Produce installable clone of the SVG element matching `id` in this | |
6029 * iconset, or `undefined` if there is no matching element. | |
6030 * | |
6031 * @return {Element} Returns an installable clone of the SVG element | |
6032 * matching `id`. | |
6033 */ | |
6034 _cloneIcon: function(id) { | 4338 _cloneIcon: function(id) { |
6035 // create the icon map on-demand, since the iconset itself has no discrete | |
6036 // signal to know when it's children are fully parsed | |
6037 this._icons = this._icons || this._createIconMap(); | 4339 this._icons = this._icons || this._createIconMap(); |
6038 return this._prepareSvgClone(this._icons[id], this.size); | 4340 return this._prepareSvgClone(this._icons[id], this.size); |
6039 }, | 4341 }, |
6040 | 4342 |
6041 /** | |
6042 * @param {Element} sourceSvg | |
6043 * @param {number} size | |
6044 * @return {Element} | |
6045 */ | |
6046 _prepareSvgClone: function(sourceSvg, size) { | 4343 _prepareSvgClone: function(sourceSvg, size) { |
6047 if (sourceSvg) { | 4344 if (sourceSvg) { |
6048 var content = sourceSvg.cloneNode(true), | 4345 var content = sourceSvg.cloneNode(true), |
6049 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), | 4346 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), |
6050 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s
ize; | 4347 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s
ize; |
6051 svg.setAttribute('viewBox', viewBox); | 4348 svg.setAttribute('viewBox', viewBox); |
6052 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); | 4349 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
6053 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/
370136 | |
6054 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a
shadow-root | |
6055 svg.style.cssText = 'pointer-events: none; display: block; width: 100%;
height: 100%;'; | 4350 svg.style.cssText = 'pointer-events: none; display: block; width: 100%;
height: 100%;'; |
6056 svg.appendChild(content).removeAttribute('id'); | 4351 svg.appendChild(content).removeAttribute('id'); |
6057 return svg; | 4352 return svg; |
6058 } | 4353 } |
6059 return null; | 4354 return null; |
6060 } | 4355 } |
6061 | 4356 |
6062 }); | 4357 }); |
6063 Polymer({ | 4358 Polymer({ |
6064 is: 'paper-tabs', | 4359 is: 'paper-tabs', |
6065 | 4360 |
6066 behaviors: [ | 4361 behaviors: [ |
6067 Polymer.IronResizableBehavior, | 4362 Polymer.IronResizableBehavior, |
6068 Polymer.IronMenubarBehavior | 4363 Polymer.IronMenubarBehavior |
6069 ], | 4364 ], |
6070 | 4365 |
6071 properties: { | 4366 properties: { |
6072 /** | |
6073 * If true, ink ripple effect is disabled. When this property is changed
, | |
6074 * all descendant `<paper-tab>` elements have their `noink` property | |
6075 * changed to the new value as well. | |
6076 */ | |
6077 noink: { | 4367 noink: { |
6078 type: Boolean, | 4368 type: Boolean, |
6079 value: false, | 4369 value: false, |
6080 observer: '_noinkChanged' | 4370 observer: '_noinkChanged' |
6081 }, | 4371 }, |
6082 | 4372 |
6083 /** | |
6084 * If true, the bottom bar to indicate the selected tab will not be show
n. | |
6085 */ | |
6086 noBar: { | 4373 noBar: { |
6087 type: Boolean, | 4374 type: Boolean, |
6088 value: false | 4375 value: false |
6089 }, | 4376 }, |
6090 | 4377 |
6091 /** | |
6092 * If true, the slide effect for the bottom bar is disabled. | |
6093 */ | |
6094 noSlide: { | 4378 noSlide: { |
6095 type: Boolean, | 4379 type: Boolean, |
6096 value: false | 4380 value: false |
6097 }, | 4381 }, |
6098 | 4382 |
6099 /** | |
6100 * If true, tabs are scrollable and the tab width is based on the label
width. | |
6101 */ | |
6102 scrollable: { | 4383 scrollable: { |
6103 type: Boolean, | 4384 type: Boolean, |
6104 value: false | 4385 value: false |
6105 }, | 4386 }, |
6106 | 4387 |
6107 /** | |
6108 * If true, tabs expand to fit their container. This currently only appl
ies when | |
6109 * scrollable is true. | |
6110 */ | |
6111 fitContainer: { | 4388 fitContainer: { |
6112 type: Boolean, | 4389 type: Boolean, |
6113 value: false | 4390 value: false |
6114 }, | 4391 }, |
6115 | 4392 |
6116 /** | |
6117 * If true, dragging on the tabs to scroll is disabled. | |
6118 */ | |
6119 disableDrag: { | 4393 disableDrag: { |
6120 type: Boolean, | 4394 type: Boolean, |
6121 value: false | 4395 value: false |
6122 }, | 4396 }, |
6123 | 4397 |
6124 /** | |
6125 * If true, scroll buttons (left/right arrow) will be hidden for scrolla
ble tabs. | |
6126 */ | |
6127 hideScrollButtons: { | 4398 hideScrollButtons: { |
6128 type: Boolean, | 4399 type: Boolean, |
6129 value: false | 4400 value: false |
6130 }, | 4401 }, |
6131 | 4402 |
6132 /** | |
6133 * If true, the tabs are aligned to bottom (the selection bar appears at
the top). | |
6134 */ | |
6135 alignBottom: { | 4403 alignBottom: { |
6136 type: Boolean, | 4404 type: Boolean, |
6137 value: false | 4405 value: false |
6138 }, | 4406 }, |
6139 | 4407 |
6140 selectable: { | 4408 selectable: { |
6141 type: String, | 4409 type: String, |
6142 value: 'paper-tab' | 4410 value: 'paper-tab' |
6143 }, | 4411 }, |
6144 | 4412 |
6145 /** | |
6146 * If true, tabs are automatically selected when focused using the | |
6147 * keyboard. | |
6148 */ | |
6149 autoselect: { | 4413 autoselect: { |
6150 type: Boolean, | 4414 type: Boolean, |
6151 value: false | 4415 value: false |
6152 }, | 4416 }, |
6153 | 4417 |
6154 /** | |
6155 * The delay (in milliseconds) between when the user stops interacting | |
6156 * with the tabs through the keyboard and when the focused item is | |
6157 * automatically selected (if `autoselect` is true). | |
6158 */ | |
6159 autoselectDelay: { | 4418 autoselectDelay: { |
6160 type: Number, | 4419 type: Number, |
6161 value: 0 | 4420 value: 0 |
6162 }, | 4421 }, |
6163 | 4422 |
6164 _step: { | 4423 _step: { |
6165 type: Number, | 4424 type: Number, |
6166 value: 10 | 4425 value: 10 |
6167 }, | 4426 }, |
6168 | 4427 |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6249 _computeSelectionBarClass: function(noBar, alignBottom) { | 4508 _computeSelectionBarClass: function(noBar, alignBottom) { |
6250 if (noBar) { | 4509 if (noBar) { |
6251 return 'hidden'; | 4510 return 'hidden'; |
6252 } else if (alignBottom) { | 4511 } else if (alignBottom) { |
6253 return 'align-bottom'; | 4512 return 'align-bottom'; |
6254 } | 4513 } |
6255 | 4514 |
6256 return ''; | 4515 return ''; |
6257 }, | 4516 }, |
6258 | 4517 |
6259 // TODO(cdata): Add `track` response back in when gesture lands. | |
6260 | 4518 |
6261 _onTabSizingChanged: function() { | 4519 _onTabSizingChanged: function() { |
6262 this.debounce('_onTabSizingChanged', function() { | 4520 this.debounce('_onTabSizingChanged', function() { |
6263 this._scroll(); | 4521 this._scroll(); |
6264 this._tabChanged(this.selectedItem); | 4522 this._tabChanged(this.selectedItem); |
6265 }, 10); | 4523 }, 10); |
6266 }, | 4524 }, |
6267 | 4525 |
6268 _onIronSelect: function(event) { | 4526 _onIronSelect: function(event) { |
6269 this._tabChanged(event.detail.item, this._previousTab); | 4527 this._tabChanged(event.detail.item, this._previousTab); |
6270 this._previousTab = event.detail.item; | 4528 this._previousTab = event.detail.item; |
6271 this.cancelDebouncer('tab-changed'); | 4529 this.cancelDebouncer('tab-changed'); |
6272 }, | 4530 }, |
6273 | 4531 |
6274 _onIronDeselect: function(event) { | 4532 _onIronDeselect: function(event) { |
6275 this.debounce('tab-changed', function() { | 4533 this.debounce('tab-changed', function() { |
6276 this._tabChanged(null, this._previousTab); | 4534 this._tabChanged(null, this._previousTab); |
6277 this._previousTab = null; | 4535 this._previousTab = null; |
6278 // See polymer/polymer#1305 | |
6279 }, 1); | 4536 }, 1); |
6280 }, | 4537 }, |
6281 | 4538 |
6282 _activateHandler: function() { | 4539 _activateHandler: function() { |
6283 // Cancel item activations scheduled by keyboard events when any other | |
6284 // action causes an item to be activated (e.g. clicks). | |
6285 this._cancelPendingActivation(); | 4540 this._cancelPendingActivation(); |
6286 | 4541 |
6287 Polymer.IronMenuBehaviorImpl._activateHandler.apply(this, arguments); | 4542 Polymer.IronMenuBehaviorImpl._activateHandler.apply(this, arguments); |
6288 }, | 4543 }, |
6289 | 4544 |
6290 /** | |
6291 * Activates an item after a delay (in milliseconds). | |
6292 */ | |
6293 _scheduleActivation: function(item, delay) { | 4545 _scheduleActivation: function(item, delay) { |
6294 this._pendingActivationItem = item; | 4546 this._pendingActivationItem = item; |
6295 this._pendingActivationTimeout = this.async( | 4547 this._pendingActivationTimeout = this.async( |
6296 this._bindDelayedActivationHandler, delay); | 4548 this._bindDelayedActivationHandler, delay); |
6297 }, | 4549 }, |
6298 | 4550 |
6299 /** | |
6300 * Activates the last item given to `_scheduleActivation`. | |
6301 */ | |
6302 _delayedActivationHandler: function() { | 4551 _delayedActivationHandler: function() { |
6303 var item = this._pendingActivationItem; | 4552 var item = this._pendingActivationItem; |
6304 this._pendingActivationItem = undefined; | 4553 this._pendingActivationItem = undefined; |
6305 this._pendingActivationTimeout = undefined; | 4554 this._pendingActivationTimeout = undefined; |
6306 item.fire(this.activateEvent, null, { | 4555 item.fire(this.activateEvent, null, { |
6307 bubbles: true, | 4556 bubbles: true, |
6308 cancelable: true | 4557 cancelable: true |
6309 }); | 4558 }); |
6310 }, | 4559 }, |
6311 | 4560 |
6312 /** | |
6313 * Cancels a previously scheduled item activation made with | |
6314 * `_scheduleActivation`. | |
6315 */ | |
6316 _cancelPendingActivation: function() { | 4561 _cancelPendingActivation: function() { |
6317 if (this._pendingActivationTimeout !== undefined) { | 4562 if (this._pendingActivationTimeout !== undefined) { |
6318 this.cancelAsync(this._pendingActivationTimeout); | 4563 this.cancelAsync(this._pendingActivationTimeout); |
6319 this._pendingActivationItem = undefined; | 4564 this._pendingActivationItem = undefined; |
6320 this._pendingActivationTimeout = undefined; | 4565 this._pendingActivationTimeout = undefined; |
6321 } | 4566 } |
6322 }, | 4567 }, |
6323 | 4568 |
6324 _onArrowKeyup: function(event) { | 4569 _onArrowKeyup: function(event) { |
6325 if (this.autoselect) { | 4570 if (this.autoselect) { |
6326 this._scheduleActivation(this.focusedItem, this.autoselectDelay); | 4571 this._scheduleActivation(this.focusedItem, this.autoselectDelay); |
6327 } | 4572 } |
6328 }, | 4573 }, |
6329 | 4574 |
6330 _onBlurCapture: function(event) { | 4575 _onBlurCapture: function(event) { |
6331 // Cancel a scheduled item activation (if any) when that item is | |
6332 // blurred. | |
6333 if (event.target === this._pendingActivationItem) { | 4576 if (event.target === this._pendingActivationItem) { |
6334 this._cancelPendingActivation(); | 4577 this._cancelPendingActivation(); |
6335 } | 4578 } |
6336 }, | 4579 }, |
6337 | 4580 |
6338 get _tabContainerScrollSize () { | 4581 get _tabContainerScrollSize () { |
6339 return Math.max( | 4582 return Math.max( |
6340 0, | 4583 0, |
6341 this.$.tabsContainer.scrollWidth - | 4584 this.$.tabsContainer.scrollWidth - |
6342 this.$.tabsContainer.offsetWidth | 4585 this.$.tabsContainer.offsetWidth |
6343 ); | 4586 ); |
6344 }, | 4587 }, |
6345 | 4588 |
6346 _scroll: function(e, detail) { | 4589 _scroll: function(e, detail) { |
6347 if (!this.scrollable) { | 4590 if (!this.scrollable) { |
6348 return; | 4591 return; |
6349 } | 4592 } |
6350 | 4593 |
6351 var ddx = (detail && -detail.ddx) || 0; | 4594 var ddx = (detail && -detail.ddx) || 0; |
6352 this._affectScroll(ddx); | 4595 this._affectScroll(ddx); |
6353 }, | 4596 }, |
6354 | 4597 |
6355 _down: function(e) { | 4598 _down: function(e) { |
6356 // go one beat async to defeat IronMenuBehavior | |
6357 // autorefocus-on-no-selection timeout | |
6358 this.async(function() { | 4599 this.async(function() { |
6359 if (this._defaultFocusAsync) { | 4600 if (this._defaultFocusAsync) { |
6360 this.cancelAsync(this._defaultFocusAsync); | 4601 this.cancelAsync(this._defaultFocusAsync); |
6361 this._defaultFocusAsync = null; | 4602 this._defaultFocusAsync = null; |
6362 } | 4603 } |
6363 }, 1); | 4604 }, 1); |
6364 }, | 4605 }, |
6365 | 4606 |
6366 _affectScroll: function(dx) { | 4607 _affectScroll: function(dx) { |
6367 this.$.tabsContainer.scrollLeft += dx; | 4608 this.$.tabsContainer.scrollLeft += dx; |
(...skipping 22 matching lines...) Expand all Loading... |
6390 _scrollToLeft: function() { | 4631 _scrollToLeft: function() { |
6391 this._affectScroll(-this._step); | 4632 this._affectScroll(-this._step); |
6392 }, | 4633 }, |
6393 | 4634 |
6394 _scrollToRight: function() { | 4635 _scrollToRight: function() { |
6395 this._affectScroll(this._step); | 4636 this._affectScroll(this._step); |
6396 }, | 4637 }, |
6397 | 4638 |
6398 _tabChanged: function(tab, old) { | 4639 _tabChanged: function(tab, old) { |
6399 if (!tab) { | 4640 if (!tab) { |
6400 // Remove the bar without animation. | |
6401 this.$.selectionBar.classList.remove('expand'); | 4641 this.$.selectionBar.classList.remove('expand'); |
6402 this.$.selectionBar.classList.remove('contract'); | 4642 this.$.selectionBar.classList.remove('contract'); |
6403 this._positionBar(0, 0); | 4643 this._positionBar(0, 0); |
6404 return; | 4644 return; |
6405 } | 4645 } |
6406 | 4646 |
6407 var r = this.$.tabsContent.getBoundingClientRect(); | 4647 var r = this.$.tabsContent.getBoundingClientRect(); |
6408 var w = r.width; | 4648 var w = r.width; |
6409 var tabRect = tab.getBoundingClientRect(); | 4649 var tabRect = tab.getBoundingClientRect(); |
6410 var tabOffsetLeft = tabRect.left - r.left; | 4650 var tabOffsetLeft = tabRect.left - r.left; |
6411 | 4651 |
6412 this._pos = { | 4652 this._pos = { |
6413 width: this._calcPercent(tabRect.width, w), | 4653 width: this._calcPercent(tabRect.width, w), |
6414 left: this._calcPercent(tabOffsetLeft, w) | 4654 left: this._calcPercent(tabOffsetLeft, w) |
6415 }; | 4655 }; |
6416 | 4656 |
6417 if (this.noSlide || old == null) { | 4657 if (this.noSlide || old == null) { |
6418 // Position the bar without animation. | |
6419 this.$.selectionBar.classList.remove('expand'); | 4658 this.$.selectionBar.classList.remove('expand'); |
6420 this.$.selectionBar.classList.remove('contract'); | 4659 this.$.selectionBar.classList.remove('contract'); |
6421 this._positionBar(this._pos.width, this._pos.left); | 4660 this._positionBar(this._pos.width, this._pos.left); |
6422 return; | 4661 return; |
6423 } | 4662 } |
6424 | 4663 |
6425 var oldRect = old.getBoundingClientRect(); | 4664 var oldRect = old.getBoundingClientRect(); |
6426 var oldIndex = this.items.indexOf(old); | 4665 var oldIndex = this.items.indexOf(old); |
6427 var index = this.items.indexOf(tab); | 4666 var index = this.items.indexOf(tab); |
6428 var m = 5; | 4667 var m = 5; |
6429 | 4668 |
6430 // bar animation: expand | |
6431 this.$.selectionBar.classList.add('expand'); | 4669 this.$.selectionBar.classList.add('expand'); |
6432 | 4670 |
6433 var moveRight = oldIndex < index; | 4671 var moveRight = oldIndex < index; |
6434 var isRTL = this._isRTL; | 4672 var isRTL = this._isRTL; |
6435 if (isRTL) { | 4673 if (isRTL) { |
6436 moveRight = !moveRight; | 4674 moveRight = !moveRight; |
6437 } | 4675 } |
6438 | 4676 |
6439 if (moveRight) { | 4677 if (moveRight) { |
6440 this._positionBar(this._calcPercent(tabRect.left + tabRect.width - old
Rect.left, w) - m, | 4678 this._positionBar(this._calcPercent(tabRect.left + tabRect.width - old
Rect.left, w) - m, |
(...skipping 30 matching lines...) Expand all Loading... |
6471 | 4709 |
6472 this._width = width; | 4710 this._width = width; |
6473 this._left = left; | 4711 this._left = left; |
6474 this.transform( | 4712 this.transform( |
6475 'translateX(' + left + '%) scaleX(' + (width / 100) + ')', | 4713 'translateX(' + left + '%) scaleX(' + (width / 100) + ')', |
6476 this.$.selectionBar); | 4714 this.$.selectionBar); |
6477 }, | 4715 }, |
6478 | 4716 |
6479 _onBarTransitionEnd: function(e) { | 4717 _onBarTransitionEnd: function(e) { |
6480 var cl = this.$.selectionBar.classList; | 4718 var cl = this.$.selectionBar.classList; |
6481 // bar animation: expand -> contract | |
6482 if (cl.contains('expand')) { | 4719 if (cl.contains('expand')) { |
6483 cl.remove('expand'); | 4720 cl.remove('expand'); |
6484 cl.add('contract'); | 4721 cl.add('contract'); |
6485 this._positionBar(this._pos.width, this._pos.left); | 4722 this._positionBar(this._pos.width, this._pos.left); |
6486 // bar animation done | |
6487 } else if (cl.contains('contract')) { | 4723 } else if (cl.contains('contract')) { |
6488 cl.remove('contract'); | 4724 cl.remove('contract'); |
6489 } | 4725 } |
6490 } | 4726 } |
6491 }); | 4727 }); |
6492 (function() { | 4728 (function() { |
6493 'use strict'; | 4729 'use strict'; |
6494 | 4730 |
6495 Polymer.IronA11yAnnouncer = Polymer({ | 4731 Polymer.IronA11yAnnouncer = Polymer({ |
6496 is: 'iron-a11y-announcer', | 4732 is: 'iron-a11y-announcer', |
6497 | 4733 |
6498 properties: { | 4734 properties: { |
6499 | 4735 |
6500 /** | |
6501 * The value of mode is used to set the `aria-live` attribute | |
6502 * for the element that will be announced. Valid values are: `off`, | |
6503 * `polite` and `assertive`. | |
6504 */ | |
6505 mode: { | 4736 mode: { |
6506 type: String, | 4737 type: String, |
6507 value: 'polite' | 4738 value: 'polite' |
6508 }, | 4739 }, |
6509 | 4740 |
6510 _text: { | 4741 _text: { |
6511 type: String, | 4742 type: String, |
6512 value: '' | 4743 value: '' |
6513 } | 4744 } |
6514 }, | 4745 }, |
6515 | 4746 |
6516 created: function() { | 4747 created: function() { |
6517 if (!Polymer.IronA11yAnnouncer.instance) { | 4748 if (!Polymer.IronA11yAnnouncer.instance) { |
6518 Polymer.IronA11yAnnouncer.instance = this; | 4749 Polymer.IronA11yAnnouncer.instance = this; |
6519 } | 4750 } |
6520 | 4751 |
6521 document.body.addEventListener('iron-announce', this._onIronAnnounce.b
ind(this)); | 4752 document.body.addEventListener('iron-announce', this._onIronAnnounce.b
ind(this)); |
6522 }, | 4753 }, |
6523 | 4754 |
6524 /** | |
6525 * Cause a text string to be announced by screen readers. | |
6526 * | |
6527 * @param {string} text The text that should be announced. | |
6528 */ | |
6529 announce: function(text) { | 4755 announce: function(text) { |
6530 this._text = ''; | 4756 this._text = ''; |
6531 this.async(function() { | 4757 this.async(function() { |
6532 this._text = text; | 4758 this._text = text; |
6533 }, 100); | 4759 }, 100); |
6534 }, | 4760 }, |
6535 | 4761 |
6536 _onIronAnnounce: function(event) { | 4762 _onIronAnnounce: function(event) { |
6537 if (event.detail && event.detail.text) { | 4763 if (event.detail && event.detail.text) { |
6538 this.announce(event.detail.text); | 4764 this.announce(event.detail.text); |
6539 } | 4765 } |
6540 } | 4766 } |
6541 }); | 4767 }); |
6542 | 4768 |
6543 Polymer.IronA11yAnnouncer.instance = null; | 4769 Polymer.IronA11yAnnouncer.instance = null; |
6544 | 4770 |
6545 Polymer.IronA11yAnnouncer.requestAvailability = function() { | 4771 Polymer.IronA11yAnnouncer.requestAvailability = function() { |
6546 if (!Polymer.IronA11yAnnouncer.instance) { | 4772 if (!Polymer.IronA11yAnnouncer.instance) { |
6547 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y
-announcer'); | 4773 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y
-announcer'); |
6548 } | 4774 } |
6549 | 4775 |
6550 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); | 4776 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); |
6551 }; | 4777 }; |
6552 })(); | 4778 })(); |
6553 /** | |
6554 * Singleton IronMeta instance. | |
6555 */ | |
6556 Polymer.IronValidatableBehaviorMeta = null; | 4779 Polymer.IronValidatableBehaviorMeta = null; |
6557 | 4780 |
6558 /** | |
6559 * `Use Polymer.IronValidatableBehavior` to implement an element that validate
s user input. | |
6560 * Use the related `Polymer.IronValidatorBehavior` to add custom validation lo
gic to an iron-input. | |
6561 * | |
6562 * By default, an `<iron-form>` element validates its fields when the user pre
sses the submit button. | |
6563 * To validate a form imperatively, call the form's `validate()` method, which
in turn will | |
6564 * call `validate()` on all its children. By using `Polymer.IronValidatableBeh
avior`, your | |
6565 * custom element will get a public `validate()`, which | |
6566 * will return the validity of the element, and a corresponding `invalid` attr
ibute, | |
6567 * which can be used for styling. | |
6568 * | |
6569 * To implement the custom validation logic of your element, you must override | |
6570 * the protected `_getValidity()` method of this behaviour, rather than `valid
ate()`. | |
6571 * See [this](https://github.com/PolymerElements/iron-form/blob/master/demo/si
mple-element.html) | |
6572 * for an example. | |
6573 * | |
6574 * ### Accessibility | |
6575 * | |
6576 * Changing the `invalid` property, either manually or by calling `validate()`
will update the | |
6577 * `aria-invalid` attribute. | |
6578 * | |
6579 * @demo demo/index.html | |
6580 * @polymerBehavior | |
6581 */ | |
6582 Polymer.IronValidatableBehavior = { | 4781 Polymer.IronValidatableBehavior = { |
6583 | 4782 |
6584 properties: { | 4783 properties: { |
6585 | 4784 |
6586 /** | |
6587 * Name of the validator to use. | |
6588 */ | |
6589 validator: { | 4785 validator: { |
6590 type: String | 4786 type: String |
6591 }, | 4787 }, |
6592 | 4788 |
6593 /** | |
6594 * True if the last call to `validate` is invalid. | |
6595 */ | |
6596 invalid: { | 4789 invalid: { |
6597 notify: true, | 4790 notify: true, |
6598 reflectToAttribute: true, | 4791 reflectToAttribute: true, |
6599 type: Boolean, | 4792 type: Boolean, |
6600 value: false | 4793 value: false |
6601 }, | 4794 }, |
6602 | 4795 |
6603 /** | |
6604 * This property is deprecated and should not be used. Use the global | |
6605 * validator meta singleton, `Polymer.IronValidatableBehaviorMeta` instead
. | |
6606 */ | |
6607 _validatorMeta: { | 4796 _validatorMeta: { |
6608 type: Object | 4797 type: Object |
6609 }, | 4798 }, |
6610 | 4799 |
6611 /** | |
6612 * Namespace for this validator. This property is deprecated and should | |
6613 * not be used. For all intents and purposes, please consider it a | |
6614 * read-only, config-time property. | |
6615 */ | |
6616 validatorType: { | 4800 validatorType: { |
6617 type: String, | 4801 type: String, |
6618 value: 'validator' | 4802 value: 'validator' |
6619 }, | 4803 }, |
6620 | 4804 |
6621 _validator: { | 4805 _validator: { |
6622 type: Object, | 4806 type: Object, |
6623 computed: '__computeValidator(validator)' | 4807 computed: '__computeValidator(validator)' |
6624 } | 4808 } |
6625 }, | 4809 }, |
6626 | 4810 |
6627 observers: [ | 4811 observers: [ |
6628 '_invalidChanged(invalid)' | 4812 '_invalidChanged(invalid)' |
6629 ], | 4813 ], |
6630 | 4814 |
6631 registered: function() { | 4815 registered: function() { |
6632 Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validat
or'}); | 4816 Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validat
or'}); |
6633 }, | 4817 }, |
6634 | 4818 |
6635 _invalidChanged: function() { | 4819 _invalidChanged: function() { |
6636 if (this.invalid) { | 4820 if (this.invalid) { |
6637 this.setAttribute('aria-invalid', 'true'); | 4821 this.setAttribute('aria-invalid', 'true'); |
6638 } else { | 4822 } else { |
6639 this.removeAttribute('aria-invalid'); | 4823 this.removeAttribute('aria-invalid'); |
6640 } | 4824 } |
6641 }, | 4825 }, |
6642 | 4826 |
6643 /** | |
6644 * @return {boolean} True if the validator `validator` exists. | |
6645 */ | |
6646 hasValidator: function() { | 4827 hasValidator: function() { |
6647 return this._validator != null; | 4828 return this._validator != null; |
6648 }, | 4829 }, |
6649 | 4830 |
6650 /** | |
6651 * Returns true if the `value` is valid, and updates `invalid`. If you want | |
6652 * your element to have custom validation logic, do not override this method
; | |
6653 * override `_getValidity(value)` instead. | |
6654 | |
6655 * @param {Object} value The value to be validated. By default, it is passed | |
6656 * to the validator's `validate()` function, if a validator is set. | |
6657 * @return {boolean} True if `value` is valid. | |
6658 */ | |
6659 validate: function(value) { | 4831 validate: function(value) { |
6660 this.invalid = !this._getValidity(value); | 4832 this.invalid = !this._getValidity(value); |
6661 return !this.invalid; | 4833 return !this.invalid; |
6662 }, | 4834 }, |
6663 | 4835 |
6664 /** | |
6665 * Returns true if `value` is valid. By default, it is passed | |
6666 * to the validator's `validate()` function, if a validator is set. You | |
6667 * should override this method if you want to implement custom validity | |
6668 * logic for your element. | |
6669 * | |
6670 * @param {Object} value The value to be validated. | |
6671 * @return {boolean} True if `value` is valid. | |
6672 */ | |
6673 | 4836 |
6674 _getValidity: function(value) { | 4837 _getValidity: function(value) { |
6675 if (this.hasValidator()) { | 4838 if (this.hasValidator()) { |
6676 return this._validator.validate(value); | 4839 return this._validator.validate(value); |
6677 } | 4840 } |
6678 return true; | 4841 return true; |
6679 }, | 4842 }, |
6680 | 4843 |
6681 __computeValidator: function() { | 4844 __computeValidator: function() { |
6682 return Polymer.IronValidatableBehaviorMeta && | 4845 return Polymer.IronValidatableBehaviorMeta && |
6683 Polymer.IronValidatableBehaviorMeta.byKey(this.validator); | 4846 Polymer.IronValidatableBehaviorMeta.byKey(this.validator); |
6684 } | 4847 } |
6685 }; | 4848 }; |
6686 /* | |
6687 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal
idatorBehavior` | |
6688 to `<input>`. | |
6689 | |
6690 ### Two-way binding | |
6691 | |
6692 By default you can only get notified of changes to an `input`'s `value` due to u
ser input: | |
6693 | |
6694 <input value="{{myValue::input}}"> | |
6695 | |
6696 `iron-input` adds the `bind-value` property that mirrors the `value` property, a
nd can be used | |
6697 for two-way data binding. `bind-value` will notify if it is changed either by us
er input or by script. | |
6698 | |
6699 <input is="iron-input" bind-value="{{myValue}}"> | |
6700 | |
6701 ### Custom validators | |
6702 | |
6703 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit
h `<iron-input>`. | |
6704 | |
6705 <input is="iron-input" validator="my-custom-validator"> | |
6706 | |
6707 ### Stopping invalid input | |
6708 | |
6709 It may be desirable to only allow users to enter certain characters. You can use
the | |
6710 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish
this. This feature | |
6711 is separate from validation, and `allowed-pattern` does not affect how the input
is validated. | |
6712 | |
6713 \x3c!-- only allow characters that match [0-9] --\x3e | |
6714 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> | |
6715 | |
6716 @hero hero.svg | |
6717 @demo demo/index.html | |
6718 */ | |
6719 | 4849 |
6720 Polymer({ | 4850 Polymer({ |
6721 | 4851 |
6722 is: 'iron-input', | 4852 is: 'iron-input', |
6723 | 4853 |
6724 extends: 'input', | 4854 extends: 'input', |
6725 | 4855 |
6726 behaviors: [ | 4856 behaviors: [ |
6727 Polymer.IronValidatableBehavior | 4857 Polymer.IronValidatableBehavior |
6728 ], | 4858 ], |
6729 | 4859 |
6730 properties: { | 4860 properties: { |
6731 | 4861 |
6732 /** | |
6733 * Use this property instead of `value` for two-way data binding. | |
6734 */ | |
6735 bindValue: { | 4862 bindValue: { |
6736 observer: '_bindValueChanged', | 4863 observer: '_bindValueChanged', |
6737 type: String | 4864 type: String |
6738 }, | 4865 }, |
6739 | 4866 |
6740 /** | |
6741 * Set to true to prevent the user from entering invalid input. If `allowe
dPattern` is set, | |
6742 * any character typed by the user will be matched against that pattern, a
nd rejected if it's not a match. | |
6743 * Pasted input will have each character checked individually; if any char
acter | |
6744 * doesn't match `allowedPattern`, the entire pasted string will be reject
ed. | |
6745 * If `allowedPattern` is not set, it will use the `type` attribute (only
supported for `type=number`). | |
6746 */ | |
6747 preventInvalidInput: { | 4867 preventInvalidInput: { |
6748 type: Boolean | 4868 type: Boolean |
6749 }, | 4869 }, |
6750 | 4870 |
6751 /** | |
6752 * Regular expression that list the characters allowed as input. | |
6753 * This pattern represents the allowed characters for the field; as the us
er inputs text, | |
6754 * each individual character will be checked against the pattern (rather t
han checking | |
6755 * the entire value as a whole). The recommended format should be a list o
f allowed characters; | |
6756 * for example, `[a-zA-Z0-9.+-!;:]` | |
6757 */ | |
6758 allowedPattern: { | 4871 allowedPattern: { |
6759 type: String, | 4872 type: String, |
6760 observer: "_allowedPatternChanged" | 4873 observer: "_allowedPatternChanged" |
6761 }, | 4874 }, |
6762 | 4875 |
6763 _previousValidInput: { | 4876 _previousValidInput: { |
6764 type: String, | 4877 type: String, |
6765 value: '' | 4878 value: '' |
6766 }, | 4879 }, |
6767 | 4880 |
6768 _patternAlreadyChecked: { | 4881 _patternAlreadyChecked: { |
6769 type: Boolean, | 4882 type: Boolean, |
6770 value: false | 4883 value: false |
6771 } | 4884 } |
6772 | 4885 |
6773 }, | 4886 }, |
6774 | 4887 |
6775 listeners: { | 4888 listeners: { |
6776 'input': '_onInput', | 4889 'input': '_onInput', |
6777 'keypress': '_onKeypress' | 4890 'keypress': '_onKeypress' |
6778 }, | 4891 }, |
6779 | 4892 |
6780 /** @suppress {checkTypes} */ | 4893 /** @suppress {checkTypes} */ |
6781 registered: function() { | 4894 registered: function() { |
6782 // Feature detect whether we need to patch dispatchEvent (i.e. on FF and I
E). | |
6783 if (!this._canDispatchEventOnDisabled()) { | 4895 if (!this._canDispatchEventOnDisabled()) { |
6784 this._origDispatchEvent = this.dispatchEvent; | 4896 this._origDispatchEvent = this.dispatchEvent; |
6785 this.dispatchEvent = this._dispatchEventFirefoxIE; | 4897 this.dispatchEvent = this._dispatchEventFirefoxIE; |
6786 } | 4898 } |
6787 }, | 4899 }, |
6788 | 4900 |
6789 created: function() { | 4901 created: function() { |
6790 Polymer.IronA11yAnnouncer.requestAvailability(); | 4902 Polymer.IronA11yAnnouncer.requestAvailability(); |
6791 }, | 4903 }, |
6792 | 4904 |
6793 _canDispatchEventOnDisabled: function() { | 4905 _canDispatchEventOnDisabled: function() { |
6794 var input = document.createElement('input'); | 4906 var input = document.createElement('input'); |
6795 var canDispatch = false; | 4907 var canDispatch = false; |
6796 input.disabled = true; | 4908 input.disabled = true; |
6797 | 4909 |
6798 input.addEventListener('feature-check-dispatch-event', function() { | 4910 input.addEventListener('feature-check-dispatch-event', function() { |
6799 canDispatch = true; | 4911 canDispatch = true; |
6800 }); | 4912 }); |
6801 | 4913 |
6802 try { | 4914 try { |
6803 input.dispatchEvent(new Event('feature-check-dispatch-event')); | 4915 input.dispatchEvent(new Event('feature-check-dispatch-event')); |
6804 } catch(e) {} | 4916 } catch(e) {} |
6805 | 4917 |
6806 return canDispatch; | 4918 return canDispatch; |
6807 }, | 4919 }, |
6808 | 4920 |
6809 _dispatchEventFirefoxIE: function() { | 4921 _dispatchEventFirefoxIE: function() { |
6810 // Due to Firefox bug, events fired on disabled form controls can throw | |
6811 // errors; furthermore, neither IE nor Firefox will actually dispatch | |
6812 // events from disabled form controls; as such, we toggle disable around | |
6813 // the dispatch to allow notifying properties to notify | |
6814 // See issue #47 for details | |
6815 var disabled = this.disabled; | 4922 var disabled = this.disabled; |
6816 this.disabled = false; | 4923 this.disabled = false; |
6817 this._origDispatchEvent.apply(this, arguments); | 4924 this._origDispatchEvent.apply(this, arguments); |
6818 this.disabled = disabled; | 4925 this.disabled = disabled; |
6819 }, | 4926 }, |
6820 | 4927 |
6821 get _patternRegExp() { | 4928 get _patternRegExp() { |
6822 var pattern; | 4929 var pattern; |
6823 if (this.allowedPattern) { | 4930 if (this.allowedPattern) { |
6824 pattern = new RegExp(this.allowedPattern); | 4931 pattern = new RegExp(this.allowedPattern); |
6825 } else { | 4932 } else { |
6826 switch (this.type) { | 4933 switch (this.type) { |
6827 case 'number': | 4934 case 'number': |
6828 pattern = /[0-9.,e-]/; | 4935 pattern = /[0-9.,e-]/; |
6829 break; | 4936 break; |
6830 } | 4937 } |
6831 } | 4938 } |
6832 return pattern; | 4939 return pattern; |
6833 }, | 4940 }, |
6834 | 4941 |
6835 ready: function() { | 4942 ready: function() { |
6836 this.bindValue = this.value; | 4943 this.bindValue = this.value; |
6837 }, | 4944 }, |
6838 | 4945 |
6839 /** | |
6840 * @suppress {checkTypes} | |
6841 */ | |
6842 _bindValueChanged: function() { | 4946 _bindValueChanged: function() { |
6843 if (this.value !== this.bindValue) { | 4947 if (this.value !== this.bindValue) { |
6844 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue
=== false) ? '' : this.bindValue; | 4948 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue
=== false) ? '' : this.bindValue; |
6845 } | 4949 } |
6846 // manually notify because we don't want to notify until after setting val
ue | |
6847 this.fire('bind-value-changed', {value: this.bindValue}); | 4950 this.fire('bind-value-changed', {value: this.bindValue}); |
6848 }, | 4951 }, |
6849 | 4952 |
6850 _allowedPatternChanged: function() { | 4953 _allowedPatternChanged: function() { |
6851 // Force to prevent invalid input when an `allowed-pattern` is set | |
6852 this.preventInvalidInput = this.allowedPattern ? true : false; | 4954 this.preventInvalidInput = this.allowedPattern ? true : false; |
6853 }, | 4955 }, |
6854 | 4956 |
6855 _onInput: function() { | 4957 _onInput: function() { |
6856 // Need to validate each of the characters pasted if they haven't | |
6857 // been validated inside `_onKeypress` already. | |
6858 if (this.preventInvalidInput && !this._patternAlreadyChecked) { | 4958 if (this.preventInvalidInput && !this._patternAlreadyChecked) { |
6859 var valid = this._checkPatternValidity(); | 4959 var valid = this._checkPatternValidity(); |
6860 if (!valid) { | 4960 if (!valid) { |
6861 this._announceInvalidCharacter('Invalid string of characters not enter
ed.'); | 4961 this._announceInvalidCharacter('Invalid string of characters not enter
ed.'); |
6862 this.value = this._previousValidInput; | 4962 this.value = this._previousValidInput; |
6863 } | 4963 } |
6864 } | 4964 } |
6865 | 4965 |
6866 this.bindValue = this.value; | 4966 this.bindValue = this.value; |
6867 this._previousValidInput = this.value; | 4967 this._previousValidInput = this.value; |
6868 this._patternAlreadyChecked = false; | 4968 this._patternAlreadyChecked = false; |
6869 }, | 4969 }, |
6870 | 4970 |
6871 _isPrintable: function(event) { | 4971 _isPrintable: function(event) { |
6872 // What a control/printable character is varies wildly based on the browse
r. | |
6873 // - most control characters (arrows, backspace) do not send a `keypress`
event | |
6874 // in Chrome, but the *do* on Firefox | |
6875 // - in Firefox, when they do send a `keypress` event, control chars have | |
6876 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) | |
6877 // - printable characters always send a keypress event. | |
6878 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the
keyCode | |
6879 // always matches the charCode. | |
6880 // None of this makes any sense. | |
6881 | 4972 |
6882 // For these keys, ASCII code == browser keycode. | |
6883 var anyNonPrintable = | 4973 var anyNonPrintable = |
6884 (event.keyCode == 8) || // backspace | 4974 (event.keyCode == 8) || // backspace |
6885 (event.keyCode == 9) || // tab | 4975 (event.keyCode == 9) || // tab |
6886 (event.keyCode == 13) || // enter | 4976 (event.keyCode == 13) || // enter |
6887 (event.keyCode == 27); // escape | 4977 (event.keyCode == 27); // escape |
6888 | 4978 |
6889 // For these keys, make sure it's a browser keycode and not an ASCII code. | |
6890 var mozNonPrintable = | 4979 var mozNonPrintable = |
6891 (event.keyCode == 19) || // pause | 4980 (event.keyCode == 19) || // pause |
6892 (event.keyCode == 20) || // caps lock | 4981 (event.keyCode == 20) || // caps lock |
6893 (event.keyCode == 45) || // insert | 4982 (event.keyCode == 45) || // insert |
6894 (event.keyCode == 46) || // delete | 4983 (event.keyCode == 46) || // delete |
6895 (event.keyCode == 144) || // num lock | 4984 (event.keyCode == 144) || // num lock |
6896 (event.keyCode == 145) || // scroll lock | 4985 (event.keyCode == 145) || // scroll lock |
6897 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho
me, arrows | 4986 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho
me, arrows |
6898 (event.keyCode > 111 && event.keyCode < 124); // fn keys | 4987 (event.keyCode > 111 && event.keyCode < 124); // fn keys |
6899 | 4988 |
6900 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); | 4989 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); |
6901 }, | 4990 }, |
6902 | 4991 |
6903 _onKeypress: function(event) { | 4992 _onKeypress: function(event) { |
6904 if (!this.preventInvalidInput && this.type !== 'number') { | 4993 if (!this.preventInvalidInput && this.type !== 'number') { |
6905 return; | 4994 return; |
6906 } | 4995 } |
6907 var regexp = this._patternRegExp; | 4996 var regexp = this._patternRegExp; |
6908 if (!regexp) { | 4997 if (!regexp) { |
6909 return; | 4998 return; |
6910 } | 4999 } |
6911 | 5000 |
6912 // Handle special keys and backspace | |
6913 if (event.metaKey || event.ctrlKey || event.altKey) | 5001 if (event.metaKey || event.ctrlKey || event.altKey) |
6914 return; | 5002 return; |
6915 | 5003 |
6916 // Check the pattern either here or in `_onInput`, but not in both. | |
6917 this._patternAlreadyChecked = true; | 5004 this._patternAlreadyChecked = true; |
6918 | 5005 |
6919 var thisChar = String.fromCharCode(event.charCode); | 5006 var thisChar = String.fromCharCode(event.charCode); |
6920 if (this._isPrintable(event) && !regexp.test(thisChar)) { | 5007 if (this._isPrintable(event) && !regexp.test(thisChar)) { |
6921 event.preventDefault(); | 5008 event.preventDefault(); |
6922 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not e
ntered.'); | 5009 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not e
ntered.'); |
6923 } | 5010 } |
6924 }, | 5011 }, |
6925 | 5012 |
6926 _checkPatternValidity: function() { | 5013 _checkPatternValidity: function() { |
6927 var regexp = this._patternRegExp; | 5014 var regexp = this._patternRegExp; |
6928 if (!regexp) { | 5015 if (!regexp) { |
6929 return true; | 5016 return true; |
6930 } | 5017 } |
6931 for (var i = 0; i < this.value.length; i++) { | 5018 for (var i = 0; i < this.value.length; i++) { |
6932 if (!regexp.test(this.value[i])) { | 5019 if (!regexp.test(this.value[i])) { |
6933 return false; | 5020 return false; |
6934 } | 5021 } |
6935 } | 5022 } |
6936 return true; | 5023 return true; |
6937 }, | 5024 }, |
6938 | 5025 |
6939 /** | |
6940 * Returns true if `value` is valid. The validator provided in `validator` w
ill be used first, | |
6941 * then any constraints. | |
6942 * @return {boolean} True if the value is valid. | |
6943 */ | |
6944 validate: function() { | 5026 validate: function() { |
6945 // First, check what the browser thinks. Some inputs (like type=number) | |
6946 // behave weirdly and will set the value to "" if something invalid is | |
6947 // entered, but will set the validity correctly. | |
6948 var valid = this.checkValidity(); | 5027 var valid = this.checkValidity(); |
6949 | 5028 |
6950 // Only do extra checking if the browser thought this was valid. | |
6951 if (valid) { | 5029 if (valid) { |
6952 // Empty, required input is invalid | |
6953 if (this.required && this.value === '') { | 5030 if (this.required && this.value === '') { |
6954 valid = false; | 5031 valid = false; |
6955 } else if (this.hasValidator()) { | 5032 } else if (this.hasValidator()) { |
6956 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value
); | 5033 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value
); |
6957 } | 5034 } |
6958 } | 5035 } |
6959 | 5036 |
6960 this.invalid = !valid; | 5037 this.invalid = !valid; |
6961 this.fire('iron-input-validate'); | 5038 this.fire('iron-input-validate'); |
6962 return valid; | 5039 return valid; |
6963 }, | 5040 }, |
6964 | 5041 |
6965 _announceInvalidCharacter: function(message) { | 5042 _announceInvalidCharacter: function(message) { |
6966 this.fire('iron-announce', { text: message }); | 5043 this.fire('iron-announce', { text: message }); |
6967 } | 5044 } |
6968 }); | 5045 }); |
6969 | 5046 |
6970 /* | |
6971 The `iron-input-validate` event is fired whenever `validate()` is called. | |
6972 @event iron-input-validate | |
6973 */ | |
6974 Polymer({ | 5047 Polymer({ |
6975 is: 'paper-input-container', | 5048 is: 'paper-input-container', |
6976 | 5049 |
6977 properties: { | 5050 properties: { |
6978 /** | |
6979 * Set to true to disable the floating label. The label disappears when th
e input value is | |
6980 * not null. | |
6981 */ | |
6982 noLabelFloat: { | 5051 noLabelFloat: { |
6983 type: Boolean, | 5052 type: Boolean, |
6984 value: false | 5053 value: false |
6985 }, | 5054 }, |
6986 | 5055 |
6987 /** | |
6988 * Set to true to always float the floating label. | |
6989 */ | |
6990 alwaysFloatLabel: { | 5056 alwaysFloatLabel: { |
6991 type: Boolean, | 5057 type: Boolean, |
6992 value: false | 5058 value: false |
6993 }, | 5059 }, |
6994 | 5060 |
6995 /** | |
6996 * The attribute to listen for value changes on. | |
6997 */ | |
6998 attrForValue: { | 5061 attrForValue: { |
6999 type: String, | 5062 type: String, |
7000 value: 'bind-value' | 5063 value: 'bind-value' |
7001 }, | 5064 }, |
7002 | 5065 |
7003 /** | |
7004 * Set to true to auto-validate the input value when it changes. | |
7005 */ | |
7006 autoValidate: { | 5066 autoValidate: { |
7007 type: Boolean, | 5067 type: Boolean, |
7008 value: false | 5068 value: false |
7009 }, | 5069 }, |
7010 | 5070 |
7011 /** | |
7012 * True if the input is invalid. This property is set automatically when t
he input value | |
7013 * changes if auto-validating, or when the `iron-input-validate` event is
heard from a child. | |
7014 */ | |
7015 invalid: { | 5071 invalid: { |
7016 observer: '_invalidChanged', | 5072 observer: '_invalidChanged', |
7017 type: Boolean, | 5073 type: Boolean, |
7018 value: false | 5074 value: false |
7019 }, | 5075 }, |
7020 | 5076 |
7021 /** | |
7022 * True if the input has focus. | |
7023 */ | |
7024 focused: { | 5077 focused: { |
7025 readOnly: true, | 5078 readOnly: true, |
7026 type: Boolean, | 5079 type: Boolean, |
7027 value: false, | 5080 value: false, |
7028 notify: true | 5081 notify: true |
7029 }, | 5082 }, |
7030 | 5083 |
7031 _addons: { | 5084 _addons: { |
7032 type: Array | 5085 type: Array |
7033 // do not set a default value here intentionally - it will be initialize
d lazily when a | |
7034 // distributed child is attached, which may occur before configuration f
or this element | |
7035 // in polyfill. | |
7036 }, | 5086 }, |
7037 | 5087 |
7038 _inputHasContent: { | 5088 _inputHasContent: { |
7039 type: Boolean, | 5089 type: Boolean, |
7040 value: false | 5090 value: false |
7041 }, | 5091 }, |
7042 | 5092 |
7043 _inputSelector: { | 5093 _inputSelector: { |
7044 type: String, | 5094 type: String, |
7045 value: 'input,textarea,.paper-input-input' | 5095 value: 'input,textarea,.paper-input-input' |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
7103 this.addEventListener('blur', this._boundOnBlur, true); | 5153 this.addEventListener('blur', this._boundOnBlur, true); |
7104 }, | 5154 }, |
7105 | 5155 |
7106 attached: function() { | 5156 attached: function() { |
7107 if (this.attrForValue) { | 5157 if (this.attrForValue) { |
7108 this._inputElement.addEventListener(this._valueChangedEvent, this._bound
ValueChanged); | 5158 this._inputElement.addEventListener(this._valueChangedEvent, this._bound
ValueChanged); |
7109 } else { | 5159 } else { |
7110 this.addEventListener('input', this._onInput); | 5160 this.addEventListener('input', this._onInput); |
7111 } | 5161 } |
7112 | 5162 |
7113 // Only validate when attached if the input already has a value. | |
7114 if (this._inputElementValue != '') { | 5163 if (this._inputElementValue != '') { |
7115 this._handleValueAndAutoValidate(this._inputElement); | 5164 this._handleValueAndAutoValidate(this._inputElement); |
7116 } else { | 5165 } else { |
7117 this._handleValue(this._inputElement); | 5166 this._handleValue(this._inputElement); |
7118 } | 5167 } |
7119 }, | 5168 }, |
7120 | 5169 |
7121 _onAddonAttached: function(event) { | 5170 _onAddonAttached: function(event) { |
7122 if (!this._addons) { | 5171 if (!this._addons) { |
7123 this._addons = []; | 5172 this._addons = []; |
(...skipping 20 matching lines...) Expand all Loading... |
7144 this._handleValueAndAutoValidate(event.target); | 5193 this._handleValueAndAutoValidate(event.target); |
7145 }, | 5194 }, |
7146 | 5195 |
7147 _onValueChanged: function(event) { | 5196 _onValueChanged: function(event) { |
7148 this._handleValueAndAutoValidate(event.target); | 5197 this._handleValueAndAutoValidate(event.target); |
7149 }, | 5198 }, |
7150 | 5199 |
7151 _handleValue: function(inputElement) { | 5200 _handleValue: function(inputElement) { |
7152 var value = this._inputElementValue; | 5201 var value = this._inputElementValue; |
7153 | 5202 |
7154 // type="number" hack needed because this.value is empty until it's valid | |
7155 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme
nt.checkValidity())) { | 5203 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme
nt.checkValidity())) { |
7156 this._inputHasContent = true; | 5204 this._inputHasContent = true; |
7157 } else { | 5205 } else { |
7158 this._inputHasContent = false; | 5206 this._inputHasContent = false; |
7159 } | 5207 } |
7160 | 5208 |
7161 this.updateAddons({ | 5209 this.updateAddons({ |
7162 inputElement: inputElement, | 5210 inputElement: inputElement, |
7163 value: value, | 5211 value: value, |
7164 invalid: this.invalid | 5212 invalid: this.invalid |
7165 }); | 5213 }); |
7166 }, | 5214 }, |
7167 | 5215 |
7168 _handleValueAndAutoValidate: function(inputElement) { | 5216 _handleValueAndAutoValidate: function(inputElement) { |
7169 if (this.autoValidate) { | 5217 if (this.autoValidate) { |
7170 var valid; | 5218 var valid; |
7171 if (inputElement.validate) { | 5219 if (inputElement.validate) { |
7172 valid = inputElement.validate(this._inputElementValue); | 5220 valid = inputElement.validate(this._inputElementValue); |
7173 } else { | 5221 } else { |
7174 valid = inputElement.checkValidity(); | 5222 valid = inputElement.checkValidity(); |
7175 } | 5223 } |
7176 this.invalid = !valid; | 5224 this.invalid = !valid; |
7177 } | 5225 } |
7178 | 5226 |
7179 // Call this last to notify the add-ons. | |
7180 this._handleValue(inputElement); | 5227 this._handleValue(inputElement); |
7181 }, | 5228 }, |
7182 | 5229 |
7183 _onIronInputValidate: function(event) { | 5230 _onIronInputValidate: function(event) { |
7184 this.invalid = this._inputElement.invalid; | 5231 this.invalid = this._inputElement.invalid; |
7185 }, | 5232 }, |
7186 | 5233 |
7187 _invalidChanged: function() { | 5234 _invalidChanged: function() { |
7188 if (this._addons) { | 5235 if (this._addons) { |
7189 this.updateAddons({invalid: this.invalid}); | 5236 this.updateAddons({invalid: this.invalid}); |
7190 } | 5237 } |
7191 }, | 5238 }, |
7192 | 5239 |
7193 /** | |
7194 * Call this to update the state of add-ons. | |
7195 * @param {Object} state Add-on state. | |
7196 */ | |
7197 updateAddons: function(state) { | 5240 updateAddons: function(state) { |
7198 for (var addon, index = 0; addon = this._addons[index]; index++) { | 5241 for (var addon, index = 0; addon = this._addons[index]; index++) { |
7199 addon.update(state); | 5242 addon.update(state); |
7200 } | 5243 } |
7201 }, | 5244 }, |
7202 | 5245 |
7203 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused,
invalid, _inputHasContent) { | 5246 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused,
invalid, _inputHasContent) { |
7204 var cls = 'input-content'; | 5247 var cls = 'input-content'; |
7205 if (!noLabelFloat) { | 5248 if (!noLabelFloat) { |
7206 var label = this.querySelector('label'); | 5249 var label = this.querySelector('label'); |
7207 | 5250 |
7208 if (alwaysFloatLabel || _inputHasContent) { | 5251 if (alwaysFloatLabel || _inputHasContent) { |
7209 cls += ' label-is-floating'; | 5252 cls += ' label-is-floating'; |
7210 // If the label is floating, ignore any offsets that may have been | |
7211 // applied from a prefix element. | |
7212 this.$.labelAndInputContainer.style.position = 'static'; | 5253 this.$.labelAndInputContainer.style.position = 'static'; |
7213 | 5254 |
7214 if (invalid) { | 5255 if (invalid) { |
7215 cls += ' is-invalid'; | 5256 cls += ' is-invalid'; |
7216 } else if (focused) { | 5257 } else if (focused) { |
7217 cls += " label-is-highlighted"; | 5258 cls += " label-is-highlighted"; |
7218 } | 5259 } |
7219 } else { | 5260 } else { |
7220 // When the label is not floating, it should overlap the input element
. | |
7221 if (label) { | 5261 if (label) { |
7222 this.$.labelAndInputContainer.style.position = 'relative'; | 5262 this.$.labelAndInputContainer.style.position = 'relative'; |
7223 } | 5263 } |
7224 } | 5264 } |
7225 } else { | 5265 } else { |
7226 if (_inputHasContent) { | 5266 if (_inputHasContent) { |
7227 cls += ' label-is-hidden'; | 5267 cls += ' label-is-hidden'; |
7228 } | 5268 } |
7229 } | 5269 } |
7230 return cls; | 5270 return cls; |
(...skipping 21 matching lines...) Expand all Loading... |
7252 }); | 5292 }); |
7253 /** @polymerBehavior */ | 5293 /** @polymerBehavior */ |
7254 Polymer.PaperSpinnerBehavior = { | 5294 Polymer.PaperSpinnerBehavior = { |
7255 | 5295 |
7256 listeners: { | 5296 listeners: { |
7257 'animationend': '__reset', | 5297 'animationend': '__reset', |
7258 'webkitAnimationEnd': '__reset' | 5298 'webkitAnimationEnd': '__reset' |
7259 }, | 5299 }, |
7260 | 5300 |
7261 properties: { | 5301 properties: { |
7262 /** | |
7263 * Displays the spinner. | |
7264 */ | |
7265 active: { | 5302 active: { |
7266 type: Boolean, | 5303 type: Boolean, |
7267 value: false, | 5304 value: false, |
7268 reflectToAttribute: true, | 5305 reflectToAttribute: true, |
7269 observer: '__activeChanged' | 5306 observer: '__activeChanged' |
7270 }, | 5307 }, |
7271 | 5308 |
7272 /** | |
7273 * Alternative text content for accessibility support. | |
7274 * If alt is present, it will add an aria-label whose content matches alt
when active. | |
7275 * If alt is not present, it will default to 'loading' as the alt value. | |
7276 */ | |
7277 alt: { | 5309 alt: { |
7278 type: String, | 5310 type: String, |
7279 value: 'loading', | 5311 value: 'loading', |
7280 observer: '__altChanged' | 5312 observer: '__altChanged' |
7281 }, | 5313 }, |
7282 | 5314 |
7283 __coolingDown: { | 5315 __coolingDown: { |
7284 type: Boolean, | 5316 type: Boolean, |
7285 value: false | 5317 value: false |
7286 } | 5318 } |
7287 }, | 5319 }, |
7288 | 5320 |
7289 __computeContainerClasses: function(active, coolingDown) { | 5321 __computeContainerClasses: function(active, coolingDown) { |
7290 return [ | 5322 return [ |
7291 active || coolingDown ? 'active' : '', | 5323 active || coolingDown ? 'active' : '', |
7292 coolingDown ? 'cooldown' : '' | 5324 coolingDown ? 'cooldown' : '' |
7293 ].join(' '); | 5325 ].join(' '); |
7294 }, | 5326 }, |
7295 | 5327 |
7296 __activeChanged: function(active, old) { | 5328 __activeChanged: function(active, old) { |
7297 this.__setAriaHidden(!active); | 5329 this.__setAriaHidden(!active); |
7298 this.__coolingDown = !active && old; | 5330 this.__coolingDown = !active && old; |
7299 }, | 5331 }, |
7300 | 5332 |
7301 __altChanged: function(alt) { | 5333 __altChanged: function(alt) { |
7302 // user-provided `aria-label` takes precedence over prototype default | |
7303 if (alt === this.getPropertyInfo('alt').value) { | 5334 if (alt === this.getPropertyInfo('alt').value) { |
7304 this.alt = this.getAttribute('aria-label') || alt; | 5335 this.alt = this.getAttribute('aria-label') || alt; |
7305 } else { | 5336 } else { |
7306 this.__setAriaHidden(alt===''); | 5337 this.__setAriaHidden(alt===''); |
7307 this.setAttribute('aria-label', alt); | 5338 this.setAttribute('aria-label', alt); |
7308 } | 5339 } |
7309 }, | 5340 }, |
7310 | 5341 |
7311 __setAriaHidden: function(hidden) { | 5342 __setAriaHidden: function(hidden) { |
7312 var attr = 'aria-hidden'; | 5343 var attr = 'aria-hidden'; |
(...skipping 13 matching lines...) Expand all Loading... |
7326 is: 'paper-spinner-lite', | 5357 is: 'paper-spinner-lite', |
7327 | 5358 |
7328 behaviors: [ | 5359 behaviors: [ |
7329 Polymer.PaperSpinnerBehavior | 5360 Polymer.PaperSpinnerBehavior |
7330 ] | 5361 ] |
7331 }); | 5362 }); |
7332 // Copyright 2016 The Chromium Authors. All rights reserved. | 5363 // Copyright 2016 The Chromium Authors. All rights reserved. |
7333 // Use of this source code is governed by a BSD-style license that can be | 5364 // Use of this source code is governed by a BSD-style license that can be |
7334 // found in the LICENSE file. | 5365 // found in the LICENSE file. |
7335 | 5366 |
7336 /** | |
7337 * Implements an incremental search field which can be shown and hidden. | |
7338 * Canonical implementation is <cr-search-field>. | |
7339 * @polymerBehavior | |
7340 */ | |
7341 var CrSearchFieldBehavior = { | 5367 var CrSearchFieldBehavior = { |
7342 properties: { | 5368 properties: { |
7343 label: { | 5369 label: { |
7344 type: String, | 5370 type: String, |
7345 value: '', | 5371 value: '', |
7346 }, | 5372 }, |
7347 | 5373 |
7348 clearLabel: { | 5374 clearLabel: { |
7349 type: String, | 5375 type: String, |
7350 value: '', | 5376 value: '', |
7351 }, | 5377 }, |
7352 | 5378 |
7353 showingSearch: { | 5379 showingSearch: { |
7354 type: Boolean, | 5380 type: Boolean, |
7355 value: false, | 5381 value: false, |
7356 notify: true, | 5382 notify: true, |
7357 observer: 'showingSearchChanged_', | 5383 observer: 'showingSearchChanged_', |
7358 reflectToAttribute: true | 5384 reflectToAttribute: true |
7359 }, | 5385 }, |
7360 | 5386 |
7361 /** @private */ | 5387 /** @private */ |
7362 lastValue_: { | 5388 lastValue_: { |
7363 type: String, | 5389 type: String, |
7364 value: '', | 5390 value: '', |
7365 }, | 5391 }, |
7366 }, | 5392 }, |
7367 | 5393 |
7368 /** | |
7369 * @abstract | |
7370 * @return {!HTMLInputElement} The input field element the behavior should | |
7371 * use. | |
7372 */ | |
7373 getSearchInput: function() {}, | 5394 getSearchInput: function() {}, |
7374 | 5395 |
7375 /** | |
7376 * @return {string} The value of the search field. | |
7377 */ | |
7378 getValue: function() { | 5396 getValue: function() { |
7379 return this.getSearchInput().value; | 5397 return this.getSearchInput().value; |
7380 }, | 5398 }, |
7381 | 5399 |
7382 /** | |
7383 * Sets the value of the search field. | |
7384 * @param {string} value | |
7385 */ | |
7386 setValue: function(value) { | 5400 setValue: function(value) { |
7387 // Use bindValue when setting the input value so that changes propagate | |
7388 // correctly. | |
7389 this.getSearchInput().bindValue = value; | 5401 this.getSearchInput().bindValue = value; |
7390 this.onValueChanged_(value); | 5402 this.onValueChanged_(value); |
7391 }, | 5403 }, |
7392 | 5404 |
7393 showAndFocus: function() { | 5405 showAndFocus: function() { |
7394 this.showingSearch = true; | 5406 this.showingSearch = true; |
7395 this.focus_(); | 5407 this.focus_(); |
7396 }, | 5408 }, |
7397 | 5409 |
7398 /** @private */ | 5410 /** @private */ |
7399 focus_: function() { | 5411 focus_: function() { |
7400 this.getSearchInput().focus(); | 5412 this.getSearchInput().focus(); |
7401 }, | 5413 }, |
7402 | 5414 |
7403 onSearchTermSearch: function() { | 5415 onSearchTermSearch: function() { |
7404 this.onValueChanged_(this.getValue()); | 5416 this.onValueChanged_(this.getValue()); |
7405 }, | 5417 }, |
7406 | 5418 |
7407 /** | |
7408 * Updates the internal state of the search field based on a change that has | |
7409 * already happened. | |
7410 * @param {string} newValue | |
7411 * @private | |
7412 */ | |
7413 onValueChanged_: function(newValue) { | 5419 onValueChanged_: function(newValue) { |
7414 if (newValue == this.lastValue_) | 5420 if (newValue == this.lastValue_) |
7415 return; | 5421 return; |
7416 | 5422 |
7417 this.fire('search-changed', newValue); | 5423 this.fire('search-changed', newValue); |
7418 this.lastValue_ = newValue; | 5424 this.lastValue_ = newValue; |
7419 }, | 5425 }, |
7420 | 5426 |
7421 onSearchTermKeydown: function(e) { | 5427 onSearchTermKeydown: function(e) { |
7422 if (e.key == 'Escape') | 5428 if (e.key == 'Escape') |
7423 this.showingSearch = false; | 5429 this.showingSearch = false; |
7424 }, | 5430 }, |
7425 | 5431 |
7426 /** @private */ | 5432 /** @private */ |
7427 showingSearchChanged_: function() { | 5433 showingSearchChanged_: function() { |
7428 if (this.showingSearch) { | 5434 if (this.showingSearch) { |
7429 this.focus_(); | 5435 this.focus_(); |
7430 return; | 5436 return; |
7431 } | 5437 } |
7432 | 5438 |
7433 this.setValue(''); | 5439 this.setValue(''); |
7434 this.getSearchInput().blur(); | 5440 this.getSearchInput().blur(); |
7435 } | 5441 } |
7436 }; | 5442 }; |
7437 // Copyright 2016 The Chromium Authors. All rights reserved. | 5443 // Copyright 2016 The Chromium Authors. All rights reserved. |
7438 // Use of this source code is governed by a BSD-style license that can be | 5444 // Use of this source code is governed by a BSD-style license that can be |
7439 // found in the LICENSE file. | 5445 // found in the LICENSE file. |
7440 | 5446 |
7441 // TODO(tsergeant): Add tests for cr-toolbar-search-field. | |
7442 Polymer({ | 5447 Polymer({ |
7443 is: 'cr-toolbar-search-field', | 5448 is: 'cr-toolbar-search-field', |
7444 | 5449 |
7445 behaviors: [CrSearchFieldBehavior], | 5450 behaviors: [CrSearchFieldBehavior], |
7446 | 5451 |
7447 properties: { | 5452 properties: { |
7448 narrow: { | 5453 narrow: { |
7449 type: Boolean, | 5454 type: Boolean, |
7450 reflectToAttribute: true, | 5455 reflectToAttribute: true, |
7451 }, | 5456 }, |
7452 | 5457 |
7453 // Prompt text to display in the search field. | |
7454 label: String, | 5458 label: String, |
7455 | 5459 |
7456 // Tooltip to display on the clear search button. | |
7457 clearLabel: String, | 5460 clearLabel: String, |
7458 | 5461 |
7459 // When true, show a loading spinner to indicate that the backend is | |
7460 // processing the search. Will only show if the search field is open. | |
7461 spinnerActive: { | 5462 spinnerActive: { |
7462 type: Boolean, | 5463 type: Boolean, |
7463 reflectToAttribute: true | 5464 reflectToAttribute: true |
7464 }, | 5465 }, |
7465 | 5466 |
7466 /** @private */ | 5467 /** @private */ |
7467 hasSearchText_: Boolean, | 5468 hasSearchText_: Boolean, |
7468 }, | 5469 }, |
7469 | 5470 |
7470 listeners: { | 5471 listeners: { |
7471 'tap': 'showSearch_', | 5472 'tap': 'showSearch_', |
7472 'searchInput.bind-value-changed': 'onBindValueChanged_', | 5473 'searchInput.bind-value-changed': 'onBindValueChanged_', |
7473 }, | 5474 }, |
7474 | 5475 |
7475 /** @return {!HTMLInputElement} */ | 5476 /** @return {!HTMLInputElement} */ |
7476 getSearchInput: function() { | 5477 getSearchInput: function() { |
7477 return this.$.searchInput; | 5478 return this.$.searchInput; |
7478 }, | 5479 }, |
7479 | 5480 |
7480 /** @return {boolean} */ | 5481 /** @return {boolean} */ |
7481 isSearchFocused: function() { | 5482 isSearchFocused: function() { |
7482 return this.$.searchTerm.focused; | 5483 return this.$.searchTerm.focused; |
7483 }, | 5484 }, |
7484 | 5485 |
7485 /** | |
7486 * @param {boolean} narrow | |
7487 * @return {number} | |
7488 * @private | |
7489 */ | |
7490 computeIconTabIndex_: function(narrow) { | 5486 computeIconTabIndex_: function(narrow) { |
7491 return narrow ? 0 : -1; | 5487 return narrow ? 0 : -1; |
7492 }, | 5488 }, |
7493 | 5489 |
7494 /** | |
7495 * @param {boolean} spinnerActive | |
7496 * @param {boolean} showingSearch | |
7497 * @return {boolean} | |
7498 * @private | |
7499 */ | |
7500 isSpinnerShown_: function(spinnerActive, showingSearch) { | 5490 isSpinnerShown_: function(spinnerActive, showingSearch) { |
7501 return spinnerActive && showingSearch; | 5491 return spinnerActive && showingSearch; |
7502 }, | 5492 }, |
7503 | 5493 |
7504 /** @private */ | 5494 /** @private */ |
7505 onInputBlur_: function() { | 5495 onInputBlur_: function() { |
7506 if (!this.hasSearchText_) | 5496 if (!this.hasSearchText_) |
7507 this.showingSearch = false; | 5497 this.showingSearch = false; |
7508 }, | 5498 }, |
7509 | 5499 |
7510 /** | |
7511 * Update the state of the search field whenever the underlying input value | |
7512 * changes. Unlike onsearch or onkeypress, this is reliably called immediately | |
7513 * after any change, whether the result of user input or JS modification. | |
7514 * @private | |
7515 */ | |
7516 onBindValueChanged_: function() { | 5500 onBindValueChanged_: function() { |
7517 var newValue = this.$.searchInput.bindValue; | 5501 var newValue = this.$.searchInput.bindValue; |
7518 this.hasSearchText_ = newValue != ''; | 5502 this.hasSearchText_ = newValue != ''; |
7519 if (newValue != '') | 5503 if (newValue != '') |
7520 this.showingSearch = true; | 5504 this.showingSearch = true; |
7521 }, | 5505 }, |
7522 | 5506 |
7523 /** | |
7524 * @param {Event} e | |
7525 * @private | |
7526 */ | |
7527 showSearch_: function(e) { | 5507 showSearch_: function(e) { |
7528 if (e.target != this.$.clearSearch) | 5508 if (e.target != this.$.clearSearch) |
7529 this.showingSearch = true; | 5509 this.showingSearch = true; |
7530 }, | 5510 }, |
7531 | 5511 |
7532 /** | |
7533 * @param {Event} e | |
7534 * @private | |
7535 */ | |
7536 hideSearch_: function(e) { | 5512 hideSearch_: function(e) { |
7537 this.showingSearch = false; | 5513 this.showingSearch = false; |
7538 e.stopPropagation(); | 5514 e.stopPropagation(); |
7539 } | 5515 } |
7540 }); | 5516 }); |
7541 // Copyright 2016 The Chromium Authors. All rights reserved. | 5517 // Copyright 2016 The Chromium Authors. All rights reserved. |
7542 // Use of this source code is governed by a BSD-style license that can be | 5518 // Use of this source code is governed by a BSD-style license that can be |
7543 // found in the LICENSE file. | 5519 // found in the LICENSE file. |
7544 | 5520 |
7545 Polymer({ | 5521 Polymer({ |
7546 is: 'cr-toolbar', | 5522 is: 'cr-toolbar', |
7547 | 5523 |
7548 properties: { | 5524 properties: { |
7549 // Name to display in the toolbar, in titlecase. | |
7550 pageName: String, | 5525 pageName: String, |
7551 | 5526 |
7552 // Prompt text to display in the search field. | |
7553 searchPrompt: String, | 5527 searchPrompt: String, |
7554 | 5528 |
7555 // Tooltip to display on the clear search button. | |
7556 clearLabel: String, | 5529 clearLabel: String, |
7557 | 5530 |
7558 // Value is proxied through to cr-toolbar-search-field. When true, | |
7559 // the search field will show a processing spinner. | |
7560 spinnerActive: Boolean, | 5531 spinnerActive: Boolean, |
7561 | 5532 |
7562 // Controls whether the menu button is shown at the start of the menu. | |
7563 showMenu: { | 5533 showMenu: { |
7564 type: Boolean, | 5534 type: Boolean, |
7565 reflectToAttribute: true, | 5535 reflectToAttribute: true, |
7566 value: true | 5536 value: true |
7567 }, | 5537 }, |
7568 | 5538 |
7569 /** @private */ | 5539 /** @private */ |
7570 narrow_: { | 5540 narrow_: { |
7571 type: Boolean, | 5541 type: Boolean, |
7572 reflectToAttribute: true | 5542 reflectToAttribute: true |
(...skipping 16 matching lines...) Expand all Loading... |
7589 this.fire('cr-menu-tap'); | 5559 this.fire('cr-menu-tap'); |
7590 } | 5560 } |
7591 }); | 5561 }); |
7592 // Copyright 2015 The Chromium Authors. All rights reserved. | 5562 // Copyright 2015 The Chromium Authors. All rights reserved. |
7593 // Use of this source code is governed by a BSD-style license that can be | 5563 // Use of this source code is governed by a BSD-style license that can be |
7594 // found in the LICENSE file. | 5564 // found in the LICENSE file. |
7595 | 5565 |
7596 Polymer({ | 5566 Polymer({ |
7597 is: 'history-toolbar', | 5567 is: 'history-toolbar', |
7598 properties: { | 5568 properties: { |
7599 // Number of history items currently selected. | |
7600 // TODO(calamity): bind this to | |
7601 // listContainer.selectedItem.selectedPaths.length. | |
7602 count: { | 5569 count: { |
7603 type: Number, | 5570 type: Number, |
7604 value: 0, | 5571 value: 0, |
7605 observer: 'changeToolbarView_' | 5572 observer: 'changeToolbarView_' |
7606 }, | 5573 }, |
7607 | 5574 |
7608 // True if 1 or more history items are selected. When this value changes | |
7609 // the background colour changes. | |
7610 itemsSelected_: { | 5575 itemsSelected_: { |
7611 type: Boolean, | 5576 type: Boolean, |
7612 value: false, | 5577 value: false, |
7613 reflectToAttribute: true | 5578 reflectToAttribute: true |
7614 }, | 5579 }, |
7615 | 5580 |
7616 // The most recent term entered in the search field. Updated incrementally | |
7617 // as the user types. | |
7618 searchTerm: { | 5581 searchTerm: { |
7619 type: String, | 5582 type: String, |
7620 notify: true, | 5583 notify: true, |
7621 }, | 5584 }, |
7622 | 5585 |
7623 // True if the backend is processing and a spinner should be shown in the | |
7624 // toolbar. | |
7625 spinnerActive: { | 5586 spinnerActive: { |
7626 type: Boolean, | 5587 type: Boolean, |
7627 value: false | 5588 value: false |
7628 }, | 5589 }, |
7629 | 5590 |
7630 hasDrawer: { | 5591 hasDrawer: { |
7631 type: Boolean, | 5592 type: Boolean, |
7632 observer: 'hasDrawerChanged_', | 5593 observer: 'hasDrawerChanged_', |
7633 reflectToAttribute: true, | 5594 reflectToAttribute: true, |
7634 }, | 5595 }, |
7635 | 5596 |
7636 // Whether domain-grouped history is enabled. | |
7637 isGroupedMode: { | 5597 isGroupedMode: { |
7638 type: Boolean, | 5598 type: Boolean, |
7639 reflectToAttribute: true, | 5599 reflectToAttribute: true, |
7640 }, | 5600 }, |
7641 | 5601 |
7642 // The period to search over. Matches BrowsingHistoryHandler::Range. | |
7643 groupedRange: { | 5602 groupedRange: { |
7644 type: Number, | 5603 type: Number, |
7645 value: 0, | 5604 value: 0, |
7646 reflectToAttribute: true, | 5605 reflectToAttribute: true, |
7647 notify: true | 5606 notify: true |
7648 }, | 5607 }, |
7649 | 5608 |
7650 // The start time of the query range. | |
7651 queryStartTime: String, | 5609 queryStartTime: String, |
7652 | 5610 |
7653 // The end time of the query range. | |
7654 queryEndTime: String, | 5611 queryEndTime: String, |
7655 }, | 5612 }, |
7656 | 5613 |
7657 /** | |
7658 * Changes the toolbar background color depending on whether any history items | |
7659 * are currently selected. | |
7660 * @private | |
7661 */ | |
7662 changeToolbarView_: function() { | 5614 changeToolbarView_: function() { |
7663 this.itemsSelected_ = this.count > 0; | 5615 this.itemsSelected_ = this.count > 0; |
7664 }, | 5616 }, |
7665 | 5617 |
7666 /** | |
7667 * When changing the search term externally, update the search field to | |
7668 * reflect the new search term. | |
7669 * @param {string} search | |
7670 */ | |
7671 setSearchTerm: function(search) { | 5618 setSearchTerm: function(search) { |
7672 if (this.searchTerm == search) | 5619 if (this.searchTerm == search) |
7673 return; | 5620 return; |
7674 | 5621 |
7675 this.searchTerm = search; | 5622 this.searchTerm = search; |
7676 var searchField = /** @type {!CrToolbarElement} */(this.$['main-toolbar']) | 5623 var searchField = /** @type {!CrToolbarElement} */(this.$['main-toolbar']) |
7677 .getSearchField(); | 5624 .getSearchField(); |
7678 searchField.showAndFocus(); | 5625 searchField.showAndFocus(); |
7679 searchField.setValue(search); | 5626 searchField.setValue(search); |
7680 }, | 5627 }, |
7681 | 5628 |
7682 /** | |
7683 * @param {!CustomEvent} event | |
7684 * @private | |
7685 */ | |
7686 onSearchChanged_: function(event) { | 5629 onSearchChanged_: function(event) { |
7687 this.searchTerm = /** @type {string} */ (event.detail); | 5630 this.searchTerm = /** @type {string} */ (event.detail); |
7688 }, | 5631 }, |
7689 | 5632 |
7690 onClearSelectionTap_: function() { | 5633 onClearSelectionTap_: function() { |
7691 this.fire('unselect-all'); | 5634 this.fire('unselect-all'); |
7692 }, | 5635 }, |
7693 | 5636 |
7694 onDeleteTap_: function() { | 5637 onDeleteTap_: function() { |
7695 this.fire('delete-selected'); | 5638 this.fire('delete-selected'); |
7696 }, | 5639 }, |
7697 | 5640 |
7698 get searchBar() { | 5641 get searchBar() { |
7699 return this.$['main-toolbar'].getSearchField(); | 5642 return this.$['main-toolbar'].getSearchField(); |
7700 }, | 5643 }, |
7701 | 5644 |
7702 showSearchField: function() { | 5645 showSearchField: function() { |
7703 /** @type {!CrToolbarElement} */(this.$['main-toolbar']) | 5646 /** @type {!CrToolbarElement} */(this.$['main-toolbar']) |
7704 .getSearchField() | 5647 .getSearchField() |
7705 .showAndFocus(); | 5648 .showAndFocus(); |
7706 }, | 5649 }, |
7707 | 5650 |
7708 /** | |
7709 * If the user is a supervised user the delete button is not shown. | |
7710 * @private | |
7711 */ | |
7712 deletingAllowed_: function() { | 5651 deletingAllowed_: function() { |
7713 return loadTimeData.getBoolean('allowDeletingHistory'); | 5652 return loadTimeData.getBoolean('allowDeletingHistory'); |
7714 }, | 5653 }, |
7715 | 5654 |
7716 numberOfItemsSelected_: function(count) { | 5655 numberOfItemsSelected_: function(count) { |
7717 return count > 0 ? loadTimeData.getStringF('itemsSelected', count) : ''; | 5656 return count > 0 ? loadTimeData.getStringF('itemsSelected', count) : ''; |
7718 }, | 5657 }, |
7719 | 5658 |
7720 getHistoryInterval_: function(queryStartTime, queryEndTime) { | 5659 getHistoryInterval_: function(queryStartTime, queryEndTime) { |
7721 // TODO(calamity): Fix the format of these dates. | |
7722 return loadTimeData.getStringF( | 5660 return loadTimeData.getStringF( |
7723 'historyInterval', queryStartTime, queryEndTime); | 5661 'historyInterval', queryStartTime, queryEndTime); |
7724 }, | 5662 }, |
7725 | 5663 |
7726 /** @private */ | 5664 /** @private */ |
7727 hasDrawerChanged_: function() { | 5665 hasDrawerChanged_: function() { |
7728 this.updateStyles(); | 5666 this.updateStyles(); |
7729 }, | 5667 }, |
7730 }); | 5668 }); |
7731 // Copyright 2016 The Chromium Authors. All rights reserved. | 5669 // Copyright 2016 The Chromium Authors. All rights reserved. |
7732 // Use of this source code is governed by a BSD-style license that can be | 5670 // Use of this source code is governed by a BSD-style license that can be |
7733 // found in the LICENSE file. | 5671 // found in the LICENSE file. |
7734 | 5672 |
7735 /** | |
7736 * @fileoverview 'cr-dialog' is a component for showing a modal dialog. If the | |
7737 * dialog is closed via close(), a 'close' event is fired. If the dialog is | |
7738 * canceled via cancel(), a 'cancel' event is fired followed by a 'close' event. | |
7739 * Additionally clients can inspect the dialog's |returnValue| property inside | |
7740 * the 'close' event listener to determine whether it was canceled or just | |
7741 * closed, where a truthy value means success, and a falsy value means it was | |
7742 * canceled. | |
7743 */ | |
7744 Polymer({ | 5673 Polymer({ |
7745 is: 'cr-dialog', | 5674 is: 'cr-dialog', |
7746 extends: 'dialog', | 5675 extends: 'dialog', |
7747 | 5676 |
7748 cancel: function() { | 5677 cancel: function() { |
7749 this.fire('cancel'); | 5678 this.fire('cancel'); |
7750 HTMLDialogElement.prototype.close.call(this, ''); | 5679 HTMLDialogElement.prototype.close.call(this, ''); |
7751 }, | 5680 }, |
7752 | 5681 |
7753 /** | |
7754 * @param {string=} opt_returnValue | |
7755 * @override | |
7756 */ | |
7757 close: function(opt_returnValue) { | 5682 close: function(opt_returnValue) { |
7758 HTMLDialogElement.prototype.close.call(this, 'success'); | 5683 HTMLDialogElement.prototype.close.call(this, 'success'); |
7759 }, | 5684 }, |
7760 | 5685 |
7761 /** @return {!PaperIconButtonElement} */ | 5686 /** @return {!PaperIconButtonElement} */ |
7762 getCloseButton: function() { | 5687 getCloseButton: function() { |
7763 return this.$.close; | 5688 return this.$.close; |
7764 }, | 5689 }, |
7765 }); | 5690 }); |
7766 /** | |
7767 `Polymer.IronFitBehavior` fits an element in another element using `max-height`
and `max-width`, and | |
7768 optionally centers it in the window or another element. | |
7769 | |
7770 The element will only be sized and/or positioned if it has not already been size
d and/or positioned | |
7771 by CSS. | |
7772 | |
7773 CSS properties | Action | |
7774 -----------------------------|------------------------------------------- | |
7775 `position` set | Element is not centered horizontally or verticall
y | |
7776 `top` or `bottom` set | Element is not vertically centered | |
7777 `left` or `right` set | Element is not horizontally centered | |
7778 `max-height` set | Element respects `max-height` | |
7779 `max-width` set | Element respects `max-width` | |
7780 | |
7781 `Polymer.IronFitBehavior` can position an element into another element using | |
7782 `verticalAlign` and `horizontalAlign`. This will override the element's css posi
tion. | |
7783 | |
7784 <div class="container"> | |
7785 <iron-fit-impl vertical-align="top" horizontal-align="auto"> | |
7786 Positioned into the container | |
7787 </iron-fit-impl> | |
7788 </div> | |
7789 | |
7790 Use `noOverlap` to position the element around another element without overlappi
ng it. | |
7791 | |
7792 <div class="container"> | |
7793 <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto"> | |
7794 Positioned around the container | |
7795 </iron-fit-impl> | |
7796 </div> | |
7797 | |
7798 @demo demo/index.html | |
7799 @polymerBehavior | |
7800 */ | |
7801 | 5691 |
7802 Polymer.IronFitBehavior = { | 5692 Polymer.IronFitBehavior = { |
7803 | 5693 |
7804 properties: { | 5694 properties: { |
7805 | 5695 |
7806 /** | |
7807 * The element that will receive a `max-height`/`width`. By default it is
the same as `this`, | |
7808 * but it can be set to a child element. This is useful, for example, for
implementing a | |
7809 * scrolling region inside the element. | |
7810 * @type {!Element} | |
7811 */ | |
7812 sizingTarget: { | 5696 sizingTarget: { |
7813 type: Object, | 5697 type: Object, |
7814 value: function() { | 5698 value: function() { |
7815 return this; | 5699 return this; |
7816 } | 5700 } |
7817 }, | 5701 }, |
7818 | 5702 |
7819 /** | |
7820 * The element to fit `this` into. | |
7821 */ | |
7822 fitInto: { | 5703 fitInto: { |
7823 type: Object, | 5704 type: Object, |
7824 value: window | 5705 value: window |
7825 }, | 5706 }, |
7826 | 5707 |
7827 /** | |
7828 * Will position the element around the positionTarget without overlapping
it. | |
7829 */ | |
7830 noOverlap: { | 5708 noOverlap: { |
7831 type: Boolean | 5709 type: Boolean |
7832 }, | 5710 }, |
7833 | 5711 |
7834 /** | |
7835 * The element that should be used to position the element. If not set, it
will | |
7836 * default to the parent node. | |
7837 * @type {!Element} | |
7838 */ | |
7839 positionTarget: { | 5712 positionTarget: { |
7840 type: Element | 5713 type: Element |
7841 }, | 5714 }, |
7842 | 5715 |
7843 /** | |
7844 * The orientation against which to align the element horizontally | |
7845 * relative to the `positionTarget`. Possible values are "left", "right",
"auto". | |
7846 */ | |
7847 horizontalAlign: { | 5716 horizontalAlign: { |
7848 type: String | 5717 type: String |
7849 }, | 5718 }, |
7850 | 5719 |
7851 /** | |
7852 * The orientation against which to align the element vertically | |
7853 * relative to the `positionTarget`. Possible values are "top", "bottom",
"auto". | |
7854 */ | |
7855 verticalAlign: { | 5720 verticalAlign: { |
7856 type: String | 5721 type: String |
7857 }, | 5722 }, |
7858 | 5723 |
7859 /** | |
7860 * If true, it will use `horizontalAlign` and `verticalAlign` values as pr
eferred alignment | |
7861 * and if there's not enough space, it will pick the values which minimize
the cropping. | |
7862 */ | |
7863 dynamicAlign: { | 5724 dynamicAlign: { |
7864 type: Boolean | 5725 type: Boolean |
7865 }, | 5726 }, |
7866 | 5727 |
7867 /** | |
7868 * The same as setting margin-left and margin-right css properties. | |
7869 * @deprecated | |
7870 */ | |
7871 horizontalOffset: { | 5728 horizontalOffset: { |
7872 type: Number, | 5729 type: Number, |
7873 value: 0, | 5730 value: 0, |
7874 notify: true | 5731 notify: true |
7875 }, | 5732 }, |
7876 | 5733 |
7877 /** | |
7878 * The same as setting margin-top and margin-bottom css properties. | |
7879 * @deprecated | |
7880 */ | |
7881 verticalOffset: { | 5734 verticalOffset: { |
7882 type: Number, | 5735 type: Number, |
7883 value: 0, | 5736 value: 0, |
7884 notify: true | 5737 notify: true |
7885 }, | 5738 }, |
7886 | 5739 |
7887 /** | |
7888 * Set to true to auto-fit on attach. | |
7889 */ | |
7890 autoFitOnAttach: { | 5740 autoFitOnAttach: { |
7891 type: Boolean, | 5741 type: Boolean, |
7892 value: false | 5742 value: false |
7893 }, | 5743 }, |
7894 | 5744 |
7895 /** @type {?Object} */ | 5745 /** @type {?Object} */ |
7896 _fitInfo: { | 5746 _fitInfo: { |
7897 type: Object | 5747 type: Object |
7898 } | 5748 } |
7899 }, | 5749 }, |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
7931 get _fitTop() { | 5781 get _fitTop() { |
7932 var fitTop; | 5782 var fitTop; |
7933 if (this.fitInto === window) { | 5783 if (this.fitInto === window) { |
7934 fitTop = 0; | 5784 fitTop = 0; |
7935 } else { | 5785 } else { |
7936 fitTop = this.fitInto.getBoundingClientRect().top; | 5786 fitTop = this.fitInto.getBoundingClientRect().top; |
7937 } | 5787 } |
7938 return fitTop; | 5788 return fitTop; |
7939 }, | 5789 }, |
7940 | 5790 |
7941 /** | |
7942 * The element that should be used to position the element, | |
7943 * if no position target is configured. | |
7944 */ | |
7945 get _defaultPositionTarget() { | 5791 get _defaultPositionTarget() { |
7946 var parent = Polymer.dom(this).parentNode; | 5792 var parent = Polymer.dom(this).parentNode; |
7947 | 5793 |
7948 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | 5794 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
7949 parent = parent.host; | 5795 parent = parent.host; |
7950 } | 5796 } |
7951 | 5797 |
7952 return parent; | 5798 return parent; |
7953 }, | 5799 }, |
7954 | 5800 |
7955 /** | |
7956 * The horizontal align value, accounting for the RTL/LTR text direction. | |
7957 */ | |
7958 get _localeHorizontalAlign() { | 5801 get _localeHorizontalAlign() { |
7959 if (this._isRTL) { | 5802 if (this._isRTL) { |
7960 // In RTL, "left" becomes "right". | |
7961 if (this.horizontalAlign === 'right') { | 5803 if (this.horizontalAlign === 'right') { |
7962 return 'left'; | 5804 return 'left'; |
7963 } | 5805 } |
7964 if (this.horizontalAlign === 'left') { | 5806 if (this.horizontalAlign === 'left') { |
7965 return 'right'; | 5807 return 'right'; |
7966 } | 5808 } |
7967 } | 5809 } |
7968 return this.horizontalAlign; | 5810 return this.horizontalAlign; |
7969 }, | 5811 }, |
7970 | 5812 |
7971 attached: function() { | 5813 attached: function() { |
7972 // Memoize this to avoid expensive calculations & relayouts. | |
7973 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; | 5814 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; |
7974 this.positionTarget = this.positionTarget || this._defaultPositionTarget; | 5815 this.positionTarget = this.positionTarget || this._defaultPositionTarget; |
7975 if (this.autoFitOnAttach) { | 5816 if (this.autoFitOnAttach) { |
7976 if (window.getComputedStyle(this).display === 'none') { | 5817 if (window.getComputedStyle(this).display === 'none') { |
7977 setTimeout(function() { | 5818 setTimeout(function() { |
7978 this.fit(); | 5819 this.fit(); |
7979 }.bind(this)); | 5820 }.bind(this)); |
7980 } else { | 5821 } else { |
7981 this.fit(); | 5822 this.fit(); |
7982 } | 5823 } |
7983 } | 5824 } |
7984 }, | 5825 }, |
7985 | 5826 |
7986 /** | |
7987 * Positions and fits the element into the `fitInto` element. | |
7988 */ | |
7989 fit: function() { | 5827 fit: function() { |
7990 this.position(); | 5828 this.position(); |
7991 this.constrain(); | 5829 this.constrain(); |
7992 this.center(); | 5830 this.center(); |
7993 }, | 5831 }, |
7994 | 5832 |
7995 /** | |
7996 * Memoize information needed to position and size the target element. | |
7997 * @suppress {deprecated} | |
7998 */ | |
7999 _discoverInfo: function() { | 5833 _discoverInfo: function() { |
8000 if (this._fitInfo) { | 5834 if (this._fitInfo) { |
8001 return; | 5835 return; |
8002 } | 5836 } |
8003 var target = window.getComputedStyle(this); | 5837 var target = window.getComputedStyle(this); |
8004 var sizer = window.getComputedStyle(this.sizingTarget); | 5838 var sizer = window.getComputedStyle(this.sizingTarget); |
8005 | 5839 |
8006 this._fitInfo = { | 5840 this._fitInfo = { |
8007 inlineStyle: { | 5841 inlineStyle: { |
8008 top: this.style.top || '', | 5842 top: this.style.top || '', |
(...skipping 18 matching lines...) Expand all Loading... |
8027 minHeight: parseInt(sizer.minHeight, 10) || 0 | 5861 minHeight: parseInt(sizer.minHeight, 10) || 0 |
8028 }, | 5862 }, |
8029 margin: { | 5863 margin: { |
8030 top: parseInt(target.marginTop, 10) || 0, | 5864 top: parseInt(target.marginTop, 10) || 0, |
8031 right: parseInt(target.marginRight, 10) || 0, | 5865 right: parseInt(target.marginRight, 10) || 0, |
8032 bottom: parseInt(target.marginBottom, 10) || 0, | 5866 bottom: parseInt(target.marginBottom, 10) || 0, |
8033 left: parseInt(target.marginLeft, 10) || 0 | 5867 left: parseInt(target.marginLeft, 10) || 0 |
8034 } | 5868 } |
8035 }; | 5869 }; |
8036 | 5870 |
8037 // Support these properties until they are removed. | |
8038 if (this.verticalOffset) { | 5871 if (this.verticalOffset) { |
8039 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf
fset; | 5872 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf
fset; |
8040 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || ''; | 5873 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || ''; |
8041 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || ''; | 5874 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || ''; |
8042 this.style.marginTop = this.style.marginBottom = this.verticalOffset + '
px'; | 5875 this.style.marginTop = this.style.marginBottom = this.verticalOffset + '
px'; |
8043 } | 5876 } |
8044 if (this.horizontalOffset) { | 5877 if (this.horizontalOffset) { |
8045 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal
Offset; | 5878 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal
Offset; |
8046 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || ''; | 5879 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || ''; |
8047 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || ''; | 5880 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || ''; |
8048 this.style.marginLeft = this.style.marginRight = this.horizontalOffset +
'px'; | 5881 this.style.marginLeft = this.style.marginRight = this.horizontalOffset +
'px'; |
8049 } | 5882 } |
8050 }, | 5883 }, |
8051 | 5884 |
8052 /** | |
8053 * Resets the target element's position and size constraints, and clear | |
8054 * the memoized data. | |
8055 */ | |
8056 resetFit: function() { | 5885 resetFit: function() { |
8057 var info = this._fitInfo || {}; | 5886 var info = this._fitInfo || {}; |
8058 for (var property in info.sizerInlineStyle) { | 5887 for (var property in info.sizerInlineStyle) { |
8059 this.sizingTarget.style[property] = info.sizerInlineStyle[property]; | 5888 this.sizingTarget.style[property] = info.sizerInlineStyle[property]; |
8060 } | 5889 } |
8061 for (var property in info.inlineStyle) { | 5890 for (var property in info.inlineStyle) { |
8062 this.style[property] = info.inlineStyle[property]; | 5891 this.style[property] = info.inlineStyle[property]; |
8063 } | 5892 } |
8064 | 5893 |
8065 this._fitInfo = null; | 5894 this._fitInfo = null; |
8066 }, | 5895 }, |
8067 | 5896 |
8068 /** | |
8069 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after | |
8070 * the element or the `fitInto` element has been resized, or if any of the | |
8071 * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated
. | |
8072 * It preserves the scroll position of the sizingTarget. | |
8073 */ | |
8074 refit: function() { | 5897 refit: function() { |
8075 var scrollLeft = this.sizingTarget.scrollLeft; | 5898 var scrollLeft = this.sizingTarget.scrollLeft; |
8076 var scrollTop = this.sizingTarget.scrollTop; | 5899 var scrollTop = this.sizingTarget.scrollTop; |
8077 this.resetFit(); | 5900 this.resetFit(); |
8078 this.fit(); | 5901 this.fit(); |
8079 this.sizingTarget.scrollLeft = scrollLeft; | 5902 this.sizingTarget.scrollLeft = scrollLeft; |
8080 this.sizingTarget.scrollTop = scrollTop; | 5903 this.sizingTarget.scrollTop = scrollTop; |
8081 }, | 5904 }, |
8082 | 5905 |
8083 /** | |
8084 * Positions the element according to `horizontalAlign, verticalAlign`. | |
8085 */ | |
8086 position: function() { | 5906 position: function() { |
8087 if (!this.horizontalAlign && !this.verticalAlign) { | 5907 if (!this.horizontalAlign && !this.verticalAlign) { |
8088 // needs to be centered, and it is done after constrain. | |
8089 return; | 5908 return; |
8090 } | 5909 } |
8091 this._discoverInfo(); | 5910 this._discoverInfo(); |
8092 | 5911 |
8093 this.style.position = 'fixed'; | 5912 this.style.position = 'fixed'; |
8094 // Need border-box for margin/padding. | |
8095 this.sizingTarget.style.boxSizing = 'border-box'; | 5913 this.sizingTarget.style.boxSizing = 'border-box'; |
8096 // Set to 0, 0 in order to discover any offset caused by parent stacking c
ontexts. | |
8097 this.style.left = '0px'; | 5914 this.style.left = '0px'; |
8098 this.style.top = '0px'; | 5915 this.style.top = '0px'; |
8099 | 5916 |
8100 var rect = this.getBoundingClientRect(); | 5917 var rect = this.getBoundingClientRect(); |
8101 var positionRect = this.__getNormalizedRect(this.positionTarget); | 5918 var positionRect = this.__getNormalizedRect(this.positionTarget); |
8102 var fitRect = this.__getNormalizedRect(this.fitInto); | 5919 var fitRect = this.__getNormalizedRect(this.fitInto); |
8103 | 5920 |
8104 var margin = this._fitInfo.margin; | 5921 var margin = this._fitInfo.margin; |
8105 | 5922 |
8106 // Consider the margin as part of the size for position calculations. | |
8107 var size = { | 5923 var size = { |
8108 width: rect.width + margin.left + margin.right, | 5924 width: rect.width + margin.left + margin.right, |
8109 height: rect.height + margin.top + margin.bottom | 5925 height: rect.height + margin.top + margin.bottom |
8110 }; | 5926 }; |
8111 | 5927 |
8112 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic
alAlign, size, positionRect, fitRect); | 5928 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic
alAlign, size, positionRect, fitRect); |
8113 | 5929 |
8114 var left = position.left + margin.left; | 5930 var left = position.left + margin.left; |
8115 var top = position.top + margin.top; | 5931 var top = position.top + margin.top; |
8116 | 5932 |
8117 // Use original size (without margin). | |
8118 var right = Math.min(fitRect.right - margin.right, left + rect.width); | 5933 var right = Math.min(fitRect.right - margin.right, left + rect.width); |
8119 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height); | 5934 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height); |
8120 | 5935 |
8121 var minWidth = this._fitInfo.sizedBy.minWidth; | 5936 var minWidth = this._fitInfo.sizedBy.minWidth; |
8122 var minHeight = this._fitInfo.sizedBy.minHeight; | 5937 var minHeight = this._fitInfo.sizedBy.minHeight; |
8123 if (left < margin.left) { | 5938 if (left < margin.left) { |
8124 left = margin.left; | 5939 left = margin.left; |
8125 if (right - left < minWidth) { | 5940 if (right - left < minWidth) { |
8126 left = right - minWidth; | 5941 left = right - minWidth; |
8127 } | 5942 } |
8128 } | 5943 } |
8129 if (top < margin.top) { | 5944 if (top < margin.top) { |
8130 top = margin.top; | 5945 top = margin.top; |
8131 if (bottom - top < minHeight) { | 5946 if (bottom - top < minHeight) { |
8132 top = bottom - minHeight; | 5947 top = bottom - minHeight; |
8133 } | 5948 } |
8134 } | 5949 } |
8135 | 5950 |
8136 this.sizingTarget.style.maxWidth = (right - left) + 'px'; | 5951 this.sizingTarget.style.maxWidth = (right - left) + 'px'; |
8137 this.sizingTarget.style.maxHeight = (bottom - top) + 'px'; | 5952 this.sizingTarget.style.maxHeight = (bottom - top) + 'px'; |
8138 | 5953 |
8139 // Remove the offset caused by any stacking context. | |
8140 this.style.left = (left - rect.left) + 'px'; | 5954 this.style.left = (left - rect.left) + 'px'; |
8141 this.style.top = (top - rect.top) + 'px'; | 5955 this.style.top = (top - rect.top) + 'px'; |
8142 }, | 5956 }, |
8143 | 5957 |
8144 /** | |
8145 * Constrains the size of the element to `fitInto` by setting `max-height` | |
8146 * and/or `max-width`. | |
8147 */ | |
8148 constrain: function() { | 5958 constrain: function() { |
8149 if (this.horizontalAlign || this.verticalAlign) { | 5959 if (this.horizontalAlign || this.verticalAlign) { |
8150 return; | 5960 return; |
8151 } | 5961 } |
8152 this._discoverInfo(); | 5962 this._discoverInfo(); |
8153 | 5963 |
8154 var info = this._fitInfo; | 5964 var info = this._fitInfo; |
8155 // position at (0px, 0px) if not already positioned, so we can measure the
natural size. | |
8156 if (!info.positionedBy.vertically) { | 5965 if (!info.positionedBy.vertically) { |
8157 this.style.position = 'fixed'; | 5966 this.style.position = 'fixed'; |
8158 this.style.top = '0px'; | 5967 this.style.top = '0px'; |
8159 } | 5968 } |
8160 if (!info.positionedBy.horizontally) { | 5969 if (!info.positionedBy.horizontally) { |
8161 this.style.position = 'fixed'; | 5970 this.style.position = 'fixed'; |
8162 this.style.left = '0px'; | 5971 this.style.left = '0px'; |
8163 } | 5972 } |
8164 | 5973 |
8165 // need border-box for margin/padding | |
8166 this.sizingTarget.style.boxSizing = 'border-box'; | 5974 this.sizingTarget.style.boxSizing = 'border-box'; |
8167 // constrain the width and height if not already set | |
8168 var rect = this.getBoundingClientRect(); | 5975 var rect = this.getBoundingClientRect(); |
8169 if (!info.sizedBy.height) { | 5976 if (!info.sizedBy.height) { |
8170 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom'
, 'Height'); | 5977 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom'
, 'Height'); |
8171 } | 5978 } |
8172 if (!info.sizedBy.width) { | 5979 if (!info.sizedBy.width) { |
8173 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ
t', 'Width'); | 5980 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ
t', 'Width'); |
8174 } | 5981 } |
8175 }, | 5982 }, |
8176 | 5983 |
8177 /** | |
8178 * @protected | |
8179 * @deprecated | |
8180 */ | |
8181 _sizeDimension: function(rect, positionedBy, start, end, extent) { | 5984 _sizeDimension: function(rect, positionedBy, start, end, extent) { |
8182 this.__sizeDimension(rect, positionedBy, start, end, extent); | 5985 this.__sizeDimension(rect, positionedBy, start, end, extent); |
8183 }, | 5986 }, |
8184 | 5987 |
8185 /** | |
8186 * @private | |
8187 */ | |
8188 __sizeDimension: function(rect, positionedBy, start, end, extent) { | 5988 __sizeDimension: function(rect, positionedBy, start, end, extent) { |
8189 var info = this._fitInfo; | 5989 var info = this._fitInfo; |
8190 var fitRect = this.__getNormalizedRect(this.fitInto); | 5990 var fitRect = this.__getNormalizedRect(this.fitInto); |
8191 var max = extent === 'Width' ? fitRect.width : fitRect.height; | 5991 var max = extent === 'Width' ? fitRect.width : fitRect.height; |
8192 var flip = (positionedBy === end); | 5992 var flip = (positionedBy === end); |
8193 var offset = flip ? max - rect[end] : rect[start]; | 5993 var offset = flip ? max - rect[end] : rect[start]; |
8194 var margin = info.margin[flip ? start : end]; | 5994 var margin = info.margin[flip ? start : end]; |
8195 var offsetExtent = 'offset' + extent; | 5995 var offsetExtent = 'offset' + extent; |
8196 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; | 5996 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; |
8197 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; | 5997 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; |
8198 }, | 5998 }, |
8199 | 5999 |
8200 /** | |
8201 * Centers horizontally and vertically if not already positioned. This also
sets | |
8202 * `position:fixed`. | |
8203 */ | |
8204 center: function() { | 6000 center: function() { |
8205 if (this.horizontalAlign || this.verticalAlign) { | 6001 if (this.horizontalAlign || this.verticalAlign) { |
8206 return; | 6002 return; |
8207 } | 6003 } |
8208 this._discoverInfo(); | 6004 this._discoverInfo(); |
8209 | 6005 |
8210 var positionedBy = this._fitInfo.positionedBy; | 6006 var positionedBy = this._fitInfo.positionedBy; |
8211 if (positionedBy.vertically && positionedBy.horizontally) { | 6007 if (positionedBy.vertically && positionedBy.horizontally) { |
8212 // Already positioned. | |
8213 return; | 6008 return; |
8214 } | 6009 } |
8215 // Need position:fixed to center | |
8216 this.style.position = 'fixed'; | 6010 this.style.position = 'fixed'; |
8217 // Take into account the offset caused by parents that create stacking | |
8218 // contexts (e.g. with transform: translate3d). Translate to 0,0 and | |
8219 // measure the bounding rect. | |
8220 if (!positionedBy.vertically) { | 6011 if (!positionedBy.vertically) { |
8221 this.style.top = '0px'; | 6012 this.style.top = '0px'; |
8222 } | 6013 } |
8223 if (!positionedBy.horizontally) { | 6014 if (!positionedBy.horizontally) { |
8224 this.style.left = '0px'; | 6015 this.style.left = '0px'; |
8225 } | 6016 } |
8226 // It will take in consideration margins and transforms | |
8227 var rect = this.getBoundingClientRect(); | 6017 var rect = this.getBoundingClientRect(); |
8228 var fitRect = this.__getNormalizedRect(this.fitInto); | 6018 var fitRect = this.__getNormalizedRect(this.fitInto); |
8229 if (!positionedBy.vertically) { | 6019 if (!positionedBy.vertically) { |
8230 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2; | 6020 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2; |
8231 this.style.top = top + 'px'; | 6021 this.style.top = top + 'px'; |
8232 } | 6022 } |
8233 if (!positionedBy.horizontally) { | 6023 if (!positionedBy.horizontally) { |
8234 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2; | 6024 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2; |
8235 this.style.left = left + 'px'; | 6025 this.style.left = left + 'px'; |
8236 } | 6026 } |
(...skipping 14 matching lines...) Expand all Loading... |
8251 }, | 6041 }, |
8252 | 6042 |
8253 __getCroppedArea: function(position, size, fitRect) { | 6043 __getCroppedArea: function(position, size, fitRect) { |
8254 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom
- (position.top + size.height)); | 6044 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom
- (position.top + size.height)); |
8255 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ
t - (position.left + size.width)); | 6045 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ
t - (position.left + size.width)); |
8256 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si
ze.height; | 6046 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si
ze.height; |
8257 }, | 6047 }, |
8258 | 6048 |
8259 | 6049 |
8260 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) { | 6050 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) { |
8261 // All the possible configurations. | |
8262 // Ordered as top-left, top-right, bottom-left, bottom-right. | |
8263 var positions = [{ | 6051 var positions = [{ |
8264 verticalAlign: 'top', | 6052 verticalAlign: 'top', |
8265 horizontalAlign: 'left', | 6053 horizontalAlign: 'left', |
8266 top: positionRect.top, | 6054 top: positionRect.top, |
8267 left: positionRect.left | 6055 left: positionRect.left |
8268 }, { | 6056 }, { |
8269 verticalAlign: 'top', | 6057 verticalAlign: 'top', |
8270 horizontalAlign: 'right', | 6058 horizontalAlign: 'right', |
8271 top: positionRect.top, | 6059 top: positionRect.top, |
8272 left: positionRect.right - size.width | 6060 left: positionRect.right - size.width |
8273 }, { | 6061 }, { |
8274 verticalAlign: 'bottom', | 6062 verticalAlign: 'bottom', |
8275 horizontalAlign: 'left', | 6063 horizontalAlign: 'left', |
8276 top: positionRect.bottom - size.height, | 6064 top: positionRect.bottom - size.height, |
8277 left: positionRect.left | 6065 left: positionRect.left |
8278 }, { | 6066 }, { |
8279 verticalAlign: 'bottom', | 6067 verticalAlign: 'bottom', |
8280 horizontalAlign: 'right', | 6068 horizontalAlign: 'right', |
8281 top: positionRect.bottom - size.height, | 6069 top: positionRect.bottom - size.height, |
8282 left: positionRect.right - size.width | 6070 left: positionRect.right - size.width |
8283 }]; | 6071 }]; |
8284 | 6072 |
8285 if (this.noOverlap) { | 6073 if (this.noOverlap) { |
8286 // Duplicate. | |
8287 for (var i = 0, l = positions.length; i < l; i++) { | 6074 for (var i = 0, l = positions.length; i < l; i++) { |
8288 var copy = {}; | 6075 var copy = {}; |
8289 for (var key in positions[i]) { | 6076 for (var key in positions[i]) { |
8290 copy[key] = positions[i][key]; | 6077 copy[key] = positions[i][key]; |
8291 } | 6078 } |
8292 positions.push(copy); | 6079 positions.push(copy); |
8293 } | 6080 } |
8294 // Horizontal overlap only. | |
8295 positions[0].top = positions[1].top += positionRect.height; | 6081 positions[0].top = positions[1].top += positionRect.height; |
8296 positions[2].top = positions[3].top -= positionRect.height; | 6082 positions[2].top = positions[3].top -= positionRect.height; |
8297 // Vertical overlap only. | |
8298 positions[4].left = positions[6].left += positionRect.width; | 6083 positions[4].left = positions[6].left += positionRect.width; |
8299 positions[5].left = positions[7].left -= positionRect.width; | 6084 positions[5].left = positions[7].left -= positionRect.width; |
8300 } | 6085 } |
8301 | 6086 |
8302 // Consider auto as null for coding convenience. | |
8303 vAlign = vAlign === 'auto' ? null : vAlign; | 6087 vAlign = vAlign === 'auto' ? null : vAlign; |
8304 hAlign = hAlign === 'auto' ? null : hAlign; | 6088 hAlign = hAlign === 'auto' ? null : hAlign; |
8305 | 6089 |
8306 var position; | 6090 var position; |
8307 for (var i = 0; i < positions.length; i++) { | 6091 for (var i = 0; i < positions.length; i++) { |
8308 var pos = positions[i]; | 6092 var pos = positions[i]; |
8309 | 6093 |
8310 // If both vAlign and hAlign are defined, return exact match. | |
8311 // For dynamicAlign and noOverlap we'll have more than one candidate, so | |
8312 // we'll have to check the croppedArea to make the best choice. | |
8313 if (!this.dynamicAlign && !this.noOverlap && | 6094 if (!this.dynamicAlign && !this.noOverlap && |
8314 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) { | 6095 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) { |
8315 position = pos; | 6096 position = pos; |
8316 break; | 6097 break; |
8317 } | 6098 } |
8318 | 6099 |
8319 // Align is ok if alignment preferences are respected. If no preferences
, | |
8320 // it is considered ok. | |
8321 var alignOk = (!vAlign || pos.verticalAlign === vAlign) && | 6100 var alignOk = (!vAlign || pos.verticalAlign === vAlign) && |
8322 (!hAlign || pos.horizontalAlign === hAlign); | 6101 (!hAlign || pos.horizontalAlign === hAlign); |
8323 | 6102 |
8324 // Filter out elements that don't match the alignment (if defined). | |
8325 // With dynamicAlign, we need to consider all the positions to find the | |
8326 // one that minimizes the cropped area. | |
8327 if (!this.dynamicAlign && !alignOk) { | 6103 if (!this.dynamicAlign && !alignOk) { |
8328 continue; | 6104 continue; |
8329 } | 6105 } |
8330 | 6106 |
8331 position = position || pos; | 6107 position = position || pos; |
8332 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect); | 6108 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect); |
8333 var diff = pos.croppedArea - position.croppedArea; | 6109 var diff = pos.croppedArea - position.croppedArea; |
8334 // Check which crops less. If it crops equally, check if align is ok. | |
8335 if (diff < 0 || (diff === 0 && alignOk)) { | 6110 if (diff < 0 || (diff === 0 && alignOk)) { |
8336 position = pos; | 6111 position = pos; |
8337 } | 6112 } |
8338 // If not cropped and respects the align requirements, keep it. | |
8339 // This allows to prefer positions overlapping horizontally over the | |
8340 // ones overlapping vertically. | |
8341 if (position.croppedArea === 0 && alignOk) { | 6113 if (position.croppedArea === 0 && alignOk) { |
8342 break; | 6114 break; |
8343 } | 6115 } |
8344 } | 6116 } |
8345 | 6117 |
8346 return position; | 6118 return position; |
8347 } | 6119 } |
8348 | 6120 |
8349 }; | 6121 }; |
8350 (function() { | 6122 (function() { |
8351 'use strict'; | 6123 'use strict'; |
8352 | 6124 |
8353 Polymer({ | 6125 Polymer({ |
8354 | 6126 |
8355 is: 'iron-overlay-backdrop', | 6127 is: 'iron-overlay-backdrop', |
8356 | 6128 |
8357 properties: { | 6129 properties: { |
8358 | 6130 |
8359 /** | |
8360 * Returns true if the backdrop is opened. | |
8361 */ | |
8362 opened: { | 6131 opened: { |
8363 reflectToAttribute: true, | 6132 reflectToAttribute: true, |
8364 type: Boolean, | 6133 type: Boolean, |
8365 value: false, | 6134 value: false, |
8366 observer: '_openedChanged' | 6135 observer: '_openedChanged' |
8367 } | 6136 } |
8368 | 6137 |
8369 }, | 6138 }, |
8370 | 6139 |
8371 listeners: { | 6140 listeners: { |
8372 'transitionend': '_onTransitionend' | 6141 'transitionend': '_onTransitionend' |
8373 }, | 6142 }, |
8374 | 6143 |
8375 created: function() { | 6144 created: function() { |
8376 // Used to cancel previous requestAnimationFrame calls when opened changes
. | |
8377 this.__openedRaf = null; | 6145 this.__openedRaf = null; |
8378 }, | 6146 }, |
8379 | 6147 |
8380 attached: function() { | 6148 attached: function() { |
8381 this.opened && this._openedChanged(this.opened); | 6149 this.opened && this._openedChanged(this.opened); |
8382 }, | 6150 }, |
8383 | 6151 |
8384 /** | |
8385 * Appends the backdrop to document body if needed. | |
8386 */ | |
8387 prepare: function() { | 6152 prepare: function() { |
8388 if (this.opened && !this.parentNode) { | 6153 if (this.opened && !this.parentNode) { |
8389 Polymer.dom(document.body).appendChild(this); | 6154 Polymer.dom(document.body).appendChild(this); |
8390 } | 6155 } |
8391 }, | 6156 }, |
8392 | 6157 |
8393 /** | |
8394 * Shows the backdrop. | |
8395 */ | |
8396 open: function() { | 6158 open: function() { |
8397 this.opened = true; | 6159 this.opened = true; |
8398 }, | 6160 }, |
8399 | 6161 |
8400 /** | |
8401 * Hides the backdrop. | |
8402 */ | |
8403 close: function() { | 6162 close: function() { |
8404 this.opened = false; | 6163 this.opened = false; |
8405 }, | 6164 }, |
8406 | 6165 |
8407 /** | |
8408 * Removes the backdrop from document body if needed. | |
8409 */ | |
8410 complete: function() { | 6166 complete: function() { |
8411 if (!this.opened && this.parentNode === document.body) { | 6167 if (!this.opened && this.parentNode === document.body) { |
8412 Polymer.dom(this.parentNode).removeChild(this); | 6168 Polymer.dom(this.parentNode).removeChild(this); |
8413 } | 6169 } |
8414 }, | 6170 }, |
8415 | 6171 |
8416 _onTransitionend: function(event) { | 6172 _onTransitionend: function(event) { |
8417 if (event && event.target === this) { | 6173 if (event && event.target === this) { |
8418 this.complete(); | 6174 this.complete(); |
8419 } | 6175 } |
8420 }, | 6176 }, |
8421 | 6177 |
8422 /** | |
8423 * @param {boolean} opened | |
8424 * @private | |
8425 */ | |
8426 _openedChanged: function(opened) { | 6178 _openedChanged: function(opened) { |
8427 if (opened) { | 6179 if (opened) { |
8428 // Auto-attach. | |
8429 this.prepare(); | 6180 this.prepare(); |
8430 } else { | 6181 } else { |
8431 // Animation might be disabled via the mixin or opacity custom property. | |
8432 // If it is disabled in other ways, it's up to the user to call complete
. | |
8433 var cs = window.getComputedStyle(this); | 6182 var cs = window.getComputedStyle(this); |
8434 if (cs.transitionDuration === '0s' || cs.opacity == 0) { | 6183 if (cs.transitionDuration === '0s' || cs.opacity == 0) { |
8435 this.complete(); | 6184 this.complete(); |
8436 } | 6185 } |
8437 } | 6186 } |
8438 | 6187 |
8439 if (!this.isAttached) { | 6188 if (!this.isAttached) { |
8440 return; | 6189 return; |
8441 } | 6190 } |
8442 | 6191 |
8443 // Always cancel previous requestAnimationFrame. | |
8444 if (this.__openedRaf) { | 6192 if (this.__openedRaf) { |
8445 window.cancelAnimationFrame(this.__openedRaf); | 6193 window.cancelAnimationFrame(this.__openedRaf); |
8446 this.__openedRaf = null; | 6194 this.__openedRaf = null; |
8447 } | 6195 } |
8448 // Force relayout to ensure proper transitions. | |
8449 this.scrollTop = this.scrollTop; | 6196 this.scrollTop = this.scrollTop; |
8450 this.__openedRaf = window.requestAnimationFrame(function() { | 6197 this.__openedRaf = window.requestAnimationFrame(function() { |
8451 this.__openedRaf = null; | 6198 this.__openedRaf = null; |
8452 this.toggleClass('opened', this.opened); | 6199 this.toggleClass('opened', this.opened); |
8453 }.bind(this)); | 6200 }.bind(this)); |
8454 } | 6201 } |
8455 }); | 6202 }); |
8456 | 6203 |
8457 })(); | 6204 })(); |
8458 /** | |
8459 * @struct | |
8460 * @constructor | |
8461 * @private | |
8462 */ | |
8463 Polymer.IronOverlayManagerClass = function() { | 6205 Polymer.IronOverlayManagerClass = function() { |
8464 /** | |
8465 * Used to keep track of the opened overlays. | |
8466 * @private {Array<Element>} | |
8467 */ | |
8468 this._overlays = []; | 6206 this._overlays = []; |
8469 | 6207 |
8470 /** | |
8471 * iframes have a default z-index of 100, | |
8472 * so this default should be at least that. | |
8473 * @private {number} | |
8474 */ | |
8475 this._minimumZ = 101; | 6208 this._minimumZ = 101; |
8476 | 6209 |
8477 /** | |
8478 * Memoized backdrop element. | |
8479 * @private {Element|null} | |
8480 */ | |
8481 this._backdropElement = null; | 6210 this._backdropElement = null; |
8482 | 6211 |
8483 // Enable document-wide tap recognizer. | |
8484 Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this)); | 6212 Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this)); |
8485 | 6213 |
8486 document.addEventListener('focus', this._onCaptureFocus.bind(this), true); | 6214 document.addEventListener('focus', this._onCaptureFocus.bind(this), true); |
8487 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true
); | 6215 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true
); |
8488 }; | 6216 }; |
8489 | 6217 |
8490 Polymer.IronOverlayManagerClass.prototype = { | 6218 Polymer.IronOverlayManagerClass.prototype = { |
8491 | 6219 |
8492 constructor: Polymer.IronOverlayManagerClass, | 6220 constructor: Polymer.IronOverlayManagerClass, |
8493 | 6221 |
8494 /** | |
8495 * The shared backdrop element. | |
8496 * @type {!Element} backdropElement | |
8497 */ | |
8498 get backdropElement() { | 6222 get backdropElement() { |
8499 if (!this._backdropElement) { | 6223 if (!this._backdropElement) { |
8500 this._backdropElement = document.createElement('iron-overlay-backdrop'); | 6224 this._backdropElement = document.createElement('iron-overlay-backdrop'); |
8501 } | 6225 } |
8502 return this._backdropElement; | 6226 return this._backdropElement; |
8503 }, | 6227 }, |
8504 | 6228 |
8505 /** | |
8506 * The deepest active element. | |
8507 * @type {!Element} activeElement the active element | |
8508 */ | |
8509 get deepActiveElement() { | 6229 get deepActiveElement() { |
8510 // document.activeElement can be null | |
8511 // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement | |
8512 // In case of null, default it to document.body. | |
8513 var active = document.activeElement || document.body; | 6230 var active = document.activeElement || document.body; |
8514 while (active.root && Polymer.dom(active.root).activeElement) { | 6231 while (active.root && Polymer.dom(active.root).activeElement) { |
8515 active = Polymer.dom(active.root).activeElement; | 6232 active = Polymer.dom(active.root).activeElement; |
8516 } | 6233 } |
8517 return active; | 6234 return active; |
8518 }, | 6235 }, |
8519 | 6236 |
8520 /** | |
8521 * Brings the overlay at the specified index to the front. | |
8522 * @param {number} i | |
8523 * @private | |
8524 */ | |
8525 _bringOverlayAtIndexToFront: function(i) { | 6237 _bringOverlayAtIndexToFront: function(i) { |
8526 var overlay = this._overlays[i]; | 6238 var overlay = this._overlays[i]; |
8527 if (!overlay) { | 6239 if (!overlay) { |
8528 return; | 6240 return; |
8529 } | 6241 } |
8530 var lastI = this._overlays.length - 1; | 6242 var lastI = this._overlays.length - 1; |
8531 var currentOverlay = this._overlays[lastI]; | 6243 var currentOverlay = this._overlays[lastI]; |
8532 // Ensure always-on-top overlay stays on top. | |
8533 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { | 6244 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { |
8534 lastI--; | 6245 lastI--; |
8535 } | 6246 } |
8536 // If already the top element, return. | |
8537 if (i >= lastI) { | 6247 if (i >= lastI) { |
8538 return; | 6248 return; |
8539 } | 6249 } |
8540 // Update z-index to be on top. | |
8541 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); | 6250 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); |
8542 if (this._getZ(overlay) <= minimumZ) { | 6251 if (this._getZ(overlay) <= minimumZ) { |
8543 this._applyOverlayZ(overlay, minimumZ); | 6252 this._applyOverlayZ(overlay, minimumZ); |
8544 } | 6253 } |
8545 | 6254 |
8546 // Shift other overlays behind the new on top. | |
8547 while (i < lastI) { | 6255 while (i < lastI) { |
8548 this._overlays[i] = this._overlays[i + 1]; | 6256 this._overlays[i] = this._overlays[i + 1]; |
8549 i++; | 6257 i++; |
8550 } | 6258 } |
8551 this._overlays[lastI] = overlay; | 6259 this._overlays[lastI] = overlay; |
8552 }, | 6260 }, |
8553 | 6261 |
8554 /** | |
8555 * Adds the overlay and updates its z-index if it's opened, or removes it if
it's closed. | |
8556 * Also updates the backdrop z-index. | |
8557 * @param {!Element} overlay | |
8558 */ | |
8559 addOrRemoveOverlay: function(overlay) { | 6262 addOrRemoveOverlay: function(overlay) { |
8560 if (overlay.opened) { | 6263 if (overlay.opened) { |
8561 this.addOverlay(overlay); | 6264 this.addOverlay(overlay); |
8562 } else { | 6265 } else { |
8563 this.removeOverlay(overlay); | 6266 this.removeOverlay(overlay); |
8564 } | 6267 } |
8565 }, | 6268 }, |
8566 | 6269 |
8567 /** | |
8568 * Tracks overlays for z-index and focus management. | |
8569 * Ensures the last added overlay with always-on-top remains on top. | |
8570 * @param {!Element} overlay | |
8571 */ | |
8572 addOverlay: function(overlay) { | 6270 addOverlay: function(overlay) { |
8573 var i = this._overlays.indexOf(overlay); | 6271 var i = this._overlays.indexOf(overlay); |
8574 if (i >= 0) { | 6272 if (i >= 0) { |
8575 this._bringOverlayAtIndexToFront(i); | 6273 this._bringOverlayAtIndexToFront(i); |
8576 this.trackBackdrop(); | 6274 this.trackBackdrop(); |
8577 return; | 6275 return; |
8578 } | 6276 } |
8579 var insertionIndex = this._overlays.length; | 6277 var insertionIndex = this._overlays.length; |
8580 var currentOverlay = this._overlays[insertionIndex - 1]; | 6278 var currentOverlay = this._overlays[insertionIndex - 1]; |
8581 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); | 6279 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); |
8582 var newZ = this._getZ(overlay); | 6280 var newZ = this._getZ(overlay); |
8583 | 6281 |
8584 // Ensure always-on-top overlay stays on top. | |
8585 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { | 6282 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { |
8586 // This bumps the z-index of +2. | |
8587 this._applyOverlayZ(currentOverlay, minimumZ); | 6283 this._applyOverlayZ(currentOverlay, minimumZ); |
8588 insertionIndex--; | 6284 insertionIndex--; |
8589 // Update minimumZ to match previous overlay's z-index. | |
8590 var previousOverlay = this._overlays[insertionIndex - 1]; | 6285 var previousOverlay = this._overlays[insertionIndex - 1]; |
8591 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); | 6286 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); |
8592 } | 6287 } |
8593 | 6288 |
8594 // Update z-index and insert overlay. | |
8595 if (newZ <= minimumZ) { | 6289 if (newZ <= minimumZ) { |
8596 this._applyOverlayZ(overlay, minimumZ); | 6290 this._applyOverlayZ(overlay, minimumZ); |
8597 } | 6291 } |
8598 this._overlays.splice(insertionIndex, 0, overlay); | 6292 this._overlays.splice(insertionIndex, 0, overlay); |
8599 | 6293 |
8600 this.trackBackdrop(); | 6294 this.trackBackdrop(); |
8601 }, | 6295 }, |
8602 | 6296 |
8603 /** | |
8604 * @param {!Element} overlay | |
8605 */ | |
8606 removeOverlay: function(overlay) { | 6297 removeOverlay: function(overlay) { |
8607 var i = this._overlays.indexOf(overlay); | 6298 var i = this._overlays.indexOf(overlay); |
8608 if (i === -1) { | 6299 if (i === -1) { |
8609 return; | 6300 return; |
8610 } | 6301 } |
8611 this._overlays.splice(i, 1); | 6302 this._overlays.splice(i, 1); |
8612 | 6303 |
8613 this.trackBackdrop(); | 6304 this.trackBackdrop(); |
8614 }, | 6305 }, |
8615 | 6306 |
8616 /** | |
8617 * Returns the current overlay. | |
8618 * @return {Element|undefined} | |
8619 */ | |
8620 currentOverlay: function() { | 6307 currentOverlay: function() { |
8621 var i = this._overlays.length - 1; | 6308 var i = this._overlays.length - 1; |
8622 return this._overlays[i]; | 6309 return this._overlays[i]; |
8623 }, | 6310 }, |
8624 | 6311 |
8625 /** | |
8626 * Returns the current overlay z-index. | |
8627 * @return {number} | |
8628 */ | |
8629 currentOverlayZ: function() { | 6312 currentOverlayZ: function() { |
8630 return this._getZ(this.currentOverlay()); | 6313 return this._getZ(this.currentOverlay()); |
8631 }, | 6314 }, |
8632 | 6315 |
8633 /** | |
8634 * Ensures that the minimum z-index of new overlays is at least `minimumZ`. | |
8635 * This does not effect the z-index of any existing overlays. | |
8636 * @param {number} minimumZ | |
8637 */ | |
8638 ensureMinimumZ: function(minimumZ) { | 6316 ensureMinimumZ: function(minimumZ) { |
8639 this._minimumZ = Math.max(this._minimumZ, minimumZ); | 6317 this._minimumZ = Math.max(this._minimumZ, minimumZ); |
8640 }, | 6318 }, |
8641 | 6319 |
8642 focusOverlay: function() { | 6320 focusOverlay: function() { |
8643 var current = /** @type {?} */ (this.currentOverlay()); | 6321 var current = /** @type {?} */ (this.currentOverlay()); |
8644 if (current) { | 6322 if (current) { |
8645 current._applyFocus(); | 6323 current._applyFocus(); |
8646 } | 6324 } |
8647 }, | 6325 }, |
8648 | 6326 |
8649 /** | |
8650 * Updates the backdrop z-index. | |
8651 */ | |
8652 trackBackdrop: function() { | 6327 trackBackdrop: function() { |
8653 var overlay = this._overlayWithBackdrop(); | 6328 var overlay = this._overlayWithBackdrop(); |
8654 // Avoid creating the backdrop if there is no overlay with backdrop. | |
8655 if (!overlay && !this._backdropElement) { | 6329 if (!overlay && !this._backdropElement) { |
8656 return; | 6330 return; |
8657 } | 6331 } |
8658 this.backdropElement.style.zIndex = this._getZ(overlay) - 1; | 6332 this.backdropElement.style.zIndex = this._getZ(overlay) - 1; |
8659 this.backdropElement.opened = !!overlay; | 6333 this.backdropElement.opened = !!overlay; |
8660 }, | 6334 }, |
8661 | 6335 |
8662 /** | |
8663 * @return {Array<Element>} | |
8664 */ | |
8665 getBackdrops: function() { | 6336 getBackdrops: function() { |
8666 var backdrops = []; | 6337 var backdrops = []; |
8667 for (var i = 0; i < this._overlays.length; i++) { | 6338 for (var i = 0; i < this._overlays.length; i++) { |
8668 if (this._overlays[i].withBackdrop) { | 6339 if (this._overlays[i].withBackdrop) { |
8669 backdrops.push(this._overlays[i]); | 6340 backdrops.push(this._overlays[i]); |
8670 } | 6341 } |
8671 } | 6342 } |
8672 return backdrops; | 6343 return backdrops; |
8673 }, | 6344 }, |
8674 | 6345 |
8675 /** | |
8676 * Returns the z-index for the backdrop. | |
8677 * @return {number} | |
8678 */ | |
8679 backdropZ: function() { | 6346 backdropZ: function() { |
8680 return this._getZ(this._overlayWithBackdrop()) - 1; | 6347 return this._getZ(this._overlayWithBackdrop()) - 1; |
8681 }, | 6348 }, |
8682 | 6349 |
8683 /** | |
8684 * Returns the first opened overlay that has a backdrop. | |
8685 * @return {Element|undefined} | |
8686 * @private | |
8687 */ | |
8688 _overlayWithBackdrop: function() { | 6350 _overlayWithBackdrop: function() { |
8689 for (var i = 0; i < this._overlays.length; i++) { | 6351 for (var i = 0; i < this._overlays.length; i++) { |
8690 if (this._overlays[i].withBackdrop) { | 6352 if (this._overlays[i].withBackdrop) { |
8691 return this._overlays[i]; | 6353 return this._overlays[i]; |
8692 } | 6354 } |
8693 } | 6355 } |
8694 }, | 6356 }, |
8695 | 6357 |
8696 /** | |
8697 * Calculates the minimum z-index for the overlay. | |
8698 * @param {Element=} overlay | |
8699 * @private | |
8700 */ | |
8701 _getZ: function(overlay) { | 6358 _getZ: function(overlay) { |
8702 var z = this._minimumZ; | 6359 var z = this._minimumZ; |
8703 if (overlay) { | 6360 if (overlay) { |
8704 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay)
.zIndex); | 6361 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay)
.zIndex); |
8705 // Check if is a number | |
8706 // Number.isNaN not supported in IE 10+ | |
8707 if (z1 === z1) { | 6362 if (z1 === z1) { |
8708 z = z1; | 6363 z = z1; |
8709 } | 6364 } |
8710 } | 6365 } |
8711 return z; | 6366 return z; |
8712 }, | 6367 }, |
8713 | 6368 |
8714 /** | |
8715 * @param {!Element} element | |
8716 * @param {number|string} z | |
8717 * @private | |
8718 */ | |
8719 _setZ: function(element, z) { | 6369 _setZ: function(element, z) { |
8720 element.style.zIndex = z; | 6370 element.style.zIndex = z; |
8721 }, | 6371 }, |
8722 | 6372 |
8723 /** | |
8724 * @param {!Element} overlay | |
8725 * @param {number} aboveZ | |
8726 * @private | |
8727 */ | |
8728 _applyOverlayZ: function(overlay, aboveZ) { | 6373 _applyOverlayZ: function(overlay, aboveZ) { |
8729 this._setZ(overlay, aboveZ + 2); | 6374 this._setZ(overlay, aboveZ + 2); |
8730 }, | 6375 }, |
8731 | 6376 |
8732 /** | |
8733 * Returns the deepest overlay in the path. | |
8734 * @param {Array<Element>=} path | |
8735 * @return {Element|undefined} | |
8736 * @suppress {missingProperties} | |
8737 * @private | |
8738 */ | |
8739 _overlayInPath: function(path) { | 6377 _overlayInPath: function(path) { |
8740 path = path || []; | 6378 path = path || []; |
8741 for (var i = 0; i < path.length; i++) { | 6379 for (var i = 0; i < path.length; i++) { |
8742 if (path[i]._manager === this) { | 6380 if (path[i]._manager === this) { |
8743 return path[i]; | 6381 return path[i]; |
8744 } | 6382 } |
8745 } | 6383 } |
8746 }, | 6384 }, |
8747 | 6385 |
8748 /** | |
8749 * Ensures the click event is delegated to the right overlay. | |
8750 * @param {!Event} event | |
8751 * @private | |
8752 */ | |
8753 _onCaptureClick: function(event) { | 6386 _onCaptureClick: function(event) { |
8754 var overlay = /** @type {?} */ (this.currentOverlay()); | 6387 var overlay = /** @type {?} */ (this.currentOverlay()); |
8755 // Check if clicked outside of top overlay. | |
8756 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { | 6388 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { |
8757 overlay._onCaptureClick(event); | 6389 overlay._onCaptureClick(event); |
8758 } | 6390 } |
8759 }, | 6391 }, |
8760 | 6392 |
8761 /** | |
8762 * Ensures the focus event is delegated to the right overlay. | |
8763 * @param {!Event} event | |
8764 * @private | |
8765 */ | |
8766 _onCaptureFocus: function(event) { | 6393 _onCaptureFocus: function(event) { |
8767 var overlay = /** @type {?} */ (this.currentOverlay()); | 6394 var overlay = /** @type {?} */ (this.currentOverlay()); |
8768 if (overlay) { | 6395 if (overlay) { |
8769 overlay._onCaptureFocus(event); | 6396 overlay._onCaptureFocus(event); |
8770 } | 6397 } |
8771 }, | 6398 }, |
8772 | 6399 |
8773 /** | |
8774 * Ensures TAB and ESC keyboard events are delegated to the right overlay. | |
8775 * @param {!Event} event | |
8776 * @private | |
8777 */ | |
8778 _onCaptureKeyDown: function(event) { | 6400 _onCaptureKeyDown: function(event) { |
8779 var overlay = /** @type {?} */ (this.currentOverlay()); | 6401 var overlay = /** @type {?} */ (this.currentOverlay()); |
8780 if (overlay) { | 6402 if (overlay) { |
8781 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc'))
{ | 6403 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc'))
{ |
8782 overlay._onCaptureEsc(event); | 6404 overlay._onCaptureEsc(event); |
8783 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event,
'tab')) { | 6405 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event,
'tab')) { |
8784 overlay._onCaptureTab(event); | 6406 overlay._onCaptureTab(event); |
8785 } | 6407 } |
8786 } | 6408 } |
8787 }, | 6409 }, |
8788 | 6410 |
8789 /** | |
8790 * Returns if the overlay1 should be behind overlay2. | |
8791 * @param {!Element} overlay1 | |
8792 * @param {!Element} overlay2 | |
8793 * @return {boolean} | |
8794 * @suppress {missingProperties} | |
8795 * @private | |
8796 */ | |
8797 _shouldBeBehindOverlay: function(overlay1, overlay2) { | 6411 _shouldBeBehindOverlay: function(overlay1, overlay2) { |
8798 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; | 6412 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; |
8799 } | 6413 } |
8800 }; | 6414 }; |
8801 | 6415 |
8802 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); | 6416 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); |
8803 (function() { | 6417 (function() { |
8804 'use strict'; | 6418 'use strict'; |
8805 | 6419 |
8806 /** | |
8807 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays | |
8808 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety | |
8809 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. | |
8810 | |
8811 See the [demo source code](https://github.com/PolymerElements/iron-overlay-behav
ior/blob/master/demo/simple-overlay.html) | |
8812 for an example. | |
8813 | |
8814 ### Closing and canceling | |
8815 | |
8816 An overlay may be hidden by closing or canceling. The difference between close a
nd cancel is user | |
8817 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, | |
8818 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is | |
8819 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. | |
8820 `close()` should be called explicitly by the implementer when the user interacts
with a control | |
8821 in the overlay element. When the dialog is canceled, the overlay fires an 'iron-
overlay-canceled' | |
8822 event. Call `preventDefault` on this event to prevent the overlay from closing. | |
8823 | |
8824 ### Positioning | |
8825 | |
8826 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can | |
8827 position and size it manually using CSS. See `Polymer.IronFitBehavior`. | |
8828 | |
8829 ### Backdrop | |
8830 | |
8831 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is | |
8832 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling | |
8833 options. | |
8834 | |
8835 In addition, `with-backdrop` will wrap the focus within the content in the light
DOM. | |
8836 Override the [`_focusableNodes` getter](#Polymer.IronOverlayBehavior:property-_f
ocusableNodes) | |
8837 to achieve a different behavior. | |
8838 | |
8839 ### Limitations | |
8840 | |
8841 The element is styled to appear on top of other content by setting its `z-index`
property. You | |
8842 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking | |
8843 context. You should place this element as a child of `<body>` whenever possible. | |
8844 | |
8845 @demo demo/index.html | |
8846 @polymerBehavior Polymer.IronOverlayBehavior | |
8847 */ | |
8848 | 6420 |
8849 Polymer.IronOverlayBehaviorImpl = { | 6421 Polymer.IronOverlayBehaviorImpl = { |
8850 | 6422 |
8851 properties: { | 6423 properties: { |
8852 | 6424 |
8853 /** | |
8854 * True if the overlay is currently displayed. | |
8855 */ | |
8856 opened: { | 6425 opened: { |
8857 observer: '_openedChanged', | 6426 observer: '_openedChanged', |
8858 type: Boolean, | 6427 type: Boolean, |
8859 value: false, | 6428 value: false, |
8860 notify: true | 6429 notify: true |
8861 }, | 6430 }, |
8862 | 6431 |
8863 /** | |
8864 * True if the overlay was canceled when it was last closed. | |
8865 */ | |
8866 canceled: { | 6432 canceled: { |
8867 observer: '_canceledChanged', | 6433 observer: '_canceledChanged', |
8868 readOnly: true, | 6434 readOnly: true, |
8869 type: Boolean, | 6435 type: Boolean, |
8870 value: false | 6436 value: false |
8871 }, | 6437 }, |
8872 | 6438 |
8873 /** | |
8874 * Set to true to display a backdrop behind the overlay. It traps the focu
s | |
8875 * within the light DOM of the overlay. | |
8876 */ | |
8877 withBackdrop: { | 6439 withBackdrop: { |
8878 observer: '_withBackdropChanged', | 6440 observer: '_withBackdropChanged', |
8879 type: Boolean | 6441 type: Boolean |
8880 }, | 6442 }, |
8881 | 6443 |
8882 /** | |
8883 * Set to true to disable auto-focusing the overlay or child nodes with | |
8884 * the `autofocus` attribute` when the overlay is opened. | |
8885 */ | |
8886 noAutoFocus: { | 6444 noAutoFocus: { |
8887 type: Boolean, | 6445 type: Boolean, |
8888 value: false | 6446 value: false |
8889 }, | 6447 }, |
8890 | 6448 |
8891 /** | |
8892 * Set to true to disable canceling the overlay with the ESC key. | |
8893 */ | |
8894 noCancelOnEscKey: { | 6449 noCancelOnEscKey: { |
8895 type: Boolean, | 6450 type: Boolean, |
8896 value: false | 6451 value: false |
8897 }, | 6452 }, |
8898 | 6453 |
8899 /** | |
8900 * Set to true to disable canceling the overlay by clicking outside it. | |
8901 */ | |
8902 noCancelOnOutsideClick: { | 6454 noCancelOnOutsideClick: { |
8903 type: Boolean, | 6455 type: Boolean, |
8904 value: false | 6456 value: false |
8905 }, | 6457 }, |
8906 | 6458 |
8907 /** | |
8908 * Contains the reason(s) this overlay was last closed (see `iron-overlay-
closed`). | |
8909 * `IronOverlayBehavior` provides the `canceled` reason; implementers of t
he | |
8910 * behavior can provide other reasons in addition to `canceled`. | |
8911 */ | |
8912 closingReason: { | 6459 closingReason: { |
8913 // was a getter before, but needs to be a property so other | |
8914 // behaviors can override this. | |
8915 type: Object | 6460 type: Object |
8916 }, | 6461 }, |
8917 | 6462 |
8918 /** | |
8919 * Set to true to enable restoring of focus when overlay is closed. | |
8920 */ | |
8921 restoreFocusOnClose: { | 6463 restoreFocusOnClose: { |
8922 type: Boolean, | 6464 type: Boolean, |
8923 value: false | 6465 value: false |
8924 }, | 6466 }, |
8925 | 6467 |
8926 /** | |
8927 * Set to true to keep overlay always on top. | |
8928 */ | |
8929 alwaysOnTop: { | 6468 alwaysOnTop: { |
8930 type: Boolean | 6469 type: Boolean |
8931 }, | 6470 }, |
8932 | 6471 |
8933 /** | |
8934 * Shortcut to access to the overlay manager. | |
8935 * @private | |
8936 * @type {Polymer.IronOverlayManagerClass} | |
8937 */ | |
8938 _manager: { | 6472 _manager: { |
8939 type: Object, | 6473 type: Object, |
8940 value: Polymer.IronOverlayManager | 6474 value: Polymer.IronOverlayManager |
8941 }, | 6475 }, |
8942 | 6476 |
8943 /** | |
8944 * The node being focused. | |
8945 * @type {?Node} | |
8946 */ | |
8947 _focusedChild: { | 6477 _focusedChild: { |
8948 type: Object | 6478 type: Object |
8949 } | 6479 } |
8950 | 6480 |
8951 }, | 6481 }, |
8952 | 6482 |
8953 listeners: { | 6483 listeners: { |
8954 'iron-resize': '_onIronResize' | 6484 'iron-resize': '_onIronResize' |
8955 }, | 6485 }, |
8956 | 6486 |
8957 /** | |
8958 * The backdrop element. | |
8959 * @type {Element} | |
8960 */ | |
8961 get backdropElement() { | 6487 get backdropElement() { |
8962 return this._manager.backdropElement; | 6488 return this._manager.backdropElement; |
8963 }, | 6489 }, |
8964 | 6490 |
8965 /** | |
8966 * Returns the node to give focus to. | |
8967 * @type {Node} | |
8968 */ | |
8969 get _focusNode() { | 6491 get _focusNode() { |
8970 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]'
) || this; | 6492 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]'
) || this; |
8971 }, | 6493 }, |
8972 | 6494 |
8973 /** | |
8974 * Array of nodes that can receive focus (overlay included), ordered by `tab
index`. | |
8975 * This is used to retrieve which is the first and last focusable nodes in o
rder | |
8976 * to wrap the focus for overlays `with-backdrop`. | |
8977 * | |
8978 * If you know what is your content (specifically the first and last focusab
le children), | |
8979 * you can override this method to return only `[firstFocusable, lastFocusab
le];` | |
8980 * @type {Array<Node>} | |
8981 * @protected | |
8982 */ | |
8983 get _focusableNodes() { | 6495 get _focusableNodes() { |
8984 // Elements that can be focused even if they have [disabled] attribute. | |
8985 var FOCUSABLE_WITH_DISABLED = [ | 6496 var FOCUSABLE_WITH_DISABLED = [ |
8986 'a[href]', | 6497 'a[href]', |
8987 'area[href]', | 6498 'area[href]', |
8988 'iframe', | 6499 'iframe', |
8989 '[tabindex]', | 6500 '[tabindex]', |
8990 '[contentEditable=true]' | 6501 '[contentEditable=true]' |
8991 ]; | 6502 ]; |
8992 | 6503 |
8993 // Elements that cannot be focused if they have [disabled] attribute. | |
8994 var FOCUSABLE_WITHOUT_DISABLED = [ | 6504 var FOCUSABLE_WITHOUT_DISABLED = [ |
8995 'input', | 6505 'input', |
8996 'select', | 6506 'select', |
8997 'textarea', | 6507 'textarea', |
8998 'button' | 6508 'button' |
8999 ]; | 6509 ]; |
9000 | 6510 |
9001 // Discard elements with tabindex=-1 (makes them not focusable). | |
9002 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') + | 6511 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') + |
9003 ':not([tabindex="-1"]),' + | 6512 ':not([tabindex="-1"]),' + |
9004 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),'
) + | 6513 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),'
) + |
9005 ':not([disabled]):not([tabindex="-1"])'; | 6514 ':not([disabled]):not([tabindex="-1"])'; |
9006 | 6515 |
9007 var focusables = Polymer.dom(this).querySelectorAll(selector); | 6516 var focusables = Polymer.dom(this).querySelectorAll(selector); |
9008 if (this.tabIndex >= 0) { | 6517 if (this.tabIndex >= 0) { |
9009 // Insert at the beginning because we might have all elements with tabIn
dex = 0, | |
9010 // and the overlay should be the first of the list. | |
9011 focusables.splice(0, 0, this); | 6518 focusables.splice(0, 0, this); |
9012 } | 6519 } |
9013 // Sort by tabindex. | |
9014 return focusables.sort(function (a, b) { | 6520 return focusables.sort(function (a, b) { |
9015 if (a.tabIndex === b.tabIndex) { | 6521 if (a.tabIndex === b.tabIndex) { |
9016 return 0; | 6522 return 0; |
9017 } | 6523 } |
9018 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) { | 6524 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) { |
9019 return 1; | 6525 return 1; |
9020 } | 6526 } |
9021 return -1; | 6527 return -1; |
9022 }); | 6528 }); |
9023 }, | 6529 }, |
9024 | 6530 |
9025 ready: function() { | 6531 ready: function() { |
9026 // Used to skip calls to notifyResize and refit while the overlay is anima
ting. | |
9027 this.__isAnimating = false; | 6532 this.__isAnimating = false; |
9028 // with-backdrop needs tabindex to be set in order to trap the focus. | |
9029 // If it is not set, IronOverlayBehavior will set it, and remove it if wit
h-backdrop = false. | |
9030 this.__shouldRemoveTabIndex = false; | 6533 this.__shouldRemoveTabIndex = false; |
9031 // Used for wrapping the focus on TAB / Shift+TAB. | |
9032 this.__firstFocusableNode = this.__lastFocusableNode = null; | 6534 this.__firstFocusableNode = this.__lastFocusableNode = null; |
9033 // Used by __onNextAnimationFrame to cancel any previous callback. | |
9034 this.__raf = null; | 6535 this.__raf = null; |
9035 // Focused node before overlay gets opened. Can be restored on close. | |
9036 this.__restoreFocusNode = null; | 6536 this.__restoreFocusNode = null; |
9037 this._ensureSetup(); | 6537 this._ensureSetup(); |
9038 }, | 6538 }, |
9039 | 6539 |
9040 attached: function() { | 6540 attached: function() { |
9041 // Call _openedChanged here so that position can be computed correctly. | |
9042 if (this.opened) { | 6541 if (this.opened) { |
9043 this._openedChanged(this.opened); | 6542 this._openedChanged(this.opened); |
9044 } | 6543 } |
9045 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange); | 6544 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange); |
9046 }, | 6545 }, |
9047 | 6546 |
9048 detached: function() { | 6547 detached: function() { |
9049 Polymer.dom(this).unobserveNodes(this._observer); | 6548 Polymer.dom(this).unobserveNodes(this._observer); |
9050 this._observer = null; | 6549 this._observer = null; |
9051 if (this.__raf) { | 6550 if (this.__raf) { |
9052 window.cancelAnimationFrame(this.__raf); | 6551 window.cancelAnimationFrame(this.__raf); |
9053 this.__raf = null; | 6552 this.__raf = null; |
9054 } | 6553 } |
9055 this._manager.removeOverlay(this); | 6554 this._manager.removeOverlay(this); |
9056 }, | 6555 }, |
9057 | 6556 |
9058 /** | |
9059 * Toggle the opened state of the overlay. | |
9060 */ | |
9061 toggle: function() { | 6557 toggle: function() { |
9062 this._setCanceled(false); | 6558 this._setCanceled(false); |
9063 this.opened = !this.opened; | 6559 this.opened = !this.opened; |
9064 }, | 6560 }, |
9065 | 6561 |
9066 /** | |
9067 * Open the overlay. | |
9068 */ | |
9069 open: function() { | 6562 open: function() { |
9070 this._setCanceled(false); | 6563 this._setCanceled(false); |
9071 this.opened = true; | 6564 this.opened = true; |
9072 }, | 6565 }, |
9073 | 6566 |
9074 /** | |
9075 * Close the overlay. | |
9076 */ | |
9077 close: function() { | 6567 close: function() { |
9078 this._setCanceled(false); | 6568 this._setCanceled(false); |
9079 this.opened = false; | 6569 this.opened = false; |
9080 }, | 6570 }, |
9081 | 6571 |
9082 /** | |
9083 * Cancels the overlay. | |
9084 * @param {Event=} event The original event | |
9085 */ | |
9086 cancel: function(event) { | 6572 cancel: function(event) { |
9087 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t
rue}); | 6573 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t
rue}); |
9088 if (cancelEvent.defaultPrevented) { | 6574 if (cancelEvent.defaultPrevented) { |
9089 return; | 6575 return; |
9090 } | 6576 } |
9091 | 6577 |
9092 this._setCanceled(true); | 6578 this._setCanceled(true); |
9093 this.opened = false; | 6579 this.opened = false; |
9094 }, | 6580 }, |
9095 | 6581 |
9096 _ensureSetup: function() { | 6582 _ensureSetup: function() { |
9097 if (this._overlaySetup) { | 6583 if (this._overlaySetup) { |
9098 return; | 6584 return; |
9099 } | 6585 } |
9100 this._overlaySetup = true; | 6586 this._overlaySetup = true; |
9101 this.style.outline = 'none'; | 6587 this.style.outline = 'none'; |
9102 this.style.display = 'none'; | 6588 this.style.display = 'none'; |
9103 }, | 6589 }, |
9104 | 6590 |
9105 /** | |
9106 * Called when `opened` changes. | |
9107 * @param {boolean=} opened | |
9108 * @protected | |
9109 */ | |
9110 _openedChanged: function(opened) { | 6591 _openedChanged: function(opened) { |
9111 if (opened) { | 6592 if (opened) { |
9112 this.removeAttribute('aria-hidden'); | 6593 this.removeAttribute('aria-hidden'); |
9113 } else { | 6594 } else { |
9114 this.setAttribute('aria-hidden', 'true'); | 6595 this.setAttribute('aria-hidden', 'true'); |
9115 } | 6596 } |
9116 | 6597 |
9117 // Defer any animation-related code on attached | |
9118 // (_openedChanged gets called again on attached). | |
9119 if (!this.isAttached) { | 6598 if (!this.isAttached) { |
9120 return; | 6599 return; |
9121 } | 6600 } |
9122 | 6601 |
9123 this.__isAnimating = true; | 6602 this.__isAnimating = true; |
9124 | 6603 |
9125 // Use requestAnimationFrame for non-blocking rendering. | |
9126 this.__onNextAnimationFrame(this.__openedChanged); | 6604 this.__onNextAnimationFrame(this.__openedChanged); |
9127 }, | 6605 }, |
9128 | 6606 |
9129 _canceledChanged: function() { | 6607 _canceledChanged: function() { |
9130 this.closingReason = this.closingReason || {}; | 6608 this.closingReason = this.closingReason || {}; |
9131 this.closingReason.canceled = this.canceled; | 6609 this.closingReason.canceled = this.canceled; |
9132 }, | 6610 }, |
9133 | 6611 |
9134 _withBackdropChanged: function() { | 6612 _withBackdropChanged: function() { |
9135 // If tabindex is already set, no need to override it. | |
9136 if (this.withBackdrop && !this.hasAttribute('tabindex')) { | 6613 if (this.withBackdrop && !this.hasAttribute('tabindex')) { |
9137 this.setAttribute('tabindex', '-1'); | 6614 this.setAttribute('tabindex', '-1'); |
9138 this.__shouldRemoveTabIndex = true; | 6615 this.__shouldRemoveTabIndex = true; |
9139 } else if (this.__shouldRemoveTabIndex) { | 6616 } else if (this.__shouldRemoveTabIndex) { |
9140 this.removeAttribute('tabindex'); | 6617 this.removeAttribute('tabindex'); |
9141 this.__shouldRemoveTabIndex = false; | 6618 this.__shouldRemoveTabIndex = false; |
9142 } | 6619 } |
9143 if (this.opened && this.isAttached) { | 6620 if (this.opened && this.isAttached) { |
9144 this._manager.trackBackdrop(); | 6621 this._manager.trackBackdrop(); |
9145 } | 6622 } |
9146 }, | 6623 }, |
9147 | 6624 |
9148 /** | |
9149 * tasks which must occur before opening; e.g. making the element visible. | |
9150 * @protected | |
9151 */ | |
9152 _prepareRenderOpened: function() { | 6625 _prepareRenderOpened: function() { |
9153 // Store focused node. | |
9154 this.__restoreFocusNode = this._manager.deepActiveElement; | 6626 this.__restoreFocusNode = this._manager.deepActiveElement; |
9155 | 6627 |
9156 // Needed to calculate the size of the overlay so that transitions on its
size | |
9157 // will have the correct starting points. | |
9158 this._preparePositioning(); | 6628 this._preparePositioning(); |
9159 this.refit(); | 6629 this.refit(); |
9160 this._finishPositioning(); | 6630 this._finishPositioning(); |
9161 | 6631 |
9162 // Safari will apply the focus to the autofocus element when displayed | |
9163 // for the first time, so we make sure to return the focus where it was. | |
9164 if (this.noAutoFocus && document.activeElement === this._focusNode) { | 6632 if (this.noAutoFocus && document.activeElement === this._focusNode) { |
9165 this._focusNode.blur(); | 6633 this._focusNode.blur(); |
9166 this.__restoreFocusNode.focus(); | 6634 this.__restoreFocusNode.focus(); |
9167 } | 6635 } |
9168 }, | 6636 }, |
9169 | 6637 |
9170 /** | |
9171 * Tasks which cause the overlay to actually open; typically play an animati
on. | |
9172 * @protected | |
9173 */ | |
9174 _renderOpened: function() { | 6638 _renderOpened: function() { |
9175 this._finishRenderOpened(); | 6639 this._finishRenderOpened(); |
9176 }, | 6640 }, |
9177 | 6641 |
9178 /** | |
9179 * Tasks which cause the overlay to actually close; typically play an animat
ion. | |
9180 * @protected | |
9181 */ | |
9182 _renderClosed: function() { | 6642 _renderClosed: function() { |
9183 this._finishRenderClosed(); | 6643 this._finishRenderClosed(); |
9184 }, | 6644 }, |
9185 | 6645 |
9186 /** | |
9187 * Tasks to be performed at the end of open action. Will fire `iron-overlay-
opened`. | |
9188 * @protected | |
9189 */ | |
9190 _finishRenderOpened: function() { | 6646 _finishRenderOpened: function() { |
9191 this.notifyResize(); | 6647 this.notifyResize(); |
9192 this.__isAnimating = false; | 6648 this.__isAnimating = false; |
9193 | 6649 |
9194 // Store it so we don't query too much. | |
9195 var focusableNodes = this._focusableNodes; | 6650 var focusableNodes = this._focusableNodes; |
9196 this.__firstFocusableNode = focusableNodes[0]; | 6651 this.__firstFocusableNode = focusableNodes[0]; |
9197 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1]; | 6652 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1]; |
9198 | 6653 |
9199 this.fire('iron-overlay-opened'); | 6654 this.fire('iron-overlay-opened'); |
9200 }, | 6655 }, |
9201 | 6656 |
9202 /** | |
9203 * Tasks to be performed at the end of close action. Will fire `iron-overlay
-closed`. | |
9204 * @protected | |
9205 */ | |
9206 _finishRenderClosed: function() { | 6657 _finishRenderClosed: function() { |
9207 // Hide the overlay. | |
9208 this.style.display = 'none'; | 6658 this.style.display = 'none'; |
9209 // Reset z-index only at the end of the animation. | |
9210 this.style.zIndex = ''; | 6659 this.style.zIndex = ''; |
9211 this.notifyResize(); | 6660 this.notifyResize(); |
9212 this.__isAnimating = false; | 6661 this.__isAnimating = false; |
9213 this.fire('iron-overlay-closed', this.closingReason); | 6662 this.fire('iron-overlay-closed', this.closingReason); |
9214 }, | 6663 }, |
9215 | 6664 |
9216 _preparePositioning: function() { | 6665 _preparePositioning: function() { |
9217 this.style.transition = this.style.webkitTransition = 'none'; | 6666 this.style.transition = this.style.webkitTransition = 'none'; |
9218 this.style.transform = this.style.webkitTransform = 'none'; | 6667 this.style.transform = this.style.webkitTransform = 'none'; |
9219 this.style.display = ''; | 6668 this.style.display = ''; |
9220 }, | 6669 }, |
9221 | 6670 |
9222 _finishPositioning: function() { | 6671 _finishPositioning: function() { |
9223 // First, make it invisible & reactivate animations. | |
9224 this.style.display = 'none'; | 6672 this.style.display = 'none'; |
9225 // Force reflow before re-enabling animations so that they don't start. | |
9226 // Set scrollTop to itself so that Closure Compiler doesn't remove this. | |
9227 this.scrollTop = this.scrollTop; | 6673 this.scrollTop = this.scrollTop; |
9228 this.style.transition = this.style.webkitTransition = ''; | 6674 this.style.transition = this.style.webkitTransition = ''; |
9229 this.style.transform = this.style.webkitTransform = ''; | 6675 this.style.transform = this.style.webkitTransform = ''; |
9230 // Now that animations are enabled, make it visible again | |
9231 this.style.display = ''; | 6676 this.style.display = ''; |
9232 // Force reflow, so that following animations are properly started. | |
9233 // Set scrollTop to itself so that Closure Compiler doesn't remove this. | |
9234 this.scrollTop = this.scrollTop; | 6677 this.scrollTop = this.scrollTop; |
9235 }, | 6678 }, |
9236 | 6679 |
9237 /** | |
9238 * Applies focus according to the opened state. | |
9239 * @protected | |
9240 */ | |
9241 _applyFocus: function() { | 6680 _applyFocus: function() { |
9242 if (this.opened) { | 6681 if (this.opened) { |
9243 if (!this.noAutoFocus) { | 6682 if (!this.noAutoFocus) { |
9244 this._focusNode.focus(); | 6683 this._focusNode.focus(); |
9245 } | 6684 } |
9246 } | 6685 } |
9247 else { | 6686 else { |
9248 this._focusNode.blur(); | 6687 this._focusNode.blur(); |
9249 this._focusedChild = null; | 6688 this._focusedChild = null; |
9250 // Restore focus. | |
9251 if (this.restoreFocusOnClose && this.__restoreFocusNode) { | 6689 if (this.restoreFocusOnClose && this.__restoreFocusNode) { |
9252 this.__restoreFocusNode.focus(); | 6690 this.__restoreFocusNode.focus(); |
9253 } | 6691 } |
9254 this.__restoreFocusNode = null; | 6692 this.__restoreFocusNode = null; |
9255 // If many overlays get closed at the same time, one of them would still | |
9256 // be the currentOverlay even if already closed, and would call _applyFo
cus | |
9257 // infinitely, so we check for this not to be the current overlay. | |
9258 var currentOverlay = this._manager.currentOverlay(); | 6693 var currentOverlay = this._manager.currentOverlay(); |
9259 if (currentOverlay && this !== currentOverlay) { | 6694 if (currentOverlay && this !== currentOverlay) { |
9260 currentOverlay._applyFocus(); | 6695 currentOverlay._applyFocus(); |
9261 } | 6696 } |
9262 } | 6697 } |
9263 }, | 6698 }, |
9264 | 6699 |
9265 /** | |
9266 * Cancels (closes) the overlay. Call when click happens outside the overlay
. | |
9267 * @param {!Event} event | |
9268 * @protected | |
9269 */ | |
9270 _onCaptureClick: function(event) { | 6700 _onCaptureClick: function(event) { |
9271 if (!this.noCancelOnOutsideClick) { | 6701 if (!this.noCancelOnOutsideClick) { |
9272 this.cancel(event); | 6702 this.cancel(event); |
9273 } | 6703 } |
9274 }, | 6704 }, |
9275 | 6705 |
9276 /** | |
9277 * Keeps track of the focused child. If withBackdrop, traps focus within ove
rlay. | |
9278 * @param {!Event} event | |
9279 * @protected | |
9280 */ | |
9281 _onCaptureFocus: function (event) { | 6706 _onCaptureFocus: function (event) { |
9282 if (!this.withBackdrop) { | 6707 if (!this.withBackdrop) { |
9283 return; | 6708 return; |
9284 } | 6709 } |
9285 var path = Polymer.dom(event).path; | 6710 var path = Polymer.dom(event).path; |
9286 if (path.indexOf(this) === -1) { | 6711 if (path.indexOf(this) === -1) { |
9287 event.stopPropagation(); | 6712 event.stopPropagation(); |
9288 this._applyFocus(); | 6713 this._applyFocus(); |
9289 } else { | 6714 } else { |
9290 this._focusedChild = path[0]; | 6715 this._focusedChild = path[0]; |
9291 } | 6716 } |
9292 }, | 6717 }, |
9293 | 6718 |
9294 /** | |
9295 * Handles the ESC key event and cancels (closes) the overlay. | |
9296 * @param {!Event} event | |
9297 * @protected | |
9298 */ | |
9299 _onCaptureEsc: function(event) { | 6719 _onCaptureEsc: function(event) { |
9300 if (!this.noCancelOnEscKey) { | 6720 if (!this.noCancelOnEscKey) { |
9301 this.cancel(event); | 6721 this.cancel(event); |
9302 } | 6722 } |
9303 }, | 6723 }, |
9304 | 6724 |
9305 /** | |
9306 * Handles TAB key events to track focus changes. | |
9307 * Will wrap focus for overlays withBackdrop. | |
9308 * @param {!Event} event | |
9309 * @protected | |
9310 */ | |
9311 _onCaptureTab: function(event) { | 6725 _onCaptureTab: function(event) { |
9312 if (!this.withBackdrop) { | 6726 if (!this.withBackdrop) { |
9313 return; | 6727 return; |
9314 } | 6728 } |
9315 // TAB wraps from last to first focusable. | |
9316 // Shift + TAB wraps from first to last focusable. | |
9317 var shift = event.shiftKey; | 6729 var shift = event.shiftKey; |
9318 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable
Node; | 6730 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable
Node; |
9319 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo
de; | 6731 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo
de; |
9320 var shouldWrap = false; | 6732 var shouldWrap = false; |
9321 if (nodeToCheck === nodeToSet) { | 6733 if (nodeToCheck === nodeToSet) { |
9322 // If nodeToCheck is the same as nodeToSet, it means we have an overlay | |
9323 // with 0 or 1 focusables; in either case we still need to trap the | |
9324 // focus within the overlay. | |
9325 shouldWrap = true; | 6734 shouldWrap = true; |
9326 } else { | 6735 } else { |
9327 // In dom=shadow, the manager will receive focus changes on the main | |
9328 // root but not the ones within other shadow roots, so we can't rely on | |
9329 // _focusedChild, but we should check the deepest active element. | |
9330 var focusedNode = this._manager.deepActiveElement; | 6736 var focusedNode = this._manager.deepActiveElement; |
9331 // If the active element is not the nodeToCheck but the overlay itself, | |
9332 // it means the focus is about to go outside the overlay, hence we | |
9333 // should prevent that (e.g. user opens the overlay and hit Shift+TAB). | |
9334 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this); | 6737 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this); |
9335 } | 6738 } |
9336 | 6739 |
9337 if (shouldWrap) { | 6740 if (shouldWrap) { |
9338 // When the overlay contains the last focusable element of the document | |
9339 // and it's already focused, pressing TAB would move the focus outside | |
9340 // the document (e.g. to the browser search bar). Similarly, when the | |
9341 // overlay contains the first focusable element of the document and it's | |
9342 // already focused, pressing Shift+TAB would move the focus outside the | |
9343 // document (e.g. to the browser search bar). | |
9344 // In both cases, we would not receive a focus event, but only a blur. | |
9345 // In order to achieve focus wrapping, we prevent this TAB event and | |
9346 // force the focus. This will also prevent the focus to temporarily move | |
9347 // outside the overlay, which might cause scrolling. | |
9348 event.preventDefault(); | 6741 event.preventDefault(); |
9349 this._focusedChild = nodeToSet; | 6742 this._focusedChild = nodeToSet; |
9350 this._applyFocus(); | 6743 this._applyFocus(); |
9351 } | 6744 } |
9352 }, | 6745 }, |
9353 | 6746 |
9354 /** | |
9355 * Refits if the overlay is opened and not animating. | |
9356 * @protected | |
9357 */ | |
9358 _onIronResize: function() { | 6747 _onIronResize: function() { |
9359 if (this.opened && !this.__isAnimating) { | 6748 if (this.opened && !this.__isAnimating) { |
9360 this.__onNextAnimationFrame(this.refit); | 6749 this.__onNextAnimationFrame(this.refit); |
9361 } | 6750 } |
9362 }, | 6751 }, |
9363 | 6752 |
9364 /** | |
9365 * Will call notifyResize if overlay is opened. | |
9366 * Can be overridden in order to avoid multiple observers on the same node. | |
9367 * @protected | |
9368 */ | |
9369 _onNodesChange: function() { | 6753 _onNodesChange: function() { |
9370 if (this.opened && !this.__isAnimating) { | 6754 if (this.opened && !this.__isAnimating) { |
9371 this.notifyResize(); | 6755 this.notifyResize(); |
9372 } | 6756 } |
9373 }, | 6757 }, |
9374 | 6758 |
9375 /** | |
9376 * Tasks executed when opened changes: prepare for the opening, move the | |
9377 * focus, update the manager, render opened/closed. | |
9378 * @private | |
9379 */ | |
9380 __openedChanged: function() { | 6759 __openedChanged: function() { |
9381 if (this.opened) { | 6760 if (this.opened) { |
9382 // Make overlay visible, then add it to the manager. | |
9383 this._prepareRenderOpened(); | 6761 this._prepareRenderOpened(); |
9384 this._manager.addOverlay(this); | 6762 this._manager.addOverlay(this); |
9385 // Move the focus to the child node with [autofocus]. | |
9386 this._applyFocus(); | 6763 this._applyFocus(); |
9387 | 6764 |
9388 this._renderOpened(); | 6765 this._renderOpened(); |
9389 } else { | 6766 } else { |
9390 // Remove overlay, then restore the focus before actually closing. | |
9391 this._manager.removeOverlay(this); | 6767 this._manager.removeOverlay(this); |
9392 this._applyFocus(); | 6768 this._applyFocus(); |
9393 | 6769 |
9394 this._renderClosed(); | 6770 this._renderClosed(); |
9395 } | 6771 } |
9396 }, | 6772 }, |
9397 | 6773 |
9398 /** | |
9399 * Executes a callback on the next animation frame, overriding any previous | |
9400 * callback awaiting for the next animation frame. e.g. | |
9401 * `__onNextAnimationFrame(callback1) && __onNextAnimationFrame(callback2)`; | |
9402 * `callback1` will never be invoked. | |
9403 * @param {!Function} callback Its `this` parameter is the overlay itself. | |
9404 * @private | |
9405 */ | |
9406 __onNextAnimationFrame: function(callback) { | 6774 __onNextAnimationFrame: function(callback) { |
9407 if (this.__raf) { | 6775 if (this.__raf) { |
9408 window.cancelAnimationFrame(this.__raf); | 6776 window.cancelAnimationFrame(this.__raf); |
9409 } | 6777 } |
9410 var self = this; | 6778 var self = this; |
9411 this.__raf = window.requestAnimationFrame(function nextAnimationFrame() { | 6779 this.__raf = window.requestAnimationFrame(function nextAnimationFrame() { |
9412 self.__raf = null; | 6780 self.__raf = null; |
9413 callback.call(self); | 6781 callback.call(self); |
9414 }); | 6782 }); |
9415 } | 6783 } |
9416 | 6784 |
9417 }; | 6785 }; |
9418 | 6786 |
9419 /** @polymerBehavior */ | 6787 /** @polymerBehavior */ |
9420 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; | 6788 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; |
9421 | 6789 |
9422 /** | |
9423 * Fired after the overlay opens. | |
9424 * @event iron-overlay-opened | |
9425 */ | |
9426 | 6790 |
9427 /** | |
9428 * Fired when the overlay is canceled, but before it is closed. | |
9429 * @event iron-overlay-canceled | |
9430 * @param {Event} event The closing of the overlay can be prevented | |
9431 * by calling `event.preventDefault()`. The `event.detail` is the original eve
nt that | |
9432 * originated the canceling (e.g. ESC keyboard event or click event outside th
e overlay). | |
9433 */ | |
9434 | 6791 |
9435 /** | |
9436 * Fired after the overlay closes. | |
9437 * @event iron-overlay-closed | |
9438 * @param {Event} event The `event.detail` is the `closingReason` property | |
9439 * (contains `canceled`, whether the overlay was canceled). | |
9440 */ | |
9441 | 6792 |
9442 })(); | 6793 })(); |
9443 /** | |
9444 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim
ations for use with | |
9445 * elements implementing `Polymer.NeonAnimationRunnerBehavior`. | |
9446 * @polymerBehavior | |
9447 */ | |
9448 Polymer.NeonAnimatableBehavior = { | 6794 Polymer.NeonAnimatableBehavior = { |
9449 | 6795 |
9450 properties: { | 6796 properties: { |
9451 | 6797 |
9452 /** | |
9453 * Animation configuration. See README for more info. | |
9454 */ | |
9455 animationConfig: { | 6798 animationConfig: { |
9456 type: Object | 6799 type: Object |
9457 }, | 6800 }, |
9458 | 6801 |
9459 /** | |
9460 * Convenience property for setting an 'entry' animation. Do not set `anim
ationConfig.entry` | |
9461 * manually if using this. The animated node is set to `this` if using thi
s property. | |
9462 */ | |
9463 entryAnimation: { | 6802 entryAnimation: { |
9464 observer: '_entryAnimationChanged', | 6803 observer: '_entryAnimationChanged', |
9465 type: String | 6804 type: String |
9466 }, | 6805 }, |
9467 | 6806 |
9468 /** | |
9469 * Convenience property for setting an 'exit' animation. Do not set `anima
tionConfig.exit` | |
9470 * manually if using this. The animated node is set to `this` if using thi
s property. | |
9471 */ | |
9472 exitAnimation: { | 6807 exitAnimation: { |
9473 observer: '_exitAnimationChanged', | 6808 observer: '_exitAnimationChanged', |
9474 type: String | 6809 type: String |
9475 } | 6810 } |
9476 | 6811 |
9477 }, | 6812 }, |
9478 | 6813 |
9479 _entryAnimationChanged: function() { | 6814 _entryAnimationChanged: function() { |
9480 this.animationConfig = this.animationConfig || {}; | 6815 this.animationConfig = this.animationConfig || {}; |
9481 this.animationConfig['entry'] = [{ | 6816 this.animationConfig['entry'] = [{ |
9482 name: this.entryAnimation, | 6817 name: this.entryAnimation, |
9483 node: this | 6818 node: this |
9484 }]; | 6819 }]; |
9485 }, | 6820 }, |
9486 | 6821 |
9487 _exitAnimationChanged: function() { | 6822 _exitAnimationChanged: function() { |
9488 this.animationConfig = this.animationConfig || {}; | 6823 this.animationConfig = this.animationConfig || {}; |
9489 this.animationConfig['exit'] = [{ | 6824 this.animationConfig['exit'] = [{ |
9490 name: this.exitAnimation, | 6825 name: this.exitAnimation, |
9491 node: this | 6826 node: this |
9492 }]; | 6827 }]; |
9493 }, | 6828 }, |
9494 | 6829 |
9495 _copyProperties: function(config1, config2) { | 6830 _copyProperties: function(config1, config2) { |
9496 // shallowly copy properties from config2 to config1 | |
9497 for (var property in config2) { | 6831 for (var property in config2) { |
9498 config1[property] = config2[property]; | 6832 config1[property] = config2[property]; |
9499 } | 6833 } |
9500 }, | 6834 }, |
9501 | 6835 |
9502 _cloneConfig: function(config) { | 6836 _cloneConfig: function(config) { |
9503 var clone = { | 6837 var clone = { |
9504 isClone: true | 6838 isClone: true |
9505 }; | 6839 }; |
9506 this._copyProperties(clone, config); | 6840 this._copyProperties(clone, config); |
9507 return clone; | 6841 return clone; |
9508 }, | 6842 }, |
9509 | 6843 |
9510 _getAnimationConfigRecursive: function(type, map, allConfigs) { | 6844 _getAnimationConfigRecursive: function(type, map, allConfigs) { |
9511 if (!this.animationConfig) { | 6845 if (!this.animationConfig) { |
9512 return; | 6846 return; |
9513 } | 6847 } |
9514 | 6848 |
9515 if(this.animationConfig.value && typeof this.animationConfig.value === 'fu
nction') { | 6849 if(this.animationConfig.value && typeof this.animationConfig.value === 'fu
nction') { |
9516 this._warn(this._logf('playAnimation', "Please put 'animationConfig' ins
ide of your components 'properties' object instead of outside of it.")); | 6850 this._warn(this._logf('playAnimation', "Please put 'animationConfig' ins
ide of your components 'properties' object instead of outside of it.")); |
9517 return; | 6851 return; |
9518 } | 6852 } |
9519 | 6853 |
9520 // type is optional | |
9521 var thisConfig; | 6854 var thisConfig; |
9522 if (type) { | 6855 if (type) { |
9523 thisConfig = this.animationConfig[type]; | 6856 thisConfig = this.animationConfig[type]; |
9524 } else { | 6857 } else { |
9525 thisConfig = this.animationConfig; | 6858 thisConfig = this.animationConfig; |
9526 } | 6859 } |
9527 | 6860 |
9528 if (!Array.isArray(thisConfig)) { | 6861 if (!Array.isArray(thisConfig)) { |
9529 thisConfig = [thisConfig]; | 6862 thisConfig = [thisConfig]; |
9530 } | 6863 } |
9531 | 6864 |
9532 // iterate animations and recurse to process configurations from child nod
es | |
9533 if (thisConfig) { | 6865 if (thisConfig) { |
9534 for (var config, index = 0; config = thisConfig[index]; index++) { | 6866 for (var config, index = 0; config = thisConfig[index]; index++) { |
9535 if (config.animatable) { | 6867 if (config.animatable) { |
9536 config.animatable._getAnimationConfigRecursive(config.type || type,
map, allConfigs); | 6868 config.animatable._getAnimationConfigRecursive(config.type || type,
map, allConfigs); |
9537 } else { | 6869 } else { |
9538 if (config.id) { | 6870 if (config.id) { |
9539 var cachedConfig = map[config.id]; | 6871 var cachedConfig = map[config.id]; |
9540 if (cachedConfig) { | 6872 if (cachedConfig) { |
9541 // merge configurations with the same id, making a clone lazily | |
9542 if (!cachedConfig.isClone) { | 6873 if (!cachedConfig.isClone) { |
9543 map[config.id] = this._cloneConfig(cachedConfig) | 6874 map[config.id] = this._cloneConfig(cachedConfig) |
9544 cachedConfig = map[config.id]; | 6875 cachedConfig = map[config.id]; |
9545 } | 6876 } |
9546 this._copyProperties(cachedConfig, config); | 6877 this._copyProperties(cachedConfig, config); |
9547 } else { | 6878 } else { |
9548 // put any configs with an id into a map | |
9549 map[config.id] = config; | 6879 map[config.id] = config; |
9550 } | 6880 } |
9551 } else { | 6881 } else { |
9552 allConfigs.push(config); | 6882 allConfigs.push(config); |
9553 } | 6883 } |
9554 } | 6884 } |
9555 } | 6885 } |
9556 } | 6886 } |
9557 }, | 6887 }, |
9558 | 6888 |
9559 /** | |
9560 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this
method to configure | |
9561 * an animation with an optional type. Elements implementing `Polymer.NeonAn
imatableBehavior` | |
9562 * should define the property `animationConfig`, which is either a configura
tion object | |
9563 * or a map of animation type to array of configuration objects. | |
9564 */ | |
9565 getAnimationConfig: function(type) { | 6889 getAnimationConfig: function(type) { |
9566 var map = {}; | 6890 var map = {}; |
9567 var allConfigs = []; | 6891 var allConfigs = []; |
9568 this._getAnimationConfigRecursive(type, map, allConfigs); | 6892 this._getAnimationConfigRecursive(type, map, allConfigs); |
9569 // append the configurations saved in the map to the array | |
9570 for (var key in map) { | 6893 for (var key in map) { |
9571 allConfigs.push(map[key]); | 6894 allConfigs.push(map[key]); |
9572 } | 6895 } |
9573 return allConfigs; | 6896 return allConfigs; |
9574 } | 6897 } |
9575 | 6898 |
9576 }; | 6899 }; |
9577 /** | |
9578 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations. | |
9579 * | |
9580 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior | |
9581 */ | |
9582 Polymer.NeonAnimationRunnerBehaviorImpl = { | 6900 Polymer.NeonAnimationRunnerBehaviorImpl = { |
9583 | 6901 |
9584 _configureAnimations: function(configs) { | 6902 _configureAnimations: function(configs) { |
9585 var results = []; | 6903 var results = []; |
9586 if (configs.length > 0) { | 6904 if (configs.length > 0) { |
9587 for (var config, index = 0; config = configs[index]; index++) { | 6905 for (var config, index = 0; config = configs[index]; index++) { |
9588 var neonAnimation = document.createElement(config.name); | 6906 var neonAnimation = document.createElement(config.name); |
9589 // is this element actually a neon animation? | |
9590 if (neonAnimation.isNeonAnimation) { | 6907 if (neonAnimation.isNeonAnimation) { |
9591 var result = null; | 6908 var result = null; |
9592 // configuration or play could fail if polyfills aren't loaded | |
9593 try { | 6909 try { |
9594 result = neonAnimation.configure(config); | 6910 result = neonAnimation.configure(config); |
9595 // Check if we have an Effect rather than an Animation | |
9596 if (typeof result.cancel != 'function') { | 6911 if (typeof result.cancel != 'function') { |
9597 result = document.timeline.play(result); | 6912 result = document.timeline.play(result); |
9598 } | 6913 } |
9599 } catch (e) { | 6914 } catch (e) { |
9600 result = null; | 6915 result = null; |
9601 console.warn('Couldnt play', '(', config.name, ').', e); | 6916 console.warn('Couldnt play', '(', config.name, ').', e); |
9602 } | 6917 } |
9603 if (result) { | 6918 if (result) { |
9604 results.push({ | 6919 results.push({ |
9605 neonAnimation: neonAnimation, | 6920 neonAnimation: neonAnimation, |
(...skipping 22 matching lines...) Expand all Loading... |
9628 | 6943 |
9629 _complete: function(activeEntries) { | 6944 _complete: function(activeEntries) { |
9630 for (var i = 0; i < activeEntries.length; i++) { | 6945 for (var i = 0; i < activeEntries.length; i++) { |
9631 activeEntries[i].neonAnimation.complete(activeEntries[i].config); | 6946 activeEntries[i].neonAnimation.complete(activeEntries[i].config); |
9632 } | 6947 } |
9633 for (var i = 0; i < activeEntries.length; i++) { | 6948 for (var i = 0; i < activeEntries.length; i++) { |
9634 activeEntries[i].animation.cancel(); | 6949 activeEntries[i].animation.cancel(); |
9635 } | 6950 } |
9636 }, | 6951 }, |
9637 | 6952 |
9638 /** | |
9639 * Plays an animation with an optional `type`. | |
9640 * @param {string=} type | |
9641 * @param {!Object=} cookie | |
9642 */ | |
9643 playAnimation: function(type, cookie) { | 6953 playAnimation: function(type, cookie) { |
9644 var configs = this.getAnimationConfig(type); | 6954 var configs = this.getAnimationConfig(type); |
9645 if (!configs) { | 6955 if (!configs) { |
9646 return; | 6956 return; |
9647 } | 6957 } |
9648 this._active = this._active || {}; | 6958 this._active = this._active || {}; |
9649 if (this._active[type]) { | 6959 if (this._active[type]) { |
9650 this._complete(this._active[type]); | 6960 this._complete(this._active[type]); |
9651 delete this._active[type]; | 6961 delete this._active[type]; |
9652 } | 6962 } |
(...skipping 11 matching lines...) Expand all Loading... |
9664 activeEntries[i].animation.onfinish = function() { | 6974 activeEntries[i].animation.onfinish = function() { |
9665 if (this._shouldComplete(activeEntries)) { | 6975 if (this._shouldComplete(activeEntries)) { |
9666 this._complete(activeEntries); | 6976 this._complete(activeEntries); |
9667 delete this._active[type]; | 6977 delete this._active[type]; |
9668 this.fire('neon-animation-finish', cookie, {bubbles: false}); | 6978 this.fire('neon-animation-finish', cookie, {bubbles: false}); |
9669 } | 6979 } |
9670 }.bind(this); | 6980 }.bind(this); |
9671 } | 6981 } |
9672 }, | 6982 }, |
9673 | 6983 |
9674 /** | |
9675 * Cancels the currently running animations. | |
9676 */ | |
9677 cancelAnimation: function() { | 6984 cancelAnimation: function() { |
9678 for (var k in this._animations) { | 6985 for (var k in this._animations) { |
9679 this._animations[k].cancel(); | 6986 this._animations[k].cancel(); |
9680 } | 6987 } |
9681 this._animations = {}; | 6988 this._animations = {}; |
9682 } | 6989 } |
9683 }; | 6990 }; |
9684 | 6991 |
9685 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ | 6992 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ |
9686 Polymer.NeonAnimationRunnerBehavior = [ | 6993 Polymer.NeonAnimationRunnerBehavior = [ |
9687 Polymer.NeonAnimatableBehavior, | 6994 Polymer.NeonAnimatableBehavior, |
9688 Polymer.NeonAnimationRunnerBehaviorImpl | 6995 Polymer.NeonAnimationRunnerBehaviorImpl |
9689 ]; | 6996 ]; |
9690 /** | |
9691 * Use `Polymer.NeonAnimationBehavior` to implement an animation. | |
9692 * @polymerBehavior | |
9693 */ | |
9694 Polymer.NeonAnimationBehavior = { | 6997 Polymer.NeonAnimationBehavior = { |
9695 | 6998 |
9696 properties: { | 6999 properties: { |
9697 | 7000 |
9698 /** | |
9699 * Defines the animation timing. | |
9700 */ | |
9701 animationTiming: { | 7001 animationTiming: { |
9702 type: Object, | 7002 type: Object, |
9703 value: function() { | 7003 value: function() { |
9704 return { | 7004 return { |
9705 duration: 500, | 7005 duration: 500, |
9706 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', | 7006 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', |
9707 fill: 'both' | 7007 fill: 'both' |
9708 } | 7008 } |
9709 } | 7009 } |
9710 } | 7010 } |
9711 | 7011 |
9712 }, | 7012 }, |
9713 | 7013 |
9714 /** | |
9715 * Can be used to determine that elements implement this behavior. | |
9716 */ | |
9717 isNeonAnimation: true, | 7014 isNeonAnimation: true, |
9718 | 7015 |
9719 /** | |
9720 * Do any animation configuration here. | |
9721 */ | |
9722 // configure: function(config) { | |
9723 // }, | |
9724 | 7016 |
9725 /** | |
9726 * Returns the animation timing by mixing in properties from `config` to the
defaults defined | |
9727 * by the animation. | |
9728 */ | |
9729 timingFromConfig: function(config) { | 7017 timingFromConfig: function(config) { |
9730 if (config.timing) { | 7018 if (config.timing) { |
9731 for (var property in config.timing) { | 7019 for (var property in config.timing) { |
9732 this.animationTiming[property] = config.timing[property]; | 7020 this.animationTiming[property] = config.timing[property]; |
9733 } | 7021 } |
9734 } | 7022 } |
9735 return this.animationTiming; | 7023 return this.animationTiming; |
9736 }, | 7024 }, |
9737 | 7025 |
9738 /** | |
9739 * Sets `transform` and `transformOrigin` properties along with the prefixed
versions. | |
9740 */ | |
9741 setPrefixedProperty: function(node, property, value) { | 7026 setPrefixedProperty: function(node, property, value) { |
9742 var map = { | 7027 var map = { |
9743 'transform': ['webkitTransform'], | 7028 'transform': ['webkitTransform'], |
9744 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] | 7029 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] |
9745 }; | 7030 }; |
9746 var prefixes = map[property]; | 7031 var prefixes = map[property]; |
9747 for (var prefix, index = 0; prefix = prefixes[index]; index++) { | 7032 for (var prefix, index = 0; prefix = prefixes[index]; index++) { |
9748 node.style[prefix] = value; | 7033 node.style[prefix] = value; |
9749 } | 7034 } |
9750 node.style[property] = value; | 7035 node.style[property] = value; |
9751 }, | 7036 }, |
9752 | 7037 |
9753 /** | |
9754 * Called when the animation finishes. | |
9755 */ | |
9756 complete: function() {} | 7038 complete: function() {} |
9757 | 7039 |
9758 }; | 7040 }; |
9759 Polymer({ | 7041 Polymer({ |
9760 | 7042 |
9761 is: 'opaque-animation', | 7043 is: 'opaque-animation', |
9762 | 7044 |
9763 behaviors: [ | 7045 behaviors: [ |
9764 Polymer.NeonAnimationBehavior | 7046 Polymer.NeonAnimationBehavior |
9765 ], | 7047 ], |
9766 | 7048 |
9767 configure: function(config) { | 7049 configure: function(config) { |
9768 var node = config.node; | 7050 var node = config.node; |
9769 this._effect = new KeyframeEffect(node, [ | 7051 this._effect = new KeyframeEffect(node, [ |
9770 {'opacity': '1'}, | 7052 {'opacity': '1'}, |
9771 {'opacity': '1'} | 7053 {'opacity': '1'} |
9772 ], this.timingFromConfig(config)); | 7054 ], this.timingFromConfig(config)); |
9773 node.style.opacity = '0'; | 7055 node.style.opacity = '0'; |
9774 return this._effect; | 7056 return this._effect; |
9775 }, | 7057 }, |
9776 | 7058 |
9777 complete: function(config) { | 7059 complete: function(config) { |
9778 config.node.style.opacity = ''; | 7060 config.node.style.opacity = ''; |
9779 } | 7061 } |
9780 | 7062 |
9781 }); | 7063 }); |
9782 (function() { | 7064 (function() { |
9783 'use strict'; | 7065 'use strict'; |
9784 // Used to calculate the scroll direction during touch events. | |
9785 var LAST_TOUCH_POSITION = { | 7066 var LAST_TOUCH_POSITION = { |
9786 pageX: 0, | 7067 pageX: 0, |
9787 pageY: 0 | 7068 pageY: 0 |
9788 }; | 7069 }; |
9789 // Used to avoid computing event.path and filter scrollable nodes (better pe
rf). | |
9790 var ROOT_TARGET = null; | 7070 var ROOT_TARGET = null; |
9791 var SCROLLABLE_NODES = []; | 7071 var SCROLLABLE_NODES = []; |
9792 | 7072 |
9793 /** | |
9794 * The IronDropdownScrollManager is intended to provide a central source | |
9795 * of authority and control over which elements in a document are currently | |
9796 * allowed to scroll. | |
9797 */ | |
9798 | 7073 |
9799 Polymer.IronDropdownScrollManager = { | 7074 Polymer.IronDropdownScrollManager = { |
9800 | 7075 |
9801 /** | |
9802 * The current element that defines the DOM boundaries of the | |
9803 * scroll lock. This is always the most recently locking element. | |
9804 */ | |
9805 get currentLockingElement() { | 7076 get currentLockingElement() { |
9806 return this._lockingElements[this._lockingElements.length - 1]; | 7077 return this._lockingElements[this._lockingElements.length - 1]; |
9807 }, | 7078 }, |
9808 | 7079 |
9809 /** | |
9810 * Returns true if the provided element is "scroll locked", which is to | |
9811 * say that it cannot be scrolled via pointer or keyboard interactions. | |
9812 * | |
9813 * @param {HTMLElement} element An HTML element instance which may or may | |
9814 * not be scroll locked. | |
9815 */ | |
9816 elementIsScrollLocked: function(element) { | 7080 elementIsScrollLocked: function(element) { |
9817 var currentLockingElement = this.currentLockingElement; | 7081 var currentLockingElement = this.currentLockingElement; |
9818 | 7082 |
9819 if (currentLockingElement === undefined) | 7083 if (currentLockingElement === undefined) |
9820 return false; | 7084 return false; |
9821 | 7085 |
9822 var scrollLocked; | 7086 var scrollLocked; |
9823 | 7087 |
9824 if (this._hasCachedLockedElement(element)) { | 7088 if (this._hasCachedLockedElement(element)) { |
9825 return true; | 7089 return true; |
9826 } | 7090 } |
9827 | 7091 |
9828 if (this._hasCachedUnlockedElement(element)) { | 7092 if (this._hasCachedUnlockedElement(element)) { |
9829 return false; | 7093 return false; |
9830 } | 7094 } |
9831 | 7095 |
9832 scrollLocked = !!currentLockingElement && | 7096 scrollLocked = !!currentLockingElement && |
9833 currentLockingElement !== element && | 7097 currentLockingElement !== element && |
9834 !this._composedTreeContains(currentLockingElement, element); | 7098 !this._composedTreeContains(currentLockingElement, element); |
9835 | 7099 |
9836 if (scrollLocked) { | 7100 if (scrollLocked) { |
9837 this._lockedElementCache.push(element); | 7101 this._lockedElementCache.push(element); |
9838 } else { | 7102 } else { |
9839 this._unlockedElementCache.push(element); | 7103 this._unlockedElementCache.push(element); |
9840 } | 7104 } |
9841 | 7105 |
9842 return scrollLocked; | 7106 return scrollLocked; |
9843 }, | 7107 }, |
9844 | 7108 |
9845 /** | |
9846 * Push an element onto the current scroll lock stack. The most recently | |
9847 * pushed element and its children will be considered scrollable. All | |
9848 * other elements will not be scrollable. | |
9849 * | |
9850 * Scroll locking is implemented as a stack so that cases such as | |
9851 * dropdowns within dropdowns are handled well. | |
9852 * | |
9853 * @param {HTMLElement} element The element that should lock scroll. | |
9854 */ | |
9855 pushScrollLock: function(element) { | 7109 pushScrollLock: function(element) { |
9856 // Prevent pushing the same element twice | |
9857 if (this._lockingElements.indexOf(element) >= 0) { | 7110 if (this._lockingElements.indexOf(element) >= 0) { |
9858 return; | 7111 return; |
9859 } | 7112 } |
9860 | 7113 |
9861 if (this._lockingElements.length === 0) { | 7114 if (this._lockingElements.length === 0) { |
9862 this._lockScrollInteractions(); | 7115 this._lockScrollInteractions(); |
9863 } | 7116 } |
9864 | 7117 |
9865 this._lockingElements.push(element); | 7118 this._lockingElements.push(element); |
9866 | 7119 |
9867 this._lockedElementCache = []; | 7120 this._lockedElementCache = []; |
9868 this._unlockedElementCache = []; | 7121 this._unlockedElementCache = []; |
9869 }, | 7122 }, |
9870 | 7123 |
9871 /** | |
9872 * Remove an element from the scroll lock stack. The element being | |
9873 * removed does not need to be the most recently pushed element. However, | |
9874 * the scroll lock constraints only change when the most recently pushed | |
9875 * element is removed. | |
9876 * | |
9877 * @param {HTMLElement} element The element to remove from the scroll | |
9878 * lock stack. | |
9879 */ | |
9880 removeScrollLock: function(element) { | 7124 removeScrollLock: function(element) { |
9881 var index = this._lockingElements.indexOf(element); | 7125 var index = this._lockingElements.indexOf(element); |
9882 | 7126 |
9883 if (index === -1) { | 7127 if (index === -1) { |
9884 return; | 7128 return; |
9885 } | 7129 } |
9886 | 7130 |
9887 this._lockingElements.splice(index, 1); | 7131 this._lockingElements.splice(index, 1); |
9888 | 7132 |
9889 this._lockedElementCache = []; | 7133 this._lockedElementCache = []; |
(...skipping 12 matching lines...) Expand all Loading... |
9902 | 7146 |
9903 _hasCachedLockedElement: function(element) { | 7147 _hasCachedLockedElement: function(element) { |
9904 return this._lockedElementCache.indexOf(element) > -1; | 7148 return this._lockedElementCache.indexOf(element) > -1; |
9905 }, | 7149 }, |
9906 | 7150 |
9907 _hasCachedUnlockedElement: function(element) { | 7151 _hasCachedUnlockedElement: function(element) { |
9908 return this._unlockedElementCache.indexOf(element) > -1; | 7152 return this._unlockedElementCache.indexOf(element) > -1; |
9909 }, | 7153 }, |
9910 | 7154 |
9911 _composedTreeContains: function(element, child) { | 7155 _composedTreeContains: function(element, child) { |
9912 // NOTE(cdata): This method iterates over content elements and their | |
9913 // corresponding distributed nodes to implement a contains-like method | |
9914 // that pierces through the composed tree of the ShadowDOM. Results of | |
9915 // this operation are cached (elsewhere) on a per-scroll-lock basis, to | |
9916 // guard against potentially expensive lookups happening repeatedly as | |
9917 // a user scrolls / touchmoves. | |
9918 var contentElements; | 7156 var contentElements; |
9919 var distributedNodes; | 7157 var distributedNodes; |
9920 var contentIndex; | 7158 var contentIndex; |
9921 var nodeIndex; | 7159 var nodeIndex; |
9922 | 7160 |
9923 if (element.contains(child)) { | 7161 if (element.contains(child)) { |
9924 return true; | 7162 return true; |
9925 } | 7163 } |
9926 | 7164 |
9927 contentElements = Polymer.dom(element).querySelectorAll('content'); | 7165 contentElements = Polymer.dom(element).querySelectorAll('content'); |
9928 | 7166 |
9929 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { | 7167 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { |
9930 | 7168 |
9931 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); | 7169 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); |
9932 | 7170 |
9933 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ | 7171 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ |
9934 | 7172 |
9935 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ | 7173 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ |
9936 return true; | 7174 return true; |
9937 } | 7175 } |
9938 } | 7176 } |
9939 } | 7177 } |
9940 | 7178 |
9941 return false; | 7179 return false; |
9942 }, | 7180 }, |
9943 | 7181 |
9944 _scrollInteractionHandler: function(event) { | 7182 _scrollInteractionHandler: function(event) { |
9945 // Avoid canceling an event with cancelable=false, e.g. scrolling is in | |
9946 // progress and cannot be interrupted. | |
9947 if (event.cancelable && this._shouldPreventScrolling(event)) { | 7183 if (event.cancelable && this._shouldPreventScrolling(event)) { |
9948 event.preventDefault(); | 7184 event.preventDefault(); |
9949 } | 7185 } |
9950 // If event has targetTouches (touch event), update last touch position. | |
9951 if (event.targetTouches) { | 7186 if (event.targetTouches) { |
9952 var touch = event.targetTouches[0]; | 7187 var touch = event.targetTouches[0]; |
9953 LAST_TOUCH_POSITION.pageX = touch.pageX; | 7188 LAST_TOUCH_POSITION.pageX = touch.pageX; |
9954 LAST_TOUCH_POSITION.pageY = touch.pageY; | 7189 LAST_TOUCH_POSITION.pageY = touch.pageY; |
9955 } | 7190 } |
9956 }, | 7191 }, |
9957 | 7192 |
9958 _lockScrollInteractions: function() { | 7193 _lockScrollInteractions: function() { |
9959 this._boundScrollHandler = this._boundScrollHandler || | 7194 this._boundScrollHandler = this._boundScrollHandler || |
9960 this._scrollInteractionHandler.bind(this); | 7195 this._scrollInteractionHandler.bind(this); |
9961 // Modern `wheel` event for mouse wheel scrolling: | |
9962 document.addEventListener('wheel', this._boundScrollHandler, true); | 7196 document.addEventListener('wheel', this._boundScrollHandler, true); |
9963 // Older, non-standard `mousewheel` event for some FF: | |
9964 document.addEventListener('mousewheel', this._boundScrollHandler, true); | 7197 document.addEventListener('mousewheel', this._boundScrollHandler, true); |
9965 // IE: | |
9966 document.addEventListener('DOMMouseScroll', this._boundScrollHandler, tr
ue); | 7198 document.addEventListener('DOMMouseScroll', this._boundScrollHandler, tr
ue); |
9967 // Save the SCROLLABLE_NODES on touchstart, to be used on touchmove. | |
9968 document.addEventListener('touchstart', this._boundScrollHandler, true); | 7199 document.addEventListener('touchstart', this._boundScrollHandler, true); |
9969 // Mobile devices can scroll on touch move: | |
9970 document.addEventListener('touchmove', this._boundScrollHandler, true); | 7200 document.addEventListener('touchmove', this._boundScrollHandler, true); |
9971 }, | 7201 }, |
9972 | 7202 |
9973 _unlockScrollInteractions: function() { | 7203 _unlockScrollInteractions: function() { |
9974 document.removeEventListener('wheel', this._boundScrollHandler, true); | 7204 document.removeEventListener('wheel', this._boundScrollHandler, true); |
9975 document.removeEventListener('mousewheel', this._boundScrollHandler, tru
e); | 7205 document.removeEventListener('mousewheel', this._boundScrollHandler, tru
e); |
9976 document.removeEventListener('DOMMouseScroll', this._boundScrollHandler,
true); | 7206 document.removeEventListener('DOMMouseScroll', this._boundScrollHandler,
true); |
9977 document.removeEventListener('touchstart', this._boundScrollHandler, tru
e); | 7207 document.removeEventListener('touchstart', this._boundScrollHandler, tru
e); |
9978 document.removeEventListener('touchmove', this._boundScrollHandler, true
); | 7208 document.removeEventListener('touchmove', this._boundScrollHandler, true
); |
9979 }, | 7209 }, |
9980 | 7210 |
9981 /** | |
9982 * Returns true if the event causes scroll outside the current locking | |
9983 * element, e.g. pointer/keyboard interactions, or scroll "leaking" | |
9984 * outside the locking element when it is already at its scroll boundaries
. | |
9985 * @param {!Event} event | |
9986 * @return {boolean} | |
9987 * @private | |
9988 */ | |
9989 _shouldPreventScrolling: function(event) { | 7211 _shouldPreventScrolling: function(event) { |
9990 | 7212 |
9991 // Update if root target changed. For touch events, ensure we don't | |
9992 // update during touchmove. | |
9993 var target = Polymer.dom(event).rootTarget; | 7213 var target = Polymer.dom(event).rootTarget; |
9994 if (event.type !== 'touchmove' && ROOT_TARGET !== target) { | 7214 if (event.type !== 'touchmove' && ROOT_TARGET !== target) { |
9995 ROOT_TARGET = target; | 7215 ROOT_TARGET = target; |
9996 SCROLLABLE_NODES = this._getScrollableNodes(Polymer.dom(event).path); | 7216 SCROLLABLE_NODES = this._getScrollableNodes(Polymer.dom(event).path); |
9997 } | 7217 } |
9998 | 7218 |
9999 // Prevent event if no scrollable nodes. | |
10000 if (!SCROLLABLE_NODES.length) { | 7219 if (!SCROLLABLE_NODES.length) { |
10001 return true; | 7220 return true; |
10002 } | 7221 } |
10003 // Don't prevent touchstart event inside the locking element when it has | |
10004 // scrollable nodes. | |
10005 if (event.type === 'touchstart') { | 7222 if (event.type === 'touchstart') { |
10006 return false; | 7223 return false; |
10007 } | 7224 } |
10008 // Get deltaX/Y. | |
10009 var info = this._getScrollInfo(event); | 7225 var info = this._getScrollInfo(event); |
10010 // Prevent if there is no child that can scroll. | |
10011 return !this._getScrollingNode(SCROLLABLE_NODES, info.deltaX, info.delta
Y); | 7226 return !this._getScrollingNode(SCROLLABLE_NODES, info.deltaX, info.delta
Y); |
10012 }, | 7227 }, |
10013 | 7228 |
10014 /** | |
10015 * Returns an array of scrollable nodes up to the current locking element, | |
10016 * which is included too if scrollable. | |
10017 * @param {!Array<Node>} nodes | |
10018 * @return {Array<Node>} scrollables | |
10019 * @private | |
10020 */ | |
10021 _getScrollableNodes: function(nodes) { | 7229 _getScrollableNodes: function(nodes) { |
10022 var scrollables = []; | 7230 var scrollables = []; |
10023 var lockingIndex = nodes.indexOf(this.currentLockingElement); | 7231 var lockingIndex = nodes.indexOf(this.currentLockingElement); |
10024 // Loop from root target to locking element (included). | |
10025 for (var i = 0; i <= lockingIndex; i++) { | 7232 for (var i = 0; i <= lockingIndex; i++) { |
10026 var node = nodes[i]; | 7233 var node = nodes[i]; |
10027 // Skip document fragments. | |
10028 if (node.nodeType === 11) { | 7234 if (node.nodeType === 11) { |
10029 continue; | 7235 continue; |
10030 } | 7236 } |
10031 // Check inline style before checking computed style. | |
10032 var style = node.style; | 7237 var style = node.style; |
10033 if (style.overflow !== 'scroll' && style.overflow !== 'auto') { | 7238 if (style.overflow !== 'scroll' && style.overflow !== 'auto') { |
10034 style = window.getComputedStyle(node); | 7239 style = window.getComputedStyle(node); |
10035 } | 7240 } |
10036 if (style.overflow === 'scroll' || style.overflow === 'auto') { | 7241 if (style.overflow === 'scroll' || style.overflow === 'auto') { |
10037 scrollables.push(node); | 7242 scrollables.push(node); |
10038 } | 7243 } |
10039 } | 7244 } |
10040 return scrollables; | 7245 return scrollables; |
10041 }, | 7246 }, |
10042 | 7247 |
10043 /** | |
10044 * Returns the node that is scrolling. If there is no scrolling, | |
10045 * returns undefined. | |
10046 * @param {!Array<Node>} nodes | |
10047 * @param {number} deltaX Scroll delta on the x-axis | |
10048 * @param {number} deltaY Scroll delta on the y-axis | |
10049 * @return {Node|undefined} | |
10050 * @private | |
10051 */ | |
10052 _getScrollingNode: function(nodes, deltaX, deltaY) { | 7248 _getScrollingNode: function(nodes, deltaX, deltaY) { |
10053 // No scroll. | |
10054 if (!deltaX && !deltaY) { | 7249 if (!deltaX && !deltaY) { |
10055 return; | 7250 return; |
10056 } | 7251 } |
10057 // Check only one axis according to where there is more scroll. | |
10058 // Prefer vertical to horizontal. | |
10059 var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX); | 7252 var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX); |
10060 for (var i = 0; i < nodes.length; i++) { | 7253 for (var i = 0; i < nodes.length; i++) { |
10061 var node = nodes[i]; | 7254 var node = nodes[i]; |
10062 var canScroll = false; | 7255 var canScroll = false; |
10063 if (verticalScroll) { | 7256 if (verticalScroll) { |
10064 // delta < 0 is scroll up, delta > 0 is scroll down. | |
10065 canScroll = deltaY < 0 ? node.scrollTop > 0 : | 7257 canScroll = deltaY < 0 ? node.scrollTop > 0 : |
10066 node.scrollTop < node.scrollHeight - node.clientHeight; | 7258 node.scrollTop < node.scrollHeight - node.clientHeight; |
10067 } else { | 7259 } else { |
10068 // delta < 0 is scroll left, delta > 0 is scroll right. | |
10069 canScroll = deltaX < 0 ? node.scrollLeft > 0 : | 7260 canScroll = deltaX < 0 ? node.scrollLeft > 0 : |
10070 node.scrollLeft < node.scrollWidth - node.clientWidth; | 7261 node.scrollLeft < node.scrollWidth - node.clientWidth; |
10071 } | 7262 } |
10072 if (canScroll) { | 7263 if (canScroll) { |
10073 return node; | 7264 return node; |
10074 } | 7265 } |
10075 } | 7266 } |
10076 }, | 7267 }, |
10077 | 7268 |
10078 /** | |
10079 * Returns scroll `deltaX` and `deltaY`. | |
10080 * @param {!Event} event The scroll event | |
10081 * @return {{ | |
10082 * deltaX: number The x-axis scroll delta (positive: scroll right, | |
10083 * negative: scroll left, 0: no scroll), | |
10084 * deltaY: number The y-axis scroll delta (positive: scroll down, | |
10085 * negative: scroll up, 0: no scroll) | |
10086 * }} info | |
10087 * @private | |
10088 */ | |
10089 _getScrollInfo: function(event) { | 7269 _getScrollInfo: function(event) { |
10090 var info = { | 7270 var info = { |
10091 deltaX: event.deltaX, | 7271 deltaX: event.deltaX, |
10092 deltaY: event.deltaY | 7272 deltaY: event.deltaY |
10093 }; | 7273 }; |
10094 // Already available. | |
10095 if ('deltaX' in event) { | 7274 if ('deltaX' in event) { |
10096 // do nothing, values are already good. | |
10097 } | 7275 } |
10098 // Safari has scroll info in `wheelDeltaX/Y`. | |
10099 else if ('wheelDeltaX' in event) { | 7276 else if ('wheelDeltaX' in event) { |
10100 info.deltaX = -event.wheelDeltaX; | 7277 info.deltaX = -event.wheelDeltaX; |
10101 info.deltaY = -event.wheelDeltaY; | 7278 info.deltaY = -event.wheelDeltaY; |
10102 } | 7279 } |
10103 // Firefox has scroll info in `detail` and `axis`. | |
10104 else if ('axis' in event) { | 7280 else if ('axis' in event) { |
10105 info.deltaX = event.axis === 1 ? event.detail : 0; | 7281 info.deltaX = event.axis === 1 ? event.detail : 0; |
10106 info.deltaY = event.axis === 2 ? event.detail : 0; | 7282 info.deltaY = event.axis === 2 ? event.detail : 0; |
10107 } | 7283 } |
10108 // On mobile devices, calculate scroll direction. | |
10109 else if (event.targetTouches) { | 7284 else if (event.targetTouches) { |
10110 var touch = event.targetTouches[0]; | 7285 var touch = event.targetTouches[0]; |
10111 // Touch moves from right to left => scrolling goes right. | |
10112 info.deltaX = LAST_TOUCH_POSITION.pageX - touch.pageX; | 7286 info.deltaX = LAST_TOUCH_POSITION.pageX - touch.pageX; |
10113 // Touch moves from down to up => scrolling goes down. | |
10114 info.deltaY = LAST_TOUCH_POSITION.pageY - touch.pageY; | 7287 info.deltaY = LAST_TOUCH_POSITION.pageY - touch.pageY; |
10115 } | 7288 } |
10116 return info; | 7289 return info; |
10117 } | 7290 } |
10118 }; | 7291 }; |
10119 })(); | 7292 })(); |
10120 (function() { | 7293 (function() { |
10121 'use strict'; | 7294 'use strict'; |
10122 | 7295 |
10123 Polymer({ | 7296 Polymer({ |
10124 is: 'iron-dropdown', | 7297 is: 'iron-dropdown', |
10125 | 7298 |
10126 behaviors: [ | 7299 behaviors: [ |
10127 Polymer.IronControlState, | 7300 Polymer.IronControlState, |
10128 Polymer.IronA11yKeysBehavior, | 7301 Polymer.IronA11yKeysBehavior, |
10129 Polymer.IronOverlayBehavior, | 7302 Polymer.IronOverlayBehavior, |
10130 Polymer.NeonAnimationRunnerBehavior | 7303 Polymer.NeonAnimationRunnerBehavior |
10131 ], | 7304 ], |
10132 | 7305 |
10133 properties: { | 7306 properties: { |
10134 /** | |
10135 * The orientation against which to align the dropdown content | |
10136 * horizontally relative to the dropdown trigger. | |
10137 * Overridden from `Polymer.IronFitBehavior`. | |
10138 */ | |
10139 horizontalAlign: { | 7307 horizontalAlign: { |
10140 type: String, | 7308 type: String, |
10141 value: 'left', | 7309 value: 'left', |
10142 reflectToAttribute: true | 7310 reflectToAttribute: true |
10143 }, | 7311 }, |
10144 | 7312 |
10145 /** | |
10146 * The orientation against which to align the dropdown content | |
10147 * vertically relative to the dropdown trigger. | |
10148 * Overridden from `Polymer.IronFitBehavior`. | |
10149 */ | |
10150 verticalAlign: { | 7313 verticalAlign: { |
10151 type: String, | 7314 type: String, |
10152 value: 'top', | 7315 value: 'top', |
10153 reflectToAttribute: true | 7316 reflectToAttribute: true |
10154 }, | 7317 }, |
10155 | 7318 |
10156 /** | |
10157 * An animation config. If provided, this will be used to animate the | |
10158 * opening of the dropdown. | |
10159 */ | |
10160 openAnimationConfig: { | 7319 openAnimationConfig: { |
10161 type: Object | 7320 type: Object |
10162 }, | 7321 }, |
10163 | 7322 |
10164 /** | |
10165 * An animation config. If provided, this will be used to animate the | |
10166 * closing of the dropdown. | |
10167 */ | |
10168 closeAnimationConfig: { | 7323 closeAnimationConfig: { |
10169 type: Object | 7324 type: Object |
10170 }, | 7325 }, |
10171 | 7326 |
10172 /** | |
10173 * If provided, this will be the element that will be focused when | |
10174 * the dropdown opens. | |
10175 */ | |
10176 focusTarget: { | 7327 focusTarget: { |
10177 type: Object | 7328 type: Object |
10178 }, | 7329 }, |
10179 | 7330 |
10180 /** | |
10181 * Set to true to disable animations when opening and closing the | |
10182 * dropdown. | |
10183 */ | |
10184 noAnimations: { | 7331 noAnimations: { |
10185 type: Boolean, | 7332 type: Boolean, |
10186 value: false | 7333 value: false |
10187 }, | 7334 }, |
10188 | 7335 |
10189 /** | |
10190 * By default, the dropdown will constrain scrolling on the page | |
10191 * to itself when opened. | |
10192 * Set to true in order to prevent scroll from being constrained | |
10193 * to the dropdown when it opens. | |
10194 */ | |
10195 allowOutsideScroll: { | 7336 allowOutsideScroll: { |
10196 type: Boolean, | 7337 type: Boolean, |
10197 value: false | 7338 value: false |
10198 }, | 7339 }, |
10199 | 7340 |
10200 /** | |
10201 * Callback for scroll events. | |
10202 * @type {Function} | |
10203 * @private | |
10204 */ | |
10205 _boundOnCaptureScroll: { | 7341 _boundOnCaptureScroll: { |
10206 type: Function, | 7342 type: Function, |
10207 value: function() { | 7343 value: function() { |
10208 return this._onCaptureScroll.bind(this); | 7344 return this._onCaptureScroll.bind(this); |
10209 } | 7345 } |
10210 } | 7346 } |
10211 }, | 7347 }, |
10212 | 7348 |
10213 listeners: { | 7349 listeners: { |
10214 'neon-animation-finish': '_onNeonAnimationFinish' | 7350 'neon-animation-finish': '_onNeonAnimationFinish' |
10215 }, | 7351 }, |
10216 | 7352 |
10217 observers: [ | 7353 observers: [ |
10218 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign
, verticalOffset, horizontalOffset)' | 7354 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign
, verticalOffset, horizontalOffset)' |
10219 ], | 7355 ], |
10220 | 7356 |
10221 /** | |
10222 * The element that is contained by the dropdown, if any. | |
10223 */ | |
10224 get containedElement() { | 7357 get containedElement() { |
10225 return Polymer.dom(this.$.content).getDistributedNodes()[0]; | 7358 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
10226 }, | 7359 }, |
10227 | 7360 |
10228 /** | |
10229 * The element that should be focused when the dropdown opens. | |
10230 * @deprecated | |
10231 */ | |
10232 get _focusTarget() { | 7361 get _focusTarget() { |
10233 return this.focusTarget || this.containedElement; | 7362 return this.focusTarget || this.containedElement; |
10234 }, | 7363 }, |
10235 | 7364 |
10236 ready: function() { | 7365 ready: function() { |
10237 // Memoized scrolling position, used to block scrolling outside. | |
10238 this._scrollTop = 0; | 7366 this._scrollTop = 0; |
10239 this._scrollLeft = 0; | 7367 this._scrollLeft = 0; |
10240 // Used to perform a non-blocking refit on scroll. | |
10241 this._refitOnScrollRAF = null; | 7368 this._refitOnScrollRAF = null; |
10242 }, | 7369 }, |
10243 | 7370 |
10244 detached: function() { | 7371 detached: function() { |
10245 this.cancelAnimation(); | 7372 this.cancelAnimation(); |
10246 Polymer.IronDropdownScrollManager.removeScrollLock(this); | 7373 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
10247 }, | 7374 }, |
10248 | 7375 |
10249 /** | |
10250 * Called when the value of `opened` changes. | |
10251 * Overridden from `IronOverlayBehavior` | |
10252 */ | |
10253 _openedChanged: function() { | 7376 _openedChanged: function() { |
10254 if (this.opened && this.disabled) { | 7377 if (this.opened && this.disabled) { |
10255 this.cancel(); | 7378 this.cancel(); |
10256 } else { | 7379 } else { |
10257 this.cancelAnimation(); | 7380 this.cancelAnimation(); |
10258 this.sizingTarget = this.containedElement || this.sizingTarget; | 7381 this.sizingTarget = this.containedElement || this.sizingTarget; |
10259 this._updateAnimationConfig(); | 7382 this._updateAnimationConfig(); |
10260 this._saveScrollPosition(); | 7383 this._saveScrollPosition(); |
10261 if (this.opened) { | 7384 if (this.opened) { |
10262 document.addEventListener('scroll', this._boundOnCaptureScroll); | 7385 document.addEventListener('scroll', this._boundOnCaptureScroll); |
10263 !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.push
ScrollLock(this); | 7386 !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.push
ScrollLock(this); |
10264 } else { | 7387 } else { |
10265 document.removeEventListener('scroll', this._boundOnCaptureScroll)
; | 7388 document.removeEventListener('scroll', this._boundOnCaptureScroll)
; |
10266 Polymer.IronDropdownScrollManager.removeScrollLock(this); | 7389 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
10267 } | 7390 } |
10268 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); | 7391 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); |
10269 } | 7392 } |
10270 }, | 7393 }, |
10271 | 7394 |
10272 /** | |
10273 * Overridden from `IronOverlayBehavior`. | |
10274 */ | |
10275 _renderOpened: function() { | 7395 _renderOpened: function() { |
10276 if (!this.noAnimations && this.animationConfig.open) { | 7396 if (!this.noAnimations && this.animationConfig.open) { |
10277 this.$.contentWrapper.classList.add('animating'); | 7397 this.$.contentWrapper.classList.add('animating'); |
10278 this.playAnimation('open'); | 7398 this.playAnimation('open'); |
10279 } else { | 7399 } else { |
10280 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; | 7400 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; |
10281 } | 7401 } |
10282 }, | 7402 }, |
10283 | 7403 |
10284 /** | |
10285 * Overridden from `IronOverlayBehavior`. | |
10286 */ | |
10287 _renderClosed: function() { | 7404 _renderClosed: function() { |
10288 | 7405 |
10289 if (!this.noAnimations && this.animationConfig.close) { | 7406 if (!this.noAnimations && this.animationConfig.close) { |
10290 this.$.contentWrapper.classList.add('animating'); | 7407 this.$.contentWrapper.classList.add('animating'); |
10291 this.playAnimation('close'); | 7408 this.playAnimation('close'); |
10292 } else { | 7409 } else { |
10293 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; | 7410 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; |
10294 } | 7411 } |
10295 }, | 7412 }, |
10296 | 7413 |
10297 /** | |
10298 * Called when animation finishes on the dropdown (when opening or | |
10299 * closing). Responsible for "completing" the process of opening or | |
10300 * closing the dropdown by positioning it or setting its display to | |
10301 * none. | |
10302 */ | |
10303 _onNeonAnimationFinish: function() { | 7414 _onNeonAnimationFinish: function() { |
10304 this.$.contentWrapper.classList.remove('animating'); | 7415 this.$.contentWrapper.classList.remove('animating'); |
10305 if (this.opened) { | 7416 if (this.opened) { |
10306 this._finishRenderOpened(); | 7417 this._finishRenderOpened(); |
10307 } else { | 7418 } else { |
10308 this._finishRenderClosed(); | 7419 this._finishRenderClosed(); |
10309 } | 7420 } |
10310 }, | 7421 }, |
10311 | 7422 |
10312 _onCaptureScroll: function() { | 7423 _onCaptureScroll: function() { |
10313 if (!this.allowOutsideScroll) { | 7424 if (!this.allowOutsideScroll) { |
10314 this._restoreScrollPosition(); | 7425 this._restoreScrollPosition(); |
10315 } else { | 7426 } else { |
10316 this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnS
crollRAF); | 7427 this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnS
crollRAF); |
10317 this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bin
d(this)); | 7428 this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bin
d(this)); |
10318 } | 7429 } |
10319 }, | 7430 }, |
10320 | 7431 |
10321 /** | |
10322 * Memoizes the scroll position of the outside scrolling element. | |
10323 * @private | |
10324 */ | |
10325 _saveScrollPosition: function() { | 7432 _saveScrollPosition: function() { |
10326 if (document.scrollingElement) { | 7433 if (document.scrollingElement) { |
10327 this._scrollTop = document.scrollingElement.scrollTop; | 7434 this._scrollTop = document.scrollingElement.scrollTop; |
10328 this._scrollLeft = document.scrollingElement.scrollLeft; | 7435 this._scrollLeft = document.scrollingElement.scrollLeft; |
10329 } else { | 7436 } else { |
10330 // Since we don't know if is the body or html, get max. | |
10331 this._scrollTop = Math.max(document.documentElement.scrollTop, docum
ent.body.scrollTop); | 7437 this._scrollTop = Math.max(document.documentElement.scrollTop, docum
ent.body.scrollTop); |
10332 this._scrollLeft = Math.max(document.documentElement.scrollLeft, doc
ument.body.scrollLeft); | 7438 this._scrollLeft = Math.max(document.documentElement.scrollLeft, doc
ument.body.scrollLeft); |
10333 } | 7439 } |
10334 }, | 7440 }, |
10335 | 7441 |
10336 /** | |
10337 * Resets the scroll position of the outside scrolling element. | |
10338 * @private | |
10339 */ | |
10340 _restoreScrollPosition: function() { | 7442 _restoreScrollPosition: function() { |
10341 if (document.scrollingElement) { | 7443 if (document.scrollingElement) { |
10342 document.scrollingElement.scrollTop = this._scrollTop; | 7444 document.scrollingElement.scrollTop = this._scrollTop; |
10343 document.scrollingElement.scrollLeft = this._scrollLeft; | 7445 document.scrollingElement.scrollLeft = this._scrollLeft; |
10344 } else { | 7446 } else { |
10345 // Since we don't know if is the body or html, set both. | |
10346 document.documentElement.scrollTop = this._scrollTop; | 7447 document.documentElement.scrollTop = this._scrollTop; |
10347 document.documentElement.scrollLeft = this._scrollLeft; | 7448 document.documentElement.scrollLeft = this._scrollLeft; |
10348 document.body.scrollTop = this._scrollTop; | 7449 document.body.scrollTop = this._scrollTop; |
10349 document.body.scrollLeft = this._scrollLeft; | 7450 document.body.scrollLeft = this._scrollLeft; |
10350 } | 7451 } |
10351 }, | 7452 }, |
10352 | 7453 |
10353 /** | |
10354 * Constructs the final animation config from different properties used | |
10355 * to configure specific parts of the opening and closing animations. | |
10356 */ | |
10357 _updateAnimationConfig: function() { | 7454 _updateAnimationConfig: function() { |
10358 var animations = (this.openAnimationConfig || []).concat(this.closeAni
mationConfig || []); | 7455 var animations = (this.openAnimationConfig || []).concat(this.closeAni
mationConfig || []); |
10359 for (var i = 0; i < animations.length; i++) { | 7456 for (var i = 0; i < animations.length; i++) { |
10360 animations[i].node = this.containedElement; | 7457 animations[i].node = this.containedElement; |
10361 } | 7458 } |
10362 this.animationConfig = { | 7459 this.animationConfig = { |
10363 open: this.openAnimationConfig, | 7460 open: this.openAnimationConfig, |
10364 close: this.closeAnimationConfig | 7461 close: this.closeAnimationConfig |
10365 }; | 7462 }; |
10366 }, | 7463 }, |
10367 | 7464 |
10368 /** | |
10369 * Updates the overlay position based on configured horizontal | |
10370 * and vertical alignment. | |
10371 */ | |
10372 _updateOverlayPosition: function() { | 7465 _updateOverlayPosition: function() { |
10373 if (this.isAttached) { | 7466 if (this.isAttached) { |
10374 // This triggers iron-resize, and iron-overlay-behavior will call re
fit if needed. | |
10375 this.notifyResize(); | 7467 this.notifyResize(); |
10376 } | 7468 } |
10377 }, | 7469 }, |
10378 | 7470 |
10379 /** | |
10380 * Apply focus to focusTarget or containedElement | |
10381 */ | |
10382 _applyFocus: function () { | 7471 _applyFocus: function () { |
10383 var focusTarget = this.focusTarget || this.containedElement; | 7472 var focusTarget = this.focusTarget || this.containedElement; |
10384 if (focusTarget && this.opened && !this.noAutoFocus) { | 7473 if (focusTarget && this.opened && !this.noAutoFocus) { |
10385 focusTarget.focus(); | 7474 focusTarget.focus(); |
10386 } else { | 7475 } else { |
10387 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); | 7476 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); |
10388 } | 7477 } |
10389 } | 7478 } |
10390 }); | 7479 }); |
10391 })(); | 7480 })(); |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
10530 behaviors: [Polymer.IronA11yKeysBehavior], | 7619 behaviors: [Polymer.IronA11yKeysBehavior], |
10531 | 7620 |
10532 properties: { | 7621 properties: { |
10533 menuOpen: { | 7622 menuOpen: { |
10534 type: Boolean, | 7623 type: Boolean, |
10535 observer: 'menuOpenChanged_', | 7624 observer: 'menuOpenChanged_', |
10536 value: false, | 7625 value: false, |
10537 notify: true, | 7626 notify: true, |
10538 }, | 7627 }, |
10539 | 7628 |
10540 /** | |
10541 * The contextual item that this menu was clicked for. | |
10542 * e.g. the data used to render an item in an <iron-list> or <dom-repeat> | |
10543 * @type {?Object} | |
10544 */ | |
10545 itemData: { | 7629 itemData: { |
10546 type: Object, | 7630 type: Object, |
10547 value: null, | 7631 value: null, |
10548 }, | 7632 }, |
10549 | 7633 |
10550 /** @override */ | 7634 /** @override */ |
10551 keyEventTarget: { | 7635 keyEventTarget: { |
10552 type: Object, | 7636 type: Object, |
10553 value: function() { | 7637 value: function() { |
10554 return this.$.menu; | 7638 return this.$.menu; |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
10596 }, | 7680 }, |
10597 | 7681 |
10598 keyBindings: { | 7682 keyBindings: { |
10599 'tab': 'onTabPressed_', | 7683 'tab': 'onTabPressed_', |
10600 }, | 7684 }, |
10601 | 7685 |
10602 listeners: { | 7686 listeners: { |
10603 'dropdown.iron-overlay-canceled': 'onOverlayCanceled_', | 7687 'dropdown.iron-overlay-canceled': 'onOverlayCanceled_', |
10604 }, | 7688 }, |
10605 | 7689 |
10606 /** | |
10607 * The last anchor that was used to open a menu. It's necessary for toggling. | |
10608 * @private {?Element} | |
10609 */ | |
10610 lastAnchor_: null, | 7690 lastAnchor_: null, |
10611 | 7691 |
10612 /** | |
10613 * The first focusable child in the menu's light DOM. | |
10614 * @private {?Element} | |
10615 */ | |
10616 firstFocus_: null, | 7692 firstFocus_: null, |
10617 | 7693 |
10618 /** | |
10619 * The last focusable child in the menu's light DOM. | |
10620 * @private {?Element} | |
10621 */ | |
10622 lastFocus_: null, | 7694 lastFocus_: null, |
10623 | 7695 |
10624 /** @override */ | 7696 /** @override */ |
10625 attached: function() { | 7697 attached: function() { |
10626 window.addEventListener('resize', this.closeMenu.bind(this)); | 7698 window.addEventListener('resize', this.closeMenu.bind(this)); |
10627 }, | 7699 }, |
10628 | 7700 |
10629 /** Closes the menu. */ | 7701 /** Closes the menu. */ |
10630 closeMenu: function() { | 7702 closeMenu: function() { |
10631 if (this.root.activeElement == null) { | 7703 if (this.root.activeElement == null) { |
10632 // Something else has taken focus away from the menu. Do not attempt to | |
10633 // restore focus to the button which opened the menu. | |
10634 this.$.dropdown.restoreFocusOnClose = false; | 7704 this.$.dropdown.restoreFocusOnClose = false; |
10635 } | 7705 } |
10636 this.menuOpen = false; | 7706 this.menuOpen = false; |
10637 }, | 7707 }, |
10638 | 7708 |
10639 /** | |
10640 * Opens the menu at the anchor location. | |
10641 * @param {!Element} anchor The location to display the menu. | |
10642 * @param {!Object} itemData The contextual item's data. | |
10643 */ | |
10644 openMenu: function(anchor, itemData) { | 7709 openMenu: function(anchor, itemData) { |
10645 if (this.lastAnchor_ == anchor && this.menuOpen) | 7710 if (this.lastAnchor_ == anchor && this.menuOpen) |
10646 return; | 7711 return; |
10647 | 7712 |
10648 if (this.menuOpen) | 7713 if (this.menuOpen) |
10649 this.closeMenu(); | 7714 this.closeMenu(); |
10650 | 7715 |
10651 this.itemData = itemData; | 7716 this.itemData = itemData; |
10652 this.lastAnchor_ = anchor; | 7717 this.lastAnchor_ = anchor; |
10653 this.$.dropdown.restoreFocusOnClose = true; | 7718 this.$.dropdown.restoreFocusOnClose = true; |
10654 | 7719 |
10655 var focusableChildren = Polymer.dom(this).querySelectorAll( | 7720 var focusableChildren = Polymer.dom(this).querySelectorAll( |
10656 '[tabindex]:not([hidden]),button:not([hidden])'); | 7721 '[tabindex]:not([hidden]),button:not([hidden])'); |
10657 if (focusableChildren.length > 0) { | 7722 if (focusableChildren.length > 0) { |
10658 this.$.dropdown.focusTarget = focusableChildren[0]; | 7723 this.$.dropdown.focusTarget = focusableChildren[0]; |
10659 this.firstFocus_ = focusableChildren[0]; | 7724 this.firstFocus_ = focusableChildren[0]; |
10660 this.lastFocus_ = focusableChildren[focusableChildren.length - 1]; | 7725 this.lastFocus_ = focusableChildren[focusableChildren.length - 1]; |
10661 } | 7726 } |
10662 | 7727 |
10663 // Move the menu to the anchor. | |
10664 this.$.dropdown.positionTarget = anchor; | 7728 this.$.dropdown.positionTarget = anchor; |
10665 this.menuOpen = true; | 7729 this.menuOpen = true; |
10666 }, | 7730 }, |
10667 | 7731 |
10668 /** | |
10669 * Toggles the menu for the anchor that is passed in. | |
10670 * @param {!Element} anchor The location to display the menu. | |
10671 * @param {!Object} itemData The contextual item's data. | |
10672 */ | |
10673 toggleMenu: function(anchor, itemData) { | 7732 toggleMenu: function(anchor, itemData) { |
10674 if (anchor == this.lastAnchor_ && this.menuOpen) | 7733 if (anchor == this.lastAnchor_ && this.menuOpen) |
10675 this.closeMenu(); | 7734 this.closeMenu(); |
10676 else | 7735 else |
10677 this.openMenu(anchor, itemData); | 7736 this.openMenu(anchor, itemData); |
10678 }, | 7737 }, |
10679 | 7738 |
10680 /** | |
10681 * Trap focus inside the menu. As a very basic heuristic, will wrap focus from | |
10682 * the first element with a nonzero tabindex to the last such element. | |
10683 * TODO(tsergeant): Use iron-focus-wrap-behavior once it is available | |
10684 * (https://github.com/PolymerElements/iron-overlay-behavior/issues/179). | |
10685 * @param {CustomEvent} e | |
10686 */ | |
10687 onTabPressed_: function(e) { | 7739 onTabPressed_: function(e) { |
10688 if (!this.firstFocus_ || !this.lastFocus_) | 7740 if (!this.firstFocus_ || !this.lastFocus_) |
10689 return; | 7741 return; |
10690 | 7742 |
10691 var toFocus; | 7743 var toFocus; |
10692 var keyEvent = e.detail.keyboardEvent; | 7744 var keyEvent = e.detail.keyboardEvent; |
10693 if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_) | 7745 if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_) |
10694 toFocus = this.lastFocus_; | 7746 toFocus = this.lastFocus_; |
10695 else if (keyEvent.target == this.lastFocus_) | 7747 else if (keyEvent.target == this.lastFocus_) |
10696 toFocus = this.firstFocus_; | 7748 toFocus = this.firstFocus_; |
10697 | 7749 |
10698 if (!toFocus) | 7750 if (!toFocus) |
10699 return; | 7751 return; |
10700 | 7752 |
10701 e.preventDefault(); | 7753 e.preventDefault(); |
10702 toFocus.focus(); | 7754 toFocus.focus(); |
10703 }, | 7755 }, |
10704 | 7756 |
10705 /** | |
10706 * Ensure the menu is reset properly when it is closed by the dropdown (eg, | |
10707 * clicking outside). | |
10708 * @private | |
10709 */ | |
10710 menuOpenChanged_: function() { | 7757 menuOpenChanged_: function() { |
10711 if (!this.menuOpen) { | 7758 if (!this.menuOpen) { |
10712 this.itemData = null; | 7759 this.itemData = null; |
10713 this.lastAnchor_ = null; | 7760 this.lastAnchor_ = null; |
10714 } | 7761 } |
10715 }, | 7762 }, |
10716 | 7763 |
10717 /** | |
10718 * Prevent focus restoring when tapping outside the menu. This stops the | |
10719 * focus moving around unexpectedly when closing the menu with the mouse. | |
10720 * @param {CustomEvent} e | |
10721 * @private | |
10722 */ | |
10723 onOverlayCanceled_: function(e) { | 7764 onOverlayCanceled_: function(e) { |
10724 if (e.detail.type == 'tap') | 7765 if (e.detail.type == 'tap') |
10725 this.$.dropdown.restoreFocusOnClose = false; | 7766 this.$.dropdown.restoreFocusOnClose = false; |
10726 }, | 7767 }, |
10727 }); | 7768 }); |
10728 /** @polymerBehavior Polymer.PaperItemBehavior */ | 7769 /** @polymerBehavior Polymer.PaperItemBehavior */ |
10729 Polymer.PaperItemBehaviorImpl = { | 7770 Polymer.PaperItemBehaviorImpl = { |
10730 hostAttributes: { | 7771 hostAttributes: { |
10731 role: 'option', | 7772 role: 'option', |
10732 tabindex: '0' | 7773 tabindex: '0' |
(...skipping 16 matching lines...) Expand all Loading... |
10749 Polymer({ | 7790 Polymer({ |
10750 | 7791 |
10751 is: 'iron-collapse', | 7792 is: 'iron-collapse', |
10752 | 7793 |
10753 behaviors: [ | 7794 behaviors: [ |
10754 Polymer.IronResizableBehavior | 7795 Polymer.IronResizableBehavior |
10755 ], | 7796 ], |
10756 | 7797 |
10757 properties: { | 7798 properties: { |
10758 | 7799 |
10759 /** | |
10760 * If true, the orientation is horizontal; otherwise is vertical. | |
10761 * | |
10762 * @attribute horizontal | |
10763 */ | |
10764 horizontal: { | 7800 horizontal: { |
10765 type: Boolean, | 7801 type: Boolean, |
10766 value: false, | 7802 value: false, |
10767 observer: '_horizontalChanged' | 7803 observer: '_horizontalChanged' |
10768 }, | 7804 }, |
10769 | 7805 |
10770 /** | |
10771 * Set opened to true to show the collapse element and to false to hide it
. | |
10772 * | |
10773 * @attribute opened | |
10774 */ | |
10775 opened: { | 7806 opened: { |
10776 type: Boolean, | 7807 type: Boolean, |
10777 value: false, | 7808 value: false, |
10778 notify: true, | 7809 notify: true, |
10779 observer: '_openedChanged' | 7810 observer: '_openedChanged' |
10780 }, | 7811 }, |
10781 | 7812 |
10782 /** | |
10783 * Set noAnimation to true to disable animations | |
10784 * | |
10785 * @attribute noAnimation | |
10786 */ | |
10787 noAnimation: { | 7813 noAnimation: { |
10788 type: Boolean | 7814 type: Boolean |
10789 }, | 7815 }, |
10790 | 7816 |
10791 }, | 7817 }, |
10792 | 7818 |
10793 get dimension() { | 7819 get dimension() { |
10794 return this.horizontal ? 'width' : 'height'; | 7820 return this.horizontal ? 'width' : 'height'; |
10795 }, | 7821 }, |
10796 | 7822 |
10797 /** | |
10798 * `maxWidth` or `maxHeight`. | |
10799 * @private | |
10800 */ | |
10801 get _dimensionMax() { | 7823 get _dimensionMax() { |
10802 return this.horizontal ? 'maxWidth' : 'maxHeight'; | 7824 return this.horizontal ? 'maxWidth' : 'maxHeight'; |
10803 }, | 7825 }, |
10804 | 7826 |
10805 /** | |
10806 * `max-width` or `max-height`. | |
10807 * @private | |
10808 */ | |
10809 get _dimensionMaxCss() { | 7827 get _dimensionMaxCss() { |
10810 return this.horizontal ? 'max-width' : 'max-height'; | 7828 return this.horizontal ? 'max-width' : 'max-height'; |
10811 }, | 7829 }, |
10812 | 7830 |
10813 hostAttributes: { | 7831 hostAttributes: { |
10814 role: 'group', | 7832 role: 'group', |
10815 'aria-hidden': 'true', | 7833 'aria-hidden': 'true', |
10816 'aria-expanded': 'false' | 7834 'aria-expanded': 'false' |
10817 }, | 7835 }, |
10818 | 7836 |
10819 listeners: { | 7837 listeners: { |
10820 transitionend: '_transitionEnd' | 7838 transitionend: '_transitionEnd' |
10821 }, | 7839 }, |
10822 | 7840 |
10823 attached: function() { | 7841 attached: function() { |
10824 // It will take care of setting correct classes and styles. | |
10825 this._transitionEnd(); | 7842 this._transitionEnd(); |
10826 }, | 7843 }, |
10827 | 7844 |
10828 /** | |
10829 * Toggle the opened state. | |
10830 * | |
10831 * @method toggle | |
10832 */ | |
10833 toggle: function() { | 7845 toggle: function() { |
10834 this.opened = !this.opened; | 7846 this.opened = !this.opened; |
10835 }, | 7847 }, |
10836 | 7848 |
10837 show: function() { | 7849 show: function() { |
10838 this.opened = true; | 7850 this.opened = true; |
10839 }, | 7851 }, |
10840 | 7852 |
10841 hide: function() { | 7853 hide: function() { |
10842 this.opened = false; | 7854 this.opened = false; |
10843 }, | 7855 }, |
10844 | 7856 |
10845 /** | |
10846 * Updates the size of the element. | |
10847 * @param {string} size The new value for `maxWidth`/`maxHeight` as css prop
erty value, usually `auto` or `0px`. | |
10848 * @param {boolean=} animated if `true` updates the size with an animation,
otherwise without. | |
10849 */ | |
10850 updateSize: function(size, animated) { | 7857 updateSize: function(size, animated) { |
10851 // No change! | |
10852 var curSize = this.style[this._dimensionMax]; | 7858 var curSize = this.style[this._dimensionMax]; |
10853 if (curSize === size || (size === 'auto' && !curSize)) { | 7859 if (curSize === size || (size === 'auto' && !curSize)) { |
10854 return; | 7860 return; |
10855 } | 7861 } |
10856 | 7862 |
10857 this._updateTransition(false); | 7863 this._updateTransition(false); |
10858 // If we can animate, must do some prep work. | |
10859 if (animated && !this.noAnimation && this._isDisplayed) { | 7864 if (animated && !this.noAnimation && this._isDisplayed) { |
10860 // Animation will start at the current size. | |
10861 var startSize = this._calcSize(); | 7865 var startSize = this._calcSize(); |
10862 // For `auto` we must calculate what is the final size for the animation
. | |
10863 // After the transition is done, _transitionEnd will set the size back t
o `auto`. | |
10864 if (size === 'auto') { | 7866 if (size === 'auto') { |
10865 this.style[this._dimensionMax] = ''; | 7867 this.style[this._dimensionMax] = ''; |
10866 size = this._calcSize(); | 7868 size = this._calcSize(); |
10867 } | 7869 } |
10868 // Go to startSize without animation. | |
10869 this.style[this._dimensionMax] = startSize; | 7870 this.style[this._dimensionMax] = startSize; |
10870 // Force layout to ensure transition will go. Set scrollTop to itself | |
10871 // so that compilers won't remove it. | |
10872 this.scrollTop = this.scrollTop; | 7871 this.scrollTop = this.scrollTop; |
10873 // Enable animation. | |
10874 this._updateTransition(true); | 7872 this._updateTransition(true); |
10875 } | 7873 } |
10876 // Set the final size. | |
10877 if (size === 'auto') { | 7874 if (size === 'auto') { |
10878 this.style[this._dimensionMax] = ''; | 7875 this.style[this._dimensionMax] = ''; |
10879 } else { | 7876 } else { |
10880 this.style[this._dimensionMax] = size; | 7877 this.style[this._dimensionMax] = size; |
10881 } | 7878 } |
10882 }, | 7879 }, |
10883 | 7880 |
10884 /** | |
10885 * enableTransition() is deprecated, but left over so it doesn't break exist
ing code. | |
10886 * Please use `noAnimation` property instead. | |
10887 * | |
10888 * @method enableTransition | |
10889 * @deprecated since version 1.0.4 | |
10890 */ | |
10891 enableTransition: function(enabled) { | 7881 enableTransition: function(enabled) { |
10892 Polymer.Base._warn('`enableTransition()` is deprecated, use `noAnimation`
instead.'); | 7882 Polymer.Base._warn('`enableTransition()` is deprecated, use `noAnimation`
instead.'); |
10893 this.noAnimation = !enabled; | 7883 this.noAnimation = !enabled; |
10894 }, | 7884 }, |
10895 | 7885 |
10896 _updateTransition: function(enabled) { | 7886 _updateTransition: function(enabled) { |
10897 this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s'
; | 7887 this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s'
; |
10898 }, | 7888 }, |
10899 | 7889 |
10900 _horizontalChanged: function() { | 7890 _horizontalChanged: function() { |
10901 this.style.transitionProperty = this._dimensionMaxCss; | 7891 this.style.transitionProperty = this._dimensionMaxCss; |
10902 var otherDimension = this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'ma
xWidth'; | 7892 var otherDimension = this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'ma
xWidth'; |
10903 this.style[otherDimension] = ''; | 7893 this.style[otherDimension] = ''; |
10904 this.updateSize(this.opened ? 'auto' : '0px', false); | 7894 this.updateSize(this.opened ? 'auto' : '0px', false); |
10905 }, | 7895 }, |
10906 | 7896 |
10907 _openedChanged: function() { | 7897 _openedChanged: function() { |
10908 this.setAttribute('aria-expanded', this.opened); | 7898 this.setAttribute('aria-expanded', this.opened); |
10909 this.setAttribute('aria-hidden', !this.opened); | 7899 this.setAttribute('aria-hidden', !this.opened); |
10910 | 7900 |
10911 this.toggleClass('iron-collapse-closed', false); | 7901 this.toggleClass('iron-collapse-closed', false); |
10912 this.toggleClass('iron-collapse-opened', false); | 7902 this.toggleClass('iron-collapse-opened', false); |
10913 this.updateSize(this.opened ? 'auto' : '0px', true); | 7903 this.updateSize(this.opened ? 'auto' : '0px', true); |
10914 | 7904 |
10915 // Focus the current collapse. | |
10916 if (this.opened) { | 7905 if (this.opened) { |
10917 this.focus(); | 7906 this.focus(); |
10918 } | 7907 } |
10919 if (this.noAnimation) { | 7908 if (this.noAnimation) { |
10920 this._transitionEnd(); | 7909 this._transitionEnd(); |
10921 } | 7910 } |
10922 }, | 7911 }, |
10923 | 7912 |
10924 _transitionEnd: function() { | 7913 _transitionEnd: function() { |
10925 if (this.opened) { | 7914 if (this.opened) { |
10926 this.style[this._dimensionMax] = ''; | 7915 this.style[this._dimensionMax] = ''; |
10927 } | 7916 } |
10928 this.toggleClass('iron-collapse-closed', !this.opened); | 7917 this.toggleClass('iron-collapse-closed', !this.opened); |
10929 this.toggleClass('iron-collapse-opened', this.opened); | 7918 this.toggleClass('iron-collapse-opened', this.opened); |
10930 this._updateTransition(false); | 7919 this._updateTransition(false); |
10931 this.notifyResize(); | 7920 this.notifyResize(); |
10932 }, | 7921 }, |
10933 | 7922 |
10934 /** | |
10935 * Simplistic heuristic to detect if element has a parent with display: none | |
10936 * | |
10937 * @private | |
10938 */ | |
10939 get _isDisplayed() { | 7923 get _isDisplayed() { |
10940 var rect = this.getBoundingClientRect(); | 7924 var rect = this.getBoundingClientRect(); |
10941 for (var prop in rect) { | 7925 for (var prop in rect) { |
10942 if (rect[prop] !== 0) return true; | 7926 if (rect[prop] !== 0) return true; |
10943 } | 7927 } |
10944 return false; | 7928 return false; |
10945 }, | 7929 }, |
10946 | 7930 |
10947 _calcSize: function() { | 7931 _calcSize: function() { |
10948 return this.getBoundingClientRect()[this.dimension] + 'px'; | 7932 return this.getBoundingClientRect()[this.dimension] + 'px'; |
10949 } | 7933 } |
10950 | 7934 |
10951 }); | 7935 }); |
10952 /** | |
10953 Polymer.IronFormElementBehavior enables a custom element to be included | |
10954 in an `iron-form`. | |
10955 | |
10956 @demo demo/index.html | |
10957 @polymerBehavior | |
10958 */ | |
10959 Polymer.IronFormElementBehavior = { | 7936 Polymer.IronFormElementBehavior = { |
10960 | 7937 |
10961 properties: { | 7938 properties: { |
10962 /** | |
10963 * Fired when the element is added to an `iron-form`. | |
10964 * | |
10965 * @event iron-form-element-register | |
10966 */ | |
10967 | 7939 |
10968 /** | |
10969 * Fired when the element is removed from an `iron-form`. | |
10970 * | |
10971 * @event iron-form-element-unregister | |
10972 */ | |
10973 | 7940 |
10974 /** | |
10975 * The name of this element. | |
10976 */ | |
10977 name: { | 7941 name: { |
10978 type: String | 7942 type: String |
10979 }, | 7943 }, |
10980 | 7944 |
10981 /** | |
10982 * The value for this element. | |
10983 */ | |
10984 value: { | 7945 value: { |
10985 notify: true, | 7946 notify: true, |
10986 type: String | 7947 type: String |
10987 }, | 7948 }, |
10988 | 7949 |
10989 /** | |
10990 * Set to true to mark the input as required. If used in a form, a | |
10991 * custom element that uses this behavior should also use | |
10992 * Polymer.IronValidatableBehavior and define a custom validation method. | |
10993 * Otherwise, a `required` element will always be considered valid. | |
10994 * It's also strongly recommended to provide a visual style for the elemen
t | |
10995 * when its value is invalid. | |
10996 */ | |
10997 required: { | 7950 required: { |
10998 type: Boolean, | 7951 type: Boolean, |
10999 value: false | 7952 value: false |
11000 }, | 7953 }, |
11001 | 7954 |
11002 /** | |
11003 * The form that the element is registered to. | |
11004 */ | |
11005 _parentForm: { | 7955 _parentForm: { |
11006 type: Object | 7956 type: Object |
11007 } | 7957 } |
11008 }, | 7958 }, |
11009 | 7959 |
11010 attached: function() { | 7960 attached: function() { |
11011 // Note: the iron-form that this element belongs to will set this | |
11012 // element's _parentForm property when handling this event. | |
11013 this.fire('iron-form-element-register'); | 7961 this.fire('iron-form-element-register'); |
11014 }, | 7962 }, |
11015 | 7963 |
11016 detached: function() { | 7964 detached: function() { |
11017 if (this._parentForm) { | 7965 if (this._parentForm) { |
11018 this._parentForm.fire('iron-form-element-unregister', {target: this}); | 7966 this._parentForm.fire('iron-form-element-unregister', {target: this}); |
11019 } | 7967 } |
11020 } | 7968 } |
11021 | 7969 |
11022 }; | 7970 }; |
11023 /** | |
11024 * Use `Polymer.IronCheckedElementBehavior` to implement a custom element | |
11025 * that has a `checked` property, which can be used for validation if the | |
11026 * element is also `required`. Element instances implementing this behavior | |
11027 * will also be registered for use in an `iron-form` element. | |
11028 * | |
11029 * @demo demo/index.html | |
11030 * @polymerBehavior Polymer.IronCheckedElementBehavior | |
11031 */ | |
11032 Polymer.IronCheckedElementBehaviorImpl = { | 7971 Polymer.IronCheckedElementBehaviorImpl = { |
11033 | 7972 |
11034 properties: { | 7973 properties: { |
11035 /** | |
11036 * Fired when the checked state changes. | |
11037 * | |
11038 * @event iron-change | |
11039 */ | |
11040 | 7974 |
11041 /** | |
11042 * Gets or sets the state, `true` is checked and `false` is unchecked. | |
11043 */ | |
11044 checked: { | 7975 checked: { |
11045 type: Boolean, | 7976 type: Boolean, |
11046 value: false, | 7977 value: false, |
11047 reflectToAttribute: true, | 7978 reflectToAttribute: true, |
11048 notify: true, | 7979 notify: true, |
11049 observer: '_checkedChanged' | 7980 observer: '_checkedChanged' |
11050 }, | 7981 }, |
11051 | 7982 |
11052 /** | |
11053 * If true, the button toggles the active state with each tap or press | |
11054 * of the spacebar. | |
11055 */ | |
11056 toggles: { | 7983 toggles: { |
11057 type: Boolean, | 7984 type: Boolean, |
11058 value: true, | 7985 value: true, |
11059 reflectToAttribute: true | 7986 reflectToAttribute: true |
11060 }, | 7987 }, |
11061 | 7988 |
11062 /* Overriden from Polymer.IronFormElementBehavior */ | 7989 /* Overriden from Polymer.IronFormElementBehavior */ |
11063 value: { | 7990 value: { |
11064 type: String, | 7991 type: String, |
11065 value: 'on', | 7992 value: 'on', |
11066 observer: '_valueChanged' | 7993 observer: '_valueChanged' |
11067 } | 7994 } |
11068 }, | 7995 }, |
11069 | 7996 |
11070 observers: [ | 7997 observers: [ |
11071 '_requiredChanged(required)' | 7998 '_requiredChanged(required)' |
11072 ], | 7999 ], |
11073 | 8000 |
11074 created: function() { | 8001 created: function() { |
11075 // Used by `iron-form` to handle the case that an element with this behavi
or | |
11076 // doesn't have a role of 'checkbox' or 'radio', but should still only be | |
11077 // included when the form is serialized if `this.checked === true`. | |
11078 this._hasIronCheckedElementBehavior = true; | 8002 this._hasIronCheckedElementBehavior = true; |
11079 }, | 8003 }, |
11080 | 8004 |
11081 /** | |
11082 * Returns false if the element is required and not checked, and true otherw
ise. | |
11083 * @param {*=} _value Ignored. | |
11084 * @return {boolean} true if `required` is false or if `checked` is true. | |
11085 */ | |
11086 _getValidity: function(_value) { | 8005 _getValidity: function(_value) { |
11087 return this.disabled || !this.required || this.checked; | 8006 return this.disabled || !this.required || this.checked; |
11088 }, | 8007 }, |
11089 | 8008 |
11090 /** | |
11091 * Update the aria-required label when `required` is changed. | |
11092 */ | |
11093 _requiredChanged: function() { | 8009 _requiredChanged: function() { |
11094 if (this.required) { | 8010 if (this.required) { |
11095 this.setAttribute('aria-required', 'true'); | 8011 this.setAttribute('aria-required', 'true'); |
11096 } else { | 8012 } else { |
11097 this.removeAttribute('aria-required'); | 8013 this.removeAttribute('aria-required'); |
11098 } | 8014 } |
11099 }, | 8015 }, |
11100 | 8016 |
11101 /** | |
11102 * Fire `iron-changed` when the checked state changes. | |
11103 */ | |
11104 _checkedChanged: function() { | 8017 _checkedChanged: function() { |
11105 this.active = this.checked; | 8018 this.active = this.checked; |
11106 this.fire('iron-change'); | 8019 this.fire('iron-change'); |
11107 }, | 8020 }, |
11108 | 8021 |
11109 /** | |
11110 * Reset value to 'on' if it is set to `undefined`. | |
11111 */ | |
11112 _valueChanged: function() { | 8022 _valueChanged: function() { |
11113 if (this.value === undefined || this.value === null) { | 8023 if (this.value === undefined || this.value === null) { |
11114 this.value = 'on'; | 8024 this.value = 'on'; |
11115 } | 8025 } |
11116 } | 8026 } |
11117 }; | 8027 }; |
11118 | 8028 |
11119 /** @polymerBehavior Polymer.IronCheckedElementBehavior */ | 8029 /** @polymerBehavior Polymer.IronCheckedElementBehavior */ |
11120 Polymer.IronCheckedElementBehavior = [ | 8030 Polymer.IronCheckedElementBehavior = [ |
11121 Polymer.IronFormElementBehavior, | 8031 Polymer.IronFormElementBehavior, |
11122 Polymer.IronValidatableBehavior, | 8032 Polymer.IronValidatableBehavior, |
11123 Polymer.IronCheckedElementBehaviorImpl | 8033 Polymer.IronCheckedElementBehaviorImpl |
11124 ]; | 8034 ]; |
11125 /** | |
11126 * Use `Polymer.PaperCheckedElementBehavior` to implement a custom element | |
11127 * that has a `checked` property similar to `Polymer.IronCheckedElementBehavio
r` | |
11128 * and is compatible with having a ripple effect. | |
11129 * @polymerBehavior Polymer.PaperCheckedElementBehavior | |
11130 */ | |
11131 Polymer.PaperCheckedElementBehaviorImpl = { | 8035 Polymer.PaperCheckedElementBehaviorImpl = { |
11132 /** | |
11133 * Synchronizes the element's checked state with its ripple effect. | |
11134 */ | |
11135 _checkedChanged: function() { | 8036 _checkedChanged: function() { |
11136 Polymer.IronCheckedElementBehaviorImpl._checkedChanged.call(this); | 8037 Polymer.IronCheckedElementBehaviorImpl._checkedChanged.call(this); |
11137 if (this.hasRipple()) { | 8038 if (this.hasRipple()) { |
11138 if (this.checked) { | 8039 if (this.checked) { |
11139 this._ripple.setAttribute('checked', ''); | 8040 this._ripple.setAttribute('checked', ''); |
11140 } else { | 8041 } else { |
11141 this._ripple.removeAttribute('checked'); | 8042 this._ripple.removeAttribute('checked'); |
11142 } | 8043 } |
11143 } | 8044 } |
11144 }, | 8045 }, |
11145 | 8046 |
11146 /** | |
11147 * Synchronizes the element's `active` and `checked` state. | |
11148 */ | |
11149 _buttonStateChanged: function() { | 8047 _buttonStateChanged: function() { |
11150 Polymer.PaperRippleBehavior._buttonStateChanged.call(this); | 8048 Polymer.PaperRippleBehavior._buttonStateChanged.call(this); |
11151 if (this.disabled) { | 8049 if (this.disabled) { |
11152 return; | 8050 return; |
11153 } | 8051 } |
11154 if (this.isAttached) { | 8052 if (this.isAttached) { |
11155 this.checked = this.active; | 8053 this.checked = this.active; |
11156 } | 8054 } |
11157 } | 8055 } |
11158 }; | 8056 }; |
(...skipping 11 matching lines...) Expand all Loading... |
11170 Polymer.PaperCheckedElementBehavior | 8068 Polymer.PaperCheckedElementBehavior |
11171 ], | 8069 ], |
11172 | 8070 |
11173 hostAttributes: { | 8071 hostAttributes: { |
11174 role: 'checkbox', | 8072 role: 'checkbox', |
11175 'aria-checked': false, | 8073 'aria-checked': false, |
11176 tabindex: 0 | 8074 tabindex: 0 |
11177 }, | 8075 }, |
11178 | 8076 |
11179 properties: { | 8077 properties: { |
11180 /** | |
11181 * Fired when the checked state changes due to user interaction. | |
11182 * | |
11183 * @event change | |
11184 */ | |
11185 | 8078 |
11186 /** | |
11187 * Fired when the checked state changes. | |
11188 * | |
11189 * @event iron-change | |
11190 */ | |
11191 ariaActiveAttribute: { | 8079 ariaActiveAttribute: { |
11192 type: String, | 8080 type: String, |
11193 value: 'aria-checked' | 8081 value: 'aria-checked' |
11194 } | 8082 } |
11195 }, | 8083 }, |
11196 | 8084 |
11197 _computeCheckboxClass: function(checked, invalid) { | 8085 _computeCheckboxClass: function(checked, invalid) { |
11198 var className = ''; | 8086 var className = ''; |
11199 if (checked) { | 8087 if (checked) { |
11200 className += 'checked '; | 8088 className += 'checked '; |
11201 } | 8089 } |
11202 if (invalid) { | 8090 if (invalid) { |
11203 className += 'invalid'; | 8091 className += 'invalid'; |
11204 } | 8092 } |
11205 return className; | 8093 return className; |
11206 }, | 8094 }, |
11207 | 8095 |
11208 _computeCheckmarkClass: function(checked) { | 8096 _computeCheckmarkClass: function(checked) { |
11209 return checked ? '' : 'hidden'; | 8097 return checked ? '' : 'hidden'; |
11210 }, | 8098 }, |
11211 | 8099 |
11212 // create ripple inside the checkboxContainer | |
11213 _createRipple: function() { | 8100 _createRipple: function() { |
11214 this._rippleContainer = this.$.checkboxContainer; | 8101 this._rippleContainer = this.$.checkboxContainer; |
11215 return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this); | 8102 return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this); |
11216 } | 8103 } |
11217 | 8104 |
11218 }); | 8105 }); |
11219 Polymer({ | 8106 Polymer({ |
11220 is: 'paper-icon-button-light', | 8107 is: 'paper-icon-button-light', |
11221 extends: 'button', | 8108 extends: 'button', |
11222 | 8109 |
11223 behaviors: [ | 8110 behaviors: [ |
11224 Polymer.PaperRippleBehavior | 8111 Polymer.PaperRippleBehavior |
11225 ], | 8112 ], |
11226 | 8113 |
11227 listeners: { | 8114 listeners: { |
11228 'down': '_rippleDown', | 8115 'down': '_rippleDown', |
11229 'up': '_rippleUp', | 8116 'up': '_rippleUp', |
11230 'focus': '_rippleDown', | 8117 'focus': '_rippleDown', |
11231 'blur': '_rippleUp', | 8118 'blur': '_rippleUp', |
11232 }, | 8119 }, |
11233 | 8120 |
11234 _rippleDown: function() { | 8121 _rippleDown: function() { |
11235 this.getRipple().downAction(); | 8122 this.getRipple().downAction(); |
11236 }, | 8123 }, |
11237 | 8124 |
11238 _rippleUp: function() { | 8125 _rippleUp: function() { |
11239 this.getRipple().upAction(); | 8126 this.getRipple().upAction(); |
11240 }, | 8127 }, |
11241 | 8128 |
11242 /** | |
11243 * @param {...*} var_args | |
11244 */ | |
11245 ensureRipple: function(var_args) { | 8129 ensureRipple: function(var_args) { |
11246 var lastRipple = this._ripple; | 8130 var lastRipple = this._ripple; |
11247 Polymer.PaperRippleBehavior.ensureRipple.apply(this, arguments); | 8131 Polymer.PaperRippleBehavior.ensureRipple.apply(this, arguments); |
11248 if (this._ripple && this._ripple !== lastRipple) { | 8132 if (this._ripple && this._ripple !== lastRipple) { |
11249 this._ripple.center = true; | 8133 this._ripple.center = true; |
11250 this._ripple.classList.add('circle'); | 8134 this._ripple.classList.add('circle'); |
11251 } | 8135 } |
11252 } | 8136 } |
11253 }); | 8137 }); |
11254 // Copyright 2016 The Chromium Authors. All rights reserved. | 8138 // Copyright 2016 The Chromium Authors. All rights reserved. |
11255 // Use of this source code is governed by a BSD-style license that can be | 8139 // Use of this source code is governed by a BSD-style license that can be |
11256 // found in the LICENSE file. | 8140 // found in the LICENSE file. |
11257 | 8141 |
11258 cr.define('cr.icon', function() { | 8142 cr.define('cr.icon', function() { |
11259 /** | |
11260 * @return {!Array<number>} The scale factors supported by this platform for | |
11261 * webui resources. | |
11262 */ | |
11263 function getSupportedScaleFactors() { | 8143 function getSupportedScaleFactors() { |
11264 var supportedScaleFactors = []; | 8144 var supportedScaleFactors = []; |
11265 if (cr.isMac || cr.isChromeOS || cr.isWindows || cr.isLinux) { | 8145 if (cr.isMac || cr.isChromeOS || cr.isWindows || cr.isLinux) { |
11266 // All desktop platforms support zooming which also updates the | |
11267 // renderer's device scale factors (a.k.a devicePixelRatio), and | |
11268 // these platforms has high DPI assets for 2.0x. Use 1x and 2x in | |
11269 // image-set on these platforms so that the renderer can pick the | |
11270 // closest image for the current device scale factor. | |
11271 supportedScaleFactors.push(1); | 8146 supportedScaleFactors.push(1); |
11272 supportedScaleFactors.push(2); | 8147 supportedScaleFactors.push(2); |
11273 } else { | 8148 } else { |
11274 // For other platforms that use fixed device scale factor, use | |
11275 // the window's device pixel ratio. | |
11276 // TODO(oshima): Investigate if Android/iOS need to use image-set. | |
11277 supportedScaleFactors.push(window.devicePixelRatio); | 8149 supportedScaleFactors.push(window.devicePixelRatio); |
11278 } | 8150 } |
11279 return supportedScaleFactors; | 8151 return supportedScaleFactors; |
11280 } | 8152 } |
11281 | 8153 |
11282 /** | |
11283 * Returns the URL of the image, or an image set of URLs for the profile | |
11284 * avatar. Default avatars have resources available for multiple scalefactors, | |
11285 * whereas the GAIA profile image only comes in one size. | |
11286 * | |
11287 * @param {string} path The path of the image. | |
11288 * @return {string} The url, or an image set of URLs of the avatar image. | |
11289 */ | |
11290 function getProfileAvatarIcon(path) { | 8154 function getProfileAvatarIcon(path) { |
11291 var chromeThemePath = 'chrome://theme'; | 8155 var chromeThemePath = 'chrome://theme'; |
11292 var isDefaultAvatar = | 8156 var isDefaultAvatar = |
11293 (path.slice(0, chromeThemePath.length) == chromeThemePath); | 8157 (path.slice(0, chromeThemePath.length) == chromeThemePath); |
11294 return isDefaultAvatar ? imageset(path + '@scalefactorx'): url(path); | 8158 return isDefaultAvatar ? imageset(path + '@scalefactorx'): url(path); |
11295 } | 8159 } |
11296 | 8160 |
11297 /** | |
11298 * Generates a CSS -webkit-image-set for a chrome:// url. | |
11299 * An entry in the image set is added for each of getSupportedScaleFactors(). | |
11300 * The scale-factor-specific url is generated by replacing the first instance | |
11301 * of 'scalefactor' in |path| with the numeric scale factor. | |
11302 * @param {string} path The URL to generate an image set for. | |
11303 * 'scalefactor' should be a substring of |path|. | |
11304 * @return {string} The CSS -webkit-image-set. | |
11305 */ | |
11306 function imageset(path) { | 8161 function imageset(path) { |
11307 var supportedScaleFactors = getSupportedScaleFactors(); | 8162 var supportedScaleFactors = getSupportedScaleFactors(); |
11308 | 8163 |
11309 var replaceStartIndex = path.indexOf('scalefactor'); | 8164 var replaceStartIndex = path.indexOf('scalefactor'); |
11310 if (replaceStartIndex < 0) | 8165 if (replaceStartIndex < 0) |
11311 return url(path); | 8166 return url(path); |
11312 | 8167 |
11313 var s = ''; | 8168 var s = ''; |
11314 for (var i = 0; i < supportedScaleFactors.length; ++i) { | 8169 for (var i = 0; i < supportedScaleFactors.length; ++i) { |
11315 var scaleFactor = supportedScaleFactors[i]; | 8170 var scaleFactor = supportedScaleFactors[i]; |
11316 var pathWithScaleFactor = path.substr(0, replaceStartIndex) + | 8171 var pathWithScaleFactor = path.substr(0, replaceStartIndex) + |
11317 scaleFactor + path.substr(replaceStartIndex + 'scalefactor'.length); | 8172 scaleFactor + path.substr(replaceStartIndex + 'scalefactor'.length); |
11318 | 8173 |
11319 s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x'; | 8174 s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x'; |
11320 | 8175 |
11321 if (i != supportedScaleFactors.length - 1) | 8176 if (i != supportedScaleFactors.length - 1) |
11322 s += ', '; | 8177 s += ', '; |
11323 } | 8178 } |
11324 return '-webkit-image-set(' + s + ')'; | 8179 return '-webkit-image-set(' + s + ')'; |
11325 } | 8180 } |
11326 | 8181 |
11327 /** | |
11328 * A regular expression for identifying favicon URLs. | |
11329 * @const {!RegExp} | |
11330 */ | |
11331 var FAVICON_URL_REGEX = /\.ico$/i; | 8182 var FAVICON_URL_REGEX = /\.ico$/i; |
11332 | 8183 |
11333 /** | |
11334 * Creates a CSS -webkit-image-set for a favicon request. | |
11335 * @param {string} url Either the URL of the original page or of the favicon | |
11336 * itself. | |
11337 * @param {number=} opt_size Optional preferred size of the favicon. | |
11338 * @param {string=} opt_type Optional type of favicon to request. Valid values | |
11339 * are 'favicon' and 'touch-icon'. Default is 'favicon'. | |
11340 * @return {string} -webkit-image-set for the favicon. | |
11341 */ | |
11342 function getFaviconImageSet(url, opt_size, opt_type) { | 8184 function getFaviconImageSet(url, opt_size, opt_type) { |
11343 var size = opt_size || 16; | 8185 var size = opt_size || 16; |
11344 var type = opt_type || 'favicon'; | 8186 var type = opt_type || 'favicon'; |
11345 | 8187 |
11346 return imageset( | 8188 return imageset( |
11347 'chrome://' + type + '/size/' + size + '@scalefactorx/' + | 8189 'chrome://' + type + '/size/' + size + '@scalefactorx/' + |
11348 // Note: Literal 'iconurl' must match |kIconURLParameter| in | |
11349 // components/favicon_base/favicon_url_parser.cc. | |
11350 (FAVICON_URL_REGEX.test(url) ? 'iconurl/' : '') + url); | 8190 (FAVICON_URL_REGEX.test(url) ? 'iconurl/' : '') + url); |
11351 } | 8191 } |
11352 | 8192 |
11353 return { | 8193 return { |
11354 getSupportedScaleFactors: getSupportedScaleFactors, | 8194 getSupportedScaleFactors: getSupportedScaleFactors, |
11355 getProfileAvatarIcon: getProfileAvatarIcon, | 8195 getProfileAvatarIcon: getProfileAvatarIcon, |
11356 getFaviconImageSet: getFaviconImageSet, | 8196 getFaviconImageSet: getFaviconImageSet, |
11357 }; | 8197 }; |
11358 }); | 8198 }); |
11359 // Copyright 2016 The Chromium Authors. All rights reserved. | 8199 // Copyright 2016 The Chromium Authors. All rights reserved. |
11360 // Use of this source code is governed by a BSD-style license that can be | 8200 // Use of this source code is governed by a BSD-style license that can be |
11361 // found in the LICENSE file. | 8201 // found in the LICENSE file. |
11362 | 8202 |
11363 /** | |
11364 * @fileoverview Defines a singleton object, md_history.BrowserService, which | |
11365 * provides access to chrome.send APIs. | |
11366 */ | |
11367 | 8203 |
11368 cr.define('md_history', function() { | 8204 cr.define('md_history', function() { |
11369 /** @constructor */ | 8205 /** @constructor */ |
11370 function BrowserService() { | 8206 function BrowserService() { |
11371 /** @private {Array<!HistoryEntry>} */ | 8207 /** @private {Array<!HistoryEntry>} */ |
11372 this.pendingDeleteItems_ = null; | 8208 this.pendingDeleteItems_ = null; |
11373 /** @private {PromiseResolver} */ | 8209 /** @private {PromiseResolver} */ |
11374 this.pendingDeletePromise_ = null; | 8210 this.pendingDeletePromise_ = null; |
11375 } | 8211 } |
11376 | 8212 |
11377 BrowserService.prototype = { | 8213 BrowserService.prototype = { |
11378 /** | |
11379 * @param {!Array<!HistoryEntry>} items | |
11380 * @return {Promise<!Array<!HistoryEntry>>} | |
11381 */ | |
11382 deleteItems: function(items) { | 8214 deleteItems: function(items) { |
11383 if (this.pendingDeleteItems_ != null) { | 8215 if (this.pendingDeleteItems_ != null) { |
11384 // There's already a deletion in progress, reject immediately. | |
11385 return new Promise(function(resolve, reject) { reject(items); }); | 8216 return new Promise(function(resolve, reject) { reject(items); }); |
11386 } | 8217 } |
11387 | 8218 |
11388 var removalList = items.map(function(item) { | 8219 var removalList = items.map(function(item) { |
11389 return { | 8220 return { |
11390 url: item.url, | 8221 url: item.url, |
11391 timestamps: item.allTimestamps | 8222 timestamps: item.allTimestamps |
11392 }; | 8223 }; |
11393 }); | 8224 }); |
11394 | 8225 |
11395 this.pendingDeleteItems_ = items; | 8226 this.pendingDeleteItems_ = items; |
11396 this.pendingDeletePromise_ = new PromiseResolver(); | 8227 this.pendingDeletePromise_ = new PromiseResolver(); |
11397 | 8228 |
11398 chrome.send('removeVisits', removalList); | 8229 chrome.send('removeVisits', removalList); |
11399 | 8230 |
11400 return this.pendingDeletePromise_.promise; | 8231 return this.pendingDeletePromise_.promise; |
11401 }, | 8232 }, |
11402 | 8233 |
11403 /** | |
11404 * @param {!string} url | |
11405 */ | |
11406 removeBookmark: function(url) { | 8234 removeBookmark: function(url) { |
11407 chrome.send('removeBookmark', [url]); | 8235 chrome.send('removeBookmark', [url]); |
11408 }, | 8236 }, |
11409 | 8237 |
11410 /** | |
11411 * @param {string} sessionTag | |
11412 */ | |
11413 openForeignSessionAllTabs: function(sessionTag) { | 8238 openForeignSessionAllTabs: function(sessionTag) { |
11414 chrome.send('openForeignSession', [sessionTag]); | 8239 chrome.send('openForeignSession', [sessionTag]); |
11415 }, | 8240 }, |
11416 | 8241 |
11417 /** | |
11418 * @param {string} sessionTag | |
11419 * @param {number} windowId | |
11420 * @param {number} tabId | |
11421 * @param {MouseEvent} e | |
11422 */ | |
11423 openForeignSessionTab: function(sessionTag, windowId, tabId, e) { | 8242 openForeignSessionTab: function(sessionTag, windowId, tabId, e) { |
11424 chrome.send('openForeignSession', [ | 8243 chrome.send('openForeignSession', [ |
11425 sessionTag, String(windowId), String(tabId), e.button || 0, e.altKey, | 8244 sessionTag, String(windowId), String(tabId), e.button || 0, e.altKey, |
11426 e.ctrlKey, e.metaKey, e.shiftKey | 8245 e.ctrlKey, e.metaKey, e.shiftKey |
11427 ]); | 8246 ]); |
11428 }, | 8247 }, |
11429 | 8248 |
11430 /** | |
11431 * @param {string} sessionTag | |
11432 */ | |
11433 deleteForeignSession: function(sessionTag) { | 8249 deleteForeignSession: function(sessionTag) { |
11434 chrome.send('deleteForeignSession', [sessionTag]); | 8250 chrome.send('deleteForeignSession', [sessionTag]); |
11435 }, | 8251 }, |
11436 | 8252 |
11437 openClearBrowsingData: function() { | 8253 openClearBrowsingData: function() { |
11438 chrome.send('clearBrowsingData'); | 8254 chrome.send('clearBrowsingData'); |
11439 }, | 8255 }, |
11440 | 8256 |
11441 /** | |
11442 * Record an action in UMA. | |
11443 * @param {string} actionDesc The name of the action to be logged. | |
11444 */ | |
11445 recordAction: function(actionDesc) { | 8257 recordAction: function(actionDesc) { |
11446 chrome.send('metricsHandler:recordAction', [actionDesc]); | 8258 chrome.send('metricsHandler:recordAction', [actionDesc]); |
11447 }, | 8259 }, |
11448 | 8260 |
11449 /** | |
11450 * @param {boolean} successful | |
11451 * @private | |
11452 */ | |
11453 resolveDelete_: function(successful) { | 8261 resolveDelete_: function(successful) { |
11454 if (this.pendingDeleteItems_ == null || | 8262 if (this.pendingDeleteItems_ == null || |
11455 this.pendingDeletePromise_ == null) { | 8263 this.pendingDeletePromise_ == null) { |
11456 return; | 8264 return; |
11457 } | 8265 } |
11458 | 8266 |
11459 if (successful) | 8267 if (successful) |
11460 this.pendingDeletePromise_.resolve(this.pendingDeleteItems_); | 8268 this.pendingDeletePromise_.resolve(this.pendingDeleteItems_); |
11461 else | 8269 else |
11462 this.pendingDeletePromise_.reject(this.pendingDeleteItems_); | 8270 this.pendingDeletePromise_.reject(this.pendingDeleteItems_); |
11463 | 8271 |
11464 this.pendingDeleteItems_ = null; | 8272 this.pendingDeleteItems_ = null; |
11465 this.pendingDeletePromise_ = null; | 8273 this.pendingDeletePromise_ = null; |
11466 }, | 8274 }, |
11467 }; | 8275 }; |
11468 | 8276 |
11469 cr.addSingletonGetter(BrowserService); | 8277 cr.addSingletonGetter(BrowserService); |
11470 | 8278 |
11471 return {BrowserService: BrowserService}; | 8279 return {BrowserService: BrowserService}; |
11472 }); | 8280 }); |
11473 | 8281 |
11474 /** | |
11475 * Called by the history backend when deletion was succesful. | |
11476 */ | |
11477 function deleteComplete() { | 8282 function deleteComplete() { |
11478 md_history.BrowserService.getInstance().resolveDelete_(true); | 8283 md_history.BrowserService.getInstance().resolveDelete_(true); |
11479 } | 8284 } |
11480 | 8285 |
11481 /** | |
11482 * Called by the history backend when the deletion failed. | |
11483 */ | |
11484 function deleteFailed() { | 8286 function deleteFailed() { |
11485 md_history.BrowserService.getInstance().resolveDelete_(false); | 8287 md_history.BrowserService.getInstance().resolveDelete_(false); |
11486 }; | 8288 }; |
11487 // Copyright 2016 The Chromium Authors. All rights reserved. | 8289 // Copyright 2016 The Chromium Authors. All rights reserved. |
11488 // Use of this source code is governed by a BSD-style license that can be | 8290 // Use of this source code is governed by a BSD-style license that can be |
11489 // found in the LICENSE file. | 8291 // found in the LICENSE file. |
11490 | 8292 |
11491 Polymer({ | 8293 Polymer({ |
11492 is: 'history-searched-label', | 8294 is: 'history-searched-label', |
11493 | 8295 |
11494 properties: { | 8296 properties: { |
11495 // The text to show in this label. | |
11496 title: String, | 8297 title: String, |
11497 | 8298 |
11498 // The search term to bold within the title. | |
11499 searchTerm: String, | 8299 searchTerm: String, |
11500 }, | 8300 }, |
11501 | 8301 |
11502 observers: ['setSearchedTextToBold_(title, searchTerm)'], | 8302 observers: ['setSearchedTextToBold_(title, searchTerm)'], |
11503 | 8303 |
11504 /** | |
11505 * Updates the page title. If a search term is specified, highlights any | |
11506 * occurrences of the search term in bold. | |
11507 * @private | |
11508 */ | |
11509 setSearchedTextToBold_: function() { | 8304 setSearchedTextToBold_: function() { |
11510 var i = 0; | 8305 var i = 0; |
11511 var titleElem = this.$.container; | 8306 var titleElem = this.$.container; |
11512 var titleText = this.title; | 8307 var titleText = this.title; |
11513 | 8308 |
11514 if (this.searchTerm == '' || this.searchTerm == null) { | 8309 if (this.searchTerm == '' || this.searchTerm == null) { |
11515 titleElem.textContent = titleText; | 8310 titleElem.textContent = titleText; |
11516 return; | 8311 return; |
11517 } | 8312 } |
11518 | 8313 |
11519 var re = new RegExp(quoteString(this.searchTerm), 'gim'); | 8314 var re = new RegExp(quoteString(this.searchTerm), 'gim'); |
11520 var match; | 8315 var match; |
11521 titleElem.textContent = ''; | 8316 titleElem.textContent = ''; |
11522 while (match = re.exec(titleText)) { | 8317 while (match = re.exec(titleText)) { |
11523 if (match.index > i) | 8318 if (match.index > i) |
11524 titleElem.appendChild(document.createTextNode( | 8319 titleElem.appendChild(document.createTextNode( |
11525 titleText.slice(i, match.index))); | 8320 titleText.slice(i, match.index))); |
11526 i = re.lastIndex; | 8321 i = re.lastIndex; |
11527 // Mark the highlighted text in bold. | |
11528 var b = document.createElement('b'); | 8322 var b = document.createElement('b'); |
11529 b.textContent = titleText.substring(match.index, i); | 8323 b.textContent = titleText.substring(match.index, i); |
11530 titleElem.appendChild(b); | 8324 titleElem.appendChild(b); |
11531 } | 8325 } |
11532 if (i < titleText.length) | 8326 if (i < titleText.length) |
11533 titleElem.appendChild( | 8327 titleElem.appendChild( |
11534 document.createTextNode(titleText.slice(i))); | 8328 document.createTextNode(titleText.slice(i))); |
11535 }, | 8329 }, |
11536 }); | 8330 }); |
11537 // Copyright 2015 The Chromium Authors. All rights reserved. | 8331 // Copyright 2015 The Chromium Authors. All rights reserved. |
11538 // Use of this source code is governed by a BSD-style license that can be | 8332 // Use of this source code is governed by a BSD-style license that can be |
11539 // found in the LICENSE file. | 8333 // found in the LICENSE file. |
11540 | 8334 |
11541 cr.define('md_history', function() { | 8335 cr.define('md_history', function() { |
11542 var HistoryItem = Polymer({ | 8336 var HistoryItem = Polymer({ |
11543 is: 'history-item', | 8337 is: 'history-item', |
11544 | 8338 |
11545 properties: { | 8339 properties: { |
11546 // Underlying HistoryEntry data for this item. Contains read-only fields | |
11547 // from the history backend, as well as fields computed by history-list. | |
11548 item: {type: Object, observer: 'showIcon_'}, | 8340 item: {type: Object, observer: 'showIcon_'}, |
11549 | 8341 |
11550 // Search term used to obtain this history-item. | |
11551 searchTerm: {type: String}, | 8342 searchTerm: {type: String}, |
11552 | 8343 |
11553 selected: {type: Boolean, notify: true}, | 8344 selected: {type: Boolean, notify: true}, |
11554 | 8345 |
11555 isFirstItem: {type: Boolean, reflectToAttribute: true}, | 8346 isFirstItem: {type: Boolean, reflectToAttribute: true}, |
11556 | 8347 |
11557 isCardStart: {type: Boolean, reflectToAttribute: true}, | 8348 isCardStart: {type: Boolean, reflectToAttribute: true}, |
11558 | 8349 |
11559 isCardEnd: {type: Boolean, reflectToAttribute: true}, | 8350 isCardEnd: {type: Boolean, reflectToAttribute: true}, |
11560 | 8351 |
11561 // True if the item is being displayed embedded in another element and | |
11562 // should not manage its own borders or size. | |
11563 embedded: {type: Boolean, reflectToAttribute: true}, | 8352 embedded: {type: Boolean, reflectToAttribute: true}, |
11564 | 8353 |
11565 hasTimeGap: {type: Boolean}, | 8354 hasTimeGap: {type: Boolean}, |
11566 | 8355 |
11567 numberOfItems: {type: Number}, | 8356 numberOfItems: {type: Number}, |
11568 | 8357 |
11569 // The path of this history item inside its parent. | |
11570 path: String, | 8358 path: String, |
11571 }, | 8359 }, |
11572 | 8360 |
11573 /** | |
11574 * When a history-item is selected the toolbar is notified and increases | |
11575 * or decreases its count of selected items accordingly. | |
11576 * @param {MouseEvent} e | |
11577 * @private | |
11578 */ | |
11579 onCheckboxSelected_: function(e) { | 8361 onCheckboxSelected_: function(e) { |
11580 // TODO(calamity): Fire this event whenever |selected| changes. | |
11581 this.fire('history-checkbox-select', { | 8362 this.fire('history-checkbox-select', { |
11582 element: this, | 8363 element: this, |
11583 shiftKey: e.shiftKey, | 8364 shiftKey: e.shiftKey, |
11584 }); | 8365 }); |
11585 e.preventDefault(); | 8366 e.preventDefault(); |
11586 }, | 8367 }, |
11587 | 8368 |
11588 /** | |
11589 * @param {MouseEvent} e | |
11590 * @private | |
11591 */ | |
11592 onCheckboxMousedown_: function(e) { | 8369 onCheckboxMousedown_: function(e) { |
11593 // Prevent shift clicking a checkbox from selecting text. | |
11594 if (e.shiftKey) | 8370 if (e.shiftKey) |
11595 e.preventDefault(); | 8371 e.preventDefault(); |
11596 }, | 8372 }, |
11597 | 8373 |
11598 /** | |
11599 * Remove bookmark of current item when bookmark-star is clicked. | |
11600 * @private | |
11601 */ | |
11602 onRemoveBookmarkTap_: function() { | 8374 onRemoveBookmarkTap_: function() { |
11603 if (!this.item.starred) | 8375 if (!this.item.starred) |
11604 return; | 8376 return; |
11605 | 8377 |
11606 if (this.$$('#bookmark-star') == this.root.activeElement) | 8378 if (this.$$('#bookmark-star') == this.root.activeElement) |
11607 this.$['menu-button'].focus(); | 8379 this.$['menu-button'].focus(); |
11608 | 8380 |
11609 md_history.BrowserService.getInstance() | 8381 md_history.BrowserService.getInstance() |
11610 .removeBookmark(this.item.url); | 8382 .removeBookmark(this.item.url); |
11611 this.fire('remove-bookmark-stars', this.item.url); | 8383 this.fire('remove-bookmark-stars', this.item.url); |
11612 }, | 8384 }, |
11613 | 8385 |
11614 /** | |
11615 * Fires a custom event when the menu button is clicked. Sends the details | |
11616 * of the history item and where the menu should appear. | |
11617 */ | |
11618 onMenuButtonTap_: function(e) { | 8386 onMenuButtonTap_: function(e) { |
11619 this.fire('toggle-menu', { | 8387 this.fire('toggle-menu', { |
11620 target: Polymer.dom(e).localTarget, | 8388 target: Polymer.dom(e).localTarget, |
11621 item: this.item, | 8389 item: this.item, |
11622 path: this.path, | 8390 path: this.path, |
11623 }); | 8391 }); |
11624 | 8392 |
11625 // Stops the 'tap' event from closing the menu when it opens. | |
11626 e.stopPropagation(); | 8393 e.stopPropagation(); |
11627 }, | 8394 }, |
11628 | 8395 |
11629 /** | |
11630 * Set the favicon image, based on the URL of the history item. | |
11631 * @private | |
11632 */ | |
11633 showIcon_: function() { | 8396 showIcon_: function() { |
11634 this.$.icon.style.backgroundImage = | 8397 this.$.icon.style.backgroundImage = |
11635 cr.icon.getFaviconImageSet(this.item.url); | 8398 cr.icon.getFaviconImageSet(this.item.url); |
11636 }, | 8399 }, |
11637 | 8400 |
11638 selectionNotAllowed_: function() { | 8401 selectionNotAllowed_: function() { |
11639 return !loadTimeData.getBoolean('allowDeletingHistory'); | 8402 return !loadTimeData.getBoolean('allowDeletingHistory'); |
11640 }, | 8403 }, |
11641 | 8404 |
11642 /** | |
11643 * Generates the title for this history card. | |
11644 * @param {number} numberOfItems The number of items in the card. | |
11645 * @param {string} search The search term associated with these results. | |
11646 * @private | |
11647 */ | |
11648 cardTitle_: function(numberOfItems, historyDate, search) { | 8405 cardTitle_: function(numberOfItems, historyDate, search) { |
11649 if (!search) | 8406 if (!search) |
11650 return this.item.dateRelativeDay; | 8407 return this.item.dateRelativeDay; |
11651 | 8408 |
11652 var resultId = numberOfItems == 1 ? 'searchResult' : 'searchResults'; | 8409 var resultId = numberOfItems == 1 ? 'searchResult' : 'searchResults'; |
11653 return loadTimeData.getStringF('foundSearchResults', numberOfItems, | 8410 return loadTimeData.getStringF('foundSearchResults', numberOfItems, |
11654 loadTimeData.getString(resultId), search); | 8411 loadTimeData.getString(resultId), search); |
11655 }, | 8412 }, |
11656 | 8413 |
11657 /** | |
11658 * Crop long item titles to reduce their effect on layout performance. See | |
11659 * crbug.com/621347. | |
11660 * @param {string} title | |
11661 * @return {string} | |
11662 */ | |
11663 cropItemTitle_: function(title) { | 8414 cropItemTitle_: function(title) { |
11664 return (title.length > TITLE_MAX_LENGTH) ? | 8415 return (title.length > TITLE_MAX_LENGTH) ? |
11665 title.substr(0, TITLE_MAX_LENGTH) : | 8416 title.substr(0, TITLE_MAX_LENGTH) : |
11666 title; | 8417 title; |
11667 } | 8418 } |
11668 }); | 8419 }); |
11669 | 8420 |
11670 /** | |
11671 * Check whether the time difference between the given history item and the | |
11672 * next one is large enough for a spacer to be required. | |
11673 * @param {Array<HistoryEntry>} visits | |
11674 * @param {number} currentIndex | |
11675 * @param {string} searchedTerm | |
11676 * @return {boolean} Whether or not time gap separator is required. | |
11677 * @private | |
11678 */ | |
11679 HistoryItem.needsTimeGap = function(visits, currentIndex, searchedTerm) { | 8421 HistoryItem.needsTimeGap = function(visits, currentIndex, searchedTerm) { |
11680 if (currentIndex >= visits.length - 1 || visits.length == 0) | 8422 if (currentIndex >= visits.length - 1 || visits.length == 0) |
11681 return false; | 8423 return false; |
11682 | 8424 |
11683 var currentItem = visits[currentIndex]; | 8425 var currentItem = visits[currentIndex]; |
11684 var nextItem = visits[currentIndex + 1]; | 8426 var nextItem = visits[currentIndex + 1]; |
11685 | 8427 |
11686 if (searchedTerm) | 8428 if (searchedTerm) |
11687 return currentItem.dateShort != nextItem.dateShort; | 8429 return currentItem.dateShort != nextItem.dateShort; |
11688 | 8430 |
11689 return currentItem.time - nextItem.time > BROWSING_GAP_TIME && | 8431 return currentItem.time - nextItem.time > BROWSING_GAP_TIME && |
11690 currentItem.dateRelativeDay == nextItem.dateRelativeDay; | 8432 currentItem.dateRelativeDay == nextItem.dateRelativeDay; |
11691 }; | 8433 }; |
11692 | 8434 |
11693 return { HistoryItem: HistoryItem }; | 8435 return { HistoryItem: HistoryItem }; |
11694 }); | 8436 }); |
11695 // Copyright 2016 The Chromium Authors. All rights reserved. | 8437 // Copyright 2016 The Chromium Authors. All rights reserved. |
11696 // Use of this source code is governed by a BSD-style license that can be | 8438 // Use of this source code is governed by a BSD-style license that can be |
11697 // found in the LICENSE file. | 8439 // found in the LICENSE file. |
11698 | 8440 |
11699 /** | |
11700 * @constructor | |
11701 * @param {string} currentPath | |
11702 */ | |
11703 var SelectionTreeNode = function(currentPath) { | 8441 var SelectionTreeNode = function(currentPath) { |
11704 /** @type {string} */ | 8442 /** @type {string} */ |
11705 this.currentPath = currentPath; | 8443 this.currentPath = currentPath; |
11706 /** @type {boolean} */ | 8444 /** @type {boolean} */ |
11707 this.leaf = false; | 8445 this.leaf = false; |
11708 /** @type {Array<number>} */ | 8446 /** @type {Array<number>} */ |
11709 this.indexes = []; | 8447 this.indexes = []; |
11710 /** @type {Array<SelectionTreeNode>} */ | 8448 /** @type {Array<SelectionTreeNode>} */ |
11711 this.children = []; | 8449 this.children = []; |
11712 }; | 8450 }; |
11713 | 8451 |
11714 /** | |
11715 * @param {number} index | |
11716 * @param {string} path | |
11717 */ | |
11718 SelectionTreeNode.prototype.addChild = function(index, path) { | 8452 SelectionTreeNode.prototype.addChild = function(index, path) { |
11719 this.indexes.push(index); | 8453 this.indexes.push(index); |
11720 this.children[index] = new SelectionTreeNode(path); | 8454 this.children[index] = new SelectionTreeNode(path); |
11721 }; | 8455 }; |
11722 | 8456 |
11723 /** @polymerBehavior */ | 8457 /** @polymerBehavior */ |
11724 var HistoryListBehavior = { | 8458 var HistoryListBehavior = { |
11725 properties: { | 8459 properties: { |
11726 /** | |
11727 * Polymer paths to the history items contained in this list. | |
11728 * @type {!Set<string>} selectedPaths | |
11729 */ | |
11730 selectedPaths: { | 8460 selectedPaths: { |
11731 type: Object, | 8461 type: Object, |
11732 value: /** @return {!Set<string>} */ function() { return new Set(); } | 8462 value: /** @return {!Set<string>} */ function() { return new Set(); } |
11733 }, | 8463 }, |
11734 | 8464 |
11735 lastSelectedPath: String, | 8465 lastSelectedPath: String, |
11736 }, | 8466 }, |
11737 | 8467 |
11738 listeners: { | 8468 listeners: { |
11739 'history-checkbox-select': 'itemSelected_', | 8469 'history-checkbox-select': 'itemSelected_', |
11740 }, | 8470 }, |
11741 | 8471 |
11742 /** | |
11743 * @param {number} historyDataLength | |
11744 * @return {boolean} | |
11745 * @private | |
11746 */ | |
11747 hasResults: function(historyDataLength) { return historyDataLength > 0; }, | 8472 hasResults: function(historyDataLength) { return historyDataLength > 0; }, |
11748 | 8473 |
11749 /** | |
11750 * @param {string} searchedTerm | |
11751 * @param {boolean} isLoading | |
11752 * @return {string} | |
11753 * @private | |
11754 */ | |
11755 noResultsMessage: function(searchedTerm, isLoading) { | 8474 noResultsMessage: function(searchedTerm, isLoading) { |
11756 if (isLoading) | 8475 if (isLoading) |
11757 return ''; | 8476 return ''; |
11758 | 8477 |
11759 var messageId = searchedTerm !== '' ? 'noSearchResults' : 'noResults'; | 8478 var messageId = searchedTerm !== '' ? 'noSearchResults' : 'noResults'; |
11760 return loadTimeData.getString(messageId); | 8479 return loadTimeData.getString(messageId); |
11761 }, | 8480 }, |
11762 | 8481 |
11763 /** | |
11764 * Deselect each item in |selectedPaths|. | |
11765 */ | |
11766 unselectAllItems: function() { | 8482 unselectAllItems: function() { |
11767 this.selectedPaths.forEach(function(path) { | 8483 this.selectedPaths.forEach(function(path) { |
11768 this.set(path + '.selected', false); | 8484 this.set(path + '.selected', false); |
11769 }.bind(this)); | 8485 }.bind(this)); |
11770 | 8486 |
11771 this.selectedPaths.clear(); | 8487 this.selectedPaths.clear(); |
11772 }, | 8488 }, |
11773 | 8489 |
11774 /** | |
11775 * Performs a request to the backend to delete all selected items. If | |
11776 * successful, removes them from the view. Does not prompt the user before | |
11777 * deleting -- see <history-list-container> for a version of this method which | |
11778 * does prompt. | |
11779 */ | |
11780 deleteSelected: function() { | 8490 deleteSelected: function() { |
11781 var toBeRemoved = | 8491 var toBeRemoved = |
11782 Array.from(this.selectedPaths.values()).map(function(path) { | 8492 Array.from(this.selectedPaths.values()).map(function(path) { |
11783 return this.get(path); | 8493 return this.get(path); |
11784 }.bind(this)); | 8494 }.bind(this)); |
11785 | 8495 |
11786 md_history.BrowserService.getInstance() | 8496 md_history.BrowserService.getInstance() |
11787 .deleteItems(toBeRemoved) | 8497 .deleteItems(toBeRemoved) |
11788 .then(function() { | 8498 .then(function() { |
11789 this.removeItemsByPath(Array.from(this.selectedPaths)); | 8499 this.removeItemsByPath(Array.from(this.selectedPaths)); |
11790 this.fire('unselect-all'); | 8500 this.fire('unselect-all'); |
11791 }.bind(this)); | 8501 }.bind(this)); |
11792 }, | 8502 }, |
11793 | 8503 |
11794 /** | |
11795 * Removes the history items in |paths|. Assumes paths are of a.0.b.0... | |
11796 * structure. | |
11797 * | |
11798 * We want to use notifySplices to update the arrays for performance reasons | |
11799 * which requires manually batching and sending the notifySplices for each | |
11800 * level. To do this, we build a tree where each node is an array and then | |
11801 * depth traverse it to remove items. Each time a node has all children | |
11802 * deleted, we can also remove the node. | |
11803 * | |
11804 * @param {Array<string>} paths | |
11805 * @private | |
11806 */ | |
11807 removeItemsByPath: function(paths) { | 8504 removeItemsByPath: function(paths) { |
11808 if (paths.length == 0) | 8505 if (paths.length == 0) |
11809 return; | 8506 return; |
11810 | 8507 |
11811 this.removeItemsBeneathNode_(this.buildRemovalTree_(paths)); | 8508 this.removeItemsBeneathNode_(this.buildRemovalTree_(paths)); |
11812 }, | 8509 }, |
11813 | 8510 |
11814 /** | |
11815 * Creates the tree to traverse in order to remove |paths| from this list. | |
11816 * Assumes paths are of a.0.b.0... | |
11817 * structure. | |
11818 * | |
11819 * @param {Array<string>} paths | |
11820 * @return {SelectionTreeNode} | |
11821 * @private | |
11822 */ | |
11823 buildRemovalTree_: function(paths) { | 8511 buildRemovalTree_: function(paths) { |
11824 var rootNode = new SelectionTreeNode(paths[0].split('.')[0]); | 8512 var rootNode = new SelectionTreeNode(paths[0].split('.')[0]); |
11825 | 8513 |
11826 // Build a tree to each history item specified in |paths|. | |
11827 paths.forEach(function(path) { | 8514 paths.forEach(function(path) { |
11828 var components = path.split('.'); | 8515 var components = path.split('.'); |
11829 var node = rootNode; | 8516 var node = rootNode; |
11830 components.shift(); | 8517 components.shift(); |
11831 while (components.length > 1) { | 8518 while (components.length > 1) { |
11832 var index = Number(components.shift()); | 8519 var index = Number(components.shift()); |
11833 var arrayName = components.shift(); | 8520 var arrayName = components.shift(); |
11834 | 8521 |
11835 if (!node.children[index]) | 8522 if (!node.children[index]) |
11836 node.addChild(index, [node.currentPath, index, arrayName].join('.')); | 8523 node.addChild(index, [node.currentPath, index, arrayName].join('.')); |
11837 | 8524 |
11838 node = node.children[index]; | 8525 node = node.children[index]; |
11839 } | 8526 } |
11840 node.leaf = true; | 8527 node.leaf = true; |
11841 node.indexes.push(Number(components.shift())); | 8528 node.indexes.push(Number(components.shift())); |
11842 }); | 8529 }); |
11843 | 8530 |
11844 return rootNode; | 8531 return rootNode; |
11845 }, | 8532 }, |
11846 | 8533 |
11847 /** | |
11848 * Removes the history items underneath |node| and deletes container arrays as | |
11849 * they become empty. | |
11850 * @param {SelectionTreeNode} node | |
11851 * @return {boolean} Whether this node's array should be deleted. | |
11852 * @private | |
11853 */ | |
11854 removeItemsBeneathNode_: function(node) { | 8534 removeItemsBeneathNode_: function(node) { |
11855 var array = this.get(node.currentPath); | 8535 var array = this.get(node.currentPath); |
11856 var splices = []; | 8536 var splices = []; |
11857 | 8537 |
11858 node.indexes.sort(function(a, b) { return b - a; }); | 8538 node.indexes.sort(function(a, b) { return b - a; }); |
11859 node.indexes.forEach(function(index) { | 8539 node.indexes.forEach(function(index) { |
11860 if (node.leaf || this.removeItemsBeneathNode_(node.children[index])) { | 8540 if (node.leaf || this.removeItemsBeneathNode_(node.children[index])) { |
11861 var item = array.splice(index, 1); | 8541 var item = array.splice(index, 1); |
11862 splices.push({ | 8542 splices.push({ |
11863 index: index, | 8543 index: index, |
11864 removed: [item], | 8544 removed: [item], |
11865 addedCount: 0, | 8545 addedCount: 0, |
11866 object: array, | 8546 object: array, |
11867 type: 'splice' | 8547 type: 'splice' |
11868 }); | 8548 }); |
11869 } | 8549 } |
11870 }.bind(this)); | 8550 }.bind(this)); |
11871 | 8551 |
11872 if (array.length == 0) | 8552 if (array.length == 0) |
11873 return true; | 8553 return true; |
11874 | 8554 |
11875 // notifySplices gives better performance than individually splicing as it | |
11876 // batches all of the updates together. | |
11877 this.notifySplices(node.currentPath, splices); | 8555 this.notifySplices(node.currentPath, splices); |
11878 return false; | 8556 return false; |
11879 }, | 8557 }, |
11880 | 8558 |
11881 /** | |
11882 * @param {Event} e | |
11883 * @private | |
11884 */ | |
11885 itemSelected_: function(e) { | 8559 itemSelected_: function(e) { |
11886 var item = e.detail.element; | 8560 var item = e.detail.element; |
11887 var paths = []; | 8561 var paths = []; |
11888 var itemPath = item.path; | 8562 var itemPath = item.path; |
11889 | 8563 |
11890 // Handle shift selection. Change the selection state of all items between | |
11891 // |path| and |lastSelected| to the selection state of |item|. | |
11892 if (e.detail.shiftKey && this.lastSelectedPath) { | 8564 if (e.detail.shiftKey && this.lastSelectedPath) { |
11893 var itemPathComponents = itemPath.split('.'); | 8565 var itemPathComponents = itemPath.split('.'); |
11894 var itemIndex = Number(itemPathComponents.pop()); | 8566 var itemIndex = Number(itemPathComponents.pop()); |
11895 var itemArrayPath = itemPathComponents.join('.'); | 8567 var itemArrayPath = itemPathComponents.join('.'); |
11896 | 8568 |
11897 var lastItemPathComponents = this.lastSelectedPath.split('.'); | 8569 var lastItemPathComponents = this.lastSelectedPath.split('.'); |
11898 var lastItemIndex = Number(lastItemPathComponents.pop()); | 8570 var lastItemIndex = Number(lastItemPathComponents.pop()); |
11899 if (itemArrayPath == lastItemPathComponents.join('.')) { | 8571 if (itemArrayPath == lastItemPathComponents.join('.')) { |
11900 for (var i = Math.min(itemIndex, lastItemIndex); | 8572 for (var i = Math.min(itemIndex, lastItemIndex); |
11901 i <= Math.max(itemIndex, lastItemIndex); i++) { | 8573 i <= Math.max(itemIndex, lastItemIndex); i++) { |
(...skipping 16 matching lines...) Expand all Loading... |
11918 this.selectedPaths.delete(path); | 8590 this.selectedPaths.delete(path); |
11919 }.bind(this)); | 8591 }.bind(this)); |
11920 | 8592 |
11921 this.lastSelectedPath = itemPath; | 8593 this.lastSelectedPath = itemPath; |
11922 }, | 8594 }, |
11923 }; | 8595 }; |
11924 // Copyright 2016 The Chromium Authors. All rights reserved. | 8596 // Copyright 2016 The Chromium Authors. All rights reserved. |
11925 // Use of this source code is governed by a BSD-style license that can be | 8597 // Use of this source code is governed by a BSD-style license that can be |
11926 // found in the LICENSE file. | 8598 // found in the LICENSE file. |
11927 | 8599 |
11928 /** | |
11929 * @typedef {{domain: string, | |
11930 * visits: !Array<HistoryEntry>, | |
11931 * rendered: boolean, | |
11932 * expanded: boolean}} | |
11933 */ | |
11934 var HistoryDomain; | 8600 var HistoryDomain; |
11935 | 8601 |
11936 /** | |
11937 * @typedef {{title: string, | |
11938 * domains: !Array<HistoryDomain>}} | |
11939 */ | |
11940 var HistoryGroup; | 8602 var HistoryGroup; |
11941 | 8603 |
11942 Polymer({ | 8604 Polymer({ |
11943 is: 'history-grouped-list', | 8605 is: 'history-grouped-list', |
11944 | 8606 |
11945 behaviors: [HistoryListBehavior], | 8607 behaviors: [HistoryListBehavior], |
11946 | 8608 |
11947 properties: { | 8609 properties: { |
11948 // An array of history entries in reverse chronological order. | |
11949 historyData: { | 8610 historyData: { |
11950 type: Array, | 8611 type: Array, |
11951 }, | 8612 }, |
11952 | 8613 |
11953 /** | |
11954 * @type {Array<HistoryGroup>} | |
11955 */ | |
11956 groupedHistoryData_: { | 8614 groupedHistoryData_: { |
11957 type: Array, | 8615 type: Array, |
11958 }, | 8616 }, |
11959 | 8617 |
11960 searchedTerm: { | 8618 searchedTerm: { |
11961 type: String, | 8619 type: String, |
11962 value: '' | 8620 value: '' |
11963 }, | 8621 }, |
11964 | 8622 |
11965 range: { | 8623 range: { |
11966 type: Number, | 8624 type: Number, |
11967 }, | 8625 }, |
11968 | 8626 |
11969 queryStartTime: String, | 8627 queryStartTime: String, |
11970 queryEndTime: String, | 8628 queryEndTime: String, |
11971 }, | 8629 }, |
11972 | 8630 |
11973 observers: [ | 8631 observers: [ |
11974 'updateGroupedHistoryData_(range, historyData)' | 8632 'updateGroupedHistoryData_(range, historyData)' |
11975 ], | 8633 ], |
11976 | 8634 |
11977 /** | |
11978 * Make a list of domains from visits. | |
11979 * @param {!Array<!HistoryEntry>} visits | |
11980 * @return {!Array<!HistoryDomain>} | |
11981 */ | |
11982 createHistoryDomains_: function(visits) { | 8635 createHistoryDomains_: function(visits) { |
11983 var domainIndexes = {}; | 8636 var domainIndexes = {}; |
11984 var domains = []; | 8637 var domains = []; |
11985 | 8638 |
11986 // Group the visits into a dictionary and generate a list of domains. | |
11987 for (var i = 0, visit; visit = visits[i]; i++) { | 8639 for (var i = 0, visit; visit = visits[i]; i++) { |
11988 var domain = visit.domain; | 8640 var domain = visit.domain; |
11989 if (domainIndexes[domain] == undefined) { | 8641 if (domainIndexes[domain] == undefined) { |
11990 domainIndexes[domain] = domains.length; | 8642 domainIndexes[domain] = domains.length; |
11991 domains.push({ | 8643 domains.push({ |
11992 domain: domain, | 8644 domain: domain, |
11993 visits: [], | 8645 visits: [], |
11994 expanded: false, | 8646 expanded: false, |
11995 rendered: false, | 8647 rendered: false, |
11996 }); | 8648 }); |
11997 } | 8649 } |
11998 domains[domainIndexes[domain]].visits.push(visit); | 8650 domains[domainIndexes[domain]].visits.push(visit); |
11999 } | 8651 } |
12000 var sortByVisits = function(a, b) { | 8652 var sortByVisits = function(a, b) { |
12001 return b.visits.length - a.visits.length; | 8653 return b.visits.length - a.visits.length; |
12002 }; | 8654 }; |
12003 domains.sort(sortByVisits); | 8655 domains.sort(sortByVisits); |
12004 | 8656 |
12005 return domains; | 8657 return domains; |
12006 }, | 8658 }, |
12007 | 8659 |
12008 updateGroupedHistoryData_: function() { | 8660 updateGroupedHistoryData_: function() { |
12009 if (this.historyData.length == 0) { | 8661 if (this.historyData.length == 0) { |
12010 this.groupedHistoryData_ = []; | 8662 this.groupedHistoryData_ = []; |
12011 return; | 8663 return; |
12012 } | 8664 } |
12013 | 8665 |
12014 if (this.range == HistoryRange.WEEK) { | 8666 if (this.range == HistoryRange.WEEK) { |
12015 // Group each day into a list of results. | |
12016 var days = []; | 8667 var days = []; |
12017 var currentDayVisits = [this.historyData[0]]; | 8668 var currentDayVisits = [this.historyData[0]]; |
12018 | 8669 |
12019 var pushCurrentDay = function() { | 8670 var pushCurrentDay = function() { |
12020 days.push({ | 8671 days.push({ |
12021 title: this.searchedTerm ? currentDayVisits[0].dateShort : | 8672 title: this.searchedTerm ? currentDayVisits[0].dateShort : |
12022 currentDayVisits[0].dateRelativeDay, | 8673 currentDayVisits[0].dateRelativeDay, |
12023 domains: this.createHistoryDomains_(currentDayVisits), | 8674 domains: this.createHistoryDomains_(currentDayVisits), |
12024 }); | 8675 }); |
12025 }.bind(this); | 8676 }.bind(this); |
(...skipping 10 matching lines...) Expand all Loading... |
12036 if (!visitsSameDay(visit, currentDayVisits[0])) { | 8687 if (!visitsSameDay(visit, currentDayVisits[0])) { |
12037 pushCurrentDay(); | 8688 pushCurrentDay(); |
12038 currentDayVisits = []; | 8689 currentDayVisits = []; |
12039 } | 8690 } |
12040 currentDayVisits.push(visit); | 8691 currentDayVisits.push(visit); |
12041 } | 8692 } |
12042 pushCurrentDay(); | 8693 pushCurrentDay(); |
12043 | 8694 |
12044 this.groupedHistoryData_ = days; | 8695 this.groupedHistoryData_ = days; |
12045 } else if (this.range == HistoryRange.MONTH) { | 8696 } else if (this.range == HistoryRange.MONTH) { |
12046 // Group each all visits into a single list. | |
12047 this.groupedHistoryData_ = [{ | 8697 this.groupedHistoryData_ = [{ |
12048 title: this.queryStartTime + ' – ' + this.queryEndTime, | 8698 title: this.queryStartTime + ' – ' + this.queryEndTime, |
12049 domains: this.createHistoryDomains_(this.historyData) | 8699 domains: this.createHistoryDomains_(this.historyData) |
12050 }]; | 8700 }]; |
12051 } | 8701 } |
12052 }, | 8702 }, |
12053 | 8703 |
12054 /** | |
12055 * @param {{model:Object, currentTarget:IronCollapseElement}} e | |
12056 */ | |
12057 toggleDomainExpanded_: function(e) { | 8704 toggleDomainExpanded_: function(e) { |
12058 var collapse = e.currentTarget.parentNode.querySelector('iron-collapse'); | 8705 var collapse = e.currentTarget.parentNode.querySelector('iron-collapse'); |
12059 e.model.set('domain.rendered', true); | 8706 e.model.set('domain.rendered', true); |
12060 | 8707 |
12061 // Give the history-items time to render. | |
12062 setTimeout(function() { collapse.toggle() }, 0); | 8708 setTimeout(function() { collapse.toggle() }, 0); |
12063 }, | 8709 }, |
12064 | 8710 |
12065 /** | |
12066 * Check whether the time difference between the given history item and the | |
12067 * next one is large enough for a spacer to be required. | |
12068 * @param {number} groupIndex | |
12069 * @param {number} domainIndex | |
12070 * @param {number} itemIndex | |
12071 * @return {boolean} Whether or not time gap separator is required. | |
12072 * @private | |
12073 */ | |
12074 needsTimeGap_: function(groupIndex, domainIndex, itemIndex) { | 8711 needsTimeGap_: function(groupIndex, domainIndex, itemIndex) { |
12075 var visits = | 8712 var visits = |
12076 this.groupedHistoryData_[groupIndex].domains[domainIndex].visits; | 8713 this.groupedHistoryData_[groupIndex].domains[domainIndex].visits; |
12077 | 8714 |
12078 return md_history.HistoryItem.needsTimeGap( | 8715 return md_history.HistoryItem.needsTimeGap( |
12079 visits, itemIndex, this.searchedTerm); | 8716 visits, itemIndex, this.searchedTerm); |
12080 }, | 8717 }, |
12081 | 8718 |
12082 /** | |
12083 * @param {number} groupIndex | |
12084 * @param {number} domainIndex | |
12085 * @param {number} itemIndex | |
12086 * @return {string} | |
12087 * @private | |
12088 */ | |
12089 pathForItem_: function(groupIndex, domainIndex, itemIndex) { | 8719 pathForItem_: function(groupIndex, domainIndex, itemIndex) { |
12090 return [ | 8720 return [ |
12091 'groupedHistoryData_', groupIndex, 'domains', domainIndex, 'visits', | 8721 'groupedHistoryData_', groupIndex, 'domains', domainIndex, 'visits', |
12092 itemIndex | 8722 itemIndex |
12093 ].join('.'); | 8723 ].join('.'); |
12094 }, | 8724 }, |
12095 | 8725 |
12096 /** | |
12097 * @param {HistoryDomain} domain | |
12098 * @return {string} | |
12099 * @private | |
12100 */ | |
12101 getWebsiteIconStyle_: function(domain) { | 8726 getWebsiteIconStyle_: function(domain) { |
12102 return 'background-image: ' + | 8727 return 'background-image: ' + |
12103 cr.icon.getFaviconImageSet(domain.visits[0].url); | 8728 cr.icon.getFaviconImageSet(domain.visits[0].url); |
12104 }, | 8729 }, |
12105 | 8730 |
12106 /** | |
12107 * @param {boolean} expanded | |
12108 * @return {string} | |
12109 * @private | |
12110 */ | |
12111 getDropdownIcon_: function(expanded) { | 8731 getDropdownIcon_: function(expanded) { |
12112 return expanded ? 'cr:expand-less' : 'cr:expand-more'; | 8732 return expanded ? 'cr:expand-less' : 'cr:expand-more'; |
12113 }, | 8733 }, |
12114 }); | 8734 }); |
12115 /** | |
12116 * `Polymer.IronScrollTargetBehavior` allows an element to respond to scroll e
vents from a | |
12117 * designated scroll target. | |
12118 * | |
12119 * Elements that consume this behavior can override the `_scrollHandler` | |
12120 * method to add logic on the scroll event. | |
12121 * | |
12122 * @demo demo/scrolling-region.html Scrolling Region | |
12123 * @demo demo/document.html Document Element | |
12124 * @polymerBehavior | |
12125 */ | |
12126 Polymer.IronScrollTargetBehavior = { | 8735 Polymer.IronScrollTargetBehavior = { |
12127 | 8736 |
12128 properties: { | 8737 properties: { |
12129 | 8738 |
12130 /** | |
12131 * Specifies the element that will handle the scroll event | |
12132 * on the behalf of the current element. This is typically a reference to
an element, | |
12133 * but there are a few more posibilities: | |
12134 * | |
12135 * ### Elements id | |
12136 * | |
12137 *```html | |
12138 * <div id="scrollable-element" style="overflow: auto;"> | |
12139 * <x-element scroll-target="scrollable-element"> | |
12140 * \x3c!-- Content--\x3e | |
12141 * </x-element> | |
12142 * </div> | |
12143 *``` | |
12144 * In this case, the `scrollTarget` will point to the outer div element. | |
12145 * | |
12146 * ### Document scrolling | |
12147 * | |
12148 * For document scrolling, you can use the reserved word `document`: | |
12149 * | |
12150 *```html | |
12151 * <x-element scroll-target="document"> | |
12152 * \x3c!-- Content --\x3e | |
12153 * </x-element> | |
12154 *``` | |
12155 * | |
12156 * ### Elements reference | |
12157 * | |
12158 *```js | |
12159 * appHeader.scrollTarget = document.querySelector('#scrollable-element'); | |
12160 *``` | |
12161 * | |
12162 * @type {HTMLElement} | |
12163 */ | |
12164 scrollTarget: { | 8739 scrollTarget: { |
12165 type: HTMLElement, | 8740 type: HTMLElement, |
12166 value: function() { | 8741 value: function() { |
12167 return this._defaultScrollTarget; | 8742 return this._defaultScrollTarget; |
12168 } | 8743 } |
12169 } | 8744 } |
12170 }, | 8745 }, |
12171 | 8746 |
12172 observers: [ | 8747 observers: [ |
12173 '_scrollTargetChanged(scrollTarget, isAttached)' | 8748 '_scrollTargetChanged(scrollTarget, isAttached)' |
12174 ], | 8749 ], |
12175 | 8750 |
12176 _scrollTargetChanged: function(scrollTarget, isAttached) { | 8751 _scrollTargetChanged: function(scrollTarget, isAttached) { |
12177 var eventTarget; | 8752 var eventTarget; |
12178 | 8753 |
12179 if (this._oldScrollTarget) { | 8754 if (this._oldScrollTarget) { |
12180 eventTarget = this._oldScrollTarget === this._doc ? window : this._oldSc
rollTarget; | 8755 eventTarget = this._oldScrollTarget === this._doc ? window : this._oldSc
rollTarget; |
12181 eventTarget.removeEventListener('scroll', this._boundScrollHandler); | 8756 eventTarget.removeEventListener('scroll', this._boundScrollHandler); |
12182 this._oldScrollTarget = null; | 8757 this._oldScrollTarget = null; |
12183 } | 8758 } |
12184 | 8759 |
12185 if (!isAttached) { | 8760 if (!isAttached) { |
12186 return; | 8761 return; |
12187 } | 8762 } |
12188 // Support element id references | |
12189 if (scrollTarget === 'document') { | 8763 if (scrollTarget === 'document') { |
12190 | 8764 |
12191 this.scrollTarget = this._doc; | 8765 this.scrollTarget = this._doc; |
12192 | 8766 |
12193 } else if (typeof scrollTarget === 'string') { | 8767 } else if (typeof scrollTarget === 'string') { |
12194 | 8768 |
12195 this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] : | 8769 this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] : |
12196 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget); | 8770 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget); |
12197 | 8771 |
12198 } else if (this._isValidScrollTarget()) { | 8772 } else if (this._isValidScrollTarget()) { |
12199 | 8773 |
12200 eventTarget = scrollTarget === this._doc ? window : scrollTarget; | 8774 eventTarget = scrollTarget === this._doc ? window : scrollTarget; |
12201 this._boundScrollHandler = this._boundScrollHandler || this._scrollHandl
er.bind(this); | 8775 this._boundScrollHandler = this._boundScrollHandler || this._scrollHandl
er.bind(this); |
12202 this._oldScrollTarget = scrollTarget; | 8776 this._oldScrollTarget = scrollTarget; |
12203 | 8777 |
12204 eventTarget.addEventListener('scroll', this._boundScrollHandler); | 8778 eventTarget.addEventListener('scroll', this._boundScrollHandler); |
12205 } | 8779 } |
12206 }, | 8780 }, |
12207 | 8781 |
12208 /** | |
12209 * Runs on every scroll event. Consumer of this behavior may override this m
ethod. | |
12210 * | |
12211 * @protected | |
12212 */ | |
12213 _scrollHandler: function scrollHandler() {}, | 8782 _scrollHandler: function scrollHandler() {}, |
12214 | 8783 |
12215 /** | |
12216 * The default scroll target. Consumers of this behavior may want to customi
ze | |
12217 * the default scroll target. | |
12218 * | |
12219 * @type {Element} | |
12220 */ | |
12221 get _defaultScrollTarget() { | 8784 get _defaultScrollTarget() { |
12222 return this._doc; | 8785 return this._doc; |
12223 }, | 8786 }, |
12224 | 8787 |
12225 /** | |
12226 * Shortcut for the document element | |
12227 * | |
12228 * @type {Element} | |
12229 */ | |
12230 get _doc() { | 8788 get _doc() { |
12231 return this.ownerDocument.documentElement; | 8789 return this.ownerDocument.documentElement; |
12232 }, | 8790 }, |
12233 | 8791 |
12234 /** | |
12235 * Gets the number of pixels that the content of an element is scrolled upwa
rd. | |
12236 * | |
12237 * @type {number} | |
12238 */ | |
12239 get _scrollTop() { | 8792 get _scrollTop() { |
12240 if (this._isValidScrollTarget()) { | 8793 if (this._isValidScrollTarget()) { |
12241 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol
lTarget.scrollTop; | 8794 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol
lTarget.scrollTop; |
12242 } | 8795 } |
12243 return 0; | 8796 return 0; |
12244 }, | 8797 }, |
12245 | 8798 |
12246 /** | |
12247 * Gets the number of pixels that the content of an element is scrolled to t
he left. | |
12248 * | |
12249 * @type {number} | |
12250 */ | |
12251 get _scrollLeft() { | 8799 get _scrollLeft() { |
12252 if (this._isValidScrollTarget()) { | 8800 if (this._isValidScrollTarget()) { |
12253 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol
lTarget.scrollLeft; | 8801 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol
lTarget.scrollLeft; |
12254 } | 8802 } |
12255 return 0; | 8803 return 0; |
12256 }, | 8804 }, |
12257 | 8805 |
12258 /** | |
12259 * Sets the number of pixels that the content of an element is scrolled upwa
rd. | |
12260 * | |
12261 * @type {number} | |
12262 */ | |
12263 set _scrollTop(top) { | 8806 set _scrollTop(top) { |
12264 if (this.scrollTarget === this._doc) { | 8807 if (this.scrollTarget === this._doc) { |
12265 window.scrollTo(window.pageXOffset, top); | 8808 window.scrollTo(window.pageXOffset, top); |
12266 } else if (this._isValidScrollTarget()) { | 8809 } else if (this._isValidScrollTarget()) { |
12267 this.scrollTarget.scrollTop = top; | 8810 this.scrollTarget.scrollTop = top; |
12268 } | 8811 } |
12269 }, | 8812 }, |
12270 | 8813 |
12271 /** | |
12272 * Sets the number of pixels that the content of an element is scrolled to t
he left. | |
12273 * | |
12274 * @type {number} | |
12275 */ | |
12276 set _scrollLeft(left) { | 8814 set _scrollLeft(left) { |
12277 if (this.scrollTarget === this._doc) { | 8815 if (this.scrollTarget === this._doc) { |
12278 window.scrollTo(left, window.pageYOffset); | 8816 window.scrollTo(left, window.pageYOffset); |
12279 } else if (this._isValidScrollTarget()) { | 8817 } else if (this._isValidScrollTarget()) { |
12280 this.scrollTarget.scrollLeft = left; | 8818 this.scrollTarget.scrollLeft = left; |
12281 } | 8819 } |
12282 }, | 8820 }, |
12283 | 8821 |
12284 /** | |
12285 * Scrolls the content to a particular place. | |
12286 * | |
12287 * @method scroll | |
12288 * @param {number} left The left position | |
12289 * @param {number} top The top position | |
12290 */ | |
12291 scroll: function(left, top) { | 8822 scroll: function(left, top) { |
12292 if (this.scrollTarget === this._doc) { | 8823 if (this.scrollTarget === this._doc) { |
12293 window.scrollTo(left, top); | 8824 window.scrollTo(left, top); |
12294 } else if (this._isValidScrollTarget()) { | 8825 } else if (this._isValidScrollTarget()) { |
12295 this.scrollTarget.scrollLeft = left; | 8826 this.scrollTarget.scrollLeft = left; |
12296 this.scrollTarget.scrollTop = top; | 8827 this.scrollTarget.scrollTop = top; |
12297 } | 8828 } |
12298 }, | 8829 }, |
12299 | 8830 |
12300 /** | |
12301 * Gets the width of the scroll target. | |
12302 * | |
12303 * @type {number} | |
12304 */ | |
12305 get _scrollTargetWidth() { | 8831 get _scrollTargetWidth() { |
12306 if (this._isValidScrollTarget()) { | 8832 if (this._isValidScrollTarget()) { |
12307 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll
Target.offsetWidth; | 8833 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll
Target.offsetWidth; |
12308 } | 8834 } |
12309 return 0; | 8835 return 0; |
12310 }, | 8836 }, |
12311 | 8837 |
12312 /** | |
12313 * Gets the height of the scroll target. | |
12314 * | |
12315 * @type {number} | |
12316 */ | |
12317 get _scrollTargetHeight() { | 8838 get _scrollTargetHeight() { |
12318 if (this._isValidScrollTarget()) { | 8839 if (this._isValidScrollTarget()) { |
12319 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol
lTarget.offsetHeight; | 8840 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol
lTarget.offsetHeight; |
12320 } | 8841 } |
12321 return 0; | 8842 return 0; |
12322 }, | 8843 }, |
12323 | 8844 |
12324 /** | |
12325 * Returns true if the scroll target is a valid HTMLElement. | |
12326 * | |
12327 * @return {boolean} | |
12328 */ | |
12329 _isValidScrollTarget: function() { | 8845 _isValidScrollTarget: function() { |
12330 return this.scrollTarget instanceof HTMLElement; | 8846 return this.scrollTarget instanceof HTMLElement; |
12331 } | 8847 } |
12332 }; | 8848 }; |
12333 (function() { | 8849 (function() { |
12334 | 8850 |
12335 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); | 8851 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); |
12336 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; | 8852 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; |
12337 var DEFAULT_PHYSICAL_COUNT = 3; | 8853 var DEFAULT_PHYSICAL_COUNT = 3; |
12338 var HIDDEN_Y = '-10000px'; | 8854 var HIDDEN_Y = '-10000px'; |
12339 var DEFAULT_GRID_SIZE = 200; | 8855 var DEFAULT_GRID_SIZE = 200; |
12340 var SECRET_TABINDEX = -100; | 8856 var SECRET_TABINDEX = -100; |
12341 | 8857 |
12342 Polymer({ | 8858 Polymer({ |
12343 | 8859 |
12344 is: 'iron-list', | 8860 is: 'iron-list', |
12345 | 8861 |
12346 properties: { | 8862 properties: { |
12347 | 8863 |
12348 /** | |
12349 * An array containing items determining how many instances of the templat
e | |
12350 * to stamp and that that each template instance should bind to. | |
12351 */ | |
12352 items: { | 8864 items: { |
12353 type: Array | 8865 type: Array |
12354 }, | 8866 }, |
12355 | 8867 |
12356 /** | |
12357 * The max count of physical items the pool can extend to. | |
12358 */ | |
12359 maxPhysicalCount: { | 8868 maxPhysicalCount: { |
12360 type: Number, | 8869 type: Number, |
12361 value: 500 | 8870 value: 500 |
12362 }, | 8871 }, |
12363 | 8872 |
12364 /** | |
12365 * The name of the variable to add to the binding scope for the array | |
12366 * element associated with a given template instance. | |
12367 */ | |
12368 as: { | 8873 as: { |
12369 type: String, | 8874 type: String, |
12370 value: 'item' | 8875 value: 'item' |
12371 }, | 8876 }, |
12372 | 8877 |
12373 /** | |
12374 * The name of the variable to add to the binding scope with the index | |
12375 * for the row. | |
12376 */ | |
12377 indexAs: { | 8878 indexAs: { |
12378 type: String, | 8879 type: String, |
12379 value: 'index' | 8880 value: 'index' |
12380 }, | 8881 }, |
12381 | 8882 |
12382 /** | |
12383 * The name of the variable to add to the binding scope to indicate | |
12384 * if the row is selected. | |
12385 */ | |
12386 selectedAs: { | 8883 selectedAs: { |
12387 type: String, | 8884 type: String, |
12388 value: 'selected' | 8885 value: 'selected' |
12389 }, | 8886 }, |
12390 | 8887 |
12391 /** | |
12392 * When true, the list is rendered as a grid. Grid items must have | |
12393 * fixed width and height set via CSS. e.g. | |
12394 * | |
12395 * ```html | |
12396 * <iron-list grid> | |
12397 * <template> | |
12398 * <div style="width: 100px; height: 100px;"> 100x100 </div> | |
12399 * </template> | |
12400 * </iron-list> | |
12401 * ``` | |
12402 */ | |
12403 grid: { | 8888 grid: { |
12404 type: Boolean, | 8889 type: Boolean, |
12405 value: false, | 8890 value: false, |
12406 reflectToAttribute: true | 8891 reflectToAttribute: true |
12407 }, | 8892 }, |
12408 | 8893 |
12409 /** | |
12410 * When true, tapping a row will select the item, placing its data model | |
12411 * in the set of selected items retrievable via the selection property. | |
12412 * | |
12413 * Note that tapping focusable elements within the list item will not | |
12414 * result in selection, since they are presumed to have their * own action
. | |
12415 */ | |
12416 selectionEnabled: { | 8894 selectionEnabled: { |
12417 type: Boolean, | 8895 type: Boolean, |
12418 value: false | 8896 value: false |
12419 }, | 8897 }, |
12420 | 8898 |
12421 /** | |
12422 * When `multiSelection` is false, this is the currently selected item, or
`null` | |
12423 * if no item is selected. | |
12424 */ | |
12425 selectedItem: { | 8899 selectedItem: { |
12426 type: Object, | 8900 type: Object, |
12427 notify: true | 8901 notify: true |
12428 }, | 8902 }, |
12429 | 8903 |
12430 /** | |
12431 * When `multiSelection` is true, this is an array that contains the selec
ted items. | |
12432 */ | |
12433 selectedItems: { | 8904 selectedItems: { |
12434 type: Object, | 8905 type: Object, |
12435 notify: true | 8906 notify: true |
12436 }, | 8907 }, |
12437 | 8908 |
12438 /** | |
12439 * When `true`, multiple items may be selected at once (in this case, | |
12440 * `selected` is an array of currently selected items). When `false`, | |
12441 * only one item may be selected at a time. | |
12442 */ | |
12443 multiSelection: { | 8909 multiSelection: { |
12444 type: Boolean, | 8910 type: Boolean, |
12445 value: false | 8911 value: false |
12446 } | 8912 } |
12447 }, | 8913 }, |
12448 | 8914 |
12449 observers: [ | 8915 observers: [ |
12450 '_itemsChanged(items.*)', | 8916 '_itemsChanged(items.*)', |
12451 '_selectionEnabledChanged(selectionEnabled)', | 8917 '_selectionEnabledChanged(selectionEnabled)', |
12452 '_multiSelectionChanged(multiSelection)', | 8918 '_multiSelectionChanged(multiSelection)', |
12453 '_setOverflow(scrollTarget)' | 8919 '_setOverflow(scrollTarget)' |
12454 ], | 8920 ], |
12455 | 8921 |
12456 behaviors: [ | 8922 behaviors: [ |
12457 Polymer.Templatizer, | 8923 Polymer.Templatizer, |
12458 Polymer.IronResizableBehavior, | 8924 Polymer.IronResizableBehavior, |
12459 Polymer.IronA11yKeysBehavior, | 8925 Polymer.IronA11yKeysBehavior, |
12460 Polymer.IronScrollTargetBehavior | 8926 Polymer.IronScrollTargetBehavior |
12461 ], | 8927 ], |
12462 | 8928 |
12463 keyBindings: { | 8929 keyBindings: { |
12464 'up': '_didMoveUp', | 8930 'up': '_didMoveUp', |
12465 'down': '_didMoveDown', | 8931 'down': '_didMoveDown', |
12466 'enter': '_didEnter' | 8932 'enter': '_didEnter' |
12467 }, | 8933 }, |
12468 | 8934 |
12469 /** | |
12470 * The ratio of hidden tiles that should remain in the scroll direction. | |
12471 * Recommended value ~0.5, so it will distribute tiles evely in both directi
ons. | |
12472 */ | |
12473 _ratio: 0.5, | 8935 _ratio: 0.5, |
12474 | 8936 |
12475 /** | |
12476 * The padding-top value for the list. | |
12477 */ | |
12478 _scrollerPaddingTop: 0, | 8937 _scrollerPaddingTop: 0, |
12479 | 8938 |
12480 /** | |
12481 * This value is the same as `scrollTop`. | |
12482 */ | |
12483 _scrollPosition: 0, | 8939 _scrollPosition: 0, |
12484 | 8940 |
12485 /** | |
12486 * The sum of the heights of all the tiles in the DOM. | |
12487 */ | |
12488 _physicalSize: 0, | 8941 _physicalSize: 0, |
12489 | 8942 |
12490 /** | |
12491 * The average `offsetHeight` of the tiles observed till now. | |
12492 */ | |
12493 _physicalAverage: 0, | 8943 _physicalAverage: 0, |
12494 | 8944 |
12495 /** | |
12496 * The number of tiles which `offsetHeight` > 0 observed until now. | |
12497 */ | |
12498 _physicalAverageCount: 0, | 8945 _physicalAverageCount: 0, |
12499 | 8946 |
12500 /** | |
12501 * The Y position of the item rendered in the `_physicalStart` | |
12502 * tile relative to the scrolling list. | |
12503 */ | |
12504 _physicalTop: 0, | 8947 _physicalTop: 0, |
12505 | 8948 |
12506 /** | |
12507 * The number of items in the list. | |
12508 */ | |
12509 _virtualCount: 0, | 8949 _virtualCount: 0, |
12510 | 8950 |
12511 /** | |
12512 * A map between an item key and its physical item index | |
12513 */ | |
12514 _physicalIndexForKey: null, | 8951 _physicalIndexForKey: null, |
12515 | 8952 |
12516 /** | |
12517 * The estimated scroll height based on `_physicalAverage` | |
12518 */ | |
12519 _estScrollHeight: 0, | 8953 _estScrollHeight: 0, |
12520 | 8954 |
12521 /** | |
12522 * The scroll height of the dom node | |
12523 */ | |
12524 _scrollHeight: 0, | 8955 _scrollHeight: 0, |
12525 | 8956 |
12526 /** | |
12527 * The height of the list. This is referred as the viewport in the context o
f list. | |
12528 */ | |
12529 _viewportHeight: 0, | 8957 _viewportHeight: 0, |
12530 | 8958 |
12531 /** | |
12532 * The width of the list. This is referred as the viewport in the context of
list. | |
12533 */ | |
12534 _viewportWidth: 0, | 8959 _viewportWidth: 0, |
12535 | 8960 |
12536 /** | |
12537 * An array of DOM nodes that are currently in the tree | |
12538 * @type {?Array<!TemplatizerNode>} | |
12539 */ | |
12540 _physicalItems: null, | 8961 _physicalItems: null, |
12541 | 8962 |
12542 /** | |
12543 * An array of heights for each item in `_physicalItems` | |
12544 * @type {?Array<number>} | |
12545 */ | |
12546 _physicalSizes: null, | 8963 _physicalSizes: null, |
12547 | 8964 |
12548 /** | |
12549 * A cached value for the first visible index. | |
12550 * See `firstVisibleIndex` | |
12551 * @type {?number} | |
12552 */ | |
12553 _firstVisibleIndexVal: null, | 8965 _firstVisibleIndexVal: null, |
12554 | 8966 |
12555 /** | |
12556 * A cached value for the last visible index. | |
12557 * See `lastVisibleIndex` | |
12558 * @type {?number} | |
12559 */ | |
12560 _lastVisibleIndexVal: null, | 8967 _lastVisibleIndexVal: null, |
12561 | 8968 |
12562 /** | |
12563 * A Polymer collection for the items. | |
12564 * @type {?Polymer.Collection} | |
12565 */ | |
12566 _collection: null, | 8969 _collection: null, |
12567 | 8970 |
12568 /** | |
12569 * True if the current item list was rendered for the first time | |
12570 * after attached. | |
12571 */ | |
12572 _itemsRendered: false, | 8971 _itemsRendered: false, |
12573 | 8972 |
12574 /** | |
12575 * The page that is currently rendered. | |
12576 */ | |
12577 _lastPage: null, | 8973 _lastPage: null, |
12578 | 8974 |
12579 /** | |
12580 * The max number of pages to render. One page is equivalent to the height o
f the list. | |
12581 */ | |
12582 _maxPages: 3, | 8975 _maxPages: 3, |
12583 | 8976 |
12584 /** | |
12585 * The currently focused physical item. | |
12586 */ | |
12587 _focusedItem: null, | 8977 _focusedItem: null, |
12588 | 8978 |
12589 /** | |
12590 * The index of the `_focusedItem`. | |
12591 */ | |
12592 _focusedIndex: -1, | 8979 _focusedIndex: -1, |
12593 | 8980 |
12594 /** | |
12595 * The the item that is focused if it is moved offscreen. | |
12596 * @private {?TemplatizerNode} | |
12597 */ | |
12598 _offscreenFocusedItem: null, | 8981 _offscreenFocusedItem: null, |
12599 | 8982 |
12600 /** | |
12601 * The item that backfills the `_offscreenFocusedItem` in the physical items | |
12602 * list when that item is moved offscreen. | |
12603 */ | |
12604 _focusBackfillItem: null, | 8983 _focusBackfillItem: null, |
12605 | 8984 |
12606 /** | |
12607 * The maximum items per row | |
12608 */ | |
12609 _itemsPerRow: 1, | 8985 _itemsPerRow: 1, |
12610 | 8986 |
12611 /** | |
12612 * The width of each grid item | |
12613 */ | |
12614 _itemWidth: 0, | 8987 _itemWidth: 0, |
12615 | 8988 |
12616 /** | |
12617 * The height of the row in grid layout. | |
12618 */ | |
12619 _rowHeight: 0, | 8989 _rowHeight: 0, |
12620 | 8990 |
12621 /** | |
12622 * The bottom of the physical content. | |
12623 */ | |
12624 get _physicalBottom() { | 8991 get _physicalBottom() { |
12625 return this._physicalTop + this._physicalSize; | 8992 return this._physicalTop + this._physicalSize; |
12626 }, | 8993 }, |
12627 | 8994 |
12628 /** | |
12629 * The bottom of the scroll. | |
12630 */ | |
12631 get _scrollBottom() { | 8995 get _scrollBottom() { |
12632 return this._scrollPosition + this._viewportHeight; | 8996 return this._scrollPosition + this._viewportHeight; |
12633 }, | 8997 }, |
12634 | 8998 |
12635 /** | |
12636 * The n-th item rendered in the last physical item. | |
12637 */ | |
12638 get _virtualEnd() { | 8999 get _virtualEnd() { |
12639 return this._virtualStart + this._physicalCount - 1; | 9000 return this._virtualStart + this._physicalCount - 1; |
12640 }, | 9001 }, |
12641 | 9002 |
12642 /** | |
12643 * The height of the physical content that isn't on the screen. | |
12644 */ | |
12645 get _hiddenContentSize() { | 9003 get _hiddenContentSize() { |
12646 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic
alSize; | 9004 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic
alSize; |
12647 return size - this._viewportHeight; | 9005 return size - this._viewportHeight; |
12648 }, | 9006 }, |
12649 | 9007 |
12650 /** | |
12651 * The maximum scroll top value. | |
12652 */ | |
12653 get _maxScrollTop() { | 9008 get _maxScrollTop() { |
12654 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin
gTop; | 9009 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin
gTop; |
12655 }, | 9010 }, |
12656 | 9011 |
12657 /** | |
12658 * The lowest n-th value for an item such that it can be rendered in `_physi
calStart`. | |
12659 */ | |
12660 _minVirtualStart: 0, | 9012 _minVirtualStart: 0, |
12661 | 9013 |
12662 /** | |
12663 * The largest n-th value for an item such that it can be rendered in `_phys
icalStart`. | |
12664 */ | |
12665 get _maxVirtualStart() { | 9014 get _maxVirtualStart() { |
12666 return Math.max(0, this._virtualCount - this._physicalCount); | 9015 return Math.max(0, this._virtualCount - this._physicalCount); |
12667 }, | 9016 }, |
12668 | 9017 |
12669 /** | |
12670 * The n-th item rendered in the `_physicalStart` tile. | |
12671 */ | |
12672 _virtualStartVal: 0, | 9018 _virtualStartVal: 0, |
12673 | 9019 |
12674 set _virtualStart(val) { | 9020 set _virtualStart(val) { |
12675 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min
VirtualStart, val)); | 9021 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min
VirtualStart, val)); |
12676 }, | 9022 }, |
12677 | 9023 |
12678 get _virtualStart() { | 9024 get _virtualStart() { |
12679 return this._virtualStartVal || 0; | 9025 return this._virtualStartVal || 0; |
12680 }, | 9026 }, |
12681 | 9027 |
12682 /** | |
12683 * The k-th tile that is at the top of the scrolling list. | |
12684 */ | |
12685 _physicalStartVal: 0, | 9028 _physicalStartVal: 0, |
12686 | 9029 |
12687 set _physicalStart(val) { | 9030 set _physicalStart(val) { |
12688 this._physicalStartVal = val % this._physicalCount; | 9031 this._physicalStartVal = val % this._physicalCount; |
12689 if (this._physicalStartVal < 0) { | 9032 if (this._physicalStartVal < 0) { |
12690 this._physicalStartVal = this._physicalCount + this._physicalStartVal; | 9033 this._physicalStartVal = this._physicalCount + this._physicalStartVal; |
12691 } | 9034 } |
12692 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; | 9035 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; |
12693 }, | 9036 }, |
12694 | 9037 |
12695 get _physicalStart() { | 9038 get _physicalStart() { |
12696 return this._physicalStartVal || 0; | 9039 return this._physicalStartVal || 0; |
12697 }, | 9040 }, |
12698 | 9041 |
12699 /** | |
12700 * The number of tiles in the DOM. | |
12701 */ | |
12702 _physicalCountVal: 0, | 9042 _physicalCountVal: 0, |
12703 | 9043 |
12704 set _physicalCount(val) { | 9044 set _physicalCount(val) { |
12705 this._physicalCountVal = val; | 9045 this._physicalCountVal = val; |
12706 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; | 9046 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; |
12707 }, | 9047 }, |
12708 | 9048 |
12709 get _physicalCount() { | 9049 get _physicalCount() { |
12710 return this._physicalCountVal; | 9050 return this._physicalCountVal; |
12711 }, | 9051 }, |
12712 | 9052 |
12713 /** | |
12714 * The k-th tile that is at the bottom of the scrolling list. | |
12715 */ | |
12716 _physicalEnd: 0, | 9053 _physicalEnd: 0, |
12717 | 9054 |
12718 /** | |
12719 * An optimal physical size such that we will have enough physical items | |
12720 * to fill up the viewport and recycle when the user scrolls. | |
12721 * | |
12722 * This default value assumes that we will at least have the equivalent | |
12723 * to a viewport of physical items above and below the user's viewport. | |
12724 */ | |
12725 get _optPhysicalSize() { | 9055 get _optPhysicalSize() { |
12726 if (this.grid) { | 9056 if (this.grid) { |
12727 return this._estRowsInView * this._rowHeight * this._maxPages; | 9057 return this._estRowsInView * this._rowHeight * this._maxPages; |
12728 } | 9058 } |
12729 return this._viewportHeight * this._maxPages; | 9059 return this._viewportHeight * this._maxPages; |
12730 }, | 9060 }, |
12731 | 9061 |
12732 get _optPhysicalCount() { | 9062 get _optPhysicalCount() { |
12733 return this._estRowsInView * this._itemsPerRow * this._maxPages; | 9063 return this._estRowsInView * this._itemsPerRow * this._maxPages; |
12734 }, | 9064 }, |
12735 | 9065 |
12736 /** | |
12737 * True if the current list is visible. | |
12738 */ | |
12739 get _isVisible() { | 9066 get _isVisible() { |
12740 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this.
scrollTarget.offsetHeight); | 9067 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this.
scrollTarget.offsetHeight); |
12741 }, | 9068 }, |
12742 | 9069 |
12743 /** | |
12744 * Gets the index of the first visible item in the viewport. | |
12745 * | |
12746 * @type {number} | |
12747 */ | |
12748 get firstVisibleIndex() { | 9070 get firstVisibleIndex() { |
12749 if (this._firstVisibleIndexVal === null) { | 9071 if (this._firstVisibleIndexVal === null) { |
12750 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); | 9072 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); |
12751 | 9073 |
12752 this._firstVisibleIndexVal = this._iterateItems( | 9074 this._firstVisibleIndexVal = this._iterateItems( |
12753 function(pidx, vidx) { | 9075 function(pidx, vidx) { |
12754 physicalOffset += this._getPhysicalSizeIncrement(pidx); | 9076 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
12755 | 9077 |
12756 if (physicalOffset > this._scrollPosition) { | 9078 if (physicalOffset > this._scrollPosition) { |
12757 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; | 9079 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; |
12758 } | 9080 } |
12759 // Handle a partially rendered final row in grid mode | |
12760 if (this.grid && this._virtualCount - 1 === vidx) { | 9081 if (this.grid && this._virtualCount - 1 === vidx) { |
12761 return vidx - (vidx % this._itemsPerRow); | 9082 return vidx - (vidx % this._itemsPerRow); |
12762 } | 9083 } |
12763 }) || 0; | 9084 }) || 0; |
12764 } | 9085 } |
12765 return this._firstVisibleIndexVal; | 9086 return this._firstVisibleIndexVal; |
12766 }, | 9087 }, |
12767 | 9088 |
12768 /** | |
12769 * Gets the index of the last visible item in the viewport. | |
12770 * | |
12771 * @type {number} | |
12772 */ | |
12773 get lastVisibleIndex() { | 9089 get lastVisibleIndex() { |
12774 if (this._lastVisibleIndexVal === null) { | 9090 if (this._lastVisibleIndexVal === null) { |
12775 if (this.grid) { | 9091 if (this.grid) { |
12776 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i
temsPerRow - 1; | 9092 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i
temsPerRow - 1; |
12777 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex); | 9093 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex); |
12778 } else { | 9094 } else { |
12779 var physicalOffset = this._physicalTop; | 9095 var physicalOffset = this._physicalTop; |
12780 this._iterateItems(function(pidx, vidx) { | 9096 this._iterateItems(function(pidx, vidx) { |
12781 if (physicalOffset < this._scrollBottom) { | 9097 if (physicalOffset < this._scrollBottom) { |
12782 this._lastVisibleIndexVal = vidx; | 9098 this._lastVisibleIndexVal = vidx; |
12783 } else { | 9099 } else { |
12784 // Break _iterateItems | |
12785 return true; | 9100 return true; |
12786 } | 9101 } |
12787 physicalOffset += this._getPhysicalSizeIncrement(pidx); | 9102 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
12788 }); | 9103 }); |
12789 } | 9104 } |
12790 } | 9105 } |
12791 return this._lastVisibleIndexVal; | 9106 return this._lastVisibleIndexVal; |
12792 }, | 9107 }, |
12793 | 9108 |
12794 get _defaultScrollTarget() { | 9109 get _defaultScrollTarget() { |
(...skipping 11 matching lines...) Expand all Loading... |
12806 return Math.ceil(this._physicalCount / this._itemsPerRow); | 9121 return Math.ceil(this._physicalCount / this._itemsPerRow); |
12807 }, | 9122 }, |
12808 | 9123 |
12809 ready: function() { | 9124 ready: function() { |
12810 this.addEventListener('focus', this._didFocus.bind(this), true); | 9125 this.addEventListener('focus', this._didFocus.bind(this), true); |
12811 }, | 9126 }, |
12812 | 9127 |
12813 attached: function() { | 9128 attached: function() { |
12814 this.updateViewportBoundaries(); | 9129 this.updateViewportBoundaries(); |
12815 this._render(); | 9130 this._render(); |
12816 // `iron-resize` is fired when the list is attached if the event is added | |
12817 // before attached causing unnecessary work. | |
12818 this.listen(this, 'iron-resize', '_resizeHandler'); | 9131 this.listen(this, 'iron-resize', '_resizeHandler'); |
12819 }, | 9132 }, |
12820 | 9133 |
12821 detached: function() { | 9134 detached: function() { |
12822 this._itemsRendered = false; | 9135 this._itemsRendered = false; |
12823 this.unlisten(this, 'iron-resize', '_resizeHandler'); | 9136 this.unlisten(this, 'iron-resize', '_resizeHandler'); |
12824 }, | 9137 }, |
12825 | 9138 |
12826 /** | |
12827 * Set the overflow property if this element has its own scrolling region | |
12828 */ | |
12829 _setOverflow: function(scrollTarget) { | 9139 _setOverflow: function(scrollTarget) { |
12830 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; | 9140 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; |
12831 this.style.overflow = scrollTarget === this ? 'auto' : ''; | 9141 this.style.overflow = scrollTarget === this ? 'auto' : ''; |
12832 }, | 9142 }, |
12833 | 9143 |
12834 /** | |
12835 * Invoke this method if you dynamically update the viewport's | |
12836 * size or CSS padding. | |
12837 * | |
12838 * @method updateViewportBoundaries | |
12839 */ | |
12840 updateViewportBoundaries: function() { | 9144 updateViewportBoundaries: function() { |
12841 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : | 9145 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : |
12842 parseInt(window.getComputedStyle(this)['padding-top'], 10); | 9146 parseInt(window.getComputedStyle(this)['padding-top'], 10); |
12843 | 9147 |
12844 this._viewportHeight = this._scrollTargetHeight; | 9148 this._viewportHeight = this._scrollTargetHeight; |
12845 if (this.grid) { | 9149 if (this.grid) { |
12846 this._updateGridMetrics(); | 9150 this._updateGridMetrics(); |
12847 } | 9151 } |
12848 }, | 9152 }, |
12849 | 9153 |
12850 /** | |
12851 * Update the models, the position of the | |
12852 * items in the viewport and recycle tiles as needed. | |
12853 */ | |
12854 _scrollHandler: function() { | 9154 _scrollHandler: function() { |
12855 // clamp the `scrollTop` value | |
12856 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop))
; | 9155 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop))
; |
12857 var delta = scrollTop - this._scrollPosition; | 9156 var delta = scrollTop - this._scrollPosition; |
12858 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto
m; | 9157 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto
m; |
12859 var ratio = this._ratio; | 9158 var ratio = this._ratio; |
12860 var recycledTiles = 0; | 9159 var recycledTiles = 0; |
12861 var hiddenContentSize = this._hiddenContentSize; | 9160 var hiddenContentSize = this._hiddenContentSize; |
12862 var currentRatio = ratio; | 9161 var currentRatio = ratio; |
12863 var movingUp = []; | 9162 var movingUp = []; |
12864 | 9163 |
12865 // track the last `scrollTop` | |
12866 this._scrollPosition = scrollTop; | 9164 this._scrollPosition = scrollTop; |
12867 | 9165 |
12868 // clear cached visible indexes | |
12869 this._firstVisibleIndexVal = null; | 9166 this._firstVisibleIndexVal = null; |
12870 this._lastVisibleIndexVal = null; | 9167 this._lastVisibleIndexVal = null; |
12871 | 9168 |
12872 scrollBottom = this._scrollBottom; | 9169 scrollBottom = this._scrollBottom; |
12873 physicalBottom = this._physicalBottom; | 9170 physicalBottom = this._physicalBottom; |
12874 | 9171 |
12875 // random access | |
12876 if (Math.abs(delta) > this._physicalSize) { | 9172 if (Math.abs(delta) > this._physicalSize) { |
12877 this._physicalTop += delta; | 9173 this._physicalTop += delta; |
12878 recycledTiles = Math.round(delta / this._physicalAverage); | 9174 recycledTiles = Math.round(delta / this._physicalAverage); |
12879 } | 9175 } |
12880 // scroll up | |
12881 else if (delta < 0) { | 9176 else if (delta < 0) { |
12882 var topSpace = scrollTop - this._physicalTop; | 9177 var topSpace = scrollTop - this._physicalTop; |
12883 var virtualStart = this._virtualStart; | 9178 var virtualStart = this._virtualStart; |
12884 | 9179 |
12885 recycledTileSet = []; | 9180 recycledTileSet = []; |
12886 | 9181 |
12887 kth = this._physicalEnd; | 9182 kth = this._physicalEnd; |
12888 currentRatio = topSpace / hiddenContentSize; | 9183 currentRatio = topSpace / hiddenContentSize; |
12889 | 9184 |
12890 // move tiles from bottom to top | |
12891 while ( | 9185 while ( |
12892 // approximate `currentRatio` to `ratio` | |
12893 currentRatio < ratio && | 9186 currentRatio < ratio && |
12894 // recycle less physical items than the total | |
12895 recycledTiles < this._physicalCount && | 9187 recycledTiles < this._physicalCount && |
12896 // ensure that these recycled tiles are needed | |
12897 virtualStart - recycledTiles > 0 && | 9188 virtualStart - recycledTiles > 0 && |
12898 // ensure that the tile is not visible | |
12899 physicalBottom - this._getPhysicalSizeIncrement(kth) > scrollBottom | 9189 physicalBottom - this._getPhysicalSizeIncrement(kth) > scrollBottom |
12900 ) { | 9190 ) { |
12901 | 9191 |
12902 tileHeight = this._getPhysicalSizeIncrement(kth); | 9192 tileHeight = this._getPhysicalSizeIncrement(kth); |
12903 currentRatio += tileHeight / hiddenContentSize; | 9193 currentRatio += tileHeight / hiddenContentSize; |
12904 physicalBottom -= tileHeight; | 9194 physicalBottom -= tileHeight; |
12905 recycledTileSet.push(kth); | 9195 recycledTileSet.push(kth); |
12906 recycledTiles++; | 9196 recycledTiles++; |
12907 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; | 9197 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; |
12908 } | 9198 } |
12909 | 9199 |
12910 movingUp = recycledTileSet; | 9200 movingUp = recycledTileSet; |
12911 recycledTiles = -recycledTiles; | 9201 recycledTiles = -recycledTiles; |
12912 } | 9202 } |
12913 // scroll down | |
12914 else if (delta > 0) { | 9203 else if (delta > 0) { |
12915 var bottomSpace = physicalBottom - scrollBottom; | 9204 var bottomSpace = physicalBottom - scrollBottom; |
12916 var virtualEnd = this._virtualEnd; | 9205 var virtualEnd = this._virtualEnd; |
12917 var lastVirtualItemIndex = this._virtualCount-1; | 9206 var lastVirtualItemIndex = this._virtualCount-1; |
12918 | 9207 |
12919 recycledTileSet = []; | 9208 recycledTileSet = []; |
12920 | 9209 |
12921 kth = this._physicalStart; | 9210 kth = this._physicalStart; |
12922 currentRatio = bottomSpace / hiddenContentSize; | 9211 currentRatio = bottomSpace / hiddenContentSize; |
12923 | 9212 |
12924 // move tiles from top to bottom | |
12925 while ( | 9213 while ( |
12926 // approximate `currentRatio` to `ratio` | |
12927 currentRatio < ratio && | 9214 currentRatio < ratio && |
12928 // recycle less physical items than the total | |
12929 recycledTiles < this._physicalCount && | 9215 recycledTiles < this._physicalCount && |
12930 // ensure that these recycled tiles are needed | |
12931 virtualEnd + recycledTiles < lastVirtualItemIndex && | 9216 virtualEnd + recycledTiles < lastVirtualItemIndex && |
12932 // ensure that the tile is not visible | |
12933 this._physicalTop + this._getPhysicalSizeIncrement(kth) < scrollTop | 9217 this._physicalTop + this._getPhysicalSizeIncrement(kth) < scrollTop |
12934 ) { | 9218 ) { |
12935 | 9219 |
12936 tileHeight = this._getPhysicalSizeIncrement(kth); | 9220 tileHeight = this._getPhysicalSizeIncrement(kth); |
12937 currentRatio += tileHeight / hiddenContentSize; | 9221 currentRatio += tileHeight / hiddenContentSize; |
12938 | 9222 |
12939 this._physicalTop += tileHeight; | 9223 this._physicalTop += tileHeight; |
12940 recycledTileSet.push(kth); | 9224 recycledTileSet.push(kth); |
12941 recycledTiles++; | 9225 recycledTiles++; |
12942 kth = (kth + 1) % this._physicalCount; | 9226 kth = (kth + 1) % this._physicalCount; |
12943 } | 9227 } |
12944 } | 9228 } |
12945 | 9229 |
12946 if (recycledTiles === 0) { | 9230 if (recycledTiles === 0) { |
12947 // Try to increase the pool if the list's client height isn't filled up
with physical items | |
12948 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { | 9231 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { |
12949 this._increasePoolIfNeeded(); | 9232 this._increasePoolIfNeeded(); |
12950 } | 9233 } |
12951 } else { | 9234 } else { |
12952 this._virtualStart = this._virtualStart + recycledTiles; | 9235 this._virtualStart = this._virtualStart + recycledTiles; |
12953 this._physicalStart = this._physicalStart + recycledTiles; | 9236 this._physicalStart = this._physicalStart + recycledTiles; |
12954 this._update(recycledTileSet, movingUp); | 9237 this._update(recycledTileSet, movingUp); |
12955 } | 9238 } |
12956 }, | 9239 }, |
12957 | 9240 |
12958 /** | |
12959 * Update the list of items, starting from the `_virtualStart` item. | |
12960 * @param {!Array<number>=} itemSet | |
12961 * @param {!Array<number>=} movingUp | |
12962 */ | |
12963 _update: function(itemSet, movingUp) { | 9241 _update: function(itemSet, movingUp) { |
12964 // manage focus | |
12965 this._manageFocus(); | 9242 this._manageFocus(); |
12966 // update models | |
12967 this._assignModels(itemSet); | 9243 this._assignModels(itemSet); |
12968 // measure heights | |
12969 this._updateMetrics(itemSet); | 9244 this._updateMetrics(itemSet); |
12970 // adjust offset after measuring | |
12971 if (movingUp) { | 9245 if (movingUp) { |
12972 while (movingUp.length) { | 9246 while (movingUp.length) { |
12973 var idx = movingUp.pop(); | 9247 var idx = movingUp.pop(); |
12974 this._physicalTop -= this._getPhysicalSizeIncrement(idx); | 9248 this._physicalTop -= this._getPhysicalSizeIncrement(idx); |
12975 } | 9249 } |
12976 } | 9250 } |
12977 // update the position of the items | |
12978 this._positionItems(); | 9251 this._positionItems(); |
12979 // set the scroller size | |
12980 this._updateScrollerSize(); | 9252 this._updateScrollerSize(); |
12981 // increase the pool of physical items | |
12982 this._increasePoolIfNeeded(); | 9253 this._increasePoolIfNeeded(); |
12983 }, | 9254 }, |
12984 | 9255 |
12985 /** | |
12986 * Creates a pool of DOM elements and attaches them to the local dom. | |
12987 */ | |
12988 _createPool: function(size) { | 9256 _createPool: function(size) { |
12989 var physicalItems = new Array(size); | 9257 var physicalItems = new Array(size); |
12990 | 9258 |
12991 this._ensureTemplatized(); | 9259 this._ensureTemplatized(); |
12992 | 9260 |
12993 for (var i = 0; i < size; i++) { | 9261 for (var i = 0; i < size; i++) { |
12994 var inst = this.stamp(null); | 9262 var inst = this.stamp(null); |
12995 // First element child is item; Safari doesn't support children[0] | |
12996 // on a doc fragment | |
12997 physicalItems[i] = inst.root.querySelector('*'); | 9263 physicalItems[i] = inst.root.querySelector('*'); |
12998 Polymer.dom(this).appendChild(inst.root); | 9264 Polymer.dom(this).appendChild(inst.root); |
12999 } | 9265 } |
13000 return physicalItems; | 9266 return physicalItems; |
13001 }, | 9267 }, |
13002 | 9268 |
13003 /** | |
13004 * Increases the pool of physical items only if needed. | |
13005 * | |
13006 * @return {boolean} True if the pool was increased. | |
13007 */ | |
13008 _increasePoolIfNeeded: function() { | 9269 _increasePoolIfNeeded: function() { |
13009 // Base case 1: the list has no height. | |
13010 if (this._viewportHeight === 0) { | 9270 if (this._viewportHeight === 0) { |
13011 return false; | 9271 return false; |
13012 } | 9272 } |
13013 // Base case 2: If the physical size is optimal and the list's client heig
ht is full | |
13014 // with physical items, don't increase the pool. | |
13015 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi
s._physicalTop <= this._scrollPosition; | 9273 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi
s._physicalTop <= this._scrollPosition; |
13016 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { | 9274 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { |
13017 return false; | 9275 return false; |
13018 } | 9276 } |
13019 // this value should range between [0 <= `currentPage` <= `_maxPages`] | |
13020 var currentPage = Math.floor(this._physicalSize / this._viewportHeight); | 9277 var currentPage = Math.floor(this._physicalSize / this._viewportHeight); |
13021 | 9278 |
13022 if (currentPage === 0) { | 9279 if (currentPage === 0) { |
13023 // fill the first page | |
13024 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); | 9280 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); |
13025 } else if (this._lastPage !== currentPage && isClientHeightFull) { | 9281 } else if (this._lastPage !== currentPage && isClientHeightFull) { |
13026 // paint the page and defer the next increase | |
13027 // wait 16ms which is rough enough to get paint cycle. | |
13028 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, this._itemsPerRow), 16)); | 9282 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, this._itemsPerRow), 16)); |
13029 } else { | 9283 } else { |
13030 // fill the rest of the pages | |
13031 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow))
; | 9284 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow))
; |
13032 } | 9285 } |
13033 | 9286 |
13034 this._lastPage = currentPage; | 9287 this._lastPage = currentPage; |
13035 | 9288 |
13036 return true; | 9289 return true; |
13037 }, | 9290 }, |
13038 | 9291 |
13039 /** | |
13040 * Increases the pool size. | |
13041 */ | |
13042 _increasePool: function(missingItems) { | 9292 _increasePool: function(missingItems) { |
13043 var nextPhysicalCount = Math.min( | 9293 var nextPhysicalCount = Math.min( |
13044 this._physicalCount + missingItems, | 9294 this._physicalCount + missingItems, |
13045 this._virtualCount - this._virtualStart, | 9295 this._virtualCount - this._virtualStart, |
13046 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) | 9296 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) |
13047 ); | 9297 ); |
13048 var prevPhysicalCount = this._physicalCount; | 9298 var prevPhysicalCount = this._physicalCount; |
13049 var delta = nextPhysicalCount - prevPhysicalCount; | 9299 var delta = nextPhysicalCount - prevPhysicalCount; |
13050 | 9300 |
13051 if (delta <= 0) { | 9301 if (delta <= 0) { |
13052 return; | 9302 return; |
13053 } | 9303 } |
13054 | 9304 |
13055 [].push.apply(this._physicalItems, this._createPool(delta)); | 9305 [].push.apply(this._physicalItems, this._createPool(delta)); |
13056 [].push.apply(this._physicalSizes, new Array(delta)); | 9306 [].push.apply(this._physicalSizes, new Array(delta)); |
13057 | 9307 |
13058 this._physicalCount = prevPhysicalCount + delta; | 9308 this._physicalCount = prevPhysicalCount + delta; |
13059 | 9309 |
13060 // update the physical start if we need to preserve the model of the focus
ed item. | |
13061 // In this situation, the focused item is currently rendered and its model
would | |
13062 // have changed after increasing the pool if the physical start remained u
nchanged. | |
13063 if (this._physicalStart > this._physicalEnd && | 9310 if (this._physicalStart > this._physicalEnd && |
13064 this._isIndexRendered(this._focusedIndex) && | 9311 this._isIndexRendered(this._focusedIndex) && |
13065 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { | 9312 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { |
13066 this._physicalStart = this._physicalStart + delta; | 9313 this._physicalStart = this._physicalStart + delta; |
13067 } | 9314 } |
13068 this._update(); | 9315 this._update(); |
13069 }, | 9316 }, |
13070 | 9317 |
13071 /** | |
13072 * Render a new list of items. This method does exactly the same as `update`
, | |
13073 * but it also ensures that only one `update` cycle is created. | |
13074 */ | |
13075 _render: function() { | 9318 _render: function() { |
13076 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; | 9319 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; |
13077 | 9320 |
13078 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { | 9321 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { |
13079 this._lastPage = 0; | 9322 this._lastPage = 0; |
13080 this._update(); | 9323 this._update(); |
13081 this._itemsRendered = true; | 9324 this._itemsRendered = true; |
13082 } | 9325 } |
13083 }, | 9326 }, |
13084 | 9327 |
13085 /** | |
13086 * Templetizes the user template. | |
13087 */ | |
13088 _ensureTemplatized: function() { | 9328 _ensureTemplatized: function() { |
13089 if (!this.ctor) { | 9329 if (!this.ctor) { |
13090 // Template instance props that should be excluded from forwarding | |
13091 var props = {}; | 9330 var props = {}; |
13092 props.__key__ = true; | 9331 props.__key__ = true; |
13093 props[this.as] = true; | 9332 props[this.as] = true; |
13094 props[this.indexAs] = true; | 9333 props[this.indexAs] = true; |
13095 props[this.selectedAs] = true; | 9334 props[this.selectedAs] = true; |
13096 props.tabIndex = true; | 9335 props.tabIndex = true; |
13097 | 9336 |
13098 this._instanceProps = props; | 9337 this._instanceProps = props; |
13099 this._userTemplate = Polymer.dom(this).querySelector('template'); | 9338 this._userTemplate = Polymer.dom(this).querySelector('template'); |
13100 | 9339 |
13101 if (this._userTemplate) { | 9340 if (this._userTemplate) { |
13102 this.templatize(this._userTemplate); | 9341 this.templatize(this._userTemplate); |
13103 } else { | 9342 } else { |
13104 console.warn('iron-list requires a template to be provided in light-do
m'); | 9343 console.warn('iron-list requires a template to be provided in light-do
m'); |
13105 } | 9344 } |
13106 } | 9345 } |
13107 }, | 9346 }, |
13108 | 9347 |
13109 /** | |
13110 * Implements extension point from Templatizer mixin. | |
13111 */ | |
13112 _getStampedChildren: function() { | 9348 _getStampedChildren: function() { |
13113 return this._physicalItems; | 9349 return this._physicalItems; |
13114 }, | 9350 }, |
13115 | 9351 |
13116 /** | |
13117 * Implements extension point from Templatizer | |
13118 * Called as a side effect of a template instance path change, responsible | |
13119 * for notifying items.<key-for-instance>.<path> change up to host. | |
13120 */ | |
13121 _forwardInstancePath: function(inst, path, value) { | 9352 _forwardInstancePath: function(inst, path, value) { |
13122 if (path.indexOf(this.as + '.') === 0) { | 9353 if (path.indexOf(this.as + '.') === 0) { |
13123 this.notifyPath('items.' + inst.__key__ + '.' + | 9354 this.notifyPath('items.' + inst.__key__ + '.' + |
13124 path.slice(this.as.length + 1), value); | 9355 path.slice(this.as.length + 1), value); |
13125 } | 9356 } |
13126 }, | 9357 }, |
13127 | 9358 |
13128 /** | |
13129 * Implements extension point from Templatizer mixin | |
13130 * Called as side-effect of a host property change, responsible for | |
13131 * notifying parent path change on each row. | |
13132 */ | |
13133 _forwardParentProp: function(prop, value) { | 9359 _forwardParentProp: function(prop, value) { |
13134 if (this._physicalItems) { | 9360 if (this._physicalItems) { |
13135 this._physicalItems.forEach(function(item) { | 9361 this._physicalItems.forEach(function(item) { |
13136 item._templateInstance[prop] = value; | 9362 item._templateInstance[prop] = value; |
13137 }, this); | 9363 }, this); |
13138 } | 9364 } |
13139 }, | 9365 }, |
13140 | 9366 |
13141 /** | |
13142 * Implements extension point from Templatizer | |
13143 * Called as side-effect of a host path change, responsible for | |
13144 * notifying parent.<path> path change on each row. | |
13145 */ | |
13146 _forwardParentPath: function(path, value) { | 9367 _forwardParentPath: function(path, value) { |
13147 if (this._physicalItems) { | 9368 if (this._physicalItems) { |
13148 this._physicalItems.forEach(function(item) { | 9369 this._physicalItems.forEach(function(item) { |
13149 item._templateInstance.notifyPath(path, value, true); | 9370 item._templateInstance.notifyPath(path, value, true); |
13150 }, this); | 9371 }, this); |
13151 } | 9372 } |
13152 }, | 9373 }, |
13153 | 9374 |
13154 /** | |
13155 * Called as a side effect of a host items.<key>.<path> path change, | |
13156 * responsible for notifying item.<path> changes. | |
13157 */ | |
13158 _forwardItemPath: function(path, value) { | 9375 _forwardItemPath: function(path, value) { |
13159 if (!this._physicalIndexForKey) { | 9376 if (!this._physicalIndexForKey) { |
13160 return; | 9377 return; |
13161 } | 9378 } |
13162 var dot = path.indexOf('.'); | 9379 var dot = path.indexOf('.'); |
13163 var key = path.substring(0, dot < 0 ? path.length : dot); | 9380 var key = path.substring(0, dot < 0 ? path.length : dot); |
13164 var idx = this._physicalIndexForKey[key]; | 9381 var idx = this._physicalIndexForKey[key]; |
13165 var offscreenItem = this._offscreenFocusedItem; | 9382 var offscreenItem = this._offscreenFocusedItem; |
13166 var el = offscreenItem && offscreenItem._templateInstance.__key__ === key
? | 9383 var el = offscreenItem && offscreenItem._templateInstance.__key__ === key
? |
13167 offscreenItem : this._physicalItems[idx]; | 9384 offscreenItem : this._physicalItems[idx]; |
13168 | 9385 |
13169 if (!el || el._templateInstance.__key__ !== key) { | 9386 if (!el || el._templateInstance.__key__ !== key) { |
13170 return; | 9387 return; |
13171 } | 9388 } |
13172 if (dot >= 0) { | 9389 if (dot >= 0) { |
13173 path = this.as + '.' + path.substring(dot+1); | 9390 path = this.as + '.' + path.substring(dot+1); |
13174 el._templateInstance.notifyPath(path, value, true); | 9391 el._templateInstance.notifyPath(path, value, true); |
13175 } else { | 9392 } else { |
13176 // Update selection if needed | |
13177 var currentItem = el._templateInstance[this.as]; | 9393 var currentItem = el._templateInstance[this.as]; |
13178 if (Array.isArray(this.selectedItems)) { | 9394 if (Array.isArray(this.selectedItems)) { |
13179 for (var i = 0; i < this.selectedItems.length; i++) { | 9395 for (var i = 0; i < this.selectedItems.length; i++) { |
13180 if (this.selectedItems[i] === currentItem) { | 9396 if (this.selectedItems[i] === currentItem) { |
13181 this.set('selectedItems.' + i, value); | 9397 this.set('selectedItems.' + i, value); |
13182 break; | 9398 break; |
13183 } | 9399 } |
13184 } | 9400 } |
13185 } else if (this.selectedItem === currentItem) { | 9401 } else if (this.selectedItem === currentItem) { |
13186 this.set('selectedItem', value); | 9402 this.set('selectedItem', value); |
13187 } | 9403 } |
13188 el._templateInstance[this.as] = value; | 9404 el._templateInstance[this.as] = value; |
13189 } | 9405 } |
13190 }, | 9406 }, |
13191 | 9407 |
13192 /** | |
13193 * Called when the items have changed. That is, ressignments | |
13194 * to `items`, splices or updates to a single item. | |
13195 */ | |
13196 _itemsChanged: function(change) { | 9408 _itemsChanged: function(change) { |
13197 if (change.path === 'items') { | 9409 if (change.path === 'items') { |
13198 // reset items | |
13199 this._virtualStart = 0; | 9410 this._virtualStart = 0; |
13200 this._physicalTop = 0; | 9411 this._physicalTop = 0; |
13201 this._virtualCount = this.items ? this.items.length : 0; | 9412 this._virtualCount = this.items ? this.items.length : 0; |
13202 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; | 9413 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; |
13203 this._physicalIndexForKey = {}; | 9414 this._physicalIndexForKey = {}; |
13204 this._firstVisibleIndexVal = null; | 9415 this._firstVisibleIndexVal = null; |
13205 this._lastVisibleIndexVal = null; | 9416 this._lastVisibleIndexVal = null; |
13206 | 9417 |
13207 this._resetScrollPosition(0); | 9418 this._resetScrollPosition(0); |
13208 this._removeFocusedItem(); | 9419 this._removeFocusedItem(); |
13209 // create the initial physical items | |
13210 if (!this._physicalItems) { | 9420 if (!this._physicalItems) { |
13211 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi
s._virtualCount)); | 9421 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi
s._virtualCount)); |
13212 this._physicalItems = this._createPool(this._physicalCount); | 9422 this._physicalItems = this._createPool(this._physicalCount); |
13213 this._physicalSizes = new Array(this._physicalCount); | 9423 this._physicalSizes = new Array(this._physicalCount); |
13214 } | 9424 } |
13215 | 9425 |
13216 this._physicalStart = 0; | 9426 this._physicalStart = 0; |
13217 | 9427 |
13218 } else if (change.path === 'items.splices') { | 9428 } else if (change.path === 'items.splices') { |
13219 | 9429 |
13220 this._adjustVirtualIndex(change.value.indexSplices); | 9430 this._adjustVirtualIndex(change.value.indexSplices); |
13221 this._virtualCount = this.items ? this.items.length : 0; | 9431 this._virtualCount = this.items ? this.items.length : 0; |
13222 | 9432 |
13223 } else { | 9433 } else { |
13224 // update a single item | |
13225 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); | 9434 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); |
13226 return; | 9435 return; |
13227 } | 9436 } |
13228 | 9437 |
13229 this._itemsRendered = false; | 9438 this._itemsRendered = false; |
13230 this._debounceTemplate(this._render); | 9439 this._debounceTemplate(this._render); |
13231 }, | 9440 }, |
13232 | 9441 |
13233 /** | |
13234 * @param {!Array<!PolymerSplice>} splices | |
13235 */ | |
13236 _adjustVirtualIndex: function(splices) { | 9442 _adjustVirtualIndex: function(splices) { |
13237 splices.forEach(function(splice) { | 9443 splices.forEach(function(splice) { |
13238 // deselect removed items | |
13239 splice.removed.forEach(this._removeItem, this); | 9444 splice.removed.forEach(this._removeItem, this); |
13240 // We only need to care about changes happening above the current positi
on | |
13241 if (splice.index < this._virtualStart) { | 9445 if (splice.index < this._virtualStart) { |
13242 var delta = Math.max( | 9446 var delta = Math.max( |
13243 splice.addedCount - splice.removed.length, | 9447 splice.addedCount - splice.removed.length, |
13244 splice.index - this._virtualStart); | 9448 splice.index - this._virtualStart); |
13245 | 9449 |
13246 this._virtualStart = this._virtualStart + delta; | 9450 this._virtualStart = this._virtualStart + delta; |
13247 | 9451 |
13248 if (this._focusedIndex >= 0) { | 9452 if (this._focusedIndex >= 0) { |
13249 this._focusedIndex = this._focusedIndex + delta; | 9453 this._focusedIndex = this._focusedIndex + delta; |
13250 } | 9454 } |
13251 } | 9455 } |
13252 }, this); | 9456 }, this); |
13253 }, | 9457 }, |
13254 | 9458 |
13255 _removeItem: function(item) { | 9459 _removeItem: function(item) { |
13256 this.$.selector.deselect(item); | 9460 this.$.selector.deselect(item); |
13257 // remove the current focused item | |
13258 if (this._focusedItem && this._focusedItem._templateInstance[this.as] ===
item) { | 9461 if (this._focusedItem && this._focusedItem._templateInstance[this.as] ===
item) { |
13259 this._removeFocusedItem(); | 9462 this._removeFocusedItem(); |
13260 } | 9463 } |
13261 }, | 9464 }, |
13262 | 9465 |
13263 /** | |
13264 * Executes a provided function per every physical index in `itemSet` | |
13265 * `itemSet` default value is equivalent to the entire set of physical index
es. | |
13266 * | |
13267 * @param {!function(number, number)} fn | |
13268 * @param {!Array<number>=} itemSet | |
13269 */ | |
13270 _iterateItems: function(fn, itemSet) { | 9466 _iterateItems: function(fn, itemSet) { |
13271 var pidx, vidx, rtn, i; | 9467 var pidx, vidx, rtn, i; |
13272 | 9468 |
13273 if (arguments.length === 2 && itemSet) { | 9469 if (arguments.length === 2 && itemSet) { |
13274 for (i = 0; i < itemSet.length; i++) { | 9470 for (i = 0; i < itemSet.length; i++) { |
13275 pidx = itemSet[i]; | 9471 pidx = itemSet[i]; |
13276 vidx = this._computeVidx(pidx); | 9472 vidx = this._computeVidx(pidx); |
13277 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 9473 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
13278 return rtn; | 9474 return rtn; |
13279 } | 9475 } |
13280 } | 9476 } |
13281 } else { | 9477 } else { |
13282 pidx = this._physicalStart; | 9478 pidx = this._physicalStart; |
13283 vidx = this._virtualStart; | 9479 vidx = this._virtualStart; |
13284 | 9480 |
13285 for (; pidx < this._physicalCount; pidx++, vidx++) { | 9481 for (; pidx < this._physicalCount; pidx++, vidx++) { |
13286 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 9482 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
13287 return rtn; | 9483 return rtn; |
13288 } | 9484 } |
13289 } | 9485 } |
13290 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) { | 9486 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) { |
13291 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 9487 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
13292 return rtn; | 9488 return rtn; |
13293 } | 9489 } |
13294 } | 9490 } |
13295 } | 9491 } |
13296 }, | 9492 }, |
13297 | 9493 |
13298 /** | |
13299 * Returns the virtual index for a given physical index | |
13300 * | |
13301 * @param {number} pidx Physical index | |
13302 * @return {number} | |
13303 */ | |
13304 _computeVidx: function(pidx) { | 9494 _computeVidx: function(pidx) { |
13305 if (pidx >= this._physicalStart) { | 9495 if (pidx >= this._physicalStart) { |
13306 return this._virtualStart + (pidx - this._physicalStart); | 9496 return this._virtualStart + (pidx - this._physicalStart); |
13307 } | 9497 } |
13308 return this._virtualStart + (this._physicalCount - this._physicalStart) +
pidx; | 9498 return this._virtualStart + (this._physicalCount - this._physicalStart) +
pidx; |
13309 }, | 9499 }, |
13310 | 9500 |
13311 /** | |
13312 * Assigns the data models to a given set of items. | |
13313 * @param {!Array<number>=} itemSet | |
13314 */ | |
13315 _assignModels: function(itemSet) { | 9501 _assignModels: function(itemSet) { |
13316 this._iterateItems(function(pidx, vidx) { | 9502 this._iterateItems(function(pidx, vidx) { |
13317 var el = this._physicalItems[pidx]; | 9503 var el = this._physicalItems[pidx]; |
13318 var inst = el._templateInstance; | 9504 var inst = el._templateInstance; |
13319 var item = this.items && this.items[vidx]; | 9505 var item = this.items && this.items[vidx]; |
13320 | 9506 |
13321 if (item != null) { | 9507 if (item != null) { |
13322 inst[this.as] = item; | 9508 inst[this.as] = item; |
13323 inst.__key__ = this._collection.getKey(item); | 9509 inst.__key__ = this._collection.getKey(item); |
13324 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s
elector).isSelected(item); | 9510 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s
elector).isSelected(item); |
13325 inst[this.indexAs] = vidx; | 9511 inst[this.indexAs] = vidx; |
13326 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1; | 9512 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1; |
13327 this._physicalIndexForKey[inst.__key__] = pidx; | 9513 this._physicalIndexForKey[inst.__key__] = pidx; |
13328 el.removeAttribute('hidden'); | 9514 el.removeAttribute('hidden'); |
13329 } else { | 9515 } else { |
13330 inst.__key__ = null; | 9516 inst.__key__ = null; |
13331 el.setAttribute('hidden', ''); | 9517 el.setAttribute('hidden', ''); |
13332 } | 9518 } |
13333 }, itemSet); | 9519 }, itemSet); |
13334 }, | 9520 }, |
13335 | 9521 |
13336 /** | |
13337 * Updates the height for a given set of items. | |
13338 * | |
13339 * @param {!Array<number>=} itemSet | |
13340 */ | |
13341 _updateMetrics: function(itemSet) { | 9522 _updateMetrics: function(itemSet) { |
13342 // Make sure we distributed all the physical items | |
13343 // so we can measure them | |
13344 Polymer.dom.flush(); | 9523 Polymer.dom.flush(); |
13345 | 9524 |
13346 var newPhysicalSize = 0; | 9525 var newPhysicalSize = 0; |
13347 var oldPhysicalSize = 0; | 9526 var oldPhysicalSize = 0; |
13348 var prevAvgCount = this._physicalAverageCount; | 9527 var prevAvgCount = this._physicalAverageCount; |
13349 var prevPhysicalAvg = this._physicalAverage; | 9528 var prevPhysicalAvg = this._physicalAverage; |
13350 | 9529 |
13351 this._iterateItems(function(pidx, vidx) { | 9530 this._iterateItems(function(pidx, vidx) { |
13352 | 9531 |
13353 oldPhysicalSize += this._physicalSizes[pidx] || 0; | 9532 oldPhysicalSize += this._physicalSizes[pidx] || 0; |
13354 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; | 9533 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; |
13355 newPhysicalSize += this._physicalSizes[pidx]; | 9534 newPhysicalSize += this._physicalSizes[pidx]; |
13356 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; | 9535 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; |
13357 | 9536 |
13358 }, itemSet); | 9537 }, itemSet); |
13359 | 9538 |
13360 this._viewportHeight = this._scrollTargetHeight; | 9539 this._viewportHeight = this._scrollTargetHeight; |
13361 if (this.grid) { | 9540 if (this.grid) { |
13362 this._updateGridMetrics(); | 9541 this._updateGridMetrics(); |
13363 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow)
* this._rowHeight; | 9542 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow)
* this._rowHeight; |
13364 } else { | 9543 } else { |
13365 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS
ize; | 9544 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS
ize; |
13366 } | 9545 } |
13367 | 9546 |
13368 // update the average if we measured something | |
13369 if (this._physicalAverageCount !== prevAvgCount) { | 9547 if (this._physicalAverageCount !== prevAvgCount) { |
13370 this._physicalAverage = Math.round( | 9548 this._physicalAverage = Math.round( |
13371 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / | 9549 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / |
13372 this._physicalAverageCount); | 9550 this._physicalAverageCount); |
13373 } | 9551 } |
13374 }, | 9552 }, |
13375 | 9553 |
13376 _updateGridMetrics: function() { | 9554 _updateGridMetrics: function() { |
13377 this._viewportWidth = this.$.items.offsetWidth; | 9555 this._viewportWidth = this.$.items.offsetWidth; |
13378 // Set item width to the value of the _physicalItems offsetWidth | |
13379 this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoun
dingClientRect().width : DEFAULT_GRID_SIZE; | 9556 this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoun
dingClientRect().width : DEFAULT_GRID_SIZE; |
13380 // Set row height to the value of the _physicalItems offsetHeight | |
13381 this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetH
eight : DEFAULT_GRID_SIZE; | 9557 this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetH
eight : DEFAULT_GRID_SIZE; |
13382 // If in grid mode compute how many items with exist in each row | |
13383 this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / thi
s._itemWidth) : this._itemsPerRow; | 9558 this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / thi
s._itemWidth) : this._itemsPerRow; |
13384 }, | 9559 }, |
13385 | 9560 |
13386 /** | |
13387 * Updates the position of the physical items. | |
13388 */ | |
13389 _positionItems: function() { | 9561 _positionItems: function() { |
13390 this._adjustScrollPosition(); | 9562 this._adjustScrollPosition(); |
13391 | 9563 |
13392 var y = this._physicalTop; | 9564 var y = this._physicalTop; |
13393 | 9565 |
13394 if (this.grid) { | 9566 if (this.grid) { |
13395 var totalItemWidth = this._itemsPerRow * this._itemWidth; | 9567 var totalItemWidth = this._itemsPerRow * this._itemWidth; |
13396 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; | 9568 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; |
13397 | 9569 |
13398 this._iterateItems(function(pidx, vidx) { | 9570 this._iterateItems(function(pidx, vidx) { |
(...skipping 21 matching lines...) Expand all Loading... |
13420 _getPhysicalSizeIncrement: function(pidx) { | 9592 _getPhysicalSizeIncrement: function(pidx) { |
13421 if (!this.grid) { | 9593 if (!this.grid) { |
13422 return this._physicalSizes[pidx]; | 9594 return this._physicalSizes[pidx]; |
13423 } | 9595 } |
13424 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1)
{ | 9596 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1)
{ |
13425 return 0; | 9597 return 0; |
13426 } | 9598 } |
13427 return this._rowHeight; | 9599 return this._rowHeight; |
13428 }, | 9600 }, |
13429 | 9601 |
13430 /** | |
13431 * Returns, based on the current index, | |
13432 * whether or not the next index will need | |
13433 * to be rendered on a new row. | |
13434 * | |
13435 * @param {number} vidx Virtual index | |
13436 * @return {boolean} | |
13437 */ | |
13438 _shouldRenderNextRow: function(vidx) { | 9602 _shouldRenderNextRow: function(vidx) { |
13439 return vidx % this._itemsPerRow === this._itemsPerRow - 1; | 9603 return vidx % this._itemsPerRow === this._itemsPerRow - 1; |
13440 }, | 9604 }, |
13441 | 9605 |
13442 /** | |
13443 * Adjusts the scroll position when it was overestimated. | |
13444 */ | |
13445 _adjustScrollPosition: function() { | 9606 _adjustScrollPosition: function() { |
13446 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : | 9607 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : |
13447 Math.min(this._scrollPosition + this._physicalTop, 0); | 9608 Math.min(this._scrollPosition + this._physicalTop, 0); |
13448 | 9609 |
13449 if (deltaHeight) { | 9610 if (deltaHeight) { |
13450 this._physicalTop = this._physicalTop - deltaHeight; | 9611 this._physicalTop = this._physicalTop - deltaHeight; |
13451 // juking scroll position during interial scrolling on iOS is no bueno | |
13452 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) { | 9612 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) { |
13453 this._resetScrollPosition(this._scrollTop - deltaHeight); | 9613 this._resetScrollPosition(this._scrollTop - deltaHeight); |
13454 } | 9614 } |
13455 } | 9615 } |
13456 }, | 9616 }, |
13457 | 9617 |
13458 /** | |
13459 * Sets the position of the scroll. | |
13460 */ | |
13461 _resetScrollPosition: function(pos) { | 9618 _resetScrollPosition: function(pos) { |
13462 if (this.scrollTarget) { | 9619 if (this.scrollTarget) { |
13463 this._scrollTop = pos; | 9620 this._scrollTop = pos; |
13464 this._scrollPosition = this._scrollTop; | 9621 this._scrollPosition = this._scrollTop; |
13465 } | 9622 } |
13466 }, | 9623 }, |
13467 | 9624 |
13468 /** | |
13469 * Sets the scroll height, that's the height of the content, | |
13470 * | |
13471 * @param {boolean=} forceUpdate If true, updates the height no matter what. | |
13472 */ | |
13473 _updateScrollerSize: function(forceUpdate) { | 9625 _updateScrollerSize: function(forceUpdate) { |
13474 if (this.grid) { | 9626 if (this.grid) { |
13475 this._estScrollHeight = this._virtualRowCount * this._rowHeight; | 9627 this._estScrollHeight = this._virtualRowCount * this._rowHeight; |
13476 } else { | 9628 } else { |
13477 this._estScrollHeight = (this._physicalBottom + | 9629 this._estScrollHeight = (this._physicalBottom + |
13478 Math.max(this._virtualCount - this._physicalCount - this._virtualSta
rt, 0) * this._physicalAverage); | 9630 Math.max(this._virtualCount - this._physicalCount - this._virtualSta
rt, 0) * this._physicalAverage); |
13479 } | 9631 } |
13480 | 9632 |
13481 forceUpdate = forceUpdate || this._scrollHeight === 0; | 9633 forceUpdate = forceUpdate || this._scrollHeight === 0; |
13482 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; | 9634 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; |
13483 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this
._estScrollHeight; | 9635 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this
._estScrollHeight; |
13484 | 9636 |
13485 // amortize height adjustment, so it won't trigger repaints very often | |
13486 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { | 9637 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { |
13487 this.$.items.style.height = this._estScrollHeight + 'px'; | 9638 this.$.items.style.height = this._estScrollHeight + 'px'; |
13488 this._scrollHeight = this._estScrollHeight; | 9639 this._scrollHeight = this._estScrollHeight; |
13489 } | 9640 } |
13490 }, | 9641 }, |
13491 | 9642 |
13492 /** | |
13493 * Scroll to a specific item in the virtual list regardless | |
13494 * of the physical items in the DOM tree. | |
13495 * | |
13496 * @method scrollToItem | |
13497 * @param {(Object)} item The item to be scrolled to | |
13498 */ | |
13499 scrollToItem: function(item){ | 9643 scrollToItem: function(item){ |
13500 return this.scrollToIndex(this.items.indexOf(item)); | 9644 return this.scrollToIndex(this.items.indexOf(item)); |
13501 }, | 9645 }, |
13502 | 9646 |
13503 /** | |
13504 * Scroll to a specific index in the virtual list regardless | |
13505 * of the physical items in the DOM tree. | |
13506 * | |
13507 * @method scrollToIndex | |
13508 * @param {number} idx The index of the item | |
13509 */ | |
13510 scrollToIndex: function(idx) { | 9647 scrollToIndex: function(idx) { |
13511 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { | 9648 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { |
13512 return; | 9649 return; |
13513 } | 9650 } |
13514 | 9651 |
13515 Polymer.dom.flush(); | 9652 Polymer.dom.flush(); |
13516 | 9653 |
13517 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); | 9654 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); |
13518 // update the virtual start only when needed | |
13519 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { | 9655 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { |
13520 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); | 9656 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); |
13521 } | 9657 } |
13522 // manage focus | |
13523 this._manageFocus(); | 9658 this._manageFocus(); |
13524 // assign new models | |
13525 this._assignModels(); | 9659 this._assignModels(); |
13526 // measure the new sizes | |
13527 this._updateMetrics(); | 9660 this._updateMetrics(); |
13528 | 9661 |
13529 // estimate new physical offset | |
13530 var estPhysicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; | 9662 var estPhysicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; |
13531 this._physicalTop = estPhysicalTop; | 9663 this._physicalTop = estPhysicalTop; |
13532 | 9664 |
13533 var currentTopItem = this._physicalStart; | 9665 var currentTopItem = this._physicalStart; |
13534 var currentVirtualItem = this._virtualStart; | 9666 var currentVirtualItem = this._virtualStart; |
13535 var targetOffsetTop = 0; | 9667 var targetOffsetTop = 0; |
13536 var hiddenContentSize = this._hiddenContentSize; | 9668 var hiddenContentSize = this._hiddenContentSize; |
13537 | 9669 |
13538 // scroll to the item as much as we can | |
13539 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { | 9670 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { |
13540 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); | 9671 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); |
13541 currentTopItem = (currentTopItem + 1) % this._physicalCount; | 9672 currentTopItem = (currentTopItem + 1) % this._physicalCount; |
13542 currentVirtualItem++; | 9673 currentVirtualItem++; |
13543 } | 9674 } |
13544 // update the scroller size | |
13545 this._updateScrollerSize(true); | 9675 this._updateScrollerSize(true); |
13546 // update the position of the items | |
13547 this._positionItems(); | 9676 this._positionItems(); |
13548 // set the new scroll position | |
13549 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); | 9677 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); |
13550 // increase the pool of physical items if needed | |
13551 this._increasePoolIfNeeded(); | 9678 this._increasePoolIfNeeded(); |
13552 // clear cached visible index | |
13553 this._firstVisibleIndexVal = null; | 9679 this._firstVisibleIndexVal = null; |
13554 this._lastVisibleIndexVal = null; | 9680 this._lastVisibleIndexVal = null; |
13555 }, | 9681 }, |
13556 | 9682 |
13557 /** | |
13558 * Reset the physical average and the average count. | |
13559 */ | |
13560 _resetAverage: function() { | 9683 _resetAverage: function() { |
13561 this._physicalAverage = 0; | 9684 this._physicalAverage = 0; |
13562 this._physicalAverageCount = 0; | 9685 this._physicalAverageCount = 0; |
13563 }, | 9686 }, |
13564 | 9687 |
13565 /** | |
13566 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` | |
13567 * when the element is resized. | |
13568 */ | |
13569 _resizeHandler: function() { | 9688 _resizeHandler: function() { |
13570 // iOS fires the resize event when the address bar slides up | |
13571 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100
) { | 9689 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100
) { |
13572 return; | 9690 return; |
13573 } | 9691 } |
13574 // In Desktop Safari 9.0.3, if the scroll bars are always shown, | |
13575 // changing the scroll position from a resize handler would result in | |
13576 // the scroll position being reset. Waiting 1ms fixes the issue. | |
13577 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { | 9692 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { |
13578 this.updateViewportBoundaries(); | 9693 this.updateViewportBoundaries(); |
13579 this._render(); | 9694 this._render(); |
13580 | 9695 |
13581 if (this._itemsRendered && this._physicalItems && this._isVisible) { | 9696 if (this._itemsRendered && this._physicalItems && this._isVisible) { |
13582 this._resetAverage(); | 9697 this._resetAverage(); |
13583 this.scrollToIndex(this.firstVisibleIndex); | 9698 this.scrollToIndex(this.firstVisibleIndex); |
13584 } | 9699 } |
13585 }.bind(this), 1)); | 9700 }.bind(this), 1)); |
13586 }, | 9701 }, |
13587 | 9702 |
13588 _getModelFromItem: function(item) { | 9703 _getModelFromItem: function(item) { |
13589 var key = this._collection.getKey(item); | 9704 var key = this._collection.getKey(item); |
13590 var pidx = this._physicalIndexForKey[key]; | 9705 var pidx = this._physicalIndexForKey[key]; |
13591 | 9706 |
13592 if (pidx != null) { | 9707 if (pidx != null) { |
13593 return this._physicalItems[pidx]._templateInstance; | 9708 return this._physicalItems[pidx]._templateInstance; |
13594 } | 9709 } |
13595 return null; | 9710 return null; |
13596 }, | 9711 }, |
13597 | 9712 |
13598 /** | |
13599 * Gets a valid item instance from its index or the object value. | |
13600 * | |
13601 * @param {(Object|number)} item The item object or its index | |
13602 */ | |
13603 _getNormalizedItem: function(item) { | 9713 _getNormalizedItem: function(item) { |
13604 if (this._collection.getKey(item) === undefined) { | 9714 if (this._collection.getKey(item) === undefined) { |
13605 if (typeof item === 'number') { | 9715 if (typeof item === 'number') { |
13606 item = this.items[item]; | 9716 item = this.items[item]; |
13607 if (!item) { | 9717 if (!item) { |
13608 throw new RangeError('<item> not found'); | 9718 throw new RangeError('<item> not found'); |
13609 } | 9719 } |
13610 return item; | 9720 return item; |
13611 } | 9721 } |
13612 throw new TypeError('<item> should be a valid item'); | 9722 throw new TypeError('<item> should be a valid item'); |
13613 } | 9723 } |
13614 return item; | 9724 return item; |
13615 }, | 9725 }, |
13616 | 9726 |
13617 /** | |
13618 * Select the list item at the given index. | |
13619 * | |
13620 * @method selectItem | |
13621 * @param {(Object|number)} item The item object or its index | |
13622 */ | |
13623 selectItem: function(item) { | 9727 selectItem: function(item) { |
13624 item = this._getNormalizedItem(item); | 9728 item = this._getNormalizedItem(item); |
13625 var model = this._getModelFromItem(item); | 9729 var model = this._getModelFromItem(item); |
13626 | 9730 |
13627 if (!this.multiSelection && this.selectedItem) { | 9731 if (!this.multiSelection && this.selectedItem) { |
13628 this.deselectItem(this.selectedItem); | 9732 this.deselectItem(this.selectedItem); |
13629 } | 9733 } |
13630 if (model) { | 9734 if (model) { |
13631 model[this.selectedAs] = true; | 9735 model[this.selectedAs] = true; |
13632 } | 9736 } |
13633 this.$.selector.select(item); | 9737 this.$.selector.select(item); |
13634 this.updateSizeForItem(item); | 9738 this.updateSizeForItem(item); |
13635 }, | 9739 }, |
13636 | 9740 |
13637 /** | |
13638 * Deselects the given item list if it is already selected. | |
13639 * | |
13640 | |
13641 * @method deselect | |
13642 * @param {(Object|number)} item The item object or its index | |
13643 */ | |
13644 deselectItem: function(item) { | 9741 deselectItem: function(item) { |
13645 item = this._getNormalizedItem(item); | 9742 item = this._getNormalizedItem(item); |
13646 var model = this._getModelFromItem(item); | 9743 var model = this._getModelFromItem(item); |
13647 | 9744 |
13648 if (model) { | 9745 if (model) { |
13649 model[this.selectedAs] = false; | 9746 model[this.selectedAs] = false; |
13650 } | 9747 } |
13651 this.$.selector.deselect(item); | 9748 this.$.selector.deselect(item); |
13652 this.updateSizeForItem(item); | 9749 this.updateSizeForItem(item); |
13653 }, | 9750 }, |
13654 | 9751 |
13655 /** | |
13656 * Select or deselect a given item depending on whether the item | |
13657 * has already been selected. | |
13658 * | |
13659 * @method toggleSelectionForItem | |
13660 * @param {(Object|number)} item The item object or its index | |
13661 */ | |
13662 toggleSelectionForItem: function(item) { | 9752 toggleSelectionForItem: function(item) { |
13663 item = this._getNormalizedItem(item); | 9753 item = this._getNormalizedItem(item); |
13664 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item
)) { | 9754 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item
)) { |
13665 this.deselectItem(item); | 9755 this.deselectItem(item); |
13666 } else { | 9756 } else { |
13667 this.selectItem(item); | 9757 this.selectItem(item); |
13668 } | 9758 } |
13669 }, | 9759 }, |
13670 | 9760 |
13671 /** | |
13672 * Clears the current selection state of the list. | |
13673 * | |
13674 * @method clearSelection | |
13675 */ | |
13676 clearSelection: function() { | 9761 clearSelection: function() { |
13677 function unselect(item) { | 9762 function unselect(item) { |
13678 var model = this._getModelFromItem(item); | 9763 var model = this._getModelFromItem(item); |
13679 if (model) { | 9764 if (model) { |
13680 model[this.selectedAs] = false; | 9765 model[this.selectedAs] = false; |
13681 } | 9766 } |
13682 } | 9767 } |
13683 | 9768 |
13684 if (Array.isArray(this.selectedItems)) { | 9769 if (Array.isArray(this.selectedItems)) { |
13685 this.selectedItems.forEach(unselect, this); | 9770 this.selectedItems.forEach(unselect, this); |
13686 } else if (this.selectedItem) { | 9771 } else if (this.selectedItem) { |
13687 unselect.call(this, this.selectedItem); | 9772 unselect.call(this, this.selectedItem); |
13688 } | 9773 } |
13689 | 9774 |
13690 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); | 9775 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); |
13691 }, | 9776 }, |
13692 | 9777 |
13693 /** | |
13694 * Add an event listener to `tap` if `selectionEnabled` is true, | |
13695 * it will remove the listener otherwise. | |
13696 */ | |
13697 _selectionEnabledChanged: function(selectionEnabled) { | 9778 _selectionEnabledChanged: function(selectionEnabled) { |
13698 var handler = selectionEnabled ? this.listen : this.unlisten; | 9779 var handler = selectionEnabled ? this.listen : this.unlisten; |
13699 handler.call(this, this, 'tap', '_selectionHandler'); | 9780 handler.call(this, this, 'tap', '_selectionHandler'); |
13700 }, | 9781 }, |
13701 | 9782 |
13702 /** | |
13703 * Select an item from an event object. | |
13704 */ | |
13705 _selectionHandler: function(e) { | 9783 _selectionHandler: function(e) { |
13706 var model = this.modelForElement(e.target); | 9784 var model = this.modelForElement(e.target); |
13707 if (!model) { | 9785 if (!model) { |
13708 return; | 9786 return; |
13709 } | 9787 } |
13710 var modelTabIndex, activeElTabIndex; | 9788 var modelTabIndex, activeElTabIndex; |
13711 var target = Polymer.dom(e).path[0]; | 9789 var target = Polymer.dom(e).path[0]; |
13712 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac
tiveElement; | 9790 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac
tiveElement; |
13713 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i
ndexAs])]; | 9791 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i
ndexAs])]; |
13714 // Safari does not focus certain form controls via mouse | |
13715 // https://bugs.webkit.org/show_bug.cgi?id=118043 | |
13716 if (target.localName === 'input' || | 9792 if (target.localName === 'input' || |
13717 target.localName === 'button' || | 9793 target.localName === 'button' || |
13718 target.localName === 'select') { | 9794 target.localName === 'select') { |
13719 return; | 9795 return; |
13720 } | 9796 } |
13721 // Set a temporary tabindex | |
13722 modelTabIndex = model.tabIndex; | 9797 modelTabIndex = model.tabIndex; |
13723 model.tabIndex = SECRET_TABINDEX; | 9798 model.tabIndex = SECRET_TABINDEX; |
13724 activeElTabIndex = activeEl ? activeEl.tabIndex : -1; | 9799 activeElTabIndex = activeEl ? activeEl.tabIndex : -1; |
13725 model.tabIndex = modelTabIndex; | 9800 model.tabIndex = modelTabIndex; |
13726 // Only select the item if the tap wasn't on a focusable child | |
13727 // or the element bound to `tabIndex` | |
13728 if (activeEl && physicalItem.contains(activeEl) && activeElTabIndex !== SE
CRET_TABINDEX) { | 9801 if (activeEl && physicalItem.contains(activeEl) && activeElTabIndex !== SE
CRET_TABINDEX) { |
13729 return; | 9802 return; |
13730 } | 9803 } |
13731 this.toggleSelectionForItem(model[this.as]); | 9804 this.toggleSelectionForItem(model[this.as]); |
13732 }, | 9805 }, |
13733 | 9806 |
13734 _multiSelectionChanged: function(multiSelection) { | 9807 _multiSelectionChanged: function(multiSelection) { |
13735 this.clearSelection(); | 9808 this.clearSelection(); |
13736 this.$.selector.multi = multiSelection; | 9809 this.$.selector.multi = multiSelection; |
13737 }, | 9810 }, |
13738 | 9811 |
13739 /** | |
13740 * Updates the size of an item. | |
13741 * | |
13742 * @method updateSizeForItem | |
13743 * @param {(Object|number)} item The item object or its index | |
13744 */ | |
13745 updateSizeForItem: function(item) { | 9812 updateSizeForItem: function(item) { |
13746 item = this._getNormalizedItem(item); | 9813 item = this._getNormalizedItem(item); |
13747 var key = this._collection.getKey(item); | 9814 var key = this._collection.getKey(item); |
13748 var pidx = this._physicalIndexForKey[key]; | 9815 var pidx = this._physicalIndexForKey[key]; |
13749 | 9816 |
13750 if (pidx != null) { | 9817 if (pidx != null) { |
13751 this._updateMetrics([pidx]); | 9818 this._updateMetrics([pidx]); |
13752 this._positionItems(); | 9819 this._positionItems(); |
13753 } | 9820 } |
13754 }, | 9821 }, |
13755 | 9822 |
13756 /** | |
13757 * Creates a temporary backfill item in the rendered pool of physical items | |
13758 * to replace the main focused item. The focused item has tabIndex = 0 | |
13759 * and might be currently focused by the user. | |
13760 * | |
13761 * This dynamic replacement helps to preserve the focus state. | |
13762 */ | |
13763 _manageFocus: function() { | 9823 _manageFocus: function() { |
13764 var fidx = this._focusedIndex; | 9824 var fidx = this._focusedIndex; |
13765 | 9825 |
13766 if (fidx >= 0 && fidx < this._virtualCount) { | 9826 if (fidx >= 0 && fidx < this._virtualCount) { |
13767 // if it's a valid index, check if that index is rendered | |
13768 // in a physical item. | |
13769 if (this._isIndexRendered(fidx)) { | 9827 if (this._isIndexRendered(fidx)) { |
13770 this._restoreFocusedItem(); | 9828 this._restoreFocusedItem(); |
13771 } else { | 9829 } else { |
13772 this._createFocusBackfillItem(); | 9830 this._createFocusBackfillItem(); |
13773 } | 9831 } |
13774 } else if (this._virtualCount > 0 && this._physicalCount > 0) { | 9832 } else if (this._virtualCount > 0 && this._physicalCount > 0) { |
13775 // otherwise, assign the initial focused index. | |
13776 this._focusedIndex = this._virtualStart; | 9833 this._focusedIndex = this._virtualStart; |
13777 this._focusedItem = this._physicalItems[this._physicalStart]; | 9834 this._focusedItem = this._physicalItems[this._physicalStart]; |
13778 } | 9835 } |
13779 }, | 9836 }, |
13780 | 9837 |
13781 _isIndexRendered: function(idx) { | 9838 _isIndexRendered: function(idx) { |
13782 return idx >= this._virtualStart && idx <= this._virtualEnd; | 9839 return idx >= this._virtualStart && idx <= this._virtualEnd; |
13783 }, | 9840 }, |
13784 | 9841 |
13785 _isIndexVisible: function(idx) { | 9842 _isIndexVisible: function(idx) { |
13786 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex; | 9843 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex; |
13787 }, | 9844 }, |
13788 | 9845 |
13789 _getPhysicalIndex: function(idx) { | 9846 _getPhysicalIndex: function(idx) { |
13790 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz
edItem(idx))]; | 9847 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz
edItem(idx))]; |
13791 }, | 9848 }, |
13792 | 9849 |
13793 _focusPhysicalItem: function(idx) { | 9850 _focusPhysicalItem: function(idx) { |
13794 if (idx < 0 || idx >= this._virtualCount) { | 9851 if (idx < 0 || idx >= this._virtualCount) { |
13795 return; | 9852 return; |
13796 } | 9853 } |
13797 this._restoreFocusedItem(); | 9854 this._restoreFocusedItem(); |
13798 // scroll to index to make sure it's rendered | |
13799 if (!this._isIndexRendered(idx)) { | 9855 if (!this._isIndexRendered(idx)) { |
13800 this.scrollToIndex(idx); | 9856 this.scrollToIndex(idx); |
13801 } | 9857 } |
13802 | 9858 |
13803 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)]; | 9859 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)]; |
13804 var model = physicalItem._templateInstance; | 9860 var model = physicalItem._templateInstance; |
13805 var focusable; | 9861 var focusable; |
13806 | 9862 |
13807 // set a secret tab index | |
13808 model.tabIndex = SECRET_TABINDEX; | 9863 model.tabIndex = SECRET_TABINDEX; |
13809 // check if focusable element is the physical item | |
13810 if (physicalItem.tabIndex === SECRET_TABINDEX) { | 9864 if (physicalItem.tabIndex === SECRET_TABINDEX) { |
13811 focusable = physicalItem; | 9865 focusable = physicalItem; |
13812 } | 9866 } |
13813 // search for the element which tabindex is bound to the secret tab index | |
13814 if (!focusable) { | 9867 if (!focusable) { |
13815 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR
ET_TABINDEX + '"]'); | 9868 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR
ET_TABINDEX + '"]'); |
13816 } | 9869 } |
13817 // restore the tab index | |
13818 model.tabIndex = 0; | 9870 model.tabIndex = 0; |
13819 // focus the focusable element | |
13820 this._focusedIndex = idx; | 9871 this._focusedIndex = idx; |
13821 focusable && focusable.focus(); | 9872 focusable && focusable.focus(); |
13822 }, | 9873 }, |
13823 | 9874 |
13824 _removeFocusedItem: function() { | 9875 _removeFocusedItem: function() { |
13825 if (this._offscreenFocusedItem) { | 9876 if (this._offscreenFocusedItem) { |
13826 Polymer.dom(this).removeChild(this._offscreenFocusedItem); | 9877 Polymer.dom(this).removeChild(this._offscreenFocusedItem); |
13827 } | 9878 } |
13828 this._offscreenFocusedItem = null; | 9879 this._offscreenFocusedItem = null; |
13829 this._focusBackfillItem = null; | 9880 this._focusBackfillItem = null; |
13830 this._focusedItem = null; | 9881 this._focusedItem = null; |
13831 this._focusedIndex = -1; | 9882 this._focusedIndex = -1; |
13832 }, | 9883 }, |
13833 | 9884 |
13834 _createFocusBackfillItem: function() { | 9885 _createFocusBackfillItem: function() { |
13835 var pidx, fidx = this._focusedIndex; | 9886 var pidx, fidx = this._focusedIndex; |
13836 if (this._offscreenFocusedItem || fidx < 0) { | 9887 if (this._offscreenFocusedItem || fidx < 0) { |
13837 return; | 9888 return; |
13838 } | 9889 } |
13839 if (!this._focusBackfillItem) { | 9890 if (!this._focusBackfillItem) { |
13840 // create a physical item, so that it backfills the focused item. | |
13841 var stampedTemplate = this.stamp(null); | 9891 var stampedTemplate = this.stamp(null); |
13842 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); | 9892 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); |
13843 Polymer.dom(this).appendChild(stampedTemplate.root); | 9893 Polymer.dom(this).appendChild(stampedTemplate.root); |
13844 } | 9894 } |
13845 // get the physical index for the focused index | |
13846 pidx = this._getPhysicalIndex(fidx); | 9895 pidx = this._getPhysicalIndex(fidx); |
13847 | 9896 |
13848 if (pidx != null) { | 9897 if (pidx != null) { |
13849 // set the offcreen focused physical item | |
13850 this._offscreenFocusedItem = this._physicalItems[pidx]; | 9898 this._offscreenFocusedItem = this._physicalItems[pidx]; |
13851 // backfill the focused physical item | |
13852 this._physicalItems[pidx] = this._focusBackfillItem; | 9899 this._physicalItems[pidx] = this._focusBackfillItem; |
13853 // hide the focused physical | |
13854 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); | 9900 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); |
13855 } | 9901 } |
13856 }, | 9902 }, |
13857 | 9903 |
13858 _restoreFocusedItem: function() { | 9904 _restoreFocusedItem: function() { |
13859 var pidx, fidx = this._focusedIndex; | 9905 var pidx, fidx = this._focusedIndex; |
13860 | 9906 |
13861 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { | 9907 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { |
13862 return; | 9908 return; |
13863 } | 9909 } |
13864 // assign models to the focused index | |
13865 this._assignModels(); | 9910 this._assignModels(); |
13866 // get the new physical index for the focused index | |
13867 pidx = this._getPhysicalIndex(fidx); | 9911 pidx = this._getPhysicalIndex(fidx); |
13868 | 9912 |
13869 if (pidx != null) { | 9913 if (pidx != null) { |
13870 // flip the focus backfill | |
13871 this._focusBackfillItem = this._physicalItems[pidx]; | 9914 this._focusBackfillItem = this._physicalItems[pidx]; |
13872 // restore the focused physical item | |
13873 this._physicalItems[pidx] = this._offscreenFocusedItem; | 9915 this._physicalItems[pidx] = this._offscreenFocusedItem; |
13874 // reset the offscreen focused item | |
13875 this._offscreenFocusedItem = null; | 9916 this._offscreenFocusedItem = null; |
13876 // hide the physical item that backfills | |
13877 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); | 9917 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); |
13878 } | 9918 } |
13879 }, | 9919 }, |
13880 | 9920 |
13881 _didFocus: function(e) { | 9921 _didFocus: function(e) { |
13882 var targetModel = this.modelForElement(e.target); | 9922 var targetModel = this.modelForElement(e.target); |
13883 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance
: null; | 9923 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance
: null; |
13884 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; | 9924 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; |
13885 var fidx = this._focusedIndex; | 9925 var fidx = this._focusedIndex; |
13886 | 9926 |
13887 if (!targetModel || !focusedModel) { | 9927 if (!targetModel || !focusedModel) { |
13888 return; | 9928 return; |
13889 } | 9929 } |
13890 if (focusedModel === targetModel) { | 9930 if (focusedModel === targetModel) { |
13891 // if the user focused the same item, then bring it into view if it's no
t visible | |
13892 if (!this._isIndexVisible(fidx)) { | 9931 if (!this._isIndexVisible(fidx)) { |
13893 this.scrollToIndex(fidx); | 9932 this.scrollToIndex(fidx); |
13894 } | 9933 } |
13895 } else { | 9934 } else { |
13896 this._restoreFocusedItem(); | 9935 this._restoreFocusedItem(); |
13897 // restore tabIndex for the currently focused item | |
13898 focusedModel.tabIndex = -1; | 9936 focusedModel.tabIndex = -1; |
13899 // set the tabIndex for the next focused item | |
13900 targetModel.tabIndex = 0; | 9937 targetModel.tabIndex = 0; |
13901 fidx = targetModel[this.indexAs]; | 9938 fidx = targetModel[this.indexAs]; |
13902 this._focusedIndex = fidx; | 9939 this._focusedIndex = fidx; |
13903 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)]; | 9940 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)]; |
13904 | 9941 |
13905 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) { | 9942 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) { |
13906 this._update(); | 9943 this._update(); |
13907 } | 9944 } |
13908 } | 9945 } |
13909 }, | 9946 }, |
13910 | 9947 |
13911 _didMoveUp: function() { | 9948 _didMoveUp: function() { |
13912 this._focusPhysicalItem(this._focusedIndex - 1); | 9949 this._focusPhysicalItem(this._focusedIndex - 1); |
13913 }, | 9950 }, |
13914 | 9951 |
13915 _didMoveDown: function(e) { | 9952 _didMoveDown: function(e) { |
13916 // disable scroll when pressing the down key | |
13917 e.detail.keyboardEvent.preventDefault(); | 9953 e.detail.keyboardEvent.preventDefault(); |
13918 this._focusPhysicalItem(this._focusedIndex + 1); | 9954 this._focusPhysicalItem(this._focusedIndex + 1); |
13919 }, | 9955 }, |
13920 | 9956 |
13921 _didEnter: function(e) { | 9957 _didEnter: function(e) { |
13922 this._focusPhysicalItem(this._focusedIndex); | 9958 this._focusPhysicalItem(this._focusedIndex); |
13923 this._selectionHandler(e.detail.keyboardEvent); | 9959 this._selectionHandler(e.detail.keyboardEvent); |
13924 } | 9960 } |
13925 }); | 9961 }); |
13926 | 9962 |
13927 })(); | 9963 })(); |
13928 Polymer({ | 9964 Polymer({ |
13929 | 9965 |
13930 is: 'iron-scroll-threshold', | 9966 is: 'iron-scroll-threshold', |
13931 | 9967 |
13932 properties: { | 9968 properties: { |
13933 | 9969 |
13934 /** | |
13935 * Distance from the top (or left, for horizontal) bound of the scroller | |
13936 * where the "upper trigger" will fire. | |
13937 */ | |
13938 upperThreshold: { | 9970 upperThreshold: { |
13939 type: Number, | 9971 type: Number, |
13940 value: 100 | 9972 value: 100 |
13941 }, | 9973 }, |
13942 | 9974 |
13943 /** | |
13944 * Distance from the bottom (or right, for horizontal) bound of the scroll
er | |
13945 * where the "lower trigger" will fire. | |
13946 */ | |
13947 lowerThreshold: { | 9975 lowerThreshold: { |
13948 type: Number, | 9976 type: Number, |
13949 value: 100 | 9977 value: 100 |
13950 }, | 9978 }, |
13951 | 9979 |
13952 /** | |
13953 * Read-only value that tracks the triggered state of the upper threshold. | |
13954 */ | |
13955 upperTriggered: { | 9980 upperTriggered: { |
13956 type: Boolean, | 9981 type: Boolean, |
13957 value: false, | 9982 value: false, |
13958 notify: true, | 9983 notify: true, |
13959 readOnly: true | 9984 readOnly: true |
13960 }, | 9985 }, |
13961 | 9986 |
13962 /** | |
13963 * Read-only value that tracks the triggered state of the lower threshold. | |
13964 */ | |
13965 lowerTriggered: { | 9987 lowerTriggered: { |
13966 type: Boolean, | 9988 type: Boolean, |
13967 value: false, | 9989 value: false, |
13968 notify: true, | 9990 notify: true, |
13969 readOnly: true | 9991 readOnly: true |
13970 }, | 9992 }, |
13971 | 9993 |
13972 /** | |
13973 * True if the orientation of the scroller is horizontal. | |
13974 */ | |
13975 horizontal: { | 9994 horizontal: { |
13976 type: Boolean, | 9995 type: Boolean, |
13977 value: false | 9996 value: false |
13978 } | 9997 } |
13979 }, | 9998 }, |
13980 | 9999 |
13981 behaviors: [ | 10000 behaviors: [ |
13982 Polymer.IronScrollTargetBehavior | 10001 Polymer.IronScrollTargetBehavior |
13983 ], | 10002 ], |
13984 | 10003 |
13985 observers: [ | 10004 observers: [ |
13986 '_setOverflow(scrollTarget)', | 10005 '_setOverflow(scrollTarget)', |
13987 '_initCheck(horizontal, isAttached)' | 10006 '_initCheck(horizontal, isAttached)' |
13988 ], | 10007 ], |
13989 | 10008 |
13990 get _defaultScrollTarget() { | 10009 get _defaultScrollTarget() { |
13991 return this; | 10010 return this; |
13992 }, | 10011 }, |
13993 | 10012 |
13994 _setOverflow: function(scrollTarget) { | 10013 _setOverflow: function(scrollTarget) { |
13995 this.style.overflow = scrollTarget === this ? 'auto' : ''; | 10014 this.style.overflow = scrollTarget === this ? 'auto' : ''; |
13996 }, | 10015 }, |
13997 | 10016 |
13998 _scrollHandler: function() { | 10017 _scrollHandler: function() { |
13999 // throttle the work on the scroll event | |
14000 var THROTTLE_THRESHOLD = 200; | 10018 var THROTTLE_THRESHOLD = 200; |
14001 if (!this.isDebouncerActive('_checkTheshold')) { | 10019 if (!this.isDebouncerActive('_checkTheshold')) { |
14002 this.debounce('_checkTheshold', function() { | 10020 this.debounce('_checkTheshold', function() { |
14003 this.checkScrollThesholds(); | 10021 this.checkScrollThesholds(); |
14004 }, THROTTLE_THRESHOLD); | 10022 }, THROTTLE_THRESHOLD); |
14005 } | 10023 } |
14006 }, | 10024 }, |
14007 | 10025 |
14008 _initCheck: function(horizontal, isAttached) { | 10026 _initCheck: function(horizontal, isAttached) { |
14009 if (isAttached) { | 10027 if (isAttached) { |
14010 this.debounce('_init', function() { | 10028 this.debounce('_init', function() { |
14011 this.clearTriggers(); | 10029 this.clearTriggers(); |
14012 this.checkScrollThesholds(); | 10030 this.checkScrollThesholds(); |
14013 }); | 10031 }); |
14014 } | 10032 } |
14015 }, | 10033 }, |
14016 | 10034 |
14017 /** | |
14018 * Checks the scroll thresholds. | |
14019 * This method is automatically called by iron-scroll-threshold. | |
14020 * | |
14021 * @method checkScrollThesholds | |
14022 */ | |
14023 checkScrollThesholds: function() { | 10035 checkScrollThesholds: function() { |
14024 if (!this.scrollTarget || (this.lowerTriggered && this.upperTriggered)) { | 10036 if (!this.scrollTarget || (this.lowerTriggered && this.upperTriggered)) { |
14025 return; | 10037 return; |
14026 } | 10038 } |
14027 var upperScrollValue = this.horizontal ? this._scrollLeft : this._scrollTo
p; | 10039 var upperScrollValue = this.horizontal ? this._scrollLeft : this._scrollTo
p; |
14028 var lowerScrollValue = this.horizontal ? | 10040 var lowerScrollValue = this.horizontal ? |
14029 this.scrollTarget.scrollWidth - this._scrollTargetWidth - this._scroll
Left : | 10041 this.scrollTarget.scrollWidth - this._scrollTargetWidth - this._scroll
Left : |
14030 this.scrollTarget.scrollHeight - this._scrollTargetHeight - this._
scrollTop; | 10042 this.scrollTarget.scrollHeight - this._scrollTargetHeight - this._
scrollTop; |
14031 | 10043 |
14032 // Detect upper threshold | |
14033 if (upperScrollValue <= this.upperThreshold && !this.upperTriggered) { | 10044 if (upperScrollValue <= this.upperThreshold && !this.upperTriggered) { |
14034 this._setUpperTriggered(true); | 10045 this._setUpperTriggered(true); |
14035 this.fire('upper-threshold'); | 10046 this.fire('upper-threshold'); |
14036 } | 10047 } |
14037 // Detect lower threshold | |
14038 if (lowerScrollValue <= this.lowerThreshold && !this.lowerTriggered) { | 10048 if (lowerScrollValue <= this.lowerThreshold && !this.lowerTriggered) { |
14039 this._setLowerTriggered(true); | 10049 this._setLowerTriggered(true); |
14040 this.fire('lower-threshold'); | 10050 this.fire('lower-threshold'); |
14041 } | 10051 } |
14042 }, | 10052 }, |
14043 | 10053 |
14044 /** | |
14045 * Clear the upper and lower threshold states. | |
14046 * | |
14047 * @method clearTriggers | |
14048 */ | |
14049 clearTriggers: function() { | 10054 clearTriggers: function() { |
14050 this._setUpperTriggered(false); | 10055 this._setUpperTriggered(false); |
14051 this._setLowerTriggered(false); | 10056 this._setLowerTriggered(false); |
14052 } | 10057 } |
14053 | 10058 |
14054 /** | |
14055 * Fires when the lower threshold has been reached. | |
14056 * | |
14057 * @event lower-threshold | |
14058 */ | |
14059 | 10059 |
14060 /** | |
14061 * Fires when the upper threshold has been reached. | |
14062 * | |
14063 * @event upper-threshold | |
14064 */ | |
14065 | 10060 |
14066 }); | 10061 }); |
14067 // Copyright 2015 The Chromium Authors. All rights reserved. | 10062 // Copyright 2015 The Chromium Authors. All rights reserved. |
14068 // Use of this source code is governed by a BSD-style license that can be | 10063 // Use of this source code is governed by a BSD-style license that can be |
14069 // found in the LICENSE file. | 10064 // found in the LICENSE file. |
14070 | 10065 |
14071 Polymer({ | 10066 Polymer({ |
14072 is: 'history-list', | 10067 is: 'history-list', |
14073 | 10068 |
14074 behaviors: [HistoryListBehavior], | 10069 behaviors: [HistoryListBehavior], |
14075 | 10070 |
14076 properties: { | 10071 properties: { |
14077 // The search term for the current query. Set when the query returns. | |
14078 searchedTerm: { | 10072 searchedTerm: { |
14079 type: String, | 10073 type: String, |
14080 value: '', | 10074 value: '', |
14081 }, | 10075 }, |
14082 | 10076 |
14083 lastSearchedTerm_: String, | 10077 lastSearchedTerm_: String, |
14084 | 10078 |
14085 querying: Boolean, | 10079 querying: Boolean, |
14086 | 10080 |
14087 // An array of history entries in reverse chronological order. | |
14088 historyData_: Array, | 10081 historyData_: Array, |
14089 | 10082 |
14090 resultLoadingDisabled_: { | 10083 resultLoadingDisabled_: { |
14091 type: Boolean, | 10084 type: Boolean, |
14092 value: false, | 10085 value: false, |
14093 }, | 10086 }, |
14094 }, | 10087 }, |
14095 | 10088 |
14096 listeners: { | 10089 listeners: { |
14097 'scroll': 'notifyListScroll_', | 10090 'scroll': 'notifyListScroll_', |
14098 'remove-bookmark-stars': 'removeBookmarkStars_', | 10091 'remove-bookmark-stars': 'removeBookmarkStars_', |
14099 }, | 10092 }, |
14100 | 10093 |
14101 /** @override */ | 10094 /** @override */ |
14102 attached: function() { | 10095 attached: function() { |
14103 // It is possible (eg, when middle clicking the reload button) for all other | |
14104 // resize events to fire before the list is attached and can be measured. | |
14105 // Adding another resize here ensures it will get sized correctly. | |
14106 /** @type {IronListElement} */(this.$['infinite-list']).notifyResize(); | 10096 /** @type {IronListElement} */(this.$['infinite-list']).notifyResize(); |
14107 this.$['infinite-list'].scrollTarget = this; | 10097 this.$['infinite-list'].scrollTarget = this; |
14108 this.$['scroll-threshold'].scrollTarget = this; | 10098 this.$['scroll-threshold'].scrollTarget = this; |
14109 }, | 10099 }, |
14110 | 10100 |
14111 /** | |
14112 * Remove bookmark star for history items with matching URLs. | |
14113 * @param {{detail: !string}} e | |
14114 * @private | |
14115 */ | |
14116 removeBookmarkStars_: function(e) { | 10101 removeBookmarkStars_: function(e) { |
14117 var url = e.detail; | 10102 var url = e.detail; |
14118 | 10103 |
14119 if (this.historyData_ === undefined) | 10104 if (this.historyData_ === undefined) |
14120 return; | 10105 return; |
14121 | 10106 |
14122 for (var i = 0; i < this.historyData_.length; i++) { | 10107 for (var i = 0; i < this.historyData_.length; i++) { |
14123 if (this.historyData_[i].url == url) | 10108 if (this.historyData_[i].url == url) |
14124 this.set('historyData_.' + i + '.starred', false); | 10109 this.set('historyData_.' + i + '.starred', false); |
14125 } | 10110 } |
14126 }, | 10111 }, |
14127 | 10112 |
14128 /** | |
14129 * Disables history result loading when there are no more history results. | |
14130 */ | |
14131 disableResultLoading: function() { | 10113 disableResultLoading: function() { |
14132 this.resultLoadingDisabled_ = true; | 10114 this.resultLoadingDisabled_ = true; |
14133 }, | 10115 }, |
14134 | 10116 |
14135 /** | |
14136 * Adds the newly updated history results into historyData_. Adds new fields | |
14137 * for each result. | |
14138 * @param {!Array<!HistoryEntry>} historyResults The new history results. | |
14139 */ | |
14140 addNewResults: function(historyResults) { | 10117 addNewResults: function(historyResults) { |
14141 var results = historyResults.slice(); | 10118 var results = historyResults.slice(); |
14142 /** @type {IronScrollThresholdElement} */(this.$['scroll-threshold']) | 10119 /** @type {IronScrollThresholdElement} */(this.$['scroll-threshold']) |
14143 .clearTriggers(); | 10120 .clearTriggers(); |
14144 | 10121 |
14145 if (this.lastSearchedTerm_ != this.searchedTerm) { | 10122 if (this.lastSearchedTerm_ != this.searchedTerm) { |
14146 this.resultLoadingDisabled_ = false; | 10123 this.resultLoadingDisabled_ = false; |
14147 if (this.historyData_) | 10124 if (this.historyData_) |
14148 this.splice('historyData_', 0, this.historyData_.length); | 10125 this.splice('historyData_', 0, this.historyData_.length); |
14149 this.fire('unselect-all'); | 10126 this.fire('unselect-all'); |
14150 this.lastSearchedTerm_ = this.searchedTerm; | 10127 this.lastSearchedTerm_ = this.searchedTerm; |
14151 } | 10128 } |
14152 | 10129 |
14153 if (this.historyData_) { | 10130 if (this.historyData_) { |
14154 // If we have previously received data, push the new items onto the | |
14155 // existing array. | |
14156 results.unshift('historyData_'); | 10131 results.unshift('historyData_'); |
14157 this.push.apply(this, results); | 10132 this.push.apply(this, results); |
14158 } else { | 10133 } else { |
14159 // The first time we receive data, use set() to ensure the iron-list is | |
14160 // initialized correctly. | |
14161 this.set('historyData_', results); | 10134 this.set('historyData_', results); |
14162 } | 10135 } |
14163 }, | 10136 }, |
14164 | 10137 |
14165 /** | |
14166 * Called when the page is scrolled to near the bottom of the list. | |
14167 * @private | |
14168 */ | |
14169 loadMoreData_: function() { | 10138 loadMoreData_: function() { |
14170 if (this.resultLoadingDisabled_ || this.querying) | 10139 if (this.resultLoadingDisabled_ || this.querying) |
14171 return; | 10140 return; |
14172 | 10141 |
14173 this.fire('load-more-history'); | 10142 this.fire('load-more-history'); |
14174 }, | 10143 }, |
14175 | 10144 |
14176 /** | |
14177 * Check whether the time difference between the given history item and the | |
14178 * next one is large enough for a spacer to be required. | |
14179 * @param {HistoryEntry} item | |
14180 * @param {number} index The index of |item| in |historyData_|. | |
14181 * @param {number} length The length of |historyData_|. | |
14182 * @return {boolean} Whether or not time gap separator is required. | |
14183 * @private | |
14184 */ | |
14185 needsTimeGap_: function(item, index, length) { | 10145 needsTimeGap_: function(item, index, length) { |
14186 return md_history.HistoryItem.needsTimeGap( | 10146 return md_history.HistoryItem.needsTimeGap( |
14187 this.historyData_, index, this.searchedTerm); | 10147 this.historyData_, index, this.searchedTerm); |
14188 }, | 10148 }, |
14189 | 10149 |
14190 /** | |
14191 * True if the given item is the beginning of a new card. | |
14192 * @param {HistoryEntry} item | |
14193 * @param {number} i Index of |item| within |historyData_|. | |
14194 * @param {number} length | |
14195 * @return {boolean} | |
14196 * @private | |
14197 */ | |
14198 isCardStart_: function(item, i, length) { | 10150 isCardStart_: function(item, i, length) { |
14199 if (length == 0 || i > length - 1) | 10151 if (length == 0 || i > length - 1) |
14200 return false; | 10152 return false; |
14201 return i == 0 || | 10153 return i == 0 || |
14202 this.historyData_[i].dateRelativeDay != | 10154 this.historyData_[i].dateRelativeDay != |
14203 this.historyData_[i - 1].dateRelativeDay; | 10155 this.historyData_[i - 1].dateRelativeDay; |
14204 }, | 10156 }, |
14205 | 10157 |
14206 /** | |
14207 * True if the given item is the end of a card. | |
14208 * @param {HistoryEntry} item | |
14209 * @param {number} i Index of |item| within |historyData_|. | |
14210 * @param {number} length | |
14211 * @return {boolean} | |
14212 * @private | |
14213 */ | |
14214 isCardEnd_: function(item, i, length) { | 10158 isCardEnd_: function(item, i, length) { |
14215 if (length == 0 || i > length - 1) | 10159 if (length == 0 || i > length - 1) |
14216 return false; | 10160 return false; |
14217 return i == length - 1 || | 10161 return i == length - 1 || |
14218 this.historyData_[i].dateRelativeDay != | 10162 this.historyData_[i].dateRelativeDay != |
14219 this.historyData_[i + 1].dateRelativeDay; | 10163 this.historyData_[i + 1].dateRelativeDay; |
14220 }, | 10164 }, |
14221 | 10165 |
14222 /** | |
14223 * @param {number} index | |
14224 * @return {boolean} | |
14225 * @private | |
14226 */ | |
14227 isFirstItem_: function(index) { | 10166 isFirstItem_: function(index) { |
14228 return index == 0; | 10167 return index == 0; |
14229 }, | 10168 }, |
14230 | 10169 |
14231 /** | |
14232 * @private | |
14233 */ | |
14234 notifyListScroll_: function() { | 10170 notifyListScroll_: function() { |
14235 this.fire('history-list-scrolled'); | 10171 this.fire('history-list-scrolled'); |
14236 }, | 10172 }, |
14237 | 10173 |
14238 /** | |
14239 * @param {number} index | |
14240 * @return {string} | |
14241 * @private | |
14242 */ | |
14243 pathForItem_: function(index) { | 10174 pathForItem_: function(index) { |
14244 return 'historyData_.' + index; | 10175 return 'historyData_.' + index; |
14245 }, | 10176 }, |
14246 }); | 10177 }); |
14247 // Copyright 2016 The Chromium Authors. All rights reserved. | 10178 // Copyright 2016 The Chromium Authors. All rights reserved. |
14248 // Use of this source code is governed by a BSD-style license that can be | 10179 // Use of this source code is governed by a BSD-style license that can be |
14249 // found in the LICENSE file. | 10180 // found in the LICENSE file. |
14250 | 10181 |
14251 /** | |
14252 * @fileoverview | |
14253 * history-lazy-render is a simple variant of dom-if designed for lazy rendering | |
14254 * of elements that are accessed imperatively. | |
14255 * Usage: | |
14256 * <template is="history-lazy-render" id="menu"> | |
14257 * <heavy-menu></heavy-menu> | |
14258 * </template> | |
14259 * | |
14260 * this.$.menu.get().then(function(menu) { | |
14261 * menu.show(); | |
14262 * }); | |
14263 */ | |
14264 | 10182 |
14265 Polymer({ | 10183 Polymer({ |
14266 is: 'history-lazy-render', | 10184 is: 'history-lazy-render', |
14267 extends: 'template', | 10185 extends: 'template', |
14268 | 10186 |
14269 behaviors: [ | 10187 behaviors: [ |
14270 Polymer.Templatizer | 10188 Polymer.Templatizer |
14271 ], | 10189 ], |
14272 | 10190 |
14273 /** @private {Promise<Element>} */ | 10191 /** @private {Promise<Element>} */ |
14274 _renderPromise: null, | 10192 _renderPromise: null, |
14275 | 10193 |
14276 /** @private {TemplateInstance} */ | 10194 /** @private {TemplateInstance} */ |
14277 _instance: null, | 10195 _instance: null, |
14278 | 10196 |
14279 /** | |
14280 * Stamp the template into the DOM tree asynchronously | |
14281 * @return {Promise<Element>} Promise which resolves when the template has | |
14282 * been stamped. | |
14283 */ | |
14284 get: function() { | 10197 get: function() { |
14285 if (!this._renderPromise) { | 10198 if (!this._renderPromise) { |
14286 this._renderPromise = new Promise(function(resolve) { | 10199 this._renderPromise = new Promise(function(resolve) { |
14287 this._debounceTemplate(function() { | 10200 this._debounceTemplate(function() { |
14288 this._render(); | 10201 this._render(); |
14289 this._renderPromise = null; | 10202 this._renderPromise = null; |
14290 resolve(this.getIfExists()); | 10203 resolve(this.getIfExists()); |
14291 }.bind(this)); | 10204 }.bind(this)); |
14292 }.bind(this)); | 10205 }.bind(this)); |
14293 } | 10206 } |
14294 return this._renderPromise; | 10207 return this._renderPromise; |
14295 }, | 10208 }, |
14296 | 10209 |
14297 /** | |
14298 * @return {?Element} The element contained in the template, if it has | |
14299 * already been stamped. | |
14300 */ | |
14301 getIfExists: function() { | 10210 getIfExists: function() { |
14302 if (this._instance) { | 10211 if (this._instance) { |
14303 var children = this._instance._children; | 10212 var children = this._instance._children; |
14304 | 10213 |
14305 for (var i = 0; i < children.length; i++) { | 10214 for (var i = 0; i < children.length; i++) { |
14306 if (children[i].nodeType == Node.ELEMENT_NODE) | 10215 if (children[i].nodeType == Node.ELEMENT_NODE) |
14307 return children[i]; | 10216 return children[i]; |
14308 } | 10217 } |
14309 } | 10218 } |
14310 return null; | 10219 return null; |
14311 }, | 10220 }, |
14312 | 10221 |
14313 _render: function() { | 10222 _render: function() { |
14314 if (!this.ctor) | 10223 if (!this.ctor) |
14315 this.templatize(this); | 10224 this.templatize(this); |
14316 var parentNode = this.parentNode; | 10225 var parentNode = this.parentNode; |
14317 if (parentNode && !this._instance) { | 10226 if (parentNode && !this._instance) { |
14318 this._instance = /** @type {TemplateInstance} */(this.stamp({})); | 10227 this._instance = /** @type {TemplateInstance} */(this.stamp({})); |
14319 var root = this._instance.root; | 10228 var root = this._instance.root; |
14320 parentNode.insertBefore(root, this); | 10229 parentNode.insertBefore(root, this); |
14321 } | 10230 } |
14322 }, | 10231 }, |
14323 | 10232 |
14324 /** | |
14325 * @param {string} prop | |
14326 * @param {Object} value | |
14327 */ | |
14328 _forwardParentProp: function(prop, value) { | 10233 _forwardParentProp: function(prop, value) { |
14329 if (this._instance) | 10234 if (this._instance) |
14330 this._instance.__setProperty(prop, value, true); | 10235 this._instance.__setProperty(prop, value, true); |
14331 }, | 10236 }, |
14332 | 10237 |
14333 /** | |
14334 * @param {string} path | |
14335 * @param {Object} value | |
14336 */ | |
14337 _forwardParentPath: function(path, value) { | 10238 _forwardParentPath: function(path, value) { |
14338 if (this._instance) | 10239 if (this._instance) |
14339 this._instance._notifyPath(path, value, true); | 10240 this._instance._notifyPath(path, value, true); |
14340 } | 10241 } |
14341 }); | 10242 }); |
14342 // Copyright 2016 The Chromium Authors. All rights reserved. | 10243 // Copyright 2016 The Chromium Authors. All rights reserved. |
14343 // Use of this source code is governed by a BSD-style license that can be | 10244 // Use of this source code is governed by a BSD-style license that can be |
14344 // found in the LICENSE file. | 10245 // found in the LICENSE file. |
14345 | 10246 |
14346 Polymer({ | 10247 Polymer({ |
14347 is: 'history-list-container', | 10248 is: 'history-list-container', |
14348 | 10249 |
14349 properties: { | 10250 properties: { |
14350 // The path of the currently selected page. | |
14351 selectedPage_: String, | 10251 selectedPage_: String, |
14352 | 10252 |
14353 // Whether domain-grouped history is enabled. | |
14354 grouped: Boolean, | 10253 grouped: Boolean, |
14355 | 10254 |
14356 /** @type {!QueryState} */ | 10255 /** @type {!QueryState} */ |
14357 queryState: Object, | 10256 queryState: Object, |
14358 | 10257 |
14359 /** @type {!QueryResult} */ | 10258 /** @type {!QueryResult} */ |
14360 queryResult: Object, | 10259 queryResult: Object, |
14361 }, | 10260 }, |
14362 | 10261 |
14363 observers: [ | 10262 observers: [ |
14364 'groupedRangeChanged_(queryState.range)', | 10263 'groupedRangeChanged_(queryState.range)', |
14365 ], | 10264 ], |
14366 | 10265 |
14367 listeners: { | 10266 listeners: { |
14368 'history-list-scrolled': 'closeMenu_', | 10267 'history-list-scrolled': 'closeMenu_', |
14369 'load-more-history': 'loadMoreHistory_', | 10268 'load-more-history': 'loadMoreHistory_', |
14370 'toggle-menu': 'toggleMenu_', | 10269 'toggle-menu': 'toggleMenu_', |
14371 }, | 10270 }, |
14372 | 10271 |
14373 /** | |
14374 * @param {HistoryQuery} info An object containing information about the | |
14375 * query. | |
14376 * @param {!Array<HistoryEntry>} results A list of results. | |
14377 */ | |
14378 historyResult: function(info, results) { | 10272 historyResult: function(info, results) { |
14379 this.initializeResults_(info, results); | 10273 this.initializeResults_(info, results); |
14380 this.closeMenu_(); | 10274 this.closeMenu_(); |
14381 | 10275 |
14382 if (this.selectedPage_ == 'grouped-list') { | 10276 if (this.selectedPage_ == 'grouped-list') { |
14383 this.$$('#grouped-list').historyData = results; | 10277 this.$$('#grouped-list').historyData = results; |
14384 return; | 10278 return; |
14385 } | 10279 } |
14386 | 10280 |
14387 var list = /** @type {HistoryListElement} */(this.$['infinite-list']); | 10281 var list = /** @type {HistoryListElement} */(this.$['infinite-list']); |
14388 list.addNewResults(results); | 10282 list.addNewResults(results); |
14389 if (info.finished) | 10283 if (info.finished) |
14390 list.disableResultLoading(); | 10284 list.disableResultLoading(); |
14391 }, | 10285 }, |
14392 | 10286 |
14393 /** | |
14394 * Queries the history backend for results based on queryState. | |
14395 * @param {boolean} incremental Whether the new query should continue where | |
14396 * the previous query stopped. | |
14397 */ | |
14398 queryHistory: function(incremental) { | 10287 queryHistory: function(incremental) { |
14399 var queryState = this.queryState; | 10288 var queryState = this.queryState; |
14400 // Disable querying until the first set of results have been returned. If | |
14401 // there is a search, query immediately to support search query params from | |
14402 // the URL. | |
14403 var noResults = !this.queryResult || this.queryResult.results == null; | 10289 var noResults = !this.queryResult || this.queryResult.results == null; |
14404 if (queryState.queryingDisabled || | 10290 if (queryState.queryingDisabled || |
14405 (!this.queryState.searchTerm && noResults)) { | 10291 (!this.queryState.searchTerm && noResults)) { |
14406 return; | 10292 return; |
14407 } | 10293 } |
14408 | 10294 |
14409 // Close any open dialog if a new query is initiated. | |
14410 var dialog = this.$.dialog.getIfExists(); | 10295 var dialog = this.$.dialog.getIfExists(); |
14411 if (!incremental && dialog && dialog.open) | 10296 if (!incremental && dialog && dialog.open) |
14412 dialog.close(); | 10297 dialog.close(); |
14413 | 10298 |
14414 this.set('queryState.querying', true); | 10299 this.set('queryState.querying', true); |
14415 this.set('queryState.incremental', incremental); | 10300 this.set('queryState.incremental', incremental); |
14416 | 10301 |
14417 var lastVisitTime = 0; | 10302 var lastVisitTime = 0; |
14418 if (incremental) { | 10303 if (incremental) { |
14419 var lastVisit = this.queryResult.results.slice(-1)[0]; | 10304 var lastVisit = this.queryResult.results.slice(-1)[0]; |
(...skipping 10 matching lines...) Expand all Loading... |
14430 | 10315 |
14431 /** @return {number} */ | 10316 /** @return {number} */ |
14432 getSelectedItemCount: function() { | 10317 getSelectedItemCount: function() { |
14433 return this.getSelectedList_().selectedPaths.size; | 10318 return this.getSelectedList_().selectedPaths.size; |
14434 }, | 10319 }, |
14435 | 10320 |
14436 unselectAllItems: function(count) { | 10321 unselectAllItems: function(count) { |
14437 this.getSelectedList_().unselectAllItems(count); | 10322 this.getSelectedList_().unselectAllItems(count); |
14438 }, | 10323 }, |
14439 | 10324 |
14440 /** | |
14441 * Delete all the currently selected history items. Will prompt the user with | |
14442 * a dialog to confirm that the deletion should be performed. | |
14443 */ | |
14444 deleteSelectedWithPrompt: function() { | 10325 deleteSelectedWithPrompt: function() { |
14445 if (!loadTimeData.getBoolean('allowDeletingHistory')) | 10326 if (!loadTimeData.getBoolean('allowDeletingHistory')) |
14446 return; | 10327 return; |
14447 this.$.dialog.get().then(function(dialog) { | 10328 this.$.dialog.get().then(function(dialog) { |
14448 dialog.showModal(); | 10329 dialog.showModal(); |
14449 }); | 10330 }); |
14450 }, | 10331 }, |
14451 | 10332 |
14452 /** | |
14453 * @param {HistoryRange} range | |
14454 * @private | |
14455 */ | |
14456 groupedRangeChanged_: function(range) { | 10333 groupedRangeChanged_: function(range) { |
14457 this.selectedPage_ = this.queryState.range == HistoryRange.ALL_TIME ? | 10334 this.selectedPage_ = this.queryState.range == HistoryRange.ALL_TIME ? |
14458 'infinite-list' : 'grouped-list'; | 10335 'infinite-list' : 'grouped-list'; |
14459 | 10336 |
14460 this.queryHistory(false); | 10337 this.queryHistory(false); |
14461 }, | 10338 }, |
14462 | 10339 |
14463 /** @private */ | 10340 /** @private */ |
14464 loadMoreHistory_: function() { this.queryHistory(true); }, | 10341 loadMoreHistory_: function() { this.queryHistory(true); }, |
14465 | 10342 |
14466 /** | |
14467 * @param {HistoryQuery} info | |
14468 * @param {!Array<HistoryEntry>} results | |
14469 * @private | |
14470 */ | |
14471 initializeResults_: function(info, results) { | 10343 initializeResults_: function(info, results) { |
14472 if (results.length == 0) | 10344 if (results.length == 0) |
14473 return; | 10345 return; |
14474 | 10346 |
14475 var currentDate = results[0].dateRelativeDay; | 10347 var currentDate = results[0].dateRelativeDay; |
14476 | 10348 |
14477 for (var i = 0; i < results.length; i++) { | 10349 for (var i = 0; i < results.length; i++) { |
14478 // Sets the default values for these fields to prevent undefined types. | |
14479 results[i].selected = false; | 10350 results[i].selected = false; |
14480 results[i].readableTimestamp = | 10351 results[i].readableTimestamp = |
14481 info.term == '' ? results[i].dateTimeOfDay : results[i].dateShort; | 10352 info.term == '' ? results[i].dateTimeOfDay : results[i].dateShort; |
14482 | 10353 |
14483 if (results[i].dateRelativeDay != currentDate) { | 10354 if (results[i].dateRelativeDay != currentDate) { |
14484 currentDate = results[i].dateRelativeDay; | 10355 currentDate = results[i].dateRelativeDay; |
14485 } | 10356 } |
14486 } | 10357 } |
14487 }, | 10358 }, |
14488 | 10359 |
14489 /** @private */ | 10360 /** @private */ |
14490 onDialogConfirmTap_: function() { | 10361 onDialogConfirmTap_: function() { |
14491 this.getSelectedList_().deleteSelected(); | 10362 this.getSelectedList_().deleteSelected(); |
14492 var dialog = assert(this.$.dialog.getIfExists()); | 10363 var dialog = assert(this.$.dialog.getIfExists()); |
14493 dialog.close(); | 10364 dialog.close(); |
14494 }, | 10365 }, |
14495 | 10366 |
14496 /** @private */ | 10367 /** @private */ |
14497 onDialogCancelTap_: function() { | 10368 onDialogCancelTap_: function() { |
14498 var dialog = assert(this.$.dialog.getIfExists()); | 10369 var dialog = assert(this.$.dialog.getIfExists()); |
14499 dialog.close(); | 10370 dialog.close(); |
14500 }, | 10371 }, |
14501 | 10372 |
14502 /** | |
14503 * Closes the overflow menu. | |
14504 * @private | |
14505 */ | |
14506 closeMenu_: function() { | 10373 closeMenu_: function() { |
14507 var menu = this.$.sharedMenu.getIfExists(); | 10374 var menu = this.$.sharedMenu.getIfExists(); |
14508 if (menu) | 10375 if (menu) |
14509 menu.closeMenu(); | 10376 menu.closeMenu(); |
14510 }, | 10377 }, |
14511 | 10378 |
14512 /** | |
14513 * Opens the overflow menu unless the menu is already open and the same button | |
14514 * is pressed. | |
14515 * @param {{detail: {item: !HistoryEntry, target: !HTMLElement}}} e | |
14516 * @return {Promise<Element>} | |
14517 * @private | |
14518 */ | |
14519 toggleMenu_: function(e) { | 10379 toggleMenu_: function(e) { |
14520 var target = e.detail.target; | 10380 var target = e.detail.target; |
14521 return this.$.sharedMenu.get().then(function(menu) { | 10381 return this.$.sharedMenu.get().then(function(menu) { |
14522 /** @type {CrSharedMenuElement} */(menu).toggleMenu( | 10382 /** @type {CrSharedMenuElement} */(menu).toggleMenu( |
14523 target, e.detail); | 10383 target, e.detail); |
14524 }); | 10384 }); |
14525 }, | 10385 }, |
14526 | 10386 |
14527 /** @private */ | 10387 /** @private */ |
14528 onMoreFromSiteTap_: function() { | 10388 onMoreFromSiteTap_: function() { |
14529 var menu = assert(this.$.sharedMenu.getIfExists()); | 10389 var menu = assert(this.$.sharedMenu.getIfExists()); |
14530 this.fire('search-domain', {domain: menu.itemData.item.domain}); | 10390 this.fire('search-domain', {domain: menu.itemData.item.domain}); |
14531 menu.closeMenu(); | 10391 menu.closeMenu(); |
14532 }, | 10392 }, |
14533 | 10393 |
14534 /** @private */ | 10394 /** @private */ |
14535 onRemoveFromHistoryTap_: function() { | 10395 onRemoveFromHistoryTap_: function() { |
14536 var menu = assert(this.$.sharedMenu.getIfExists()); | 10396 var menu = assert(this.$.sharedMenu.getIfExists()); |
14537 var itemData = menu.itemData; | 10397 var itemData = menu.itemData; |
14538 md_history.BrowserService.getInstance() | 10398 md_history.BrowserService.getInstance() |
14539 .deleteItems([itemData.item]) | 10399 .deleteItems([itemData.item]) |
14540 .then(function(items) { | 10400 .then(function(items) { |
14541 this.getSelectedList_().removeItemsByPath([itemData.path]); | 10401 this.getSelectedList_().removeItemsByPath([itemData.path]); |
14542 // This unselect-all is to reset the toolbar when deleting a selected | |
14543 // item. TODO(tsergeant): Make this automatic based on observing list | |
14544 // modifications. | |
14545 this.fire('unselect-all'); | 10402 this.fire('unselect-all'); |
14546 }.bind(this)); | 10403 }.bind(this)); |
14547 menu.closeMenu(); | 10404 menu.closeMenu(); |
14548 }, | 10405 }, |
14549 | 10406 |
14550 /** | |
14551 * @return {HTMLElement} | |
14552 * @private | |
14553 */ | |
14554 getSelectedList_: function() { | 10407 getSelectedList_: function() { |
14555 return this.$.content.selectedItem; | 10408 return this.$.content.selectedItem; |
14556 }, | 10409 }, |
14557 }); | 10410 }); |
14558 // Copyright 2016 The Chromium Authors. All rights reserved. | 10411 // Copyright 2016 The Chromium Authors. All rights reserved. |
14559 // Use of this source code is governed by a BSD-style license that can be | 10412 // Use of this source code is governed by a BSD-style license that can be |
14560 // found in the LICENSE file. | 10413 // found in the LICENSE file. |
14561 | 10414 |
14562 Polymer({ | 10415 Polymer({ |
14563 is: 'history-synced-device-card', | 10416 is: 'history-synced-device-card', |
14564 | 10417 |
14565 properties: { | 10418 properties: { |
14566 // Name of the synced device. | |
14567 device: String, | 10419 device: String, |
14568 | 10420 |
14569 // When the device information was last updated. | |
14570 lastUpdateTime: String, | 10421 lastUpdateTime: String, |
14571 | 10422 |
14572 /** | |
14573 * The list of tabs open for this device. | |
14574 * @type {!Array<!ForeignSessionTab>} | |
14575 */ | |
14576 tabs: { | 10423 tabs: { |
14577 type: Array, | 10424 type: Array, |
14578 value: function() { return []; }, | 10425 value: function() { return []; }, |
14579 observer: 'updateIcons_' | 10426 observer: 'updateIcons_' |
14580 }, | 10427 }, |
14581 | 10428 |
14582 /** | |
14583 * The indexes where a window separator should be shown. The use of a | |
14584 * separate array here is necessary for window separators to appear | |
14585 * correctly in search. See http://crrev.com/2022003002 for more details. | |
14586 * @type {!Array<number>} | |
14587 */ | |
14588 separatorIndexes: Array, | 10429 separatorIndexes: Array, |
14589 | 10430 |
14590 // Whether the card is open. | |
14591 cardOpen_: {type: Boolean, value: true}, | 10431 cardOpen_: {type: Boolean, value: true}, |
14592 | 10432 |
14593 searchTerm: String, | 10433 searchTerm: String, |
14594 | 10434 |
14595 // Internal identifier for the device. | |
14596 sessionTag: String, | 10435 sessionTag: String, |
14597 }, | 10436 }, |
14598 | 10437 |
14599 /** | |
14600 * Open a single synced tab. Listens to 'click' rather than 'tap' | |
14601 * to determine what modifier keys were pressed. | |
14602 * @param {DomRepeatClickEvent} e | |
14603 * @private | |
14604 */ | |
14605 openTab_: function(e) { | 10438 openTab_: function(e) { |
14606 var tab = /** @type {ForeignSessionTab} */(e.model.tab); | 10439 var tab = /** @type {ForeignSessionTab} */(e.model.tab); |
14607 md_history.BrowserService.getInstance().openForeignSessionTab( | 10440 md_history.BrowserService.getInstance().openForeignSessionTab( |
14608 this.sessionTag, tab.windowId, tab.sessionId, e); | 10441 this.sessionTag, tab.windowId, tab.sessionId, e); |
14609 e.preventDefault(); | 10442 e.preventDefault(); |
14610 }, | 10443 }, |
14611 | 10444 |
14612 /** | |
14613 * Toggles the dropdown display of synced tabs for each device card. | |
14614 */ | |
14615 toggleTabCard: function() { | 10445 toggleTabCard: function() { |
14616 this.$.collapse.toggle(); | 10446 this.$.collapse.toggle(); |
14617 this.$['dropdown-indicator'].icon = | 10447 this.$['dropdown-indicator'].icon = |
14618 this.$.collapse.opened ? 'cr:expand-less' : 'cr:expand-more'; | 10448 this.$.collapse.opened ? 'cr:expand-less' : 'cr:expand-more'; |
14619 }, | 10449 }, |
14620 | 10450 |
14621 /** | |
14622 * When the synced tab information is set, the icon associated with the tab | |
14623 * website is also set. | |
14624 * @private | |
14625 */ | |
14626 updateIcons_: function() { | 10451 updateIcons_: function() { |
14627 this.async(function() { | 10452 this.async(function() { |
14628 var icons = Polymer.dom(this.root).querySelectorAll('.website-icon'); | 10453 var icons = Polymer.dom(this.root).querySelectorAll('.website-icon'); |
14629 | 10454 |
14630 for (var i = 0; i < this.tabs.length; i++) { | 10455 for (var i = 0; i < this.tabs.length; i++) { |
14631 icons[i].style.backgroundImage = | 10456 icons[i].style.backgroundImage = |
14632 cr.icon.getFaviconImageSet(this.tabs[i].url); | 10457 cr.icon.getFaviconImageSet(this.tabs[i].url); |
14633 } | 10458 } |
14634 }); | 10459 }); |
14635 }, | 10460 }, |
14636 | 10461 |
14637 /** @private */ | 10462 /** @private */ |
14638 isWindowSeparatorIndex_: function(index, separatorIndexes) { | 10463 isWindowSeparatorIndex_: function(index, separatorIndexes) { |
14639 return this.separatorIndexes.indexOf(index) != -1; | 10464 return this.separatorIndexes.indexOf(index) != -1; |
14640 }, | 10465 }, |
14641 | 10466 |
14642 /** | |
14643 * @param {boolean} cardOpen | |
14644 * @return {string} | |
14645 */ | |
14646 getCollapseTitle_: function(cardOpen) { | 10467 getCollapseTitle_: function(cardOpen) { |
14647 return cardOpen ? loadTimeData.getString('collapseSessionButton') : | 10468 return cardOpen ? loadTimeData.getString('collapseSessionButton') : |
14648 loadTimeData.getString('expandSessionButton'); | 10469 loadTimeData.getString('expandSessionButton'); |
14649 }, | 10470 }, |
14650 | 10471 |
14651 /** | |
14652 * @param {CustomEvent} e | |
14653 * @private | |
14654 */ | |
14655 onMenuButtonTap_: function(e) { | 10472 onMenuButtonTap_: function(e) { |
14656 this.fire('toggle-menu', { | 10473 this.fire('toggle-menu', { |
14657 target: Polymer.dom(e).localTarget, | 10474 target: Polymer.dom(e).localTarget, |
14658 tag: this.sessionTag | 10475 tag: this.sessionTag |
14659 }); | 10476 }); |
14660 e.stopPropagation(); // Prevent iron-collapse. | 10477 e.stopPropagation(); // Prevent iron-collapse. |
14661 }, | 10478 }, |
14662 }); | 10479 }); |
14663 // Copyright 2016 The Chromium Authors. All rights reserved. | 10480 // Copyright 2016 The Chromium Authors. All rights reserved. |
14664 // Use of this source code is governed by a BSD-style license that can be | 10481 // Use of this source code is governed by a BSD-style license that can be |
14665 // found in the LICENSE file. | 10482 // found in the LICENSE file. |
14666 | 10483 |
14667 /** | |
14668 * @typedef {{device: string, | |
14669 * lastUpdateTime: string, | |
14670 * separatorIndexes: !Array<number>, | |
14671 * timestamp: number, | |
14672 * tabs: !Array<!ForeignSessionTab>, | |
14673 * tag: string}} | |
14674 */ | |
14675 var ForeignDeviceInternal; | 10484 var ForeignDeviceInternal; |
14676 | 10485 |
14677 Polymer({ | 10486 Polymer({ |
14678 is: 'history-synced-device-manager', | 10487 is: 'history-synced-device-manager', |
14679 | 10488 |
14680 properties: { | 10489 properties: { |
14681 /** | |
14682 * @type {?Array<!ForeignSession>} | |
14683 */ | |
14684 sessionList: { | 10490 sessionList: { |
14685 type: Array, | 10491 type: Array, |
14686 observer: 'updateSyncedDevices' | 10492 observer: 'updateSyncedDevices' |
14687 }, | 10493 }, |
14688 | 10494 |
14689 searchTerm: { | 10495 searchTerm: { |
14690 type: String, | 10496 type: String, |
14691 observer: 'searchTermChanged' | 10497 observer: 'searchTermChanged' |
14692 }, | 10498 }, |
14693 | 10499 |
14694 /** | |
14695 * An array of synced devices with synced tab data. | |
14696 * @type {!Array<!ForeignDeviceInternal>} | |
14697 */ | |
14698 syncedDevices_: { | 10500 syncedDevices_: { |
14699 type: Array, | 10501 type: Array, |
14700 value: function() { return []; } | 10502 value: function() { return []; } |
14701 }, | 10503 }, |
14702 | 10504 |
14703 /** @private */ | 10505 /** @private */ |
14704 signInState_: { | 10506 signInState_: { |
14705 type: Boolean, | 10507 type: Boolean, |
14706 value: loadTimeData.getBoolean('isUserSignedIn'), | 10508 value: loadTimeData.getBoolean('isUserSignedIn'), |
14707 }, | 10509 }, |
(...skipping 10 matching lines...) Expand all Loading... |
14718 value: false, | 10520 value: false, |
14719 } | 10521 } |
14720 }, | 10522 }, |
14721 | 10523 |
14722 listeners: { | 10524 listeners: { |
14723 'toggle-menu': 'onToggleMenu_', | 10525 'toggle-menu': 'onToggleMenu_', |
14724 }, | 10526 }, |
14725 | 10527 |
14726 /** @override */ | 10528 /** @override */ |
14727 attached: function() { | 10529 attached: function() { |
14728 // Update the sign in state. | |
14729 chrome.send('otherDevicesInitialized'); | 10530 chrome.send('otherDevicesInitialized'); |
14730 }, | 10531 }, |
14731 | 10532 |
14732 /** | |
14733 * @param {!ForeignSession} session | |
14734 * @return {!ForeignDeviceInternal} | |
14735 */ | |
14736 createInternalDevice_: function(session) { | 10533 createInternalDevice_: function(session) { |
14737 var tabs = []; | 10534 var tabs = []; |
14738 var separatorIndexes = []; | 10535 var separatorIndexes = []; |
14739 for (var i = 0; i < session.windows.length; i++) { | 10536 for (var i = 0; i < session.windows.length; i++) { |
14740 var windowId = session.windows[i].sessionId; | 10537 var windowId = session.windows[i].sessionId; |
14741 var newTabs = session.windows[i].tabs; | 10538 var newTabs = session.windows[i].tabs; |
14742 if (newTabs.length == 0) | 10539 if (newTabs.length == 0) |
14743 continue; | 10540 continue; |
14744 | 10541 |
14745 newTabs.forEach(function(tab) { | 10542 newTabs.forEach(function(tab) { |
14746 tab.windowId = windowId; | 10543 tab.windowId = windowId; |
14747 }); | 10544 }); |
14748 | 10545 |
14749 var windowAdded = false; | 10546 var windowAdded = false; |
14750 if (!this.searchTerm) { | 10547 if (!this.searchTerm) { |
14751 // Add all the tabs if there is no search term. | |
14752 tabs = tabs.concat(newTabs); | 10548 tabs = tabs.concat(newTabs); |
14753 windowAdded = true; | 10549 windowAdded = true; |
14754 } else { | 10550 } else { |
14755 var searchText = this.searchTerm.toLowerCase(); | 10551 var searchText = this.searchTerm.toLowerCase(); |
14756 for (var j = 0; j < newTabs.length; j++) { | 10552 for (var j = 0; j < newTabs.length; j++) { |
14757 var tab = newTabs[j]; | 10553 var tab = newTabs[j]; |
14758 if (tab.title.toLowerCase().indexOf(searchText) != -1) { | 10554 if (tab.title.toLowerCase().indexOf(searchText) != -1) { |
14759 tabs.push(tab); | 10555 tabs.push(tab); |
14760 windowAdded = true; | 10556 windowAdded = true; |
14761 } | 10557 } |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
14796 md_history.BrowserService.getInstance().deleteForeignSession( | 10592 md_history.BrowserService.getInstance().deleteForeignSession( |
14797 menu.itemData); | 10593 menu.itemData); |
14798 menu.closeMenu(); | 10594 menu.closeMenu(); |
14799 }, | 10595 }, |
14800 | 10596 |
14801 /** @private */ | 10597 /** @private */ |
14802 clearDisplayedSyncedDevices_: function() { | 10598 clearDisplayedSyncedDevices_: function() { |
14803 this.syncedDevices_ = []; | 10599 this.syncedDevices_ = []; |
14804 }, | 10600 }, |
14805 | 10601 |
14806 /** | |
14807 * Decide whether or not should display no synced tabs message. | |
14808 * @param {boolean} signInState | |
14809 * @param {number} syncedDevicesLength | |
14810 * @param {boolean} guestSession | |
14811 * @return {boolean} | |
14812 */ | |
14813 showNoSyncedMessage: function( | 10602 showNoSyncedMessage: function( |
14814 signInState, syncedDevicesLength, guestSession) { | 10603 signInState, syncedDevicesLength, guestSession) { |
14815 if (guestSession) | 10604 if (guestSession) |
14816 return true; | 10605 return true; |
14817 | 10606 |
14818 return signInState && syncedDevicesLength == 0; | 10607 return signInState && syncedDevicesLength == 0; |
14819 }, | 10608 }, |
14820 | 10609 |
14821 /** | |
14822 * Shows the signin guide when the user is not signed in and not in a guest | |
14823 * session. | |
14824 * @param {boolean} signInState | |
14825 * @param {boolean} guestSession | |
14826 * @return {boolean} | |
14827 */ | |
14828 showSignInGuide: function(signInState, guestSession) { | 10610 showSignInGuide: function(signInState, guestSession) { |
14829 var show = !signInState && !guestSession; | 10611 var show = !signInState && !guestSession; |
14830 if (show) { | 10612 if (show) { |
14831 md_history.BrowserService.getInstance().recordAction( | 10613 md_history.BrowserService.getInstance().recordAction( |
14832 'Signin_Impression_FromRecentTabs'); | 10614 'Signin_Impression_FromRecentTabs'); |
14833 } | 10615 } |
14834 | 10616 |
14835 return show; | 10617 return show; |
14836 }, | 10618 }, |
14837 | 10619 |
14838 /** | |
14839 * Decide what message should be displayed when user is logged in and there | |
14840 * are no synced tabs. | |
14841 * @param {boolean} fetchingSyncedTabs | |
14842 * @return {string} | |
14843 */ | |
14844 noSyncedTabsMessage: function(fetchingSyncedTabs) { | 10620 noSyncedTabsMessage: function(fetchingSyncedTabs) { |
14845 return loadTimeData.getString( | 10621 return loadTimeData.getString( |
14846 fetchingSyncedTabs ? 'loading' : 'noSyncedResults'); | 10622 fetchingSyncedTabs ? 'loading' : 'noSyncedResults'); |
14847 }, | 10623 }, |
14848 | 10624 |
14849 /** | |
14850 * Replaces the currently displayed synced tabs with |sessionList|. It is | |
14851 * common for only a single session within the list to have changed, We try to | |
14852 * avoid doing extra work in this case. The logic could be more intelligent | |
14853 * about updating individual tabs rather than replacing whole sessions, but | |
14854 * this approach seems to have acceptable performance. | |
14855 * @param {?Array<!ForeignSession>} sessionList | |
14856 */ | |
14857 updateSyncedDevices: function(sessionList) { | 10625 updateSyncedDevices: function(sessionList) { |
14858 this.fetchingSyncedTabs_ = false; | 10626 this.fetchingSyncedTabs_ = false; |
14859 | 10627 |
14860 if (!sessionList) | 10628 if (!sessionList) |
14861 return; | 10629 return; |
14862 | 10630 |
14863 // First, update any existing devices that have changed. | |
14864 var updateCount = Math.min(sessionList.length, this.syncedDevices_.length); | 10631 var updateCount = Math.min(sessionList.length, this.syncedDevices_.length); |
14865 for (var i = 0; i < updateCount; i++) { | 10632 for (var i = 0; i < updateCount; i++) { |
14866 var oldDevice = this.syncedDevices_[i]; | 10633 var oldDevice = this.syncedDevices_[i]; |
14867 if (oldDevice.tag != sessionList[i].tag || | 10634 if (oldDevice.tag != sessionList[i].tag || |
14868 oldDevice.timestamp != sessionList[i].timestamp) { | 10635 oldDevice.timestamp != sessionList[i].timestamp) { |
14869 this.splice( | 10636 this.splice( |
14870 'syncedDevices_', i, 1, this.createInternalDevice_(sessionList[i])); | 10637 'syncedDevices_', i, 1, this.createInternalDevice_(sessionList[i])); |
14871 } | 10638 } |
14872 } | 10639 } |
14873 | 10640 |
14874 // Then, append any new devices. | |
14875 for (var i = updateCount; i < sessionList.length; i++) { | 10641 for (var i = updateCount; i < sessionList.length; i++) { |
14876 this.push('syncedDevices_', this.createInternalDevice_(sessionList[i])); | 10642 this.push('syncedDevices_', this.createInternalDevice_(sessionList[i])); |
14877 } | 10643 } |
14878 }, | 10644 }, |
14879 | 10645 |
14880 /** | |
14881 * End fetching synced tabs when sync is disabled. | |
14882 */ | |
14883 tabSyncDisabled: function() { | 10646 tabSyncDisabled: function() { |
14884 this.fetchingSyncedTabs_ = false; | 10647 this.fetchingSyncedTabs_ = false; |
14885 this.clearDisplayedSyncedDevices_(); | 10648 this.clearDisplayedSyncedDevices_(); |
14886 }, | 10649 }, |
14887 | 10650 |
14888 /** | |
14889 * Get called when user's sign in state changes, this will affect UI of synced | |
14890 * tabs page. Sign in promo gets displayed when user is signed out, and | |
14891 * different messages are shown when there are no synced tabs. | |
14892 * @param {boolean} isUserSignedIn | |
14893 */ | |
14894 updateSignInState: function(isUserSignedIn) { | 10651 updateSignInState: function(isUserSignedIn) { |
14895 // If user's sign in state didn't change, then don't change message or | |
14896 // update UI. | |
14897 if (this.signInState_ == isUserSignedIn) | 10652 if (this.signInState_ == isUserSignedIn) |
14898 return; | 10653 return; |
14899 | 10654 |
14900 this.signInState_ = isUserSignedIn; | 10655 this.signInState_ = isUserSignedIn; |
14901 | 10656 |
14902 // User signed out, clear synced device list and show the sign in promo. | |
14903 if (!isUserSignedIn) { | 10657 if (!isUserSignedIn) { |
14904 this.clearDisplayedSyncedDevices_(); | 10658 this.clearDisplayedSyncedDevices_(); |
14905 return; | 10659 return; |
14906 } | 10660 } |
14907 // User signed in, show the loading message when querying for synced | |
14908 // devices. | |
14909 this.fetchingSyncedTabs_ = true; | 10661 this.fetchingSyncedTabs_ = true; |
14910 }, | 10662 }, |
14911 | 10663 |
14912 searchTermChanged: function(searchTerm) { | 10664 searchTermChanged: function(searchTerm) { |
14913 this.clearDisplayedSyncedDevices_(); | 10665 this.clearDisplayedSyncedDevices_(); |
14914 this.updateSyncedDevices(this.sessionList); | 10666 this.updateSyncedDevices(this.sessionList); |
14915 } | 10667 } |
14916 }); | 10668 }); |
14917 /** | |
14918 `iron-selector` is an element which can be used to manage a list of elements | |
14919 that can be selected. Tapping on the item will make the item selected. The `
selected` indicates | |
14920 which item is being selected. The default is to use the index of the item. | |
14921 | |
14922 Example: | |
14923 | |
14924 <iron-selector selected="0"> | |
14925 <div>Item 1</div> | |
14926 <div>Item 2</div> | |
14927 <div>Item 3</div> | |
14928 </iron-selector> | |
14929 | |
14930 If you want to use the attribute value of an element for `selected` instead of
the index, | |
14931 set `attrForSelected` to the name of the attribute. For example, if you want
to select item by | |
14932 `name`, set `attrForSelected` to `name`. | |
14933 | |
14934 Example: | |
14935 | |
14936 <iron-selector attr-for-selected="name" selected="foo"> | |
14937 <div name="foo">Foo</div> | |
14938 <div name="bar">Bar</div> | |
14939 <div name="zot">Zot</div> | |
14940 </iron-selector> | |
14941 | |
14942 You can specify a default fallback with `fallbackSelection` in case the `selec
ted` attribute does | |
14943 not match the `attrForSelected` attribute of any elements. | |
14944 | |
14945 Example: | |
14946 | |
14947 <iron-selector attr-for-selected="name" selected="non-existing" | |
14948 fallback-selection="default"> | |
14949 <div name="foo">Foo</div> | |
14950 <div name="bar">Bar</div> | |
14951 <div name="default">Default</div> | |
14952 </iron-selector> | |
14953 | |
14954 Note: When the selector is multi, the selection will set to `fallbackSelection
` iff | |
14955 the number of matching elements is zero. | |
14956 | |
14957 `iron-selector` is not styled. Use the `iron-selected` CSS class to style the
selected element. | |
14958 | |
14959 Example: | |
14960 | |
14961 <style> | |
14962 .iron-selected { | |
14963 background: #eee; | |
14964 } | |
14965 </style> | |
14966 | |
14967 ... | |
14968 | |
14969 <iron-selector selected="0"> | |
14970 <div>Item 1</div> | |
14971 <div>Item 2</div> | |
14972 <div>Item 3</div> | |
14973 </iron-selector> | |
14974 | |
14975 @demo demo/index.html | |
14976 */ | |
14977 | 10669 |
14978 Polymer({ | 10670 Polymer({ |
14979 | 10671 |
14980 is: 'iron-selector', | 10672 is: 'iron-selector', |
14981 | 10673 |
14982 behaviors: [ | 10674 behaviors: [ |
14983 Polymer.IronMultiSelectableBehavior | 10675 Polymer.IronMultiSelectableBehavior |
14984 ] | 10676 ] |
14985 | 10677 |
14986 }); | 10678 }); |
14987 // Copyright 2016 The Chromium Authors. All rights reserved. | 10679 // Copyright 2016 The Chromium Authors. All rights reserved. |
14988 // Use of this source code is governed by a BSD-style license that can be | 10680 // Use of this source code is governed by a BSD-style license that can be |
14989 // found in the LICENSE file. | 10681 // found in the LICENSE file. |
14990 | 10682 |
14991 Polymer({ | 10683 Polymer({ |
14992 is: 'history-side-bar', | 10684 is: 'history-side-bar', |
14993 | 10685 |
14994 properties: { | 10686 properties: { |
14995 selectedPage: { | 10687 selectedPage: { |
14996 type: String, | 10688 type: String, |
14997 notify: true | 10689 notify: true |
14998 }, | 10690 }, |
14999 | 10691 |
15000 route: Object, | 10692 route: Object, |
15001 | 10693 |
15002 showFooter: Boolean, | 10694 showFooter: Boolean, |
15003 | 10695 |
15004 // If true, the sidebar is contained within an app-drawer. | |
15005 drawer: { | 10696 drawer: { |
15006 type: Boolean, | 10697 type: Boolean, |
15007 reflectToAttribute: true | 10698 reflectToAttribute: true |
15008 }, | 10699 }, |
15009 }, | 10700 }, |
15010 | 10701 |
15011 /** @private */ | 10702 /** @private */ |
15012 onSelectorActivate_: function() { | 10703 onSelectorActivate_: function() { |
15013 this.fire('history-close-drawer'); | 10704 this.fire('history-close-drawer'); |
15014 }, | 10705 }, |
15015 | 10706 |
15016 /** | |
15017 * Relocates the user to the clear browsing data section of the settings page. | |
15018 * @param {Event} e | |
15019 * @private | |
15020 */ | |
15021 onClearBrowsingDataTap_: function(e) { | 10707 onClearBrowsingDataTap_: function(e) { |
15022 md_history.BrowserService.getInstance().openClearBrowsingData(); | 10708 md_history.BrowserService.getInstance().openClearBrowsingData(); |
15023 e.preventDefault(); | 10709 e.preventDefault(); |
15024 }, | 10710 }, |
15025 | 10711 |
15026 /** | |
15027 * @param {Object} route | |
15028 * @private | |
15029 */ | |
15030 getQueryString_: function(route) { | 10712 getQueryString_: function(route) { |
15031 return window.location.search; | 10713 return window.location.search; |
15032 } | 10714 } |
15033 }); | 10715 }); |
15034 // Copyright 2016 The Chromium Authors. All rights reserved. | 10716 // Copyright 2016 The Chromium Authors. All rights reserved. |
15035 // Use of this source code is governed by a BSD-style license that can be | 10717 // Use of this source code is governed by a BSD-style license that can be |
15036 // found in the LICENSE file. | 10718 // found in the LICENSE file. |
15037 | 10719 |
15038 Polymer({ | 10720 Polymer({ |
15039 is: 'history-app', | 10721 is: 'history-app', |
15040 | 10722 |
15041 properties: { | 10723 properties: { |
15042 showSidebarFooter: Boolean, | 10724 showSidebarFooter: Boolean, |
15043 | 10725 |
15044 // The id of the currently selected page. | |
15045 selectedPage_: {type: String, value: 'history', observer: 'unselectAll'}, | 10726 selectedPage_: {type: String, value: 'history', observer: 'unselectAll'}, |
15046 | 10727 |
15047 // Whether domain-grouped history is enabled. | |
15048 grouped_: {type: Boolean, reflectToAttribute: true}, | 10728 grouped_: {type: Boolean, reflectToAttribute: true}, |
15049 | 10729 |
15050 /** @type {!QueryState} */ | 10730 /** @type {!QueryState} */ |
15051 queryState_: { | 10731 queryState_: { |
15052 type: Object, | 10732 type: Object, |
15053 value: function() { | 10733 value: function() { |
15054 return { | 10734 return { |
15055 // Whether the most recent query was incremental. | |
15056 incremental: false, | 10735 incremental: false, |
15057 // A query is initiated by page load. | |
15058 querying: true, | 10736 querying: true, |
15059 queryingDisabled: false, | 10737 queryingDisabled: false, |
15060 _range: HistoryRange.ALL_TIME, | 10738 _range: HistoryRange.ALL_TIME, |
15061 searchTerm: '', | 10739 searchTerm: '', |
15062 // TODO(calamity): Make history toolbar buttons change the offset | |
15063 groupedOffset: 0, | 10740 groupedOffset: 0, |
15064 | 10741 |
15065 set range(val) { this._range = Number(val); }, | 10742 set range(val) { this._range = Number(val); }, |
15066 get range() { return this._range; }, | 10743 get range() { return this._range; }, |
15067 }; | 10744 }; |
15068 } | 10745 } |
15069 }, | 10746 }, |
15070 | 10747 |
15071 /** @type {!QueryResult} */ | 10748 /** @type {!QueryResult} */ |
15072 queryResult_: { | 10749 queryResult_: { |
15073 type: Object, | 10750 type: Object, |
15074 value: function() { | 10751 value: function() { |
15075 return { | 10752 return { |
15076 info: null, | 10753 info: null, |
15077 results: null, | 10754 results: null, |
15078 sessionList: null, | 10755 sessionList: null, |
15079 }; | 10756 }; |
15080 } | 10757 } |
15081 }, | 10758 }, |
15082 | 10759 |
15083 // Route data for the current page. | |
15084 routeData_: Object, | 10760 routeData_: Object, |
15085 | 10761 |
15086 // The query params for the page. | |
15087 queryParams_: Object, | 10762 queryParams_: Object, |
15088 | 10763 |
15089 // True if the window is narrow enough for the page to have a drawer. | |
15090 hasDrawer_: Boolean, | 10764 hasDrawer_: Boolean, |
15091 }, | 10765 }, |
15092 | 10766 |
15093 observers: [ | 10767 observers: [ |
15094 // routeData_.page <=> selectedPage | |
15095 'routeDataChanged_(routeData_.page)', | 10768 'routeDataChanged_(routeData_.page)', |
15096 'selectedPageChanged_(selectedPage_)', | 10769 'selectedPageChanged_(selectedPage_)', |
15097 | 10770 |
15098 // queryParams_.q <=> queryState.searchTerm | |
15099 'searchTermChanged_(queryState_.searchTerm)', | 10771 'searchTermChanged_(queryState_.searchTerm)', |
15100 'searchQueryParamChanged_(queryParams_.q)', | 10772 'searchQueryParamChanged_(queryParams_.q)', |
15101 | 10773 |
15102 ], | 10774 ], |
15103 | 10775 |
15104 // TODO(calamity): Replace these event listeners with data bound properties. | |
15105 listeners: { | 10776 listeners: { |
15106 'cr-menu-tap': 'onMenuTap_', | 10777 'cr-menu-tap': 'onMenuTap_', |
15107 'history-checkbox-select': 'checkboxSelected', | 10778 'history-checkbox-select': 'checkboxSelected', |
15108 'unselect-all': 'unselectAll', | 10779 'unselect-all': 'unselectAll', |
15109 'delete-selected': 'deleteSelected', | 10780 'delete-selected': 'deleteSelected', |
15110 'search-domain': 'searchDomain_', | 10781 'search-domain': 'searchDomain_', |
15111 'history-close-drawer': 'closeDrawer_', | 10782 'history-close-drawer': 'closeDrawer_', |
15112 }, | 10783 }, |
15113 | 10784 |
15114 /** @override */ | 10785 /** @override */ |
15115 ready: function() { | 10786 ready: function() { |
15116 this.grouped_ = loadTimeData.getBoolean('groupByDomain'); | 10787 this.grouped_ = loadTimeData.getBoolean('groupByDomain'); |
15117 | 10788 |
15118 cr.ui.decorate('command', cr.ui.Command); | 10789 cr.ui.decorate('command', cr.ui.Command); |
15119 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | 10790 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
15120 document.addEventListener('command', this.onCommand_.bind(this)); | 10791 document.addEventListener('command', this.onCommand_.bind(this)); |
15121 | 10792 |
15122 // Redirect legacy search URLs to URLs compatible with material history. | |
15123 if (window.location.hash) { | 10793 if (window.location.hash) { |
15124 window.location.href = window.location.href.split('#')[0] + '?' + | 10794 window.location.href = window.location.href.split('#')[0] + '?' + |
15125 window.location.hash.substr(1); | 10795 window.location.hash.substr(1); |
15126 } | 10796 } |
15127 }, | 10797 }, |
15128 | 10798 |
15129 /** @private */ | 10799 /** @private */ |
15130 onMenuTap_: function() { | 10800 onMenuTap_: function() { |
15131 var drawer = this.$$('#drawer'); | 10801 var drawer = this.$$('#drawer'); |
15132 if (drawer) | 10802 if (drawer) |
15133 drawer.toggle(); | 10803 drawer.toggle(); |
15134 }, | 10804 }, |
15135 | 10805 |
15136 /** | |
15137 * Listens for history-item being selected or deselected (through checkbox) | |
15138 * and changes the view of the top toolbar. | |
15139 * @param {{detail: {countAddition: number}}} e | |
15140 */ | |
15141 checkboxSelected: function(e) { | 10806 checkboxSelected: function(e) { |
15142 var toolbar = /** @type {HistoryToolbarElement} */ (this.$.toolbar); | 10807 var toolbar = /** @type {HistoryToolbarElement} */ (this.$.toolbar); |
15143 toolbar.count = /** @type {HistoryListContainerElement} */ (this.$.history) | 10808 toolbar.count = /** @type {HistoryListContainerElement} */ (this.$.history) |
15144 .getSelectedItemCount(); | 10809 .getSelectedItemCount(); |
15145 }, | 10810 }, |
15146 | 10811 |
15147 /** | |
15148 * Listens for call to cancel selection and loops through all items to set | |
15149 * checkbox to be unselected. | |
15150 * @private | |
15151 */ | |
15152 unselectAll: function() { | 10812 unselectAll: function() { |
15153 var listContainer = | 10813 var listContainer = |
15154 /** @type {HistoryListContainerElement} */ (this.$.history); | 10814 /** @type {HistoryListContainerElement} */ (this.$.history); |
15155 var toolbar = /** @type {HistoryToolbarElement} */ (this.$.toolbar); | 10815 var toolbar = /** @type {HistoryToolbarElement} */ (this.$.toolbar); |
15156 listContainer.unselectAllItems(toolbar.count); | 10816 listContainer.unselectAllItems(toolbar.count); |
15157 toolbar.count = 0; | 10817 toolbar.count = 0; |
15158 }, | 10818 }, |
15159 | 10819 |
15160 deleteSelected: function() { | 10820 deleteSelected: function() { |
15161 this.$.history.deleteSelectedWithPrompt(); | 10821 this.$.history.deleteSelectedWithPrompt(); |
15162 }, | 10822 }, |
15163 | 10823 |
15164 /** | |
15165 * @param {HistoryQuery} info An object containing information about the | |
15166 * query. | |
15167 * @param {!Array<HistoryEntry>} results A list of results. | |
15168 */ | |
15169 historyResult: function(info, results) { | 10824 historyResult: function(info, results) { |
15170 this.set('queryState_.querying', false); | 10825 this.set('queryState_.querying', false); |
15171 this.set('queryResult_.info', info); | 10826 this.set('queryResult_.info', info); |
15172 this.set('queryResult_.results', results); | 10827 this.set('queryResult_.results', results); |
15173 var listContainer = | 10828 var listContainer = |
15174 /** @type {HistoryListContainerElement} */ (this.$['history']); | 10829 /** @type {HistoryListContainerElement} */ (this.$['history']); |
15175 listContainer.historyResult(info, results); | 10830 listContainer.historyResult(info, results); |
15176 }, | 10831 }, |
15177 | 10832 |
15178 /** | |
15179 * Fired when the user presses 'More from this site'. | |
15180 * @param {{detail: {domain: string}}} e | |
15181 */ | |
15182 searchDomain_: function(e) { this.$.toolbar.setSearchTerm(e.detail.domain); }, | 10833 searchDomain_: function(e) { this.$.toolbar.setSearchTerm(e.detail.domain); }, |
15183 | 10834 |
15184 /** | |
15185 * @param {Event} e | |
15186 * @private | |
15187 */ | |
15188 onCanExecute_: function(e) { | 10835 onCanExecute_: function(e) { |
15189 e = /** @type {cr.ui.CanExecuteEvent} */(e); | 10836 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
15190 switch (e.command.id) { | 10837 switch (e.command.id) { |
15191 case 'find-command': | 10838 case 'find-command': |
15192 e.canExecute = true; | 10839 e.canExecute = true; |
15193 break; | 10840 break; |
15194 case 'slash-command': | 10841 case 'slash-command': |
15195 e.canExecute = !this.$.toolbar.searchBar.isSearchFocused(); | 10842 e.canExecute = !this.$.toolbar.searchBar.isSearchFocused(); |
15196 break; | 10843 break; |
15197 case 'delete-command': | 10844 case 'delete-command': |
15198 e.canExecute = this.$.toolbar.count > 0; | 10845 e.canExecute = this.$.toolbar.count > 0; |
15199 break; | 10846 break; |
15200 } | 10847 } |
15201 }, | 10848 }, |
15202 | 10849 |
15203 /** | |
15204 * @param {string} searchTerm | |
15205 * @private | |
15206 */ | |
15207 searchTermChanged_: function(searchTerm) { | 10850 searchTermChanged_: function(searchTerm) { |
15208 this.set('queryParams_.q', searchTerm || null); | 10851 this.set('queryParams_.q', searchTerm || null); |
15209 this.$['history'].queryHistory(false); | 10852 this.$['history'].queryHistory(false); |
15210 }, | 10853 }, |
15211 | 10854 |
15212 /** | |
15213 * @param {string} searchQuery | |
15214 * @private | |
15215 */ | |
15216 searchQueryParamChanged_: function(searchQuery) { | 10855 searchQueryParamChanged_: function(searchQuery) { |
15217 this.$.toolbar.setSearchTerm(searchQuery || ''); | 10856 this.$.toolbar.setSearchTerm(searchQuery || ''); |
15218 }, | 10857 }, |
15219 | 10858 |
15220 /** | |
15221 * @param {Event} e | |
15222 * @private | |
15223 */ | |
15224 onCommand_: function(e) { | 10859 onCommand_: function(e) { |
15225 if (e.command.id == 'find-command' || e.command.id == 'slash-command') | 10860 if (e.command.id == 'find-command' || e.command.id == 'slash-command') |
15226 this.$.toolbar.showSearchField(); | 10861 this.$.toolbar.showSearchField(); |
15227 if (e.command.id == 'delete-command') | 10862 if (e.command.id == 'delete-command') |
15228 this.deleteSelected(); | 10863 this.deleteSelected(); |
15229 }, | 10864 }, |
15230 | 10865 |
15231 /** | |
15232 * @param {!Array<!ForeignSession>} sessionList Array of objects describing | |
15233 * the sessions from other devices. | |
15234 * @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile? | |
15235 */ | |
15236 setForeignSessions: function(sessionList, isTabSyncEnabled) { | 10866 setForeignSessions: function(sessionList, isTabSyncEnabled) { |
15237 if (!isTabSyncEnabled) { | 10867 if (!isTabSyncEnabled) { |
15238 var syncedDeviceManagerElem = | 10868 var syncedDeviceManagerElem = |
15239 /** @type {HistorySyncedDeviceManagerElement} */this | 10869 /** @type {HistorySyncedDeviceManagerElement} */this |
15240 .$$('history-synced-device-manager'); | 10870 .$$('history-synced-device-manager'); |
15241 if (syncedDeviceManagerElem) | 10871 if (syncedDeviceManagerElem) |
15242 syncedDeviceManagerElem.tabSyncDisabled(); | 10872 syncedDeviceManagerElem.tabSyncDisabled(); |
15243 return; | 10873 return; |
15244 } | 10874 } |
15245 | 10875 |
15246 this.set('queryResult_.sessionList', sessionList); | 10876 this.set('queryResult_.sessionList', sessionList); |
15247 }, | 10877 }, |
15248 | 10878 |
15249 /** | |
15250 * Update sign in state of synced device manager after user logs in or out. | |
15251 * @param {boolean} isUserSignedIn | |
15252 */ | |
15253 updateSignInState: function(isUserSignedIn) { | 10879 updateSignInState: function(isUserSignedIn) { |
15254 var syncedDeviceManagerElem = | 10880 var syncedDeviceManagerElem = |
15255 /** @type {HistorySyncedDeviceManagerElement} */this | 10881 /** @type {HistorySyncedDeviceManagerElement} */this |
15256 .$$('history-synced-device-manager'); | 10882 .$$('history-synced-device-manager'); |
15257 if (syncedDeviceManagerElem) | 10883 if (syncedDeviceManagerElem) |
15258 syncedDeviceManagerElem.updateSignInState(isUserSignedIn); | 10884 syncedDeviceManagerElem.updateSignInState(isUserSignedIn); |
15259 }, | 10885 }, |
15260 | 10886 |
15261 /** | |
15262 * @param {string} selectedPage | |
15263 * @return {boolean} | |
15264 * @private | |
15265 */ | |
15266 syncedTabsSelected_: function(selectedPage) { | 10887 syncedTabsSelected_: function(selectedPage) { |
15267 return selectedPage == 'syncedTabs'; | 10888 return selectedPage == 'syncedTabs'; |
15268 }, | 10889 }, |
15269 | 10890 |
15270 /** | |
15271 * @param {boolean} querying | |
15272 * @param {boolean} incremental | |
15273 * @param {string} searchTerm | |
15274 * @return {boolean} Whether a loading spinner should be shown (implies the | |
15275 * backend is querying a new search term). | |
15276 * @private | |
15277 */ | |
15278 shouldShowSpinner_: function(querying, incremental, searchTerm) { | 10891 shouldShowSpinner_: function(querying, incremental, searchTerm) { |
15279 return querying && !incremental && searchTerm != ''; | 10892 return querying && !incremental && searchTerm != ''; |
15280 }, | 10893 }, |
15281 | 10894 |
15282 /** | |
15283 * @param {string} page | |
15284 * @private | |
15285 */ | |
15286 routeDataChanged_: function(page) { | 10895 routeDataChanged_: function(page) { |
15287 this.selectedPage_ = page; | 10896 this.selectedPage_ = page; |
15288 }, | 10897 }, |
15289 | 10898 |
15290 /** | |
15291 * @param {string} selectedPage | |
15292 * @private | |
15293 */ | |
15294 selectedPageChanged_: function(selectedPage) { | 10899 selectedPageChanged_: function(selectedPage) { |
15295 this.set('routeData_.page', selectedPage); | 10900 this.set('routeData_.page', selectedPage); |
15296 }, | 10901 }, |
15297 | 10902 |
15298 /** | |
15299 * This computed binding is needed to make the iron-pages selector update when | |
15300 * the synced-device-manager is instantiated for the first time. Otherwise the | |
15301 * fallback selection will continue to be used after the corresponding item is | |
15302 * added as a child of iron-pages. | |
15303 * @param {string} selectedPage | |
15304 * @param {Array} items | |
15305 * @return {string} | |
15306 * @private | |
15307 */ | |
15308 getSelectedPage_: function(selectedPage, items) { | 10903 getSelectedPage_: function(selectedPage, items) { |
15309 return selectedPage; | 10904 return selectedPage; |
15310 }, | 10905 }, |
15311 | 10906 |
15312 /** @private */ | 10907 /** @private */ |
15313 closeDrawer_: function() { | 10908 closeDrawer_: function() { |
15314 var drawer = this.$$('#drawer'); | 10909 var drawer = this.$$('#drawer'); |
15315 if (drawer) | 10910 if (drawer) |
15316 drawer.close(); | 10911 drawer.close(); |
15317 }, | 10912 }, |
15318 }); | 10913 }); |
OLD | NEW |