| 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 |