| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | |
| 6 * @fileoverview Assertion support. | |
| 7 */ | |
| 8 | 5 |
| 9 /** | |
| 10 * Verify |condition| is truthy and return |condition| if so. | |
| 11 * @template T | |
| 12 * @param {T} condition A condition to check for truthiness. Note that this | |
| 13 * may be used to test whether a value is defined or not, and we don't want | |
| 14 * to force a cast to Boolean. | |
| 15 * @param {string=} opt_message A message to show on failure. | |
| 16 * @return {T} A non-null |condition|. | |
| 17 */ | |
| 18 function assert(condition, opt_message) { | 6 function assert(condition, opt_message) { |
| 19 if (!condition) { | 7 if (!condition) { |
| 20 var message = 'Assertion failed'; | 8 var message = 'Assertion failed'; |
| 21 if (opt_message) | 9 if (opt_message) |
| 22 message = message + ': ' + opt_message; | 10 message = message + ': ' + opt_message; |
| 23 var error = new Error(message); | 11 var error = new Error(message); |
| 24 var global = function() { return this; }(); | 12 var global = function() { return this; }(); |
| 25 if (global.traceAssertionsForTesting) | 13 if (global.traceAssertionsForTesting) |
| 26 console.warn(error.stack); | 14 console.warn(error.stack); |
| 27 throw error; | 15 throw error; |
| 28 } | 16 } |
| 29 return condition; | 17 return condition; |
| 30 } | 18 } |
| 31 | 19 |
| 32 /** | |
| 33 * Call this from places in the code that should never be reached. | |
| 34 * | |
| 35 * For example, handling all the values of enum with a switch() like this: | |
| 36 * | |
| 37 * function getValueFromEnum(enum) { | |
| 38 * switch (enum) { | |
| 39 * case ENUM_FIRST_OF_TWO: | |
| 40 * return first | |
| 41 * case ENUM_LAST_OF_TWO: | |
| 42 * return last; | |
| 43 * } | |
| 44 * assertNotReached(); | |
| 45 * return document; | |
| 46 * } | |
| 47 * | |
| 48 * This code should only be hit in the case of serious programmer error or | |
| 49 * unexpected input. | |
| 50 * | |
| 51 * @param {string=} opt_message A message to show when this is hit. | |
| 52 */ | |
| 53 function assertNotReached(opt_message) { | 20 function assertNotReached(opt_message) { |
| 54 assert(false, opt_message || 'Unreachable code hit'); | 21 assert(false, opt_message || 'Unreachable code hit'); |
| 55 } | 22 } |
| 56 | 23 |
| 57 /** | |
| 58 * @param {*} value The value to check. | |
| 59 * @param {function(new: T, ...)} type A user-defined constructor. | |
| 60 * @param {string=} opt_message A message to show when this is hit. | |
| 61 * @return {T} | |
| 62 * @template T | |
| 63 */ | |
| 64 function assertInstanceof(value, type, opt_message) { | 24 function assertInstanceof(value, type, opt_message) { |
| 65 // We don't use assert immediately here so that we avoid constructing an error | |
| 66 // message if we don't have to. | |
| 67 if (!(value instanceof type)) { | 25 if (!(value instanceof type)) { |
| 68 assertNotReached(opt_message || 'Value ' + value + | 26 assertNotReached(opt_message || 'Value ' + value + |
| 69 ' is not a[n] ' + (type.name || typeof type)); | 27 ' is not a[n] ' + (type.name || typeof type)); |
| 70 } | 28 } |
| 71 return value; | 29 return value; |
| 72 }; | 30 }; |
| 73 // Copyright 2016 The Chromium Authors. All rights reserved. | 31 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 74 // Use of this source code is governed by a BSD-style license that can be | 32 // Use of this source code is governed by a BSD-style license that can be |
| 75 // found in the LICENSE file. | 33 // found in the LICENSE file. |
| 76 | 34 |
| 77 /** | |
| 78 * @fileoverview PromiseResolver is a helper class that allows creating a | |
| 79 * Promise that will be fulfilled (resolved or rejected) some time later. | |
| 80 * | |
| 81 * Example: | |
| 82 * var resolver = new PromiseResolver(); | |
| 83 * resolver.promise.then(function(result) { | |
| 84 * console.log('resolved with', result); | |
| 85 * }); | |
| 86 * ... | |
| 87 * ... | |
| 88 * resolver.resolve({hello: 'world'}); | |
| 89 */ | |
| 90 | 35 |
| 91 /** | |
| 92 * @constructor @struct | |
| 93 * @template T | |
| 94 */ | |
| 95 function PromiseResolver() { | 36 function PromiseResolver() { |
| 96 /** @private {function(T=): void} */ | 37 /** @private {function(T=): void} */ |
| 97 this.resolve_; | 38 this.resolve_; |
| 98 | 39 |
| 99 /** @private {function(*=): void} */ | 40 /** @private {function(*=): void} */ |
| 100 this.reject_; | 41 this.reject_; |
| 101 | 42 |
| 102 /** @private {!Promise<T>} */ | 43 /** @private {!Promise<T>} */ |
| 103 this.promise_ = new Promise(function(resolve, reject) { | 44 this.promise_ = new Promise(function(resolve, reject) { |
| 104 this.resolve_ = resolve; | 45 this.resolve_ = resolve; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 116 set resolve(r) { assertNotReached(); }, | 57 set resolve(r) { assertNotReached(); }, |
| 117 | 58 |
| 118 /** @return {function(*=): void} */ | 59 /** @return {function(*=): void} */ |
| 119 get reject() { return this.reject_; }, | 60 get reject() { return this.reject_; }, |
| 120 set reject(s) { assertNotReached(); }, | 61 set reject(s) { assertNotReached(); }, |
| 121 }; | 62 }; |
| 122 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 63 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 123 // Use of this source code is governed by a BSD-style license that can be | 64 // Use of this source code is governed by a BSD-style license that can be |
| 124 // found in the LICENSE file. | 65 // found in the LICENSE file. |
| 125 | 66 |
| 126 /** | |
| 127 * The global object. | |
| 128 * @type {!Object} | |
| 129 * @const | |
| 130 */ | |
| 131 var global = this; | 67 var global = this; |
| 132 | 68 |
| 133 /** @typedef {{eventName: string, uid: number}} */ | 69 /** @typedef {{eventName: string, uid: number}} */ |
| 134 var WebUIListener; | 70 var WebUIListener; |
| 135 | 71 |
| 136 /** Platform, package, object property, and Event support. **/ | 72 /** Platform, package, object property, and Event support. **/ |
| 137 var cr = cr || function() { | 73 var cr = cr || function() { |
| 138 'use strict'; | 74 'use strict'; |
| 139 | 75 |
| 140 /** | |
| 141 * Builds an object structure for the provided namespace path, | |
| 142 * ensuring that names that already exist are not overwritten. For | |
| 143 * example: | |
| 144 * "a.b.c" -> a = {};a.b={};a.b.c={}; | |
| 145 * @param {string} name Name of the object that this file defines. | |
| 146 * @param {*=} opt_object The object to expose at the end of the path. | |
| 147 * @param {Object=} opt_objectToExportTo The object to add the path to; | |
| 148 * default is {@code global}. | |
| 149 * @return {!Object} The last object exported (i.e. exportPath('cr.ui') | |
| 150 * returns a reference to the ui property of window.cr). | |
| 151 * @private | |
| 152 */ | |
| 153 function exportPath(name, opt_object, opt_objectToExportTo) { | 76 function exportPath(name, opt_object, opt_objectToExportTo) { |
| 154 var parts = name.split('.'); | 77 var parts = name.split('.'); |
| 155 var cur = opt_objectToExportTo || global; | 78 var cur = opt_objectToExportTo || global; |
| 156 | 79 |
| 157 for (var part; parts.length && (part = parts.shift());) { | 80 for (var part; parts.length && (part = parts.shift());) { |
| 158 if (!parts.length && opt_object !== undefined) { | 81 if (!parts.length && opt_object !== undefined) { |
| 159 // last part and we have an object; use it | |
| 160 cur[part] = opt_object; | 82 cur[part] = opt_object; |
| 161 } else if (part in cur) { | 83 } else if (part in cur) { |
| 162 cur = cur[part]; | 84 cur = cur[part]; |
| 163 } else { | 85 } else { |
| 164 cur = cur[part] = {}; | 86 cur = cur[part] = {}; |
| 165 } | 87 } |
| 166 } | 88 } |
| 167 return cur; | 89 return cur; |
| 168 } | 90 } |
| 169 | 91 |
| 170 /** | |
| 171 * Fires a property change event on the target. | |
| 172 * @param {EventTarget} target The target to dispatch the event on. | |
| 173 * @param {string} propertyName The name of the property that changed. | |
| 174 * @param {*} newValue The new value for the property. | |
| 175 * @param {*} oldValue The old value for the property. | |
| 176 */ | |
| 177 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { | 92 function dispatchPropertyChange(target, propertyName, newValue, oldValue) { |
| 178 var e = new Event(propertyName + 'Change'); | 93 var e = new Event(propertyName + 'Change'); |
| 179 e.propertyName = propertyName; | 94 e.propertyName = propertyName; |
| 180 e.newValue = newValue; | 95 e.newValue = newValue; |
| 181 e.oldValue = oldValue; | 96 e.oldValue = oldValue; |
| 182 target.dispatchEvent(e); | 97 target.dispatchEvent(e); |
| 183 } | 98 } |
| 184 | 99 |
| 185 /** | |
| 186 * Converts a camelCase javascript property name to a hyphenated-lower-case | |
| 187 * attribute name. | |
| 188 * @param {string} jsName The javascript camelCase property name. | |
| 189 * @return {string} The equivalent hyphenated-lower-case attribute name. | |
| 190 */ | |
| 191 function getAttributeName(jsName) { | 100 function getAttributeName(jsName) { |
| 192 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); | 101 return jsName.replace(/([A-Z])/g, '-$1').toLowerCase(); |
| 193 } | 102 } |
| 194 | 103 |
| 195 /** | |
| 196 * The kind of property to define in {@code defineProperty}. | |
| 197 * @enum {string} | |
| 198 * @const | |
| 199 */ | |
| 200 var PropertyKind = { | 104 var PropertyKind = { |
| 201 /** | |
| 202 * Plain old JS property where the backing data is stored as a "private" | |
| 203 * field on the object. | |
| 204 * Use for properties of any type. Type will not be checked. | |
| 205 */ | |
| 206 JS: 'js', | 105 JS: 'js', |
| 207 | 106 |
| 208 /** | |
| 209 * The property backing data is stored as an attribute on an element. | |
| 210 * Use only for properties of type {string}. | |
| 211 */ | |
| 212 ATTR: 'attr', | 107 ATTR: 'attr', |
| 213 | 108 |
| 214 /** | |
| 215 * The property backing data is stored as an attribute on an element. If the | |
| 216 * element has the attribute then the value is true. | |
| 217 * Use only for properties of type {boolean}. | |
| 218 */ | |
| 219 BOOL_ATTR: 'boolAttr' | 109 BOOL_ATTR: 'boolAttr' |
| 220 }; | 110 }; |
| 221 | 111 |
| 222 /** | |
| 223 * Helper function for defineProperty that returns the getter to use for the | |
| 224 * property. | |
| 225 * @param {string} name The name of the property. | |
| 226 * @param {PropertyKind} kind The kind of the property. | |
| 227 * @return {function():*} The getter for the property. | |
| 228 */ | |
| 229 function getGetter(name, kind) { | 112 function getGetter(name, kind) { |
| 230 switch (kind) { | 113 switch (kind) { |
| 231 case PropertyKind.JS: | 114 case PropertyKind.JS: |
| 232 var privateName = name + '_'; | 115 var privateName = name + '_'; |
| 233 return function() { | 116 return function() { |
| 234 return this[privateName]; | 117 return this[privateName]; |
| 235 }; | 118 }; |
| 236 case PropertyKind.ATTR: | 119 case PropertyKind.ATTR: |
| 237 var attributeName = getAttributeName(name); | 120 var attributeName = getAttributeName(name); |
| 238 return function() { | 121 return function() { |
| 239 return this.getAttribute(attributeName); | 122 return this.getAttribute(attributeName); |
| 240 }; | 123 }; |
| 241 case PropertyKind.BOOL_ATTR: | 124 case PropertyKind.BOOL_ATTR: |
| 242 var attributeName = getAttributeName(name); | 125 var attributeName = getAttributeName(name); |
| 243 return function() { | 126 return function() { |
| 244 return this.hasAttribute(attributeName); | 127 return this.hasAttribute(attributeName); |
| 245 }; | 128 }; |
| 246 } | 129 } |
| 247 | 130 |
| 248 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax | |
| 249 // the browser/unit tests to preprocess this file through grit. | |
| 250 throw 'not reached'; | 131 throw 'not reached'; |
| 251 } | 132 } |
| 252 | 133 |
| 253 /** | |
| 254 * Helper function for defineProperty that returns the setter of the right | |
| 255 * kind. | |
| 256 * @param {string} name The name of the property we are defining the setter | |
| 257 * for. | |
| 258 * @param {PropertyKind} kind The kind of property we are getting the | |
| 259 * setter for. | |
| 260 * @param {function(*, *):void=} opt_setHook A function to run after the | |
| 261 * property is set, but before the propertyChange event is fired. | |
| 262 * @return {function(*):void} The function to use as a setter. | |
| 263 */ | |
| 264 function getSetter(name, kind, opt_setHook) { | 134 function getSetter(name, kind, opt_setHook) { |
| 265 switch (kind) { | 135 switch (kind) { |
| 266 case PropertyKind.JS: | 136 case PropertyKind.JS: |
| 267 var privateName = name + '_'; | 137 var privateName = name + '_'; |
| 268 return function(value) { | 138 return function(value) { |
| 269 var oldValue = this[name]; | 139 var oldValue = this[name]; |
| 270 if (value !== oldValue) { | 140 if (value !== oldValue) { |
| 271 this[privateName] = value; | 141 this[privateName] = value; |
| 272 if (opt_setHook) | 142 if (opt_setHook) |
| 273 opt_setHook.call(this, value, oldValue); | 143 opt_setHook.call(this, value, oldValue); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 299 this.setAttribute(attributeName, name); | 169 this.setAttribute(attributeName, name); |
| 300 else | 170 else |
| 301 this.removeAttribute(attributeName); | 171 this.removeAttribute(attributeName); |
| 302 if (opt_setHook) | 172 if (opt_setHook) |
| 303 opt_setHook.call(this, value, oldValue); | 173 opt_setHook.call(this, value, oldValue); |
| 304 dispatchPropertyChange(this, name, value, oldValue); | 174 dispatchPropertyChange(this, name, value, oldValue); |
| 305 } | 175 } |
| 306 }; | 176 }; |
| 307 } | 177 } |
| 308 | 178 |
| 309 // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax | |
| 310 // the browser/unit tests to preprocess this file through grit. | |
| 311 throw 'not reached'; | 179 throw 'not reached'; |
| 312 } | 180 } |
| 313 | 181 |
| 314 /** | |
| 315 * Defines a property on an object. When the setter changes the value a | |
| 316 * property change event with the type {@code name + 'Change'} is fired. | |
| 317 * @param {!Object} obj The object to define the property for. | |
| 318 * @param {string} name The name of the property. | |
| 319 * @param {PropertyKind=} opt_kind What kind of underlying storage to use. | |
| 320 * @param {function(*, *):void=} opt_setHook A function to run after the | |
| 321 * property is set, but before the propertyChange event is fired. | |
| 322 */ | |
| 323 function defineProperty(obj, name, opt_kind, opt_setHook) { | 182 function defineProperty(obj, name, opt_kind, opt_setHook) { |
| 324 if (typeof obj == 'function') | 183 if (typeof obj == 'function') |
| 325 obj = obj.prototype; | 184 obj = obj.prototype; |
| 326 | 185 |
| 327 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); | 186 var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS); |
| 328 | 187 |
| 329 if (!obj.__lookupGetter__(name)) | 188 if (!obj.__lookupGetter__(name)) |
| 330 obj.__defineGetter__(name, getGetter(name, kind)); | 189 obj.__defineGetter__(name, getGetter(name, kind)); |
| 331 | 190 |
| 332 if (!obj.__lookupSetter__(name)) | 191 if (!obj.__lookupSetter__(name)) |
| 333 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); | 192 obj.__defineSetter__(name, getSetter(name, kind, opt_setHook)); |
| 334 } | 193 } |
| 335 | 194 |
| 336 /** | |
| 337 * Counter for use with createUid | |
| 338 */ | |
| 339 var uidCounter = 1; | 195 var uidCounter = 1; |
| 340 | 196 |
| 341 /** | |
| 342 * @return {number} A new unique ID. | |
| 343 */ | |
| 344 function createUid() { | 197 function createUid() { |
| 345 return uidCounter++; | 198 return uidCounter++; |
| 346 } | 199 } |
| 347 | 200 |
| 348 /** | |
| 349 * Returns a unique ID for the item. This mutates the item so it needs to be | |
| 350 * an object | |
| 351 * @param {!Object} item The item to get the unique ID for. | |
| 352 * @return {number} The unique ID for the item. | |
| 353 */ | |
| 354 function getUid(item) { | 201 function getUid(item) { |
| 355 if (item.hasOwnProperty('uid')) | 202 if (item.hasOwnProperty('uid')) |
| 356 return item.uid; | 203 return item.uid; |
| 357 return item.uid = createUid(); | 204 return item.uid = createUid(); |
| 358 } | 205 } |
| 359 | 206 |
| 360 /** | |
| 361 * Dispatches a simple event on an event target. | |
| 362 * @param {!EventTarget} target The event target to dispatch the event on. | |
| 363 * @param {string} type The type of the event. | |
| 364 * @param {boolean=} opt_bubbles Whether the event bubbles or not. | |
| 365 * @param {boolean=} opt_cancelable Whether the default action of the event | |
| 366 * can be prevented. Default is true. | |
| 367 * @return {boolean} If any of the listeners called {@code preventDefault} | |
| 368 * during the dispatch this will return false. | |
| 369 */ | |
| 370 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { | 207 function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { |
| 371 var e = new Event(type, { | 208 var e = new Event(type, { |
| 372 bubbles: opt_bubbles, | 209 bubbles: opt_bubbles, |
| 373 cancelable: opt_cancelable === undefined || opt_cancelable | 210 cancelable: opt_cancelable === undefined || opt_cancelable |
| 374 }); | 211 }); |
| 375 return target.dispatchEvent(e); | 212 return target.dispatchEvent(e); |
| 376 } | 213 } |
| 377 | 214 |
| 378 /** | |
| 379 * Calls |fun| and adds all the fields of the returned object to the object | |
| 380 * named by |name|. For example, cr.define('cr.ui', function() { | |
| 381 * function List() { | |
| 382 * ... | |
| 383 * } | |
| 384 * function ListItem() { | |
| 385 * ... | |
| 386 * } | |
| 387 * return { | |
| 388 * List: List, | |
| 389 * ListItem: ListItem, | |
| 390 * }; | |
| 391 * }); | |
| 392 * defines the functions cr.ui.List and cr.ui.ListItem. | |
| 393 * @param {string} name The name of the object that we are adding fields to. | |
| 394 * @param {!Function} fun The function that will return an object containing | |
| 395 * the names and values of the new fields. | |
| 396 */ | |
| 397 function define(name, fun) { | 215 function define(name, fun) { |
| 398 var obj = exportPath(name); | 216 var obj = exportPath(name); |
| 399 var exports = fun(); | 217 var exports = fun(); |
| 400 for (var propertyName in exports) { | 218 for (var propertyName in exports) { |
| 401 // Maybe we should check the prototype chain here? The current usage | |
| 402 // pattern is always using an object literal so we only care about own | |
| 403 // properties. | |
| 404 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, | 219 var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, |
| 405 propertyName); | 220 propertyName); |
| 406 if (propertyDescriptor) | 221 if (propertyDescriptor) |
| 407 Object.defineProperty(obj, propertyName, propertyDescriptor); | 222 Object.defineProperty(obj, propertyName, propertyDescriptor); |
| 408 } | 223 } |
| 409 } | 224 } |
| 410 | 225 |
| 411 /** | |
| 412 * Adds a {@code getInstance} static method that always return the same | |
| 413 * instance object. | |
| 414 * @param {!Function} ctor The constructor for the class to add the static | |
| 415 * method to. | |
| 416 */ | |
| 417 function addSingletonGetter(ctor) { | 226 function addSingletonGetter(ctor) { |
| 418 ctor.getInstance = function() { | 227 ctor.getInstance = function() { |
| 419 return ctor.instance_ || (ctor.instance_ = new ctor()); | 228 return ctor.instance_ || (ctor.instance_ = new ctor()); |
| 420 }; | 229 }; |
| 421 } | 230 } |
| 422 | 231 |
| 423 /** | |
| 424 * Forwards public APIs to private implementations. | |
| 425 * @param {Function} ctor Constructor that have private implementations in its | |
| 426 * prototype. | |
| 427 * @param {Array<string>} methods List of public method names that have their | |
| 428 * underscored counterparts in constructor's prototype. | |
| 429 * @param {string=} opt_target Selector for target node. | |
| 430 */ | |
| 431 function makePublic(ctor, methods, opt_target) { | 232 function makePublic(ctor, methods, opt_target) { |
| 432 methods.forEach(function(method) { | 233 methods.forEach(function(method) { |
| 433 ctor[method] = function() { | 234 ctor[method] = function() { |
| 434 var target = opt_target ? document.getElementById(opt_target) : | 235 var target = opt_target ? document.getElementById(opt_target) : |
| 435 ctor.getInstance(); | 236 ctor.getInstance(); |
| 436 return target[method + '_'].apply(target, arguments); | 237 return target[method + '_'].apply(target, arguments); |
| 437 }; | 238 }; |
| 438 }); | 239 }); |
| 439 } | 240 } |
| 440 | 241 |
| 441 /** | |
| 442 * The mapping used by the sendWithPromise mechanism to tie the Promise | |
| 443 * returned to callers with the corresponding WebUI response. The mapping is | |
| 444 * from ID to the PromiseResolver helper; the ID is generated by | |
| 445 * sendWithPromise and is unique across all invocations of said method. | |
| 446 * @type {!Object<!PromiseResolver>} | |
| 447 */ | |
| 448 var chromeSendResolverMap = {}; | 242 var chromeSendResolverMap = {}; |
| 449 | 243 |
| 450 /** | |
| 451 * The named method the WebUI handler calls directly in response to a | |
| 452 * chrome.send call that expects a response. The handler requires no knowledge | |
| 453 * of the specific name of this method, as the name is passed to the handler | |
| 454 * as the first argument in the arguments list of chrome.send. The handler | |
| 455 * must pass the ID, also sent via the chrome.send arguments list, as the | |
| 456 * first argument of the JS invocation; additionally, the handler may | |
| 457 * supply any number of other arguments that will be included in the response. | |
| 458 * @param {string} id The unique ID identifying the Promise this response is | |
| 459 * tied to. | |
| 460 * @param {boolean} isSuccess Whether the request was successful. | |
| 461 * @param {*} response The response as sent from C++. | |
| 462 */ | |
| 463 function webUIResponse(id, isSuccess, response) { | 244 function webUIResponse(id, isSuccess, response) { |
| 464 var resolver = chromeSendResolverMap[id]; | 245 var resolver = chromeSendResolverMap[id]; |
| 465 delete chromeSendResolverMap[id]; | 246 delete chromeSendResolverMap[id]; |
| 466 | 247 |
| 467 if (isSuccess) | 248 if (isSuccess) |
| 468 resolver.resolve(response); | 249 resolver.resolve(response); |
| 469 else | 250 else |
| 470 resolver.reject(response); | 251 resolver.reject(response); |
| 471 } | 252 } |
| 472 | 253 |
| 473 /** | |
| 474 * A variation of chrome.send, suitable for messages that expect a single | |
| 475 * response from C++. | |
| 476 * @param {string} methodName The name of the WebUI handler API. | |
| 477 * @param {...*} var_args Varibale number of arguments to be forwarded to the | |
| 478 * C++ call. | |
| 479 * @return {!Promise} | |
| 480 */ | |
| 481 function sendWithPromise(methodName, var_args) { | 254 function sendWithPromise(methodName, var_args) { |
| 482 var args = Array.prototype.slice.call(arguments, 1); | 255 var args = Array.prototype.slice.call(arguments, 1); |
| 483 var promiseResolver = new PromiseResolver(); | 256 var promiseResolver = new PromiseResolver(); |
| 484 var id = methodName + '_' + createUid(); | 257 var id = methodName + '_' + createUid(); |
| 485 chromeSendResolverMap[id] = promiseResolver; | 258 chromeSendResolverMap[id] = promiseResolver; |
| 486 chrome.send(methodName, [id].concat(args)); | 259 chrome.send(methodName, [id].concat(args)); |
| 487 return promiseResolver.promise; | 260 return promiseResolver.promise; |
| 488 } | 261 } |
| 489 | 262 |
| 490 /** | |
| 491 * A map of maps associating event names with listeners. The 2nd level map | |
| 492 * associates a listener ID with the callback function, such that individual | |
| 493 * listeners can be removed from an event without affecting other listeners of | |
| 494 * the same event. | |
| 495 * @type {!Object<!Object<!Function>>} | |
| 496 */ | |
| 497 var webUIListenerMap = {}; | 263 var webUIListenerMap = {}; |
| 498 | 264 |
| 499 /** | |
| 500 * The named method the WebUI handler calls directly when an event occurs. | |
| 501 * The WebUI handler must supply the name of the event as the first argument | |
| 502 * of the JS invocation; additionally, the handler may supply any number of | |
| 503 * other arguments that will be forwarded to the listener callbacks. | |
| 504 * @param {string} event The name of the event that has occurred. | |
| 505 * @param {...*} var_args Additional arguments passed from C++. | |
| 506 */ | |
| 507 function webUIListenerCallback(event, var_args) { | 265 function webUIListenerCallback(event, var_args) { |
| 508 var eventListenersMap = webUIListenerMap[event]; | 266 var eventListenersMap = webUIListenerMap[event]; |
| 509 if (!eventListenersMap) { | 267 if (!eventListenersMap) { |
| 510 // C++ event sent for an event that has no listeners. | |
| 511 // TODO(dpapad): Should a warning be displayed here? | |
| 512 return; | 268 return; |
| 513 } | 269 } |
| 514 | 270 |
| 515 var args = Array.prototype.slice.call(arguments, 1); | 271 var args = Array.prototype.slice.call(arguments, 1); |
| 516 for (var listenerId in eventListenersMap) { | 272 for (var listenerId in eventListenersMap) { |
| 517 eventListenersMap[listenerId].apply(null, args); | 273 eventListenersMap[listenerId].apply(null, args); |
| 518 } | 274 } |
| 519 } | 275 } |
| 520 | 276 |
| 521 /** | |
| 522 * Registers a listener for an event fired from WebUI handlers. Any number of | |
| 523 * listeners may register for a single event. | |
| 524 * @param {string} eventName The event to listen to. | |
| 525 * @param {!Function} callback The callback run when the event is fired. | |
| 526 * @return {!WebUIListener} An object to be used for removing a listener via | |
| 527 * cr.removeWebUIListener. Should be treated as read-only. | |
| 528 */ | |
| 529 function addWebUIListener(eventName, callback) { | 277 function addWebUIListener(eventName, callback) { |
| 530 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {}; | 278 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {}; |
| 531 var uid = createUid(); | 279 var uid = createUid(); |
| 532 webUIListenerMap[eventName][uid] = callback; | 280 webUIListenerMap[eventName][uid] = callback; |
| 533 return {eventName: eventName, uid: uid}; | 281 return {eventName: eventName, uid: uid}; |
| 534 } | 282 } |
| 535 | 283 |
| 536 /** | |
| 537 * Removes a listener. Does nothing if the specified listener is not found. | |
| 538 * @param {!WebUIListener} listener The listener to be removed (as returned by | |
| 539 * addWebUIListener). | |
| 540 * @return {boolean} Whether the given listener was found and actually | |
| 541 * removed. | |
| 542 */ | |
| 543 function removeWebUIListener(listener) { | 284 function removeWebUIListener(listener) { |
| 544 var listenerExists = webUIListenerMap[listener.eventName] && | 285 var listenerExists = webUIListenerMap[listener.eventName] && |
| 545 webUIListenerMap[listener.eventName][listener.uid]; | 286 webUIListenerMap[listener.eventName][listener.uid]; |
| 546 if (listenerExists) { | 287 if (listenerExists) { |
| 547 delete webUIListenerMap[listener.eventName][listener.uid]; | 288 delete webUIListenerMap[listener.eventName][listener.uid]; |
| 548 return true; | 289 return true; |
| 549 } | 290 } |
| 550 return false; | 291 return false; |
| 551 } | 292 } |
| 552 | 293 |
| 553 return { | 294 return { |
| 554 addSingletonGetter: addSingletonGetter, | 295 addSingletonGetter: addSingletonGetter, |
| 555 createUid: createUid, | 296 createUid: createUid, |
| 556 define: define, | 297 define: define, |
| 557 defineProperty: defineProperty, | 298 defineProperty: defineProperty, |
| 558 dispatchPropertyChange: dispatchPropertyChange, | 299 dispatchPropertyChange: dispatchPropertyChange, |
| 559 dispatchSimpleEvent: dispatchSimpleEvent, | 300 dispatchSimpleEvent: dispatchSimpleEvent, |
| 560 exportPath: exportPath, | 301 exportPath: exportPath, |
| 561 getUid: getUid, | 302 getUid: getUid, |
| 562 makePublic: makePublic, | 303 makePublic: makePublic, |
| 563 PropertyKind: PropertyKind, | 304 PropertyKind: PropertyKind, |
| 564 | 305 |
| 565 // C++ <-> JS communication related methods. | |
| 566 addWebUIListener: addWebUIListener, | 306 addWebUIListener: addWebUIListener, |
| 567 removeWebUIListener: removeWebUIListener, | 307 removeWebUIListener: removeWebUIListener, |
| 568 sendWithPromise: sendWithPromise, | 308 sendWithPromise: sendWithPromise, |
| 569 webUIListenerCallback: webUIListenerCallback, | 309 webUIListenerCallback: webUIListenerCallback, |
| 570 webUIResponse: webUIResponse, | 310 webUIResponse: webUIResponse, |
| 571 | 311 |
| 572 get doc() { | 312 get doc() { |
| 573 return document; | 313 return document; |
| 574 }, | 314 }, |
| 575 | 315 |
| (...skipping 27 matching lines...) Expand all Loading... |
| 603 return /iPad|iPhone|iPod/.test(navigator.platform); | 343 return /iPad|iPhone|iPod/.test(navigator.platform); |
| 604 } | 344 } |
| 605 }; | 345 }; |
| 606 }(); | 346 }(); |
| 607 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 347 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 608 // Use of this source code is governed by a BSD-style license that can be | 348 // Use of this source code is governed by a BSD-style license that can be |
| 609 // found in the LICENSE file. | 349 // found in the LICENSE file. |
| 610 | 350 |
| 611 cr.define('cr.ui', function() { | 351 cr.define('cr.ui', function() { |
| 612 | 352 |
| 613 /** | |
| 614 * Decorates elements as an instance of a class. | |
| 615 * @param {string|!Element} source The way to find the element(s) to decorate. | |
| 616 * If this is a string then {@code querySeletorAll} is used to find the | |
| 617 * elements to decorate. | |
| 618 * @param {!Function} constr The constructor to decorate with. The constr | |
| 619 * needs to have a {@code decorate} function. | |
| 620 */ | |
| 621 function decorate(source, constr) { | 353 function decorate(source, constr) { |
| 622 var elements; | 354 var elements; |
| 623 if (typeof source == 'string') | 355 if (typeof source == 'string') |
| 624 elements = cr.doc.querySelectorAll(source); | 356 elements = cr.doc.querySelectorAll(source); |
| 625 else | 357 else |
| 626 elements = [source]; | 358 elements = [source]; |
| 627 | 359 |
| 628 for (var i = 0, el; el = elements[i]; i++) { | 360 for (var i = 0, el; el = elements[i]; i++) { |
| 629 if (!(el instanceof constr)) | 361 if (!(el instanceof constr)) |
| 630 constr.decorate(el); | 362 constr.decorate(el); |
| 631 } | 363 } |
| 632 } | 364 } |
| 633 | 365 |
| 634 /** | |
| 635 * Helper function for creating new element for define. | |
| 636 */ | |
| 637 function createElementHelper(tagName, opt_bag) { | 366 function createElementHelper(tagName, opt_bag) { |
| 638 // Allow passing in ownerDocument to create in a different document. | |
| 639 var doc; | 367 var doc; |
| 640 if (opt_bag && opt_bag.ownerDocument) | 368 if (opt_bag && opt_bag.ownerDocument) |
| 641 doc = opt_bag.ownerDocument; | 369 doc = opt_bag.ownerDocument; |
| 642 else | 370 else |
| 643 doc = cr.doc; | 371 doc = cr.doc; |
| 644 return doc.createElement(tagName); | 372 return doc.createElement(tagName); |
| 645 } | 373 } |
| 646 | 374 |
| 647 /** | |
| 648 * Creates the constructor for a UI element class. | |
| 649 * | |
| 650 * Usage: | |
| 651 * <pre> | |
| 652 * var List = cr.ui.define('list'); | |
| 653 * List.prototype = { | |
| 654 * __proto__: HTMLUListElement.prototype, | |
| 655 * decorate: function() { | |
| 656 * ... | |
| 657 * }, | |
| 658 * ... | |
| 659 * }; | |
| 660 * </pre> | |
| 661 * | |
| 662 * @param {string|Function} tagNameOrFunction The tagName or | |
| 663 * function to use for newly created elements. If this is a function it | |
| 664 * needs to return a new element when called. | |
| 665 * @return {function(Object=):Element} The constructor function which takes | |
| 666 * an optional property bag. The function also has a static | |
| 667 * {@code decorate} method added to it. | |
| 668 */ | |
| 669 function define(tagNameOrFunction) { | 375 function define(tagNameOrFunction) { |
| 670 var createFunction, tagName; | 376 var createFunction, tagName; |
| 671 if (typeof tagNameOrFunction == 'function') { | 377 if (typeof tagNameOrFunction == 'function') { |
| 672 createFunction = tagNameOrFunction; | 378 createFunction = tagNameOrFunction; |
| 673 tagName = ''; | 379 tagName = ''; |
| 674 } else { | 380 } else { |
| 675 createFunction = createElementHelper; | 381 createFunction = createElementHelper; |
| 676 tagName = tagNameOrFunction; | 382 tagName = tagNameOrFunction; |
| 677 } | 383 } |
| 678 | 384 |
| 679 /** | |
| 680 * Creates a new UI element constructor. | |
| 681 * @param {Object=} opt_propertyBag Optional bag of properties to set on the | |
| 682 * object after created. The property {@code ownerDocument} is special | |
| 683 * cased and it allows you to create the element in a different | |
| 684 * document than the default. | |
| 685 * @constructor | |
| 686 */ | |
| 687 function f(opt_propertyBag) { | 385 function f(opt_propertyBag) { |
| 688 var el = createFunction(tagName, opt_propertyBag); | 386 var el = createFunction(tagName, opt_propertyBag); |
| 689 f.decorate(el); | 387 f.decorate(el); |
| 690 for (var propertyName in opt_propertyBag) { | 388 for (var propertyName in opt_propertyBag) { |
| 691 el[propertyName] = opt_propertyBag[propertyName]; | 389 el[propertyName] = opt_propertyBag[propertyName]; |
| 692 } | 390 } |
| 693 return el; | 391 return el; |
| 694 } | 392 } |
| 695 | 393 |
| 696 /** | |
| 697 * Decorates an element as a UI element class. | |
| 698 * @param {!Element} el The element to decorate. | |
| 699 */ | |
| 700 f.decorate = function(el) { | 394 f.decorate = function(el) { |
| 701 el.__proto__ = f.prototype; | 395 el.__proto__ = f.prototype; |
| 702 el.decorate(); | 396 el.decorate(); |
| 703 }; | 397 }; |
| 704 | 398 |
| 705 return f; | 399 return f; |
| 706 } | 400 } |
| 707 | 401 |
| 708 /** | |
| 709 * Input elements do not grow and shrink with their content. This is a simple | |
| 710 * (and not very efficient) way of handling shrinking to content with support | |
| 711 * for min width and limited by the width of the parent element. | |
| 712 * @param {!HTMLElement} el The element to limit the width for. | |
| 713 * @param {!HTMLElement} parentEl The parent element that should limit the | |
| 714 * size. | |
| 715 * @param {number} min The minimum width. | |
| 716 * @param {number=} opt_scale Optional scale factor to apply to the width. | |
| 717 */ | |
| 718 function limitInputWidth(el, parentEl, min, opt_scale) { | 402 function limitInputWidth(el, parentEl, min, opt_scale) { |
| 719 // Needs a size larger than borders | |
| 720 el.style.width = '10px'; | 403 el.style.width = '10px'; |
| 721 var doc = el.ownerDocument; | 404 var doc = el.ownerDocument; |
| 722 var win = doc.defaultView; | 405 var win = doc.defaultView; |
| 723 var computedStyle = win.getComputedStyle(el); | 406 var computedStyle = win.getComputedStyle(el); |
| 724 var parentComputedStyle = win.getComputedStyle(parentEl); | 407 var parentComputedStyle = win.getComputedStyle(parentEl); |
| 725 var rtl = computedStyle.direction == 'rtl'; | 408 var rtl = computedStyle.direction == 'rtl'; |
| 726 | 409 |
| 727 // To get the max width we get the width of the treeItem minus the position | |
| 728 // of the input. | |
| 729 var inputRect = el.getBoundingClientRect(); // box-sizing | 410 var inputRect = el.getBoundingClientRect(); // box-sizing |
| 730 var parentRect = parentEl.getBoundingClientRect(); | 411 var parentRect = parentEl.getBoundingClientRect(); |
| 731 var startPos = rtl ? parentRect.right - inputRect.right : | 412 var startPos = rtl ? parentRect.right - inputRect.right : |
| 732 inputRect.left - parentRect.left; | 413 inputRect.left - parentRect.left; |
| 733 | 414 |
| 734 // Add up border and padding of the input. | |
| 735 var inner = parseInt(computedStyle.borderLeftWidth, 10) + | 415 var inner = parseInt(computedStyle.borderLeftWidth, 10) + |
| 736 parseInt(computedStyle.paddingLeft, 10) + | 416 parseInt(computedStyle.paddingLeft, 10) + |
| 737 parseInt(computedStyle.paddingRight, 10) + | 417 parseInt(computedStyle.paddingRight, 10) + |
| 738 parseInt(computedStyle.borderRightWidth, 10); | 418 parseInt(computedStyle.borderRightWidth, 10); |
| 739 | 419 |
| 740 // We also need to subtract the padding of parent to prevent it to overflow. | |
| 741 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : | 420 var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : |
| 742 parseInt(parentComputedStyle.paddingRight, 10); | 421 parseInt(parentComputedStyle.paddingRight, 10); |
| 743 | 422 |
| 744 var max = parentEl.clientWidth - startPos - inner - parentPadding; | 423 var max = parentEl.clientWidth - startPos - inner - parentPadding; |
| 745 if (opt_scale) | 424 if (opt_scale) |
| 746 max *= opt_scale; | 425 max *= opt_scale; |
| 747 | 426 |
| 748 function limit() { | 427 function limit() { |
| 749 if (el.scrollWidth > max) { | 428 if (el.scrollWidth > max) { |
| 750 el.style.width = max + 'px'; | 429 el.style.width = max + 'px'; |
| 751 } else { | 430 } else { |
| 752 el.style.width = 0; | 431 el.style.width = 0; |
| 753 var sw = el.scrollWidth; | 432 var sw = el.scrollWidth; |
| 754 if (sw < min) { | 433 if (sw < min) { |
| 755 el.style.width = min + 'px'; | 434 el.style.width = min + 'px'; |
| 756 } else { | 435 } else { |
| 757 el.style.width = sw + 'px'; | 436 el.style.width = sw + 'px'; |
| 758 } | 437 } |
| 759 } | 438 } |
| 760 } | 439 } |
| 761 | 440 |
| 762 el.addEventListener('input', limit); | 441 el.addEventListener('input', limit); |
| 763 limit(); | 442 limit(); |
| 764 } | 443 } |
| 765 | 444 |
| 766 /** | |
| 767 * Takes a number and spits out a value CSS will be happy with. To avoid | |
| 768 * subpixel layout issues, the value is rounded to the nearest integral value. | |
| 769 * @param {number} pixels The number of pixels. | |
| 770 * @return {string} e.g. '16px'. | |
| 771 */ | |
| 772 function toCssPx(pixels) { | 445 function toCssPx(pixels) { |
| 773 if (!window.isFinite(pixels)) | 446 if (!window.isFinite(pixels)) |
| 774 console.error('Pixel value is not a number: ' + pixels); | 447 console.error('Pixel value is not a number: ' + pixels); |
| 775 return Math.round(pixels) + 'px'; | 448 return Math.round(pixels) + 'px'; |
| 776 } | 449 } |
| 777 | 450 |
| 778 /** | |
| 779 * Users complain they occasionaly use doubleclicks instead of clicks | |
| 780 * (http://crbug.com/140364). To fix it we freeze click handling for | |
| 781 * the doubleclick time interval. | |
| 782 * @param {MouseEvent} e Initial click event. | |
| 783 */ | |
| 784 function swallowDoubleClick(e) { | 451 function swallowDoubleClick(e) { |
| 785 var doc = e.target.ownerDocument; | 452 var doc = e.target.ownerDocument; |
| 786 var counter = Math.min(1, e.detail); | 453 var counter = Math.min(1, e.detail); |
| 787 function swallow(e) { | 454 function swallow(e) { |
| 788 e.stopPropagation(); | 455 e.stopPropagation(); |
| 789 e.preventDefault(); | 456 e.preventDefault(); |
| 790 } | 457 } |
| 791 function onclick(e) { | 458 function onclick(e) { |
| 792 if (e.detail > counter) { | 459 if (e.detail > counter) { |
| 793 counter = e.detail; | 460 counter = e.detail; |
| 794 // Swallow the click since it's a click inside the doubleclick timeout. | |
| 795 swallow(e); | 461 swallow(e); |
| 796 } else { | 462 } else { |
| 797 // Stop tracking clicks and let regular handling. | |
| 798 doc.removeEventListener('dblclick', swallow, true); | 463 doc.removeEventListener('dblclick', swallow, true); |
| 799 doc.removeEventListener('click', onclick, true); | 464 doc.removeEventListener('click', onclick, true); |
| 800 } | 465 } |
| 801 } | 466 } |
| 802 // The following 'click' event (if e.type == 'mouseup') mustn't be taken | |
| 803 // into account (it mustn't stop tracking clicks). Start event listening | |
| 804 // after zero timeout. | |
| 805 setTimeout(function() { | 467 setTimeout(function() { |
| 806 doc.addEventListener('click', onclick, true); | 468 doc.addEventListener('click', onclick, true); |
| 807 doc.addEventListener('dblclick', swallow, true); | 469 doc.addEventListener('dblclick', swallow, true); |
| 808 }, 0); | 470 }, 0); |
| 809 } | 471 } |
| 810 | 472 |
| 811 return { | 473 return { |
| 812 decorate: decorate, | 474 decorate: decorate, |
| 813 define: define, | 475 define: define, |
| 814 limitInputWidth: limitInputWidth, | 476 limitInputWidth: limitInputWidth, |
| 815 toCssPx: toCssPx, | 477 toCssPx: toCssPx, |
| 816 swallowDoubleClick: swallowDoubleClick | 478 swallowDoubleClick: swallowDoubleClick |
| 817 }; | 479 }; |
| 818 }); | 480 }); |
| 819 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 481 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 820 // Use of this source code is governed by a BSD-style license that can be | 482 // Use of this source code is governed by a BSD-style license that can be |
| 821 // found in the LICENSE file. | 483 // found in the LICENSE file. |
| 822 | 484 |
| 823 /** | |
| 824 * @fileoverview A command is an abstraction of an action a user can do in the | |
| 825 * UI. | |
| 826 * | |
| 827 * When the focus changes in the document for each command a canExecute event | |
| 828 * is dispatched on the active element. By listening to this event you can | |
| 829 * enable and disable the command by setting the event.canExecute property. | |
| 830 * | |
| 831 * When a command is executed a command event is dispatched on the active | |
| 832 * element. Note that you should stop the propagation after you have handled the | |
| 833 * command if there might be other command listeners higher up in the DOM tree. | |
| 834 */ | |
| 835 | 485 |
| 836 cr.define('cr.ui', function() { | 486 cr.define('cr.ui', function() { |
| 837 | 487 |
| 838 /** | |
| 839 * This is used to identify keyboard shortcuts. | |
| 840 * @param {string} shortcut The text used to describe the keys for this | |
| 841 * keyboard shortcut. | |
| 842 * @constructor | |
| 843 */ | |
| 844 function KeyboardShortcut(shortcut) { | 488 function KeyboardShortcut(shortcut) { |
| 845 var mods = {}; | 489 var mods = {}; |
| 846 var ident = ''; | 490 var ident = ''; |
| 847 shortcut.split('|').forEach(function(part) { | 491 shortcut.split('|').forEach(function(part) { |
| 848 var partLc = part.toLowerCase(); | 492 var partLc = part.toLowerCase(); |
| 849 switch (partLc) { | 493 switch (partLc) { |
| 850 case 'alt': | 494 case 'alt': |
| 851 case 'ctrl': | 495 case 'ctrl': |
| 852 case 'meta': | 496 case 'meta': |
| 853 case 'shift': | 497 case 'shift': |
| 854 mods[partLc + 'Key'] = true; | 498 mods[partLc + 'Key'] = true; |
| 855 break; | 499 break; |
| 856 default: | 500 default: |
| 857 if (ident) | 501 if (ident) |
| 858 throw Error('Invalid shortcut'); | 502 throw Error('Invalid shortcut'); |
| 859 ident = part; | 503 ident = part; |
| 860 } | 504 } |
| 861 }); | 505 }); |
| 862 | 506 |
| 863 this.ident_ = ident; | 507 this.ident_ = ident; |
| 864 this.mods_ = mods; | 508 this.mods_ = mods; |
| 865 } | 509 } |
| 866 | 510 |
| 867 KeyboardShortcut.prototype = { | 511 KeyboardShortcut.prototype = { |
| 868 /** | |
| 869 * Whether the keyboard shortcut object matches a keyboard event. | |
| 870 * @param {!Event} e The keyboard event object. | |
| 871 * @return {boolean} Whether we found a match or not. | |
| 872 */ | |
| 873 matchesEvent: function(e) { | 512 matchesEvent: function(e) { |
| 874 if (e.key == this.ident_) { | 513 if (e.key == this.ident_) { |
| 875 // All keyboard modifiers needs to match. | |
| 876 var mods = this.mods_; | 514 var mods = this.mods_; |
| 877 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { | 515 return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { |
| 878 return e[k] == !!mods[k]; | 516 return e[k] == !!mods[k]; |
| 879 }); | 517 }); |
| 880 } | 518 } |
| 881 return false; | 519 return false; |
| 882 } | 520 } |
| 883 }; | 521 }; |
| 884 | 522 |
| 885 /** | |
| 886 * Creates a new command element. | |
| 887 * @constructor | |
| 888 * @extends {HTMLElement} | |
| 889 */ | |
| 890 var Command = cr.ui.define('command'); | 523 var Command = cr.ui.define('command'); |
| 891 | 524 |
| 892 Command.prototype = { | 525 Command.prototype = { |
| 893 __proto__: HTMLElement.prototype, | 526 __proto__: HTMLElement.prototype, |
| 894 | 527 |
| 895 /** | |
| 896 * Initializes the command. | |
| 897 */ | |
| 898 decorate: function() { | 528 decorate: function() { |
| 899 CommandManager.init(assert(this.ownerDocument)); | 529 CommandManager.init(assert(this.ownerDocument)); |
| 900 | 530 |
| 901 if (this.hasAttribute('shortcut')) | 531 if (this.hasAttribute('shortcut')) |
| 902 this.shortcut = this.getAttribute('shortcut'); | 532 this.shortcut = this.getAttribute('shortcut'); |
| 903 }, | 533 }, |
| 904 | 534 |
| 905 /** | |
| 906 * Executes the command by dispatching a command event on the given element. | |
| 907 * If |element| isn't given, the active element is used instead. | |
| 908 * If the command is {@code disabled} this does nothing. | |
| 909 * @param {HTMLElement=} opt_element Optional element to dispatch event on. | |
| 910 */ | |
| 911 execute: function(opt_element) { | 535 execute: function(opt_element) { |
| 912 if (this.disabled) | 536 if (this.disabled) |
| 913 return; | 537 return; |
| 914 var doc = this.ownerDocument; | 538 var doc = this.ownerDocument; |
| 915 if (doc.activeElement) { | 539 if (doc.activeElement) { |
| 916 var e = new Event('command', {bubbles: true}); | 540 var e = new Event('command', {bubbles: true}); |
| 917 e.command = this; | 541 e.command = this; |
| 918 | 542 |
| 919 (opt_element || doc.activeElement).dispatchEvent(e); | 543 (opt_element || doc.activeElement).dispatchEvent(e); |
| 920 } | 544 } |
| 921 }, | 545 }, |
| 922 | 546 |
| 923 /** | |
| 924 * Call this when there have been changes that might change whether the | |
| 925 * command can be executed or not. | |
| 926 * @param {Node=} opt_node Node for which to actuate command state. | |
| 927 */ | |
| 928 canExecuteChange: function(opt_node) { | 547 canExecuteChange: function(opt_node) { |
| 929 dispatchCanExecuteEvent(this, | 548 dispatchCanExecuteEvent(this, |
| 930 opt_node || this.ownerDocument.activeElement); | 549 opt_node || this.ownerDocument.activeElement); |
| 931 }, | 550 }, |
| 932 | 551 |
| 933 /** | |
| 934 * The keyboard shortcut that triggers the command. This is a string | |
| 935 * consisting of a key (as reported by WebKit in keydown) as | |
| 936 * well as optional key modifiers joinded with a '|'. | |
| 937 * | |
| 938 * Multiple keyboard shortcuts can be provided by separating them by | |
| 939 * whitespace. | |
| 940 * | |
| 941 * For example: | |
| 942 * "F1" | |
| 943 * "Backspace|Meta" for Apple command backspace. | |
| 944 * "a|Ctrl" for Control A | |
| 945 * "Delete Backspace|Meta" for Delete and Command Backspace | |
| 946 * | |
| 947 * @type {string} | |
| 948 */ | |
| 949 shortcut_: '', | 552 shortcut_: '', |
| 950 get shortcut() { | 553 get shortcut() { |
| 951 return this.shortcut_; | 554 return this.shortcut_; |
| 952 }, | 555 }, |
| 953 set shortcut(shortcut) { | 556 set shortcut(shortcut) { |
| 954 var oldShortcut = this.shortcut_; | 557 var oldShortcut = this.shortcut_; |
| 955 if (shortcut !== oldShortcut) { | 558 if (shortcut !== oldShortcut) { |
| 956 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { | 559 this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { |
| 957 return new KeyboardShortcut(shortcut); | 560 return new KeyboardShortcut(shortcut); |
| 958 }); | 561 }); |
| 959 | 562 |
| 960 // Set this after the keyboardShortcuts_ since that might throw. | |
| 961 this.shortcut_ = shortcut; | 563 this.shortcut_ = shortcut; |
| 962 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, | 564 cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, |
| 963 oldShortcut); | 565 oldShortcut); |
| 964 } | 566 } |
| 965 }, | 567 }, |
| 966 | 568 |
| 967 /** | |
| 968 * Whether the event object matches the shortcut for this command. | |
| 969 * @param {!Event} e The key event object. | |
| 970 * @return {boolean} Whether it matched or not. | |
| 971 */ | |
| 972 matchesEvent: function(e) { | 569 matchesEvent: function(e) { |
| 973 if (!this.keyboardShortcuts_) | 570 if (!this.keyboardShortcuts_) |
| 974 return false; | 571 return false; |
| 975 | 572 |
| 976 return this.keyboardShortcuts_.some(function(keyboardShortcut) { | 573 return this.keyboardShortcuts_.some(function(keyboardShortcut) { |
| 977 return keyboardShortcut.matchesEvent(e); | 574 return keyboardShortcut.matchesEvent(e); |
| 978 }); | 575 }); |
| 979 }, | 576 }, |
| 980 }; | 577 }; |
| 981 | 578 |
| 982 /** | |
| 983 * The label of the command. | |
| 984 */ | |
| 985 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); | 579 cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); |
| 986 | 580 |
| 987 /** | |
| 988 * Whether the command is disabled or not. | |
| 989 */ | |
| 990 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); | 581 cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); |
| 991 | 582 |
| 992 /** | |
| 993 * Whether the command is hidden or not. | |
| 994 */ | |
| 995 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); | 583 cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); |
| 996 | 584 |
| 997 /** | |
| 998 * Whether the command is checked or not. | |
| 999 */ | |
| 1000 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR); | 585 cr.defineProperty(Command, 'checked', cr.PropertyKind.BOOL_ATTR); |
| 1001 | 586 |
| 1002 /** | |
| 1003 * The flag that prevents the shortcut text from being displayed on menu. | |
| 1004 * | |
| 1005 * If false, the keyboard shortcut text (eg. "Ctrl+X" for the cut command) | |
| 1006 * is displayed in menu when the command is assosiated with a menu item. | |
| 1007 * Otherwise, no text is displayed. | |
| 1008 */ | |
| 1009 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR); | 587 cr.defineProperty(Command, 'hideShortcutText', cr.PropertyKind.BOOL_ATTR); |
| 1010 | 588 |
| 1011 /** | |
| 1012 * Dispatches a canExecute event on the target. | |
| 1013 * @param {!cr.ui.Command} command The command that we are testing for. | |
| 1014 * @param {EventTarget} target The target element to dispatch the event on. | |
| 1015 */ | |
| 1016 function dispatchCanExecuteEvent(command, target) { | 589 function dispatchCanExecuteEvent(command, target) { |
| 1017 var e = new CanExecuteEvent(command); | 590 var e = new CanExecuteEvent(command); |
| 1018 target.dispatchEvent(e); | 591 target.dispatchEvent(e); |
| 1019 command.disabled = !e.canExecute; | 592 command.disabled = !e.canExecute; |
| 1020 } | 593 } |
| 1021 | 594 |
| 1022 /** | |
| 1023 * The command managers for different documents. | |
| 1024 */ | |
| 1025 var commandManagers = {}; | 595 var commandManagers = {}; |
| 1026 | 596 |
| 1027 /** | |
| 1028 * Keeps track of the focused element and updates the commands when the focus | |
| 1029 * changes. | |
| 1030 * @param {!Document} doc The document that we are managing the commands for. | |
| 1031 * @constructor | |
| 1032 */ | |
| 1033 function CommandManager(doc) { | 597 function CommandManager(doc) { |
| 1034 doc.addEventListener('focus', this.handleFocus_.bind(this), true); | 598 doc.addEventListener('focus', this.handleFocus_.bind(this), true); |
| 1035 // Make sure we add the listener to the bubbling phase so that elements can | |
| 1036 // prevent the command. | |
| 1037 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false); | 599 doc.addEventListener('keydown', this.handleKeyDown_.bind(this), false); |
| 1038 } | 600 } |
| 1039 | 601 |
| 1040 /** | |
| 1041 * Initializes a command manager for the document as needed. | |
| 1042 * @param {!Document} doc The document to manage the commands for. | |
| 1043 */ | |
| 1044 CommandManager.init = function(doc) { | 602 CommandManager.init = function(doc) { |
| 1045 var uid = cr.getUid(doc); | 603 var uid = cr.getUid(doc); |
| 1046 if (!(uid in commandManagers)) { | 604 if (!(uid in commandManagers)) { |
| 1047 commandManagers[uid] = new CommandManager(doc); | 605 commandManagers[uid] = new CommandManager(doc); |
| 1048 } | 606 } |
| 1049 }; | 607 }; |
| 1050 | 608 |
| 1051 CommandManager.prototype = { | 609 CommandManager.prototype = { |
| 1052 | 610 |
| 1053 /** | |
| 1054 * Handles focus changes on the document. | |
| 1055 * @param {Event} e The focus event object. | |
| 1056 * @private | |
| 1057 * @suppress {checkTypes} | |
| 1058 * TODO(vitalyp): remove the suppression. | |
| 1059 */ | |
| 1060 handleFocus_: function(e) { | 611 handleFocus_: function(e) { |
| 1061 var target = e.target; | 612 var target = e.target; |
| 1062 | 613 |
| 1063 // Ignore focus on a menu button or command item. | |
| 1064 if (target.menu || target.command) | 614 if (target.menu || target.command) |
| 1065 return; | 615 return; |
| 1066 | 616 |
| 1067 var commands = Array.prototype.slice.call( | 617 var commands = Array.prototype.slice.call( |
| 1068 target.ownerDocument.querySelectorAll('command')); | 618 target.ownerDocument.querySelectorAll('command')); |
| 1069 | 619 |
| 1070 commands.forEach(function(command) { | 620 commands.forEach(function(command) { |
| 1071 dispatchCanExecuteEvent(command, target); | 621 dispatchCanExecuteEvent(command, target); |
| 1072 }); | 622 }); |
| 1073 }, | 623 }, |
| 1074 | 624 |
| 1075 /** | |
| 1076 * Handles the keydown event and routes it to the right command. | |
| 1077 * @param {!Event} e The keydown event. | |
| 1078 */ | |
| 1079 handleKeyDown_: function(e) { | 625 handleKeyDown_: function(e) { |
| 1080 var target = e.target; | 626 var target = e.target; |
| 1081 var commands = Array.prototype.slice.call( | 627 var commands = Array.prototype.slice.call( |
| 1082 target.ownerDocument.querySelectorAll('command')); | 628 target.ownerDocument.querySelectorAll('command')); |
| 1083 | 629 |
| 1084 for (var i = 0, command; command = commands[i]; i++) { | 630 for (var i = 0, command; command = commands[i]; i++) { |
| 1085 if (command.matchesEvent(e)) { | 631 if (command.matchesEvent(e)) { |
| 1086 // When invoking a command via a shortcut, we have to manually check | |
| 1087 // if it can be executed, since focus might not have been changed | |
| 1088 // what would have updated the command's state. | |
| 1089 command.canExecuteChange(); | 632 command.canExecuteChange(); |
| 1090 | 633 |
| 1091 if (!command.disabled) { | 634 if (!command.disabled) { |
| 1092 e.preventDefault(); | 635 e.preventDefault(); |
| 1093 // We do not want any other element to handle this. | |
| 1094 e.stopPropagation(); | 636 e.stopPropagation(); |
| 1095 command.execute(); | 637 command.execute(); |
| 1096 return; | 638 return; |
| 1097 } | 639 } |
| 1098 } | 640 } |
| 1099 } | 641 } |
| 1100 } | 642 } |
| 1101 }; | 643 }; |
| 1102 | 644 |
| 1103 /** | |
| 1104 * The event type used for canExecute events. | |
| 1105 * @param {!cr.ui.Command} command The command that we are evaluating. | |
| 1106 * @extends {Event} | |
| 1107 * @constructor | |
| 1108 * @class | |
| 1109 */ | |
| 1110 function CanExecuteEvent(command) { | 645 function CanExecuteEvent(command) { |
| 1111 var e = new Event('canExecute', {bubbles: true, cancelable: true}); | 646 var e = new Event('canExecute', {bubbles: true, cancelable: true}); |
| 1112 e.__proto__ = CanExecuteEvent.prototype; | 647 e.__proto__ = CanExecuteEvent.prototype; |
| 1113 e.command = command; | 648 e.command = command; |
| 1114 return e; | 649 return e; |
| 1115 } | 650 } |
| 1116 | 651 |
| 1117 CanExecuteEvent.prototype = { | 652 CanExecuteEvent.prototype = { |
| 1118 __proto__: Event.prototype, | 653 __proto__: Event.prototype, |
| 1119 | 654 |
| 1120 /** | |
| 1121 * The current command | |
| 1122 * @type {cr.ui.Command} | |
| 1123 */ | |
| 1124 command: null, | 655 command: null, |
| 1125 | 656 |
| 1126 /** | |
| 1127 * Whether the target can execute the command. Setting this also stops the | |
| 1128 * propagation and prevents the default. Callers can tell if an event has | |
| 1129 * been handled via |this.defaultPrevented|. | |
| 1130 * @type {boolean} | |
| 1131 */ | |
| 1132 canExecute_: false, | 657 canExecute_: false, |
| 1133 get canExecute() { | 658 get canExecute() { |
| 1134 return this.canExecute_; | 659 return this.canExecute_; |
| 1135 }, | 660 }, |
| 1136 set canExecute(canExecute) { | 661 set canExecute(canExecute) { |
| 1137 this.canExecute_ = !!canExecute; | 662 this.canExecute_ = !!canExecute; |
| 1138 this.stopPropagation(); | 663 this.stopPropagation(); |
| 1139 this.preventDefault(); | 664 this.preventDefault(); |
| 1140 } | 665 } |
| 1141 }; | 666 }; |
| 1142 | 667 |
| 1143 // Export | |
| 1144 return { | 668 return { |
| 1145 Command: Command, | 669 Command: Command, |
| 1146 CanExecuteEvent: CanExecuteEvent | 670 CanExecuteEvent: CanExecuteEvent |
| 1147 }; | 671 }; |
| 1148 }); | 672 }); |
| 1149 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 673 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 1150 // Use of this source code is governed by a BSD-style license that can be | 674 // Use of this source code is governed by a BSD-style license that can be |
| 1151 // found in the LICENSE file. | 675 // found in the LICENSE file. |
| 1152 | 676 |
| 1153 // <include src-disabled="assert.js"> | |
| 1154 | 677 |
| 1155 /** | |
| 1156 * Alias for document.getElementById. Found elements must be HTMLElements. | |
| 1157 * @param {string} id The ID of the element to find. | |
| 1158 * @return {HTMLElement} The found element or null if not found. | |
| 1159 */ | |
| 1160 function $(id) { | 678 function $(id) { |
| 1161 var el = document.getElementById(id); | 679 var el = document.getElementById(id); |
| 1162 return el ? assertInstanceof(el, HTMLElement) : null; | 680 return el ? assertInstanceof(el, HTMLElement) : null; |
| 1163 } | 681 } |
| 1164 | 682 |
| 1165 // TODO(devlin): This should return SVGElement, but closure compiler is missing | |
| 1166 // those externs. | |
| 1167 /** | |
| 1168 * Alias for document.getElementById. Found elements must be SVGElements. | |
| 1169 * @param {string} id The ID of the element to find. | |
| 1170 * @return {Element} The found element or null if not found. | |
| 1171 */ | |
| 1172 function getSVGElement(id) { | 683 function getSVGElement(id) { |
| 1173 var el = document.getElementById(id); | 684 var el = document.getElementById(id); |
| 1174 return el ? assertInstanceof(el, Element) : null; | 685 return el ? assertInstanceof(el, Element) : null; |
| 1175 } | 686 } |
| 1176 | 687 |
| 1177 /** | |
| 1178 * Add an accessible message to the page that will be announced to | |
| 1179 * users who have spoken feedback on, but will be invisible to all | |
| 1180 * other users. It's removed right away so it doesn't clutter the DOM. | |
| 1181 * @param {string} msg The text to be pronounced. | |
| 1182 */ | |
| 1183 function announceAccessibleMessage(msg) { | 688 function announceAccessibleMessage(msg) { |
| 1184 var element = document.createElement('div'); | 689 var element = document.createElement('div'); |
| 1185 element.setAttribute('aria-live', 'polite'); | 690 element.setAttribute('aria-live', 'polite'); |
| 1186 element.style.position = 'relative'; | 691 element.style.position = 'relative'; |
| 1187 element.style.left = '-9999px'; | 692 element.style.left = '-9999px'; |
| 1188 element.style.height = '0px'; | 693 element.style.height = '0px'; |
| 1189 element.innerText = msg; | 694 element.innerText = msg; |
| 1190 document.body.appendChild(element); | 695 document.body.appendChild(element); |
| 1191 window.setTimeout(function() { | 696 window.setTimeout(function() { |
| 1192 document.body.removeChild(element); | 697 document.body.removeChild(element); |
| 1193 }, 0); | 698 }, 0); |
| 1194 } | 699 } |
| 1195 | 700 |
| 1196 /** | |
| 1197 * Generates a CSS url string. | |
| 1198 * @param {string} s The URL to generate the CSS url for. | |
| 1199 * @return {string} The CSS url string. | |
| 1200 */ | |
| 1201 function url(s) { | 701 function url(s) { |
| 1202 // http://www.w3.org/TR/css3-values/#uris | |
| 1203 // Parentheses, commas, whitespace characters, single quotes (') and double | |
| 1204 // quotes (") appearing in a URI must be escaped with a backslash | |
| 1205 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); | 702 var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); |
| 1206 // WebKit has a bug when it comes to URLs that end with \ | |
| 1207 // https://bugs.webkit.org/show_bug.cgi?id=28885 | |
| 1208 if (/\\\\$/.test(s2)) { | 703 if (/\\\\$/.test(s2)) { |
| 1209 // Add a space to work around the WebKit bug. | |
| 1210 s2 += ' '; | 704 s2 += ' '; |
| 1211 } | 705 } |
| 1212 return 'url("' + s2 + '")'; | 706 return 'url("' + s2 + '")'; |
| 1213 } | 707 } |
| 1214 | 708 |
| 1215 /** | |
| 1216 * Parses query parameters from Location. | |
| 1217 * @param {Location} location The URL to generate the CSS url for. | |
| 1218 * @return {Object} Dictionary containing name value pairs for URL | |
| 1219 */ | |
| 1220 function parseQueryParams(location) { | 709 function parseQueryParams(location) { |
| 1221 var params = {}; | 710 var params = {}; |
| 1222 var query = unescape(location.search.substring(1)); | 711 var query = unescape(location.search.substring(1)); |
| 1223 var vars = query.split('&'); | 712 var vars = query.split('&'); |
| 1224 for (var i = 0; i < vars.length; i++) { | 713 for (var i = 0; i < vars.length; i++) { |
| 1225 var pair = vars[i].split('='); | 714 var pair = vars[i].split('='); |
| 1226 params[pair[0]] = pair[1]; | 715 params[pair[0]] = pair[1]; |
| 1227 } | 716 } |
| 1228 return params; | 717 return params; |
| 1229 } | 718 } |
| 1230 | 719 |
| 1231 /** | |
| 1232 * Creates a new URL by appending or replacing the given query key and value. | |
| 1233 * Not supporting URL with username and password. | |
| 1234 * @param {Location} location The original URL. | |
| 1235 * @param {string} key The query parameter name. | |
| 1236 * @param {string} value The query parameter value. | |
| 1237 * @return {string} The constructed new URL. | |
| 1238 */ | |
| 1239 function setQueryParam(location, key, value) { | 720 function setQueryParam(location, key, value) { |
| 1240 var query = parseQueryParams(location); | 721 var query = parseQueryParams(location); |
| 1241 query[encodeURIComponent(key)] = encodeURIComponent(value); | 722 query[encodeURIComponent(key)] = encodeURIComponent(value); |
| 1242 | 723 |
| 1243 var newQuery = ''; | 724 var newQuery = ''; |
| 1244 for (var q in query) { | 725 for (var q in query) { |
| 1245 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; | 726 newQuery += (newQuery ? '&' : '?') + q + '=' + query[q]; |
| 1246 } | 727 } |
| 1247 | 728 |
| 1248 return location.origin + location.pathname + newQuery + location.hash; | 729 return location.origin + location.pathname + newQuery + location.hash; |
| 1249 } | 730 } |
| 1250 | 731 |
| 1251 /** | |
| 1252 * @param {Node} el A node to search for ancestors with |className|. | |
| 1253 * @param {string} className A class to search for. | |
| 1254 * @return {Element} A node with class of |className| or null if none is found. | |
| 1255 */ | |
| 1256 function findAncestorByClass(el, className) { | 732 function findAncestorByClass(el, className) { |
| 1257 return /** @type {Element} */(findAncestor(el, function(el) { | 733 return /** @type {Element} */(findAncestor(el, function(el) { |
| 1258 return el.classList && el.classList.contains(className); | 734 return el.classList && el.classList.contains(className); |
| 1259 })); | 735 })); |
| 1260 } | 736 } |
| 1261 | 737 |
| 1262 /** | |
| 1263 * Return the first ancestor for which the {@code predicate} returns true. | |
| 1264 * @param {Node} node The node to check. | |
| 1265 * @param {function(Node):boolean} predicate The function that tests the | |
| 1266 * nodes. | |
| 1267 * @return {Node} The found ancestor or null if not found. | |
| 1268 */ | |
| 1269 function findAncestor(node, predicate) { | 738 function findAncestor(node, predicate) { |
| 1270 var last = false; | 739 var last = false; |
| 1271 while (node != null && !(last = predicate(node))) { | 740 while (node != null && !(last = predicate(node))) { |
| 1272 node = node.parentNode; | 741 node = node.parentNode; |
| 1273 } | 742 } |
| 1274 return last ? node : null; | 743 return last ? node : null; |
| 1275 } | 744 } |
| 1276 | 745 |
| 1277 function swapDomNodes(a, b) { | 746 function swapDomNodes(a, b) { |
| 1278 var afterA = a.nextSibling; | 747 var afterA = a.nextSibling; |
| 1279 if (afterA == b) { | 748 if (afterA == b) { |
| 1280 swapDomNodes(b, a); | 749 swapDomNodes(b, a); |
| 1281 return; | 750 return; |
| 1282 } | 751 } |
| 1283 var aParent = a.parentNode; | 752 var aParent = a.parentNode; |
| 1284 b.parentNode.replaceChild(a, b); | 753 b.parentNode.replaceChild(a, b); |
| 1285 aParent.insertBefore(b, afterA); | 754 aParent.insertBefore(b, afterA); |
| 1286 } | 755 } |
| 1287 | 756 |
| 1288 /** | |
| 1289 * Disables text selection and dragging, with optional whitelist callbacks. | |
| 1290 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function | |
| 1291 * is defined and returns true, the onselectionstart event will be | |
| 1292 * surpressed. | |
| 1293 * @param {function(Event):boolean=} opt_allowDragStart Unless this function | |
| 1294 * is defined and returns true, the ondragstart event will be surpressed. | |
| 1295 */ | |
| 1296 function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { | 757 function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) { |
| 1297 // Disable text selection. | |
| 1298 document.onselectstart = function(e) { | 758 document.onselectstart = function(e) { |
| 1299 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) | 759 if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e))) |
| 1300 e.preventDefault(); | 760 e.preventDefault(); |
| 1301 }; | 761 }; |
| 1302 | 762 |
| 1303 // Disable dragging. | |
| 1304 document.ondragstart = function(e) { | 763 document.ondragstart = function(e) { |
| 1305 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) | 764 if (!(opt_allowDragStart && opt_allowDragStart.call(this, e))) |
| 1306 e.preventDefault(); | 765 e.preventDefault(); |
| 1307 }; | 766 }; |
| 1308 } | 767 } |
| 1309 | 768 |
| 1310 /** | |
| 1311 * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead. | |
| 1312 * Call this to stop clicks on <a href="#"> links from scrolling to the top of | |
| 1313 * the page (and possibly showing a # in the link). | |
| 1314 */ | |
| 1315 function preventDefaultOnPoundLinkClicks() { | 769 function preventDefaultOnPoundLinkClicks() { |
| 1316 document.addEventListener('click', function(e) { | 770 document.addEventListener('click', function(e) { |
| 1317 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) { | 771 var anchor = findAncestor(/** @type {Node} */(e.target), function(el) { |
| 1318 return el.tagName == 'A'; | 772 return el.tagName == 'A'; |
| 1319 }); | 773 }); |
| 1320 // Use getAttribute() to prevent URL normalization. | |
| 1321 if (anchor && anchor.getAttribute('href') == '#') | 774 if (anchor && anchor.getAttribute('href') == '#') |
| 1322 e.preventDefault(); | 775 e.preventDefault(); |
| 1323 }); | 776 }); |
| 1324 } | 777 } |
| 1325 | 778 |
| 1326 /** | |
| 1327 * Check the directionality of the page. | |
| 1328 * @return {boolean} True if Chrome is running an RTL UI. | |
| 1329 */ | |
| 1330 function isRTL() { | 779 function isRTL() { |
| 1331 return document.documentElement.dir == 'rtl'; | 780 return document.documentElement.dir == 'rtl'; |
| 1332 } | 781 } |
| 1333 | 782 |
| 1334 /** | |
| 1335 * Get an element that's known to exist by its ID. We use this instead of just | |
| 1336 * calling getElementById and not checking the result because this lets us | |
| 1337 * satisfy the JSCompiler type system. | |
| 1338 * @param {string} id The identifier name. | |
| 1339 * @return {!HTMLElement} the Element. | |
| 1340 */ | |
| 1341 function getRequiredElement(id) { | 783 function getRequiredElement(id) { |
| 1342 return assertInstanceof($(id), HTMLElement, | 784 return assertInstanceof($(id), HTMLElement, |
| 1343 'Missing required element: ' + id); | 785 'Missing required element: ' + id); |
| 1344 } | 786 } |
| 1345 | 787 |
| 1346 /** | |
| 1347 * Query an element that's known to exist by a selector. We use this instead of | |
| 1348 * just calling querySelector and not checking the result because this lets us | |
| 1349 * satisfy the JSCompiler type system. | |
| 1350 * @param {string} selectors CSS selectors to query the element. | |
| 1351 * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional | |
| 1352 * context object for querySelector. | |
| 1353 * @return {!HTMLElement} the Element. | |
| 1354 */ | |
| 1355 function queryRequiredElement(selectors, opt_context) { | 788 function queryRequiredElement(selectors, opt_context) { |
| 1356 var element = (opt_context || document).querySelector(selectors); | 789 var element = (opt_context || document).querySelector(selectors); |
| 1357 return assertInstanceof(element, HTMLElement, | 790 return assertInstanceof(element, HTMLElement, |
| 1358 'Missing required element: ' + selectors); | 791 'Missing required element: ' + selectors); |
| 1359 } | 792 } |
| 1360 | 793 |
| 1361 // Handle click on a link. If the link points to a chrome: or file: url, then | |
| 1362 // call into the browser to do the navigation. | |
| 1363 ['click', 'auxclick'].forEach(function(eventName) { | 794 ['click', 'auxclick'].forEach(function(eventName) { |
| 1364 document.addEventListener(eventName, function(e) { | 795 document.addEventListener(eventName, function(e) { |
| 1365 if (e.defaultPrevented) | 796 if (e.defaultPrevented) |
| 1366 return; | 797 return; |
| 1367 | 798 |
| 1368 var eventPath = e.path; | 799 var eventPath = e.path; |
| 1369 var anchor = null; | 800 var anchor = null; |
| 1370 if (eventPath) { | 801 if (eventPath) { |
| 1371 for (var i = 0; i < eventPath.length; i++) { | 802 for (var i = 0; i < eventPath.length; i++) { |
| 1372 var element = eventPath[i]; | 803 var element = eventPath[i]; |
| 1373 if (element.tagName === 'A' && element.href) { | 804 if (element.tagName === 'A' && element.href) { |
| 1374 anchor = element; | 805 anchor = element; |
| 1375 break; | 806 break; |
| 1376 } | 807 } |
| 1377 } | 808 } |
| 1378 } | 809 } |
| 1379 | 810 |
| 1380 // Fallback if Event.path is not available. | |
| 1381 var el = e.target; | 811 var el = e.target; |
| 1382 if (!anchor && el.nodeType == Node.ELEMENT_NODE && | 812 if (!anchor && el.nodeType == Node.ELEMENT_NODE && |
| 1383 el.webkitMatchesSelector('A, A *')) { | 813 el.webkitMatchesSelector('A, A *')) { |
| 1384 while (el.tagName != 'A') { | 814 while (el.tagName != 'A') { |
| 1385 el = el.parentElement; | 815 el = el.parentElement; |
| 1386 } | 816 } |
| 1387 anchor = el; | 817 anchor = el; |
| 1388 } | 818 } |
| 1389 | 819 |
| 1390 if (!anchor) | 820 if (!anchor) |
| 1391 return; | 821 return; |
| 1392 | 822 |
| 1393 anchor = /** @type {!HTMLAnchorElement} */(anchor); | 823 anchor = /** @type {!HTMLAnchorElement} */(anchor); |
| 1394 if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') && | 824 if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') && |
| 1395 (e.button == 0 || e.button == 1)) { | 825 (e.button == 0 || e.button == 1)) { |
| 1396 chrome.send('navigateToUrl', [ | 826 chrome.send('navigateToUrl', [ |
| 1397 anchor.href, | 827 anchor.href, |
| 1398 anchor.target, | 828 anchor.target, |
| 1399 e.button, | 829 e.button, |
| 1400 e.altKey, | 830 e.altKey, |
| 1401 e.ctrlKey, | 831 e.ctrlKey, |
| 1402 e.metaKey, | 832 e.metaKey, |
| 1403 e.shiftKey | 833 e.shiftKey |
| 1404 ]); | 834 ]); |
| 1405 e.preventDefault(); | 835 e.preventDefault(); |
| 1406 } | 836 } |
| 1407 }); | 837 }); |
| 1408 }); | 838 }); |
| 1409 | 839 |
| 1410 /** | |
| 1411 * Creates a new URL which is the old URL with a GET param of key=value. | |
| 1412 * @param {string} url The base URL. There is not sanity checking on the URL so | |
| 1413 * it must be passed in a proper format. | |
| 1414 * @param {string} key The key of the param. | |
| 1415 * @param {string} value The value of the param. | |
| 1416 * @return {string} The new URL. | |
| 1417 */ | |
| 1418 function appendParam(url, key, value) { | 840 function appendParam(url, key, value) { |
| 1419 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); | 841 var param = encodeURIComponent(key) + '=' + encodeURIComponent(value); |
| 1420 | 842 |
| 1421 if (url.indexOf('?') == -1) | 843 if (url.indexOf('?') == -1) |
| 1422 return url + '?' + param; | 844 return url + '?' + param; |
| 1423 return url + '&' + param; | 845 return url + '&' + param; |
| 1424 } | 846 } |
| 1425 | 847 |
| 1426 /** | |
| 1427 * Creates an element of a specified type with a specified class name. | |
| 1428 * @param {string} type The node type. | |
| 1429 * @param {string} className The class name to use. | |
| 1430 * @return {Element} The created element. | |
| 1431 */ | |
| 1432 function createElementWithClassName(type, className) { | 848 function createElementWithClassName(type, className) { |
| 1433 var elm = document.createElement(type); | 849 var elm = document.createElement(type); |
| 1434 elm.className = className; | 850 elm.className = className; |
| 1435 return elm; | 851 return elm; |
| 1436 } | 852 } |
| 1437 | 853 |
| 1438 /** | |
| 1439 * webkitTransitionEnd does not always fire (e.g. when animation is aborted | |
| 1440 * or when no paint happens during the animation). This function sets up | |
| 1441 * a timer and emulate the event if it is not fired when the timer expires. | |
| 1442 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd. | |
| 1443 * @param {number=} opt_timeOut The maximum wait time in milliseconds for the | |
| 1444 * webkitTransitionEnd to happen. If not specified, it is fetched from |el| | |
| 1445 * using the transitionDuration style value. | |
| 1446 */ | |
| 1447 function ensureTransitionEndEvent(el, opt_timeOut) { | 854 function ensureTransitionEndEvent(el, opt_timeOut) { |
| 1448 if (opt_timeOut === undefined) { | 855 if (opt_timeOut === undefined) { |
| 1449 var style = getComputedStyle(el); | 856 var style = getComputedStyle(el); |
| 1450 opt_timeOut = parseFloat(style.transitionDuration) * 1000; | 857 opt_timeOut = parseFloat(style.transitionDuration) * 1000; |
| 1451 | 858 |
| 1452 // Give an additional 50ms buffer for the animation to complete. | |
| 1453 opt_timeOut += 50; | 859 opt_timeOut += 50; |
| 1454 } | 860 } |
| 1455 | 861 |
| 1456 var fired = false; | 862 var fired = false; |
| 1457 el.addEventListener('webkitTransitionEnd', function f(e) { | 863 el.addEventListener('webkitTransitionEnd', function f(e) { |
| 1458 el.removeEventListener('webkitTransitionEnd', f); | 864 el.removeEventListener('webkitTransitionEnd', f); |
| 1459 fired = true; | 865 fired = true; |
| 1460 }); | 866 }); |
| 1461 window.setTimeout(function() { | 867 window.setTimeout(function() { |
| 1462 if (!fired) | 868 if (!fired) |
| 1463 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true); | 869 cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true); |
| 1464 }, opt_timeOut); | 870 }, opt_timeOut); |
| 1465 } | 871 } |
| 1466 | 872 |
| 1467 /** | |
| 1468 * Alias for document.scrollTop getter. | |
| 1469 * @param {!HTMLDocument} doc The document node where information will be | |
| 1470 * queried from. | |
| 1471 * @return {number} The Y document scroll offset. | |
| 1472 */ | |
| 1473 function scrollTopForDocument(doc) { | 873 function scrollTopForDocument(doc) { |
| 1474 return doc.documentElement.scrollTop || doc.body.scrollTop; | 874 return doc.documentElement.scrollTop || doc.body.scrollTop; |
| 1475 } | 875 } |
| 1476 | 876 |
| 1477 /** | |
| 1478 * Alias for document.scrollTop setter. | |
| 1479 * @param {!HTMLDocument} doc The document node where information will be | |
| 1480 * queried from. | |
| 1481 * @param {number} value The target Y scroll offset. | |
| 1482 */ | |
| 1483 function setScrollTopForDocument(doc, value) { | 877 function setScrollTopForDocument(doc, value) { |
| 1484 doc.documentElement.scrollTop = doc.body.scrollTop = value; | 878 doc.documentElement.scrollTop = doc.body.scrollTop = value; |
| 1485 } | 879 } |
| 1486 | 880 |
| 1487 /** | |
| 1488 * Alias for document.scrollLeft getter. | |
| 1489 * @param {!HTMLDocument} doc The document node where information will be | |
| 1490 * queried from. | |
| 1491 * @return {number} The X document scroll offset. | |
| 1492 */ | |
| 1493 function scrollLeftForDocument(doc) { | 881 function scrollLeftForDocument(doc) { |
| 1494 return doc.documentElement.scrollLeft || doc.body.scrollLeft; | 882 return doc.documentElement.scrollLeft || doc.body.scrollLeft; |
| 1495 } | 883 } |
| 1496 | 884 |
| 1497 /** | |
| 1498 * Alias for document.scrollLeft setter. | |
| 1499 * @param {!HTMLDocument} doc The document node where information will be | |
| 1500 * queried from. | |
| 1501 * @param {number} value The target X scroll offset. | |
| 1502 */ | |
| 1503 function setScrollLeftForDocument(doc, value) { | 885 function setScrollLeftForDocument(doc, value) { |
| 1504 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; | 886 doc.documentElement.scrollLeft = doc.body.scrollLeft = value; |
| 1505 } | 887 } |
| 1506 | 888 |
| 1507 /** | |
| 1508 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding. | |
| 1509 * @param {string} original The original string. | |
| 1510 * @return {string} The string with all the characters mentioned above replaced. | |
| 1511 */ | |
| 1512 function HTMLEscape(original) { | 889 function HTMLEscape(original) { |
| 1513 return original.replace(/&/g, '&') | 890 return original.replace(/&/g, '&') |
| 1514 .replace(/</g, '<') | 891 .replace(/</g, '<') |
| 1515 .replace(/>/g, '>') | 892 .replace(/>/g, '>') |
| 1516 .replace(/"/g, '"') | 893 .replace(/"/g, '"') |
| 1517 .replace(/'/g, '''); | 894 .replace(/'/g, '''); |
| 1518 } | 895 } |
| 1519 | 896 |
| 1520 /** | |
| 1521 * Shortens the provided string (if necessary) to a string of length at most | |
| 1522 * |maxLength|. | |
| 1523 * @param {string} original The original string. | |
| 1524 * @param {number} maxLength The maximum length allowed for the string. | |
| 1525 * @return {string} The original string if its length does not exceed | |
| 1526 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...' | |
| 1527 * appended. | |
| 1528 */ | |
| 1529 function elide(original, maxLength) { | 897 function elide(original, maxLength) { |
| 1530 if (original.length <= maxLength) | 898 if (original.length <= maxLength) |
| 1531 return original; | 899 return original; |
| 1532 return original.substring(0, maxLength - 1) + '\u2026'; | 900 return original.substring(0, maxLength - 1) + '\u2026'; |
| 1533 } | 901 } |
| 1534 | 902 |
| 1535 /** | |
| 1536 * Quote a string so it can be used in a regular expression. | |
| 1537 * @param {string} str The source string. | |
| 1538 * @return {string} The escaped string. | |
| 1539 */ | |
| 1540 function quoteString(str) { | 903 function quoteString(str) { |
| 1541 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); | 904 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); |
| 1542 } | 905 } |
| 1543 | 906 |
| 1544 // <if expr="is_ios"> | 907 // <if expr="is_ios"> |
| 1545 // Polyfill 'key' in KeyboardEvent for iOS. | |
| 1546 // This function is not intended to be complete but should | |
| 1547 // be sufficient enough to have iOS work correctly while | |
| 1548 // it does not support key yet. | |
| 1549 if (!('key' in KeyboardEvent.prototype)) { | 908 if (!('key' in KeyboardEvent.prototype)) { |
| 1550 Object.defineProperty(KeyboardEvent.prototype, 'key', { | 909 Object.defineProperty(KeyboardEvent.prototype, 'key', { |
| 1551 /** @this {KeyboardEvent} */ | 910 /** @this {KeyboardEvent} */ |
| 1552 get: function () { | 911 get: function () { |
| 1553 // 0-9 | |
| 1554 if (this.keyCode >= 0x30 && this.keyCode <= 0x39) | 912 if (this.keyCode >= 0x30 && this.keyCode <= 0x39) |
| 1555 return String.fromCharCode(this.keyCode); | 913 return String.fromCharCode(this.keyCode); |
| 1556 | 914 |
| 1557 // A-Z | |
| 1558 if (this.keyCode >= 0x41 && this.keyCode <= 0x5a) { | 915 if (this.keyCode >= 0x41 && this.keyCode <= 0x5a) { |
| 1559 var result = String.fromCharCode(this.keyCode).toLowerCase(); | 916 var result = String.fromCharCode(this.keyCode).toLowerCase(); |
| 1560 if (this.shiftKey) | 917 if (this.shiftKey) |
| 1561 result = result.toUpperCase(); | 918 result = result.toUpperCase(); |
| 1562 return result; | 919 return result; |
| 1563 } | 920 } |
| 1564 | 921 |
| 1565 // Special characters | |
| 1566 switch(this.keyCode) { | 922 switch(this.keyCode) { |
| 1567 case 0x08: return 'Backspace'; | 923 case 0x08: return 'Backspace'; |
| 1568 case 0x09: return 'Tab'; | 924 case 0x09: return 'Tab'; |
| 1569 case 0x0d: return 'Enter'; | 925 case 0x0d: return 'Enter'; |
| 1570 case 0x10: return 'Shift'; | 926 case 0x10: return 'Shift'; |
| 1571 case 0x11: return 'Control'; | 927 case 0x11: return 'Control'; |
| 1572 case 0x12: return 'Alt'; | 928 case 0x12: return 'Alt'; |
| 1573 case 0x1b: return 'Escape'; | 929 case 0x1b: return 'Escape'; |
| 1574 case 0x20: return ' '; | 930 case 0x20: return ' '; |
| 1575 case 0x21: return 'PageUp'; | 931 case 0x21: return 'PageUp'; |
| (...skipping 24 matching lines...) Expand all Loading... |
| 1600 case 0xdb: return '['; | 956 case 0xdb: return '['; |
| 1601 case 0xdd: return ']'; | 957 case 0xdd: return ']'; |
| 1602 } | 958 } |
| 1603 return 'Unidentified'; | 959 return 'Unidentified'; |
| 1604 } | 960 } |
| 1605 }); | 961 }); |
| 1606 } else { | 962 } else { |
| 1607 window.console.log("KeyboardEvent.Key polyfill not required"); | 963 window.console.log("KeyboardEvent.Key polyfill not required"); |
| 1608 } | 964 } |
| 1609 // </if> /* is_ios */ | 965 // </if> /* is_ios */ |
| 1610 /** | |
| 1611 * `IronResizableBehavior` is a behavior that can be used in Polymer elements
to | |
| 1612 * coordinate the flow of resize events between "resizers" (elements that cont
rol the | |
| 1613 * size or hidden state of their children) and "resizables" (elements that nee
d to be | |
| 1614 * notified when they are resized or un-hidden by their parents in order to ta
ke | |
| 1615 * action on their new measurements). | |
| 1616 * | |
| 1617 * Elements that perform measurement should add the `IronResizableBehavior` be
havior to | |
| 1618 * their element definition and listen for the `iron-resize` event on themselv
es. | |
| 1619 * This event will be fired when they become showing after having been hidden, | |
| 1620 * when they are resized explicitly by another resizable, or when the window h
as been | |
| 1621 * resized. | |
| 1622 * | |
| 1623 * Note, the `iron-resize` event is non-bubbling. | |
| 1624 * | |
| 1625 * @polymerBehavior Polymer.IronResizableBehavior | |
| 1626 * @demo demo/index.html | |
| 1627 **/ | |
| 1628 Polymer.IronResizableBehavior = { | 966 Polymer.IronResizableBehavior = { |
| 1629 properties: { | 967 properties: { |
| 1630 /** | |
| 1631 * The closest ancestor element that implements `IronResizableBehavior`. | |
| 1632 */ | |
| 1633 _parentResizable: { | 968 _parentResizable: { |
| 1634 type: Object, | 969 type: Object, |
| 1635 observer: '_parentResizableChanged' | 970 observer: '_parentResizableChanged' |
| 1636 }, | 971 }, |
| 1637 | 972 |
| 1638 /** | |
| 1639 * True if this element is currently notifying its descedant elements of | |
| 1640 * resize. | |
| 1641 */ | |
| 1642 _notifyingDescendant: { | 973 _notifyingDescendant: { |
| 1643 type: Boolean, | 974 type: Boolean, |
| 1644 value: false | 975 value: false |
| 1645 } | 976 } |
| 1646 }, | 977 }, |
| 1647 | 978 |
| 1648 listeners: { | 979 listeners: { |
| 1649 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' | 980 'iron-request-resize-notifications': '_onIronRequestResizeNotifications' |
| 1650 }, | 981 }, |
| 1651 | 982 |
| 1652 created: function() { | 983 created: function() { |
| 1653 // We don't really need property effects on these, and also we want them | |
| 1654 // to be created before the `_parentResizable` observer fires: | |
| 1655 this._interestedResizables = []; | 984 this._interestedResizables = []; |
| 1656 this._boundNotifyResize = this.notifyResize.bind(this); | 985 this._boundNotifyResize = this.notifyResize.bind(this); |
| 1657 }, | 986 }, |
| 1658 | 987 |
| 1659 attached: function() { | 988 attached: function() { |
| 1660 this.fire('iron-request-resize-notifications', null, { | 989 this.fire('iron-request-resize-notifications', null, { |
| 1661 node: this, | 990 node: this, |
| 1662 bubbles: true, | 991 bubbles: true, |
| 1663 cancelable: true | 992 cancelable: true |
| 1664 }); | 993 }); |
| 1665 | 994 |
| 1666 if (!this._parentResizable) { | 995 if (!this._parentResizable) { |
| 1667 window.addEventListener('resize', this._boundNotifyResize); | 996 window.addEventListener('resize', this._boundNotifyResize); |
| 1668 this.notifyResize(); | 997 this.notifyResize(); |
| 1669 } | 998 } |
| 1670 }, | 999 }, |
| 1671 | 1000 |
| 1672 detached: function() { | 1001 detached: function() { |
| 1673 if (this._parentResizable) { | 1002 if (this._parentResizable) { |
| 1674 this._parentResizable.stopResizeNotificationsFor(this); | 1003 this._parentResizable.stopResizeNotificationsFor(this); |
| 1675 } else { | 1004 } else { |
| 1676 window.removeEventListener('resize', this._boundNotifyResize); | 1005 window.removeEventListener('resize', this._boundNotifyResize); |
| 1677 } | 1006 } |
| 1678 | 1007 |
| 1679 this._parentResizable = null; | 1008 this._parentResizable = null; |
| 1680 }, | 1009 }, |
| 1681 | 1010 |
| 1682 /** | |
| 1683 * Can be called to manually notify a resizable and its descendant | |
| 1684 * resizables of a resize change. | |
| 1685 */ | |
| 1686 notifyResize: function() { | 1011 notifyResize: function() { |
| 1687 if (!this.isAttached) { | 1012 if (!this.isAttached) { |
| 1688 return; | 1013 return; |
| 1689 } | 1014 } |
| 1690 | 1015 |
| 1691 this._interestedResizables.forEach(function(resizable) { | 1016 this._interestedResizables.forEach(function(resizable) { |
| 1692 if (this.resizerShouldNotify(resizable)) { | 1017 if (this.resizerShouldNotify(resizable)) { |
| 1693 this._notifyDescendant(resizable); | 1018 this._notifyDescendant(resizable); |
| 1694 } | 1019 } |
| 1695 }, this); | 1020 }, this); |
| 1696 | 1021 |
| 1697 this._fireResize(); | 1022 this._fireResize(); |
| 1698 }, | 1023 }, |
| 1699 | 1024 |
| 1700 /** | |
| 1701 * Used to assign the closest resizable ancestor to this resizable | |
| 1702 * if the ancestor detects a request for notifications. | |
| 1703 */ | |
| 1704 assignParentResizable: function(parentResizable) { | 1025 assignParentResizable: function(parentResizable) { |
| 1705 this._parentResizable = parentResizable; | 1026 this._parentResizable = parentResizable; |
| 1706 }, | 1027 }, |
| 1707 | 1028 |
| 1708 /** | |
| 1709 * Used to remove a resizable descendant from the list of descendants | |
| 1710 * that should be notified of a resize change. | |
| 1711 */ | |
| 1712 stopResizeNotificationsFor: function(target) { | 1029 stopResizeNotificationsFor: function(target) { |
| 1713 var index = this._interestedResizables.indexOf(target); | 1030 var index = this._interestedResizables.indexOf(target); |
| 1714 | 1031 |
| 1715 if (index > -1) { | 1032 if (index > -1) { |
| 1716 this._interestedResizables.splice(index, 1); | 1033 this._interestedResizables.splice(index, 1); |
| 1717 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); | 1034 this.unlisten(target, 'iron-resize', '_onDescendantIronResize'); |
| 1718 } | 1035 } |
| 1719 }, | 1036 }, |
| 1720 | 1037 |
| 1721 /** | |
| 1722 * This method can be overridden to filter nested elements that should or | |
| 1723 * should not be notified by the current element. Return true if an element | |
| 1724 * should be notified, or false if it should not be notified. | |
| 1725 * | |
| 1726 * @param {HTMLElement} element A candidate descendant element that | |
| 1727 * implements `IronResizableBehavior`. | |
| 1728 * @return {boolean} True if the `element` should be notified of resize. | |
| 1729 */ | |
| 1730 resizerShouldNotify: function(element) { return true; }, | 1038 resizerShouldNotify: function(element) { return true; }, |
| 1731 | 1039 |
| 1732 _onDescendantIronResize: function(event) { | 1040 _onDescendantIronResize: function(event) { |
| 1733 if (this._notifyingDescendant) { | 1041 if (this._notifyingDescendant) { |
| 1734 event.stopPropagation(); | 1042 event.stopPropagation(); |
| 1735 return; | 1043 return; |
| 1736 } | 1044 } |
| 1737 | 1045 |
| 1738 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the | |
| 1739 // otherwise non-bubbling event "just work." We do it manually here for | |
| 1740 // the case where Polymer is not using shadow roots for whatever reason: | |
| 1741 if (!Polymer.Settings.useShadow) { | 1046 if (!Polymer.Settings.useShadow) { |
| 1742 this._fireResize(); | 1047 this._fireResize(); |
| 1743 } | 1048 } |
| 1744 }, | 1049 }, |
| 1745 | 1050 |
| 1746 _fireResize: function() { | 1051 _fireResize: function() { |
| 1747 this.fire('iron-resize', null, { | 1052 this.fire('iron-resize', null, { |
| 1748 node: this, | 1053 node: this, |
| 1749 bubbles: false | 1054 bubbles: false |
| 1750 }); | 1055 }); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 1768 event.stopPropagation(); | 1073 event.stopPropagation(); |
| 1769 }, | 1074 }, |
| 1770 | 1075 |
| 1771 _parentResizableChanged: function(parentResizable) { | 1076 _parentResizableChanged: function(parentResizable) { |
| 1772 if (parentResizable) { | 1077 if (parentResizable) { |
| 1773 window.removeEventListener('resize', this._boundNotifyResize); | 1078 window.removeEventListener('resize', this._boundNotifyResize); |
| 1774 } | 1079 } |
| 1775 }, | 1080 }, |
| 1776 | 1081 |
| 1777 _notifyDescendant: function(descendant) { | 1082 _notifyDescendant: function(descendant) { |
| 1778 // NOTE(cdata): In IE10, attached is fired on children first, so it's | |
| 1779 // important not to notify them if the parent is not attached yet (or | |
| 1780 // else they will get redundantly notified when the parent attaches). | |
| 1781 if (!this.isAttached) { | 1083 if (!this.isAttached) { |
| 1782 return; | 1084 return; |
| 1783 } | 1085 } |
| 1784 | 1086 |
| 1785 this._notifyingDescendant = true; | 1087 this._notifyingDescendant = true; |
| 1786 descendant.notifyResize(); | 1088 descendant.notifyResize(); |
| 1787 this._notifyingDescendant = false; | 1089 this._notifyingDescendant = false; |
| 1788 } | 1090 } |
| 1789 }; | 1091 }; |
| 1790 (function() { | 1092 (function() { |
| 1791 'use strict'; | 1093 'use strict'; |
| 1792 | 1094 |
| 1793 /** | |
| 1794 * Chrome uses an older version of DOM Level 3 Keyboard Events | |
| 1795 * | |
| 1796 * Most keys are labeled as text, but some are Unicode codepoints. | |
| 1797 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set | |
| 1798 */ | |
| 1799 var KEY_IDENTIFIER = { | 1095 var KEY_IDENTIFIER = { |
| 1800 'U+0008': 'backspace', | 1096 'U+0008': 'backspace', |
| 1801 'U+0009': 'tab', | 1097 'U+0009': 'tab', |
| 1802 'U+001B': 'esc', | 1098 'U+001B': 'esc', |
| 1803 'U+0020': 'space', | 1099 'U+0020': 'space', |
| 1804 'U+007F': 'del' | 1100 'U+007F': 'del' |
| 1805 }; | 1101 }; |
| 1806 | 1102 |
| 1807 /** | |
| 1808 * Special table for KeyboardEvent.keyCode. | |
| 1809 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er | |
| 1810 * than that. | |
| 1811 * | |
| 1812 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode | |
| 1813 */ | |
| 1814 var KEY_CODE = { | 1103 var KEY_CODE = { |
| 1815 8: 'backspace', | 1104 8: 'backspace', |
| 1816 9: 'tab', | 1105 9: 'tab', |
| 1817 13: 'enter', | 1106 13: 'enter', |
| 1818 27: 'esc', | 1107 27: 'esc', |
| 1819 33: 'pageup', | 1108 33: 'pageup', |
| 1820 34: 'pagedown', | 1109 34: 'pagedown', |
| 1821 35: 'end', | 1110 35: 'end', |
| 1822 36: 'home', | 1111 36: 'home', |
| 1823 32: 'space', | 1112 32: 'space', |
| 1824 37: 'left', | 1113 37: 'left', |
| 1825 38: 'up', | 1114 38: 'up', |
| 1826 39: 'right', | 1115 39: 'right', |
| 1827 40: 'down', | 1116 40: 'down', |
| 1828 46: 'del', | 1117 46: 'del', |
| 1829 106: '*' | 1118 106: '*' |
| 1830 }; | 1119 }; |
| 1831 | 1120 |
| 1832 /** | |
| 1833 * MODIFIER_KEYS maps the short name for modifier keys used in a key | |
| 1834 * combo string to the property name that references those same keys | |
| 1835 * in a KeyboardEvent instance. | |
| 1836 */ | |
| 1837 var MODIFIER_KEYS = { | 1121 var MODIFIER_KEYS = { |
| 1838 'shift': 'shiftKey', | 1122 'shift': 'shiftKey', |
| 1839 'ctrl': 'ctrlKey', | 1123 'ctrl': 'ctrlKey', |
| 1840 'alt': 'altKey', | 1124 'alt': 'altKey', |
| 1841 'meta': 'metaKey' | 1125 'meta': 'metaKey' |
| 1842 }; | 1126 }; |
| 1843 | 1127 |
| 1844 /** | |
| 1845 * KeyboardEvent.key is mostly represented by printable character made by | |
| 1846 * the keyboard, with unprintable keys labeled nicely. | |
| 1847 * | |
| 1848 * However, on OS X, Alt+char can make a Unicode character that follows an | |
| 1849 * Apple-specific mapping. In this case, we fall back to .keyCode. | |
| 1850 */ | |
| 1851 var KEY_CHAR = /[a-z0-9*]/; | 1128 var KEY_CHAR = /[a-z0-9*]/; |
| 1852 | 1129 |
| 1853 /** | |
| 1854 * Matches a keyIdentifier string. | |
| 1855 */ | |
| 1856 var IDENT_CHAR = /U\+/; | 1130 var IDENT_CHAR = /U\+/; |
| 1857 | 1131 |
| 1858 /** | |
| 1859 * Matches arrow keys in Gecko 27.0+ | |
| 1860 */ | |
| 1861 var ARROW_KEY = /^arrow/; | 1132 var ARROW_KEY = /^arrow/; |
| 1862 | 1133 |
| 1863 /** | |
| 1864 * Matches space keys everywhere (notably including IE10's exceptional name | |
| 1865 * `spacebar`). | |
| 1866 */ | |
| 1867 var SPACE_KEY = /^space(bar)?/; | 1134 var SPACE_KEY = /^space(bar)?/; |
| 1868 | 1135 |
| 1869 /** | |
| 1870 * Matches ESC key. | |
| 1871 * | |
| 1872 * Value from: http://w3c.github.io/uievents-key/#key-Escape | |
| 1873 */ | |
| 1874 var ESC_KEY = /^escape$/; | 1136 var ESC_KEY = /^escape$/; |
| 1875 | 1137 |
| 1876 /** | |
| 1877 * Transforms the key. | |
| 1878 * @param {string} key The KeyBoardEvent.key | |
| 1879 * @param {Boolean} [noSpecialChars] Limits the transformation to | |
| 1880 * alpha-numeric characters. | |
| 1881 */ | |
| 1882 function transformKey(key, noSpecialChars) { | 1138 function transformKey(key, noSpecialChars) { |
| 1883 var validKey = ''; | 1139 var validKey = ''; |
| 1884 if (key) { | 1140 if (key) { |
| 1885 var lKey = key.toLowerCase(); | 1141 var lKey = key.toLowerCase(); |
| 1886 if (lKey === ' ' || SPACE_KEY.test(lKey)) { | 1142 if (lKey === ' ' || SPACE_KEY.test(lKey)) { |
| 1887 validKey = 'space'; | 1143 validKey = 'space'; |
| 1888 } else if (ESC_KEY.test(lKey)) { | 1144 } else if (ESC_KEY.test(lKey)) { |
| 1889 validKey = 'esc'; | 1145 validKey = 'esc'; |
| 1890 } else if (lKey.length == 1) { | 1146 } else if (lKey.length == 1) { |
| 1891 if (!noSpecialChars || KEY_CHAR.test(lKey)) { | 1147 if (!noSpecialChars || KEY_CHAR.test(lKey)) { |
| 1892 validKey = lKey; | 1148 validKey = lKey; |
| 1893 } | 1149 } |
| 1894 } else if (ARROW_KEY.test(lKey)) { | 1150 } else if (ARROW_KEY.test(lKey)) { |
| 1895 validKey = lKey.replace('arrow', ''); | 1151 validKey = lKey.replace('arrow', ''); |
| 1896 } else if (lKey == 'multiply') { | 1152 } else if (lKey == 'multiply') { |
| 1897 // numpad '*' can map to Multiply on IE/Windows | |
| 1898 validKey = '*'; | 1153 validKey = '*'; |
| 1899 } else { | 1154 } else { |
| 1900 validKey = lKey; | 1155 validKey = lKey; |
| 1901 } | 1156 } |
| 1902 } | 1157 } |
| 1903 return validKey; | 1158 return validKey; |
| 1904 } | 1159 } |
| 1905 | 1160 |
| 1906 function transformKeyIdentifier(keyIdent) { | 1161 function transformKeyIdentifier(keyIdent) { |
| 1907 var validKey = ''; | 1162 var validKey = ''; |
| 1908 if (keyIdent) { | 1163 if (keyIdent) { |
| 1909 if (keyIdent in KEY_IDENTIFIER) { | 1164 if (keyIdent in KEY_IDENTIFIER) { |
| 1910 validKey = KEY_IDENTIFIER[keyIdent]; | 1165 validKey = KEY_IDENTIFIER[keyIdent]; |
| 1911 } else if (IDENT_CHAR.test(keyIdent)) { | 1166 } else if (IDENT_CHAR.test(keyIdent)) { |
| 1912 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); | 1167 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); |
| 1913 validKey = String.fromCharCode(keyIdent).toLowerCase(); | 1168 validKey = String.fromCharCode(keyIdent).toLowerCase(); |
| 1914 } else { | 1169 } else { |
| 1915 validKey = keyIdent.toLowerCase(); | 1170 validKey = keyIdent.toLowerCase(); |
| 1916 } | 1171 } |
| 1917 } | 1172 } |
| 1918 return validKey; | 1173 return validKey; |
| 1919 } | 1174 } |
| 1920 | 1175 |
| 1921 function transformKeyCode(keyCode) { | 1176 function transformKeyCode(keyCode) { |
| 1922 var validKey = ''; | 1177 var validKey = ''; |
| 1923 if (Number(keyCode)) { | 1178 if (Number(keyCode)) { |
| 1924 if (keyCode >= 65 && keyCode <= 90) { | 1179 if (keyCode >= 65 && keyCode <= 90) { |
| 1925 // ascii a-z | |
| 1926 // lowercase is 32 offset from uppercase | |
| 1927 validKey = String.fromCharCode(32 + keyCode); | 1180 validKey = String.fromCharCode(32 + keyCode); |
| 1928 } else if (keyCode >= 112 && keyCode <= 123) { | 1181 } else if (keyCode >= 112 && keyCode <= 123) { |
| 1929 // function keys f1-f12 | |
| 1930 validKey = 'f' + (keyCode - 112); | 1182 validKey = 'f' + (keyCode - 112); |
| 1931 } else if (keyCode >= 48 && keyCode <= 57) { | 1183 } else if (keyCode >= 48 && keyCode <= 57) { |
| 1932 // top 0-9 keys | |
| 1933 validKey = String(keyCode - 48); | 1184 validKey = String(keyCode - 48); |
| 1934 } else if (keyCode >= 96 && keyCode <= 105) { | 1185 } else if (keyCode >= 96 && keyCode <= 105) { |
| 1935 // num pad 0-9 | |
| 1936 validKey = String(keyCode - 96); | 1186 validKey = String(keyCode - 96); |
| 1937 } else { | 1187 } else { |
| 1938 validKey = KEY_CODE[keyCode]; | 1188 validKey = KEY_CODE[keyCode]; |
| 1939 } | 1189 } |
| 1940 } | 1190 } |
| 1941 return validKey; | 1191 return validKey; |
| 1942 } | 1192 } |
| 1943 | 1193 |
| 1944 /** | |
| 1945 * Calculates the normalized key for a KeyboardEvent. | |
| 1946 * @param {KeyboardEvent} keyEvent | |
| 1947 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key | |
| 1948 * transformation to alpha-numeric chars. This is useful with key | |
| 1949 * combinations like shift + 2, which on FF for MacOS produces | |
| 1950 * keyEvent.key = @ | |
| 1951 * To get 2 returned, set noSpecialChars = true | |
| 1952 * To get @ returned, set noSpecialChars = false | |
| 1953 */ | |
| 1954 function normalizedKeyForEvent(keyEvent, noSpecialChars) { | 1194 function normalizedKeyForEvent(keyEvent, noSpecialChars) { |
| 1955 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to | |
| 1956 // .detail.key to support artificial keyboard events. | |
| 1957 return transformKey(keyEvent.key, noSpecialChars) || | 1195 return transformKey(keyEvent.key, noSpecialChars) || |
| 1958 transformKeyIdentifier(keyEvent.keyIdentifier) || | 1196 transformKeyIdentifier(keyEvent.keyIdentifier) || |
| 1959 transformKeyCode(keyEvent.keyCode) || | 1197 transformKeyCode(keyEvent.keyCode) || |
| 1960 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no
SpecialChars) || ''; | 1198 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no
SpecialChars) || ''; |
| 1961 } | 1199 } |
| 1962 | 1200 |
| 1963 function keyComboMatchesEvent(keyCombo, event) { | 1201 function keyComboMatchesEvent(keyCombo, event) { |
| 1964 // For combos with modifiers we support only alpha-numeric keys | |
| 1965 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); | 1202 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); |
| 1966 return keyEvent === keyCombo.key && | 1203 return keyEvent === keyCombo.key && |
| 1967 (!keyCombo.hasModifiers || ( | 1204 (!keyCombo.hasModifiers || ( |
| 1968 !!event.shiftKey === !!keyCombo.shiftKey && | 1205 !!event.shiftKey === !!keyCombo.shiftKey && |
| 1969 !!event.ctrlKey === !!keyCombo.ctrlKey && | 1206 !!event.ctrlKey === !!keyCombo.ctrlKey && |
| 1970 !!event.altKey === !!keyCombo.altKey && | 1207 !!event.altKey === !!keyCombo.altKey && |
| 1971 !!event.metaKey === !!keyCombo.metaKey) | 1208 !!event.metaKey === !!keyCombo.metaKey) |
| 1972 ); | 1209 ); |
| 1973 } | 1210 } |
| 1974 | 1211 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 1998 combo: keyComboString.split(':').shift() | 1235 combo: keyComboString.split(':').shift() |
| 1999 }); | 1236 }); |
| 2000 } | 1237 } |
| 2001 | 1238 |
| 2002 function parseEventString(eventString) { | 1239 function parseEventString(eventString) { |
| 2003 return eventString.trim().split(' ').map(function(keyComboString) { | 1240 return eventString.trim().split(' ').map(function(keyComboString) { |
| 2004 return parseKeyComboString(keyComboString); | 1241 return parseKeyComboString(keyComboString); |
| 2005 }); | 1242 }); |
| 2006 } | 1243 } |
| 2007 | 1244 |
| 2008 /** | |
| 2009 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces
sing | |
| 2010 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3
.org/TR/wai-aria-practices/#kbd_general_binding). | |
| 2011 * The element takes care of browser differences with respect to Keyboard ev
ents | |
| 2012 * and uses an expressive syntax to filter key presses. | |
| 2013 * | |
| 2014 * Use the `keyBindings` prototype property to express what combination of k
eys | |
| 2015 * will trigger the callback. A key binding has the format | |
| 2016 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or | |
| 2017 * `"KEY:EVENT": "callback"` are valid as well). Some examples: | |
| 2018 * | |
| 2019 * keyBindings: { | |
| 2020 * 'space': '_onKeydown', // same as 'space:keydown' | |
| 2021 * 'shift+tab': '_onKeydown', | |
| 2022 * 'enter:keypress': '_onKeypress', | |
| 2023 * 'esc:keyup': '_onKeyup' | |
| 2024 * } | |
| 2025 * | |
| 2026 * The callback will receive with an event containing the following informat
ion in `event.detail`: | |
| 2027 * | |
| 2028 * _onKeydown: function(event) { | |
| 2029 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab" | |
| 2030 * console.log(event.detail.key); // KEY only, e.g. "tab" | |
| 2031 * console.log(event.detail.event); // EVENT, e.g. "keydown" | |
| 2032 * console.log(event.detail.keyboardEvent); // the original KeyboardE
vent | |
| 2033 * } | |
| 2034 * | |
| 2035 * Use the `keyEventTarget` attribute to set up event handlers on a specific | |
| 2036 * node. | |
| 2037 * | |
| 2038 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k
eys-behavior/blob/master/demo/x-key-aware.html) | |
| 2039 * for an example. | |
| 2040 * | |
| 2041 * @demo demo/index.html | |
| 2042 * @polymerBehavior | |
| 2043 */ | |
| 2044 Polymer.IronA11yKeysBehavior = { | 1245 Polymer.IronA11yKeysBehavior = { |
| 2045 properties: { | 1246 properties: { |
| 2046 /** | |
| 2047 * The EventTarget that will be firing relevant KeyboardEvents. Set it t
o | |
| 2048 * `null` to disable the listeners. | |
| 2049 * @type {?EventTarget} | |
| 2050 */ | |
| 2051 keyEventTarget: { | 1247 keyEventTarget: { |
| 2052 type: Object, | 1248 type: Object, |
| 2053 value: function() { | 1249 value: function() { |
| 2054 return this; | 1250 return this; |
| 2055 } | 1251 } |
| 2056 }, | 1252 }, |
| 2057 | 1253 |
| 2058 /** | |
| 2059 * If true, this property will cause the implementing element to | |
| 2060 * automatically stop propagation on any handled KeyboardEvents. | |
| 2061 */ | |
| 2062 stopKeyboardEventPropagation: { | 1254 stopKeyboardEventPropagation: { |
| 2063 type: Boolean, | 1255 type: Boolean, |
| 2064 value: false | 1256 value: false |
| 2065 }, | 1257 }, |
| 2066 | 1258 |
| 2067 _boundKeyHandlers: { | 1259 _boundKeyHandlers: { |
| 2068 type: Array, | 1260 type: Array, |
| 2069 value: function() { | 1261 value: function() { |
| 2070 return []; | 1262 return []; |
| 2071 } | 1263 } |
| 2072 }, | 1264 }, |
| 2073 | 1265 |
| 2074 // We use this due to a limitation in IE10 where instances will have | |
| 2075 // own properties of everything on the "prototype". | |
| 2076 _imperativeKeyBindings: { | 1266 _imperativeKeyBindings: { |
| 2077 type: Object, | 1267 type: Object, |
| 2078 value: function() { | 1268 value: function() { |
| 2079 return {}; | 1269 return {}; |
| 2080 } | 1270 } |
| 2081 } | 1271 } |
| 2082 }, | 1272 }, |
| 2083 | 1273 |
| 2084 observers: [ | 1274 observers: [ |
| 2085 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' | 1275 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' |
| 2086 ], | 1276 ], |
| 2087 | 1277 |
| 2088 | 1278 |
| 2089 /** | |
| 2090 * To be used to express what combination of keys will trigger the relati
ve | |
| 2091 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}` | |
| 2092 * @type {Object} | |
| 2093 */ | |
| 2094 keyBindings: {}, | 1279 keyBindings: {}, |
| 2095 | 1280 |
| 2096 registered: function() { | 1281 registered: function() { |
| 2097 this._prepKeyBindings(); | 1282 this._prepKeyBindings(); |
| 2098 }, | 1283 }, |
| 2099 | 1284 |
| 2100 attached: function() { | 1285 attached: function() { |
| 2101 this._listenKeyEventListeners(); | 1286 this._listenKeyEventListeners(); |
| 2102 }, | 1287 }, |
| 2103 | 1288 |
| 2104 detached: function() { | 1289 detached: function() { |
| 2105 this._unlistenKeyEventListeners(); | 1290 this._unlistenKeyEventListeners(); |
| 2106 }, | 1291 }, |
| 2107 | 1292 |
| 2108 /** | |
| 2109 * Can be used to imperatively add a key binding to the implementing | |
| 2110 * element. This is the imperative equivalent of declaring a keybinding | |
| 2111 * in the `keyBindings` prototype property. | |
| 2112 */ | |
| 2113 addOwnKeyBinding: function(eventString, handlerName) { | 1293 addOwnKeyBinding: function(eventString, handlerName) { |
| 2114 this._imperativeKeyBindings[eventString] = handlerName; | 1294 this._imperativeKeyBindings[eventString] = handlerName; |
| 2115 this._prepKeyBindings(); | 1295 this._prepKeyBindings(); |
| 2116 this._resetKeyEventListeners(); | 1296 this._resetKeyEventListeners(); |
| 2117 }, | 1297 }, |
| 2118 | 1298 |
| 2119 /** | |
| 2120 * When called, will remove all imperatively-added key bindings. | |
| 2121 */ | |
| 2122 removeOwnKeyBindings: function() { | 1299 removeOwnKeyBindings: function() { |
| 2123 this._imperativeKeyBindings = {}; | 1300 this._imperativeKeyBindings = {}; |
| 2124 this._prepKeyBindings(); | 1301 this._prepKeyBindings(); |
| 2125 this._resetKeyEventListeners(); | 1302 this._resetKeyEventListeners(); |
| 2126 }, | 1303 }, |
| 2127 | 1304 |
| 2128 /** | |
| 2129 * Returns true if a keyboard event matches `eventString`. | |
| 2130 * | |
| 2131 * @param {KeyboardEvent} event | |
| 2132 * @param {string} eventString | |
| 2133 * @return {boolean} | |
| 2134 */ | |
| 2135 keyboardEventMatchesKeys: function(event, eventString) { | 1305 keyboardEventMatchesKeys: function(event, eventString) { |
| 2136 var keyCombos = parseEventString(eventString); | 1306 var keyCombos = parseEventString(eventString); |
| 2137 for (var i = 0; i < keyCombos.length; ++i) { | 1307 for (var i = 0; i < keyCombos.length; ++i) { |
| 2138 if (keyComboMatchesEvent(keyCombos[i], event)) { | 1308 if (keyComboMatchesEvent(keyCombos[i], event)) { |
| 2139 return true; | 1309 return true; |
| 2140 } | 1310 } |
| 2141 } | 1311 } |
| 2142 return false; | 1312 return false; |
| 2143 }, | 1313 }, |
| 2144 | 1314 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 2160 this._collectKeyBindings().forEach(function(keyBindings) { | 1330 this._collectKeyBindings().forEach(function(keyBindings) { |
| 2161 for (var eventString in keyBindings) { | 1331 for (var eventString in keyBindings) { |
| 2162 this._addKeyBinding(eventString, keyBindings[eventString]); | 1332 this._addKeyBinding(eventString, keyBindings[eventString]); |
| 2163 } | 1333 } |
| 2164 }, this); | 1334 }, this); |
| 2165 | 1335 |
| 2166 for (var eventString in this._imperativeKeyBindings) { | 1336 for (var eventString in this._imperativeKeyBindings) { |
| 2167 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); | 1337 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); |
| 2168 } | 1338 } |
| 2169 | 1339 |
| 2170 // Give precedence to combos with modifiers to be checked first. | |
| 2171 for (var eventName in this._keyBindings) { | 1340 for (var eventName in this._keyBindings) { |
| 2172 this._keyBindings[eventName].sort(function (kb1, kb2) { | 1341 this._keyBindings[eventName].sort(function (kb1, kb2) { |
| 2173 var b1 = kb1[0].hasModifiers; | 1342 var b1 = kb1[0].hasModifiers; |
| 2174 var b2 = kb2[0].hasModifiers; | 1343 var b2 = kb2[0].hasModifiers; |
| 2175 return (b1 === b2) ? 0 : b1 ? -1 : 1; | 1344 return (b1 === b2) ? 0 : b1 ? -1 : 1; |
| 2176 }) | 1345 }) |
| 2177 } | 1346 } |
| 2178 }, | 1347 }, |
| 2179 | 1348 |
| 2180 _addKeyBinding: function(eventString, handlerName) { | 1349 _addKeyBinding: function(eventString, handlerName) { |
| (...skipping 30 matching lines...) Expand all Loading... |
| 2211 }, this); | 1380 }, this); |
| 2212 }, | 1381 }, |
| 2213 | 1382 |
| 2214 _unlistenKeyEventListeners: function() { | 1383 _unlistenKeyEventListeners: function() { |
| 2215 var keyHandlerTuple; | 1384 var keyHandlerTuple; |
| 2216 var keyEventTarget; | 1385 var keyEventTarget; |
| 2217 var eventName; | 1386 var eventName; |
| 2218 var boundKeyHandler; | 1387 var boundKeyHandler; |
| 2219 | 1388 |
| 2220 while (this._boundKeyHandlers.length) { | 1389 while (this._boundKeyHandlers.length) { |
| 2221 // My kingdom for block-scope binding and destructuring assignment.. | |
| 2222 keyHandlerTuple = this._boundKeyHandlers.pop(); | 1390 keyHandlerTuple = this._boundKeyHandlers.pop(); |
| 2223 keyEventTarget = keyHandlerTuple[0]; | 1391 keyEventTarget = keyHandlerTuple[0]; |
| 2224 eventName = keyHandlerTuple[1]; | 1392 eventName = keyHandlerTuple[1]; |
| 2225 boundKeyHandler = keyHandlerTuple[2]; | 1393 boundKeyHandler = keyHandlerTuple[2]; |
| 2226 | 1394 |
| 2227 keyEventTarget.removeEventListener(eventName, boundKeyHandler); | 1395 keyEventTarget.removeEventListener(eventName, boundKeyHandler); |
| 2228 } | 1396 } |
| 2229 }, | 1397 }, |
| 2230 | 1398 |
| 2231 _onKeyBindingEvent: function(keyBindings, event) { | 1399 _onKeyBindingEvent: function(keyBindings, event) { |
| 2232 if (this.stopKeyboardEventPropagation) { | 1400 if (this.stopKeyboardEventPropagation) { |
| 2233 event.stopPropagation(); | 1401 event.stopPropagation(); |
| 2234 } | 1402 } |
| 2235 | 1403 |
| 2236 // if event has been already prevented, don't do anything | |
| 2237 if (event.defaultPrevented) { | 1404 if (event.defaultPrevented) { |
| 2238 return; | 1405 return; |
| 2239 } | 1406 } |
| 2240 | 1407 |
| 2241 for (var i = 0; i < keyBindings.length; i++) { | 1408 for (var i = 0; i < keyBindings.length; i++) { |
| 2242 var keyCombo = keyBindings[i][0]; | 1409 var keyCombo = keyBindings[i][0]; |
| 2243 var handlerName = keyBindings[i][1]; | 1410 var handlerName = keyBindings[i][1]; |
| 2244 if (keyComboMatchesEvent(keyCombo, event)) { | 1411 if (keyComboMatchesEvent(keyCombo, event)) { |
| 2245 this._triggerKeyHandler(keyCombo, handlerName, event); | 1412 this._triggerKeyHandler(keyCombo, handlerName, event); |
| 2246 // exit the loop if eventDefault was prevented | |
| 2247 if (event.defaultPrevented) { | 1413 if (event.defaultPrevented) { |
| 2248 return; | 1414 return; |
| 2249 } | 1415 } |
| 2250 } | 1416 } |
| 2251 } | 1417 } |
| 2252 }, | 1418 }, |
| 2253 | 1419 |
| 2254 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { | 1420 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { |
| 2255 var detail = Object.create(keyCombo); | 1421 var detail = Object.create(keyCombo); |
| 2256 detail.keyboardEvent = keyboardEvent; | 1422 detail.keyboardEvent = keyboardEvent; |
| 2257 var event = new CustomEvent(keyCombo.event, { | 1423 var event = new CustomEvent(keyCombo.event, { |
| 2258 detail: detail, | 1424 detail: detail, |
| 2259 cancelable: true | 1425 cancelable: true |
| 2260 }); | 1426 }); |
| 2261 this[handlerName].call(this, event); | 1427 this[handlerName].call(this, event); |
| 2262 if (event.defaultPrevented) { | 1428 if (event.defaultPrevented) { |
| 2263 keyboardEvent.preventDefault(); | 1429 keyboardEvent.preventDefault(); |
| 2264 } | 1430 } |
| 2265 } | 1431 } |
| 2266 }; | 1432 }; |
| 2267 })(); | 1433 })(); |
| 2268 /** | |
| 2269 * `Polymer.IronScrollTargetBehavior` allows an element to respond to scroll e
vents from a | |
| 2270 * designated scroll target. | |
| 2271 * | |
| 2272 * Elements that consume this behavior can override the `_scrollHandler` | |
| 2273 * method to add logic on the scroll event. | |
| 2274 * | |
| 2275 * @demo demo/scrolling-region.html Scrolling Region | |
| 2276 * @demo demo/document.html Document Element | |
| 2277 * @polymerBehavior | |
| 2278 */ | |
| 2279 Polymer.IronScrollTargetBehavior = { | 1434 Polymer.IronScrollTargetBehavior = { |
| 2280 | 1435 |
| 2281 properties: { | 1436 properties: { |
| 2282 | 1437 |
| 2283 /** | |
| 2284 * Specifies the element that will handle the scroll event | |
| 2285 * on the behalf of the current element. This is typically a reference to
an element, | |
| 2286 * but there are a few more posibilities: | |
| 2287 * | |
| 2288 * ### Elements id | |
| 2289 * | |
| 2290 *```html | |
| 2291 * <div id="scrollable-element" style="overflow: auto;"> | |
| 2292 * <x-element scroll-target="scrollable-element"> | |
| 2293 * \x3c!-- Content--\x3e | |
| 2294 * </x-element> | |
| 2295 * </div> | |
| 2296 *``` | |
| 2297 * In this case, the `scrollTarget` will point to the outer div element. | |
| 2298 * | |
| 2299 * ### Document scrolling | |
| 2300 * | |
| 2301 * For document scrolling, you can use the reserved word `document`: | |
| 2302 * | |
| 2303 *```html | |
| 2304 * <x-element scroll-target="document"> | |
| 2305 * \x3c!-- Content --\x3e | |
| 2306 * </x-element> | |
| 2307 *``` | |
| 2308 * | |
| 2309 * ### Elements reference | |
| 2310 * | |
| 2311 *```js | |
| 2312 * appHeader.scrollTarget = document.querySelector('#scrollable-element'); | |
| 2313 *``` | |
| 2314 * | |
| 2315 * @type {HTMLElement} | |
| 2316 */ | |
| 2317 scrollTarget: { | 1438 scrollTarget: { |
| 2318 type: HTMLElement, | 1439 type: HTMLElement, |
| 2319 value: function() { | 1440 value: function() { |
| 2320 return this._defaultScrollTarget; | 1441 return this._defaultScrollTarget; |
| 2321 } | 1442 } |
| 2322 } | 1443 } |
| 2323 }, | 1444 }, |
| 2324 | 1445 |
| 2325 observers: [ | 1446 observers: [ |
| 2326 '_scrollTargetChanged(scrollTarget, isAttached)' | 1447 '_scrollTargetChanged(scrollTarget, isAttached)' |
| 2327 ], | 1448 ], |
| 2328 | 1449 |
| 2329 _scrollTargetChanged: function(scrollTarget, isAttached) { | 1450 _scrollTargetChanged: function(scrollTarget, isAttached) { |
| 2330 var eventTarget; | 1451 var eventTarget; |
| 2331 | 1452 |
| 2332 if (this._oldScrollTarget) { | 1453 if (this._oldScrollTarget) { |
| 2333 eventTarget = this._oldScrollTarget === this._doc ? window : this._oldSc
rollTarget; | 1454 eventTarget = this._oldScrollTarget === this._doc ? window : this._oldSc
rollTarget; |
| 2334 eventTarget.removeEventListener('scroll', this._boundScrollHandler); | 1455 eventTarget.removeEventListener('scroll', this._boundScrollHandler); |
| 2335 this._oldScrollTarget = null; | 1456 this._oldScrollTarget = null; |
| 2336 } | 1457 } |
| 2337 | 1458 |
| 2338 if (!isAttached) { | 1459 if (!isAttached) { |
| 2339 return; | 1460 return; |
| 2340 } | 1461 } |
| 2341 // Support element id references | |
| 2342 if (scrollTarget === 'document') { | 1462 if (scrollTarget === 'document') { |
| 2343 | 1463 |
| 2344 this.scrollTarget = this._doc; | 1464 this.scrollTarget = this._doc; |
| 2345 | 1465 |
| 2346 } else if (typeof scrollTarget === 'string') { | 1466 } else if (typeof scrollTarget === 'string') { |
| 2347 | 1467 |
| 2348 this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] : | 1468 this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] : |
| 2349 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget); | 1469 Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget); |
| 2350 | 1470 |
| 2351 } else if (this._isValidScrollTarget()) { | 1471 } else if (this._isValidScrollTarget()) { |
| 2352 | 1472 |
| 2353 eventTarget = scrollTarget === this._doc ? window : scrollTarget; | 1473 eventTarget = scrollTarget === this._doc ? window : scrollTarget; |
| 2354 this._boundScrollHandler = this._boundScrollHandler || this._scrollHandl
er.bind(this); | 1474 this._boundScrollHandler = this._boundScrollHandler || this._scrollHandl
er.bind(this); |
| 2355 this._oldScrollTarget = scrollTarget; | 1475 this._oldScrollTarget = scrollTarget; |
| 2356 | 1476 |
| 2357 eventTarget.addEventListener('scroll', this._boundScrollHandler); | 1477 eventTarget.addEventListener('scroll', this._boundScrollHandler); |
| 2358 } | 1478 } |
| 2359 }, | 1479 }, |
| 2360 | 1480 |
| 2361 /** | |
| 2362 * Runs on every scroll event. Consumer of this behavior may override this m
ethod. | |
| 2363 * | |
| 2364 * @protected | |
| 2365 */ | |
| 2366 _scrollHandler: function scrollHandler() {}, | 1481 _scrollHandler: function scrollHandler() {}, |
| 2367 | 1482 |
| 2368 /** | |
| 2369 * The default scroll target. Consumers of this behavior may want to customi
ze | |
| 2370 * the default scroll target. | |
| 2371 * | |
| 2372 * @type {Element} | |
| 2373 */ | |
| 2374 get _defaultScrollTarget() { | 1483 get _defaultScrollTarget() { |
| 2375 return this._doc; | 1484 return this._doc; |
| 2376 }, | 1485 }, |
| 2377 | 1486 |
| 2378 /** | |
| 2379 * Shortcut for the document element | |
| 2380 * | |
| 2381 * @type {Element} | |
| 2382 */ | |
| 2383 get _doc() { | 1487 get _doc() { |
| 2384 return this.ownerDocument.documentElement; | 1488 return this.ownerDocument.documentElement; |
| 2385 }, | 1489 }, |
| 2386 | 1490 |
| 2387 /** | |
| 2388 * Gets the number of pixels that the content of an element is scrolled upwa
rd. | |
| 2389 * | |
| 2390 * @type {number} | |
| 2391 */ | |
| 2392 get _scrollTop() { | 1491 get _scrollTop() { |
| 2393 if (this._isValidScrollTarget()) { | 1492 if (this._isValidScrollTarget()) { |
| 2394 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol
lTarget.scrollTop; | 1493 return this.scrollTarget === this._doc ? window.pageYOffset : this.scrol
lTarget.scrollTop; |
| 2395 } | 1494 } |
| 2396 return 0; | 1495 return 0; |
| 2397 }, | 1496 }, |
| 2398 | 1497 |
| 2399 /** | |
| 2400 * Gets the number of pixels that the content of an element is scrolled to t
he left. | |
| 2401 * | |
| 2402 * @type {number} | |
| 2403 */ | |
| 2404 get _scrollLeft() { | 1498 get _scrollLeft() { |
| 2405 if (this._isValidScrollTarget()) { | 1499 if (this._isValidScrollTarget()) { |
| 2406 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol
lTarget.scrollLeft; | 1500 return this.scrollTarget === this._doc ? window.pageXOffset : this.scrol
lTarget.scrollLeft; |
| 2407 } | 1501 } |
| 2408 return 0; | 1502 return 0; |
| 2409 }, | 1503 }, |
| 2410 | 1504 |
| 2411 /** | |
| 2412 * Sets the number of pixels that the content of an element is scrolled upwa
rd. | |
| 2413 * | |
| 2414 * @type {number} | |
| 2415 */ | |
| 2416 set _scrollTop(top) { | 1505 set _scrollTop(top) { |
| 2417 if (this.scrollTarget === this._doc) { | 1506 if (this.scrollTarget === this._doc) { |
| 2418 window.scrollTo(window.pageXOffset, top); | 1507 window.scrollTo(window.pageXOffset, top); |
| 2419 } else if (this._isValidScrollTarget()) { | 1508 } else if (this._isValidScrollTarget()) { |
| 2420 this.scrollTarget.scrollTop = top; | 1509 this.scrollTarget.scrollTop = top; |
| 2421 } | 1510 } |
| 2422 }, | 1511 }, |
| 2423 | 1512 |
| 2424 /** | |
| 2425 * Sets the number of pixels that the content of an element is scrolled to t
he left. | |
| 2426 * | |
| 2427 * @type {number} | |
| 2428 */ | |
| 2429 set _scrollLeft(left) { | 1513 set _scrollLeft(left) { |
| 2430 if (this.scrollTarget === this._doc) { | 1514 if (this.scrollTarget === this._doc) { |
| 2431 window.scrollTo(left, window.pageYOffset); | 1515 window.scrollTo(left, window.pageYOffset); |
| 2432 } else if (this._isValidScrollTarget()) { | 1516 } else if (this._isValidScrollTarget()) { |
| 2433 this.scrollTarget.scrollLeft = left; | 1517 this.scrollTarget.scrollLeft = left; |
| 2434 } | 1518 } |
| 2435 }, | 1519 }, |
| 2436 | 1520 |
| 2437 /** | |
| 2438 * Scrolls the content to a particular place. | |
| 2439 * | |
| 2440 * @method scroll | |
| 2441 * @param {number} left The left position | |
| 2442 * @param {number} top The top position | |
| 2443 */ | |
| 2444 scroll: function(left, top) { | 1521 scroll: function(left, top) { |
| 2445 if (this.scrollTarget === this._doc) { | 1522 if (this.scrollTarget === this._doc) { |
| 2446 window.scrollTo(left, top); | 1523 window.scrollTo(left, top); |
| 2447 } else if (this._isValidScrollTarget()) { | 1524 } else if (this._isValidScrollTarget()) { |
| 2448 this.scrollTarget.scrollLeft = left; | 1525 this.scrollTarget.scrollLeft = left; |
| 2449 this.scrollTarget.scrollTop = top; | 1526 this.scrollTarget.scrollTop = top; |
| 2450 } | 1527 } |
| 2451 }, | 1528 }, |
| 2452 | 1529 |
| 2453 /** | |
| 2454 * Gets the width of the scroll target. | |
| 2455 * | |
| 2456 * @type {number} | |
| 2457 */ | |
| 2458 get _scrollTargetWidth() { | 1530 get _scrollTargetWidth() { |
| 2459 if (this._isValidScrollTarget()) { | 1531 if (this._isValidScrollTarget()) { |
| 2460 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll
Target.offsetWidth; | 1532 return this.scrollTarget === this._doc ? window.innerWidth : this.scroll
Target.offsetWidth; |
| 2461 } | 1533 } |
| 2462 return 0; | 1534 return 0; |
| 2463 }, | 1535 }, |
| 2464 | 1536 |
| 2465 /** | |
| 2466 * Gets the height of the scroll target. | |
| 2467 * | |
| 2468 * @type {number} | |
| 2469 */ | |
| 2470 get _scrollTargetHeight() { | 1537 get _scrollTargetHeight() { |
| 2471 if (this._isValidScrollTarget()) { | 1538 if (this._isValidScrollTarget()) { |
| 2472 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol
lTarget.offsetHeight; | 1539 return this.scrollTarget === this._doc ? window.innerHeight : this.scrol
lTarget.offsetHeight; |
| 2473 } | 1540 } |
| 2474 return 0; | 1541 return 0; |
| 2475 }, | 1542 }, |
| 2476 | 1543 |
| 2477 /** | |
| 2478 * Returns true if the scroll target is a valid HTMLElement. | |
| 2479 * | |
| 2480 * @return {boolean} | |
| 2481 */ | |
| 2482 _isValidScrollTarget: function() { | 1544 _isValidScrollTarget: function() { |
| 2483 return this.scrollTarget instanceof HTMLElement; | 1545 return this.scrollTarget instanceof HTMLElement; |
| 2484 } | 1546 } |
| 2485 }; | 1547 }; |
| 2486 (function() { | 1548 (function() { |
| 2487 | 1549 |
| 2488 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); | 1550 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/); |
| 2489 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; | 1551 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8; |
| 2490 var DEFAULT_PHYSICAL_COUNT = 3; | 1552 var DEFAULT_PHYSICAL_COUNT = 3; |
| 2491 var HIDDEN_Y = '-10000px'; | 1553 var HIDDEN_Y = '-10000px'; |
| 2492 var DEFAULT_GRID_SIZE = 200; | 1554 var DEFAULT_GRID_SIZE = 200; |
| 2493 var SECRET_TABINDEX = -100; | 1555 var SECRET_TABINDEX = -100; |
| 2494 | 1556 |
| 2495 Polymer({ | 1557 Polymer({ |
| 2496 | 1558 |
| 2497 is: 'iron-list', | 1559 is: 'iron-list', |
| 2498 | 1560 |
| 2499 properties: { | 1561 properties: { |
| 2500 | 1562 |
| 2501 /** | |
| 2502 * An array containing items determining how many instances of the templat
e | |
| 2503 * to stamp and that that each template instance should bind to. | |
| 2504 */ | |
| 2505 items: { | 1563 items: { |
| 2506 type: Array | 1564 type: Array |
| 2507 }, | 1565 }, |
| 2508 | 1566 |
| 2509 /** | |
| 2510 * The max count of physical items the pool can extend to. | |
| 2511 */ | |
| 2512 maxPhysicalCount: { | 1567 maxPhysicalCount: { |
| 2513 type: Number, | 1568 type: Number, |
| 2514 value: 500 | 1569 value: 500 |
| 2515 }, | 1570 }, |
| 2516 | 1571 |
| 2517 /** | |
| 2518 * The name of the variable to add to the binding scope for the array | |
| 2519 * element associated with a given template instance. | |
| 2520 */ | |
| 2521 as: { | 1572 as: { |
| 2522 type: String, | 1573 type: String, |
| 2523 value: 'item' | 1574 value: 'item' |
| 2524 }, | 1575 }, |
| 2525 | 1576 |
| 2526 /** | |
| 2527 * The name of the variable to add to the binding scope with the index | |
| 2528 * for the row. | |
| 2529 */ | |
| 2530 indexAs: { | 1577 indexAs: { |
| 2531 type: String, | 1578 type: String, |
| 2532 value: 'index' | 1579 value: 'index' |
| 2533 }, | 1580 }, |
| 2534 | 1581 |
| 2535 /** | |
| 2536 * The name of the variable to add to the binding scope to indicate | |
| 2537 * if the row is selected. | |
| 2538 */ | |
| 2539 selectedAs: { | 1582 selectedAs: { |
| 2540 type: String, | 1583 type: String, |
| 2541 value: 'selected' | 1584 value: 'selected' |
| 2542 }, | 1585 }, |
| 2543 | 1586 |
| 2544 /** | |
| 2545 * When true, the list is rendered as a grid. Grid items must have | |
| 2546 * fixed width and height set via CSS. e.g. | |
| 2547 * | |
| 2548 * ```html | |
| 2549 * <iron-list grid> | |
| 2550 * <template> | |
| 2551 * <div style="width: 100px; height: 100px;"> 100x100 </div> | |
| 2552 * </template> | |
| 2553 * </iron-list> | |
| 2554 * ``` | |
| 2555 */ | |
| 2556 grid: { | 1587 grid: { |
| 2557 type: Boolean, | 1588 type: Boolean, |
| 2558 value: false, | 1589 value: false, |
| 2559 reflectToAttribute: true | 1590 reflectToAttribute: true |
| 2560 }, | 1591 }, |
| 2561 | 1592 |
| 2562 /** | |
| 2563 * When true, tapping a row will select the item, placing its data model | |
| 2564 * in the set of selected items retrievable via the selection property. | |
| 2565 * | |
| 2566 * Note that tapping focusable elements within the list item will not | |
| 2567 * result in selection, since they are presumed to have their * own action
. | |
| 2568 */ | |
| 2569 selectionEnabled: { | 1593 selectionEnabled: { |
| 2570 type: Boolean, | 1594 type: Boolean, |
| 2571 value: false | 1595 value: false |
| 2572 }, | 1596 }, |
| 2573 | 1597 |
| 2574 /** | |
| 2575 * When `multiSelection` is false, this is the currently selected item, or
`null` | |
| 2576 * if no item is selected. | |
| 2577 */ | |
| 2578 selectedItem: { | 1598 selectedItem: { |
| 2579 type: Object, | 1599 type: Object, |
| 2580 notify: true | 1600 notify: true |
| 2581 }, | 1601 }, |
| 2582 | 1602 |
| 2583 /** | |
| 2584 * When `multiSelection` is true, this is an array that contains the selec
ted items. | |
| 2585 */ | |
| 2586 selectedItems: { | 1603 selectedItems: { |
| 2587 type: Object, | 1604 type: Object, |
| 2588 notify: true | 1605 notify: true |
| 2589 }, | 1606 }, |
| 2590 | 1607 |
| 2591 /** | |
| 2592 * When `true`, multiple items may be selected at once (in this case, | |
| 2593 * `selected` is an array of currently selected items). When `false`, | |
| 2594 * only one item may be selected at a time. | |
| 2595 */ | |
| 2596 multiSelection: { | 1608 multiSelection: { |
| 2597 type: Boolean, | 1609 type: Boolean, |
| 2598 value: false | 1610 value: false |
| 2599 } | 1611 } |
| 2600 }, | 1612 }, |
| 2601 | 1613 |
| 2602 observers: [ | 1614 observers: [ |
| 2603 '_itemsChanged(items.*)', | 1615 '_itemsChanged(items.*)', |
| 2604 '_selectionEnabledChanged(selectionEnabled)', | 1616 '_selectionEnabledChanged(selectionEnabled)', |
| 2605 '_multiSelectionChanged(multiSelection)', | 1617 '_multiSelectionChanged(multiSelection)', |
| 2606 '_setOverflow(scrollTarget)' | 1618 '_setOverflow(scrollTarget)' |
| 2607 ], | 1619 ], |
| 2608 | 1620 |
| 2609 behaviors: [ | 1621 behaviors: [ |
| 2610 Polymer.Templatizer, | 1622 Polymer.Templatizer, |
| 2611 Polymer.IronResizableBehavior, | 1623 Polymer.IronResizableBehavior, |
| 2612 Polymer.IronA11yKeysBehavior, | 1624 Polymer.IronA11yKeysBehavior, |
| 2613 Polymer.IronScrollTargetBehavior | 1625 Polymer.IronScrollTargetBehavior |
| 2614 ], | 1626 ], |
| 2615 | 1627 |
| 2616 keyBindings: { | 1628 keyBindings: { |
| 2617 'up': '_didMoveUp', | 1629 'up': '_didMoveUp', |
| 2618 'down': '_didMoveDown', | 1630 'down': '_didMoveDown', |
| 2619 'enter': '_didEnter' | 1631 'enter': '_didEnter' |
| 2620 }, | 1632 }, |
| 2621 | 1633 |
| 2622 /** | |
| 2623 * The ratio of hidden tiles that should remain in the scroll direction. | |
| 2624 * Recommended value ~0.5, so it will distribute tiles evely in both directi
ons. | |
| 2625 */ | |
| 2626 _ratio: 0.5, | 1634 _ratio: 0.5, |
| 2627 | 1635 |
| 2628 /** | |
| 2629 * The padding-top value for the list. | |
| 2630 */ | |
| 2631 _scrollerPaddingTop: 0, | 1636 _scrollerPaddingTop: 0, |
| 2632 | 1637 |
| 2633 /** | |
| 2634 * This value is the same as `scrollTop`. | |
| 2635 */ | |
| 2636 _scrollPosition: 0, | 1638 _scrollPosition: 0, |
| 2637 | 1639 |
| 2638 /** | |
| 2639 * The sum of the heights of all the tiles in the DOM. | |
| 2640 */ | |
| 2641 _physicalSize: 0, | 1640 _physicalSize: 0, |
| 2642 | 1641 |
| 2643 /** | |
| 2644 * The average `offsetHeight` of the tiles observed till now. | |
| 2645 */ | |
| 2646 _physicalAverage: 0, | 1642 _physicalAverage: 0, |
| 2647 | 1643 |
| 2648 /** | |
| 2649 * The number of tiles which `offsetHeight` > 0 observed until now. | |
| 2650 */ | |
| 2651 _physicalAverageCount: 0, | 1644 _physicalAverageCount: 0, |
| 2652 | 1645 |
| 2653 /** | |
| 2654 * The Y position of the item rendered in the `_physicalStart` | |
| 2655 * tile relative to the scrolling list. | |
| 2656 */ | |
| 2657 _physicalTop: 0, | 1646 _physicalTop: 0, |
| 2658 | 1647 |
| 2659 /** | |
| 2660 * The number of items in the list. | |
| 2661 */ | |
| 2662 _virtualCount: 0, | 1648 _virtualCount: 0, |
| 2663 | 1649 |
| 2664 /** | |
| 2665 * A map between an item key and its physical item index | |
| 2666 */ | |
| 2667 _physicalIndexForKey: null, | 1650 _physicalIndexForKey: null, |
| 2668 | 1651 |
| 2669 /** | |
| 2670 * The estimated scroll height based on `_physicalAverage` | |
| 2671 */ | |
| 2672 _estScrollHeight: 0, | 1652 _estScrollHeight: 0, |
| 2673 | 1653 |
| 2674 /** | |
| 2675 * The scroll height of the dom node | |
| 2676 */ | |
| 2677 _scrollHeight: 0, | 1654 _scrollHeight: 0, |
| 2678 | 1655 |
| 2679 /** | |
| 2680 * The height of the list. This is referred as the viewport in the context o
f list. | |
| 2681 */ | |
| 2682 _viewportHeight: 0, | 1656 _viewportHeight: 0, |
| 2683 | 1657 |
| 2684 /** | |
| 2685 * The width of the list. This is referred as the viewport in the context of
list. | |
| 2686 */ | |
| 2687 _viewportWidth: 0, | 1658 _viewportWidth: 0, |
| 2688 | 1659 |
| 2689 /** | |
| 2690 * An array of DOM nodes that are currently in the tree | |
| 2691 * @type {?Array<!TemplatizerNode>} | |
| 2692 */ | |
| 2693 _physicalItems: null, | 1660 _physicalItems: null, |
| 2694 | 1661 |
| 2695 /** | |
| 2696 * An array of heights for each item in `_physicalItems` | |
| 2697 * @type {?Array<number>} | |
| 2698 */ | |
| 2699 _physicalSizes: null, | 1662 _physicalSizes: null, |
| 2700 | 1663 |
| 2701 /** | |
| 2702 * A cached value for the first visible index. | |
| 2703 * See `firstVisibleIndex` | |
| 2704 * @type {?number} | |
| 2705 */ | |
| 2706 _firstVisibleIndexVal: null, | 1664 _firstVisibleIndexVal: null, |
| 2707 | 1665 |
| 2708 /** | |
| 2709 * A cached value for the last visible index. | |
| 2710 * See `lastVisibleIndex` | |
| 2711 * @type {?number} | |
| 2712 */ | |
| 2713 _lastVisibleIndexVal: null, | 1666 _lastVisibleIndexVal: null, |
| 2714 | 1667 |
| 2715 /** | |
| 2716 * A Polymer collection for the items. | |
| 2717 * @type {?Polymer.Collection} | |
| 2718 */ | |
| 2719 _collection: null, | 1668 _collection: null, |
| 2720 | 1669 |
| 2721 /** | |
| 2722 * True if the current item list was rendered for the first time | |
| 2723 * after attached. | |
| 2724 */ | |
| 2725 _itemsRendered: false, | 1670 _itemsRendered: false, |
| 2726 | 1671 |
| 2727 /** | |
| 2728 * The page that is currently rendered. | |
| 2729 */ | |
| 2730 _lastPage: null, | 1672 _lastPage: null, |
| 2731 | 1673 |
| 2732 /** | |
| 2733 * The max number of pages to render. One page is equivalent to the height o
f the list. | |
| 2734 */ | |
| 2735 _maxPages: 3, | 1674 _maxPages: 3, |
| 2736 | 1675 |
| 2737 /** | |
| 2738 * The currently focused physical item. | |
| 2739 */ | |
| 2740 _focusedItem: null, | 1676 _focusedItem: null, |
| 2741 | 1677 |
| 2742 /** | |
| 2743 * The index of the `_focusedItem`. | |
| 2744 */ | |
| 2745 _focusedIndex: -1, | 1678 _focusedIndex: -1, |
| 2746 | 1679 |
| 2747 /** | |
| 2748 * The the item that is focused if it is moved offscreen. | |
| 2749 * @private {?TemplatizerNode} | |
| 2750 */ | |
| 2751 _offscreenFocusedItem: null, | 1680 _offscreenFocusedItem: null, |
| 2752 | 1681 |
| 2753 /** | |
| 2754 * The item that backfills the `_offscreenFocusedItem` in the physical items | |
| 2755 * list when that item is moved offscreen. | |
| 2756 */ | |
| 2757 _focusBackfillItem: null, | 1682 _focusBackfillItem: null, |
| 2758 | 1683 |
| 2759 /** | |
| 2760 * The maximum items per row | |
| 2761 */ | |
| 2762 _itemsPerRow: 1, | 1684 _itemsPerRow: 1, |
| 2763 | 1685 |
| 2764 /** | |
| 2765 * The width of each grid item | |
| 2766 */ | |
| 2767 _itemWidth: 0, | 1686 _itemWidth: 0, |
| 2768 | 1687 |
| 2769 /** | |
| 2770 * The height of the row in grid layout. | |
| 2771 */ | |
| 2772 _rowHeight: 0, | 1688 _rowHeight: 0, |
| 2773 | 1689 |
| 2774 /** | |
| 2775 * The bottom of the physical content. | |
| 2776 */ | |
| 2777 get _physicalBottom() { | 1690 get _physicalBottom() { |
| 2778 return this._physicalTop + this._physicalSize; | 1691 return this._physicalTop + this._physicalSize; |
| 2779 }, | 1692 }, |
| 2780 | 1693 |
| 2781 /** | |
| 2782 * The bottom of the scroll. | |
| 2783 */ | |
| 2784 get _scrollBottom() { | 1694 get _scrollBottom() { |
| 2785 return this._scrollPosition + this._viewportHeight; | 1695 return this._scrollPosition + this._viewportHeight; |
| 2786 }, | 1696 }, |
| 2787 | 1697 |
| 2788 /** | |
| 2789 * The n-th item rendered in the last physical item. | |
| 2790 */ | |
| 2791 get _virtualEnd() { | 1698 get _virtualEnd() { |
| 2792 return this._virtualStart + this._physicalCount - 1; | 1699 return this._virtualStart + this._physicalCount - 1; |
| 2793 }, | 1700 }, |
| 2794 | 1701 |
| 2795 /** | |
| 2796 * The height of the physical content that isn't on the screen. | |
| 2797 */ | |
| 2798 get _hiddenContentSize() { | 1702 get _hiddenContentSize() { |
| 2799 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic
alSize; | 1703 var size = this.grid ? this._physicalRows * this._rowHeight : this._physic
alSize; |
| 2800 return size - this._viewportHeight; | 1704 return size - this._viewportHeight; |
| 2801 }, | 1705 }, |
| 2802 | 1706 |
| 2803 /** | |
| 2804 * The maximum scroll top value. | |
| 2805 */ | |
| 2806 get _maxScrollTop() { | 1707 get _maxScrollTop() { |
| 2807 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin
gTop; | 1708 return this._estScrollHeight - this._viewportHeight + this._scrollerPaddin
gTop; |
| 2808 }, | 1709 }, |
| 2809 | 1710 |
| 2810 /** | |
| 2811 * The lowest n-th value for an item such that it can be rendered in `_physi
calStart`. | |
| 2812 */ | |
| 2813 _minVirtualStart: 0, | 1711 _minVirtualStart: 0, |
| 2814 | 1712 |
| 2815 /** | |
| 2816 * The largest n-th value for an item such that it can be rendered in `_phys
icalStart`. | |
| 2817 */ | |
| 2818 get _maxVirtualStart() { | 1713 get _maxVirtualStart() { |
| 2819 return Math.max(0, this._virtualCount - this._physicalCount); | 1714 return Math.max(0, this._virtualCount - this._physicalCount); |
| 2820 }, | 1715 }, |
| 2821 | 1716 |
| 2822 /** | |
| 2823 * The n-th item rendered in the `_physicalStart` tile. | |
| 2824 */ | |
| 2825 _virtualStartVal: 0, | 1717 _virtualStartVal: 0, |
| 2826 | 1718 |
| 2827 set _virtualStart(val) { | 1719 set _virtualStart(val) { |
| 2828 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min
VirtualStart, val)); | 1720 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min
VirtualStart, val)); |
| 2829 }, | 1721 }, |
| 2830 | 1722 |
| 2831 get _virtualStart() { | 1723 get _virtualStart() { |
| 2832 return this._virtualStartVal || 0; | 1724 return this._virtualStartVal || 0; |
| 2833 }, | 1725 }, |
| 2834 | 1726 |
| 2835 /** | |
| 2836 * The k-th tile that is at the top of the scrolling list. | |
| 2837 */ | |
| 2838 _physicalStartVal: 0, | 1727 _physicalStartVal: 0, |
| 2839 | 1728 |
| 2840 set _physicalStart(val) { | 1729 set _physicalStart(val) { |
| 2841 this._physicalStartVal = val % this._physicalCount; | 1730 this._physicalStartVal = val % this._physicalCount; |
| 2842 if (this._physicalStartVal < 0) { | 1731 if (this._physicalStartVal < 0) { |
| 2843 this._physicalStartVal = this._physicalCount + this._physicalStartVal; | 1732 this._physicalStartVal = this._physicalCount + this._physicalStartVal; |
| 2844 } | 1733 } |
| 2845 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; | 1734 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; |
| 2846 }, | 1735 }, |
| 2847 | 1736 |
| 2848 get _physicalStart() { | 1737 get _physicalStart() { |
| 2849 return this._physicalStartVal || 0; | 1738 return this._physicalStartVal || 0; |
| 2850 }, | 1739 }, |
| 2851 | 1740 |
| 2852 /** | |
| 2853 * The number of tiles in the DOM. | |
| 2854 */ | |
| 2855 _physicalCountVal: 0, | 1741 _physicalCountVal: 0, |
| 2856 | 1742 |
| 2857 set _physicalCount(val) { | 1743 set _physicalCount(val) { |
| 2858 this._physicalCountVal = val; | 1744 this._physicalCountVal = val; |
| 2859 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; | 1745 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this
._physicalCount; |
| 2860 }, | 1746 }, |
| 2861 | 1747 |
| 2862 get _physicalCount() { | 1748 get _physicalCount() { |
| 2863 return this._physicalCountVal; | 1749 return this._physicalCountVal; |
| 2864 }, | 1750 }, |
| 2865 | 1751 |
| 2866 /** | |
| 2867 * The k-th tile that is at the bottom of the scrolling list. | |
| 2868 */ | |
| 2869 _physicalEnd: 0, | 1752 _physicalEnd: 0, |
| 2870 | 1753 |
| 2871 /** | |
| 2872 * An optimal physical size such that we will have enough physical items | |
| 2873 * to fill up the viewport and recycle when the user scrolls. | |
| 2874 * | |
| 2875 * This default value assumes that we will at least have the equivalent | |
| 2876 * to a viewport of physical items above and below the user's viewport. | |
| 2877 */ | |
| 2878 get _optPhysicalSize() { | 1754 get _optPhysicalSize() { |
| 2879 if (this.grid) { | 1755 if (this.grid) { |
| 2880 return this._estRowsInView * this._rowHeight * this._maxPages; | 1756 return this._estRowsInView * this._rowHeight * this._maxPages; |
| 2881 } | 1757 } |
| 2882 return this._viewportHeight * this._maxPages; | 1758 return this._viewportHeight * this._maxPages; |
| 2883 }, | 1759 }, |
| 2884 | 1760 |
| 2885 get _optPhysicalCount() { | 1761 get _optPhysicalCount() { |
| 2886 return this._estRowsInView * this._itemsPerRow * this._maxPages; | 1762 return this._estRowsInView * this._itemsPerRow * this._maxPages; |
| 2887 }, | 1763 }, |
| 2888 | 1764 |
| 2889 /** | |
| 2890 * True if the current list is visible. | |
| 2891 */ | |
| 2892 get _isVisible() { | 1765 get _isVisible() { |
| 2893 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this.
scrollTarget.offsetHeight); | 1766 return this.scrollTarget && Boolean(this.scrollTarget.offsetWidth || this.
scrollTarget.offsetHeight); |
| 2894 }, | 1767 }, |
| 2895 | 1768 |
| 2896 /** | |
| 2897 * Gets the index of the first visible item in the viewport. | |
| 2898 * | |
| 2899 * @type {number} | |
| 2900 */ | |
| 2901 get firstVisibleIndex() { | 1769 get firstVisibleIndex() { |
| 2902 if (this._firstVisibleIndexVal === null) { | 1770 if (this._firstVisibleIndexVal === null) { |
| 2903 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); | 1771 var physicalOffset = Math.floor(this._physicalTop + this._scrollerPaddin
gTop); |
| 2904 | 1772 |
| 2905 this._firstVisibleIndexVal = this._iterateItems( | 1773 this._firstVisibleIndexVal = this._iterateItems( |
| 2906 function(pidx, vidx) { | 1774 function(pidx, vidx) { |
| 2907 physicalOffset += this._getPhysicalSizeIncrement(pidx); | 1775 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
| 2908 | 1776 |
| 2909 if (physicalOffset > this._scrollPosition) { | 1777 if (physicalOffset > this._scrollPosition) { |
| 2910 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; | 1778 return this.grid ? vidx - (vidx % this._itemsPerRow) : vidx; |
| 2911 } | 1779 } |
| 2912 // Handle a partially rendered final row in grid mode | |
| 2913 if (this.grid && this._virtualCount - 1 === vidx) { | 1780 if (this.grid && this._virtualCount - 1 === vidx) { |
| 2914 return vidx - (vidx % this._itemsPerRow); | 1781 return vidx - (vidx % this._itemsPerRow); |
| 2915 } | 1782 } |
| 2916 }) || 0; | 1783 }) || 0; |
| 2917 } | 1784 } |
| 2918 return this._firstVisibleIndexVal; | 1785 return this._firstVisibleIndexVal; |
| 2919 }, | 1786 }, |
| 2920 | 1787 |
| 2921 /** | |
| 2922 * Gets the index of the last visible item in the viewport. | |
| 2923 * | |
| 2924 * @type {number} | |
| 2925 */ | |
| 2926 get lastVisibleIndex() { | 1788 get lastVisibleIndex() { |
| 2927 if (this._lastVisibleIndexVal === null) { | 1789 if (this._lastVisibleIndexVal === null) { |
| 2928 if (this.grid) { | 1790 if (this.grid) { |
| 2929 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i
temsPerRow - 1; | 1791 var lastIndex = this.firstVisibleIndex + this._estRowsInView * this._i
temsPerRow - 1; |
| 2930 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex); | 1792 this._lastVisibleIndexVal = Math.min(this._virtualCount, lastIndex); |
| 2931 } else { | 1793 } else { |
| 2932 var physicalOffset = this._physicalTop; | 1794 var physicalOffset = this._physicalTop; |
| 2933 this._iterateItems(function(pidx, vidx) { | 1795 this._iterateItems(function(pidx, vidx) { |
| 2934 if (physicalOffset < this._scrollBottom) { | 1796 if (physicalOffset < this._scrollBottom) { |
| 2935 this._lastVisibleIndexVal = vidx; | 1797 this._lastVisibleIndexVal = vidx; |
| 2936 } else { | 1798 } else { |
| 2937 // Break _iterateItems | |
| 2938 return true; | 1799 return true; |
| 2939 } | 1800 } |
| 2940 physicalOffset += this._getPhysicalSizeIncrement(pidx); | 1801 physicalOffset += this._getPhysicalSizeIncrement(pidx); |
| 2941 }); | 1802 }); |
| 2942 } | 1803 } |
| 2943 } | 1804 } |
| 2944 return this._lastVisibleIndexVal; | 1805 return this._lastVisibleIndexVal; |
| 2945 }, | 1806 }, |
| 2946 | 1807 |
| 2947 get _defaultScrollTarget() { | 1808 get _defaultScrollTarget() { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 2959 return Math.ceil(this._physicalCount / this._itemsPerRow); | 1820 return Math.ceil(this._physicalCount / this._itemsPerRow); |
| 2960 }, | 1821 }, |
| 2961 | 1822 |
| 2962 ready: function() { | 1823 ready: function() { |
| 2963 this.addEventListener('focus', this._didFocus.bind(this), true); | 1824 this.addEventListener('focus', this._didFocus.bind(this), true); |
| 2964 }, | 1825 }, |
| 2965 | 1826 |
| 2966 attached: function() { | 1827 attached: function() { |
| 2967 this.updateViewportBoundaries(); | 1828 this.updateViewportBoundaries(); |
| 2968 this._render(); | 1829 this._render(); |
| 2969 // `iron-resize` is fired when the list is attached if the event is added | |
| 2970 // before attached causing unnecessary work. | |
| 2971 this.listen(this, 'iron-resize', '_resizeHandler'); | 1830 this.listen(this, 'iron-resize', '_resizeHandler'); |
| 2972 }, | 1831 }, |
| 2973 | 1832 |
| 2974 detached: function() { | 1833 detached: function() { |
| 2975 this._itemsRendered = false; | 1834 this._itemsRendered = false; |
| 2976 this.unlisten(this, 'iron-resize', '_resizeHandler'); | 1835 this.unlisten(this, 'iron-resize', '_resizeHandler'); |
| 2977 }, | 1836 }, |
| 2978 | 1837 |
| 2979 /** | |
| 2980 * Set the overflow property if this element has its own scrolling region | |
| 2981 */ | |
| 2982 _setOverflow: function(scrollTarget) { | 1838 _setOverflow: function(scrollTarget) { |
| 2983 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; | 1839 this.style.webkitOverflowScrolling = scrollTarget === this ? 'touch' : ''; |
| 2984 this.style.overflow = scrollTarget === this ? 'auto' : ''; | 1840 this.style.overflow = scrollTarget === this ? 'auto' : ''; |
| 2985 }, | 1841 }, |
| 2986 | 1842 |
| 2987 /** | |
| 2988 * Invoke this method if you dynamically update the viewport's | |
| 2989 * size or CSS padding. | |
| 2990 * | |
| 2991 * @method updateViewportBoundaries | |
| 2992 */ | |
| 2993 updateViewportBoundaries: function() { | 1843 updateViewportBoundaries: function() { |
| 2994 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : | 1844 this._scrollerPaddingTop = this.scrollTarget === this ? 0 : |
| 2995 parseInt(window.getComputedStyle(this)['padding-top'], 10); | 1845 parseInt(window.getComputedStyle(this)['padding-top'], 10); |
| 2996 | 1846 |
| 2997 this._viewportHeight = this._scrollTargetHeight; | 1847 this._viewportHeight = this._scrollTargetHeight; |
| 2998 if (this.grid) { | 1848 if (this.grid) { |
| 2999 this._updateGridMetrics(); | 1849 this._updateGridMetrics(); |
| 3000 } | 1850 } |
| 3001 }, | 1851 }, |
| 3002 | 1852 |
| 3003 /** | |
| 3004 * Update the models, the position of the | |
| 3005 * items in the viewport and recycle tiles as needed. | |
| 3006 */ | |
| 3007 _scrollHandler: function() { | 1853 _scrollHandler: function() { |
| 3008 // clamp the `scrollTop` value | |
| 3009 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop))
; | 1854 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scrollTop))
; |
| 3010 var delta = scrollTop - this._scrollPosition; | 1855 var delta = scrollTop - this._scrollPosition; |
| 3011 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto
m; | 1856 var tileHeight, tileTop, kth, recycledTileSet, scrollBottom, physicalBotto
m; |
| 3012 var ratio = this._ratio; | 1857 var ratio = this._ratio; |
| 3013 var recycledTiles = 0; | 1858 var recycledTiles = 0; |
| 3014 var hiddenContentSize = this._hiddenContentSize; | 1859 var hiddenContentSize = this._hiddenContentSize; |
| 3015 var currentRatio = ratio; | 1860 var currentRatio = ratio; |
| 3016 var movingUp = []; | 1861 var movingUp = []; |
| 3017 | 1862 |
| 3018 // track the last `scrollTop` | |
| 3019 this._scrollPosition = scrollTop; | 1863 this._scrollPosition = scrollTop; |
| 3020 | 1864 |
| 3021 // clear cached visible indexes | |
| 3022 this._firstVisibleIndexVal = null; | 1865 this._firstVisibleIndexVal = null; |
| 3023 this._lastVisibleIndexVal = null; | 1866 this._lastVisibleIndexVal = null; |
| 3024 | 1867 |
| 3025 scrollBottom = this._scrollBottom; | 1868 scrollBottom = this._scrollBottom; |
| 3026 physicalBottom = this._physicalBottom; | 1869 physicalBottom = this._physicalBottom; |
| 3027 | 1870 |
| 3028 // random access | |
| 3029 if (Math.abs(delta) > this._physicalSize) { | 1871 if (Math.abs(delta) > this._physicalSize) { |
| 3030 this._physicalTop += delta; | 1872 this._physicalTop += delta; |
| 3031 recycledTiles = Math.round(delta / this._physicalAverage); | 1873 recycledTiles = Math.round(delta / this._physicalAverage); |
| 3032 } | 1874 } |
| 3033 // scroll up | |
| 3034 else if (delta < 0) { | 1875 else if (delta < 0) { |
| 3035 var topSpace = scrollTop - this._physicalTop; | 1876 var topSpace = scrollTop - this._physicalTop; |
| 3036 var virtualStart = this._virtualStart; | 1877 var virtualStart = this._virtualStart; |
| 3037 | 1878 |
| 3038 recycledTileSet = []; | 1879 recycledTileSet = []; |
| 3039 | 1880 |
| 3040 kth = this._physicalEnd; | 1881 kth = this._physicalEnd; |
| 3041 currentRatio = topSpace / hiddenContentSize; | 1882 currentRatio = topSpace / hiddenContentSize; |
| 3042 | 1883 |
| 3043 // move tiles from bottom to top | |
| 3044 while ( | 1884 while ( |
| 3045 // approximate `currentRatio` to `ratio` | |
| 3046 currentRatio < ratio && | 1885 currentRatio < ratio && |
| 3047 // recycle less physical items than the total | |
| 3048 recycledTiles < this._physicalCount && | 1886 recycledTiles < this._physicalCount && |
| 3049 // ensure that these recycled tiles are needed | |
| 3050 virtualStart - recycledTiles > 0 && | 1887 virtualStart - recycledTiles > 0 && |
| 3051 // ensure that the tile is not visible | |
| 3052 physicalBottom - this._getPhysicalSizeIncrement(kth) > scrollBottom | 1888 physicalBottom - this._getPhysicalSizeIncrement(kth) > scrollBottom |
| 3053 ) { | 1889 ) { |
| 3054 | 1890 |
| 3055 tileHeight = this._getPhysicalSizeIncrement(kth); | 1891 tileHeight = this._getPhysicalSizeIncrement(kth); |
| 3056 currentRatio += tileHeight / hiddenContentSize; | 1892 currentRatio += tileHeight / hiddenContentSize; |
| 3057 physicalBottom -= tileHeight; | 1893 physicalBottom -= tileHeight; |
| 3058 recycledTileSet.push(kth); | 1894 recycledTileSet.push(kth); |
| 3059 recycledTiles++; | 1895 recycledTiles++; |
| 3060 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; | 1896 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1; |
| 3061 } | 1897 } |
| 3062 | 1898 |
| 3063 movingUp = recycledTileSet; | 1899 movingUp = recycledTileSet; |
| 3064 recycledTiles = -recycledTiles; | 1900 recycledTiles = -recycledTiles; |
| 3065 } | 1901 } |
| 3066 // scroll down | |
| 3067 else if (delta > 0) { | 1902 else if (delta > 0) { |
| 3068 var bottomSpace = physicalBottom - scrollBottom; | 1903 var bottomSpace = physicalBottom - scrollBottom; |
| 3069 var virtualEnd = this._virtualEnd; | 1904 var virtualEnd = this._virtualEnd; |
| 3070 var lastVirtualItemIndex = this._virtualCount-1; | 1905 var lastVirtualItemIndex = this._virtualCount-1; |
| 3071 | 1906 |
| 3072 recycledTileSet = []; | 1907 recycledTileSet = []; |
| 3073 | 1908 |
| 3074 kth = this._physicalStart; | 1909 kth = this._physicalStart; |
| 3075 currentRatio = bottomSpace / hiddenContentSize; | 1910 currentRatio = bottomSpace / hiddenContentSize; |
| 3076 | 1911 |
| 3077 // move tiles from top to bottom | |
| 3078 while ( | 1912 while ( |
| 3079 // approximate `currentRatio` to `ratio` | |
| 3080 currentRatio < ratio && | 1913 currentRatio < ratio && |
| 3081 // recycle less physical items than the total | |
| 3082 recycledTiles < this._physicalCount && | 1914 recycledTiles < this._physicalCount && |
| 3083 // ensure that these recycled tiles are needed | |
| 3084 virtualEnd + recycledTiles < lastVirtualItemIndex && | 1915 virtualEnd + recycledTiles < lastVirtualItemIndex && |
| 3085 // ensure that the tile is not visible | |
| 3086 this._physicalTop + this._getPhysicalSizeIncrement(kth) < scrollTop | 1916 this._physicalTop + this._getPhysicalSizeIncrement(kth) < scrollTop |
| 3087 ) { | 1917 ) { |
| 3088 | 1918 |
| 3089 tileHeight = this._getPhysicalSizeIncrement(kth); | 1919 tileHeight = this._getPhysicalSizeIncrement(kth); |
| 3090 currentRatio += tileHeight / hiddenContentSize; | 1920 currentRatio += tileHeight / hiddenContentSize; |
| 3091 | 1921 |
| 3092 this._physicalTop += tileHeight; | 1922 this._physicalTop += tileHeight; |
| 3093 recycledTileSet.push(kth); | 1923 recycledTileSet.push(kth); |
| 3094 recycledTiles++; | 1924 recycledTiles++; |
| 3095 kth = (kth + 1) % this._physicalCount; | 1925 kth = (kth + 1) % this._physicalCount; |
| 3096 } | 1926 } |
| 3097 } | 1927 } |
| 3098 | 1928 |
| 3099 if (recycledTiles === 0) { | 1929 if (recycledTiles === 0) { |
| 3100 // Try to increase the pool if the list's client height isn't filled up
with physical items | |
| 3101 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { | 1930 if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) { |
| 3102 this._increasePoolIfNeeded(); | 1931 this._increasePoolIfNeeded(); |
| 3103 } | 1932 } |
| 3104 } else { | 1933 } else { |
| 3105 this._virtualStart = this._virtualStart + recycledTiles; | 1934 this._virtualStart = this._virtualStart + recycledTiles; |
| 3106 this._physicalStart = this._physicalStart + recycledTiles; | 1935 this._physicalStart = this._physicalStart + recycledTiles; |
| 3107 this._update(recycledTileSet, movingUp); | 1936 this._update(recycledTileSet, movingUp); |
| 3108 } | 1937 } |
| 3109 }, | 1938 }, |
| 3110 | 1939 |
| 3111 /** | |
| 3112 * Update the list of items, starting from the `_virtualStart` item. | |
| 3113 * @param {!Array<number>=} itemSet | |
| 3114 * @param {!Array<number>=} movingUp | |
| 3115 */ | |
| 3116 _update: function(itemSet, movingUp) { | 1940 _update: function(itemSet, movingUp) { |
| 3117 // manage focus | |
| 3118 this._manageFocus(); | 1941 this._manageFocus(); |
| 3119 // update models | |
| 3120 this._assignModels(itemSet); | 1942 this._assignModels(itemSet); |
| 3121 // measure heights | |
| 3122 this._updateMetrics(itemSet); | 1943 this._updateMetrics(itemSet); |
| 3123 // adjust offset after measuring | |
| 3124 if (movingUp) { | 1944 if (movingUp) { |
| 3125 while (movingUp.length) { | 1945 while (movingUp.length) { |
| 3126 var idx = movingUp.pop(); | 1946 var idx = movingUp.pop(); |
| 3127 this._physicalTop -= this._getPhysicalSizeIncrement(idx); | 1947 this._physicalTop -= this._getPhysicalSizeIncrement(idx); |
| 3128 } | 1948 } |
| 3129 } | 1949 } |
| 3130 // update the position of the items | |
| 3131 this._positionItems(); | 1950 this._positionItems(); |
| 3132 // set the scroller size | |
| 3133 this._updateScrollerSize(); | 1951 this._updateScrollerSize(); |
| 3134 // increase the pool of physical items | |
| 3135 this._increasePoolIfNeeded(); | 1952 this._increasePoolIfNeeded(); |
| 3136 }, | 1953 }, |
| 3137 | 1954 |
| 3138 /** | |
| 3139 * Creates a pool of DOM elements and attaches them to the local dom. | |
| 3140 */ | |
| 3141 _createPool: function(size) { | 1955 _createPool: function(size) { |
| 3142 var physicalItems = new Array(size); | 1956 var physicalItems = new Array(size); |
| 3143 | 1957 |
| 3144 this._ensureTemplatized(); | 1958 this._ensureTemplatized(); |
| 3145 | 1959 |
| 3146 for (var i = 0; i < size; i++) { | 1960 for (var i = 0; i < size; i++) { |
| 3147 var inst = this.stamp(null); | 1961 var inst = this.stamp(null); |
| 3148 // First element child is item; Safari doesn't support children[0] | |
| 3149 // on a doc fragment | |
| 3150 physicalItems[i] = inst.root.querySelector('*'); | 1962 physicalItems[i] = inst.root.querySelector('*'); |
| 3151 Polymer.dom(this).appendChild(inst.root); | 1963 Polymer.dom(this).appendChild(inst.root); |
| 3152 } | 1964 } |
| 3153 return physicalItems; | 1965 return physicalItems; |
| 3154 }, | 1966 }, |
| 3155 | 1967 |
| 3156 /** | |
| 3157 * Increases the pool of physical items only if needed. | |
| 3158 * | |
| 3159 * @return {boolean} True if the pool was increased. | |
| 3160 */ | |
| 3161 _increasePoolIfNeeded: function() { | 1968 _increasePoolIfNeeded: function() { |
| 3162 // Base case 1: the list has no height. | |
| 3163 if (this._viewportHeight === 0) { | 1969 if (this._viewportHeight === 0) { |
| 3164 return false; | 1970 return false; |
| 3165 } | 1971 } |
| 3166 // Base case 2: If the physical size is optimal and the list's client heig
ht is full | |
| 3167 // with physical items, don't increase the pool. | |
| 3168 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi
s._physicalTop <= this._scrollPosition; | 1972 var isClientHeightFull = this._physicalBottom >= this._scrollBottom && thi
s._physicalTop <= this._scrollPosition; |
| 3169 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { | 1973 if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) { |
| 3170 return false; | 1974 return false; |
| 3171 } | 1975 } |
| 3172 // this value should range between [0 <= `currentPage` <= `_maxPages`] | |
| 3173 var currentPage = Math.floor(this._physicalSize / this._viewportHeight); | 1976 var currentPage = Math.floor(this._physicalSize / this._viewportHeight); |
| 3174 | 1977 |
| 3175 if (currentPage === 0) { | 1978 if (currentPage === 0) { |
| 3176 // fill the first page | |
| 3177 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); | 1979 this._debounceTemplate(this._increasePool.bind(this, Math.round(this._ph
ysicalCount * 0.5))); |
| 3178 } else if (this._lastPage !== currentPage && isClientHeightFull) { | 1980 } else if (this._lastPage !== currentPage && isClientHeightFull) { |
| 3179 // paint the page and defer the next increase | |
| 3180 // wait 16ms which is rough enough to get paint cycle. | |
| 3181 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, this._itemsPerRow), 16)); | 1981 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increa
sePool.bind(this, this._itemsPerRow), 16)); |
| 3182 } else { | 1982 } else { |
| 3183 // fill the rest of the pages | |
| 3184 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow))
; | 1983 this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow))
; |
| 3185 } | 1984 } |
| 3186 | 1985 |
| 3187 this._lastPage = currentPage; | 1986 this._lastPage = currentPage; |
| 3188 | 1987 |
| 3189 return true; | 1988 return true; |
| 3190 }, | 1989 }, |
| 3191 | 1990 |
| 3192 /** | |
| 3193 * Increases the pool size. | |
| 3194 */ | |
| 3195 _increasePool: function(missingItems) { | 1991 _increasePool: function(missingItems) { |
| 3196 var nextPhysicalCount = Math.min( | 1992 var nextPhysicalCount = Math.min( |
| 3197 this._physicalCount + missingItems, | 1993 this._physicalCount + missingItems, |
| 3198 this._virtualCount - this._virtualStart, | 1994 this._virtualCount - this._virtualStart, |
| 3199 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) | 1995 Math.max(this.maxPhysicalCount, DEFAULT_PHYSICAL_COUNT) |
| 3200 ); | 1996 ); |
| 3201 var prevPhysicalCount = this._physicalCount; | 1997 var prevPhysicalCount = this._physicalCount; |
| 3202 var delta = nextPhysicalCount - prevPhysicalCount; | 1998 var delta = nextPhysicalCount - prevPhysicalCount; |
| 3203 | 1999 |
| 3204 if (delta <= 0) { | 2000 if (delta <= 0) { |
| 3205 return; | 2001 return; |
| 3206 } | 2002 } |
| 3207 | 2003 |
| 3208 [].push.apply(this._physicalItems, this._createPool(delta)); | 2004 [].push.apply(this._physicalItems, this._createPool(delta)); |
| 3209 [].push.apply(this._physicalSizes, new Array(delta)); | 2005 [].push.apply(this._physicalSizes, new Array(delta)); |
| 3210 | 2006 |
| 3211 this._physicalCount = prevPhysicalCount + delta; | 2007 this._physicalCount = prevPhysicalCount + delta; |
| 3212 | 2008 |
| 3213 // update the physical start if we need to preserve the model of the focus
ed item. | |
| 3214 // In this situation, the focused item is currently rendered and its model
would | |
| 3215 // have changed after increasing the pool if the physical start remained u
nchanged. | |
| 3216 if (this._physicalStart > this._physicalEnd && | 2009 if (this._physicalStart > this._physicalEnd && |
| 3217 this._isIndexRendered(this._focusedIndex) && | 2010 this._isIndexRendered(this._focusedIndex) && |
| 3218 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { | 2011 this._getPhysicalIndex(this._focusedIndex) < this._physicalEnd) { |
| 3219 this._physicalStart = this._physicalStart + delta; | 2012 this._physicalStart = this._physicalStart + delta; |
| 3220 } | 2013 } |
| 3221 this._update(); | 2014 this._update(); |
| 3222 }, | 2015 }, |
| 3223 | 2016 |
| 3224 /** | |
| 3225 * Render a new list of items. This method does exactly the same as `update`
, | |
| 3226 * but it also ensures that only one `update` cycle is created. | |
| 3227 */ | |
| 3228 _render: function() { | 2017 _render: function() { |
| 3229 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; | 2018 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0; |
| 3230 | 2019 |
| 3231 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { | 2020 if (this.isAttached && !this._itemsRendered && this._isVisible && requires
Update) { |
| 3232 this._lastPage = 0; | 2021 this._lastPage = 0; |
| 3233 this._update(); | 2022 this._update(); |
| 3234 this._itemsRendered = true; | 2023 this._itemsRendered = true; |
| 3235 } | 2024 } |
| 3236 }, | 2025 }, |
| 3237 | 2026 |
| 3238 /** | |
| 3239 * Templetizes the user template. | |
| 3240 */ | |
| 3241 _ensureTemplatized: function() { | 2027 _ensureTemplatized: function() { |
| 3242 if (!this.ctor) { | 2028 if (!this.ctor) { |
| 3243 // Template instance props that should be excluded from forwarding | |
| 3244 var props = {}; | 2029 var props = {}; |
| 3245 props.__key__ = true; | 2030 props.__key__ = true; |
| 3246 props[this.as] = true; | 2031 props[this.as] = true; |
| 3247 props[this.indexAs] = true; | 2032 props[this.indexAs] = true; |
| 3248 props[this.selectedAs] = true; | 2033 props[this.selectedAs] = true; |
| 3249 props.tabIndex = true; | 2034 props.tabIndex = true; |
| 3250 | 2035 |
| 3251 this._instanceProps = props; | 2036 this._instanceProps = props; |
| 3252 this._userTemplate = Polymer.dom(this).querySelector('template'); | 2037 this._userTemplate = Polymer.dom(this).querySelector('template'); |
| 3253 | 2038 |
| 3254 if (this._userTemplate) { | 2039 if (this._userTemplate) { |
| 3255 this.templatize(this._userTemplate); | 2040 this.templatize(this._userTemplate); |
| 3256 } else { | 2041 } else { |
| 3257 console.warn('iron-list requires a template to be provided in light-do
m'); | 2042 console.warn('iron-list requires a template to be provided in light-do
m'); |
| 3258 } | 2043 } |
| 3259 } | 2044 } |
| 3260 }, | 2045 }, |
| 3261 | 2046 |
| 3262 /** | |
| 3263 * Implements extension point from Templatizer mixin. | |
| 3264 */ | |
| 3265 _getStampedChildren: function() { | 2047 _getStampedChildren: function() { |
| 3266 return this._physicalItems; | 2048 return this._physicalItems; |
| 3267 }, | 2049 }, |
| 3268 | 2050 |
| 3269 /** | |
| 3270 * Implements extension point from Templatizer | |
| 3271 * Called as a side effect of a template instance path change, responsible | |
| 3272 * for notifying items.<key-for-instance>.<path> change up to host. | |
| 3273 */ | |
| 3274 _forwardInstancePath: function(inst, path, value) { | 2051 _forwardInstancePath: function(inst, path, value) { |
| 3275 if (path.indexOf(this.as + '.') === 0) { | 2052 if (path.indexOf(this.as + '.') === 0) { |
| 3276 this.notifyPath('items.' + inst.__key__ + '.' + | 2053 this.notifyPath('items.' + inst.__key__ + '.' + |
| 3277 path.slice(this.as.length + 1), value); | 2054 path.slice(this.as.length + 1), value); |
| 3278 } | 2055 } |
| 3279 }, | 2056 }, |
| 3280 | 2057 |
| 3281 /** | |
| 3282 * Implements extension point from Templatizer mixin | |
| 3283 * Called as side-effect of a host property change, responsible for | |
| 3284 * notifying parent path change on each row. | |
| 3285 */ | |
| 3286 _forwardParentProp: function(prop, value) { | 2058 _forwardParentProp: function(prop, value) { |
| 3287 if (this._physicalItems) { | 2059 if (this._physicalItems) { |
| 3288 this._physicalItems.forEach(function(item) { | 2060 this._physicalItems.forEach(function(item) { |
| 3289 item._templateInstance[prop] = value; | 2061 item._templateInstance[prop] = value; |
| 3290 }, this); | 2062 }, this); |
| 3291 } | 2063 } |
| 3292 }, | 2064 }, |
| 3293 | 2065 |
| 3294 /** | |
| 3295 * Implements extension point from Templatizer | |
| 3296 * Called as side-effect of a host path change, responsible for | |
| 3297 * notifying parent.<path> path change on each row. | |
| 3298 */ | |
| 3299 _forwardParentPath: function(path, value) { | 2066 _forwardParentPath: function(path, value) { |
| 3300 if (this._physicalItems) { | 2067 if (this._physicalItems) { |
| 3301 this._physicalItems.forEach(function(item) { | 2068 this._physicalItems.forEach(function(item) { |
| 3302 item._templateInstance.notifyPath(path, value, true); | 2069 item._templateInstance.notifyPath(path, value, true); |
| 3303 }, this); | 2070 }, this); |
| 3304 } | 2071 } |
| 3305 }, | 2072 }, |
| 3306 | 2073 |
| 3307 /** | |
| 3308 * Called as a side effect of a host items.<key>.<path> path change, | |
| 3309 * responsible for notifying item.<path> changes. | |
| 3310 */ | |
| 3311 _forwardItemPath: function(path, value) { | 2074 _forwardItemPath: function(path, value) { |
| 3312 if (!this._physicalIndexForKey) { | 2075 if (!this._physicalIndexForKey) { |
| 3313 return; | 2076 return; |
| 3314 } | 2077 } |
| 3315 var dot = path.indexOf('.'); | 2078 var dot = path.indexOf('.'); |
| 3316 var key = path.substring(0, dot < 0 ? path.length : dot); | 2079 var key = path.substring(0, dot < 0 ? path.length : dot); |
| 3317 var idx = this._physicalIndexForKey[key]; | 2080 var idx = this._physicalIndexForKey[key]; |
| 3318 var offscreenItem = this._offscreenFocusedItem; | 2081 var offscreenItem = this._offscreenFocusedItem; |
| 3319 var el = offscreenItem && offscreenItem._templateInstance.__key__ === key
? | 2082 var el = offscreenItem && offscreenItem._templateInstance.__key__ === key
? |
| 3320 offscreenItem : this._physicalItems[idx]; | 2083 offscreenItem : this._physicalItems[idx]; |
| 3321 | 2084 |
| 3322 if (!el || el._templateInstance.__key__ !== key) { | 2085 if (!el || el._templateInstance.__key__ !== key) { |
| 3323 return; | 2086 return; |
| 3324 } | 2087 } |
| 3325 if (dot >= 0) { | 2088 if (dot >= 0) { |
| 3326 path = this.as + '.' + path.substring(dot+1); | 2089 path = this.as + '.' + path.substring(dot+1); |
| 3327 el._templateInstance.notifyPath(path, value, true); | 2090 el._templateInstance.notifyPath(path, value, true); |
| 3328 } else { | 2091 } else { |
| 3329 // Update selection if needed | |
| 3330 var currentItem = el._templateInstance[this.as]; | 2092 var currentItem = el._templateInstance[this.as]; |
| 3331 if (Array.isArray(this.selectedItems)) { | 2093 if (Array.isArray(this.selectedItems)) { |
| 3332 for (var i = 0; i < this.selectedItems.length; i++) { | 2094 for (var i = 0; i < this.selectedItems.length; i++) { |
| 3333 if (this.selectedItems[i] === currentItem) { | 2095 if (this.selectedItems[i] === currentItem) { |
| 3334 this.set('selectedItems.' + i, value); | 2096 this.set('selectedItems.' + i, value); |
| 3335 break; | 2097 break; |
| 3336 } | 2098 } |
| 3337 } | 2099 } |
| 3338 } else if (this.selectedItem === currentItem) { | 2100 } else if (this.selectedItem === currentItem) { |
| 3339 this.set('selectedItem', value); | 2101 this.set('selectedItem', value); |
| 3340 } | 2102 } |
| 3341 el._templateInstance[this.as] = value; | 2103 el._templateInstance[this.as] = value; |
| 3342 } | 2104 } |
| 3343 }, | 2105 }, |
| 3344 | 2106 |
| 3345 /** | |
| 3346 * Called when the items have changed. That is, ressignments | |
| 3347 * to `items`, splices or updates to a single item. | |
| 3348 */ | |
| 3349 _itemsChanged: function(change) { | 2107 _itemsChanged: function(change) { |
| 3350 if (change.path === 'items') { | 2108 if (change.path === 'items') { |
| 3351 // reset items | |
| 3352 this._virtualStart = 0; | 2109 this._virtualStart = 0; |
| 3353 this._physicalTop = 0; | 2110 this._physicalTop = 0; |
| 3354 this._virtualCount = this.items ? this.items.length : 0; | 2111 this._virtualCount = this.items ? this.items.length : 0; |
| 3355 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; | 2112 this._collection = this.items ? Polymer.Collection.get(this.items) : nul
l; |
| 3356 this._physicalIndexForKey = {}; | 2113 this._physicalIndexForKey = {}; |
| 3357 this._firstVisibleIndexVal = null; | 2114 this._firstVisibleIndexVal = null; |
| 3358 this._lastVisibleIndexVal = null; | 2115 this._lastVisibleIndexVal = null; |
| 3359 | 2116 |
| 3360 this._resetScrollPosition(0); | 2117 this._resetScrollPosition(0); |
| 3361 this._removeFocusedItem(); | 2118 this._removeFocusedItem(); |
| 3362 // create the initial physical items | |
| 3363 if (!this._physicalItems) { | 2119 if (!this._physicalItems) { |
| 3364 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi
s._virtualCount)); | 2120 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi
s._virtualCount)); |
| 3365 this._physicalItems = this._createPool(this._physicalCount); | 2121 this._physicalItems = this._createPool(this._physicalCount); |
| 3366 this._physicalSizes = new Array(this._physicalCount); | 2122 this._physicalSizes = new Array(this._physicalCount); |
| 3367 } | 2123 } |
| 3368 | 2124 |
| 3369 this._physicalStart = 0; | 2125 this._physicalStart = 0; |
| 3370 | 2126 |
| 3371 } else if (change.path === 'items.splices') { | 2127 } else if (change.path === 'items.splices') { |
| 3372 | 2128 |
| 3373 this._adjustVirtualIndex(change.value.indexSplices); | 2129 this._adjustVirtualIndex(change.value.indexSplices); |
| 3374 this._virtualCount = this.items ? this.items.length : 0; | 2130 this._virtualCount = this.items ? this.items.length : 0; |
| 3375 | 2131 |
| 3376 } else { | 2132 } else { |
| 3377 // update a single item | |
| 3378 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); | 2133 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.
value); |
| 3379 return; | 2134 return; |
| 3380 } | 2135 } |
| 3381 | 2136 |
| 3382 this._itemsRendered = false; | 2137 this._itemsRendered = false; |
| 3383 this._debounceTemplate(this._render); | 2138 this._debounceTemplate(this._render); |
| 3384 }, | 2139 }, |
| 3385 | 2140 |
| 3386 /** | |
| 3387 * @param {!Array<!PolymerSplice>} splices | |
| 3388 */ | |
| 3389 _adjustVirtualIndex: function(splices) { | 2141 _adjustVirtualIndex: function(splices) { |
| 3390 splices.forEach(function(splice) { | 2142 splices.forEach(function(splice) { |
| 3391 // deselect removed items | |
| 3392 splice.removed.forEach(this._removeItem, this); | 2143 splice.removed.forEach(this._removeItem, this); |
| 3393 // We only need to care about changes happening above the current positi
on | |
| 3394 if (splice.index < this._virtualStart) { | 2144 if (splice.index < this._virtualStart) { |
| 3395 var delta = Math.max( | 2145 var delta = Math.max( |
| 3396 splice.addedCount - splice.removed.length, | 2146 splice.addedCount - splice.removed.length, |
| 3397 splice.index - this._virtualStart); | 2147 splice.index - this._virtualStart); |
| 3398 | 2148 |
| 3399 this._virtualStart = this._virtualStart + delta; | 2149 this._virtualStart = this._virtualStart + delta; |
| 3400 | 2150 |
| 3401 if (this._focusedIndex >= 0) { | 2151 if (this._focusedIndex >= 0) { |
| 3402 this._focusedIndex = this._focusedIndex + delta; | 2152 this._focusedIndex = this._focusedIndex + delta; |
| 3403 } | 2153 } |
| 3404 } | 2154 } |
| 3405 }, this); | 2155 }, this); |
| 3406 }, | 2156 }, |
| 3407 | 2157 |
| 3408 _removeItem: function(item) { | 2158 _removeItem: function(item) { |
| 3409 this.$.selector.deselect(item); | 2159 this.$.selector.deselect(item); |
| 3410 // remove the current focused item | |
| 3411 if (this._focusedItem && this._focusedItem._templateInstance[this.as] ===
item) { | 2160 if (this._focusedItem && this._focusedItem._templateInstance[this.as] ===
item) { |
| 3412 this._removeFocusedItem(); | 2161 this._removeFocusedItem(); |
| 3413 } | 2162 } |
| 3414 }, | 2163 }, |
| 3415 | 2164 |
| 3416 /** | |
| 3417 * Executes a provided function per every physical index in `itemSet` | |
| 3418 * `itemSet` default value is equivalent to the entire set of physical index
es. | |
| 3419 * | |
| 3420 * @param {!function(number, number)} fn | |
| 3421 * @param {!Array<number>=} itemSet | |
| 3422 */ | |
| 3423 _iterateItems: function(fn, itemSet) { | 2165 _iterateItems: function(fn, itemSet) { |
| 3424 var pidx, vidx, rtn, i; | 2166 var pidx, vidx, rtn, i; |
| 3425 | 2167 |
| 3426 if (arguments.length === 2 && itemSet) { | 2168 if (arguments.length === 2 && itemSet) { |
| 3427 for (i = 0; i < itemSet.length; i++) { | 2169 for (i = 0; i < itemSet.length; i++) { |
| 3428 pidx = itemSet[i]; | 2170 pidx = itemSet[i]; |
| 3429 vidx = this._computeVidx(pidx); | 2171 vidx = this._computeVidx(pidx); |
| 3430 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 2172 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
| 3431 return rtn; | 2173 return rtn; |
| 3432 } | 2174 } |
| 3433 } | 2175 } |
| 3434 } else { | 2176 } else { |
| 3435 pidx = this._physicalStart; | 2177 pidx = this._physicalStart; |
| 3436 vidx = this._virtualStart; | 2178 vidx = this._virtualStart; |
| 3437 | 2179 |
| 3438 for (; pidx < this._physicalCount; pidx++, vidx++) { | 2180 for (; pidx < this._physicalCount; pidx++, vidx++) { |
| 3439 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 2181 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
| 3440 return rtn; | 2182 return rtn; |
| 3441 } | 2183 } |
| 3442 } | 2184 } |
| 3443 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) { | 2185 for (pidx = 0; pidx < this._physicalStart; pidx++, vidx++) { |
| 3444 if ((rtn = fn.call(this, pidx, vidx)) != null) { | 2186 if ((rtn = fn.call(this, pidx, vidx)) != null) { |
| 3445 return rtn; | 2187 return rtn; |
| 3446 } | 2188 } |
| 3447 } | 2189 } |
| 3448 } | 2190 } |
| 3449 }, | 2191 }, |
| 3450 | 2192 |
| 3451 /** | |
| 3452 * Returns the virtual index for a given physical index | |
| 3453 * | |
| 3454 * @param {number} pidx Physical index | |
| 3455 * @return {number} | |
| 3456 */ | |
| 3457 _computeVidx: function(pidx) { | 2193 _computeVidx: function(pidx) { |
| 3458 if (pidx >= this._physicalStart) { | 2194 if (pidx >= this._physicalStart) { |
| 3459 return this._virtualStart + (pidx - this._physicalStart); | 2195 return this._virtualStart + (pidx - this._physicalStart); |
| 3460 } | 2196 } |
| 3461 return this._virtualStart + (this._physicalCount - this._physicalStart) +
pidx; | 2197 return this._virtualStart + (this._physicalCount - this._physicalStart) +
pidx; |
| 3462 }, | 2198 }, |
| 3463 | 2199 |
| 3464 /** | |
| 3465 * Assigns the data models to a given set of items. | |
| 3466 * @param {!Array<number>=} itemSet | |
| 3467 */ | |
| 3468 _assignModels: function(itemSet) { | 2200 _assignModels: function(itemSet) { |
| 3469 this._iterateItems(function(pidx, vidx) { | 2201 this._iterateItems(function(pidx, vidx) { |
| 3470 var el = this._physicalItems[pidx]; | 2202 var el = this._physicalItems[pidx]; |
| 3471 var inst = el._templateInstance; | 2203 var inst = el._templateInstance; |
| 3472 var item = this.items && this.items[vidx]; | 2204 var item = this.items && this.items[vidx]; |
| 3473 | 2205 |
| 3474 if (item != null) { | 2206 if (item != null) { |
| 3475 inst[this.as] = item; | 2207 inst[this.as] = item; |
| 3476 inst.__key__ = this._collection.getKey(item); | 2208 inst.__key__ = this._collection.getKey(item); |
| 3477 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s
elector).isSelected(item); | 2209 inst[this.selectedAs] = /** @type {!ArraySelectorElement} */ (this.$.s
elector).isSelected(item); |
| 3478 inst[this.indexAs] = vidx; | 2210 inst[this.indexAs] = vidx; |
| 3479 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1; | 2211 inst.tabIndex = this._focusedIndex === vidx ? 0 : -1; |
| 3480 this._physicalIndexForKey[inst.__key__] = pidx; | 2212 this._physicalIndexForKey[inst.__key__] = pidx; |
| 3481 el.removeAttribute('hidden'); | 2213 el.removeAttribute('hidden'); |
| 3482 } else { | 2214 } else { |
| 3483 inst.__key__ = null; | 2215 inst.__key__ = null; |
| 3484 el.setAttribute('hidden', ''); | 2216 el.setAttribute('hidden', ''); |
| 3485 } | 2217 } |
| 3486 }, itemSet); | 2218 }, itemSet); |
| 3487 }, | 2219 }, |
| 3488 | 2220 |
| 3489 /** | |
| 3490 * Updates the height for a given set of items. | |
| 3491 * | |
| 3492 * @param {!Array<number>=} itemSet | |
| 3493 */ | |
| 3494 _updateMetrics: function(itemSet) { | 2221 _updateMetrics: function(itemSet) { |
| 3495 // Make sure we distributed all the physical items | |
| 3496 // so we can measure them | |
| 3497 Polymer.dom.flush(); | 2222 Polymer.dom.flush(); |
| 3498 | 2223 |
| 3499 var newPhysicalSize = 0; | 2224 var newPhysicalSize = 0; |
| 3500 var oldPhysicalSize = 0; | 2225 var oldPhysicalSize = 0; |
| 3501 var prevAvgCount = this._physicalAverageCount; | 2226 var prevAvgCount = this._physicalAverageCount; |
| 3502 var prevPhysicalAvg = this._physicalAverage; | 2227 var prevPhysicalAvg = this._physicalAverage; |
| 3503 | 2228 |
| 3504 this._iterateItems(function(pidx, vidx) { | 2229 this._iterateItems(function(pidx, vidx) { |
| 3505 | 2230 |
| 3506 oldPhysicalSize += this._physicalSizes[pidx] || 0; | 2231 oldPhysicalSize += this._physicalSizes[pidx] || 0; |
| 3507 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; | 2232 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight; |
| 3508 newPhysicalSize += this._physicalSizes[pidx]; | 2233 newPhysicalSize += this._physicalSizes[pidx]; |
| 3509 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; | 2234 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0; |
| 3510 | 2235 |
| 3511 }, itemSet); | 2236 }, itemSet); |
| 3512 | 2237 |
| 3513 this._viewportHeight = this._scrollTargetHeight; | 2238 this._viewportHeight = this._scrollTargetHeight; |
| 3514 if (this.grid) { | 2239 if (this.grid) { |
| 3515 this._updateGridMetrics(); | 2240 this._updateGridMetrics(); |
| 3516 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow)
* this._rowHeight; | 2241 this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow)
* this._rowHeight; |
| 3517 } else { | 2242 } else { |
| 3518 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS
ize; | 2243 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalS
ize; |
| 3519 } | 2244 } |
| 3520 | 2245 |
| 3521 // update the average if we measured something | |
| 3522 if (this._physicalAverageCount !== prevAvgCount) { | 2246 if (this._physicalAverageCount !== prevAvgCount) { |
| 3523 this._physicalAverage = Math.round( | 2247 this._physicalAverage = Math.round( |
| 3524 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / | 2248 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) / |
| 3525 this._physicalAverageCount); | 2249 this._physicalAverageCount); |
| 3526 } | 2250 } |
| 3527 }, | 2251 }, |
| 3528 | 2252 |
| 3529 _updateGridMetrics: function() { | 2253 _updateGridMetrics: function() { |
| 3530 this._viewportWidth = this.$.items.offsetWidth; | 2254 this._viewportWidth = this.$.items.offsetWidth; |
| 3531 // Set item width to the value of the _physicalItems offsetWidth | |
| 3532 this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoun
dingClientRect().width : DEFAULT_GRID_SIZE; | 2255 this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoun
dingClientRect().width : DEFAULT_GRID_SIZE; |
| 3533 // Set row height to the value of the _physicalItems offsetHeight | |
| 3534 this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetH
eight : DEFAULT_GRID_SIZE; | 2256 this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetH
eight : DEFAULT_GRID_SIZE; |
| 3535 // If in grid mode compute how many items with exist in each row | |
| 3536 this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / thi
s._itemWidth) : this._itemsPerRow; | 2257 this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / thi
s._itemWidth) : this._itemsPerRow; |
| 3537 }, | 2258 }, |
| 3538 | 2259 |
| 3539 /** | |
| 3540 * Updates the position of the physical items. | |
| 3541 */ | |
| 3542 _positionItems: function() { | 2260 _positionItems: function() { |
| 3543 this._adjustScrollPosition(); | 2261 this._adjustScrollPosition(); |
| 3544 | 2262 |
| 3545 var y = this._physicalTop; | 2263 var y = this._physicalTop; |
| 3546 | 2264 |
| 3547 if (this.grid) { | 2265 if (this.grid) { |
| 3548 var totalItemWidth = this._itemsPerRow * this._itemWidth; | 2266 var totalItemWidth = this._itemsPerRow * this._itemWidth; |
| 3549 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; | 2267 var rowOffset = (this._viewportWidth - totalItemWidth) / 2; |
| 3550 | 2268 |
| 3551 this._iterateItems(function(pidx, vidx) { | 2269 this._iterateItems(function(pidx, vidx) { |
| (...skipping 21 matching lines...) Expand all Loading... |
| 3573 _getPhysicalSizeIncrement: function(pidx) { | 2291 _getPhysicalSizeIncrement: function(pidx) { |
| 3574 if (!this.grid) { | 2292 if (!this.grid) { |
| 3575 return this._physicalSizes[pidx]; | 2293 return this._physicalSizes[pidx]; |
| 3576 } | 2294 } |
| 3577 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1)
{ | 2295 if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1)
{ |
| 3578 return 0; | 2296 return 0; |
| 3579 } | 2297 } |
| 3580 return this._rowHeight; | 2298 return this._rowHeight; |
| 3581 }, | 2299 }, |
| 3582 | 2300 |
| 3583 /** | |
| 3584 * Returns, based on the current index, | |
| 3585 * whether or not the next index will need | |
| 3586 * to be rendered on a new row. | |
| 3587 * | |
| 3588 * @param {number} vidx Virtual index | |
| 3589 * @return {boolean} | |
| 3590 */ | |
| 3591 _shouldRenderNextRow: function(vidx) { | 2301 _shouldRenderNextRow: function(vidx) { |
| 3592 return vidx % this._itemsPerRow === this._itemsPerRow - 1; | 2302 return vidx % this._itemsPerRow === this._itemsPerRow - 1; |
| 3593 }, | 2303 }, |
| 3594 | 2304 |
| 3595 /** | |
| 3596 * Adjusts the scroll position when it was overestimated. | |
| 3597 */ | |
| 3598 _adjustScrollPosition: function() { | 2305 _adjustScrollPosition: function() { |
| 3599 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : | 2306 var deltaHeight = this._virtualStart === 0 ? this._physicalTop : |
| 3600 Math.min(this._scrollPosition + this._physicalTop, 0); | 2307 Math.min(this._scrollPosition + this._physicalTop, 0); |
| 3601 | 2308 |
| 3602 if (deltaHeight) { | 2309 if (deltaHeight) { |
| 3603 this._physicalTop = this._physicalTop - deltaHeight; | 2310 this._physicalTop = this._physicalTop - deltaHeight; |
| 3604 // juking scroll position during interial scrolling on iOS is no bueno | |
| 3605 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) { | 2311 if (!IOS_TOUCH_SCROLLING && this._physicalTop !== 0) { |
| 3606 this._resetScrollPosition(this._scrollTop - deltaHeight); | 2312 this._resetScrollPosition(this._scrollTop - deltaHeight); |
| 3607 } | 2313 } |
| 3608 } | 2314 } |
| 3609 }, | 2315 }, |
| 3610 | 2316 |
| 3611 /** | |
| 3612 * Sets the position of the scroll. | |
| 3613 */ | |
| 3614 _resetScrollPosition: function(pos) { | 2317 _resetScrollPosition: function(pos) { |
| 3615 if (this.scrollTarget) { | 2318 if (this.scrollTarget) { |
| 3616 this._scrollTop = pos; | 2319 this._scrollTop = pos; |
| 3617 this._scrollPosition = this._scrollTop; | 2320 this._scrollPosition = this._scrollTop; |
| 3618 } | 2321 } |
| 3619 }, | 2322 }, |
| 3620 | 2323 |
| 3621 /** | |
| 3622 * Sets the scroll height, that's the height of the content, | |
| 3623 * | |
| 3624 * @param {boolean=} forceUpdate If true, updates the height no matter what. | |
| 3625 */ | |
| 3626 _updateScrollerSize: function(forceUpdate) { | 2324 _updateScrollerSize: function(forceUpdate) { |
| 3627 if (this.grid) { | 2325 if (this.grid) { |
| 3628 this._estScrollHeight = this._virtualRowCount * this._rowHeight; | 2326 this._estScrollHeight = this._virtualRowCount * this._rowHeight; |
| 3629 } else { | 2327 } else { |
| 3630 this._estScrollHeight = (this._physicalBottom + | 2328 this._estScrollHeight = (this._physicalBottom + |
| 3631 Math.max(this._virtualCount - this._physicalCount - this._virtualSta
rt, 0) * this._physicalAverage); | 2329 Math.max(this._virtualCount - this._physicalCount - this._virtualSta
rt, 0) * this._physicalAverage); |
| 3632 } | 2330 } |
| 3633 | 2331 |
| 3634 forceUpdate = forceUpdate || this._scrollHeight === 0; | 2332 forceUpdate = forceUpdate || this._scrollHeight === 0; |
| 3635 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; | 2333 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight
- this._physicalSize; |
| 3636 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this
._estScrollHeight; | 2334 forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this
._estScrollHeight; |
| 3637 | 2335 |
| 3638 // amortize height adjustment, so it won't trigger repaints very often | |
| 3639 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { | 2336 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >=
this._optPhysicalSize) { |
| 3640 this.$.items.style.height = this._estScrollHeight + 'px'; | 2337 this.$.items.style.height = this._estScrollHeight + 'px'; |
| 3641 this._scrollHeight = this._estScrollHeight; | 2338 this._scrollHeight = this._estScrollHeight; |
| 3642 } | 2339 } |
| 3643 }, | 2340 }, |
| 3644 | 2341 |
| 3645 /** | |
| 3646 * Scroll to a specific item in the virtual list regardless | |
| 3647 * of the physical items in the DOM tree. | |
| 3648 * | |
| 3649 * @method scrollToItem | |
| 3650 * @param {(Object)} item The item to be scrolled to | |
| 3651 */ | |
| 3652 scrollToItem: function(item){ | 2342 scrollToItem: function(item){ |
| 3653 return this.scrollToIndex(this.items.indexOf(item)); | 2343 return this.scrollToIndex(this.items.indexOf(item)); |
| 3654 }, | 2344 }, |
| 3655 | 2345 |
| 3656 /** | |
| 3657 * Scroll to a specific index in the virtual list regardless | |
| 3658 * of the physical items in the DOM tree. | |
| 3659 * | |
| 3660 * @method scrollToIndex | |
| 3661 * @param {number} idx The index of the item | |
| 3662 */ | |
| 3663 scrollToIndex: function(idx) { | 2346 scrollToIndex: function(idx) { |
| 3664 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { | 2347 if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) { |
| 3665 return; | 2348 return; |
| 3666 } | 2349 } |
| 3667 | 2350 |
| 3668 Polymer.dom.flush(); | 2351 Polymer.dom.flush(); |
| 3669 | 2352 |
| 3670 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); | 2353 idx = Math.min(Math.max(idx, 0), this._virtualCount-1); |
| 3671 // update the virtual start only when needed | |
| 3672 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { | 2354 if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) { |
| 3673 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); | 2355 this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx -
1); |
| 3674 } | 2356 } |
| 3675 // manage focus | |
| 3676 this._manageFocus(); | 2357 this._manageFocus(); |
| 3677 // assign new models | |
| 3678 this._assignModels(); | 2358 this._assignModels(); |
| 3679 // measure the new sizes | |
| 3680 this._updateMetrics(); | 2359 this._updateMetrics(); |
| 3681 | 2360 |
| 3682 // estimate new physical offset | |
| 3683 var estPhysicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; | 2361 var estPhysicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage; |
| 3684 this._physicalTop = estPhysicalTop; | 2362 this._physicalTop = estPhysicalTop; |
| 3685 | 2363 |
| 3686 var currentTopItem = this._physicalStart; | 2364 var currentTopItem = this._physicalStart; |
| 3687 var currentVirtualItem = this._virtualStart; | 2365 var currentVirtualItem = this._virtualStart; |
| 3688 var targetOffsetTop = 0; | 2366 var targetOffsetTop = 0; |
| 3689 var hiddenContentSize = this._hiddenContentSize; | 2367 var hiddenContentSize = this._hiddenContentSize; |
| 3690 | 2368 |
| 3691 // scroll to the item as much as we can | |
| 3692 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { | 2369 while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) { |
| 3693 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); | 2370 targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(curre
ntTopItem); |
| 3694 currentTopItem = (currentTopItem + 1) % this._physicalCount; | 2371 currentTopItem = (currentTopItem + 1) % this._physicalCount; |
| 3695 currentVirtualItem++; | 2372 currentVirtualItem++; |
| 3696 } | 2373 } |
| 3697 // update the scroller size | |
| 3698 this._updateScrollerSize(true); | 2374 this._updateScrollerSize(true); |
| 3699 // update the position of the items | |
| 3700 this._positionItems(); | 2375 this._positionItems(); |
| 3701 // set the new scroll position | |
| 3702 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); | 2376 this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + t
argetOffsetTop); |
| 3703 // increase the pool of physical items if needed | |
| 3704 this._increasePoolIfNeeded(); | 2377 this._increasePoolIfNeeded(); |
| 3705 // clear cached visible index | |
| 3706 this._firstVisibleIndexVal = null; | 2378 this._firstVisibleIndexVal = null; |
| 3707 this._lastVisibleIndexVal = null; | 2379 this._lastVisibleIndexVal = null; |
| 3708 }, | 2380 }, |
| 3709 | 2381 |
| 3710 /** | |
| 3711 * Reset the physical average and the average count. | |
| 3712 */ | |
| 3713 _resetAverage: function() { | 2382 _resetAverage: function() { |
| 3714 this._physicalAverage = 0; | 2383 this._physicalAverage = 0; |
| 3715 this._physicalAverageCount = 0; | 2384 this._physicalAverageCount = 0; |
| 3716 }, | 2385 }, |
| 3717 | 2386 |
| 3718 /** | |
| 3719 * A handler for the `iron-resize` event triggered by `IronResizableBehavior
` | |
| 3720 * when the element is resized. | |
| 3721 */ | |
| 3722 _resizeHandler: function() { | 2387 _resizeHandler: function() { |
| 3723 // iOS fires the resize event when the address bar slides up | |
| 3724 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100
) { | 2388 if (IOS && Math.abs(this._viewportHeight - this._scrollTargetHeight) < 100
) { |
| 3725 return; | 2389 return; |
| 3726 } | 2390 } |
| 3727 // In Desktop Safari 9.0.3, if the scroll bars are always shown, | |
| 3728 // changing the scroll position from a resize handler would result in | |
| 3729 // the scroll position being reset. Waiting 1ms fixes the issue. | |
| 3730 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { | 2391 Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() { |
| 3731 this.updateViewportBoundaries(); | 2392 this.updateViewportBoundaries(); |
| 3732 this._render(); | 2393 this._render(); |
| 3733 | 2394 |
| 3734 if (this._itemsRendered && this._physicalItems && this._isVisible) { | 2395 if (this._itemsRendered && this._physicalItems && this._isVisible) { |
| 3735 this._resetAverage(); | 2396 this._resetAverage(); |
| 3736 this.scrollToIndex(this.firstVisibleIndex); | 2397 this.scrollToIndex(this.firstVisibleIndex); |
| 3737 } | 2398 } |
| 3738 }.bind(this), 1)); | 2399 }.bind(this), 1)); |
| 3739 }, | 2400 }, |
| 3740 | 2401 |
| 3741 _getModelFromItem: function(item) { | 2402 _getModelFromItem: function(item) { |
| 3742 var key = this._collection.getKey(item); | 2403 var key = this._collection.getKey(item); |
| 3743 var pidx = this._physicalIndexForKey[key]; | 2404 var pidx = this._physicalIndexForKey[key]; |
| 3744 | 2405 |
| 3745 if (pidx != null) { | 2406 if (pidx != null) { |
| 3746 return this._physicalItems[pidx]._templateInstance; | 2407 return this._physicalItems[pidx]._templateInstance; |
| 3747 } | 2408 } |
| 3748 return null; | 2409 return null; |
| 3749 }, | 2410 }, |
| 3750 | 2411 |
| 3751 /** | |
| 3752 * Gets a valid item instance from its index or the object value. | |
| 3753 * | |
| 3754 * @param {(Object|number)} item The item object or its index | |
| 3755 */ | |
| 3756 _getNormalizedItem: function(item) { | 2412 _getNormalizedItem: function(item) { |
| 3757 if (this._collection.getKey(item) === undefined) { | 2413 if (this._collection.getKey(item) === undefined) { |
| 3758 if (typeof item === 'number') { | 2414 if (typeof item === 'number') { |
| 3759 item = this.items[item]; | 2415 item = this.items[item]; |
| 3760 if (!item) { | 2416 if (!item) { |
| 3761 throw new RangeError('<item> not found'); | 2417 throw new RangeError('<item> not found'); |
| 3762 } | 2418 } |
| 3763 return item; | 2419 return item; |
| 3764 } | 2420 } |
| 3765 throw new TypeError('<item> should be a valid item'); | 2421 throw new TypeError('<item> should be a valid item'); |
| 3766 } | 2422 } |
| 3767 return item; | 2423 return item; |
| 3768 }, | 2424 }, |
| 3769 | 2425 |
| 3770 /** | |
| 3771 * Select the list item at the given index. | |
| 3772 * | |
| 3773 * @method selectItem | |
| 3774 * @param {(Object|number)} item The item object or its index | |
| 3775 */ | |
| 3776 selectItem: function(item) { | 2426 selectItem: function(item) { |
| 3777 item = this._getNormalizedItem(item); | 2427 item = this._getNormalizedItem(item); |
| 3778 var model = this._getModelFromItem(item); | 2428 var model = this._getModelFromItem(item); |
| 3779 | 2429 |
| 3780 if (!this.multiSelection && this.selectedItem) { | 2430 if (!this.multiSelection && this.selectedItem) { |
| 3781 this.deselectItem(this.selectedItem); | 2431 this.deselectItem(this.selectedItem); |
| 3782 } | 2432 } |
| 3783 if (model) { | 2433 if (model) { |
| 3784 model[this.selectedAs] = true; | 2434 model[this.selectedAs] = true; |
| 3785 } | 2435 } |
| 3786 this.$.selector.select(item); | 2436 this.$.selector.select(item); |
| 3787 this.updateSizeForItem(item); | 2437 this.updateSizeForItem(item); |
| 3788 }, | 2438 }, |
| 3789 | 2439 |
| 3790 /** | |
| 3791 * Deselects the given item list if it is already selected. | |
| 3792 * | |
| 3793 | |
| 3794 * @method deselect | |
| 3795 * @param {(Object|number)} item The item object or its index | |
| 3796 */ | |
| 3797 deselectItem: function(item) { | 2440 deselectItem: function(item) { |
| 3798 item = this._getNormalizedItem(item); | 2441 item = this._getNormalizedItem(item); |
| 3799 var model = this._getModelFromItem(item); | 2442 var model = this._getModelFromItem(item); |
| 3800 | 2443 |
| 3801 if (model) { | 2444 if (model) { |
| 3802 model[this.selectedAs] = false; | 2445 model[this.selectedAs] = false; |
| 3803 } | 2446 } |
| 3804 this.$.selector.deselect(item); | 2447 this.$.selector.deselect(item); |
| 3805 this.updateSizeForItem(item); | 2448 this.updateSizeForItem(item); |
| 3806 }, | 2449 }, |
| 3807 | 2450 |
| 3808 /** | |
| 3809 * Select or deselect a given item depending on whether the item | |
| 3810 * has already been selected. | |
| 3811 * | |
| 3812 * @method toggleSelectionForItem | |
| 3813 * @param {(Object|number)} item The item object or its index | |
| 3814 */ | |
| 3815 toggleSelectionForItem: function(item) { | 2451 toggleSelectionForItem: function(item) { |
| 3816 item = this._getNormalizedItem(item); | 2452 item = this._getNormalizedItem(item); |
| 3817 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item
)) { | 2453 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item
)) { |
| 3818 this.deselectItem(item); | 2454 this.deselectItem(item); |
| 3819 } else { | 2455 } else { |
| 3820 this.selectItem(item); | 2456 this.selectItem(item); |
| 3821 } | 2457 } |
| 3822 }, | 2458 }, |
| 3823 | 2459 |
| 3824 /** | |
| 3825 * Clears the current selection state of the list. | |
| 3826 * | |
| 3827 * @method clearSelection | |
| 3828 */ | |
| 3829 clearSelection: function() { | 2460 clearSelection: function() { |
| 3830 function unselect(item) { | 2461 function unselect(item) { |
| 3831 var model = this._getModelFromItem(item); | 2462 var model = this._getModelFromItem(item); |
| 3832 if (model) { | 2463 if (model) { |
| 3833 model[this.selectedAs] = false; | 2464 model[this.selectedAs] = false; |
| 3834 } | 2465 } |
| 3835 } | 2466 } |
| 3836 | 2467 |
| 3837 if (Array.isArray(this.selectedItems)) { | 2468 if (Array.isArray(this.selectedItems)) { |
| 3838 this.selectedItems.forEach(unselect, this); | 2469 this.selectedItems.forEach(unselect, this); |
| 3839 } else if (this.selectedItem) { | 2470 } else if (this.selectedItem) { |
| 3840 unselect.call(this, this.selectedItem); | 2471 unselect.call(this, this.selectedItem); |
| 3841 } | 2472 } |
| 3842 | 2473 |
| 3843 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); | 2474 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection(); |
| 3844 }, | 2475 }, |
| 3845 | 2476 |
| 3846 /** | |
| 3847 * Add an event listener to `tap` if `selectionEnabled` is true, | |
| 3848 * it will remove the listener otherwise. | |
| 3849 */ | |
| 3850 _selectionEnabledChanged: function(selectionEnabled) { | 2477 _selectionEnabledChanged: function(selectionEnabled) { |
| 3851 var handler = selectionEnabled ? this.listen : this.unlisten; | 2478 var handler = selectionEnabled ? this.listen : this.unlisten; |
| 3852 handler.call(this, this, 'tap', '_selectionHandler'); | 2479 handler.call(this, this, 'tap', '_selectionHandler'); |
| 3853 }, | 2480 }, |
| 3854 | 2481 |
| 3855 /** | |
| 3856 * Select an item from an event object. | |
| 3857 */ | |
| 3858 _selectionHandler: function(e) { | 2482 _selectionHandler: function(e) { |
| 3859 var model = this.modelForElement(e.target); | 2483 var model = this.modelForElement(e.target); |
| 3860 if (!model) { | 2484 if (!model) { |
| 3861 return; | 2485 return; |
| 3862 } | 2486 } |
| 3863 var modelTabIndex, activeElTabIndex; | 2487 var modelTabIndex, activeElTabIndex; |
| 3864 var target = Polymer.dom(e).path[0]; | 2488 var target = Polymer.dom(e).path[0]; |
| 3865 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac
tiveElement; | 2489 var activeEl = Polymer.dom(this.domHost ? this.domHost.root : document).ac
tiveElement; |
| 3866 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i
ndexAs])]; | 2490 var physicalItem = this._physicalItems[this._getPhysicalIndex(model[this.i
ndexAs])]; |
| 3867 // Safari does not focus certain form controls via mouse | |
| 3868 // https://bugs.webkit.org/show_bug.cgi?id=118043 | |
| 3869 if (target.localName === 'input' || | 2491 if (target.localName === 'input' || |
| 3870 target.localName === 'button' || | 2492 target.localName === 'button' || |
| 3871 target.localName === 'select') { | 2493 target.localName === 'select') { |
| 3872 return; | 2494 return; |
| 3873 } | 2495 } |
| 3874 // Set a temporary tabindex | |
| 3875 modelTabIndex = model.tabIndex; | 2496 modelTabIndex = model.tabIndex; |
| 3876 model.tabIndex = SECRET_TABINDEX; | 2497 model.tabIndex = SECRET_TABINDEX; |
| 3877 activeElTabIndex = activeEl ? activeEl.tabIndex : -1; | 2498 activeElTabIndex = activeEl ? activeEl.tabIndex : -1; |
| 3878 model.tabIndex = modelTabIndex; | 2499 model.tabIndex = modelTabIndex; |
| 3879 // Only select the item if the tap wasn't on a focusable child | |
| 3880 // or the element bound to `tabIndex` | |
| 3881 if (activeEl && physicalItem.contains(activeEl) && activeElTabIndex !== SE
CRET_TABINDEX) { | 2500 if (activeEl && physicalItem.contains(activeEl) && activeElTabIndex !== SE
CRET_TABINDEX) { |
| 3882 return; | 2501 return; |
| 3883 } | 2502 } |
| 3884 this.toggleSelectionForItem(model[this.as]); | 2503 this.toggleSelectionForItem(model[this.as]); |
| 3885 }, | 2504 }, |
| 3886 | 2505 |
| 3887 _multiSelectionChanged: function(multiSelection) { | 2506 _multiSelectionChanged: function(multiSelection) { |
| 3888 this.clearSelection(); | 2507 this.clearSelection(); |
| 3889 this.$.selector.multi = multiSelection; | 2508 this.$.selector.multi = multiSelection; |
| 3890 }, | 2509 }, |
| 3891 | 2510 |
| 3892 /** | |
| 3893 * Updates the size of an item. | |
| 3894 * | |
| 3895 * @method updateSizeForItem | |
| 3896 * @param {(Object|number)} item The item object or its index | |
| 3897 */ | |
| 3898 updateSizeForItem: function(item) { | 2511 updateSizeForItem: function(item) { |
| 3899 item = this._getNormalizedItem(item); | 2512 item = this._getNormalizedItem(item); |
| 3900 var key = this._collection.getKey(item); | 2513 var key = this._collection.getKey(item); |
| 3901 var pidx = this._physicalIndexForKey[key]; | 2514 var pidx = this._physicalIndexForKey[key]; |
| 3902 | 2515 |
| 3903 if (pidx != null) { | 2516 if (pidx != null) { |
| 3904 this._updateMetrics([pidx]); | 2517 this._updateMetrics([pidx]); |
| 3905 this._positionItems(); | 2518 this._positionItems(); |
| 3906 } | 2519 } |
| 3907 }, | 2520 }, |
| 3908 | 2521 |
| 3909 /** | |
| 3910 * Creates a temporary backfill item in the rendered pool of physical items | |
| 3911 * to replace the main focused item. The focused item has tabIndex = 0 | |
| 3912 * and might be currently focused by the user. | |
| 3913 * | |
| 3914 * This dynamic replacement helps to preserve the focus state. | |
| 3915 */ | |
| 3916 _manageFocus: function() { | 2522 _manageFocus: function() { |
| 3917 var fidx = this._focusedIndex; | 2523 var fidx = this._focusedIndex; |
| 3918 | 2524 |
| 3919 if (fidx >= 0 && fidx < this._virtualCount) { | 2525 if (fidx >= 0 && fidx < this._virtualCount) { |
| 3920 // if it's a valid index, check if that index is rendered | |
| 3921 // in a physical item. | |
| 3922 if (this._isIndexRendered(fidx)) { | 2526 if (this._isIndexRendered(fidx)) { |
| 3923 this._restoreFocusedItem(); | 2527 this._restoreFocusedItem(); |
| 3924 } else { | 2528 } else { |
| 3925 this._createFocusBackfillItem(); | 2529 this._createFocusBackfillItem(); |
| 3926 } | 2530 } |
| 3927 } else if (this._virtualCount > 0 && this._physicalCount > 0) { | 2531 } else if (this._virtualCount > 0 && this._physicalCount > 0) { |
| 3928 // otherwise, assign the initial focused index. | |
| 3929 this._focusedIndex = this._virtualStart; | 2532 this._focusedIndex = this._virtualStart; |
| 3930 this._focusedItem = this._physicalItems[this._physicalStart]; | 2533 this._focusedItem = this._physicalItems[this._physicalStart]; |
| 3931 } | 2534 } |
| 3932 }, | 2535 }, |
| 3933 | 2536 |
| 3934 _isIndexRendered: function(idx) { | 2537 _isIndexRendered: function(idx) { |
| 3935 return idx >= this._virtualStart && idx <= this._virtualEnd; | 2538 return idx >= this._virtualStart && idx <= this._virtualEnd; |
| 3936 }, | 2539 }, |
| 3937 | 2540 |
| 3938 _isIndexVisible: function(idx) { | 2541 _isIndexVisible: function(idx) { |
| 3939 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex; | 2542 return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex; |
| 3940 }, | 2543 }, |
| 3941 | 2544 |
| 3942 _getPhysicalIndex: function(idx) { | 2545 _getPhysicalIndex: function(idx) { |
| 3943 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz
edItem(idx))]; | 2546 return this._physicalIndexForKey[this._collection.getKey(this._getNormaliz
edItem(idx))]; |
| 3944 }, | 2547 }, |
| 3945 | 2548 |
| 3946 _focusPhysicalItem: function(idx) { | 2549 _focusPhysicalItem: function(idx) { |
| 3947 if (idx < 0 || idx >= this._virtualCount) { | 2550 if (idx < 0 || idx >= this._virtualCount) { |
| 3948 return; | 2551 return; |
| 3949 } | 2552 } |
| 3950 this._restoreFocusedItem(); | 2553 this._restoreFocusedItem(); |
| 3951 // scroll to index to make sure it's rendered | |
| 3952 if (!this._isIndexRendered(idx)) { | 2554 if (!this._isIndexRendered(idx)) { |
| 3953 this.scrollToIndex(idx); | 2555 this.scrollToIndex(idx); |
| 3954 } | 2556 } |
| 3955 | 2557 |
| 3956 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)]; | 2558 var physicalItem = this._physicalItems[this._getPhysicalIndex(idx)]; |
| 3957 var model = physicalItem._templateInstance; | 2559 var model = physicalItem._templateInstance; |
| 3958 var focusable; | 2560 var focusable; |
| 3959 | 2561 |
| 3960 // set a secret tab index | |
| 3961 model.tabIndex = SECRET_TABINDEX; | 2562 model.tabIndex = SECRET_TABINDEX; |
| 3962 // check if focusable element is the physical item | |
| 3963 if (physicalItem.tabIndex === SECRET_TABINDEX) { | 2563 if (physicalItem.tabIndex === SECRET_TABINDEX) { |
| 3964 focusable = physicalItem; | 2564 focusable = physicalItem; |
| 3965 } | 2565 } |
| 3966 // search for the element which tabindex is bound to the secret tab index | |
| 3967 if (!focusable) { | 2566 if (!focusable) { |
| 3968 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR
ET_TABINDEX + '"]'); | 2567 focusable = Polymer.dom(physicalItem).querySelector('[tabindex="' + SECR
ET_TABINDEX + '"]'); |
| 3969 } | 2568 } |
| 3970 // restore the tab index | |
| 3971 model.tabIndex = 0; | 2569 model.tabIndex = 0; |
| 3972 // focus the focusable element | |
| 3973 this._focusedIndex = idx; | 2570 this._focusedIndex = idx; |
| 3974 focusable && focusable.focus(); | 2571 focusable && focusable.focus(); |
| 3975 }, | 2572 }, |
| 3976 | 2573 |
| 3977 _removeFocusedItem: function() { | 2574 _removeFocusedItem: function() { |
| 3978 if (this._offscreenFocusedItem) { | 2575 if (this._offscreenFocusedItem) { |
| 3979 Polymer.dom(this).removeChild(this._offscreenFocusedItem); | 2576 Polymer.dom(this).removeChild(this._offscreenFocusedItem); |
| 3980 } | 2577 } |
| 3981 this._offscreenFocusedItem = null; | 2578 this._offscreenFocusedItem = null; |
| 3982 this._focusBackfillItem = null; | 2579 this._focusBackfillItem = null; |
| 3983 this._focusedItem = null; | 2580 this._focusedItem = null; |
| 3984 this._focusedIndex = -1; | 2581 this._focusedIndex = -1; |
| 3985 }, | 2582 }, |
| 3986 | 2583 |
| 3987 _createFocusBackfillItem: function() { | 2584 _createFocusBackfillItem: function() { |
| 3988 var pidx, fidx = this._focusedIndex; | 2585 var pidx, fidx = this._focusedIndex; |
| 3989 if (this._offscreenFocusedItem || fidx < 0) { | 2586 if (this._offscreenFocusedItem || fidx < 0) { |
| 3990 return; | 2587 return; |
| 3991 } | 2588 } |
| 3992 if (!this._focusBackfillItem) { | 2589 if (!this._focusBackfillItem) { |
| 3993 // create a physical item, so that it backfills the focused item. | |
| 3994 var stampedTemplate = this.stamp(null); | 2590 var stampedTemplate = this.stamp(null); |
| 3995 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); | 2591 this._focusBackfillItem = stampedTemplate.root.querySelector('*'); |
| 3996 Polymer.dom(this).appendChild(stampedTemplate.root); | 2592 Polymer.dom(this).appendChild(stampedTemplate.root); |
| 3997 } | 2593 } |
| 3998 // get the physical index for the focused index | |
| 3999 pidx = this._getPhysicalIndex(fidx); | 2594 pidx = this._getPhysicalIndex(fidx); |
| 4000 | 2595 |
| 4001 if (pidx != null) { | 2596 if (pidx != null) { |
| 4002 // set the offcreen focused physical item | |
| 4003 this._offscreenFocusedItem = this._physicalItems[pidx]; | 2597 this._offscreenFocusedItem = this._physicalItems[pidx]; |
| 4004 // backfill the focused physical item | |
| 4005 this._physicalItems[pidx] = this._focusBackfillItem; | 2598 this._physicalItems[pidx] = this._focusBackfillItem; |
| 4006 // hide the focused physical | |
| 4007 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); | 2599 this.translate3d(0, HIDDEN_Y, 0, this._offscreenFocusedItem); |
| 4008 } | 2600 } |
| 4009 }, | 2601 }, |
| 4010 | 2602 |
| 4011 _restoreFocusedItem: function() { | 2603 _restoreFocusedItem: function() { |
| 4012 var pidx, fidx = this._focusedIndex; | 2604 var pidx, fidx = this._focusedIndex; |
| 4013 | 2605 |
| 4014 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { | 2606 if (!this._offscreenFocusedItem || this._focusedIndex < 0) { |
| 4015 return; | 2607 return; |
| 4016 } | 2608 } |
| 4017 // assign models to the focused index | |
| 4018 this._assignModels(); | 2609 this._assignModels(); |
| 4019 // get the new physical index for the focused index | |
| 4020 pidx = this._getPhysicalIndex(fidx); | 2610 pidx = this._getPhysicalIndex(fidx); |
| 4021 | 2611 |
| 4022 if (pidx != null) { | 2612 if (pidx != null) { |
| 4023 // flip the focus backfill | |
| 4024 this._focusBackfillItem = this._physicalItems[pidx]; | 2613 this._focusBackfillItem = this._physicalItems[pidx]; |
| 4025 // restore the focused physical item | |
| 4026 this._physicalItems[pidx] = this._offscreenFocusedItem; | 2614 this._physicalItems[pidx] = this._offscreenFocusedItem; |
| 4027 // reset the offscreen focused item | |
| 4028 this._offscreenFocusedItem = null; | 2615 this._offscreenFocusedItem = null; |
| 4029 // hide the physical item that backfills | |
| 4030 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); | 2616 this.translate3d(0, HIDDEN_Y, 0, this._focusBackfillItem); |
| 4031 } | 2617 } |
| 4032 }, | 2618 }, |
| 4033 | 2619 |
| 4034 _didFocus: function(e) { | 2620 _didFocus: function(e) { |
| 4035 var targetModel = this.modelForElement(e.target); | 2621 var targetModel = this.modelForElement(e.target); |
| 4036 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance
: null; | 2622 var focusedModel = this._focusedItem ? this._focusedItem._templateInstance
: null; |
| 4037 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; | 2623 var hasOffscreenFocusedItem = this._offscreenFocusedItem !== null; |
| 4038 var fidx = this._focusedIndex; | 2624 var fidx = this._focusedIndex; |
| 4039 | 2625 |
| 4040 if (!targetModel || !focusedModel) { | 2626 if (!targetModel || !focusedModel) { |
| 4041 return; | 2627 return; |
| 4042 } | 2628 } |
| 4043 if (focusedModel === targetModel) { | 2629 if (focusedModel === targetModel) { |
| 4044 // if the user focused the same item, then bring it into view if it's no
t visible | |
| 4045 if (!this._isIndexVisible(fidx)) { | 2630 if (!this._isIndexVisible(fidx)) { |
| 4046 this.scrollToIndex(fidx); | 2631 this.scrollToIndex(fidx); |
| 4047 } | 2632 } |
| 4048 } else { | 2633 } else { |
| 4049 this._restoreFocusedItem(); | 2634 this._restoreFocusedItem(); |
| 4050 // restore tabIndex for the currently focused item | |
| 4051 focusedModel.tabIndex = -1; | 2635 focusedModel.tabIndex = -1; |
| 4052 // set the tabIndex for the next focused item | |
| 4053 targetModel.tabIndex = 0; | 2636 targetModel.tabIndex = 0; |
| 4054 fidx = targetModel[this.indexAs]; | 2637 fidx = targetModel[this.indexAs]; |
| 4055 this._focusedIndex = fidx; | 2638 this._focusedIndex = fidx; |
| 4056 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)]; | 2639 this._focusedItem = this._physicalItems[this._getPhysicalIndex(fidx)]; |
| 4057 | 2640 |
| 4058 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) { | 2641 if (hasOffscreenFocusedItem && !this._offscreenFocusedItem) { |
| 4059 this._update(); | 2642 this._update(); |
| 4060 } | 2643 } |
| 4061 } | 2644 } |
| 4062 }, | 2645 }, |
| 4063 | 2646 |
| 4064 _didMoveUp: function() { | 2647 _didMoveUp: function() { |
| 4065 this._focusPhysicalItem(this._focusedIndex - 1); | 2648 this._focusPhysicalItem(this._focusedIndex - 1); |
| 4066 }, | 2649 }, |
| 4067 | 2650 |
| 4068 _didMoveDown: function(e) { | 2651 _didMoveDown: function(e) { |
| 4069 // disable scroll when pressing the down key | |
| 4070 e.detail.keyboardEvent.preventDefault(); | 2652 e.detail.keyboardEvent.preventDefault(); |
| 4071 this._focusPhysicalItem(this._focusedIndex + 1); | 2653 this._focusPhysicalItem(this._focusedIndex + 1); |
| 4072 }, | 2654 }, |
| 4073 | 2655 |
| 4074 _didEnter: function(e) { | 2656 _didEnter: function(e) { |
| 4075 this._focusPhysicalItem(this._focusedIndex); | 2657 this._focusPhysicalItem(this._focusedIndex); |
| 4076 this._selectionHandler(e.detail.keyboardEvent); | 2658 this._selectionHandler(e.detail.keyboardEvent); |
| 4077 } | 2659 } |
| 4078 }); | 2660 }); |
| 4079 | 2661 |
| 4080 })(); | 2662 })(); |
| 4081 // Copyright 2015 The Chromium Authors. All rights reserved. | 2663 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 4082 // Use of this source code is governed by a BSD-style license that can be | 2664 // Use of this source code is governed by a BSD-style license that can be |
| 4083 // found in the LICENSE file. | 2665 // found in the LICENSE file. |
| 4084 | 2666 |
| 4085 cr.define('downloads', function() { | 2667 cr.define('downloads', function() { |
| 4086 /** | |
| 4087 * @param {string} chromeSendName | |
| 4088 * @return {function(string):void} A chrome.send() callback with curried name. | |
| 4089 */ | |
| 4090 function chromeSendWithId(chromeSendName) { | 2668 function chromeSendWithId(chromeSendName) { |
| 4091 return function(id) { chrome.send(chromeSendName, [id]); }; | 2669 return function(id) { chrome.send(chromeSendName, [id]); }; |
| 4092 } | 2670 } |
| 4093 | 2671 |
| 4094 /** @constructor */ | 2672 /** @constructor */ |
| 4095 function ActionService() { | 2673 function ActionService() { |
| 4096 /** @private {Array<string>} */ | 2674 /** @private {Array<string>} */ |
| 4097 this.searchTerms_ = []; | 2675 this.searchTerms_ = []; |
| 4098 } | 2676 } |
| 4099 | 2677 |
| 4100 /** | |
| 4101 * @param {string} s | |
| 4102 * @return {string} |s| without whitespace at the beginning or end. | |
| 4103 */ | |
| 4104 function trim(s) { return s.trim(); } | 2678 function trim(s) { return s.trim(); } |
| 4105 | 2679 |
| 4106 /** | |
| 4107 * @param {string|undefined} value | |
| 4108 * @return {boolean} Whether |value| is truthy. | |
| 4109 */ | |
| 4110 function truthy(value) { return !!value; } | 2680 function truthy(value) { return !!value; } |
| 4111 | 2681 |
| 4112 /** | |
| 4113 * @param {string} searchText Input typed by the user into a search box. | |
| 4114 * @return {Array<string>} A list of terms extracted from |searchText|. | |
| 4115 */ | |
| 4116 ActionService.splitTerms = function(searchText) { | 2682 ActionService.splitTerms = function(searchText) { |
| 4117 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']). | |
| 4118 return searchText.split(/"([^"]*)"/).map(trim).filter(truthy); | 2683 return searchText.split(/"([^"]*)"/).map(trim).filter(truthy); |
| 4119 }; | 2684 }; |
| 4120 | 2685 |
| 4121 ActionService.prototype = { | 2686 ActionService.prototype = { |
| 4122 /** @param {string} id ID of the download to cancel. */ | 2687 /** @param {string} id ID of the download to cancel. */ |
| 4123 cancel: chromeSendWithId('cancel'), | 2688 cancel: chromeSendWithId('cancel'), |
| 4124 | 2689 |
| 4125 /** Instructs the browser to clear all finished downloads. */ | 2690 /** Instructs the browser to clear all finished downloads. */ |
| 4126 clearAll: function() { | 2691 clearAll: function() { |
| 4127 if (loadTimeData.getBoolean('allowDeletingHistory')) { | 2692 if (loadTimeData.getBoolean('allowDeletingHistory')) { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 4142 }, | 2707 }, |
| 4143 | 2708 |
| 4144 /** @param {string} id ID of the download that the user started dragging. */ | 2709 /** @param {string} id ID of the download that the user started dragging. */ |
| 4145 drag: chromeSendWithId('drag'), | 2710 drag: chromeSendWithId('drag'), |
| 4146 | 2711 |
| 4147 /** Loads more downloads with the current search terms. */ | 2712 /** Loads more downloads with the current search terms. */ |
| 4148 loadMore: function() { | 2713 loadMore: function() { |
| 4149 chrome.send('getDownloads', this.searchTerms_); | 2714 chrome.send('getDownloads', this.searchTerms_); |
| 4150 }, | 2715 }, |
| 4151 | 2716 |
| 4152 /** | |
| 4153 * @return {boolean} Whether the user is currently searching for downloads | |
| 4154 * (i.e. has a non-empty search term). | |
| 4155 */ | |
| 4156 isSearching: function() { | 2717 isSearching: function() { |
| 4157 return this.searchTerms_.length > 0; | 2718 return this.searchTerms_.length > 0; |
| 4158 }, | 2719 }, |
| 4159 | 2720 |
| 4160 /** Opens the current local destination for downloads. */ | 2721 /** Opens the current local destination for downloads. */ |
| 4161 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'), | 2722 openDownloadsFolder: chrome.send.bind(chrome, 'openDownloadsFolder'), |
| 4162 | 2723 |
| 4163 /** | |
| 4164 * @param {string} id ID of the download to run locally on the user's box. | |
| 4165 */ | |
| 4166 openFile: chromeSendWithId('openFile'), | 2724 openFile: chromeSendWithId('openFile'), |
| 4167 | 2725 |
| 4168 /** @param {string} id ID the of the progressing download to pause. */ | 2726 /** @param {string} id ID the of the progressing download to pause. */ |
| 4169 pause: chromeSendWithId('pause'), | 2727 pause: chromeSendWithId('pause'), |
| 4170 | 2728 |
| 4171 /** @param {string} id ID of the finished download to remove. */ | 2729 /** @param {string} id ID of the finished download to remove. */ |
| 4172 remove: chromeSendWithId('remove'), | 2730 remove: chromeSendWithId('remove'), |
| 4173 | 2731 |
| 4174 /** @param {string} id ID of the paused download to resume. */ | 2732 /** @param {string} id ID of the paused download to resume. */ |
| 4175 resume: chromeSendWithId('resume'), | 2733 resume: chromeSendWithId('resume'), |
| 4176 | 2734 |
| 4177 /** | |
| 4178 * @param {string} id ID of the dangerous download to save despite | |
| 4179 * warnings. | |
| 4180 */ | |
| 4181 saveDangerous: chromeSendWithId('saveDangerous'), | 2735 saveDangerous: chromeSendWithId('saveDangerous'), |
| 4182 | 2736 |
| 4183 /** @param {string} searchText What to search for. */ | 2737 /** @param {string} searchText What to search for. */ |
| 4184 search: function(searchText) { | 2738 search: function(searchText) { |
| 4185 var searchTerms = ActionService.splitTerms(searchText); | 2739 var searchTerms = ActionService.splitTerms(searchText); |
| 4186 var sameTerms = searchTerms.length == this.searchTerms_.length; | 2740 var sameTerms = searchTerms.length == this.searchTerms_.length; |
| 4187 | 2741 |
| 4188 for (var i = 0; sameTerms && i < searchTerms.length; ++i) { | 2742 for (var i = 0; sameTerms && i < searchTerms.length; ++i) { |
| 4189 if (searchTerms[i] != this.searchTerms_[i]) | 2743 if (searchTerms[i] != this.searchTerms_[i]) |
| 4190 sameTerms = false; | 2744 sameTerms = false; |
| 4191 } | 2745 } |
| 4192 | 2746 |
| 4193 if (sameTerms) | 2747 if (sameTerms) |
| 4194 return; | 2748 return; |
| 4195 | 2749 |
| 4196 this.searchTerms_ = searchTerms; | 2750 this.searchTerms_ = searchTerms; |
| 4197 this.loadMore(); | 2751 this.loadMore(); |
| 4198 }, | 2752 }, |
| 4199 | 2753 |
| 4200 /** | |
| 4201 * Shows the local folder a finished download resides in. | |
| 4202 * @param {string} id ID of the download to show. | |
| 4203 */ | |
| 4204 show: chromeSendWithId('show'), | 2754 show: chromeSendWithId('show'), |
| 4205 | 2755 |
| 4206 /** Undo download removal. */ | 2756 /** Undo download removal. */ |
| 4207 undo: chrome.send.bind(chrome, 'undo'), | 2757 undo: chrome.send.bind(chrome, 'undo'), |
| 4208 }; | 2758 }; |
| 4209 | 2759 |
| 4210 cr.addSingletonGetter(ActionService); | 2760 cr.addSingletonGetter(ActionService); |
| 4211 | 2761 |
| 4212 return {ActionService: ActionService}; | 2762 return {ActionService: ActionService}; |
| 4213 }); | 2763 }); |
| 4214 // Copyright 2015 The Chromium Authors. All rights reserved. | 2764 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 4215 // Use of this source code is governed by a BSD-style license that can be | 2765 // Use of this source code is governed by a BSD-style license that can be |
| 4216 // found in the LICENSE file. | 2766 // found in the LICENSE file. |
| 4217 | 2767 |
| 4218 cr.define('downloads', function() { | 2768 cr.define('downloads', function() { |
| 4219 /** | |
| 4220 * Explains why a download is in DANGEROUS state. | |
| 4221 * @enum {string} | |
| 4222 */ | |
| 4223 var DangerType = { | 2769 var DangerType = { |
| 4224 NOT_DANGEROUS: 'NOT_DANGEROUS', | 2770 NOT_DANGEROUS: 'NOT_DANGEROUS', |
| 4225 DANGEROUS_FILE: 'DANGEROUS_FILE', | 2771 DANGEROUS_FILE: 'DANGEROUS_FILE', |
| 4226 DANGEROUS_URL: 'DANGEROUS_URL', | 2772 DANGEROUS_URL: 'DANGEROUS_URL', |
| 4227 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT', | 2773 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT', |
| 4228 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT', | 2774 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT', |
| 4229 DANGEROUS_HOST: 'DANGEROUS_HOST', | 2775 DANGEROUS_HOST: 'DANGEROUS_HOST', |
| 4230 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED', | 2776 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED', |
| 4231 }; | 2777 }; |
| 4232 | 2778 |
| 4233 /** | |
| 4234 * The states a download can be in. These correspond to states defined in | |
| 4235 * DownloadsDOMHandler::CreateDownloadItemValue | |
| 4236 * @enum {string} | |
| 4237 */ | |
| 4238 var States = { | 2779 var States = { |
| 4239 IN_PROGRESS: 'IN_PROGRESS', | 2780 IN_PROGRESS: 'IN_PROGRESS', |
| 4240 CANCELLED: 'CANCELLED', | 2781 CANCELLED: 'CANCELLED', |
| 4241 COMPLETE: 'COMPLETE', | 2782 COMPLETE: 'COMPLETE', |
| 4242 PAUSED: 'PAUSED', | 2783 PAUSED: 'PAUSED', |
| 4243 DANGEROUS: 'DANGEROUS', | 2784 DANGEROUS: 'DANGEROUS', |
| 4244 INTERRUPTED: 'INTERRUPTED', | 2785 INTERRUPTED: 'INTERRUPTED', |
| 4245 }; | 2786 }; |
| 4246 | 2787 |
| 4247 return { | 2788 return { |
| 4248 DangerType: DangerType, | 2789 DangerType: DangerType, |
| 4249 States: States, | 2790 States: States, |
| 4250 }; | 2791 }; |
| 4251 }); | 2792 }); |
| 4252 // Copyright 2014 The Chromium Authors. All rights reserved. | 2793 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 4253 // Use of this source code is governed by a BSD-style license that can be | 2794 // Use of this source code is governed by a BSD-style license that can be |
| 4254 // found in the LICENSE file. | 2795 // found in the LICENSE file. |
| 4255 | 2796 |
| 4256 // Action links are elements that are used to perform an in-page navigation or | |
| 4257 // action (e.g. showing a dialog). | |
| 4258 // | |
| 4259 // They look like normal anchor (<a>) tags as their text color is blue. However, | |
| 4260 // they're subtly different as they're not initially underlined (giving users a | |
| 4261 // clue that underlined links navigate while action links don't). | |
| 4262 // | |
| 4263 // Action links look very similar to normal links when hovered (hand cursor, | |
| 4264 // underlined). This gives the user an idea that clicking this link will do | |
| 4265 // something similar to navigation but in the same page. | |
| 4266 // | |
| 4267 // They can be created in JavaScript like this: | |
| 4268 // | |
| 4269 // var link = document.createElement('a', 'action-link'); // Note second arg. | |
| 4270 // | |
| 4271 // or with a constructor like this: | |
| 4272 // | |
| 4273 // var link = new ActionLink(); | |
| 4274 // | |
| 4275 // They can be used easily from HTML as well, like so: | |
| 4276 // | |
| 4277 // <a is="action-link">Click me!</a> | |
| 4278 // | |
| 4279 // NOTE: <action-link> and document.createElement('action-link') don't work. | |
| 4280 | 2797 |
| 4281 /** | |
| 4282 * @constructor | |
| 4283 * @extends {HTMLAnchorElement} | |
| 4284 */ | |
| 4285 var ActionLink = document.registerElement('action-link', { | 2798 var ActionLink = document.registerElement('action-link', { |
| 4286 prototype: { | 2799 prototype: { |
| 4287 __proto__: HTMLAnchorElement.prototype, | 2800 __proto__: HTMLAnchorElement.prototype, |
| 4288 | 2801 |
| 4289 /** @this {ActionLink} */ | 2802 /** @this {ActionLink} */ |
| 4290 createdCallback: function() { | 2803 createdCallback: function() { |
| 4291 // Action links can start disabled (e.g. <a is="action-link" disabled>). | |
| 4292 this.tabIndex = this.disabled ? -1 : 0; | 2804 this.tabIndex = this.disabled ? -1 : 0; |
| 4293 | 2805 |
| 4294 if (!this.hasAttribute('role')) | 2806 if (!this.hasAttribute('role')) |
| 4295 this.setAttribute('role', 'link'); | 2807 this.setAttribute('role', 'link'); |
| 4296 | 2808 |
| 4297 this.addEventListener('keydown', function(e) { | 2809 this.addEventListener('keydown', function(e) { |
| 4298 if (!this.disabled && e.key == 'Enter' && !this.href) { | 2810 if (!this.disabled && e.key == 'Enter' && !this.href) { |
| 4299 // Schedule a click asynchronously because other 'keydown' handlers | |
| 4300 // may still run later (e.g. document.addEventListener('keydown')). | |
| 4301 // Specifically options dialogs break when this timeout isn't here. | |
| 4302 // NOTE: this affects the "trusted" state of the ensuing click. I | |
| 4303 // haven't found anything that breaks because of this (yet). | |
| 4304 window.setTimeout(this.click.bind(this), 0); | 2811 window.setTimeout(this.click.bind(this), 0); |
| 4305 } | 2812 } |
| 4306 }); | 2813 }); |
| 4307 | 2814 |
| 4308 function preventDefault(e) { | 2815 function preventDefault(e) { |
| 4309 e.preventDefault(); | 2816 e.preventDefault(); |
| 4310 } | 2817 } |
| 4311 | 2818 |
| 4312 function removePreventDefault() { | 2819 function removePreventDefault() { |
| 4313 document.removeEventListener('selectstart', preventDefault); | 2820 document.removeEventListener('selectstart', preventDefault); |
| 4314 document.removeEventListener('mouseup', removePreventDefault); | 2821 document.removeEventListener('mouseup', removePreventDefault); |
| 4315 } | 2822 } |
| 4316 | 2823 |
| 4317 this.addEventListener('mousedown', function() { | 2824 this.addEventListener('mousedown', function() { |
| 4318 // This handlers strives to match the behavior of <a href="...">. | |
| 4319 | 2825 |
| 4320 // While the mouse is down, prevent text selection from dragging. | |
| 4321 document.addEventListener('selectstart', preventDefault); | 2826 document.addEventListener('selectstart', preventDefault); |
| 4322 document.addEventListener('mouseup', removePreventDefault); | 2827 document.addEventListener('mouseup', removePreventDefault); |
| 4323 | 2828 |
| 4324 // If focus started via mouse press, don't show an outline. | |
| 4325 if (document.activeElement != this) | 2829 if (document.activeElement != this) |
| 4326 this.classList.add('no-outline'); | 2830 this.classList.add('no-outline'); |
| 4327 }); | 2831 }); |
| 4328 | 2832 |
| 4329 this.addEventListener('blur', function() { | 2833 this.addEventListener('blur', function() { |
| 4330 this.classList.remove('no-outline'); | 2834 this.classList.remove('no-outline'); |
| 4331 }); | 2835 }); |
| 4332 }, | 2836 }, |
| 4333 | 2837 |
| 4334 /** @type {boolean} */ | 2838 /** @type {boolean} */ |
| (...skipping 22 matching lines...) Expand all Loading... |
| 4357 this.disabled = false; | 2861 this.disabled = false; |
| 4358 else | 2862 else |
| 4359 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); | 2863 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); |
| 4360 }, | 2864 }, |
| 4361 }, | 2865 }, |
| 4362 | 2866 |
| 4363 extends: 'a', | 2867 extends: 'a', |
| 4364 }); | 2868 }); |
| 4365 (function() { | 2869 (function() { |
| 4366 | 2870 |
| 4367 // monostate data | |
| 4368 var metaDatas = {}; | 2871 var metaDatas = {}; |
| 4369 var metaArrays = {}; | 2872 var metaArrays = {}; |
| 4370 var singleton = null; | 2873 var singleton = null; |
| 4371 | 2874 |
| 4372 Polymer.IronMeta = Polymer({ | 2875 Polymer.IronMeta = Polymer({ |
| 4373 | 2876 |
| 4374 is: 'iron-meta', | 2877 is: 'iron-meta', |
| 4375 | 2878 |
| 4376 properties: { | 2879 properties: { |
| 4377 | 2880 |
| 4378 /** | |
| 4379 * The type of meta-data. All meta-data of the same type is stored | |
| 4380 * together. | |
| 4381 */ | |
| 4382 type: { | 2881 type: { |
| 4383 type: String, | 2882 type: String, |
| 4384 value: 'default', | 2883 value: 'default', |
| 4385 observer: '_typeChanged' | 2884 observer: '_typeChanged' |
| 4386 }, | 2885 }, |
| 4387 | 2886 |
| 4388 /** | |
| 4389 * The key used to store `value` under the `type` namespace. | |
| 4390 */ | |
| 4391 key: { | 2887 key: { |
| 4392 type: String, | 2888 type: String, |
| 4393 observer: '_keyChanged' | 2889 observer: '_keyChanged' |
| 4394 }, | 2890 }, |
| 4395 | 2891 |
| 4396 /** | |
| 4397 * The meta-data to store or retrieve. | |
| 4398 */ | |
| 4399 value: { | 2892 value: { |
| 4400 type: Object, | 2893 type: Object, |
| 4401 notify: true, | 2894 notify: true, |
| 4402 observer: '_valueChanged' | 2895 observer: '_valueChanged' |
| 4403 }, | 2896 }, |
| 4404 | 2897 |
| 4405 /** | |
| 4406 * If true, `value` is set to the iron-meta instance itself. | |
| 4407 */ | |
| 4408 self: { | 2898 self: { |
| 4409 type: Boolean, | 2899 type: Boolean, |
| 4410 observer: '_selfChanged' | 2900 observer: '_selfChanged' |
| 4411 }, | 2901 }, |
| 4412 | 2902 |
| 4413 /** | |
| 4414 * Array of all meta-data values for the given type. | |
| 4415 */ | |
| 4416 list: { | 2903 list: { |
| 4417 type: Array, | 2904 type: Array, |
| 4418 notify: true | 2905 notify: true |
| 4419 } | 2906 } |
| 4420 | 2907 |
| 4421 }, | 2908 }, |
| 4422 | 2909 |
| 4423 hostAttributes: { | 2910 hostAttributes: { |
| 4424 hidden: true | 2911 hidden: true |
| 4425 }, | 2912 }, |
| 4426 | 2913 |
| 4427 /** | |
| 4428 * Only runs if someone invokes the factory/constructor directly | |
| 4429 * e.g. `new Polymer.IronMeta()` | |
| 4430 * | |
| 4431 * @param {{type: (string|undefined), key: (string|undefined), value}=} co
nfig | |
| 4432 */ | |
| 4433 factoryImpl: function(config) { | 2914 factoryImpl: function(config) { |
| 4434 if (config) { | 2915 if (config) { |
| 4435 for (var n in config) { | 2916 for (var n in config) { |
| 4436 switch(n) { | 2917 switch(n) { |
| 4437 case 'type': | 2918 case 'type': |
| 4438 case 'key': | 2919 case 'key': |
| 4439 case 'value': | 2920 case 'value': |
| 4440 this[n] = config[n]; | 2921 this[n] = config[n]; |
| 4441 break; | 2922 break; |
| 4442 } | 2923 } |
| 4443 } | 2924 } |
| 4444 } | 2925 } |
| 4445 }, | 2926 }, |
| 4446 | 2927 |
| 4447 created: function() { | 2928 created: function() { |
| 4448 // TODO(sjmiles): good for debugging? | |
| 4449 this._metaDatas = metaDatas; | 2929 this._metaDatas = metaDatas; |
| 4450 this._metaArrays = metaArrays; | 2930 this._metaArrays = metaArrays; |
| 4451 }, | 2931 }, |
| 4452 | 2932 |
| 4453 _keyChanged: function(key, old) { | 2933 _keyChanged: function(key, old) { |
| 4454 this._resetRegistration(old); | 2934 this._resetRegistration(old); |
| 4455 }, | 2935 }, |
| 4456 | 2936 |
| 4457 _valueChanged: function(value) { | 2937 _valueChanged: function(value) { |
| 4458 this._resetRegistration(this.key); | 2938 this._resetRegistration(this.key); |
| (...skipping 11 matching lines...) Expand all Loading... |
| 4470 metaDatas[type] = {}; | 2950 metaDatas[type] = {}; |
| 4471 } | 2951 } |
| 4472 this._metaData = metaDatas[type]; | 2952 this._metaData = metaDatas[type]; |
| 4473 if (!metaArrays[type]) { | 2953 if (!metaArrays[type]) { |
| 4474 metaArrays[type] = []; | 2954 metaArrays[type] = []; |
| 4475 } | 2955 } |
| 4476 this.list = metaArrays[type]; | 2956 this.list = metaArrays[type]; |
| 4477 this._registerKeyValue(this.key, this.value); | 2957 this._registerKeyValue(this.key, this.value); |
| 4478 }, | 2958 }, |
| 4479 | 2959 |
| 4480 /** | |
| 4481 * Retrieves meta data value by key. | |
| 4482 * | |
| 4483 * @method byKey | |
| 4484 * @param {string} key The key of the meta-data to be returned. | |
| 4485 * @return {*} | |
| 4486 */ | |
| 4487 byKey: function(key) { | 2960 byKey: function(key) { |
| 4488 return this._metaData && this._metaData[key]; | 2961 return this._metaData && this._metaData[key]; |
| 4489 }, | 2962 }, |
| 4490 | 2963 |
| 4491 _resetRegistration: function(oldKey) { | 2964 _resetRegistration: function(oldKey) { |
| 4492 this._unregisterKey(oldKey); | 2965 this._unregisterKey(oldKey); |
| 4493 this._registerKeyValue(this.key, this.value); | 2966 this._registerKeyValue(this.key, this.value); |
| 4494 }, | 2967 }, |
| 4495 | 2968 |
| 4496 _unregisterKey: function(key) { | 2969 _unregisterKey: function(key) { |
| (...skipping 23 matching lines...) Expand all Loading... |
| 4520 | 2993 |
| 4521 }); | 2994 }); |
| 4522 | 2995 |
| 4523 Polymer.IronMeta.getIronMeta = function getIronMeta() { | 2996 Polymer.IronMeta.getIronMeta = function getIronMeta() { |
| 4524 if (singleton === null) { | 2997 if (singleton === null) { |
| 4525 singleton = new Polymer.IronMeta(); | 2998 singleton = new Polymer.IronMeta(); |
| 4526 } | 2999 } |
| 4527 return singleton; | 3000 return singleton; |
| 4528 }; | 3001 }; |
| 4529 | 3002 |
| 4530 /** | |
| 4531 `iron-meta-query` can be used to access infomation stored in `iron-meta`. | |
| 4532 | |
| 4533 Examples: | |
| 4534 | |
| 4535 If I create an instance like this: | |
| 4536 | |
| 4537 <iron-meta key="info" value="foo/bar"></iron-meta> | |
| 4538 | |
| 4539 Note that value="foo/bar" is the metadata I've defined. I could define more | |
| 4540 attributes or use child nodes to define additional metadata. | |
| 4541 | |
| 4542 Now I can access that element (and it's metadata) from any `iron-meta-query`
instance: | |
| 4543 | |
| 4544 var value = new Polymer.IronMetaQuery({key: 'info'}).value; | |
| 4545 | |
| 4546 @group Polymer Iron Elements | |
| 4547 @element iron-meta-query | |
| 4548 */ | |
| 4549 Polymer.IronMetaQuery = Polymer({ | 3003 Polymer.IronMetaQuery = Polymer({ |
| 4550 | 3004 |
| 4551 is: 'iron-meta-query', | 3005 is: 'iron-meta-query', |
| 4552 | 3006 |
| 4553 properties: { | 3007 properties: { |
| 4554 | 3008 |
| 4555 /** | |
| 4556 * The type of meta-data. All meta-data of the same type is stored | |
| 4557 * together. | |
| 4558 */ | |
| 4559 type: { | 3009 type: { |
| 4560 type: String, | 3010 type: String, |
| 4561 value: 'default', | 3011 value: 'default', |
| 4562 observer: '_typeChanged' | 3012 observer: '_typeChanged' |
| 4563 }, | 3013 }, |
| 4564 | 3014 |
| 4565 /** | |
| 4566 * Specifies a key to use for retrieving `value` from the `type` | |
| 4567 * namespace. | |
| 4568 */ | |
| 4569 key: { | 3015 key: { |
| 4570 type: String, | 3016 type: String, |
| 4571 observer: '_keyChanged' | 3017 observer: '_keyChanged' |
| 4572 }, | 3018 }, |
| 4573 | 3019 |
| 4574 /** | |
| 4575 * The meta-data to store or retrieve. | |
| 4576 */ | |
| 4577 value: { | 3020 value: { |
| 4578 type: Object, | 3021 type: Object, |
| 4579 notify: true, | 3022 notify: true, |
| 4580 readOnly: true | 3023 readOnly: true |
| 4581 }, | 3024 }, |
| 4582 | 3025 |
| 4583 /** | |
| 4584 * Array of all meta-data values for the given type. | |
| 4585 */ | |
| 4586 list: { | 3026 list: { |
| 4587 type: Array, | 3027 type: Array, |
| 4588 notify: true | 3028 notify: true |
| 4589 } | 3029 } |
| 4590 | 3030 |
| 4591 }, | 3031 }, |
| 4592 | 3032 |
| 4593 /** | |
| 4594 * Actually a factory method, not a true constructor. Only runs if | |
| 4595 * someone invokes it directly (via `new Polymer.IronMeta()`); | |
| 4596 * | |
| 4597 * @param {{type: (string|undefined), key: (string|undefined)}=} config | |
| 4598 */ | |
| 4599 factoryImpl: function(config) { | 3033 factoryImpl: function(config) { |
| 4600 if (config) { | 3034 if (config) { |
| 4601 for (var n in config) { | 3035 for (var n in config) { |
| 4602 switch(n) { | 3036 switch(n) { |
| 4603 case 'type': | 3037 case 'type': |
| 4604 case 'key': | 3038 case 'key': |
| 4605 this[n] = config[n]; | 3039 this[n] = config[n]; |
| 4606 break; | 3040 break; |
| 4607 } | 3041 } |
| 4608 } | 3042 } |
| 4609 } | 3043 } |
| 4610 }, | 3044 }, |
| 4611 | 3045 |
| 4612 created: function() { | 3046 created: function() { |
| 4613 // TODO(sjmiles): good for debugging? | |
| 4614 this._metaDatas = metaDatas; | 3047 this._metaDatas = metaDatas; |
| 4615 this._metaArrays = metaArrays; | 3048 this._metaArrays = metaArrays; |
| 4616 }, | 3049 }, |
| 4617 | 3050 |
| 4618 _keyChanged: function(key) { | 3051 _keyChanged: function(key) { |
| 4619 this._setValue(this._metaData && this._metaData[key]); | 3052 this._setValue(this._metaData && this._metaData[key]); |
| 4620 }, | 3053 }, |
| 4621 | 3054 |
| 4622 _typeChanged: function(type) { | 3055 _typeChanged: function(type) { |
| 4623 this._metaData = metaDatas[type]; | 3056 this._metaData = metaDatas[type]; |
| 4624 this.list = metaArrays[type]; | 3057 this.list = metaArrays[type]; |
| 4625 if (this.key) { | 3058 if (this.key) { |
| 4626 this._keyChanged(this.key); | 3059 this._keyChanged(this.key); |
| 4627 } | 3060 } |
| 4628 }, | 3061 }, |
| 4629 | 3062 |
| 4630 /** | |
| 4631 * Retrieves meta data value by key. | |
| 4632 * @param {string} key The key of the meta-data to be returned. | |
| 4633 * @return {*} | |
| 4634 */ | |
| 4635 byKey: function(key) { | 3063 byKey: function(key) { |
| 4636 return this._metaData && this._metaData[key]; | 3064 return this._metaData && this._metaData[key]; |
| 4637 } | 3065 } |
| 4638 | 3066 |
| 4639 }); | 3067 }); |
| 4640 | 3068 |
| 4641 })(); | 3069 })(); |
| 4642 Polymer({ | 3070 Polymer({ |
| 4643 | 3071 |
| 4644 is: 'iron-icon', | 3072 is: 'iron-icon', |
| 4645 | 3073 |
| 4646 properties: { | 3074 properties: { |
| 4647 | 3075 |
| 4648 /** | |
| 4649 * The name of the icon to use. The name should be of the form: | |
| 4650 * `iconset_name:icon_name`. | |
| 4651 */ | |
| 4652 icon: { | 3076 icon: { |
| 4653 type: String, | 3077 type: String, |
| 4654 observer: '_iconChanged' | 3078 observer: '_iconChanged' |
| 4655 }, | 3079 }, |
| 4656 | 3080 |
| 4657 /** | |
| 4658 * The name of the theme to used, if one is specified by the | |
| 4659 * iconset. | |
| 4660 */ | |
| 4661 theme: { | 3081 theme: { |
| 4662 type: String, | 3082 type: String, |
| 4663 observer: '_updateIcon' | 3083 observer: '_updateIcon' |
| 4664 }, | 3084 }, |
| 4665 | 3085 |
| 4666 /** | |
| 4667 * If using iron-icon without an iconset, you can set the src to be | |
| 4668 * the URL of an individual icon image file. Note that this will take | |
| 4669 * precedence over a given icon attribute. | |
| 4670 */ | |
| 4671 src: { | 3086 src: { |
| 4672 type: String, | 3087 type: String, |
| 4673 observer: '_srcChanged' | 3088 observer: '_srcChanged' |
| 4674 }, | 3089 }, |
| 4675 | 3090 |
| 4676 /** | |
| 4677 * @type {!Polymer.IronMeta} | |
| 4678 */ | |
| 4679 _meta: { | 3091 _meta: { |
| 4680 value: Polymer.Base.create('iron-meta', {type: 'iconset'}), | 3092 value: Polymer.Base.create('iron-meta', {type: 'iconset'}), |
| 4681 observer: '_updateIcon' | 3093 observer: '_updateIcon' |
| 4682 } | 3094 } |
| 4683 | 3095 |
| 4684 }, | 3096 }, |
| 4685 | 3097 |
| 4686 _DEFAULT_ICONSET: 'icons', | 3098 _DEFAULT_ICONSET: 'icons', |
| 4687 | 3099 |
| 4688 _iconChanged: function(icon) { | 3100 _iconChanged: function(icon) { |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4729 this._img.style.width = '100%'; | 3141 this._img.style.width = '100%'; |
| 4730 this._img.style.height = '100%'; | 3142 this._img.style.height = '100%'; |
| 4731 this._img.draggable = false; | 3143 this._img.draggable = false; |
| 4732 } | 3144 } |
| 4733 this._img.src = this.src; | 3145 this._img.src = this.src; |
| 4734 Polymer.dom(this.root).appendChild(this._img); | 3146 Polymer.dom(this.root).appendChild(this._img); |
| 4735 } | 3147 } |
| 4736 } | 3148 } |
| 4737 | 3149 |
| 4738 }); | 3150 }); |
| 4739 /** | |
| 4740 * @demo demo/index.html | |
| 4741 * @polymerBehavior | |
| 4742 */ | |
| 4743 Polymer.IronControlState = { | 3151 Polymer.IronControlState = { |
| 4744 | 3152 |
| 4745 properties: { | 3153 properties: { |
| 4746 | 3154 |
| 4747 /** | |
| 4748 * If true, the element currently has focus. | |
| 4749 */ | |
| 4750 focused: { | 3155 focused: { |
| 4751 type: Boolean, | 3156 type: Boolean, |
| 4752 value: false, | 3157 value: false, |
| 4753 notify: true, | 3158 notify: true, |
| 4754 readOnly: true, | 3159 readOnly: true, |
| 4755 reflectToAttribute: true | 3160 reflectToAttribute: true |
| 4756 }, | 3161 }, |
| 4757 | 3162 |
| 4758 /** | |
| 4759 * If true, the user cannot interact with this element. | |
| 4760 */ | |
| 4761 disabled: { | 3163 disabled: { |
| 4762 type: Boolean, | 3164 type: Boolean, |
| 4763 value: false, | 3165 value: false, |
| 4764 notify: true, | 3166 notify: true, |
| 4765 observer: '_disabledChanged', | 3167 observer: '_disabledChanged', |
| 4766 reflectToAttribute: true | 3168 reflectToAttribute: true |
| 4767 }, | 3169 }, |
| 4768 | 3170 |
| 4769 _oldTabIndex: { | 3171 _oldTabIndex: { |
| 4770 type: Number | 3172 type: Number |
| (...skipping 11 matching lines...) Expand all Loading... |
| 4782 observers: [ | 3184 observers: [ |
| 4783 '_changedControlState(focused, disabled)' | 3185 '_changedControlState(focused, disabled)' |
| 4784 ], | 3186 ], |
| 4785 | 3187 |
| 4786 ready: function() { | 3188 ready: function() { |
| 4787 this.addEventListener('focus', this._boundFocusBlurHandler, true); | 3189 this.addEventListener('focus', this._boundFocusBlurHandler, true); |
| 4788 this.addEventListener('blur', this._boundFocusBlurHandler, true); | 3190 this.addEventListener('blur', this._boundFocusBlurHandler, true); |
| 4789 }, | 3191 }, |
| 4790 | 3192 |
| 4791 _focusBlurHandler: function(event) { | 3193 _focusBlurHandler: function(event) { |
| 4792 // NOTE(cdata): if we are in ShadowDOM land, `event.target` will | |
| 4793 // eventually become `this` due to retargeting; if we are not in | |
| 4794 // ShadowDOM land, `event.target` will eventually become `this` due | |
| 4795 // to the second conditional which fires a synthetic event (that is also | |
| 4796 // handled). In either case, we can disregard `event.path`. | |
| 4797 | 3194 |
| 4798 if (event.target === this) { | 3195 if (event.target === this) { |
| 4799 this._setFocused(event.type === 'focus'); | 3196 this._setFocused(event.type === 'focus'); |
| 4800 } else if (!this.shadowRoot) { | 3197 } else if (!this.shadowRoot) { |
| 4801 var target = /** @type {Node} */(Polymer.dom(event).localTarget); | 3198 var target = /** @type {Node} */(Polymer.dom(event).localTarget); |
| 4802 if (!this.isLightDescendant(target)) { | 3199 if (!this.isLightDescendant(target)) { |
| 4803 this.fire(event.type, {sourceEvent: event}, { | 3200 this.fire(event.type, {sourceEvent: event}, { |
| 4804 node: this, | 3201 node: this, |
| 4805 bubbles: event.bubbles, | 3202 bubbles: event.bubbles, |
| 4806 cancelable: event.cancelable | 3203 cancelable: event.cancelable |
| 4807 }); | 3204 }); |
| 4808 } | 3205 } |
| 4809 } | 3206 } |
| 4810 }, | 3207 }, |
| 4811 | 3208 |
| 4812 _disabledChanged: function(disabled, old) { | 3209 _disabledChanged: function(disabled, old) { |
| 4813 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); | 3210 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
| 4814 this.style.pointerEvents = disabled ? 'none' : ''; | 3211 this.style.pointerEvents = disabled ? 'none' : ''; |
| 4815 if (disabled) { | 3212 if (disabled) { |
| 4816 this._oldTabIndex = this.tabIndex; | 3213 this._oldTabIndex = this.tabIndex; |
| 4817 this._setFocused(false); | 3214 this._setFocused(false); |
| 4818 this.tabIndex = -1; | 3215 this.tabIndex = -1; |
| 4819 this.blur(); | 3216 this.blur(); |
| 4820 } else if (this._oldTabIndex !== undefined) { | 3217 } else if (this._oldTabIndex !== undefined) { |
| 4821 this.tabIndex = this._oldTabIndex; | 3218 this.tabIndex = this._oldTabIndex; |
| 4822 } | 3219 } |
| 4823 }, | 3220 }, |
| 4824 | 3221 |
| 4825 _changedControlState: function() { | 3222 _changedControlState: function() { |
| 4826 // _controlStateChanged is abstract, follow-on behaviors may implement it | |
| 4827 if (this._controlStateChanged) { | 3223 if (this._controlStateChanged) { |
| 4828 this._controlStateChanged(); | 3224 this._controlStateChanged(); |
| 4829 } | 3225 } |
| 4830 } | 3226 } |
| 4831 | 3227 |
| 4832 }; | 3228 }; |
| 4833 /** | |
| 4834 * @demo demo/index.html | |
| 4835 * @polymerBehavior Polymer.IronButtonState | |
| 4836 */ | |
| 4837 Polymer.IronButtonStateImpl = { | 3229 Polymer.IronButtonStateImpl = { |
| 4838 | 3230 |
| 4839 properties: { | 3231 properties: { |
| 4840 | 3232 |
| 4841 /** | |
| 4842 * If true, the user is currently holding down the button. | |
| 4843 */ | |
| 4844 pressed: { | 3233 pressed: { |
| 4845 type: Boolean, | 3234 type: Boolean, |
| 4846 readOnly: true, | 3235 readOnly: true, |
| 4847 value: false, | 3236 value: false, |
| 4848 reflectToAttribute: true, | 3237 reflectToAttribute: true, |
| 4849 observer: '_pressedChanged' | 3238 observer: '_pressedChanged' |
| 4850 }, | 3239 }, |
| 4851 | 3240 |
| 4852 /** | |
| 4853 * If true, the button toggles the active state with each tap or press | |
| 4854 * of the spacebar. | |
| 4855 */ | |
| 4856 toggles: { | 3241 toggles: { |
| 4857 type: Boolean, | 3242 type: Boolean, |
| 4858 value: false, | 3243 value: false, |
| 4859 reflectToAttribute: true | 3244 reflectToAttribute: true |
| 4860 }, | 3245 }, |
| 4861 | 3246 |
| 4862 /** | |
| 4863 * If true, the button is a toggle and is currently in the active state. | |
| 4864 */ | |
| 4865 active: { | 3247 active: { |
| 4866 type: Boolean, | 3248 type: Boolean, |
| 4867 value: false, | 3249 value: false, |
| 4868 notify: true, | 3250 notify: true, |
| 4869 reflectToAttribute: true | 3251 reflectToAttribute: true |
| 4870 }, | 3252 }, |
| 4871 | 3253 |
| 4872 /** | |
| 4873 * True if the element is currently being pressed by a "pointer," which | |
| 4874 * is loosely defined as mouse or touch input (but specifically excluding | |
| 4875 * keyboard input). | |
| 4876 */ | |
| 4877 pointerDown: { | 3254 pointerDown: { |
| 4878 type: Boolean, | 3255 type: Boolean, |
| 4879 readOnly: true, | 3256 readOnly: true, |
| 4880 value: false | 3257 value: false |
| 4881 }, | 3258 }, |
| 4882 | 3259 |
| 4883 /** | |
| 4884 * True if the input device that caused the element to receive focus | |
| 4885 * was a keyboard. | |
| 4886 */ | |
| 4887 receivedFocusFromKeyboard: { | 3260 receivedFocusFromKeyboard: { |
| 4888 type: Boolean, | 3261 type: Boolean, |
| 4889 readOnly: true | 3262 readOnly: true |
| 4890 }, | 3263 }, |
| 4891 | 3264 |
| 4892 /** | |
| 4893 * The aria attribute to be set if the button is a toggle and in the | |
| 4894 * active state. | |
| 4895 */ | |
| 4896 ariaActiveAttribute: { | 3265 ariaActiveAttribute: { |
| 4897 type: String, | 3266 type: String, |
| 4898 value: 'aria-pressed', | 3267 value: 'aria-pressed', |
| 4899 observer: '_ariaActiveAttributeChanged' | 3268 observer: '_ariaActiveAttributeChanged' |
| 4900 } | 3269 } |
| 4901 }, | 3270 }, |
| 4902 | 3271 |
| 4903 listeners: { | 3272 listeners: { |
| 4904 down: '_downHandler', | 3273 down: '_downHandler', |
| 4905 up: '_upHandler', | 3274 up: '_upHandler', |
| 4906 tap: '_tapHandler' | 3275 tap: '_tapHandler' |
| 4907 }, | 3276 }, |
| 4908 | 3277 |
| 4909 observers: [ | 3278 observers: [ |
| 4910 '_detectKeyboardFocus(focused)', | 3279 '_detectKeyboardFocus(focused)', |
| 4911 '_activeChanged(active, ariaActiveAttribute)' | 3280 '_activeChanged(active, ariaActiveAttribute)' |
| 4912 ], | 3281 ], |
| 4913 | 3282 |
| 4914 keyBindings: { | 3283 keyBindings: { |
| 4915 'enter:keydown': '_asyncClick', | 3284 'enter:keydown': '_asyncClick', |
| 4916 'space:keydown': '_spaceKeyDownHandler', | 3285 'space:keydown': '_spaceKeyDownHandler', |
| 4917 'space:keyup': '_spaceKeyUpHandler', | 3286 'space:keyup': '_spaceKeyUpHandler', |
| 4918 }, | 3287 }, |
| 4919 | 3288 |
| 4920 _mouseEventRe: /^mouse/, | 3289 _mouseEventRe: /^mouse/, |
| 4921 | 3290 |
| 4922 _tapHandler: function() { | 3291 _tapHandler: function() { |
| 4923 if (this.toggles) { | 3292 if (this.toggles) { |
| 4924 // a tap is needed to toggle the active state | |
| 4925 this._userActivate(!this.active); | 3293 this._userActivate(!this.active); |
| 4926 } else { | 3294 } else { |
| 4927 this.active = false; | 3295 this.active = false; |
| 4928 } | 3296 } |
| 4929 }, | 3297 }, |
| 4930 | 3298 |
| 4931 _detectKeyboardFocus: function(focused) { | 3299 _detectKeyboardFocus: function(focused) { |
| 4932 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); | 3300 this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); |
| 4933 }, | 3301 }, |
| 4934 | 3302 |
| 4935 // to emulate native checkbox, (de-)activations from a user interaction fire | |
| 4936 // 'change' events | |
| 4937 _userActivate: function(active) { | 3303 _userActivate: function(active) { |
| 4938 if (this.active !== active) { | 3304 if (this.active !== active) { |
| 4939 this.active = active; | 3305 this.active = active; |
| 4940 this.fire('change'); | 3306 this.fire('change'); |
| 4941 } | 3307 } |
| 4942 }, | 3308 }, |
| 4943 | 3309 |
| 4944 _downHandler: function(event) { | 3310 _downHandler: function(event) { |
| 4945 this._setPointerDown(true); | 3311 this._setPointerDown(true); |
| 4946 this._setPressed(true); | 3312 this._setPressed(true); |
| 4947 this._setReceivedFocusFromKeyboard(false); | 3313 this._setReceivedFocusFromKeyboard(false); |
| 4948 }, | 3314 }, |
| 4949 | 3315 |
| 4950 _upHandler: function() { | 3316 _upHandler: function() { |
| 4951 this._setPointerDown(false); | 3317 this._setPointerDown(false); |
| 4952 this._setPressed(false); | 3318 this._setPressed(false); |
| 4953 }, | 3319 }, |
| 4954 | 3320 |
| 4955 /** | |
| 4956 * @param {!KeyboardEvent} event . | |
| 4957 */ | |
| 4958 _spaceKeyDownHandler: function(event) { | 3321 _spaceKeyDownHandler: function(event) { |
| 4959 var keyboardEvent = event.detail.keyboardEvent; | 3322 var keyboardEvent = event.detail.keyboardEvent; |
| 4960 var target = Polymer.dom(keyboardEvent).localTarget; | 3323 var target = Polymer.dom(keyboardEvent).localTarget; |
| 4961 | 3324 |
| 4962 // Ignore the event if this is coming from a focused light child, since th
at | |
| 4963 // element will deal with it. | |
| 4964 if (this.isLightDescendant(/** @type {Node} */(target))) | 3325 if (this.isLightDescendant(/** @type {Node} */(target))) |
| 4965 return; | 3326 return; |
| 4966 | 3327 |
| 4967 keyboardEvent.preventDefault(); | 3328 keyboardEvent.preventDefault(); |
| 4968 keyboardEvent.stopImmediatePropagation(); | 3329 keyboardEvent.stopImmediatePropagation(); |
| 4969 this._setPressed(true); | 3330 this._setPressed(true); |
| 4970 }, | 3331 }, |
| 4971 | 3332 |
| 4972 /** | |
| 4973 * @param {!KeyboardEvent} event . | |
| 4974 */ | |
| 4975 _spaceKeyUpHandler: function(event) { | 3333 _spaceKeyUpHandler: function(event) { |
| 4976 var keyboardEvent = event.detail.keyboardEvent; | 3334 var keyboardEvent = event.detail.keyboardEvent; |
| 4977 var target = Polymer.dom(keyboardEvent).localTarget; | 3335 var target = Polymer.dom(keyboardEvent).localTarget; |
| 4978 | 3336 |
| 4979 // Ignore the event if this is coming from a focused light child, since th
at | |
| 4980 // element will deal with it. | |
| 4981 if (this.isLightDescendant(/** @type {Node} */(target))) | 3337 if (this.isLightDescendant(/** @type {Node} */(target))) |
| 4982 return; | 3338 return; |
| 4983 | 3339 |
| 4984 if (this.pressed) { | 3340 if (this.pressed) { |
| 4985 this._asyncClick(); | 3341 this._asyncClick(); |
| 4986 } | 3342 } |
| 4987 this._setPressed(false); | 3343 this._setPressed(false); |
| 4988 }, | 3344 }, |
| 4989 | 3345 |
| 4990 // trigger click asynchronously, the asynchrony is useful to allow one | |
| 4991 // event handler to unwind before triggering another event | |
| 4992 _asyncClick: function() { | 3346 _asyncClick: function() { |
| 4993 this.async(function() { | 3347 this.async(function() { |
| 4994 this.click(); | 3348 this.click(); |
| 4995 }, 1); | 3349 }, 1); |
| 4996 }, | 3350 }, |
| 4997 | 3351 |
| 4998 // any of these changes are considered a change to button state | |
| 4999 | 3352 |
| 5000 _pressedChanged: function(pressed) { | 3353 _pressedChanged: function(pressed) { |
| 5001 this._changedButtonState(); | 3354 this._changedButtonState(); |
| 5002 }, | 3355 }, |
| 5003 | 3356 |
| 5004 _ariaActiveAttributeChanged: function(value, oldValue) { | 3357 _ariaActiveAttributeChanged: function(value, oldValue) { |
| 5005 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { | 3358 if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { |
| 5006 this.removeAttribute(oldValue); | 3359 this.removeAttribute(oldValue); |
| 5007 } | 3360 } |
| 5008 }, | 3361 }, |
| 5009 | 3362 |
| 5010 _activeChanged: function(active, ariaActiveAttribute) { | 3363 _activeChanged: function(active, ariaActiveAttribute) { |
| 5011 if (this.toggles) { | 3364 if (this.toggles) { |
| 5012 this.setAttribute(this.ariaActiveAttribute, | 3365 this.setAttribute(this.ariaActiveAttribute, |
| 5013 active ? 'true' : 'false'); | 3366 active ? 'true' : 'false'); |
| 5014 } else { | 3367 } else { |
| 5015 this.removeAttribute(this.ariaActiveAttribute); | 3368 this.removeAttribute(this.ariaActiveAttribute); |
| 5016 } | 3369 } |
| 5017 this._changedButtonState(); | 3370 this._changedButtonState(); |
| 5018 }, | 3371 }, |
| 5019 | 3372 |
| 5020 _controlStateChanged: function() { | 3373 _controlStateChanged: function() { |
| 5021 if (this.disabled) { | 3374 if (this.disabled) { |
| 5022 this._setPressed(false); | 3375 this._setPressed(false); |
| 5023 } else { | 3376 } else { |
| 5024 this._changedButtonState(); | 3377 this._changedButtonState(); |
| 5025 } | 3378 } |
| 5026 }, | 3379 }, |
| 5027 | 3380 |
| 5028 // provide hook for follow-on behaviors to react to button-state | |
| 5029 | 3381 |
| 5030 _changedButtonState: function() { | 3382 _changedButtonState: function() { |
| 5031 if (this._buttonStateChanged) { | 3383 if (this._buttonStateChanged) { |
| 5032 this._buttonStateChanged(); // abstract | 3384 this._buttonStateChanged(); // abstract |
| 5033 } | 3385 } |
| 5034 } | 3386 } |
| 5035 | 3387 |
| 5036 }; | 3388 }; |
| 5037 | 3389 |
| 5038 /** @polymerBehavior */ | 3390 /** @polymerBehavior */ |
| 5039 Polymer.IronButtonState = [ | 3391 Polymer.IronButtonState = [ |
| 5040 Polymer.IronA11yKeysBehavior, | 3392 Polymer.IronA11yKeysBehavior, |
| 5041 Polymer.IronButtonStateImpl | 3393 Polymer.IronButtonStateImpl |
| 5042 ]; | 3394 ]; |
| 5043 (function() { | 3395 (function() { |
| 5044 var Utility = { | 3396 var Utility = { |
| 5045 distance: function(x1, y1, x2, y2) { | 3397 distance: function(x1, y1, x2, y2) { |
| 5046 var xDelta = (x1 - x2); | 3398 var xDelta = (x1 - x2); |
| 5047 var yDelta = (y1 - y2); | 3399 var yDelta = (y1 - y2); |
| 5048 | 3400 |
| 5049 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); | 3401 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); |
| 5050 }, | 3402 }, |
| 5051 | 3403 |
| 5052 now: window.performance && window.performance.now ? | 3404 now: window.performance && window.performance.now ? |
| 5053 window.performance.now.bind(window.performance) : Date.now | 3405 window.performance.now.bind(window.performance) : Date.now |
| 5054 }; | 3406 }; |
| 5055 | 3407 |
| 5056 /** | |
| 5057 * @param {HTMLElement} element | |
| 5058 * @constructor | |
| 5059 */ | |
| 5060 function ElementMetrics(element) { | 3408 function ElementMetrics(element) { |
| 5061 this.element = element; | 3409 this.element = element; |
| 5062 this.width = this.boundingRect.width; | 3410 this.width = this.boundingRect.width; |
| 5063 this.height = this.boundingRect.height; | 3411 this.height = this.boundingRect.height; |
| 5064 | 3412 |
| 5065 this.size = Math.max(this.width, this.height); | 3413 this.size = Math.max(this.width, this.height); |
| 5066 } | 3414 } |
| 5067 | 3415 |
| 5068 ElementMetrics.prototype = { | 3416 ElementMetrics.prototype = { |
| 5069 get boundingRect () { | 3417 get boundingRect () { |
| 5070 return this.element.getBoundingClientRect(); | 3418 return this.element.getBoundingClientRect(); |
| 5071 }, | 3419 }, |
| 5072 | 3420 |
| 5073 furthestCornerDistanceFrom: function(x, y) { | 3421 furthestCornerDistanceFrom: function(x, y) { |
| 5074 var topLeft = Utility.distance(x, y, 0, 0); | 3422 var topLeft = Utility.distance(x, y, 0, 0); |
| 5075 var topRight = Utility.distance(x, y, this.width, 0); | 3423 var topRight = Utility.distance(x, y, this.width, 0); |
| 5076 var bottomLeft = Utility.distance(x, y, 0, this.height); | 3424 var bottomLeft = Utility.distance(x, y, 0, this.height); |
| 5077 var bottomRight = Utility.distance(x, y, this.width, this.height); | 3425 var bottomRight = Utility.distance(x, y, this.width, this.height); |
| 5078 | 3426 |
| 5079 return Math.max(topLeft, topRight, bottomLeft, bottomRight); | 3427 return Math.max(topLeft, topRight, bottomLeft, bottomRight); |
| 5080 } | 3428 } |
| 5081 }; | 3429 }; |
| 5082 | 3430 |
| 5083 /** | |
| 5084 * @param {HTMLElement} element | |
| 5085 * @constructor | |
| 5086 */ | |
| 5087 function Ripple(element) { | 3431 function Ripple(element) { |
| 5088 this.element = element; | 3432 this.element = element; |
| 5089 this.color = window.getComputedStyle(element).color; | 3433 this.color = window.getComputedStyle(element).color; |
| 5090 | 3434 |
| 5091 this.wave = document.createElement('div'); | 3435 this.wave = document.createElement('div'); |
| 5092 this.waveContainer = document.createElement('div'); | 3436 this.waveContainer = document.createElement('div'); |
| 5093 this.wave.style.backgroundColor = this.color; | 3437 this.wave.style.backgroundColor = this.color; |
| 5094 this.wave.classList.add('wave'); | 3438 this.wave.classList.add('wave'); |
| 5095 this.waveContainer.classList.add('wave-container'); | 3439 this.waveContainer.classList.add('wave-container'); |
| 5096 Polymer.dom(this.waveContainer).appendChild(this.wave); | 3440 Polymer.dom(this.waveContainer).appendChild(this.wave); |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5170 return this.initialOpacity; | 3514 return this.initialOpacity; |
| 5171 } | 3515 } |
| 5172 | 3516 |
| 5173 return Math.max( | 3517 return Math.max( |
| 5174 0, | 3518 0, |
| 5175 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe
locity | 3519 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe
locity |
| 5176 ); | 3520 ); |
| 5177 }, | 3521 }, |
| 5178 | 3522 |
| 5179 get outerOpacity() { | 3523 get outerOpacity() { |
| 5180 // Linear increase in background opacity, capped at the opacity | |
| 5181 // of the wavefront (waveOpacity). | |
| 5182 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; | 3524 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; |
| 5183 var waveOpacity = this.opacity; | 3525 var waveOpacity = this.opacity; |
| 5184 | 3526 |
| 5185 return Math.max( | 3527 return Math.max( |
| 5186 0, | 3528 0, |
| 5187 Math.min(outerOpacity, waveOpacity) | 3529 Math.min(outerOpacity, waveOpacity) |
| 5188 ); | 3530 ); |
| 5189 }, | 3531 }, |
| 5190 | 3532 |
| 5191 get isOpacityFullyDecayed() { | 3533 get isOpacityFullyDecayed() { |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5250 var dx; | 3592 var dx; |
| 5251 var dy; | 3593 var dy; |
| 5252 | 3594 |
| 5253 this.wave.style.opacity = this.opacity; | 3595 this.wave.style.opacity = this.opacity; |
| 5254 | 3596 |
| 5255 scale = this.radius / (this.containerMetrics.size / 2); | 3597 scale = this.radius / (this.containerMetrics.size / 2); |
| 5256 dx = this.xNow - (this.containerMetrics.width / 2); | 3598 dx = this.xNow - (this.containerMetrics.width / 2); |
| 5257 dy = this.yNow - (this.containerMetrics.height / 2); | 3599 dy = this.yNow - (this.containerMetrics.height / 2); |
| 5258 | 3600 |
| 5259 | 3601 |
| 5260 // 2d transform for safari because of border-radius and overflow:hidden
clipping bug. | |
| 5261 // https://bugs.webkit.org/show_bug.cgi?id=98538 | |
| 5262 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' +
dy + 'px)'; | 3602 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' +
dy + 'px)'; |
| 5263 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy +
'px, 0)'; | 3603 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy +
'px, 0)'; |
| 5264 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; | 3604 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; |
| 5265 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; | 3605 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; |
| 5266 }, | 3606 }, |
| 5267 | 3607 |
| 5268 /** @param {Event=} event */ | 3608 /** @param {Event=} event */ |
| 5269 downAction: function(event) { | 3609 downAction: function(event) { |
| 5270 var xCenter = this.containerMetrics.width / 2; | 3610 var xCenter = this.containerMetrics.width / 2; |
| 5271 var yCenter = this.containerMetrics.height / 2; | 3611 var yCenter = this.containerMetrics.height / 2; |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5327 }; | 3667 }; |
| 5328 | 3668 |
| 5329 Polymer({ | 3669 Polymer({ |
| 5330 is: 'paper-ripple', | 3670 is: 'paper-ripple', |
| 5331 | 3671 |
| 5332 behaviors: [ | 3672 behaviors: [ |
| 5333 Polymer.IronA11yKeysBehavior | 3673 Polymer.IronA11yKeysBehavior |
| 5334 ], | 3674 ], |
| 5335 | 3675 |
| 5336 properties: { | 3676 properties: { |
| 5337 /** | |
| 5338 * The initial opacity set on the wave. | |
| 5339 * | |
| 5340 * @attribute initialOpacity | |
| 5341 * @type number | |
| 5342 * @default 0.25 | |
| 5343 */ | |
| 5344 initialOpacity: { | 3677 initialOpacity: { |
| 5345 type: Number, | 3678 type: Number, |
| 5346 value: 0.25 | 3679 value: 0.25 |
| 5347 }, | 3680 }, |
| 5348 | 3681 |
| 5349 /** | |
| 5350 * How fast (opacity per second) the wave fades out. | |
| 5351 * | |
| 5352 * @attribute opacityDecayVelocity | |
| 5353 * @type number | |
| 5354 * @default 0.8 | |
| 5355 */ | |
| 5356 opacityDecayVelocity: { | 3682 opacityDecayVelocity: { |
| 5357 type: Number, | 3683 type: Number, |
| 5358 value: 0.8 | 3684 value: 0.8 |
| 5359 }, | 3685 }, |
| 5360 | 3686 |
| 5361 /** | |
| 5362 * If true, ripples will exhibit a gravitational pull towards | |
| 5363 * the center of their container as they fade away. | |
| 5364 * | |
| 5365 * @attribute recenters | |
| 5366 * @type boolean | |
| 5367 * @default false | |
| 5368 */ | |
| 5369 recenters: { | 3687 recenters: { |
| 5370 type: Boolean, | 3688 type: Boolean, |
| 5371 value: false | 3689 value: false |
| 5372 }, | 3690 }, |
| 5373 | 3691 |
| 5374 /** | |
| 5375 * If true, ripples will center inside its container | |
| 5376 * | |
| 5377 * @attribute recenters | |
| 5378 * @type boolean | |
| 5379 * @default false | |
| 5380 */ | |
| 5381 center: { | 3692 center: { |
| 5382 type: Boolean, | 3693 type: Boolean, |
| 5383 value: false | 3694 value: false |
| 5384 }, | 3695 }, |
| 5385 | 3696 |
| 5386 /** | |
| 5387 * A list of the visual ripples. | |
| 5388 * | |
| 5389 * @attribute ripples | |
| 5390 * @type Array | |
| 5391 * @default [] | |
| 5392 */ | |
| 5393 ripples: { | 3697 ripples: { |
| 5394 type: Array, | 3698 type: Array, |
| 5395 value: function() { | 3699 value: function() { |
| 5396 return []; | 3700 return []; |
| 5397 } | 3701 } |
| 5398 }, | 3702 }, |
| 5399 | 3703 |
| 5400 /** | |
| 5401 * True when there are visible ripples animating within the | |
| 5402 * element. | |
| 5403 */ | |
| 5404 animating: { | 3704 animating: { |
| 5405 type: Boolean, | 3705 type: Boolean, |
| 5406 readOnly: true, | 3706 readOnly: true, |
| 5407 reflectToAttribute: true, | 3707 reflectToAttribute: true, |
| 5408 value: false | 3708 value: false |
| 5409 }, | 3709 }, |
| 5410 | 3710 |
| 5411 /** | |
| 5412 * If true, the ripple will remain in the "down" state until `holdDown` | |
| 5413 * is set to false again. | |
| 5414 */ | |
| 5415 holdDown: { | 3711 holdDown: { |
| 5416 type: Boolean, | 3712 type: Boolean, |
| 5417 value: false, | 3713 value: false, |
| 5418 observer: '_holdDownChanged' | 3714 observer: '_holdDownChanged' |
| 5419 }, | 3715 }, |
| 5420 | 3716 |
| 5421 /** | |
| 5422 * If true, the ripple will not generate a ripple effect | |
| 5423 * via pointer interaction. | |
| 5424 * Calling ripple's imperative api like `simulatedRipple` will | |
| 5425 * still generate the ripple effect. | |
| 5426 */ | |
| 5427 noink: { | 3717 noink: { |
| 5428 type: Boolean, | 3718 type: Boolean, |
| 5429 value: false | 3719 value: false |
| 5430 }, | 3720 }, |
| 5431 | 3721 |
| 5432 _animating: { | 3722 _animating: { |
| 5433 type: Boolean | 3723 type: Boolean |
| 5434 }, | 3724 }, |
| 5435 | 3725 |
| 5436 _boundAnimate: { | 3726 _boundAnimate: { |
| 5437 type: Function, | 3727 type: Function, |
| 5438 value: function() { | 3728 value: function() { |
| 5439 return this.animate.bind(this); | 3729 return this.animate.bind(this); |
| 5440 } | 3730 } |
| 5441 } | 3731 } |
| 5442 }, | 3732 }, |
| 5443 | 3733 |
| 5444 get target () { | 3734 get target () { |
| 5445 return this.keyEventTarget; | 3735 return this.keyEventTarget; |
| 5446 }, | 3736 }, |
| 5447 | 3737 |
| 5448 keyBindings: { | 3738 keyBindings: { |
| 5449 'enter:keydown': '_onEnterKeydown', | 3739 'enter:keydown': '_onEnterKeydown', |
| 5450 'space:keydown': '_onSpaceKeydown', | 3740 'space:keydown': '_onSpaceKeydown', |
| 5451 'space:keyup': '_onSpaceKeyup' | 3741 'space:keyup': '_onSpaceKeyup' |
| 5452 }, | 3742 }, |
| 5453 | 3743 |
| 5454 attached: function() { | 3744 attached: function() { |
| 5455 // Set up a11yKeysBehavior to listen to key events on the target, | |
| 5456 // so that space and enter activate the ripple even if the target doesn'
t | |
| 5457 // handle key events. The key handlers deal with `noink` themselves. | |
| 5458 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE | 3745 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE |
| 5459 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host; | 3746 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host; |
| 5460 } else { | 3747 } else { |
| 5461 this.keyEventTarget = this.parentNode; | 3748 this.keyEventTarget = this.parentNode; |
| 5462 } | 3749 } |
| 5463 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); | 3750 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); |
| 5464 this.listen(keyEventTarget, 'up', 'uiUpAction'); | 3751 this.listen(keyEventTarget, 'up', 'uiUpAction'); |
| 5465 this.listen(keyEventTarget, 'down', 'uiDownAction'); | 3752 this.listen(keyEventTarget, 'down', 'uiDownAction'); |
| 5466 }, | 3753 }, |
| 5467 | 3754 |
| 5468 detached: function() { | 3755 detached: function() { |
| 5469 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); | 3756 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); |
| 5470 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); | 3757 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); |
| 5471 this.keyEventTarget = null; | 3758 this.keyEventTarget = null; |
| 5472 }, | 3759 }, |
| 5473 | 3760 |
| 5474 get shouldKeepAnimating () { | 3761 get shouldKeepAnimating () { |
| 5475 for (var index = 0; index < this.ripples.length; ++index) { | 3762 for (var index = 0; index < this.ripples.length; ++index) { |
| 5476 if (!this.ripples[index].isAnimationComplete) { | 3763 if (!this.ripples[index].isAnimationComplete) { |
| 5477 return true; | 3764 return true; |
| 5478 } | 3765 } |
| 5479 } | 3766 } |
| 5480 | 3767 |
| 5481 return false; | 3768 return false; |
| 5482 }, | 3769 }, |
| 5483 | 3770 |
| 5484 simulatedRipple: function() { | 3771 simulatedRipple: function() { |
| 5485 this.downAction(null); | 3772 this.downAction(null); |
| 5486 | 3773 |
| 5487 // Please see polymer/polymer#1305 | |
| 5488 this.async(function() { | 3774 this.async(function() { |
| 5489 this.upAction(); | 3775 this.upAction(); |
| 5490 }, 1); | 3776 }, 1); |
| 5491 }, | 3777 }, |
| 5492 | 3778 |
| 5493 /** | |
| 5494 * Provokes a ripple down effect via a UI event, | |
| 5495 * respecting the `noink` property. | |
| 5496 * @param {Event=} event | |
| 5497 */ | |
| 5498 uiDownAction: function(event) { | 3779 uiDownAction: function(event) { |
| 5499 if (!this.noink) { | 3780 if (!this.noink) { |
| 5500 this.downAction(event); | 3781 this.downAction(event); |
| 5501 } | 3782 } |
| 5502 }, | 3783 }, |
| 5503 | 3784 |
| 5504 /** | |
| 5505 * Provokes a ripple down effect via a UI event, | |
| 5506 * *not* respecting the `noink` property. | |
| 5507 * @param {Event=} event | |
| 5508 */ | |
| 5509 downAction: function(event) { | 3785 downAction: function(event) { |
| 5510 if (this.holdDown && this.ripples.length > 0) { | 3786 if (this.holdDown && this.ripples.length > 0) { |
| 5511 return; | 3787 return; |
| 5512 } | 3788 } |
| 5513 | 3789 |
| 5514 var ripple = this.addRipple(); | 3790 var ripple = this.addRipple(); |
| 5515 | 3791 |
| 5516 ripple.downAction(event); | 3792 ripple.downAction(event); |
| 5517 | 3793 |
| 5518 if (!this._animating) { | 3794 if (!this._animating) { |
| 5519 this._animating = true; | 3795 this._animating = true; |
| 5520 this.animate(); | 3796 this.animate(); |
| 5521 } | 3797 } |
| 5522 }, | 3798 }, |
| 5523 | 3799 |
| 5524 /** | |
| 5525 * Provokes a ripple up effect via a UI event, | |
| 5526 * respecting the `noink` property. | |
| 5527 * @param {Event=} event | |
| 5528 */ | |
| 5529 uiUpAction: function(event) { | 3800 uiUpAction: function(event) { |
| 5530 if (!this.noink) { | 3801 if (!this.noink) { |
| 5531 this.upAction(event); | 3802 this.upAction(event); |
| 5532 } | 3803 } |
| 5533 }, | 3804 }, |
| 5534 | 3805 |
| 5535 /** | |
| 5536 * Provokes a ripple up effect via a UI event, | |
| 5537 * *not* respecting the `noink` property. | |
| 5538 * @param {Event=} event | |
| 5539 */ | |
| 5540 upAction: function(event) { | 3806 upAction: function(event) { |
| 5541 if (this.holdDown) { | 3807 if (this.holdDown) { |
| 5542 return; | 3808 return; |
| 5543 } | 3809 } |
| 5544 | 3810 |
| 5545 this.ripples.forEach(function(ripple) { | 3811 this.ripples.forEach(function(ripple) { |
| 5546 ripple.upAction(event); | 3812 ripple.upAction(event); |
| 5547 }); | 3813 }); |
| 5548 | 3814 |
| 5549 this._animating = true; | 3815 this._animating = true; |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5616 }, | 3882 }, |
| 5617 | 3883 |
| 5618 _onSpaceKeydown: function() { | 3884 _onSpaceKeydown: function() { |
| 5619 this.uiDownAction(); | 3885 this.uiDownAction(); |
| 5620 }, | 3886 }, |
| 5621 | 3887 |
| 5622 _onSpaceKeyup: function() { | 3888 _onSpaceKeyup: function() { |
| 5623 this.uiUpAction(); | 3889 this.uiUpAction(); |
| 5624 }, | 3890 }, |
| 5625 | 3891 |
| 5626 // note: holdDown does not respect noink since it can be a focus based | |
| 5627 // effect. | |
| 5628 _holdDownChanged: function(newVal, oldVal) { | 3892 _holdDownChanged: function(newVal, oldVal) { |
| 5629 if (oldVal === undefined) { | 3893 if (oldVal === undefined) { |
| 5630 return; | 3894 return; |
| 5631 } | 3895 } |
| 5632 if (newVal) { | 3896 if (newVal) { |
| 5633 this.downAction(); | 3897 this.downAction(); |
| 5634 } else { | 3898 } else { |
| 5635 this.upAction(); | 3899 this.upAction(); |
| 5636 } | 3900 } |
| 5637 } | 3901 } |
| 5638 | 3902 |
| 5639 /** | |
| 5640 Fired when the animation finishes. | |
| 5641 This is useful if you want to wait until | |
| 5642 the ripple animation finishes to perform some action. | |
| 5643 | |
| 5644 @event transitionend | |
| 5645 @param {{node: Object}} detail Contains the animated node. | |
| 5646 */ | |
| 5647 }); | 3903 }); |
| 5648 })(); | 3904 })(); |
| 5649 /** | |
| 5650 * `Polymer.PaperRippleBehavior` dynamically implements a ripple | |
| 5651 * when the element has focus via pointer or keyboard. | |
| 5652 * | |
| 5653 * NOTE: This behavior is intended to be used in conjunction with and after | |
| 5654 * `Polymer.IronButtonState` and `Polymer.IronControlState`. | |
| 5655 * | |
| 5656 * @polymerBehavior Polymer.PaperRippleBehavior | |
| 5657 */ | |
| 5658 Polymer.PaperRippleBehavior = { | 3905 Polymer.PaperRippleBehavior = { |
| 5659 properties: { | 3906 properties: { |
| 5660 /** | |
| 5661 * If true, the element will not produce a ripple effect when interacted | |
| 5662 * with via the pointer. | |
| 5663 */ | |
| 5664 noink: { | 3907 noink: { |
| 5665 type: Boolean, | 3908 type: Boolean, |
| 5666 observer: '_noinkChanged' | 3909 observer: '_noinkChanged' |
| 5667 }, | 3910 }, |
| 5668 | 3911 |
| 5669 /** | |
| 5670 * @type {Element|undefined} | |
| 5671 */ | |
| 5672 _rippleContainer: { | 3912 _rippleContainer: { |
| 5673 type: Object, | 3913 type: Object, |
| 5674 } | 3914 } |
| 5675 }, | 3915 }, |
| 5676 | 3916 |
| 5677 /** | |
| 5678 * Ensures a `<paper-ripple>` element is available when the element is | |
| 5679 * focused. | |
| 5680 */ | |
| 5681 _buttonStateChanged: function() { | 3917 _buttonStateChanged: function() { |
| 5682 if (this.focused) { | 3918 if (this.focused) { |
| 5683 this.ensureRipple(); | 3919 this.ensureRipple(); |
| 5684 } | 3920 } |
| 5685 }, | 3921 }, |
| 5686 | 3922 |
| 5687 /** | |
| 5688 * In addition to the functionality provided in `IronButtonState`, ensures | |
| 5689 * a ripple effect is created when the element is in a `pressed` state. | |
| 5690 */ | |
| 5691 _downHandler: function(event) { | 3923 _downHandler: function(event) { |
| 5692 Polymer.IronButtonStateImpl._downHandler.call(this, event); | 3924 Polymer.IronButtonStateImpl._downHandler.call(this, event); |
| 5693 if (this.pressed) { | 3925 if (this.pressed) { |
| 5694 this.ensureRipple(event); | 3926 this.ensureRipple(event); |
| 5695 } | 3927 } |
| 5696 }, | 3928 }, |
| 5697 | 3929 |
| 5698 /** | |
| 5699 * Ensures this element contains a ripple effect. For startup efficiency | |
| 5700 * the ripple effect is dynamically on demand when needed. | |
| 5701 * @param {!Event=} optTriggeringEvent (optional) event that triggered the | |
| 5702 * ripple. | |
| 5703 */ | |
| 5704 ensureRipple: function(optTriggeringEvent) { | 3930 ensureRipple: function(optTriggeringEvent) { |
| 5705 if (!this.hasRipple()) { | 3931 if (!this.hasRipple()) { |
| 5706 this._ripple = this._createRipple(); | 3932 this._ripple = this._createRipple(); |
| 5707 this._ripple.noink = this.noink; | 3933 this._ripple.noink = this.noink; |
| 5708 var rippleContainer = this._rippleContainer || this.root; | 3934 var rippleContainer = this._rippleContainer || this.root; |
| 5709 if (rippleContainer) { | 3935 if (rippleContainer) { |
| 5710 Polymer.dom(rippleContainer).appendChild(this._ripple); | 3936 Polymer.dom(rippleContainer).appendChild(this._ripple); |
| 5711 } | 3937 } |
| 5712 if (optTriggeringEvent) { | 3938 if (optTriggeringEvent) { |
| 5713 // Check if the event happened inside of the ripple container | |
| 5714 // Fall back to host instead of the root because distributed text | |
| 5715 // nodes are not valid event targets | |
| 5716 var domContainer = Polymer.dom(this._rippleContainer || this); | 3939 var domContainer = Polymer.dom(this._rippleContainer || this); |
| 5717 var target = Polymer.dom(optTriggeringEvent).rootTarget; | 3940 var target = Polymer.dom(optTriggeringEvent).rootTarget; |
| 5718 if (domContainer.deepContains( /** @type {Node} */(target))) { | 3941 if (domContainer.deepContains( /** @type {Node} */(target))) { |
| 5719 this._ripple.uiDownAction(optTriggeringEvent); | 3942 this._ripple.uiDownAction(optTriggeringEvent); |
| 5720 } | 3943 } |
| 5721 } | 3944 } |
| 5722 } | 3945 } |
| 5723 }, | 3946 }, |
| 5724 | 3947 |
| 5725 /** | |
| 5726 * Returns the `<paper-ripple>` element used by this element to create | |
| 5727 * ripple effects. The element's ripple is created on demand, when | |
| 5728 * necessary, and calling this method will force the | |
| 5729 * ripple to be created. | |
| 5730 */ | |
| 5731 getRipple: function() { | 3948 getRipple: function() { |
| 5732 this.ensureRipple(); | 3949 this.ensureRipple(); |
| 5733 return this._ripple; | 3950 return this._ripple; |
| 5734 }, | 3951 }, |
| 5735 | 3952 |
| 5736 /** | |
| 5737 * Returns true if this element currently contains a ripple effect. | |
| 5738 * @return {boolean} | |
| 5739 */ | |
| 5740 hasRipple: function() { | 3953 hasRipple: function() { |
| 5741 return Boolean(this._ripple); | 3954 return Boolean(this._ripple); |
| 5742 }, | 3955 }, |
| 5743 | 3956 |
| 5744 /** | |
| 5745 * Create the element's ripple effect via creating a `<paper-ripple>`. | |
| 5746 * Override this method to customize the ripple element. | |
| 5747 * @return {!PaperRippleElement} Returns a `<paper-ripple>` element. | |
| 5748 */ | |
| 5749 _createRipple: function() { | 3957 _createRipple: function() { |
| 5750 return /** @type {!PaperRippleElement} */ ( | 3958 return /** @type {!PaperRippleElement} */ ( |
| 5751 document.createElement('paper-ripple')); | 3959 document.createElement('paper-ripple')); |
| 5752 }, | 3960 }, |
| 5753 | 3961 |
| 5754 _noinkChanged: function(noink) { | 3962 _noinkChanged: function(noink) { |
| 5755 if (this.hasRipple()) { | 3963 if (this.hasRipple()) { |
| 5756 this._ripple.noink = noink; | 3964 this._ripple.noink = noink; |
| 5757 } | 3965 } |
| 5758 } | 3966 } |
| 5759 }; | 3967 }; |
| 5760 /** @polymerBehavior Polymer.PaperButtonBehavior */ | 3968 /** @polymerBehavior Polymer.PaperButtonBehavior */ |
| 5761 Polymer.PaperButtonBehaviorImpl = { | 3969 Polymer.PaperButtonBehaviorImpl = { |
| 5762 properties: { | 3970 properties: { |
| 5763 /** | |
| 5764 * The z-depth of this element, from 0-5. Setting to 0 will remove the | |
| 5765 * shadow, and each increasing number greater than 0 will be "deeper" | |
| 5766 * than the last. | |
| 5767 * | |
| 5768 * @attribute elevation | |
| 5769 * @type number | |
| 5770 * @default 1 | |
| 5771 */ | |
| 5772 elevation: { | 3971 elevation: { |
| 5773 type: Number, | 3972 type: Number, |
| 5774 reflectToAttribute: true, | 3973 reflectToAttribute: true, |
| 5775 readOnly: true | 3974 readOnly: true |
| 5776 } | 3975 } |
| 5777 }, | 3976 }, |
| 5778 | 3977 |
| 5779 observers: [ | 3978 observers: [ |
| 5780 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom
Keyboard)', | 3979 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom
Keyboard)', |
| 5781 '_computeKeyboardClass(receivedFocusFromKeyboard)' | 3980 '_computeKeyboardClass(receivedFocusFromKeyboard)' |
| (...skipping 14 matching lines...) Expand all Loading... |
| 5796 } else if (this.receivedFocusFromKeyboard) { | 3995 } else if (this.receivedFocusFromKeyboard) { |
| 5797 e = 3; | 3996 e = 3; |
| 5798 } | 3997 } |
| 5799 this._setElevation(e); | 3998 this._setElevation(e); |
| 5800 }, | 3999 }, |
| 5801 | 4000 |
| 5802 _computeKeyboardClass: function(receivedFocusFromKeyboard) { | 4001 _computeKeyboardClass: function(receivedFocusFromKeyboard) { |
| 5803 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard); | 4002 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard); |
| 5804 }, | 4003 }, |
| 5805 | 4004 |
| 5806 /** | |
| 5807 * In addition to `IronButtonState` behavior, when space key goes down, | |
| 5808 * create a ripple down effect. | |
| 5809 * | |
| 5810 * @param {!KeyboardEvent} event . | |
| 5811 */ | |
| 5812 _spaceKeyDownHandler: function(event) { | 4005 _spaceKeyDownHandler: function(event) { |
| 5813 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); | 4006 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); |
| 5814 // Ensure that there is at most one ripple when the space key is held down
. | |
| 5815 if (this.hasRipple() && this.getRipple().ripples.length < 1) { | 4007 if (this.hasRipple() && this.getRipple().ripples.length < 1) { |
| 5816 this._ripple.uiDownAction(); | 4008 this._ripple.uiDownAction(); |
| 5817 } | 4009 } |
| 5818 }, | 4010 }, |
| 5819 | 4011 |
| 5820 /** | |
| 5821 * In addition to `IronButtonState` behavior, when space key goes up, | |
| 5822 * create a ripple up effect. | |
| 5823 * | |
| 5824 * @param {!KeyboardEvent} event . | |
| 5825 */ | |
| 5826 _spaceKeyUpHandler: function(event) { | 4012 _spaceKeyUpHandler: function(event) { |
| 5827 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); | 4013 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); |
| 5828 if (this.hasRipple()) { | 4014 if (this.hasRipple()) { |
| 5829 this._ripple.uiUpAction(); | 4015 this._ripple.uiUpAction(); |
| 5830 } | 4016 } |
| 5831 } | 4017 } |
| 5832 }; | 4018 }; |
| 5833 | 4019 |
| 5834 /** @polymerBehavior */ | 4020 /** @polymerBehavior */ |
| 5835 Polymer.PaperButtonBehavior = [ | 4021 Polymer.PaperButtonBehavior = [ |
| 5836 Polymer.IronButtonState, | 4022 Polymer.IronButtonState, |
| 5837 Polymer.IronControlState, | 4023 Polymer.IronControlState, |
| 5838 Polymer.PaperRippleBehavior, | 4024 Polymer.PaperRippleBehavior, |
| 5839 Polymer.PaperButtonBehaviorImpl | 4025 Polymer.PaperButtonBehaviorImpl |
| 5840 ]; | 4026 ]; |
| 5841 Polymer({ | 4027 Polymer({ |
| 5842 is: 'paper-button', | 4028 is: 'paper-button', |
| 5843 | 4029 |
| 5844 behaviors: [ | 4030 behaviors: [ |
| 5845 Polymer.PaperButtonBehavior | 4031 Polymer.PaperButtonBehavior |
| 5846 ], | 4032 ], |
| 5847 | 4033 |
| 5848 properties: { | 4034 properties: { |
| 5849 /** | |
| 5850 * If true, the button should be styled with a shadow. | |
| 5851 */ | |
| 5852 raised: { | 4035 raised: { |
| 5853 type: Boolean, | 4036 type: Boolean, |
| 5854 reflectToAttribute: true, | 4037 reflectToAttribute: true, |
| 5855 value: false, | 4038 value: false, |
| 5856 observer: '_calculateElevation' | 4039 observer: '_calculateElevation' |
| 5857 } | 4040 } |
| 5858 }, | 4041 }, |
| 5859 | 4042 |
| 5860 _calculateElevation: function() { | 4043 _calculateElevation: function() { |
| 5861 if (!this.raised) { | 4044 if (!this.raised) { |
| 5862 this._setElevation(0); | 4045 this._setElevation(0); |
| 5863 } else { | 4046 } else { |
| 5864 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); | 4047 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); |
| 5865 } | 4048 } |
| 5866 } | 4049 } |
| 5867 | 4050 |
| 5868 /** | |
| 5869 Fired when the animation finishes. | |
| 5870 This is useful if you want to wait until | |
| 5871 the ripple animation finishes to perform some action. | |
| 5872 | |
| 5873 @event transitionend | |
| 5874 Event param: {{node: Object}} detail Contains the animated node. | |
| 5875 */ | |
| 5876 }); | 4051 }); |
| 5877 Polymer({ | 4052 Polymer({ |
| 5878 is: 'paper-icon-button-light', | 4053 is: 'paper-icon-button-light', |
| 5879 extends: 'button', | 4054 extends: 'button', |
| 5880 | 4055 |
| 5881 behaviors: [ | 4056 behaviors: [ |
| 5882 Polymer.PaperRippleBehavior | 4057 Polymer.PaperRippleBehavior |
| 5883 ], | 4058 ], |
| 5884 | 4059 |
| 5885 listeners: { | 4060 listeners: { |
| 5886 'down': '_rippleDown', | 4061 'down': '_rippleDown', |
| 5887 'up': '_rippleUp', | 4062 'up': '_rippleUp', |
| 5888 'focus': '_rippleDown', | 4063 'focus': '_rippleDown', |
| 5889 'blur': '_rippleUp', | 4064 'blur': '_rippleUp', |
| 5890 }, | 4065 }, |
| 5891 | 4066 |
| 5892 _rippleDown: function() { | 4067 _rippleDown: function() { |
| 5893 this.getRipple().downAction(); | 4068 this.getRipple().downAction(); |
| 5894 }, | 4069 }, |
| 5895 | 4070 |
| 5896 _rippleUp: function() { | 4071 _rippleUp: function() { |
| 5897 this.getRipple().upAction(); | 4072 this.getRipple().upAction(); |
| 5898 }, | 4073 }, |
| 5899 | 4074 |
| 5900 /** | |
| 5901 * @param {...*} var_args | |
| 5902 */ | |
| 5903 ensureRipple: function(var_args) { | 4075 ensureRipple: function(var_args) { |
| 5904 var lastRipple = this._ripple; | 4076 var lastRipple = this._ripple; |
| 5905 Polymer.PaperRippleBehavior.ensureRipple.apply(this, arguments); | 4077 Polymer.PaperRippleBehavior.ensureRipple.apply(this, arguments); |
| 5906 if (this._ripple && this._ripple !== lastRipple) { | 4078 if (this._ripple && this._ripple !== lastRipple) { |
| 5907 this._ripple.center = true; | 4079 this._ripple.center = true; |
| 5908 this._ripple.classList.add('circle'); | 4080 this._ripple.classList.add('circle'); |
| 5909 } | 4081 } |
| 5910 } | 4082 } |
| 5911 }); | 4083 }); |
| 5912 /** | |
| 5913 * `iron-range-behavior` provides the behavior for something with a minimum to m
aximum range. | |
| 5914 * | |
| 5915 * @demo demo/index.html | |
| 5916 * @polymerBehavior | |
| 5917 */ | |
| 5918 Polymer.IronRangeBehavior = { | 4084 Polymer.IronRangeBehavior = { |
| 5919 | 4085 |
| 5920 properties: { | 4086 properties: { |
| 5921 | 4087 |
| 5922 /** | |
| 5923 * The number that represents the current value. | |
| 5924 */ | |
| 5925 value: { | 4088 value: { |
| 5926 type: Number, | 4089 type: Number, |
| 5927 value: 0, | 4090 value: 0, |
| 5928 notify: true, | 4091 notify: true, |
| 5929 reflectToAttribute: true | 4092 reflectToAttribute: true |
| 5930 }, | 4093 }, |
| 5931 | 4094 |
| 5932 /** | |
| 5933 * The number that indicates the minimum value of the range. | |
| 5934 */ | |
| 5935 min: { | 4095 min: { |
| 5936 type: Number, | 4096 type: Number, |
| 5937 value: 0, | 4097 value: 0, |
| 5938 notify: true | 4098 notify: true |
| 5939 }, | 4099 }, |
| 5940 | 4100 |
| 5941 /** | |
| 5942 * The number that indicates the maximum value of the range. | |
| 5943 */ | |
| 5944 max: { | 4101 max: { |
| 5945 type: Number, | 4102 type: Number, |
| 5946 value: 100, | 4103 value: 100, |
| 5947 notify: true | 4104 notify: true |
| 5948 }, | 4105 }, |
| 5949 | 4106 |
| 5950 /** | |
| 5951 * Specifies the value granularity of the range's value. | |
| 5952 */ | |
| 5953 step: { | 4107 step: { |
| 5954 type: Number, | 4108 type: Number, |
| 5955 value: 1, | 4109 value: 1, |
| 5956 notify: true | 4110 notify: true |
| 5957 }, | 4111 }, |
| 5958 | 4112 |
| 5959 /** | |
| 5960 * Returns the ratio of the value. | |
| 5961 */ | |
| 5962 ratio: { | 4113 ratio: { |
| 5963 type: Number, | 4114 type: Number, |
| 5964 value: 0, | 4115 value: 0, |
| 5965 readOnly: true, | 4116 readOnly: true, |
| 5966 notify: true | 4117 notify: true |
| 5967 }, | 4118 }, |
| 5968 }, | 4119 }, |
| 5969 | 4120 |
| 5970 observers: [ | 4121 observers: [ |
| 5971 '_update(value, min, max, step)' | 4122 '_update(value, min, max, step)' |
| 5972 ], | 4123 ], |
| 5973 | 4124 |
| 5974 _calcRatio: function(value) { | 4125 _calcRatio: function(value) { |
| 5975 return (this._clampValue(value) - this.min) / (this.max - this.min); | 4126 return (this._clampValue(value) - this.min) / (this.max - this.min); |
| 5976 }, | 4127 }, |
| 5977 | 4128 |
| 5978 _clampValue: function(value) { | 4129 _clampValue: function(value) { |
| 5979 return Math.min(this.max, Math.max(this.min, this._calcStep(value))); | 4130 return Math.min(this.max, Math.max(this.min, this._calcStep(value))); |
| 5980 }, | 4131 }, |
| 5981 | 4132 |
| 5982 _calcStep: function(value) { | 4133 _calcStep: function(value) { |
| 5983 // polymer/issues/2493 | |
| 5984 value = parseFloat(value); | 4134 value = parseFloat(value); |
| 5985 | 4135 |
| 5986 if (!this.step) { | 4136 if (!this.step) { |
| 5987 return value; | 4137 return value; |
| 5988 } | 4138 } |
| 5989 | 4139 |
| 5990 var numSteps = Math.round((value - this.min) / this.step); | 4140 var numSteps = Math.round((value - this.min) / this.step); |
| 5991 if (this.step < 1) { | 4141 if (this.step < 1) { |
| 5992 /** | |
| 5993 * For small values of this.step, if we calculate the step using | |
| 5994 * `Math.round(value / step) * step` we may hit a precision point issue | |
| 5995 * eg. 0.1 * 0.2 = 0.020000000000000004 | |
| 5996 * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html | |
| 5997 * | |
| 5998 * as a work around we can divide by the reciprocal of `step` | |
| 5999 */ | |
| 6000 return numSteps / (1 / this.step) + this.min; | 4142 return numSteps / (1 / this.step) + this.min; |
| 6001 } else { | 4143 } else { |
| 6002 return numSteps * this.step + this.min; | 4144 return numSteps * this.step + this.min; |
| 6003 } | 4145 } |
| 6004 }, | 4146 }, |
| 6005 | 4147 |
| 6006 _validateValue: function() { | 4148 _validateValue: function() { |
| 6007 var v = this._clampValue(this.value); | 4149 var v = this._clampValue(this.value); |
| 6008 this.value = this.oldValue = isNaN(v) ? this.oldValue : v; | 4150 this.value = this.oldValue = isNaN(v) ? this.oldValue : v; |
| 6009 return this.value !== v; | 4151 return this.value !== v; |
| 6010 }, | 4152 }, |
| 6011 | 4153 |
| 6012 _update: function() { | 4154 _update: function() { |
| 6013 this._validateValue(); | 4155 this._validateValue(); |
| 6014 this._setRatio(this._calcRatio(this.value) * 100); | 4156 this._setRatio(this._calcRatio(this.value) * 100); |
| 6015 } | 4157 } |
| 6016 | 4158 |
| 6017 }; | 4159 }; |
| 6018 Polymer({ | 4160 Polymer({ |
| 6019 is: 'paper-progress', | 4161 is: 'paper-progress', |
| 6020 | 4162 |
| 6021 behaviors: [ | 4163 behaviors: [ |
| 6022 Polymer.IronRangeBehavior | 4164 Polymer.IronRangeBehavior |
| 6023 ], | 4165 ], |
| 6024 | 4166 |
| 6025 properties: { | 4167 properties: { |
| 6026 /** | |
| 6027 * The number that represents the current secondary progress. | |
| 6028 */ | |
| 6029 secondaryProgress: { | 4168 secondaryProgress: { |
| 6030 type: Number, | 4169 type: Number, |
| 6031 value: 0 | 4170 value: 0 |
| 6032 }, | 4171 }, |
| 6033 | 4172 |
| 6034 /** | |
| 6035 * The secondary ratio | |
| 6036 */ | |
| 6037 secondaryRatio: { | 4173 secondaryRatio: { |
| 6038 type: Number, | 4174 type: Number, |
| 6039 value: 0, | 4175 value: 0, |
| 6040 readOnly: true | 4176 readOnly: true |
| 6041 }, | 4177 }, |
| 6042 | 4178 |
| 6043 /** | |
| 6044 * Use an indeterminate progress indicator. | |
| 6045 */ | |
| 6046 indeterminate: { | 4179 indeterminate: { |
| 6047 type: Boolean, | 4180 type: Boolean, |
| 6048 value: false, | 4181 value: false, |
| 6049 observer: '_toggleIndeterminate' | 4182 observer: '_toggleIndeterminate' |
| 6050 }, | 4183 }, |
| 6051 | 4184 |
| 6052 /** | |
| 6053 * True if the progress is disabled. | |
| 6054 */ | |
| 6055 disabled: { | 4185 disabled: { |
| 6056 type: Boolean, | 4186 type: Boolean, |
| 6057 value: false, | 4187 value: false, |
| 6058 reflectToAttribute: true, | 4188 reflectToAttribute: true, |
| 6059 observer: '_disabledChanged' | 4189 observer: '_disabledChanged' |
| 6060 } | 4190 } |
| 6061 }, | 4191 }, |
| 6062 | 4192 |
| 6063 observers: [ | 4193 observers: [ |
| 6064 '_progressChanged(secondaryProgress, value, min, max)' | 4194 '_progressChanged(secondaryProgress, value, min, max)' |
| 6065 ], | 4195 ], |
| 6066 | 4196 |
| 6067 hostAttributes: { | 4197 hostAttributes: { |
| 6068 role: 'progressbar' | 4198 role: 'progressbar' |
| 6069 }, | 4199 }, |
| 6070 | 4200 |
| 6071 _toggleIndeterminate: function(indeterminate) { | 4201 _toggleIndeterminate: function(indeterminate) { |
| 6072 // If we use attribute/class binding, the animation sometimes doesn't tran
slate properly | |
| 6073 // on Safari 7.1. So instead, we toggle the class here in the update metho
d. | |
| 6074 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress); | 4202 this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress); |
| 6075 }, | 4203 }, |
| 6076 | 4204 |
| 6077 _transformProgress: function(progress, ratio) { | 4205 _transformProgress: function(progress, ratio) { |
| 6078 var transform = 'scaleX(' + (ratio / 100) + ')'; | 4206 var transform = 'scaleX(' + (ratio / 100) + ')'; |
| 6079 progress.style.transform = progress.style.webkitTransform = transform; | 4207 progress.style.transform = progress.style.webkitTransform = transform; |
| 6080 }, | 4208 }, |
| 6081 | 4209 |
| 6082 _mainRatioChanged: function(ratio) { | 4210 _mainRatioChanged: function(ratio) { |
| 6083 this._transformProgress(this.$.primaryProgress, ratio); | 4211 this._transformProgress(this.$.primaryProgress, ratio); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 6102 }, | 4230 }, |
| 6103 | 4231 |
| 6104 _disabledChanged: function(disabled) { | 4232 _disabledChanged: function(disabled) { |
| 6105 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); | 4233 this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); |
| 6106 }, | 4234 }, |
| 6107 | 4235 |
| 6108 _hideSecondaryProgress: function(secondaryRatio) { | 4236 _hideSecondaryProgress: function(secondaryRatio) { |
| 6109 return secondaryRatio === 0; | 4237 return secondaryRatio === 0; |
| 6110 } | 4238 } |
| 6111 }); | 4239 }); |
| 6112 /** | |
| 6113 * The `iron-iconset-svg` element allows users to define their own icon sets | |
| 6114 * that contain svg icons. The svg icon elements should be children of the | |
| 6115 * `iron-iconset-svg` element. Multiple icons should be given distinct id's. | |
| 6116 * | |
| 6117 * Using svg elements to create icons has a few advantages over traditional | |
| 6118 * bitmap graphics like jpg or png. Icons that use svg are vector based so | |
| 6119 * they are resolution independent and should look good on any device. They | |
| 6120 * are stylable via css. Icons can be themed, colorized, and even animated. | |
| 6121 * | |
| 6122 * Example: | |
| 6123 * | |
| 6124 * <iron-iconset-svg name="my-svg-icons" size="24"> | |
| 6125 * <svg> | |
| 6126 * <defs> | |
| 6127 * <g id="shape"> | |
| 6128 * <rect x="12" y="0" width="12" height="24" /> | |
| 6129 * <circle cx="12" cy="12" r="12" /> | |
| 6130 * </g> | |
| 6131 * </defs> | |
| 6132 * </svg> | |
| 6133 * </iron-iconset-svg> | |
| 6134 * | |
| 6135 * This will automatically register the icon set "my-svg-icons" to the iconset | |
| 6136 * database. To use these icons from within another element, make a | |
| 6137 * `iron-iconset` element and call the `byId` method | |
| 6138 * to retrieve a given iconset. To apply a particular icon inside an | |
| 6139 * element use the `applyIcon` method. For example: | |
| 6140 * | |
| 6141 * iconset.applyIcon(iconNode, 'car'); | |
| 6142 * | |
| 6143 * @element iron-iconset-svg | |
| 6144 * @demo demo/index.html | |
| 6145 * @implements {Polymer.Iconset} | |
| 6146 */ | |
| 6147 Polymer({ | 4240 Polymer({ |
| 6148 is: 'iron-iconset-svg', | 4241 is: 'iron-iconset-svg', |
| 6149 | 4242 |
| 6150 properties: { | 4243 properties: { |
| 6151 | 4244 |
| 6152 /** | |
| 6153 * The name of the iconset. | |
| 6154 */ | |
| 6155 name: { | 4245 name: { |
| 6156 type: String, | 4246 type: String, |
| 6157 observer: '_nameChanged' | 4247 observer: '_nameChanged' |
| 6158 }, | 4248 }, |
| 6159 | 4249 |
| 6160 /** | |
| 6161 * The size of an individual icon. Note that icons must be square. | |
| 6162 */ | |
| 6163 size: { | 4250 size: { |
| 6164 type: Number, | 4251 type: Number, |
| 6165 value: 24 | 4252 value: 24 |
| 6166 } | 4253 } |
| 6167 | 4254 |
| 6168 }, | 4255 }, |
| 6169 | 4256 |
| 6170 attached: function() { | 4257 attached: function() { |
| 6171 this.style.display = 'none'; | 4258 this.style.display = 'none'; |
| 6172 }, | 4259 }, |
| 6173 | 4260 |
| 6174 /** | |
| 6175 * Construct an array of all icon names in this iconset. | |
| 6176 * | |
| 6177 * @return {!Array} Array of icon names. | |
| 6178 */ | |
| 6179 getIconNames: function() { | 4261 getIconNames: function() { |
| 6180 this._icons = this._createIconMap(); | 4262 this._icons = this._createIconMap(); |
| 6181 return Object.keys(this._icons).map(function(n) { | 4263 return Object.keys(this._icons).map(function(n) { |
| 6182 return this.name + ':' + n; | 4264 return this.name + ':' + n; |
| 6183 }, this); | 4265 }, this); |
| 6184 }, | 4266 }, |
| 6185 | 4267 |
| 6186 /** | |
| 6187 * Applies an icon to the given element. | |
| 6188 * | |
| 6189 * An svg icon is prepended to the element's shadowRoot if it exists, | |
| 6190 * otherwise to the element itself. | |
| 6191 * | |
| 6192 * @method applyIcon | |
| 6193 * @param {Element} element Element to which the icon is applied. | |
| 6194 * @param {string} iconName Name of the icon to apply. | |
| 6195 * @return {?Element} The svg element which renders the icon. | |
| 6196 */ | |
| 6197 applyIcon: function(element, iconName) { | 4268 applyIcon: function(element, iconName) { |
| 6198 // insert svg element into shadow root, if it exists | |
| 6199 element = element.root || element; | 4269 element = element.root || element; |
| 6200 // Remove old svg element | |
| 6201 this.removeIcon(element); | 4270 this.removeIcon(element); |
| 6202 // install new svg element | |
| 6203 var svg = this._cloneIcon(iconName); | 4271 var svg = this._cloneIcon(iconName); |
| 6204 if (svg) { | 4272 if (svg) { |
| 6205 var pde = Polymer.dom(element); | 4273 var pde = Polymer.dom(element); |
| 6206 pde.insertBefore(svg, pde.childNodes[0]); | 4274 pde.insertBefore(svg, pde.childNodes[0]); |
| 6207 return element._svgIcon = svg; | 4275 return element._svgIcon = svg; |
| 6208 } | 4276 } |
| 6209 return null; | 4277 return null; |
| 6210 }, | 4278 }, |
| 6211 | 4279 |
| 6212 /** | |
| 6213 * Remove an icon from the given element by undoing the changes effected | |
| 6214 * by `applyIcon`. | |
| 6215 * | |
| 6216 * @param {Element} element The element from which the icon is removed. | |
| 6217 */ | |
| 6218 removeIcon: function(element) { | 4280 removeIcon: function(element) { |
| 6219 // Remove old svg element | |
| 6220 if (element._svgIcon) { | 4281 if (element._svgIcon) { |
| 6221 Polymer.dom(element).removeChild(element._svgIcon); | 4282 Polymer.dom(element).removeChild(element._svgIcon); |
| 6222 element._svgIcon = null; | 4283 element._svgIcon = null; |
| 6223 } | 4284 } |
| 6224 }, | 4285 }, |
| 6225 | 4286 |
| 6226 /** | |
| 6227 * | |
| 6228 * When name is changed, register iconset metadata | |
| 6229 * | |
| 6230 */ | |
| 6231 _nameChanged: function() { | 4287 _nameChanged: function() { |
| 6232 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); | 4288 new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); |
| 6233 this.async(function() { | 4289 this.async(function() { |
| 6234 this.fire('iron-iconset-added', this, {node: window}); | 4290 this.fire('iron-iconset-added', this, {node: window}); |
| 6235 }); | 4291 }); |
| 6236 }, | 4292 }, |
| 6237 | 4293 |
| 6238 /** | |
| 6239 * Create a map of child SVG elements by id. | |
| 6240 * | |
| 6241 * @return {!Object} Map of id's to SVG elements. | |
| 6242 */ | |
| 6243 _createIconMap: function() { | 4294 _createIconMap: function() { |
| 6244 // Objects chained to Object.prototype (`{}`) have members. Specifically, | |
| 6245 // on FF there is a `watch` method that confuses the icon map, so we | |
| 6246 // need to use a null-based object here. | |
| 6247 var icons = Object.create(null); | 4295 var icons = Object.create(null); |
| 6248 Polymer.dom(this).querySelectorAll('[id]') | 4296 Polymer.dom(this).querySelectorAll('[id]') |
| 6249 .forEach(function(icon) { | 4297 .forEach(function(icon) { |
| 6250 icons[icon.id] = icon; | 4298 icons[icon.id] = icon; |
| 6251 }); | 4299 }); |
| 6252 return icons; | 4300 return icons; |
| 6253 }, | 4301 }, |
| 6254 | 4302 |
| 6255 /** | |
| 6256 * Produce installable clone of the SVG element matching `id` in this | |
| 6257 * iconset, or `undefined` if there is no matching element. | |
| 6258 * | |
| 6259 * @return {Element} Returns an installable clone of the SVG element | |
| 6260 * matching `id`. | |
| 6261 */ | |
| 6262 _cloneIcon: function(id) { | 4303 _cloneIcon: function(id) { |
| 6263 // create the icon map on-demand, since the iconset itself has no discrete | |
| 6264 // signal to know when it's children are fully parsed | |
| 6265 this._icons = this._icons || this._createIconMap(); | 4304 this._icons = this._icons || this._createIconMap(); |
| 6266 return this._prepareSvgClone(this._icons[id], this.size); | 4305 return this._prepareSvgClone(this._icons[id], this.size); |
| 6267 }, | 4306 }, |
| 6268 | 4307 |
| 6269 /** | |
| 6270 * @param {Element} sourceSvg | |
| 6271 * @param {number} size | |
| 6272 * @return {Element} | |
| 6273 */ | |
| 6274 _prepareSvgClone: function(sourceSvg, size) { | 4308 _prepareSvgClone: function(sourceSvg, size) { |
| 6275 if (sourceSvg) { | 4309 if (sourceSvg) { |
| 6276 var content = sourceSvg.cloneNode(true), | 4310 var content = sourceSvg.cloneNode(true), |
| 6277 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), | 4311 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), |
| 6278 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s
ize; | 4312 viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + s
ize; |
| 6279 svg.setAttribute('viewBox', viewBox); | 4313 svg.setAttribute('viewBox', viewBox); |
| 6280 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); | 4314 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
| 6281 // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/
370136 | |
| 6282 // TODO(sjmiles): inline style may not be ideal, but avoids requiring a
shadow-root | |
| 6283 svg.style.cssText = 'pointer-events: none; display: block; width: 100%;
height: 100%;'; | 4315 svg.style.cssText = 'pointer-events: none; display: block; width: 100%;
height: 100%;'; |
| 6284 svg.appendChild(content).removeAttribute('id'); | 4316 svg.appendChild(content).removeAttribute('id'); |
| 6285 return svg; | 4317 return svg; |
| 6286 } | 4318 } |
| 6287 return null; | 4319 return null; |
| 6288 } | 4320 } |
| 6289 | 4321 |
| 6290 }); | 4322 }); |
| 6291 // Copyright 2015 The Chromium Authors. All rights reserved. | 4323 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 6292 // Use of this source code is governed by a BSD-style license that can be | 4324 // Use of this source code is governed by a BSD-style license that can be |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6351 }, | 4383 }, |
| 6352 | 4384 |
| 6353 showProgress_: { | 4385 showProgress_: { |
| 6354 computed: 'computeShowProgress_(showCancel_, data.percent)', | 4386 computed: 'computeShowProgress_(showCancel_, data.percent)', |
| 6355 type: Boolean, | 4387 type: Boolean, |
| 6356 value: false, | 4388 value: false, |
| 6357 }, | 4389 }, |
| 6358 }, | 4390 }, |
| 6359 | 4391 |
| 6360 observers: [ | 4392 observers: [ |
| 6361 // TODO(dbeam): this gets called way more when I observe data.by_ext_id | |
| 6362 // and data.by_ext_name directly. Why? | |
| 6363 'observeControlledBy_(controlledBy_)', | 4393 'observeControlledBy_(controlledBy_)', |
| 6364 'observeIsDangerous_(isDangerous_, data)', | 4394 'observeIsDangerous_(isDangerous_, data)', |
| 6365 ], | 4395 ], |
| 6366 | 4396 |
| 6367 ready: function() { | 4397 ready: function() { |
| 6368 this.content = this.$.content; | 4398 this.content = this.$.content; |
| 6369 }, | 4399 }, |
| 6370 | 4400 |
| 6371 /** @private */ | 4401 /** @private */ |
| 6372 computeClass_: function() { | 4402 computeClass_: function() { |
| (...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6553 /** @private */ | 4583 /** @private */ |
| 6554 onCancelTap_: function() { | 4584 onCancelTap_: function() { |
| 6555 downloads.ActionService.getInstance().cancel(this.data.id); | 4585 downloads.ActionService.getInstance().cancel(this.data.id); |
| 6556 }, | 4586 }, |
| 6557 | 4587 |
| 6558 /** @private */ | 4588 /** @private */ |
| 6559 onDiscardDangerousTap_: function() { | 4589 onDiscardDangerousTap_: function() { |
| 6560 downloads.ActionService.getInstance().discardDangerous(this.data.id); | 4590 downloads.ActionService.getInstance().discardDangerous(this.data.id); |
| 6561 }, | 4591 }, |
| 6562 | 4592 |
| 6563 /** | |
| 6564 * @private | |
| 6565 * @param {Event} e | |
| 6566 */ | |
| 6567 onDragStart_: function(e) { | 4593 onDragStart_: function(e) { |
| 6568 e.preventDefault(); | 4594 e.preventDefault(); |
| 6569 downloads.ActionService.getInstance().drag(this.data.id); | 4595 downloads.ActionService.getInstance().drag(this.data.id); |
| 6570 }, | 4596 }, |
| 6571 | 4597 |
| 6572 /** | |
| 6573 * @param {Event} e | |
| 6574 * @private | |
| 6575 */ | |
| 6576 onFileLinkTap_: function(e) { | 4598 onFileLinkTap_: function(e) { |
| 6577 e.preventDefault(); | 4599 e.preventDefault(); |
| 6578 downloads.ActionService.getInstance().openFile(this.data.id); | 4600 downloads.ActionService.getInstance().openFile(this.data.id); |
| 6579 }, | 4601 }, |
| 6580 | 4602 |
| 6581 /** @private */ | 4603 /** @private */ |
| 6582 onPauseOrResumeTap_: function() { | 4604 onPauseOrResumeTap_: function() { |
| 6583 if (this.isInProgress_) | 4605 if (this.isInProgress_) |
| 6584 downloads.ActionService.getInstance().pause(this.data.id); | 4606 downloads.ActionService.getInstance().pause(this.data.id); |
| 6585 else | 4607 else |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 6623 Polymer.IronControlState, | 4645 Polymer.IronControlState, |
| 6624 Polymer.PaperItemBehaviorImpl | 4646 Polymer.PaperItemBehaviorImpl |
| 6625 ]; | 4647 ]; |
| 6626 Polymer({ | 4648 Polymer({ |
| 6627 is: 'paper-item', | 4649 is: 'paper-item', |
| 6628 | 4650 |
| 6629 behaviors: [ | 4651 behaviors: [ |
| 6630 Polymer.PaperItemBehavior | 4652 Polymer.PaperItemBehavior |
| 6631 ] | 4653 ] |
| 6632 }); | 4654 }); |
| 6633 /** | |
| 6634 * @param {!Function} selectCallback | |
| 6635 * @constructor | |
| 6636 */ | |
| 6637 Polymer.IronSelection = function(selectCallback) { | 4655 Polymer.IronSelection = function(selectCallback) { |
| 6638 this.selection = []; | 4656 this.selection = []; |
| 6639 this.selectCallback = selectCallback; | 4657 this.selectCallback = selectCallback; |
| 6640 }; | 4658 }; |
| 6641 | 4659 |
| 6642 Polymer.IronSelection.prototype = { | 4660 Polymer.IronSelection.prototype = { |
| 6643 | 4661 |
| 6644 /** | |
| 6645 * Retrieves the selected item(s). | |
| 6646 * | |
| 6647 * @method get | |
| 6648 * @returns Returns the selected item(s). If the multi property is true, | |
| 6649 * `get` will return an array, otherwise it will return | |
| 6650 * the selected item or undefined if there is no selection. | |
| 6651 */ | |
| 6652 get: function() { | 4662 get: function() { |
| 6653 return this.multi ? this.selection.slice() : this.selection[0]; | 4663 return this.multi ? this.selection.slice() : this.selection[0]; |
| 6654 }, | 4664 }, |
| 6655 | 4665 |
| 6656 /** | |
| 6657 * Clears all the selection except the ones indicated. | |
| 6658 * | |
| 6659 * @method clear | |
| 6660 * @param {Array} excludes items to be excluded. | |
| 6661 */ | |
| 6662 clear: function(excludes) { | 4666 clear: function(excludes) { |
| 6663 this.selection.slice().forEach(function(item) { | 4667 this.selection.slice().forEach(function(item) { |
| 6664 if (!excludes || excludes.indexOf(item) < 0) { | 4668 if (!excludes || excludes.indexOf(item) < 0) { |
| 6665 this.setItemSelected(item, false); | 4669 this.setItemSelected(item, false); |
| 6666 } | 4670 } |
| 6667 }, this); | 4671 }, this); |
| 6668 }, | 4672 }, |
| 6669 | 4673 |
| 6670 /** | |
| 6671 * Indicates if a given item is selected. | |
| 6672 * | |
| 6673 * @method isSelected | |
| 6674 * @param {*} item The item whose selection state should be checked. | |
| 6675 * @returns Returns true if `item` is selected. | |
| 6676 */ | |
| 6677 isSelected: function(item) { | 4674 isSelected: function(item) { |
| 6678 return this.selection.indexOf(item) >= 0; | 4675 return this.selection.indexOf(item) >= 0; |
| 6679 }, | 4676 }, |
| 6680 | 4677 |
| 6681 /** | |
| 6682 * Sets the selection state for a given item to either selected or deselecte
d. | |
| 6683 * | |
| 6684 * @method setItemSelected | |
| 6685 * @param {*} item The item to select. | |
| 6686 * @param {boolean} isSelected True for selected, false for deselected. | |
| 6687 */ | |
| 6688 setItemSelected: function(item, isSelected) { | 4678 setItemSelected: function(item, isSelected) { |
| 6689 if (item != null) { | 4679 if (item != null) { |
| 6690 if (isSelected !== this.isSelected(item)) { | 4680 if (isSelected !== this.isSelected(item)) { |
| 6691 // proceed to update selection only if requested state differs from cu
rrent | |
| 6692 if (isSelected) { | 4681 if (isSelected) { |
| 6693 this.selection.push(item); | 4682 this.selection.push(item); |
| 6694 } else { | 4683 } else { |
| 6695 var i = this.selection.indexOf(item); | 4684 var i = this.selection.indexOf(item); |
| 6696 if (i >= 0) { | 4685 if (i >= 0) { |
| 6697 this.selection.splice(i, 1); | 4686 this.selection.splice(i, 1); |
| 6698 } | 4687 } |
| 6699 } | 4688 } |
| 6700 if (this.selectCallback) { | 4689 if (this.selectCallback) { |
| 6701 this.selectCallback(item, isSelected); | 4690 this.selectCallback(item, isSelected); |
| 6702 } | 4691 } |
| 6703 } | 4692 } |
| 6704 } | 4693 } |
| 6705 }, | 4694 }, |
| 6706 | 4695 |
| 6707 /** | |
| 6708 * Sets the selection state for a given item. If the `multi` property | |
| 6709 * is true, then the selected state of `item` will be toggled; otherwise | |
| 6710 * the `item` will be selected. | |
| 6711 * | |
| 6712 * @method select | |
| 6713 * @param {*} item The item to select. | |
| 6714 */ | |
| 6715 select: function(item) { | 4696 select: function(item) { |
| 6716 if (this.multi) { | 4697 if (this.multi) { |
| 6717 this.toggle(item); | 4698 this.toggle(item); |
| 6718 } else if (this.get() !== item) { | 4699 } else if (this.get() !== item) { |
| 6719 this.setItemSelected(this.get(), false); | 4700 this.setItemSelected(this.get(), false); |
| 6720 this.setItemSelected(item, true); | 4701 this.setItemSelected(item, true); |
| 6721 } | 4702 } |
| 6722 }, | 4703 }, |
| 6723 | 4704 |
| 6724 /** | |
| 6725 * Toggles the selection state for `item`. | |
| 6726 * | |
| 6727 * @method toggle | |
| 6728 * @param {*} item The item to toggle. | |
| 6729 */ | |
| 6730 toggle: function(item) { | 4705 toggle: function(item) { |
| 6731 this.setItemSelected(item, !this.isSelected(item)); | 4706 this.setItemSelected(item, !this.isSelected(item)); |
| 6732 } | 4707 } |
| 6733 | 4708 |
| 6734 }; | 4709 }; |
| 6735 /** @polymerBehavior */ | 4710 /** @polymerBehavior */ |
| 6736 Polymer.IronSelectableBehavior = { | 4711 Polymer.IronSelectableBehavior = { |
| 6737 | 4712 |
| 6738 /** | |
| 6739 * Fired when iron-selector is activated (selected or deselected). | |
| 6740 * It is fired before the selected items are changed. | |
| 6741 * Cancel the event to abort selection. | |
| 6742 * | |
| 6743 * @event iron-activate | |
| 6744 */ | |
| 6745 | 4713 |
| 6746 /** | |
| 6747 * Fired when an item is selected | |
| 6748 * | |
| 6749 * @event iron-select | |
| 6750 */ | |
| 6751 | 4714 |
| 6752 /** | |
| 6753 * Fired when an item is deselected | |
| 6754 * | |
| 6755 * @event iron-deselect | |
| 6756 */ | |
| 6757 | 4715 |
| 6758 /** | |
| 6759 * Fired when the list of selectable items changes (e.g., items are | |
| 6760 * added or removed). The detail of the event is a mutation record that | |
| 6761 * describes what changed. | |
| 6762 * | |
| 6763 * @event iron-items-changed | |
| 6764 */ | |
| 6765 | 4716 |
| 6766 properties: { | 4717 properties: { |
| 6767 | 4718 |
| 6768 /** | |
| 6769 * If you want to use an attribute value or property of an element for | |
| 6770 * `selected` instead of the index, set this to the name of the attribute | |
| 6771 * or property. Hyphenated values are converted to camel case when used to | |
| 6772 * look up the property of a selectable element. Camel cased values are | |
| 6773 * *not* converted to hyphenated values for attribute lookup. It's | |
| 6774 * recommended that you provide the hyphenated form of the name so that | |
| 6775 * selection works in both cases. (Use `attr-or-property-name` instead of | |
| 6776 * `attrOrPropertyName`.) | |
| 6777 */ | |
| 6778 attrForSelected: { | 4719 attrForSelected: { |
| 6779 type: String, | 4720 type: String, |
| 6780 value: null | 4721 value: null |
| 6781 }, | 4722 }, |
| 6782 | 4723 |
| 6783 /** | |
| 6784 * Gets or sets the selected element. The default is to use the index of t
he item. | |
| 6785 * @type {string|number} | |
| 6786 */ | |
| 6787 selected: { | 4724 selected: { |
| 6788 type: String, | 4725 type: String, |
| 6789 notify: true | 4726 notify: true |
| 6790 }, | 4727 }, |
| 6791 | 4728 |
| 6792 /** | |
| 6793 * Returns the currently selected item. | |
| 6794 * | |
| 6795 * @type {?Object} | |
| 6796 */ | |
| 6797 selectedItem: { | 4729 selectedItem: { |
| 6798 type: Object, | 4730 type: Object, |
| 6799 readOnly: true, | 4731 readOnly: true, |
| 6800 notify: true | 4732 notify: true |
| 6801 }, | 4733 }, |
| 6802 | 4734 |
| 6803 /** | |
| 6804 * The event that fires from items when they are selected. Selectable | |
| 6805 * will listen for this event from items and update the selection state. | |
| 6806 * Set to empty string to listen to no events. | |
| 6807 */ | |
| 6808 activateEvent: { | 4735 activateEvent: { |
| 6809 type: String, | 4736 type: String, |
| 6810 value: 'tap', | 4737 value: 'tap', |
| 6811 observer: '_activateEventChanged' | 4738 observer: '_activateEventChanged' |
| 6812 }, | 4739 }, |
| 6813 | 4740 |
| 6814 /** | |
| 6815 * This is a CSS selector string. If this is set, only items that match t
he CSS selector | |
| 6816 * are selectable. | |
| 6817 */ | |
| 6818 selectable: String, | 4741 selectable: String, |
| 6819 | 4742 |
| 6820 /** | |
| 6821 * The class to set on elements when selected. | |
| 6822 */ | |
| 6823 selectedClass: { | 4743 selectedClass: { |
| 6824 type: String, | 4744 type: String, |
| 6825 value: 'iron-selected' | 4745 value: 'iron-selected' |
| 6826 }, | 4746 }, |
| 6827 | 4747 |
| 6828 /** | |
| 6829 * The attribute to set on elements when selected. | |
| 6830 */ | |
| 6831 selectedAttribute: { | 4748 selectedAttribute: { |
| 6832 type: String, | 4749 type: String, |
| 6833 value: null | 4750 value: null |
| 6834 }, | 4751 }, |
| 6835 | 4752 |
| 6836 /** | |
| 6837 * Default fallback if the selection based on selected with `attrForSelect
ed` | |
| 6838 * is not found. | |
| 6839 */ | |
| 6840 fallbackSelection: { | 4753 fallbackSelection: { |
| 6841 type: String, | 4754 type: String, |
| 6842 value: null | 4755 value: null |
| 6843 }, | 4756 }, |
| 6844 | 4757 |
| 6845 /** | |
| 6846 * The list of items from which a selection can be made. | |
| 6847 */ | |
| 6848 items: { | 4758 items: { |
| 6849 type: Array, | 4759 type: Array, |
| 6850 readOnly: true, | 4760 readOnly: true, |
| 6851 notify: true, | 4761 notify: true, |
| 6852 value: function() { | 4762 value: function() { |
| 6853 return []; | 4763 return []; |
| 6854 } | 4764 } |
| 6855 }, | 4765 }, |
| 6856 | 4766 |
| 6857 /** | |
| 6858 * The set of excluded elements where the key is the `localName` | |
| 6859 * of the element that will be ignored from the item list. | |
| 6860 * | |
| 6861 * @default {template: 1} | |
| 6862 */ | |
| 6863 _excludedLocalNames: { | 4767 _excludedLocalNames: { |
| 6864 type: Object, | 4768 type: Object, |
| 6865 value: function() { | 4769 value: function() { |
| 6866 return { | 4770 return { |
| 6867 'template': 1 | 4771 'template': 1 |
| 6868 }; | 4772 }; |
| 6869 } | 4773 } |
| 6870 } | 4774 } |
| 6871 }, | 4775 }, |
| 6872 | 4776 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 6890 this._addListener(this.activateEvent); | 4794 this._addListener(this.activateEvent); |
| 6891 }, | 4795 }, |
| 6892 | 4796 |
| 6893 detached: function() { | 4797 detached: function() { |
| 6894 if (this._observer) { | 4798 if (this._observer) { |
| 6895 Polymer.dom(this).unobserveNodes(this._observer); | 4799 Polymer.dom(this).unobserveNodes(this._observer); |
| 6896 } | 4800 } |
| 6897 this._removeListener(this.activateEvent); | 4801 this._removeListener(this.activateEvent); |
| 6898 }, | 4802 }, |
| 6899 | 4803 |
| 6900 /** | |
| 6901 * Returns the index of the given item. | |
| 6902 * | |
| 6903 * @method indexOf | |
| 6904 * @param {Object} item | |
| 6905 * @returns Returns the index of the item | |
| 6906 */ | |
| 6907 indexOf: function(item) { | 4804 indexOf: function(item) { |
| 6908 return this.items.indexOf(item); | 4805 return this.items.indexOf(item); |
| 6909 }, | 4806 }, |
| 6910 | 4807 |
| 6911 /** | |
| 6912 * Selects the given value. | |
| 6913 * | |
| 6914 * @method select | |
| 6915 * @param {string|number} value the value to select. | |
| 6916 */ | |
| 6917 select: function(value) { | 4808 select: function(value) { |
| 6918 this.selected = value; | 4809 this.selected = value; |
| 6919 }, | 4810 }, |
| 6920 | 4811 |
| 6921 /** | |
| 6922 * Selects the previous item. | |
| 6923 * | |
| 6924 * @method selectPrevious | |
| 6925 */ | |
| 6926 selectPrevious: function() { | 4812 selectPrevious: function() { |
| 6927 var length = this.items.length; | 4813 var length = this.items.length; |
| 6928 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len
gth; | 4814 var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % len
gth; |
| 6929 this.selected = this._indexToValue(index); | 4815 this.selected = this._indexToValue(index); |
| 6930 }, | 4816 }, |
| 6931 | 4817 |
| 6932 /** | |
| 6933 * Selects the next item. | |
| 6934 * | |
| 6935 * @method selectNext | |
| 6936 */ | |
| 6937 selectNext: function() { | 4818 selectNext: function() { |
| 6938 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l
ength; | 4819 var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.l
ength; |
| 6939 this.selected = this._indexToValue(index); | 4820 this.selected = this._indexToValue(index); |
| 6940 }, | 4821 }, |
| 6941 | 4822 |
| 6942 /** | |
| 6943 * Selects the item at the given index. | |
| 6944 * | |
| 6945 * @method selectIndex | |
| 6946 */ | |
| 6947 selectIndex: function(index) { | 4823 selectIndex: function(index) { |
| 6948 this.select(this._indexToValue(index)); | 4824 this.select(this._indexToValue(index)); |
| 6949 }, | 4825 }, |
| 6950 | 4826 |
| 6951 /** | |
| 6952 * Force a synchronous update of the `items` property. | |
| 6953 * | |
| 6954 * NOTE: Consider listening for the `iron-items-changed` event to respond to | |
| 6955 * updates to the set of selectable items after updates to the DOM list and | |
| 6956 * selection state have been made. | |
| 6957 * | |
| 6958 * WARNING: If you are using this method, you should probably consider an | |
| 6959 * alternate approach. Synchronously querying for items is potentially | |
| 6960 * slow for many use cases. The `items` property will update asynchronously | |
| 6961 * on its own to reflect selectable items in the DOM. | |
| 6962 */ | |
| 6963 forceSynchronousItemUpdate: function() { | 4827 forceSynchronousItemUpdate: function() { |
| 6964 this._updateItems(); | 4828 this._updateItems(); |
| 6965 }, | 4829 }, |
| 6966 | 4830 |
| 6967 get _shouldUpdateSelection() { | 4831 get _shouldUpdateSelection() { |
| 6968 return this.selected != null; | 4832 return this.selected != null; |
| 6969 }, | 4833 }, |
| 6970 | 4834 |
| 6971 _checkFallback: function() { | 4835 _checkFallback: function() { |
| 6972 if (this._shouldUpdateSelection) { | 4836 if (this._shouldUpdateSelection) { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 6998 this.selected = this._indexToValue(this.indexOf(this.selectedItem)); | 4862 this.selected = this._indexToValue(this.indexOf(this.selectedItem)); |
| 6999 } | 4863 } |
| 7000 }, | 4864 }, |
| 7001 | 4865 |
| 7002 _updateSelected: function() { | 4866 _updateSelected: function() { |
| 7003 this._selectSelected(this.selected); | 4867 this._selectSelected(this.selected); |
| 7004 }, | 4868 }, |
| 7005 | 4869 |
| 7006 _selectSelected: function(selected) { | 4870 _selectSelected: function(selected) { |
| 7007 this._selection.select(this._valueToItem(this.selected)); | 4871 this._selection.select(this._valueToItem(this.selected)); |
| 7008 // Check for items, since this array is populated only when attached | |
| 7009 // Since Number(0) is falsy, explicitly check for undefined | |
| 7010 if (this.fallbackSelection && this.items.length && (this._selection.get()
=== undefined)) { | 4872 if (this.fallbackSelection && this.items.length && (this._selection.get()
=== undefined)) { |
| 7011 this.selected = this.fallbackSelection; | 4873 this.selected = this.fallbackSelection; |
| 7012 } | 4874 } |
| 7013 }, | 4875 }, |
| 7014 | 4876 |
| 7015 _filterItem: function(node) { | 4877 _filterItem: function(node) { |
| 7016 return !this._excludedLocalNames[node.localName]; | 4878 return !this._excludedLocalNames[node.localName]; |
| 7017 }, | 4879 }, |
| 7018 | 4880 |
| 7019 _valueToItem: function(value) { | 4881 _valueToItem: function(value) { |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7056 this.toggleAttribute(this.selectedAttribute, isSelected, item); | 4918 this.toggleAttribute(this.selectedAttribute, isSelected, item); |
| 7057 } | 4919 } |
| 7058 this._selectionChange(); | 4920 this._selectionChange(); |
| 7059 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); | 4921 this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); |
| 7060 }, | 4922 }, |
| 7061 | 4923 |
| 7062 _selectionChange: function() { | 4924 _selectionChange: function() { |
| 7063 this._setSelectedItem(this._selection.get()); | 4925 this._setSelectedItem(this._selection.get()); |
| 7064 }, | 4926 }, |
| 7065 | 4927 |
| 7066 // observe items change under the given node. | |
| 7067 _observeItems: function(node) { | 4928 _observeItems: function(node) { |
| 7068 return Polymer.dom(node).observeNodes(function(mutation) { | 4929 return Polymer.dom(node).observeNodes(function(mutation) { |
| 7069 this._updateItems(); | 4930 this._updateItems(); |
| 7070 | 4931 |
| 7071 if (this._shouldUpdateSelection) { | 4932 if (this._shouldUpdateSelection) { |
| 7072 this._updateSelected(); | 4933 this._updateSelected(); |
| 7073 } | 4934 } |
| 7074 | 4935 |
| 7075 // Let other interested parties know about the change so that | |
| 7076 // we don't have to recreate mutation observers everywhere. | |
| 7077 this.fire('iron-items-changed', mutation, { | 4936 this.fire('iron-items-changed', mutation, { |
| 7078 bubbles: false, | 4937 bubbles: false, |
| 7079 cancelable: false | 4938 cancelable: false |
| 7080 }); | 4939 }); |
| 7081 }); | 4940 }); |
| 7082 }, | 4941 }, |
| 7083 | 4942 |
| 7084 _activateHandler: function(e) { | 4943 _activateHandler: function(e) { |
| 7085 var t = e.target; | 4944 var t = e.target; |
| 7086 var items = this.items; | 4945 var items = this.items; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 7100 {selected: value, item: item}, {cancelable: true}).defaultPrevented) { | 4959 {selected: value, item: item}, {cancelable: true}).defaultPrevented) { |
| 7101 this.select(value); | 4960 this.select(value); |
| 7102 } | 4961 } |
| 7103 } | 4962 } |
| 7104 | 4963 |
| 7105 }; | 4964 }; |
| 7106 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ | 4965 /** @polymerBehavior Polymer.IronMultiSelectableBehavior */ |
| 7107 Polymer.IronMultiSelectableBehaviorImpl = { | 4966 Polymer.IronMultiSelectableBehaviorImpl = { |
| 7108 properties: { | 4967 properties: { |
| 7109 | 4968 |
| 7110 /** | |
| 7111 * If true, multiple selections are allowed. | |
| 7112 */ | |
| 7113 multi: { | 4969 multi: { |
| 7114 type: Boolean, | 4970 type: Boolean, |
| 7115 value: false, | 4971 value: false, |
| 7116 observer: 'multiChanged' | 4972 observer: 'multiChanged' |
| 7117 }, | 4973 }, |
| 7118 | 4974 |
| 7119 /** | |
| 7120 * Gets or sets the selected elements. This is used instead of `selected`
when `multi` | |
| 7121 * is true. | |
| 7122 */ | |
| 7123 selectedValues: { | 4975 selectedValues: { |
| 7124 type: Array, | 4976 type: Array, |
| 7125 notify: true | 4977 notify: true |
| 7126 }, | 4978 }, |
| 7127 | 4979 |
| 7128 /** | |
| 7129 * Returns an array of currently selected items. | |
| 7130 */ | |
| 7131 selectedItems: { | 4980 selectedItems: { |
| 7132 type: Array, | 4981 type: Array, |
| 7133 readOnly: true, | 4982 readOnly: true, |
| 7134 notify: true | 4983 notify: true |
| 7135 }, | 4984 }, |
| 7136 | 4985 |
| 7137 }, | 4986 }, |
| 7138 | 4987 |
| 7139 observers: [ | 4988 observers: [ |
| 7140 '_updateSelected(selectedValues.splices)' | 4989 '_updateSelected(selectedValues.splices)' |
| 7141 ], | 4990 ], |
| 7142 | 4991 |
| 7143 /** | |
| 7144 * Selects the given value. If the `multi` property is true, then the select
ed state of the | |
| 7145 * `value` will be toggled; otherwise the `value` will be selected. | |
| 7146 * | |
| 7147 * @method select | |
| 7148 * @param {string|number} value the value to select. | |
| 7149 */ | |
| 7150 select: function(value) { | 4992 select: function(value) { |
| 7151 if (this.multi) { | 4993 if (this.multi) { |
| 7152 if (this.selectedValues) { | 4994 if (this.selectedValues) { |
| 7153 this._toggleSelected(value); | 4995 this._toggleSelected(value); |
| 7154 } else { | 4996 } else { |
| 7155 this.selectedValues = [value]; | 4997 this.selectedValues = [value]; |
| 7156 } | 4998 } |
| 7157 } else { | 4999 } else { |
| 7158 this.selected = value; | 5000 this.selected = value; |
| 7159 } | 5001 } |
| (...skipping 24 matching lines...) Expand all Loading... |
| 7184 if (this.multi) { | 5026 if (this.multi) { |
| 7185 this._selectMulti(this.selectedValues); | 5027 this._selectMulti(this.selectedValues); |
| 7186 } else { | 5028 } else { |
| 7187 this._selectSelected(this.selected); | 5029 this._selectSelected(this.selected); |
| 7188 } | 5030 } |
| 7189 }, | 5031 }, |
| 7190 | 5032 |
| 7191 _selectMulti: function(values) { | 5033 _selectMulti: function(values) { |
| 7192 if (values) { | 5034 if (values) { |
| 7193 var selectedItems = this._valuesToItems(values); | 5035 var selectedItems = this._valuesToItems(values); |
| 7194 // clear all but the current selected items | |
| 7195 this._selection.clear(selectedItems); | 5036 this._selection.clear(selectedItems); |
| 7196 // select only those not selected yet | |
| 7197 for (var i = 0; i < selectedItems.length; i++) { | 5037 for (var i = 0; i < selectedItems.length; i++) { |
| 7198 this._selection.setItemSelected(selectedItems[i], true); | 5038 this._selection.setItemSelected(selectedItems[i], true); |
| 7199 } | 5039 } |
| 7200 // Check for items, since this array is populated only when attached | |
| 7201 if (this.fallbackSelection && this.items.length && !this._selection.get(
).length) { | 5040 if (this.fallbackSelection && this.items.length && !this._selection.get(
).length) { |
| 7202 var fallback = this._valueToItem(this.fallbackSelection); | 5041 var fallback = this._valueToItem(this.fallbackSelection); |
| 7203 if (fallback) { | 5042 if (fallback) { |
| 7204 this.selectedValues = [this.fallbackSelection]; | 5043 this.selectedValues = [this.fallbackSelection]; |
| 7205 } | 5044 } |
| 7206 } | 5045 } |
| 7207 } else { | 5046 } else { |
| 7208 this._selection.clear(); | 5047 this._selection.clear(); |
| 7209 } | 5048 } |
| 7210 }, | 5049 }, |
| (...skipping 23 matching lines...) Expand all Loading... |
| 7234 return this._valueToItem(value); | 5073 return this._valueToItem(value); |
| 7235 }, this); | 5074 }, this); |
| 7236 } | 5075 } |
| 7237 }; | 5076 }; |
| 7238 | 5077 |
| 7239 /** @polymerBehavior */ | 5078 /** @polymerBehavior */ |
| 7240 Polymer.IronMultiSelectableBehavior = [ | 5079 Polymer.IronMultiSelectableBehavior = [ |
| 7241 Polymer.IronSelectableBehavior, | 5080 Polymer.IronSelectableBehavior, |
| 7242 Polymer.IronMultiSelectableBehaviorImpl | 5081 Polymer.IronMultiSelectableBehaviorImpl |
| 7243 ]; | 5082 ]; |
| 7244 /** | |
| 7245 * `Polymer.IronMenuBehavior` implements accessible menu behavior. | |
| 7246 * | |
| 7247 * @demo demo/index.html | |
| 7248 * @polymerBehavior Polymer.IronMenuBehavior | |
| 7249 */ | |
| 7250 Polymer.IronMenuBehaviorImpl = { | 5083 Polymer.IronMenuBehaviorImpl = { |
| 7251 | 5084 |
| 7252 properties: { | 5085 properties: { |
| 7253 | 5086 |
| 7254 /** | |
| 7255 * Returns the currently focused item. | |
| 7256 * @type {?Object} | |
| 7257 */ | |
| 7258 focusedItem: { | 5087 focusedItem: { |
| 7259 observer: '_focusedItemChanged', | 5088 observer: '_focusedItemChanged', |
| 7260 readOnly: true, | 5089 readOnly: true, |
| 7261 type: Object | 5090 type: Object |
| 7262 }, | 5091 }, |
| 7263 | 5092 |
| 7264 /** | |
| 7265 * The attribute to use on menu items to look up the item title. Typing th
e first | |
| 7266 * letter of an item when the menu is open focuses that item. If unset, `t
extContent` | |
| 7267 * will be used. | |
| 7268 */ | |
| 7269 attrForItemTitle: { | 5093 attrForItemTitle: { |
| 7270 type: String | 5094 type: String |
| 7271 } | 5095 } |
| 7272 }, | 5096 }, |
| 7273 | 5097 |
| 7274 hostAttributes: { | 5098 hostAttributes: { |
| 7275 'role': 'menu', | 5099 'role': 'menu', |
| 7276 'tabindex': '0' | 5100 'tabindex': '0' |
| 7277 }, | 5101 }, |
| 7278 | 5102 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 7290 'up': '_onUpKey', | 5114 'up': '_onUpKey', |
| 7291 'down': '_onDownKey', | 5115 'down': '_onDownKey', |
| 7292 'esc': '_onEscKey', | 5116 'esc': '_onEscKey', |
| 7293 'shift+tab:keydown': '_onShiftTabDown' | 5117 'shift+tab:keydown': '_onShiftTabDown' |
| 7294 }, | 5118 }, |
| 7295 | 5119 |
| 7296 attached: function() { | 5120 attached: function() { |
| 7297 this._resetTabindices(); | 5121 this._resetTabindices(); |
| 7298 }, | 5122 }, |
| 7299 | 5123 |
| 7300 /** | |
| 7301 * Selects the given value. If the `multi` property is true, then the select
ed state of the | |
| 7302 * `value` will be toggled; otherwise the `value` will be selected. | |
| 7303 * | |
| 7304 * @param {string|number} value the value to select. | |
| 7305 */ | |
| 7306 select: function(value) { | 5124 select: function(value) { |
| 7307 // Cancel automatically focusing a default item if the menu received focus | |
| 7308 // through a user action selecting a particular item. | |
| 7309 if (this._defaultFocusAsync) { | 5125 if (this._defaultFocusAsync) { |
| 7310 this.cancelAsync(this._defaultFocusAsync); | 5126 this.cancelAsync(this._defaultFocusAsync); |
| 7311 this._defaultFocusAsync = null; | 5127 this._defaultFocusAsync = null; |
| 7312 } | 5128 } |
| 7313 var item = this._valueToItem(value); | 5129 var item = this._valueToItem(value); |
| 7314 if (item && item.hasAttribute('disabled')) return; | 5130 if (item && item.hasAttribute('disabled')) return; |
| 7315 this._setFocusedItem(item); | 5131 this._setFocusedItem(item); |
| 7316 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); | 5132 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments); |
| 7317 }, | 5133 }, |
| 7318 | 5134 |
| 7319 /** | |
| 7320 * Resets all tabindex attributes to the appropriate value based on the | |
| 7321 * current selection state. The appropriate value is `0` (focusable) for | |
| 7322 * the default selected item, and `-1` (not keyboard focusable) for all | |
| 7323 * other items. | |
| 7324 */ | |
| 7325 _resetTabindices: function() { | 5135 _resetTabindices: function() { |
| 7326 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[
0]) : this.selectedItem; | 5136 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[
0]) : this.selectedItem; |
| 7327 | 5137 |
| 7328 this.items.forEach(function(item) { | 5138 this.items.forEach(function(item) { |
| 7329 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); | 5139 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1'); |
| 7330 }, this); | 5140 }, this); |
| 7331 }, | 5141 }, |
| 7332 | 5142 |
| 7333 /** | |
| 7334 * Sets appropriate ARIA based on whether or not the menu is meant to be | |
| 7335 * multi-selectable. | |
| 7336 * | |
| 7337 * @param {boolean} multi True if the menu should be multi-selectable. | |
| 7338 */ | |
| 7339 _updateMultiselectable: function(multi) { | 5143 _updateMultiselectable: function(multi) { |
| 7340 if (multi) { | 5144 if (multi) { |
| 7341 this.setAttribute('aria-multiselectable', 'true'); | 5145 this.setAttribute('aria-multiselectable', 'true'); |
| 7342 } else { | 5146 } else { |
| 7343 this.removeAttribute('aria-multiselectable'); | 5147 this.removeAttribute('aria-multiselectable'); |
| 7344 } | 5148 } |
| 7345 }, | 5149 }, |
| 7346 | 5150 |
| 7347 /** | |
| 7348 * Given a KeyboardEvent, this method will focus the appropriate item in the | |
| 7349 * menu (if there is a relevant item, and it is possible to focus it). | |
| 7350 * | |
| 7351 * @param {KeyboardEvent} event A KeyboardEvent. | |
| 7352 */ | |
| 7353 _focusWithKeyboardEvent: function(event) { | 5151 _focusWithKeyboardEvent: function(event) { |
| 7354 for (var i = 0, item; item = this.items[i]; i++) { | 5152 for (var i = 0, item; item = this.items[i]; i++) { |
| 7355 var attr = this.attrForItemTitle || 'textContent'; | 5153 var attr = this.attrForItemTitle || 'textContent'; |
| 7356 var title = item[attr] || item.getAttribute(attr); | 5154 var title = item[attr] || item.getAttribute(attr); |
| 7357 | 5155 |
| 7358 if (!item.hasAttribute('disabled') && title && | 5156 if (!item.hasAttribute('disabled') && title && |
| 7359 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k
eyCode).toLowerCase()) { | 5157 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k
eyCode).toLowerCase()) { |
| 7360 this._setFocusedItem(item); | 5158 this._setFocusedItem(item); |
| 7361 break; | 5159 break; |
| 7362 } | 5160 } |
| 7363 } | 5161 } |
| 7364 }, | 5162 }, |
| 7365 | 5163 |
| 7366 /** | |
| 7367 * Focuses the previous item (relative to the currently focused item) in the | |
| 7368 * menu, disabled items will be skipped. | |
| 7369 * Loop until length + 1 to handle case of single item in menu. | |
| 7370 */ | |
| 7371 _focusPrevious: function() { | 5164 _focusPrevious: function() { |
| 7372 var length = this.items.length; | 5165 var length = this.items.length; |
| 7373 var curFocusIndex = Number(this.indexOf(this.focusedItem)); | 5166 var curFocusIndex = Number(this.indexOf(this.focusedItem)); |
| 7374 for (var i = 1; i < length + 1; i++) { | 5167 for (var i = 1; i < length + 1; i++) { |
| 7375 var item = this.items[(curFocusIndex - i + length) % length]; | 5168 var item = this.items[(curFocusIndex - i + length) % length]; |
| 7376 if (!item.hasAttribute('disabled')) { | 5169 if (!item.hasAttribute('disabled')) { |
| 7377 this._setFocusedItem(item); | 5170 this._setFocusedItem(item); |
| 7378 return; | 5171 return; |
| 7379 } | 5172 } |
| 7380 } | 5173 } |
| 7381 }, | 5174 }, |
| 7382 | 5175 |
| 7383 /** | |
| 7384 * Focuses the next item (relative to the currently focused item) in the | |
| 7385 * menu, disabled items will be skipped. | |
| 7386 * Loop until length + 1 to handle case of single item in menu. | |
| 7387 */ | |
| 7388 _focusNext: function() { | 5176 _focusNext: function() { |
| 7389 var length = this.items.length; | 5177 var length = this.items.length; |
| 7390 var curFocusIndex = Number(this.indexOf(this.focusedItem)); | 5178 var curFocusIndex = Number(this.indexOf(this.focusedItem)); |
| 7391 for (var i = 1; i < length + 1; i++) { | 5179 for (var i = 1; i < length + 1; i++) { |
| 7392 var item = this.items[(curFocusIndex + i) % length]; | 5180 var item = this.items[(curFocusIndex + i) % length]; |
| 7393 if (!item.hasAttribute('disabled')) { | 5181 if (!item.hasAttribute('disabled')) { |
| 7394 this._setFocusedItem(item); | 5182 this._setFocusedItem(item); |
| 7395 return; | 5183 return; |
| 7396 } | 5184 } |
| 7397 } | 5185 } |
| 7398 }, | 5186 }, |
| 7399 | 5187 |
| 7400 /** | |
| 7401 * Mutates items in the menu based on provided selection details, so that | |
| 7402 * all items correctly reflect selection state. | |
| 7403 * | |
| 7404 * @param {Element} item An item in the menu. | |
| 7405 * @param {boolean} isSelected True if the item should be shown in a | |
| 7406 * selected state, otherwise false. | |
| 7407 */ | |
| 7408 _applySelection: function(item, isSelected) { | 5188 _applySelection: function(item, isSelected) { |
| 7409 if (isSelected) { | 5189 if (isSelected) { |
| 7410 item.setAttribute('aria-selected', 'true'); | 5190 item.setAttribute('aria-selected', 'true'); |
| 7411 } else { | 5191 } else { |
| 7412 item.removeAttribute('aria-selected'); | 5192 item.removeAttribute('aria-selected'); |
| 7413 } | 5193 } |
| 7414 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); | 5194 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); |
| 7415 }, | 5195 }, |
| 7416 | 5196 |
| 7417 /** | |
| 7418 * Discretely updates tabindex values among menu items as the focused item | |
| 7419 * changes. | |
| 7420 * | |
| 7421 * @param {Element} focusedItem The element that is currently focused. | |
| 7422 * @param {?Element} old The last element that was considered focused, if | |
| 7423 * applicable. | |
| 7424 */ | |
| 7425 _focusedItemChanged: function(focusedItem, old) { | 5197 _focusedItemChanged: function(focusedItem, old) { |
| 7426 old && old.setAttribute('tabindex', '-1'); | 5198 old && old.setAttribute('tabindex', '-1'); |
| 7427 if (focusedItem) { | 5199 if (focusedItem) { |
| 7428 focusedItem.setAttribute('tabindex', '0'); | 5200 focusedItem.setAttribute('tabindex', '0'); |
| 7429 focusedItem.focus(); | 5201 focusedItem.focus(); |
| 7430 } | 5202 } |
| 7431 }, | 5203 }, |
| 7432 | 5204 |
| 7433 /** | |
| 7434 * A handler that responds to mutation changes related to the list of items | |
| 7435 * in the menu. | |
| 7436 * | |
| 7437 * @param {CustomEvent} event An event containing mutation records as its | |
| 7438 * detail. | |
| 7439 */ | |
| 7440 _onIronItemsChanged: function(event) { | 5205 _onIronItemsChanged: function(event) { |
| 7441 if (event.detail.addedNodes.length) { | 5206 if (event.detail.addedNodes.length) { |
| 7442 this._resetTabindices(); | 5207 this._resetTabindices(); |
| 7443 } | 5208 } |
| 7444 }, | 5209 }, |
| 7445 | 5210 |
| 7446 /** | |
| 7447 * Handler that is called when a shift+tab keypress is detected by the menu. | |
| 7448 * | |
| 7449 * @param {CustomEvent} event A key combination event. | |
| 7450 */ | |
| 7451 _onShiftTabDown: function(event) { | 5211 _onShiftTabDown: function(event) { |
| 7452 var oldTabIndex = this.getAttribute('tabindex'); | 5212 var oldTabIndex = this.getAttribute('tabindex'); |
| 7453 | 5213 |
| 7454 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; | 5214 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; |
| 7455 | 5215 |
| 7456 this._setFocusedItem(null); | 5216 this._setFocusedItem(null); |
| 7457 | 5217 |
| 7458 this.setAttribute('tabindex', '-1'); | 5218 this.setAttribute('tabindex', '-1'); |
| 7459 | 5219 |
| 7460 this.async(function() { | 5220 this.async(function() { |
| 7461 this.setAttribute('tabindex', oldTabIndex); | 5221 this.setAttribute('tabindex', oldTabIndex); |
| 7462 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; | 5222 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
| 7463 // NOTE(cdata): polymer/polymer#1305 | |
| 7464 }, 1); | 5223 }, 1); |
| 7465 }, | 5224 }, |
| 7466 | 5225 |
| 7467 /** | |
| 7468 * Handler that is called when the menu receives focus. | |
| 7469 * | |
| 7470 * @param {FocusEvent} event A focus event. | |
| 7471 */ | |
| 7472 _onFocus: function(event) { | 5226 _onFocus: function(event) { |
| 7473 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { | 5227 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { |
| 7474 // do not focus the menu itself | |
| 7475 return; | 5228 return; |
| 7476 } | 5229 } |
| 7477 | 5230 |
| 7478 // Do not focus the selected tab if the deepest target is part of the | |
| 7479 // menu element's local DOM and is focusable. | |
| 7480 var rootTarget = /** @type {?HTMLElement} */( | 5231 var rootTarget = /** @type {?HTMLElement} */( |
| 7481 Polymer.dom(event).rootTarget); | 5232 Polymer.dom(event).rootTarget); |
| 7482 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && !
this.isLightDescendant(rootTarget)) { | 5233 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && !
this.isLightDescendant(rootTarget)) { |
| 7483 return; | 5234 return; |
| 7484 } | 5235 } |
| 7485 | 5236 |
| 7486 // clear the cached focus item | |
| 7487 this._defaultFocusAsync = this.async(function() { | 5237 this._defaultFocusAsync = this.async(function() { |
| 7488 // focus the selected item when the menu receives focus, or the first it
em | |
| 7489 // if no item is selected | |
| 7490 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem
s[0]) : this.selectedItem; | 5238 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem
s[0]) : this.selectedItem; |
| 7491 | 5239 |
| 7492 this._setFocusedItem(null); | 5240 this._setFocusedItem(null); |
| 7493 | 5241 |
| 7494 if (selectedItem) { | 5242 if (selectedItem) { |
| 7495 this._setFocusedItem(selectedItem); | 5243 this._setFocusedItem(selectedItem); |
| 7496 } else if (this.items[0]) { | 5244 } else if (this.items[0]) { |
| 7497 // We find the first none-disabled item (if one exists) | |
| 7498 this._focusNext(); | 5245 this._focusNext(); |
| 7499 } | 5246 } |
| 7500 }); | 5247 }); |
| 7501 }, | 5248 }, |
| 7502 | 5249 |
| 7503 /** | |
| 7504 * Handler that is called when the up key is pressed. | |
| 7505 * | |
| 7506 * @param {CustomEvent} event A key combination event. | |
| 7507 */ | |
| 7508 _onUpKey: function(event) { | 5250 _onUpKey: function(event) { |
| 7509 // up and down arrows moves the focus | |
| 7510 this._focusPrevious(); | 5251 this._focusPrevious(); |
| 7511 event.detail.keyboardEvent.preventDefault(); | 5252 event.detail.keyboardEvent.preventDefault(); |
| 7512 }, | 5253 }, |
| 7513 | 5254 |
| 7514 /** | |
| 7515 * Handler that is called when the down key is pressed. | |
| 7516 * | |
| 7517 * @param {CustomEvent} event A key combination event. | |
| 7518 */ | |
| 7519 _onDownKey: function(event) { | 5255 _onDownKey: function(event) { |
| 7520 this._focusNext(); | 5256 this._focusNext(); |
| 7521 event.detail.keyboardEvent.preventDefault(); | 5257 event.detail.keyboardEvent.preventDefault(); |
| 7522 }, | 5258 }, |
| 7523 | 5259 |
| 7524 /** | |
| 7525 * Handler that is called when the esc key is pressed. | |
| 7526 * | |
| 7527 * @param {CustomEvent} event A key combination event. | |
| 7528 */ | |
| 7529 _onEscKey: function(event) { | 5260 _onEscKey: function(event) { |
| 7530 // esc blurs the control | |
| 7531 this.focusedItem.blur(); | 5261 this.focusedItem.blur(); |
| 7532 }, | 5262 }, |
| 7533 | 5263 |
| 7534 /** | |
| 7535 * Handler that is called when a keydown event is detected. | |
| 7536 * | |
| 7537 * @param {KeyboardEvent} event A keyboard event. | |
| 7538 */ | |
| 7539 _onKeydown: function(event) { | 5264 _onKeydown: function(event) { |
| 7540 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) { | 5265 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) { |
| 7541 // all other keys focus the menu item starting with that character | |
| 7542 this._focusWithKeyboardEvent(event); | 5266 this._focusWithKeyboardEvent(event); |
| 7543 } | 5267 } |
| 7544 event.stopPropagation(); | 5268 event.stopPropagation(); |
| 7545 }, | 5269 }, |
| 7546 | 5270 |
| 7547 // override _activateHandler | |
| 7548 _activateHandler: function(event) { | 5271 _activateHandler: function(event) { |
| 7549 Polymer.IronSelectableBehavior._activateHandler.call(this, event); | 5272 Polymer.IronSelectableBehavior._activateHandler.call(this, event); |
| 7550 event.stopPropagation(); | 5273 event.stopPropagation(); |
| 7551 } | 5274 } |
| 7552 }; | 5275 }; |
| 7553 | 5276 |
| 7554 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; | 5277 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
| 7555 | 5278 |
| 7556 /** @polymerBehavior Polymer.IronMenuBehavior */ | 5279 /** @polymerBehavior Polymer.IronMenuBehavior */ |
| 7557 Polymer.IronMenuBehavior = [ | 5280 Polymer.IronMenuBehavior = [ |
| 7558 Polymer.IronMultiSelectableBehavior, | 5281 Polymer.IronMultiSelectableBehavior, |
| 7559 Polymer.IronA11yKeysBehavior, | 5282 Polymer.IronA11yKeysBehavior, |
| 7560 Polymer.IronMenuBehaviorImpl | 5283 Polymer.IronMenuBehaviorImpl |
| 7561 ]; | 5284 ]; |
| 7562 (function() { | 5285 (function() { |
| 7563 Polymer({ | 5286 Polymer({ |
| 7564 is: 'paper-menu', | 5287 is: 'paper-menu', |
| 7565 | 5288 |
| 7566 behaviors: [ | 5289 behaviors: [ |
| 7567 Polymer.IronMenuBehavior | 5290 Polymer.IronMenuBehavior |
| 7568 ] | 5291 ] |
| 7569 }); | 5292 }); |
| 7570 })(); | 5293 })(); |
| 7571 /** | |
| 7572 `Polymer.IronFitBehavior` fits an element in another element using `max-height`
and `max-width`, and | |
| 7573 optionally centers it in the window or another element. | |
| 7574 | |
| 7575 The element will only be sized and/or positioned if it has not already been size
d and/or positioned | |
| 7576 by CSS. | |
| 7577 | |
| 7578 CSS properties | Action | |
| 7579 -----------------------------|------------------------------------------- | |
| 7580 `position` set | Element is not centered horizontally or verticall
y | |
| 7581 `top` or `bottom` set | Element is not vertically centered | |
| 7582 `left` or `right` set | Element is not horizontally centered | |
| 7583 `max-height` set | Element respects `max-height` | |
| 7584 `max-width` set | Element respects `max-width` | |
| 7585 | |
| 7586 `Polymer.IronFitBehavior` can position an element into another element using | |
| 7587 `verticalAlign` and `horizontalAlign`. This will override the element's css posi
tion. | |
| 7588 | |
| 7589 <div class="container"> | |
| 7590 <iron-fit-impl vertical-align="top" horizontal-align="auto"> | |
| 7591 Positioned into the container | |
| 7592 </iron-fit-impl> | |
| 7593 </div> | |
| 7594 | |
| 7595 Use `noOverlap` to position the element around another element without overlappi
ng it. | |
| 7596 | |
| 7597 <div class="container"> | |
| 7598 <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto"> | |
| 7599 Positioned around the container | |
| 7600 </iron-fit-impl> | |
| 7601 </div> | |
| 7602 | |
| 7603 @demo demo/index.html | |
| 7604 @polymerBehavior | |
| 7605 */ | |
| 7606 | 5294 |
| 7607 Polymer.IronFitBehavior = { | 5295 Polymer.IronFitBehavior = { |
| 7608 | 5296 |
| 7609 properties: { | 5297 properties: { |
| 7610 | 5298 |
| 7611 /** | |
| 7612 * The element that will receive a `max-height`/`width`. By default it is
the same as `this`, | |
| 7613 * but it can be set to a child element. This is useful, for example, for
implementing a | |
| 7614 * scrolling region inside the element. | |
| 7615 * @type {!Element} | |
| 7616 */ | |
| 7617 sizingTarget: { | 5299 sizingTarget: { |
| 7618 type: Object, | 5300 type: Object, |
| 7619 value: function() { | 5301 value: function() { |
| 7620 return this; | 5302 return this; |
| 7621 } | 5303 } |
| 7622 }, | 5304 }, |
| 7623 | 5305 |
| 7624 /** | |
| 7625 * The element to fit `this` into. | |
| 7626 */ | |
| 7627 fitInto: { | 5306 fitInto: { |
| 7628 type: Object, | 5307 type: Object, |
| 7629 value: window | 5308 value: window |
| 7630 }, | 5309 }, |
| 7631 | 5310 |
| 7632 /** | |
| 7633 * Will position the element around the positionTarget without overlapping
it. | |
| 7634 */ | |
| 7635 noOverlap: { | 5311 noOverlap: { |
| 7636 type: Boolean | 5312 type: Boolean |
| 7637 }, | 5313 }, |
| 7638 | 5314 |
| 7639 /** | |
| 7640 * The element that should be used to position the element. If not set, it
will | |
| 7641 * default to the parent node. | |
| 7642 * @type {!Element} | |
| 7643 */ | |
| 7644 positionTarget: { | 5315 positionTarget: { |
| 7645 type: Element | 5316 type: Element |
| 7646 }, | 5317 }, |
| 7647 | 5318 |
| 7648 /** | |
| 7649 * The orientation against which to align the element horizontally | |
| 7650 * relative to the `positionTarget`. Possible values are "left", "right",
"auto". | |
| 7651 */ | |
| 7652 horizontalAlign: { | 5319 horizontalAlign: { |
| 7653 type: String | 5320 type: String |
| 7654 }, | 5321 }, |
| 7655 | 5322 |
| 7656 /** | |
| 7657 * The orientation against which to align the element vertically | |
| 7658 * relative to the `positionTarget`. Possible values are "top", "bottom",
"auto". | |
| 7659 */ | |
| 7660 verticalAlign: { | 5323 verticalAlign: { |
| 7661 type: String | 5324 type: String |
| 7662 }, | 5325 }, |
| 7663 | 5326 |
| 7664 /** | |
| 7665 * If true, it will use `horizontalAlign` and `verticalAlign` values as pr
eferred alignment | |
| 7666 * and if there's not enough space, it will pick the values which minimize
the cropping. | |
| 7667 */ | |
| 7668 dynamicAlign: { | 5327 dynamicAlign: { |
| 7669 type: Boolean | 5328 type: Boolean |
| 7670 }, | 5329 }, |
| 7671 | 5330 |
| 7672 /** | |
| 7673 * The same as setting margin-left and margin-right css properties. | |
| 7674 * @deprecated | |
| 7675 */ | |
| 7676 horizontalOffset: { | 5331 horizontalOffset: { |
| 7677 type: Number, | 5332 type: Number, |
| 7678 value: 0, | 5333 value: 0, |
| 7679 notify: true | 5334 notify: true |
| 7680 }, | 5335 }, |
| 7681 | 5336 |
| 7682 /** | |
| 7683 * The same as setting margin-top and margin-bottom css properties. | |
| 7684 * @deprecated | |
| 7685 */ | |
| 7686 verticalOffset: { | 5337 verticalOffset: { |
| 7687 type: Number, | 5338 type: Number, |
| 7688 value: 0, | 5339 value: 0, |
| 7689 notify: true | 5340 notify: true |
| 7690 }, | 5341 }, |
| 7691 | 5342 |
| 7692 /** | |
| 7693 * Set to true to auto-fit on attach. | |
| 7694 */ | |
| 7695 autoFitOnAttach: { | 5343 autoFitOnAttach: { |
| 7696 type: Boolean, | 5344 type: Boolean, |
| 7697 value: false | 5345 value: false |
| 7698 }, | 5346 }, |
| 7699 | 5347 |
| 7700 /** @type {?Object} */ | 5348 /** @type {?Object} */ |
| 7701 _fitInfo: { | 5349 _fitInfo: { |
| 7702 type: Object | 5350 type: Object |
| 7703 } | 5351 } |
| 7704 }, | 5352 }, |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 7736 get _fitTop() { | 5384 get _fitTop() { |
| 7737 var fitTop; | 5385 var fitTop; |
| 7738 if (this.fitInto === window) { | 5386 if (this.fitInto === window) { |
| 7739 fitTop = 0; | 5387 fitTop = 0; |
| 7740 } else { | 5388 } else { |
| 7741 fitTop = this.fitInto.getBoundingClientRect().top; | 5389 fitTop = this.fitInto.getBoundingClientRect().top; |
| 7742 } | 5390 } |
| 7743 return fitTop; | 5391 return fitTop; |
| 7744 }, | 5392 }, |
| 7745 | 5393 |
| 7746 /** | |
| 7747 * The element that should be used to position the element, | |
| 7748 * if no position target is configured. | |
| 7749 */ | |
| 7750 get _defaultPositionTarget() { | 5394 get _defaultPositionTarget() { |
| 7751 var parent = Polymer.dom(this).parentNode; | 5395 var parent = Polymer.dom(this).parentNode; |
| 7752 | 5396 |
| 7753 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | 5397 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 7754 parent = parent.host; | 5398 parent = parent.host; |
| 7755 } | 5399 } |
| 7756 | 5400 |
| 7757 return parent; | 5401 return parent; |
| 7758 }, | 5402 }, |
| 7759 | 5403 |
| 7760 /** | |
| 7761 * The horizontal align value, accounting for the RTL/LTR text direction. | |
| 7762 */ | |
| 7763 get _localeHorizontalAlign() { | 5404 get _localeHorizontalAlign() { |
| 7764 if (this._isRTL) { | 5405 if (this._isRTL) { |
| 7765 // In RTL, "left" becomes "right". | |
| 7766 if (this.horizontalAlign === 'right') { | 5406 if (this.horizontalAlign === 'right') { |
| 7767 return 'left'; | 5407 return 'left'; |
| 7768 } | 5408 } |
| 7769 if (this.horizontalAlign === 'left') { | 5409 if (this.horizontalAlign === 'left') { |
| 7770 return 'right'; | 5410 return 'right'; |
| 7771 } | 5411 } |
| 7772 } | 5412 } |
| 7773 return this.horizontalAlign; | 5413 return this.horizontalAlign; |
| 7774 }, | 5414 }, |
| 7775 | 5415 |
| 7776 attached: function() { | 5416 attached: function() { |
| 7777 // Memoize this to avoid expensive calculations & relayouts. | |
| 7778 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; | 5417 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; |
| 7779 this.positionTarget = this.positionTarget || this._defaultPositionTarget; | 5418 this.positionTarget = this.positionTarget || this._defaultPositionTarget; |
| 7780 if (this.autoFitOnAttach) { | 5419 if (this.autoFitOnAttach) { |
| 7781 if (window.getComputedStyle(this).display === 'none') { | 5420 if (window.getComputedStyle(this).display === 'none') { |
| 7782 setTimeout(function() { | 5421 setTimeout(function() { |
| 7783 this.fit(); | 5422 this.fit(); |
| 7784 }.bind(this)); | 5423 }.bind(this)); |
| 7785 } else { | 5424 } else { |
| 7786 this.fit(); | 5425 this.fit(); |
| 7787 } | 5426 } |
| 7788 } | 5427 } |
| 7789 }, | 5428 }, |
| 7790 | 5429 |
| 7791 /** | |
| 7792 * Positions and fits the element into the `fitInto` element. | |
| 7793 */ | |
| 7794 fit: function() { | 5430 fit: function() { |
| 7795 this.position(); | 5431 this.position(); |
| 7796 this.constrain(); | 5432 this.constrain(); |
| 7797 this.center(); | 5433 this.center(); |
| 7798 }, | 5434 }, |
| 7799 | 5435 |
| 7800 /** | |
| 7801 * Memoize information needed to position and size the target element. | |
| 7802 * @suppress {deprecated} | |
| 7803 */ | |
| 7804 _discoverInfo: function() { | 5436 _discoverInfo: function() { |
| 7805 if (this._fitInfo) { | 5437 if (this._fitInfo) { |
| 7806 return; | 5438 return; |
| 7807 } | 5439 } |
| 7808 var target = window.getComputedStyle(this); | 5440 var target = window.getComputedStyle(this); |
| 7809 var sizer = window.getComputedStyle(this.sizingTarget); | 5441 var sizer = window.getComputedStyle(this.sizingTarget); |
| 7810 | 5442 |
| 7811 this._fitInfo = { | 5443 this._fitInfo = { |
| 7812 inlineStyle: { | 5444 inlineStyle: { |
| 7813 top: this.style.top || '', | 5445 top: this.style.top || '', |
| (...skipping 18 matching lines...) Expand all Loading... |
| 7832 minHeight: parseInt(sizer.minHeight, 10) || 0 | 5464 minHeight: parseInt(sizer.minHeight, 10) || 0 |
| 7833 }, | 5465 }, |
| 7834 margin: { | 5466 margin: { |
| 7835 top: parseInt(target.marginTop, 10) || 0, | 5467 top: parseInt(target.marginTop, 10) || 0, |
| 7836 right: parseInt(target.marginRight, 10) || 0, | 5468 right: parseInt(target.marginRight, 10) || 0, |
| 7837 bottom: parseInt(target.marginBottom, 10) || 0, | 5469 bottom: parseInt(target.marginBottom, 10) || 0, |
| 7838 left: parseInt(target.marginLeft, 10) || 0 | 5470 left: parseInt(target.marginLeft, 10) || 0 |
| 7839 } | 5471 } |
| 7840 }; | 5472 }; |
| 7841 | 5473 |
| 7842 // Support these properties until they are removed. | |
| 7843 if (this.verticalOffset) { | 5474 if (this.verticalOffset) { |
| 7844 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf
fset; | 5475 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf
fset; |
| 7845 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || ''; | 5476 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || ''; |
| 7846 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || ''; | 5477 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || ''; |
| 7847 this.style.marginTop = this.style.marginBottom = this.verticalOffset + '
px'; | 5478 this.style.marginTop = this.style.marginBottom = this.verticalOffset + '
px'; |
| 7848 } | 5479 } |
| 7849 if (this.horizontalOffset) { | 5480 if (this.horizontalOffset) { |
| 7850 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal
Offset; | 5481 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal
Offset; |
| 7851 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || ''; | 5482 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || ''; |
| 7852 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || ''; | 5483 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || ''; |
| 7853 this.style.marginLeft = this.style.marginRight = this.horizontalOffset +
'px'; | 5484 this.style.marginLeft = this.style.marginRight = this.horizontalOffset +
'px'; |
| 7854 } | 5485 } |
| 7855 }, | 5486 }, |
| 7856 | 5487 |
| 7857 /** | |
| 7858 * Resets the target element's position and size constraints, and clear | |
| 7859 * the memoized data. | |
| 7860 */ | |
| 7861 resetFit: function() { | 5488 resetFit: function() { |
| 7862 var info = this._fitInfo || {}; | 5489 var info = this._fitInfo || {}; |
| 7863 for (var property in info.sizerInlineStyle) { | 5490 for (var property in info.sizerInlineStyle) { |
| 7864 this.sizingTarget.style[property] = info.sizerInlineStyle[property]; | 5491 this.sizingTarget.style[property] = info.sizerInlineStyle[property]; |
| 7865 } | 5492 } |
| 7866 for (var property in info.inlineStyle) { | 5493 for (var property in info.inlineStyle) { |
| 7867 this.style[property] = info.inlineStyle[property]; | 5494 this.style[property] = info.inlineStyle[property]; |
| 7868 } | 5495 } |
| 7869 | 5496 |
| 7870 this._fitInfo = null; | 5497 this._fitInfo = null; |
| 7871 }, | 5498 }, |
| 7872 | 5499 |
| 7873 /** | |
| 7874 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after | |
| 7875 * the element or the `fitInto` element has been resized, or if any of the | |
| 7876 * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated
. | |
| 7877 * It preserves the scroll position of the sizingTarget. | |
| 7878 */ | |
| 7879 refit: function() { | 5500 refit: function() { |
| 7880 var scrollLeft = this.sizingTarget.scrollLeft; | 5501 var scrollLeft = this.sizingTarget.scrollLeft; |
| 7881 var scrollTop = this.sizingTarget.scrollTop; | 5502 var scrollTop = this.sizingTarget.scrollTop; |
| 7882 this.resetFit(); | 5503 this.resetFit(); |
| 7883 this.fit(); | 5504 this.fit(); |
| 7884 this.sizingTarget.scrollLeft = scrollLeft; | 5505 this.sizingTarget.scrollLeft = scrollLeft; |
| 7885 this.sizingTarget.scrollTop = scrollTop; | 5506 this.sizingTarget.scrollTop = scrollTop; |
| 7886 }, | 5507 }, |
| 7887 | 5508 |
| 7888 /** | |
| 7889 * Positions the element according to `horizontalAlign, verticalAlign`. | |
| 7890 */ | |
| 7891 position: function() { | 5509 position: function() { |
| 7892 if (!this.horizontalAlign && !this.verticalAlign) { | 5510 if (!this.horizontalAlign && !this.verticalAlign) { |
| 7893 // needs to be centered, and it is done after constrain. | |
| 7894 return; | 5511 return; |
| 7895 } | 5512 } |
| 7896 this._discoverInfo(); | 5513 this._discoverInfo(); |
| 7897 | 5514 |
| 7898 this.style.position = 'fixed'; | 5515 this.style.position = 'fixed'; |
| 7899 // Need border-box for margin/padding. | |
| 7900 this.sizingTarget.style.boxSizing = 'border-box'; | 5516 this.sizingTarget.style.boxSizing = 'border-box'; |
| 7901 // Set to 0, 0 in order to discover any offset caused by parent stacking c
ontexts. | |
| 7902 this.style.left = '0px'; | 5517 this.style.left = '0px'; |
| 7903 this.style.top = '0px'; | 5518 this.style.top = '0px'; |
| 7904 | 5519 |
| 7905 var rect = this.getBoundingClientRect(); | 5520 var rect = this.getBoundingClientRect(); |
| 7906 var positionRect = this.__getNormalizedRect(this.positionTarget); | 5521 var positionRect = this.__getNormalizedRect(this.positionTarget); |
| 7907 var fitRect = this.__getNormalizedRect(this.fitInto); | 5522 var fitRect = this.__getNormalizedRect(this.fitInto); |
| 7908 | 5523 |
| 7909 var margin = this._fitInfo.margin; | 5524 var margin = this._fitInfo.margin; |
| 7910 | 5525 |
| 7911 // Consider the margin as part of the size for position calculations. | |
| 7912 var size = { | 5526 var size = { |
| 7913 width: rect.width + margin.left + margin.right, | 5527 width: rect.width + margin.left + margin.right, |
| 7914 height: rect.height + margin.top + margin.bottom | 5528 height: rect.height + margin.top + margin.bottom |
| 7915 }; | 5529 }; |
| 7916 | 5530 |
| 7917 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic
alAlign, size, positionRect, fitRect); | 5531 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic
alAlign, size, positionRect, fitRect); |
| 7918 | 5532 |
| 7919 var left = position.left + margin.left; | 5533 var left = position.left + margin.left; |
| 7920 var top = position.top + margin.top; | 5534 var top = position.top + margin.top; |
| 7921 | 5535 |
| 7922 // Use original size (without margin). | |
| 7923 var right = Math.min(fitRect.right - margin.right, left + rect.width); | 5536 var right = Math.min(fitRect.right - margin.right, left + rect.width); |
| 7924 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height); | 5537 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height); |
| 7925 | 5538 |
| 7926 var minWidth = this._fitInfo.sizedBy.minWidth; | 5539 var minWidth = this._fitInfo.sizedBy.minWidth; |
| 7927 var minHeight = this._fitInfo.sizedBy.minHeight; | 5540 var minHeight = this._fitInfo.sizedBy.minHeight; |
| 7928 if (left < margin.left) { | 5541 if (left < margin.left) { |
| 7929 left = margin.left; | 5542 left = margin.left; |
| 7930 if (right - left < minWidth) { | 5543 if (right - left < minWidth) { |
| 7931 left = right - minWidth; | 5544 left = right - minWidth; |
| 7932 } | 5545 } |
| 7933 } | 5546 } |
| 7934 if (top < margin.top) { | 5547 if (top < margin.top) { |
| 7935 top = margin.top; | 5548 top = margin.top; |
| 7936 if (bottom - top < minHeight) { | 5549 if (bottom - top < minHeight) { |
| 7937 top = bottom - minHeight; | 5550 top = bottom - minHeight; |
| 7938 } | 5551 } |
| 7939 } | 5552 } |
| 7940 | 5553 |
| 7941 this.sizingTarget.style.maxWidth = (right - left) + 'px'; | 5554 this.sizingTarget.style.maxWidth = (right - left) + 'px'; |
| 7942 this.sizingTarget.style.maxHeight = (bottom - top) + 'px'; | 5555 this.sizingTarget.style.maxHeight = (bottom - top) + 'px'; |
| 7943 | 5556 |
| 7944 // Remove the offset caused by any stacking context. | |
| 7945 this.style.left = (left - rect.left) + 'px'; | 5557 this.style.left = (left - rect.left) + 'px'; |
| 7946 this.style.top = (top - rect.top) + 'px'; | 5558 this.style.top = (top - rect.top) + 'px'; |
| 7947 }, | 5559 }, |
| 7948 | 5560 |
| 7949 /** | |
| 7950 * Constrains the size of the element to `fitInto` by setting `max-height` | |
| 7951 * and/or `max-width`. | |
| 7952 */ | |
| 7953 constrain: function() { | 5561 constrain: function() { |
| 7954 if (this.horizontalAlign || this.verticalAlign) { | 5562 if (this.horizontalAlign || this.verticalAlign) { |
| 7955 return; | 5563 return; |
| 7956 } | 5564 } |
| 7957 this._discoverInfo(); | 5565 this._discoverInfo(); |
| 7958 | 5566 |
| 7959 var info = this._fitInfo; | 5567 var info = this._fitInfo; |
| 7960 // position at (0px, 0px) if not already positioned, so we can measure the
natural size. | |
| 7961 if (!info.positionedBy.vertically) { | 5568 if (!info.positionedBy.vertically) { |
| 7962 this.style.position = 'fixed'; | 5569 this.style.position = 'fixed'; |
| 7963 this.style.top = '0px'; | 5570 this.style.top = '0px'; |
| 7964 } | 5571 } |
| 7965 if (!info.positionedBy.horizontally) { | 5572 if (!info.positionedBy.horizontally) { |
| 7966 this.style.position = 'fixed'; | 5573 this.style.position = 'fixed'; |
| 7967 this.style.left = '0px'; | 5574 this.style.left = '0px'; |
| 7968 } | 5575 } |
| 7969 | 5576 |
| 7970 // need border-box for margin/padding | |
| 7971 this.sizingTarget.style.boxSizing = 'border-box'; | 5577 this.sizingTarget.style.boxSizing = 'border-box'; |
| 7972 // constrain the width and height if not already set | |
| 7973 var rect = this.getBoundingClientRect(); | 5578 var rect = this.getBoundingClientRect(); |
| 7974 if (!info.sizedBy.height) { | 5579 if (!info.sizedBy.height) { |
| 7975 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom'
, 'Height'); | 5580 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom'
, 'Height'); |
| 7976 } | 5581 } |
| 7977 if (!info.sizedBy.width) { | 5582 if (!info.sizedBy.width) { |
| 7978 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ
t', 'Width'); | 5583 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ
t', 'Width'); |
| 7979 } | 5584 } |
| 7980 }, | 5585 }, |
| 7981 | 5586 |
| 7982 /** | |
| 7983 * @protected | |
| 7984 * @deprecated | |
| 7985 */ | |
| 7986 _sizeDimension: function(rect, positionedBy, start, end, extent) { | 5587 _sizeDimension: function(rect, positionedBy, start, end, extent) { |
| 7987 this.__sizeDimension(rect, positionedBy, start, end, extent); | 5588 this.__sizeDimension(rect, positionedBy, start, end, extent); |
| 7988 }, | 5589 }, |
| 7989 | 5590 |
| 7990 /** | |
| 7991 * @private | |
| 7992 */ | |
| 7993 __sizeDimension: function(rect, positionedBy, start, end, extent) { | 5591 __sizeDimension: function(rect, positionedBy, start, end, extent) { |
| 7994 var info = this._fitInfo; | 5592 var info = this._fitInfo; |
| 7995 var fitRect = this.__getNormalizedRect(this.fitInto); | 5593 var fitRect = this.__getNormalizedRect(this.fitInto); |
| 7996 var max = extent === 'Width' ? fitRect.width : fitRect.height; | 5594 var max = extent === 'Width' ? fitRect.width : fitRect.height; |
| 7997 var flip = (positionedBy === end); | 5595 var flip = (positionedBy === end); |
| 7998 var offset = flip ? max - rect[end] : rect[start]; | 5596 var offset = flip ? max - rect[end] : rect[start]; |
| 7999 var margin = info.margin[flip ? start : end]; | 5597 var margin = info.margin[flip ? start : end]; |
| 8000 var offsetExtent = 'offset' + extent; | 5598 var offsetExtent = 'offset' + extent; |
| 8001 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; | 5599 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; |
| 8002 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; | 5600 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; |
| 8003 }, | 5601 }, |
| 8004 | 5602 |
| 8005 /** | |
| 8006 * Centers horizontally and vertically if not already positioned. This also
sets | |
| 8007 * `position:fixed`. | |
| 8008 */ | |
| 8009 center: function() { | 5603 center: function() { |
| 8010 if (this.horizontalAlign || this.verticalAlign) { | 5604 if (this.horizontalAlign || this.verticalAlign) { |
| 8011 return; | 5605 return; |
| 8012 } | 5606 } |
| 8013 this._discoverInfo(); | 5607 this._discoverInfo(); |
| 8014 | 5608 |
| 8015 var positionedBy = this._fitInfo.positionedBy; | 5609 var positionedBy = this._fitInfo.positionedBy; |
| 8016 if (positionedBy.vertically && positionedBy.horizontally) { | 5610 if (positionedBy.vertically && positionedBy.horizontally) { |
| 8017 // Already positioned. | |
| 8018 return; | 5611 return; |
| 8019 } | 5612 } |
| 8020 // Need position:fixed to center | |
| 8021 this.style.position = 'fixed'; | 5613 this.style.position = 'fixed'; |
| 8022 // Take into account the offset caused by parents that create stacking | |
| 8023 // contexts (e.g. with transform: translate3d). Translate to 0,0 and | |
| 8024 // measure the bounding rect. | |
| 8025 if (!positionedBy.vertically) { | 5614 if (!positionedBy.vertically) { |
| 8026 this.style.top = '0px'; | 5615 this.style.top = '0px'; |
| 8027 } | 5616 } |
| 8028 if (!positionedBy.horizontally) { | 5617 if (!positionedBy.horizontally) { |
| 8029 this.style.left = '0px'; | 5618 this.style.left = '0px'; |
| 8030 } | 5619 } |
| 8031 // It will take in consideration margins and transforms | |
| 8032 var rect = this.getBoundingClientRect(); | 5620 var rect = this.getBoundingClientRect(); |
| 8033 var fitRect = this.__getNormalizedRect(this.fitInto); | 5621 var fitRect = this.__getNormalizedRect(this.fitInto); |
| 8034 if (!positionedBy.vertically) { | 5622 if (!positionedBy.vertically) { |
| 8035 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2; | 5623 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2; |
| 8036 this.style.top = top + 'px'; | 5624 this.style.top = top + 'px'; |
| 8037 } | 5625 } |
| 8038 if (!positionedBy.horizontally) { | 5626 if (!positionedBy.horizontally) { |
| 8039 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2; | 5627 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2; |
| 8040 this.style.left = left + 'px'; | 5628 this.style.left = left + 'px'; |
| 8041 } | 5629 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 8056 }, | 5644 }, |
| 8057 | 5645 |
| 8058 __getCroppedArea: function(position, size, fitRect) { | 5646 __getCroppedArea: function(position, size, fitRect) { |
| 8059 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom
- (position.top + size.height)); | 5647 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom
- (position.top + size.height)); |
| 8060 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ
t - (position.left + size.width)); | 5648 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ
t - (position.left + size.width)); |
| 8061 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si
ze.height; | 5649 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si
ze.height; |
| 8062 }, | 5650 }, |
| 8063 | 5651 |
| 8064 | 5652 |
| 8065 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) { | 5653 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) { |
| 8066 // All the possible configurations. | |
| 8067 // Ordered as top-left, top-right, bottom-left, bottom-right. | |
| 8068 var positions = [{ | 5654 var positions = [{ |
| 8069 verticalAlign: 'top', | 5655 verticalAlign: 'top', |
| 8070 horizontalAlign: 'left', | 5656 horizontalAlign: 'left', |
| 8071 top: positionRect.top, | 5657 top: positionRect.top, |
| 8072 left: positionRect.left | 5658 left: positionRect.left |
| 8073 }, { | 5659 }, { |
| 8074 verticalAlign: 'top', | 5660 verticalAlign: 'top', |
| 8075 horizontalAlign: 'right', | 5661 horizontalAlign: 'right', |
| 8076 top: positionRect.top, | 5662 top: positionRect.top, |
| 8077 left: positionRect.right - size.width | 5663 left: positionRect.right - size.width |
| 8078 }, { | 5664 }, { |
| 8079 verticalAlign: 'bottom', | 5665 verticalAlign: 'bottom', |
| 8080 horizontalAlign: 'left', | 5666 horizontalAlign: 'left', |
| 8081 top: positionRect.bottom - size.height, | 5667 top: positionRect.bottom - size.height, |
| 8082 left: positionRect.left | 5668 left: positionRect.left |
| 8083 }, { | 5669 }, { |
| 8084 verticalAlign: 'bottom', | 5670 verticalAlign: 'bottom', |
| 8085 horizontalAlign: 'right', | 5671 horizontalAlign: 'right', |
| 8086 top: positionRect.bottom - size.height, | 5672 top: positionRect.bottom - size.height, |
| 8087 left: positionRect.right - size.width | 5673 left: positionRect.right - size.width |
| 8088 }]; | 5674 }]; |
| 8089 | 5675 |
| 8090 if (this.noOverlap) { | 5676 if (this.noOverlap) { |
| 8091 // Duplicate. | |
| 8092 for (var i = 0, l = positions.length; i < l; i++) { | 5677 for (var i = 0, l = positions.length; i < l; i++) { |
| 8093 var copy = {}; | 5678 var copy = {}; |
| 8094 for (var key in positions[i]) { | 5679 for (var key in positions[i]) { |
| 8095 copy[key] = positions[i][key]; | 5680 copy[key] = positions[i][key]; |
| 8096 } | 5681 } |
| 8097 positions.push(copy); | 5682 positions.push(copy); |
| 8098 } | 5683 } |
| 8099 // Horizontal overlap only. | |
| 8100 positions[0].top = positions[1].top += positionRect.height; | 5684 positions[0].top = positions[1].top += positionRect.height; |
| 8101 positions[2].top = positions[3].top -= positionRect.height; | 5685 positions[2].top = positions[3].top -= positionRect.height; |
| 8102 // Vertical overlap only. | |
| 8103 positions[4].left = positions[6].left += positionRect.width; | 5686 positions[4].left = positions[6].left += positionRect.width; |
| 8104 positions[5].left = positions[7].left -= positionRect.width; | 5687 positions[5].left = positions[7].left -= positionRect.width; |
| 8105 } | 5688 } |
| 8106 | 5689 |
| 8107 // Consider auto as null for coding convenience. | |
| 8108 vAlign = vAlign === 'auto' ? null : vAlign; | 5690 vAlign = vAlign === 'auto' ? null : vAlign; |
| 8109 hAlign = hAlign === 'auto' ? null : hAlign; | 5691 hAlign = hAlign === 'auto' ? null : hAlign; |
| 8110 | 5692 |
| 8111 var position; | 5693 var position; |
| 8112 for (var i = 0; i < positions.length; i++) { | 5694 for (var i = 0; i < positions.length; i++) { |
| 8113 var pos = positions[i]; | 5695 var pos = positions[i]; |
| 8114 | 5696 |
| 8115 // If both vAlign and hAlign are defined, return exact match. | |
| 8116 // For dynamicAlign and noOverlap we'll have more than one candidate, so | |
| 8117 // we'll have to check the croppedArea to make the best choice. | |
| 8118 if (!this.dynamicAlign && !this.noOverlap && | 5697 if (!this.dynamicAlign && !this.noOverlap && |
| 8119 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) { | 5698 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) { |
| 8120 position = pos; | 5699 position = pos; |
| 8121 break; | 5700 break; |
| 8122 } | 5701 } |
| 8123 | 5702 |
| 8124 // Align is ok if alignment preferences are respected. If no preferences
, | |
| 8125 // it is considered ok. | |
| 8126 var alignOk = (!vAlign || pos.verticalAlign === vAlign) && | 5703 var alignOk = (!vAlign || pos.verticalAlign === vAlign) && |
| 8127 (!hAlign || pos.horizontalAlign === hAlign); | 5704 (!hAlign || pos.horizontalAlign === hAlign); |
| 8128 | 5705 |
| 8129 // Filter out elements that don't match the alignment (if defined). | |
| 8130 // With dynamicAlign, we need to consider all the positions to find the | |
| 8131 // one that minimizes the cropped area. | |
| 8132 if (!this.dynamicAlign && !alignOk) { | 5706 if (!this.dynamicAlign && !alignOk) { |
| 8133 continue; | 5707 continue; |
| 8134 } | 5708 } |
| 8135 | 5709 |
| 8136 position = position || pos; | 5710 position = position || pos; |
| 8137 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect); | 5711 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect); |
| 8138 var diff = pos.croppedArea - position.croppedArea; | 5712 var diff = pos.croppedArea - position.croppedArea; |
| 8139 // Check which crops less. If it crops equally, check if align is ok. | |
| 8140 if (diff < 0 || (diff === 0 && alignOk)) { | 5713 if (diff < 0 || (diff === 0 && alignOk)) { |
| 8141 position = pos; | 5714 position = pos; |
| 8142 } | 5715 } |
| 8143 // If not cropped and respects the align requirements, keep it. | |
| 8144 // This allows to prefer positions overlapping horizontally over the | |
| 8145 // ones overlapping vertically. | |
| 8146 if (position.croppedArea === 0 && alignOk) { | 5716 if (position.croppedArea === 0 && alignOk) { |
| 8147 break; | 5717 break; |
| 8148 } | 5718 } |
| 8149 } | 5719 } |
| 8150 | 5720 |
| 8151 return position; | 5721 return position; |
| 8152 } | 5722 } |
| 8153 | 5723 |
| 8154 }; | 5724 }; |
| 8155 (function() { | 5725 (function() { |
| 8156 'use strict'; | 5726 'use strict'; |
| 8157 | 5727 |
| 8158 Polymer({ | 5728 Polymer({ |
| 8159 | 5729 |
| 8160 is: 'iron-overlay-backdrop', | 5730 is: 'iron-overlay-backdrop', |
| 8161 | 5731 |
| 8162 properties: { | 5732 properties: { |
| 8163 | 5733 |
| 8164 /** | |
| 8165 * Returns true if the backdrop is opened. | |
| 8166 */ | |
| 8167 opened: { | 5734 opened: { |
| 8168 reflectToAttribute: true, | 5735 reflectToAttribute: true, |
| 8169 type: Boolean, | 5736 type: Boolean, |
| 8170 value: false, | 5737 value: false, |
| 8171 observer: '_openedChanged' | 5738 observer: '_openedChanged' |
| 8172 } | 5739 } |
| 8173 | 5740 |
| 8174 }, | 5741 }, |
| 8175 | 5742 |
| 8176 listeners: { | 5743 listeners: { |
| 8177 'transitionend': '_onTransitionend' | 5744 'transitionend': '_onTransitionend' |
| 8178 }, | 5745 }, |
| 8179 | 5746 |
| 8180 created: function() { | 5747 created: function() { |
| 8181 // Used to cancel previous requestAnimationFrame calls when opened changes
. | |
| 8182 this.__openedRaf = null; | 5748 this.__openedRaf = null; |
| 8183 }, | 5749 }, |
| 8184 | 5750 |
| 8185 attached: function() { | 5751 attached: function() { |
| 8186 this.opened && this._openedChanged(this.opened); | 5752 this.opened && this._openedChanged(this.opened); |
| 8187 }, | 5753 }, |
| 8188 | 5754 |
| 8189 /** | |
| 8190 * Appends the backdrop to document body if needed. | |
| 8191 */ | |
| 8192 prepare: function() { | 5755 prepare: function() { |
| 8193 if (this.opened && !this.parentNode) { | 5756 if (this.opened && !this.parentNode) { |
| 8194 Polymer.dom(document.body).appendChild(this); | 5757 Polymer.dom(document.body).appendChild(this); |
| 8195 } | 5758 } |
| 8196 }, | 5759 }, |
| 8197 | 5760 |
| 8198 /** | |
| 8199 * Shows the backdrop. | |
| 8200 */ | |
| 8201 open: function() { | 5761 open: function() { |
| 8202 this.opened = true; | 5762 this.opened = true; |
| 8203 }, | 5763 }, |
| 8204 | 5764 |
| 8205 /** | |
| 8206 * Hides the backdrop. | |
| 8207 */ | |
| 8208 close: function() { | 5765 close: function() { |
| 8209 this.opened = false; | 5766 this.opened = false; |
| 8210 }, | 5767 }, |
| 8211 | 5768 |
| 8212 /** | |
| 8213 * Removes the backdrop from document body if needed. | |
| 8214 */ | |
| 8215 complete: function() { | 5769 complete: function() { |
| 8216 if (!this.opened && this.parentNode === document.body) { | 5770 if (!this.opened && this.parentNode === document.body) { |
| 8217 Polymer.dom(this.parentNode).removeChild(this); | 5771 Polymer.dom(this.parentNode).removeChild(this); |
| 8218 } | 5772 } |
| 8219 }, | 5773 }, |
| 8220 | 5774 |
| 8221 _onTransitionend: function(event) { | 5775 _onTransitionend: function(event) { |
| 8222 if (event && event.target === this) { | 5776 if (event && event.target === this) { |
| 8223 this.complete(); | 5777 this.complete(); |
| 8224 } | 5778 } |
| 8225 }, | 5779 }, |
| 8226 | 5780 |
| 8227 /** | |
| 8228 * @param {boolean} opened | |
| 8229 * @private | |
| 8230 */ | |
| 8231 _openedChanged: function(opened) { | 5781 _openedChanged: function(opened) { |
| 8232 if (opened) { | 5782 if (opened) { |
| 8233 // Auto-attach. | |
| 8234 this.prepare(); | 5783 this.prepare(); |
| 8235 } else { | 5784 } else { |
| 8236 // Animation might be disabled via the mixin or opacity custom property. | |
| 8237 // If it is disabled in other ways, it's up to the user to call complete
. | |
| 8238 var cs = window.getComputedStyle(this); | 5785 var cs = window.getComputedStyle(this); |
| 8239 if (cs.transitionDuration === '0s' || cs.opacity == 0) { | 5786 if (cs.transitionDuration === '0s' || cs.opacity == 0) { |
| 8240 this.complete(); | 5787 this.complete(); |
| 8241 } | 5788 } |
| 8242 } | 5789 } |
| 8243 | 5790 |
| 8244 if (!this.isAttached) { | 5791 if (!this.isAttached) { |
| 8245 return; | 5792 return; |
| 8246 } | 5793 } |
| 8247 | 5794 |
| 8248 // Always cancel previous requestAnimationFrame. | |
| 8249 if (this.__openedRaf) { | 5795 if (this.__openedRaf) { |
| 8250 window.cancelAnimationFrame(this.__openedRaf); | 5796 window.cancelAnimationFrame(this.__openedRaf); |
| 8251 this.__openedRaf = null; | 5797 this.__openedRaf = null; |
| 8252 } | 5798 } |
| 8253 // Force relayout to ensure proper transitions. | |
| 8254 this.scrollTop = this.scrollTop; | 5799 this.scrollTop = this.scrollTop; |
| 8255 this.__openedRaf = window.requestAnimationFrame(function() { | 5800 this.__openedRaf = window.requestAnimationFrame(function() { |
| 8256 this.__openedRaf = null; | 5801 this.__openedRaf = null; |
| 8257 this.toggleClass('opened', this.opened); | 5802 this.toggleClass('opened', this.opened); |
| 8258 }.bind(this)); | 5803 }.bind(this)); |
| 8259 } | 5804 } |
| 8260 }); | 5805 }); |
| 8261 | 5806 |
| 8262 })(); | 5807 })(); |
| 8263 /** | |
| 8264 * @struct | |
| 8265 * @constructor | |
| 8266 * @private | |
| 8267 */ | |
| 8268 Polymer.IronOverlayManagerClass = function() { | 5808 Polymer.IronOverlayManagerClass = function() { |
| 8269 /** | |
| 8270 * Used to keep track of the opened overlays. | |
| 8271 * @private {Array<Element>} | |
| 8272 */ | |
| 8273 this._overlays = []; | 5809 this._overlays = []; |
| 8274 | 5810 |
| 8275 /** | |
| 8276 * iframes have a default z-index of 100, | |
| 8277 * so this default should be at least that. | |
| 8278 * @private {number} | |
| 8279 */ | |
| 8280 this._minimumZ = 101; | 5811 this._minimumZ = 101; |
| 8281 | 5812 |
| 8282 /** | |
| 8283 * Memoized backdrop element. | |
| 8284 * @private {Element|null} | |
| 8285 */ | |
| 8286 this._backdropElement = null; | 5813 this._backdropElement = null; |
| 8287 | 5814 |
| 8288 // Enable document-wide tap recognizer. | |
| 8289 Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this)); | 5815 Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this)); |
| 8290 | 5816 |
| 8291 document.addEventListener('focus', this._onCaptureFocus.bind(this), true); | 5817 document.addEventListener('focus', this._onCaptureFocus.bind(this), true); |
| 8292 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true
); | 5818 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true
); |
| 8293 }; | 5819 }; |
| 8294 | 5820 |
| 8295 Polymer.IronOverlayManagerClass.prototype = { | 5821 Polymer.IronOverlayManagerClass.prototype = { |
| 8296 | 5822 |
| 8297 constructor: Polymer.IronOverlayManagerClass, | 5823 constructor: Polymer.IronOverlayManagerClass, |
| 8298 | 5824 |
| 8299 /** | |
| 8300 * The shared backdrop element. | |
| 8301 * @type {!Element} backdropElement | |
| 8302 */ | |
| 8303 get backdropElement() { | 5825 get backdropElement() { |
| 8304 if (!this._backdropElement) { | 5826 if (!this._backdropElement) { |
| 8305 this._backdropElement = document.createElement('iron-overlay-backdrop'); | 5827 this._backdropElement = document.createElement('iron-overlay-backdrop'); |
| 8306 } | 5828 } |
| 8307 return this._backdropElement; | 5829 return this._backdropElement; |
| 8308 }, | 5830 }, |
| 8309 | 5831 |
| 8310 /** | |
| 8311 * The deepest active element. | |
| 8312 * @type {!Element} activeElement the active element | |
| 8313 */ | |
| 8314 get deepActiveElement() { | 5832 get deepActiveElement() { |
| 8315 // document.activeElement can be null | |
| 8316 // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement | |
| 8317 // In case of null, default it to document.body. | |
| 8318 var active = document.activeElement || document.body; | 5833 var active = document.activeElement || document.body; |
| 8319 while (active.root && Polymer.dom(active.root).activeElement) { | 5834 while (active.root && Polymer.dom(active.root).activeElement) { |
| 8320 active = Polymer.dom(active.root).activeElement; | 5835 active = Polymer.dom(active.root).activeElement; |
| 8321 } | 5836 } |
| 8322 return active; | 5837 return active; |
| 8323 }, | 5838 }, |
| 8324 | 5839 |
| 8325 /** | |
| 8326 * Brings the overlay at the specified index to the front. | |
| 8327 * @param {number} i | |
| 8328 * @private | |
| 8329 */ | |
| 8330 _bringOverlayAtIndexToFront: function(i) { | 5840 _bringOverlayAtIndexToFront: function(i) { |
| 8331 var overlay = this._overlays[i]; | 5841 var overlay = this._overlays[i]; |
| 8332 if (!overlay) { | 5842 if (!overlay) { |
| 8333 return; | 5843 return; |
| 8334 } | 5844 } |
| 8335 var lastI = this._overlays.length - 1; | 5845 var lastI = this._overlays.length - 1; |
| 8336 var currentOverlay = this._overlays[lastI]; | 5846 var currentOverlay = this._overlays[lastI]; |
| 8337 // Ensure always-on-top overlay stays on top. | |
| 8338 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { | 5847 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { |
| 8339 lastI--; | 5848 lastI--; |
| 8340 } | 5849 } |
| 8341 // If already the top element, return. | |
| 8342 if (i >= lastI) { | 5850 if (i >= lastI) { |
| 8343 return; | 5851 return; |
| 8344 } | 5852 } |
| 8345 // Update z-index to be on top. | |
| 8346 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); | 5853 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); |
| 8347 if (this._getZ(overlay) <= minimumZ) { | 5854 if (this._getZ(overlay) <= minimumZ) { |
| 8348 this._applyOverlayZ(overlay, minimumZ); | 5855 this._applyOverlayZ(overlay, minimumZ); |
| 8349 } | 5856 } |
| 8350 | 5857 |
| 8351 // Shift other overlays behind the new on top. | |
| 8352 while (i < lastI) { | 5858 while (i < lastI) { |
| 8353 this._overlays[i] = this._overlays[i + 1]; | 5859 this._overlays[i] = this._overlays[i + 1]; |
| 8354 i++; | 5860 i++; |
| 8355 } | 5861 } |
| 8356 this._overlays[lastI] = overlay; | 5862 this._overlays[lastI] = overlay; |
| 8357 }, | 5863 }, |
| 8358 | 5864 |
| 8359 /** | |
| 8360 * Adds the overlay and updates its z-index if it's opened, or removes it if
it's closed. | |
| 8361 * Also updates the backdrop z-index. | |
| 8362 * @param {!Element} overlay | |
| 8363 */ | |
| 8364 addOrRemoveOverlay: function(overlay) { | 5865 addOrRemoveOverlay: function(overlay) { |
| 8365 if (overlay.opened) { | 5866 if (overlay.opened) { |
| 8366 this.addOverlay(overlay); | 5867 this.addOverlay(overlay); |
| 8367 } else { | 5868 } else { |
| 8368 this.removeOverlay(overlay); | 5869 this.removeOverlay(overlay); |
| 8369 } | 5870 } |
| 8370 }, | 5871 }, |
| 8371 | 5872 |
| 8372 /** | |
| 8373 * Tracks overlays for z-index and focus management. | |
| 8374 * Ensures the last added overlay with always-on-top remains on top. | |
| 8375 * @param {!Element} overlay | |
| 8376 */ | |
| 8377 addOverlay: function(overlay) { | 5873 addOverlay: function(overlay) { |
| 8378 var i = this._overlays.indexOf(overlay); | 5874 var i = this._overlays.indexOf(overlay); |
| 8379 if (i >= 0) { | 5875 if (i >= 0) { |
| 8380 this._bringOverlayAtIndexToFront(i); | 5876 this._bringOverlayAtIndexToFront(i); |
| 8381 this.trackBackdrop(); | 5877 this.trackBackdrop(); |
| 8382 return; | 5878 return; |
| 8383 } | 5879 } |
| 8384 var insertionIndex = this._overlays.length; | 5880 var insertionIndex = this._overlays.length; |
| 8385 var currentOverlay = this._overlays[insertionIndex - 1]; | 5881 var currentOverlay = this._overlays[insertionIndex - 1]; |
| 8386 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); | 5882 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); |
| 8387 var newZ = this._getZ(overlay); | 5883 var newZ = this._getZ(overlay); |
| 8388 | 5884 |
| 8389 // Ensure always-on-top overlay stays on top. | |
| 8390 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { | 5885 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { |
| 8391 // This bumps the z-index of +2. | |
| 8392 this._applyOverlayZ(currentOverlay, minimumZ); | 5886 this._applyOverlayZ(currentOverlay, minimumZ); |
| 8393 insertionIndex--; | 5887 insertionIndex--; |
| 8394 // Update minimumZ to match previous overlay's z-index. | |
| 8395 var previousOverlay = this._overlays[insertionIndex - 1]; | 5888 var previousOverlay = this._overlays[insertionIndex - 1]; |
| 8396 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); | 5889 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); |
| 8397 } | 5890 } |
| 8398 | 5891 |
| 8399 // Update z-index and insert overlay. | |
| 8400 if (newZ <= minimumZ) { | 5892 if (newZ <= minimumZ) { |
| 8401 this._applyOverlayZ(overlay, minimumZ); | 5893 this._applyOverlayZ(overlay, minimumZ); |
| 8402 } | 5894 } |
| 8403 this._overlays.splice(insertionIndex, 0, overlay); | 5895 this._overlays.splice(insertionIndex, 0, overlay); |
| 8404 | 5896 |
| 8405 this.trackBackdrop(); | 5897 this.trackBackdrop(); |
| 8406 }, | 5898 }, |
| 8407 | 5899 |
| 8408 /** | |
| 8409 * @param {!Element} overlay | |
| 8410 */ | |
| 8411 removeOverlay: function(overlay) { | 5900 removeOverlay: function(overlay) { |
| 8412 var i = this._overlays.indexOf(overlay); | 5901 var i = this._overlays.indexOf(overlay); |
| 8413 if (i === -1) { | 5902 if (i === -1) { |
| 8414 return; | 5903 return; |
| 8415 } | 5904 } |
| 8416 this._overlays.splice(i, 1); | 5905 this._overlays.splice(i, 1); |
| 8417 | 5906 |
| 8418 this.trackBackdrop(); | 5907 this.trackBackdrop(); |
| 8419 }, | 5908 }, |
| 8420 | 5909 |
| 8421 /** | |
| 8422 * Returns the current overlay. | |
| 8423 * @return {Element|undefined} | |
| 8424 */ | |
| 8425 currentOverlay: function() { | 5910 currentOverlay: function() { |
| 8426 var i = this._overlays.length - 1; | 5911 var i = this._overlays.length - 1; |
| 8427 return this._overlays[i]; | 5912 return this._overlays[i]; |
| 8428 }, | 5913 }, |
| 8429 | 5914 |
| 8430 /** | |
| 8431 * Returns the current overlay z-index. | |
| 8432 * @return {number} | |
| 8433 */ | |
| 8434 currentOverlayZ: function() { | 5915 currentOverlayZ: function() { |
| 8435 return this._getZ(this.currentOverlay()); | 5916 return this._getZ(this.currentOverlay()); |
| 8436 }, | 5917 }, |
| 8437 | 5918 |
| 8438 /** | |
| 8439 * Ensures that the minimum z-index of new overlays is at least `minimumZ`. | |
| 8440 * This does not effect the z-index of any existing overlays. | |
| 8441 * @param {number} minimumZ | |
| 8442 */ | |
| 8443 ensureMinimumZ: function(minimumZ) { | 5919 ensureMinimumZ: function(minimumZ) { |
| 8444 this._minimumZ = Math.max(this._minimumZ, minimumZ); | 5920 this._minimumZ = Math.max(this._minimumZ, minimumZ); |
| 8445 }, | 5921 }, |
| 8446 | 5922 |
| 8447 focusOverlay: function() { | 5923 focusOverlay: function() { |
| 8448 var current = /** @type {?} */ (this.currentOverlay()); | 5924 var current = /** @type {?} */ (this.currentOverlay()); |
| 8449 if (current) { | 5925 if (current) { |
| 8450 current._applyFocus(); | 5926 current._applyFocus(); |
| 8451 } | 5927 } |
| 8452 }, | 5928 }, |
| 8453 | 5929 |
| 8454 /** | |
| 8455 * Updates the backdrop z-index. | |
| 8456 */ | |
| 8457 trackBackdrop: function() { | 5930 trackBackdrop: function() { |
| 8458 var overlay = this._overlayWithBackdrop(); | 5931 var overlay = this._overlayWithBackdrop(); |
| 8459 // Avoid creating the backdrop if there is no overlay with backdrop. | |
| 8460 if (!overlay && !this._backdropElement) { | 5932 if (!overlay && !this._backdropElement) { |
| 8461 return; | 5933 return; |
| 8462 } | 5934 } |
| 8463 this.backdropElement.style.zIndex = this._getZ(overlay) - 1; | 5935 this.backdropElement.style.zIndex = this._getZ(overlay) - 1; |
| 8464 this.backdropElement.opened = !!overlay; | 5936 this.backdropElement.opened = !!overlay; |
| 8465 }, | 5937 }, |
| 8466 | 5938 |
| 8467 /** | |
| 8468 * @return {Array<Element>} | |
| 8469 */ | |
| 8470 getBackdrops: function() { | 5939 getBackdrops: function() { |
| 8471 var backdrops = []; | 5940 var backdrops = []; |
| 8472 for (var i = 0; i < this._overlays.length; i++) { | 5941 for (var i = 0; i < this._overlays.length; i++) { |
| 8473 if (this._overlays[i].withBackdrop) { | 5942 if (this._overlays[i].withBackdrop) { |
| 8474 backdrops.push(this._overlays[i]); | 5943 backdrops.push(this._overlays[i]); |
| 8475 } | 5944 } |
| 8476 } | 5945 } |
| 8477 return backdrops; | 5946 return backdrops; |
| 8478 }, | 5947 }, |
| 8479 | 5948 |
| 8480 /** | |
| 8481 * Returns the z-index for the backdrop. | |
| 8482 * @return {number} | |
| 8483 */ | |
| 8484 backdropZ: function() { | 5949 backdropZ: function() { |
| 8485 return this._getZ(this._overlayWithBackdrop()) - 1; | 5950 return this._getZ(this._overlayWithBackdrop()) - 1; |
| 8486 }, | 5951 }, |
| 8487 | 5952 |
| 8488 /** | |
| 8489 * Returns the first opened overlay that has a backdrop. | |
| 8490 * @return {Element|undefined} | |
| 8491 * @private | |
| 8492 */ | |
| 8493 _overlayWithBackdrop: function() { | 5953 _overlayWithBackdrop: function() { |
| 8494 for (var i = 0; i < this._overlays.length; i++) { | 5954 for (var i = 0; i < this._overlays.length; i++) { |
| 8495 if (this._overlays[i].withBackdrop) { | 5955 if (this._overlays[i].withBackdrop) { |
| 8496 return this._overlays[i]; | 5956 return this._overlays[i]; |
| 8497 } | 5957 } |
| 8498 } | 5958 } |
| 8499 }, | 5959 }, |
| 8500 | 5960 |
| 8501 /** | |
| 8502 * Calculates the minimum z-index for the overlay. | |
| 8503 * @param {Element=} overlay | |
| 8504 * @private | |
| 8505 */ | |
| 8506 _getZ: function(overlay) { | 5961 _getZ: function(overlay) { |
| 8507 var z = this._minimumZ; | 5962 var z = this._minimumZ; |
| 8508 if (overlay) { | 5963 if (overlay) { |
| 8509 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay)
.zIndex); | 5964 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay)
.zIndex); |
| 8510 // Check if is a number | |
| 8511 // Number.isNaN not supported in IE 10+ | |
| 8512 if (z1 === z1) { | 5965 if (z1 === z1) { |
| 8513 z = z1; | 5966 z = z1; |
| 8514 } | 5967 } |
| 8515 } | 5968 } |
| 8516 return z; | 5969 return z; |
| 8517 }, | 5970 }, |
| 8518 | 5971 |
| 8519 /** | |
| 8520 * @param {!Element} element | |
| 8521 * @param {number|string} z | |
| 8522 * @private | |
| 8523 */ | |
| 8524 _setZ: function(element, z) { | 5972 _setZ: function(element, z) { |
| 8525 element.style.zIndex = z; | 5973 element.style.zIndex = z; |
| 8526 }, | 5974 }, |
| 8527 | 5975 |
| 8528 /** | |
| 8529 * @param {!Element} overlay | |
| 8530 * @param {number} aboveZ | |
| 8531 * @private | |
| 8532 */ | |
| 8533 _applyOverlayZ: function(overlay, aboveZ) { | 5976 _applyOverlayZ: function(overlay, aboveZ) { |
| 8534 this._setZ(overlay, aboveZ + 2); | 5977 this._setZ(overlay, aboveZ + 2); |
| 8535 }, | 5978 }, |
| 8536 | 5979 |
| 8537 /** | |
| 8538 * Returns the deepest overlay in the path. | |
| 8539 * @param {Array<Element>=} path | |
| 8540 * @return {Element|undefined} | |
| 8541 * @suppress {missingProperties} | |
| 8542 * @private | |
| 8543 */ | |
| 8544 _overlayInPath: function(path) { | 5980 _overlayInPath: function(path) { |
| 8545 path = path || []; | 5981 path = path || []; |
| 8546 for (var i = 0; i < path.length; i++) { | 5982 for (var i = 0; i < path.length; i++) { |
| 8547 if (path[i]._manager === this) { | 5983 if (path[i]._manager === this) { |
| 8548 return path[i]; | 5984 return path[i]; |
| 8549 } | 5985 } |
| 8550 } | 5986 } |
| 8551 }, | 5987 }, |
| 8552 | 5988 |
| 8553 /** | |
| 8554 * Ensures the click event is delegated to the right overlay. | |
| 8555 * @param {!Event} event | |
| 8556 * @private | |
| 8557 */ | |
| 8558 _onCaptureClick: function(event) { | 5989 _onCaptureClick: function(event) { |
| 8559 var overlay = /** @type {?} */ (this.currentOverlay()); | 5990 var overlay = /** @type {?} */ (this.currentOverlay()); |
| 8560 // Check if clicked outside of top overlay. | |
| 8561 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { | 5991 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { |
| 8562 overlay._onCaptureClick(event); | 5992 overlay._onCaptureClick(event); |
| 8563 } | 5993 } |
| 8564 }, | 5994 }, |
| 8565 | 5995 |
| 8566 /** | |
| 8567 * Ensures the focus event is delegated to the right overlay. | |
| 8568 * @param {!Event} event | |
| 8569 * @private | |
| 8570 */ | |
| 8571 _onCaptureFocus: function(event) { | 5996 _onCaptureFocus: function(event) { |
| 8572 var overlay = /** @type {?} */ (this.currentOverlay()); | 5997 var overlay = /** @type {?} */ (this.currentOverlay()); |
| 8573 if (overlay) { | 5998 if (overlay) { |
| 8574 overlay._onCaptureFocus(event); | 5999 overlay._onCaptureFocus(event); |
| 8575 } | 6000 } |
| 8576 }, | 6001 }, |
| 8577 | 6002 |
| 8578 /** | |
| 8579 * Ensures TAB and ESC keyboard events are delegated to the right overlay. | |
| 8580 * @param {!Event} event | |
| 8581 * @private | |
| 8582 */ | |
| 8583 _onCaptureKeyDown: function(event) { | 6003 _onCaptureKeyDown: function(event) { |
| 8584 var overlay = /** @type {?} */ (this.currentOverlay()); | 6004 var overlay = /** @type {?} */ (this.currentOverlay()); |
| 8585 if (overlay) { | 6005 if (overlay) { |
| 8586 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc'))
{ | 6006 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc'))
{ |
| 8587 overlay._onCaptureEsc(event); | 6007 overlay._onCaptureEsc(event); |
| 8588 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event,
'tab')) { | 6008 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event,
'tab')) { |
| 8589 overlay._onCaptureTab(event); | 6009 overlay._onCaptureTab(event); |
| 8590 } | 6010 } |
| 8591 } | 6011 } |
| 8592 }, | 6012 }, |
| 8593 | 6013 |
| 8594 /** | |
| 8595 * Returns if the overlay1 should be behind overlay2. | |
| 8596 * @param {!Element} overlay1 | |
| 8597 * @param {!Element} overlay2 | |
| 8598 * @return {boolean} | |
| 8599 * @suppress {missingProperties} | |
| 8600 * @private | |
| 8601 */ | |
| 8602 _shouldBeBehindOverlay: function(overlay1, overlay2) { | 6014 _shouldBeBehindOverlay: function(overlay1, overlay2) { |
| 8603 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; | 6015 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; |
| 8604 } | 6016 } |
| 8605 }; | 6017 }; |
| 8606 | 6018 |
| 8607 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); | 6019 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); |
| 8608 (function() { | 6020 (function() { |
| 8609 'use strict'; | 6021 'use strict'; |
| 8610 | 6022 |
| 8611 /** | |
| 8612 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays | |
| 8613 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety | |
| 8614 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. | |
| 8615 | |
| 8616 See the [demo source code](https://github.com/PolymerElements/iron-overlay-behav
ior/blob/master/demo/simple-overlay.html) | |
| 8617 for an example. | |
| 8618 | |
| 8619 ### Closing and canceling | |
| 8620 | |
| 8621 An overlay may be hidden by closing or canceling. The difference between close a
nd cancel is user | |
| 8622 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, | |
| 8623 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is | |
| 8624 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. | |
| 8625 `close()` should be called explicitly by the implementer when the user interacts
with a control | |
| 8626 in the overlay element. When the dialog is canceled, the overlay fires an 'iron-
overlay-canceled' | |
| 8627 event. Call `preventDefault` on this event to prevent the overlay from closing. | |
| 8628 | |
| 8629 ### Positioning | |
| 8630 | |
| 8631 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can | |
| 8632 position and size it manually using CSS. See `Polymer.IronFitBehavior`. | |
| 8633 | |
| 8634 ### Backdrop | |
| 8635 | |
| 8636 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is | |
| 8637 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling | |
| 8638 options. | |
| 8639 | |
| 8640 In addition, `with-backdrop` will wrap the focus within the content in the light
DOM. | |
| 8641 Override the [`_focusableNodes` getter](#Polymer.IronOverlayBehavior:property-_f
ocusableNodes) | |
| 8642 to achieve a different behavior. | |
| 8643 | |
| 8644 ### Limitations | |
| 8645 | |
| 8646 The element is styled to appear on top of other content by setting its `z-index`
property. You | |
| 8647 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking | |
| 8648 context. You should place this element as a child of `<body>` whenever possible. | |
| 8649 | |
| 8650 @demo demo/index.html | |
| 8651 @polymerBehavior Polymer.IronOverlayBehavior | |
| 8652 */ | |
| 8653 | 6023 |
| 8654 Polymer.IronOverlayBehaviorImpl = { | 6024 Polymer.IronOverlayBehaviorImpl = { |
| 8655 | 6025 |
| 8656 properties: { | 6026 properties: { |
| 8657 | 6027 |
| 8658 /** | |
| 8659 * True if the overlay is currently displayed. | |
| 8660 */ | |
| 8661 opened: { | 6028 opened: { |
| 8662 observer: '_openedChanged', | 6029 observer: '_openedChanged', |
| 8663 type: Boolean, | 6030 type: Boolean, |
| 8664 value: false, | 6031 value: false, |
| 8665 notify: true | 6032 notify: true |
| 8666 }, | 6033 }, |
| 8667 | 6034 |
| 8668 /** | |
| 8669 * True if the overlay was canceled when it was last closed. | |
| 8670 */ | |
| 8671 canceled: { | 6035 canceled: { |
| 8672 observer: '_canceledChanged', | 6036 observer: '_canceledChanged', |
| 8673 readOnly: true, | 6037 readOnly: true, |
| 8674 type: Boolean, | 6038 type: Boolean, |
| 8675 value: false | 6039 value: false |
| 8676 }, | 6040 }, |
| 8677 | 6041 |
| 8678 /** | |
| 8679 * Set to true to display a backdrop behind the overlay. It traps the focu
s | |
| 8680 * within the light DOM of the overlay. | |
| 8681 */ | |
| 8682 withBackdrop: { | 6042 withBackdrop: { |
| 8683 observer: '_withBackdropChanged', | 6043 observer: '_withBackdropChanged', |
| 8684 type: Boolean | 6044 type: Boolean |
| 8685 }, | 6045 }, |
| 8686 | 6046 |
| 8687 /** | |
| 8688 * Set to true to disable auto-focusing the overlay or child nodes with | |
| 8689 * the `autofocus` attribute` when the overlay is opened. | |
| 8690 */ | |
| 8691 noAutoFocus: { | 6047 noAutoFocus: { |
| 8692 type: Boolean, | 6048 type: Boolean, |
| 8693 value: false | 6049 value: false |
| 8694 }, | 6050 }, |
| 8695 | 6051 |
| 8696 /** | |
| 8697 * Set to true to disable canceling the overlay with the ESC key. | |
| 8698 */ | |
| 8699 noCancelOnEscKey: { | 6052 noCancelOnEscKey: { |
| 8700 type: Boolean, | 6053 type: Boolean, |
| 8701 value: false | 6054 value: false |
| 8702 }, | 6055 }, |
| 8703 | 6056 |
| 8704 /** | |
| 8705 * Set to true to disable canceling the overlay by clicking outside it. | |
| 8706 */ | |
| 8707 noCancelOnOutsideClick: { | 6057 noCancelOnOutsideClick: { |
| 8708 type: Boolean, | 6058 type: Boolean, |
| 8709 value: false | 6059 value: false |
| 8710 }, | 6060 }, |
| 8711 | 6061 |
| 8712 /** | |
| 8713 * Contains the reason(s) this overlay was last closed (see `iron-overlay-
closed`). | |
| 8714 * `IronOverlayBehavior` provides the `canceled` reason; implementers of t
he | |
| 8715 * behavior can provide other reasons in addition to `canceled`. | |
| 8716 */ | |
| 8717 closingReason: { | 6062 closingReason: { |
| 8718 // was a getter before, but needs to be a property so other | |
| 8719 // behaviors can override this. | |
| 8720 type: Object | 6063 type: Object |
| 8721 }, | 6064 }, |
| 8722 | 6065 |
| 8723 /** | |
| 8724 * Set to true to enable restoring of focus when overlay is closed. | |
| 8725 */ | |
| 8726 restoreFocusOnClose: { | 6066 restoreFocusOnClose: { |
| 8727 type: Boolean, | 6067 type: Boolean, |
| 8728 value: false | 6068 value: false |
| 8729 }, | 6069 }, |
| 8730 | 6070 |
| 8731 /** | |
| 8732 * Set to true to keep overlay always on top. | |
| 8733 */ | |
| 8734 alwaysOnTop: { | 6071 alwaysOnTop: { |
| 8735 type: Boolean | 6072 type: Boolean |
| 8736 }, | 6073 }, |
| 8737 | 6074 |
| 8738 /** | |
| 8739 * Shortcut to access to the overlay manager. | |
| 8740 * @private | |
| 8741 * @type {Polymer.IronOverlayManagerClass} | |
| 8742 */ | |
| 8743 _manager: { | 6075 _manager: { |
| 8744 type: Object, | 6076 type: Object, |
| 8745 value: Polymer.IronOverlayManager | 6077 value: Polymer.IronOverlayManager |
| 8746 }, | 6078 }, |
| 8747 | 6079 |
| 8748 /** | |
| 8749 * The node being focused. | |
| 8750 * @type {?Node} | |
| 8751 */ | |
| 8752 _focusedChild: { | 6080 _focusedChild: { |
| 8753 type: Object | 6081 type: Object |
| 8754 } | 6082 } |
| 8755 | 6083 |
| 8756 }, | 6084 }, |
| 8757 | 6085 |
| 8758 listeners: { | 6086 listeners: { |
| 8759 'iron-resize': '_onIronResize' | 6087 'iron-resize': '_onIronResize' |
| 8760 }, | 6088 }, |
| 8761 | 6089 |
| 8762 /** | |
| 8763 * The backdrop element. | |
| 8764 * @type {Element} | |
| 8765 */ | |
| 8766 get backdropElement() { | 6090 get backdropElement() { |
| 8767 return this._manager.backdropElement; | 6091 return this._manager.backdropElement; |
| 8768 }, | 6092 }, |
| 8769 | 6093 |
| 8770 /** | |
| 8771 * Returns the node to give focus to. | |
| 8772 * @type {Node} | |
| 8773 */ | |
| 8774 get _focusNode() { | 6094 get _focusNode() { |
| 8775 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]'
) || this; | 6095 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]'
) || this; |
| 8776 }, | 6096 }, |
| 8777 | 6097 |
| 8778 /** | |
| 8779 * Array of nodes that can receive focus (overlay included), ordered by `tab
index`. | |
| 8780 * This is used to retrieve which is the first and last focusable nodes in o
rder | |
| 8781 * to wrap the focus for overlays `with-backdrop`. | |
| 8782 * | |
| 8783 * If you know what is your content (specifically the first and last focusab
le children), | |
| 8784 * you can override this method to return only `[firstFocusable, lastFocusab
le];` | |
| 8785 * @type {Array<Node>} | |
| 8786 * @protected | |
| 8787 */ | |
| 8788 get _focusableNodes() { | 6098 get _focusableNodes() { |
| 8789 // Elements that can be focused even if they have [disabled] attribute. | |
| 8790 var FOCUSABLE_WITH_DISABLED = [ | 6099 var FOCUSABLE_WITH_DISABLED = [ |
| 8791 'a[href]', | 6100 'a[href]', |
| 8792 'area[href]', | 6101 'area[href]', |
| 8793 'iframe', | 6102 'iframe', |
| 8794 '[tabindex]', | 6103 '[tabindex]', |
| 8795 '[contentEditable=true]' | 6104 '[contentEditable=true]' |
| 8796 ]; | 6105 ]; |
| 8797 | 6106 |
| 8798 // Elements that cannot be focused if they have [disabled] attribute. | |
| 8799 var FOCUSABLE_WITHOUT_DISABLED = [ | 6107 var FOCUSABLE_WITHOUT_DISABLED = [ |
| 8800 'input', | 6108 'input', |
| 8801 'select', | 6109 'select', |
| 8802 'textarea', | 6110 'textarea', |
| 8803 'button' | 6111 'button' |
| 8804 ]; | 6112 ]; |
| 8805 | 6113 |
| 8806 // Discard elements with tabindex=-1 (makes them not focusable). | |
| 8807 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') + | 6114 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') + |
| 8808 ':not([tabindex="-1"]),' + | 6115 ':not([tabindex="-1"]),' + |
| 8809 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),'
) + | 6116 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),'
) + |
| 8810 ':not([disabled]):not([tabindex="-1"])'; | 6117 ':not([disabled]):not([tabindex="-1"])'; |
| 8811 | 6118 |
| 8812 var focusables = Polymer.dom(this).querySelectorAll(selector); | 6119 var focusables = Polymer.dom(this).querySelectorAll(selector); |
| 8813 if (this.tabIndex >= 0) { | 6120 if (this.tabIndex >= 0) { |
| 8814 // Insert at the beginning because we might have all elements with tabIn
dex = 0, | |
| 8815 // and the overlay should be the first of the list. | |
| 8816 focusables.splice(0, 0, this); | 6121 focusables.splice(0, 0, this); |
| 8817 } | 6122 } |
| 8818 // Sort by tabindex. | |
| 8819 return focusables.sort(function (a, b) { | 6123 return focusables.sort(function (a, b) { |
| 8820 if (a.tabIndex === b.tabIndex) { | 6124 if (a.tabIndex === b.tabIndex) { |
| 8821 return 0; | 6125 return 0; |
| 8822 } | 6126 } |
| 8823 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) { | 6127 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) { |
| 8824 return 1; | 6128 return 1; |
| 8825 } | 6129 } |
| 8826 return -1; | 6130 return -1; |
| 8827 }); | 6131 }); |
| 8828 }, | 6132 }, |
| 8829 | 6133 |
| 8830 ready: function() { | 6134 ready: function() { |
| 8831 // Used to skip calls to notifyResize and refit while the overlay is anima
ting. | |
| 8832 this.__isAnimating = false; | 6135 this.__isAnimating = false; |
| 8833 // with-backdrop needs tabindex to be set in order to trap the focus. | |
| 8834 // If it is not set, IronOverlayBehavior will set it, and remove it if wit
h-backdrop = false. | |
| 8835 this.__shouldRemoveTabIndex = false; | 6136 this.__shouldRemoveTabIndex = false; |
| 8836 // Used for wrapping the focus on TAB / Shift+TAB. | |
| 8837 this.__firstFocusableNode = this.__lastFocusableNode = null; | 6137 this.__firstFocusableNode = this.__lastFocusableNode = null; |
| 8838 // Used by __onNextAnimationFrame to cancel any previous callback. | |
| 8839 this.__raf = null; | 6138 this.__raf = null; |
| 8840 // Focused node before overlay gets opened. Can be restored on close. | |
| 8841 this.__restoreFocusNode = null; | 6139 this.__restoreFocusNode = null; |
| 8842 this._ensureSetup(); | 6140 this._ensureSetup(); |
| 8843 }, | 6141 }, |
| 8844 | 6142 |
| 8845 attached: function() { | 6143 attached: function() { |
| 8846 // Call _openedChanged here so that position can be computed correctly. | |
| 8847 if (this.opened) { | 6144 if (this.opened) { |
| 8848 this._openedChanged(this.opened); | 6145 this._openedChanged(this.opened); |
| 8849 } | 6146 } |
| 8850 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange); | 6147 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange); |
| 8851 }, | 6148 }, |
| 8852 | 6149 |
| 8853 detached: function() { | 6150 detached: function() { |
| 8854 Polymer.dom(this).unobserveNodes(this._observer); | 6151 Polymer.dom(this).unobserveNodes(this._observer); |
| 8855 this._observer = null; | 6152 this._observer = null; |
| 8856 if (this.__raf) { | 6153 if (this.__raf) { |
| 8857 window.cancelAnimationFrame(this.__raf); | 6154 window.cancelAnimationFrame(this.__raf); |
| 8858 this.__raf = null; | 6155 this.__raf = null; |
| 8859 } | 6156 } |
| 8860 this._manager.removeOverlay(this); | 6157 this._manager.removeOverlay(this); |
| 8861 }, | 6158 }, |
| 8862 | 6159 |
| 8863 /** | |
| 8864 * Toggle the opened state of the overlay. | |
| 8865 */ | |
| 8866 toggle: function() { | 6160 toggle: function() { |
| 8867 this._setCanceled(false); | 6161 this._setCanceled(false); |
| 8868 this.opened = !this.opened; | 6162 this.opened = !this.opened; |
| 8869 }, | 6163 }, |
| 8870 | 6164 |
| 8871 /** | |
| 8872 * Open the overlay. | |
| 8873 */ | |
| 8874 open: function() { | 6165 open: function() { |
| 8875 this._setCanceled(false); | 6166 this._setCanceled(false); |
| 8876 this.opened = true; | 6167 this.opened = true; |
| 8877 }, | 6168 }, |
| 8878 | 6169 |
| 8879 /** | |
| 8880 * Close the overlay. | |
| 8881 */ | |
| 8882 close: function() { | 6170 close: function() { |
| 8883 this._setCanceled(false); | 6171 this._setCanceled(false); |
| 8884 this.opened = false; | 6172 this.opened = false; |
| 8885 }, | 6173 }, |
| 8886 | 6174 |
| 8887 /** | |
| 8888 * Cancels the overlay. | |
| 8889 * @param {Event=} event The original event | |
| 8890 */ | |
| 8891 cancel: function(event) { | 6175 cancel: function(event) { |
| 8892 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t
rue}); | 6176 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t
rue}); |
| 8893 if (cancelEvent.defaultPrevented) { | 6177 if (cancelEvent.defaultPrevented) { |
| 8894 return; | 6178 return; |
| 8895 } | 6179 } |
| 8896 | 6180 |
| 8897 this._setCanceled(true); | 6181 this._setCanceled(true); |
| 8898 this.opened = false; | 6182 this.opened = false; |
| 8899 }, | 6183 }, |
| 8900 | 6184 |
| 8901 _ensureSetup: function() { | 6185 _ensureSetup: function() { |
| 8902 if (this._overlaySetup) { | 6186 if (this._overlaySetup) { |
| 8903 return; | 6187 return; |
| 8904 } | 6188 } |
| 8905 this._overlaySetup = true; | 6189 this._overlaySetup = true; |
| 8906 this.style.outline = 'none'; | 6190 this.style.outline = 'none'; |
| 8907 this.style.display = 'none'; | 6191 this.style.display = 'none'; |
| 8908 }, | 6192 }, |
| 8909 | 6193 |
| 8910 /** | |
| 8911 * Called when `opened` changes. | |
| 8912 * @param {boolean=} opened | |
| 8913 * @protected | |
| 8914 */ | |
| 8915 _openedChanged: function(opened) { | 6194 _openedChanged: function(opened) { |
| 8916 if (opened) { | 6195 if (opened) { |
| 8917 this.removeAttribute('aria-hidden'); | 6196 this.removeAttribute('aria-hidden'); |
| 8918 } else { | 6197 } else { |
| 8919 this.setAttribute('aria-hidden', 'true'); | 6198 this.setAttribute('aria-hidden', 'true'); |
| 8920 } | 6199 } |
| 8921 | 6200 |
| 8922 // Defer any animation-related code on attached | |
| 8923 // (_openedChanged gets called again on attached). | |
| 8924 if (!this.isAttached) { | 6201 if (!this.isAttached) { |
| 8925 return; | 6202 return; |
| 8926 } | 6203 } |
| 8927 | 6204 |
| 8928 this.__isAnimating = true; | 6205 this.__isAnimating = true; |
| 8929 | 6206 |
| 8930 // Use requestAnimationFrame for non-blocking rendering. | |
| 8931 this.__onNextAnimationFrame(this.__openedChanged); | 6207 this.__onNextAnimationFrame(this.__openedChanged); |
| 8932 }, | 6208 }, |
| 8933 | 6209 |
| 8934 _canceledChanged: function() { | 6210 _canceledChanged: function() { |
| 8935 this.closingReason = this.closingReason || {}; | 6211 this.closingReason = this.closingReason || {}; |
| 8936 this.closingReason.canceled = this.canceled; | 6212 this.closingReason.canceled = this.canceled; |
| 8937 }, | 6213 }, |
| 8938 | 6214 |
| 8939 _withBackdropChanged: function() { | 6215 _withBackdropChanged: function() { |
| 8940 // If tabindex is already set, no need to override it. | |
| 8941 if (this.withBackdrop && !this.hasAttribute('tabindex')) { | 6216 if (this.withBackdrop && !this.hasAttribute('tabindex')) { |
| 8942 this.setAttribute('tabindex', '-1'); | 6217 this.setAttribute('tabindex', '-1'); |
| 8943 this.__shouldRemoveTabIndex = true; | 6218 this.__shouldRemoveTabIndex = true; |
| 8944 } else if (this.__shouldRemoveTabIndex) { | 6219 } else if (this.__shouldRemoveTabIndex) { |
| 8945 this.removeAttribute('tabindex'); | 6220 this.removeAttribute('tabindex'); |
| 8946 this.__shouldRemoveTabIndex = false; | 6221 this.__shouldRemoveTabIndex = false; |
| 8947 } | 6222 } |
| 8948 if (this.opened && this.isAttached) { | 6223 if (this.opened && this.isAttached) { |
| 8949 this._manager.trackBackdrop(); | 6224 this._manager.trackBackdrop(); |
| 8950 } | 6225 } |
| 8951 }, | 6226 }, |
| 8952 | 6227 |
| 8953 /** | |
| 8954 * tasks which must occur before opening; e.g. making the element visible. | |
| 8955 * @protected | |
| 8956 */ | |
| 8957 _prepareRenderOpened: function() { | 6228 _prepareRenderOpened: function() { |
| 8958 // Store focused node. | |
| 8959 this.__restoreFocusNode = this._manager.deepActiveElement; | 6229 this.__restoreFocusNode = this._manager.deepActiveElement; |
| 8960 | 6230 |
| 8961 // Needed to calculate the size of the overlay so that transitions on its
size | |
| 8962 // will have the correct starting points. | |
| 8963 this._preparePositioning(); | 6231 this._preparePositioning(); |
| 8964 this.refit(); | 6232 this.refit(); |
| 8965 this._finishPositioning(); | 6233 this._finishPositioning(); |
| 8966 | 6234 |
| 8967 // Safari will apply the focus to the autofocus element when displayed | |
| 8968 // for the first time, so we make sure to return the focus where it was. | |
| 8969 if (this.noAutoFocus && document.activeElement === this._focusNode) { | 6235 if (this.noAutoFocus && document.activeElement === this._focusNode) { |
| 8970 this._focusNode.blur(); | 6236 this._focusNode.blur(); |
| 8971 this.__restoreFocusNode.focus(); | 6237 this.__restoreFocusNode.focus(); |
| 8972 } | 6238 } |
| 8973 }, | 6239 }, |
| 8974 | 6240 |
| 8975 /** | |
| 8976 * Tasks which cause the overlay to actually open; typically play an animati
on. | |
| 8977 * @protected | |
| 8978 */ | |
| 8979 _renderOpened: function() { | 6241 _renderOpened: function() { |
| 8980 this._finishRenderOpened(); | 6242 this._finishRenderOpened(); |
| 8981 }, | 6243 }, |
| 8982 | 6244 |
| 8983 /** | |
| 8984 * Tasks which cause the overlay to actually close; typically play an animat
ion. | |
| 8985 * @protected | |
| 8986 */ | |
| 8987 _renderClosed: function() { | 6245 _renderClosed: function() { |
| 8988 this._finishRenderClosed(); | 6246 this._finishRenderClosed(); |
| 8989 }, | 6247 }, |
| 8990 | 6248 |
| 8991 /** | |
| 8992 * Tasks to be performed at the end of open action. Will fire `iron-overlay-
opened`. | |
| 8993 * @protected | |
| 8994 */ | |
| 8995 _finishRenderOpened: function() { | 6249 _finishRenderOpened: function() { |
| 8996 this.notifyResize(); | 6250 this.notifyResize(); |
| 8997 this.__isAnimating = false; | 6251 this.__isAnimating = false; |
| 8998 | 6252 |
| 8999 // Store it so we don't query too much. | |
| 9000 var focusableNodes = this._focusableNodes; | 6253 var focusableNodes = this._focusableNodes; |
| 9001 this.__firstFocusableNode = focusableNodes[0]; | 6254 this.__firstFocusableNode = focusableNodes[0]; |
| 9002 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1]; | 6255 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1]; |
| 9003 | 6256 |
| 9004 this.fire('iron-overlay-opened'); | 6257 this.fire('iron-overlay-opened'); |
| 9005 }, | 6258 }, |
| 9006 | 6259 |
| 9007 /** | |
| 9008 * Tasks to be performed at the end of close action. Will fire `iron-overlay
-closed`. | |
| 9009 * @protected | |
| 9010 */ | |
| 9011 _finishRenderClosed: function() { | 6260 _finishRenderClosed: function() { |
| 9012 // Hide the overlay. | |
| 9013 this.style.display = 'none'; | 6261 this.style.display = 'none'; |
| 9014 // Reset z-index only at the end of the animation. | |
| 9015 this.style.zIndex = ''; | 6262 this.style.zIndex = ''; |
| 9016 this.notifyResize(); | 6263 this.notifyResize(); |
| 9017 this.__isAnimating = false; | 6264 this.__isAnimating = false; |
| 9018 this.fire('iron-overlay-closed', this.closingReason); | 6265 this.fire('iron-overlay-closed', this.closingReason); |
| 9019 }, | 6266 }, |
| 9020 | 6267 |
| 9021 _preparePositioning: function() { | 6268 _preparePositioning: function() { |
| 9022 this.style.transition = this.style.webkitTransition = 'none'; | 6269 this.style.transition = this.style.webkitTransition = 'none'; |
| 9023 this.style.transform = this.style.webkitTransform = 'none'; | 6270 this.style.transform = this.style.webkitTransform = 'none'; |
| 9024 this.style.display = ''; | 6271 this.style.display = ''; |
| 9025 }, | 6272 }, |
| 9026 | 6273 |
| 9027 _finishPositioning: function() { | 6274 _finishPositioning: function() { |
| 9028 // First, make it invisible & reactivate animations. | |
| 9029 this.style.display = 'none'; | 6275 this.style.display = 'none'; |
| 9030 // Force reflow before re-enabling animations so that they don't start. | |
| 9031 // Set scrollTop to itself so that Closure Compiler doesn't remove this. | |
| 9032 this.scrollTop = this.scrollTop; | 6276 this.scrollTop = this.scrollTop; |
| 9033 this.style.transition = this.style.webkitTransition = ''; | 6277 this.style.transition = this.style.webkitTransition = ''; |
| 9034 this.style.transform = this.style.webkitTransform = ''; | 6278 this.style.transform = this.style.webkitTransform = ''; |
| 9035 // Now that animations are enabled, make it visible again | |
| 9036 this.style.display = ''; | 6279 this.style.display = ''; |
| 9037 // Force reflow, so that following animations are properly started. | |
| 9038 // Set scrollTop to itself so that Closure Compiler doesn't remove this. | |
| 9039 this.scrollTop = this.scrollTop; | 6280 this.scrollTop = this.scrollTop; |
| 9040 }, | 6281 }, |
| 9041 | 6282 |
| 9042 /** | |
| 9043 * Applies focus according to the opened state. | |
| 9044 * @protected | |
| 9045 */ | |
| 9046 _applyFocus: function() { | 6283 _applyFocus: function() { |
| 9047 if (this.opened) { | 6284 if (this.opened) { |
| 9048 if (!this.noAutoFocus) { | 6285 if (!this.noAutoFocus) { |
| 9049 this._focusNode.focus(); | 6286 this._focusNode.focus(); |
| 9050 } | 6287 } |
| 9051 } | 6288 } |
| 9052 else { | 6289 else { |
| 9053 this._focusNode.blur(); | 6290 this._focusNode.blur(); |
| 9054 this._focusedChild = null; | 6291 this._focusedChild = null; |
| 9055 // Restore focus. | |
| 9056 if (this.restoreFocusOnClose && this.__restoreFocusNode) { | 6292 if (this.restoreFocusOnClose && this.__restoreFocusNode) { |
| 9057 this.__restoreFocusNode.focus(); | 6293 this.__restoreFocusNode.focus(); |
| 9058 } | 6294 } |
| 9059 this.__restoreFocusNode = null; | 6295 this.__restoreFocusNode = null; |
| 9060 // If many overlays get closed at the same time, one of them would still | |
| 9061 // be the currentOverlay even if already closed, and would call _applyFo
cus | |
| 9062 // infinitely, so we check for this not to be the current overlay. | |
| 9063 var currentOverlay = this._manager.currentOverlay(); | 6296 var currentOverlay = this._manager.currentOverlay(); |
| 9064 if (currentOverlay && this !== currentOverlay) { | 6297 if (currentOverlay && this !== currentOverlay) { |
| 9065 currentOverlay._applyFocus(); | 6298 currentOverlay._applyFocus(); |
| 9066 } | 6299 } |
| 9067 } | 6300 } |
| 9068 }, | 6301 }, |
| 9069 | 6302 |
| 9070 /** | |
| 9071 * Cancels (closes) the overlay. Call when click happens outside the overlay
. | |
| 9072 * @param {!Event} event | |
| 9073 * @protected | |
| 9074 */ | |
| 9075 _onCaptureClick: function(event) { | 6303 _onCaptureClick: function(event) { |
| 9076 if (!this.noCancelOnOutsideClick) { | 6304 if (!this.noCancelOnOutsideClick) { |
| 9077 this.cancel(event); | 6305 this.cancel(event); |
| 9078 } | 6306 } |
| 9079 }, | 6307 }, |
| 9080 | 6308 |
| 9081 /** | |
| 9082 * Keeps track of the focused child. If withBackdrop, traps focus within ove
rlay. | |
| 9083 * @param {!Event} event | |
| 9084 * @protected | |
| 9085 */ | |
| 9086 _onCaptureFocus: function (event) { | 6309 _onCaptureFocus: function (event) { |
| 9087 if (!this.withBackdrop) { | 6310 if (!this.withBackdrop) { |
| 9088 return; | 6311 return; |
| 9089 } | 6312 } |
| 9090 var path = Polymer.dom(event).path; | 6313 var path = Polymer.dom(event).path; |
| 9091 if (path.indexOf(this) === -1) { | 6314 if (path.indexOf(this) === -1) { |
| 9092 event.stopPropagation(); | 6315 event.stopPropagation(); |
| 9093 this._applyFocus(); | 6316 this._applyFocus(); |
| 9094 } else { | 6317 } else { |
| 9095 this._focusedChild = path[0]; | 6318 this._focusedChild = path[0]; |
| 9096 } | 6319 } |
| 9097 }, | 6320 }, |
| 9098 | 6321 |
| 9099 /** | |
| 9100 * Handles the ESC key event and cancels (closes) the overlay. | |
| 9101 * @param {!Event} event | |
| 9102 * @protected | |
| 9103 */ | |
| 9104 _onCaptureEsc: function(event) { | 6322 _onCaptureEsc: function(event) { |
| 9105 if (!this.noCancelOnEscKey) { | 6323 if (!this.noCancelOnEscKey) { |
| 9106 this.cancel(event); | 6324 this.cancel(event); |
| 9107 } | 6325 } |
| 9108 }, | 6326 }, |
| 9109 | 6327 |
| 9110 /** | |
| 9111 * Handles TAB key events to track focus changes. | |
| 9112 * Will wrap focus for overlays withBackdrop. | |
| 9113 * @param {!Event} event | |
| 9114 * @protected | |
| 9115 */ | |
| 9116 _onCaptureTab: function(event) { | 6328 _onCaptureTab: function(event) { |
| 9117 if (!this.withBackdrop) { | 6329 if (!this.withBackdrop) { |
| 9118 return; | 6330 return; |
| 9119 } | 6331 } |
| 9120 // TAB wraps from last to first focusable. | |
| 9121 // Shift + TAB wraps from first to last focusable. | |
| 9122 var shift = event.shiftKey; | 6332 var shift = event.shiftKey; |
| 9123 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable
Node; | 6333 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable
Node; |
| 9124 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo
de; | 6334 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo
de; |
| 9125 var shouldWrap = false; | 6335 var shouldWrap = false; |
| 9126 if (nodeToCheck === nodeToSet) { | 6336 if (nodeToCheck === nodeToSet) { |
| 9127 // If nodeToCheck is the same as nodeToSet, it means we have an overlay | |
| 9128 // with 0 or 1 focusables; in either case we still need to trap the | |
| 9129 // focus within the overlay. | |
| 9130 shouldWrap = true; | 6337 shouldWrap = true; |
| 9131 } else { | 6338 } else { |
| 9132 // In dom=shadow, the manager will receive focus changes on the main | |
| 9133 // root but not the ones within other shadow roots, so we can't rely on | |
| 9134 // _focusedChild, but we should check the deepest active element. | |
| 9135 var focusedNode = this._manager.deepActiveElement; | 6339 var focusedNode = this._manager.deepActiveElement; |
| 9136 // If the active element is not the nodeToCheck but the overlay itself, | |
| 9137 // it means the focus is about to go outside the overlay, hence we | |
| 9138 // should prevent that (e.g. user opens the overlay and hit Shift+TAB). | |
| 9139 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this); | 6340 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this); |
| 9140 } | 6341 } |
| 9141 | 6342 |
| 9142 if (shouldWrap) { | 6343 if (shouldWrap) { |
| 9143 // When the overlay contains the last focusable element of the document | |
| 9144 // and it's already focused, pressing TAB would move the focus outside | |
| 9145 // the document (e.g. to the browser search bar). Similarly, when the | |
| 9146 // overlay contains the first focusable element of the document and it's | |
| 9147 // already focused, pressing Shift+TAB would move the focus outside the | |
| 9148 // document (e.g. to the browser search bar). | |
| 9149 // In both cases, we would not receive a focus event, but only a blur. | |
| 9150 // In order to achieve focus wrapping, we prevent this TAB event and | |
| 9151 // force the focus. This will also prevent the focus to temporarily move | |
| 9152 // outside the overlay, which might cause scrolling. | |
| 9153 event.preventDefault(); | 6344 event.preventDefault(); |
| 9154 this._focusedChild = nodeToSet; | 6345 this._focusedChild = nodeToSet; |
| 9155 this._applyFocus(); | 6346 this._applyFocus(); |
| 9156 } | 6347 } |
| 9157 }, | 6348 }, |
| 9158 | 6349 |
| 9159 /** | |
| 9160 * Refits if the overlay is opened and not animating. | |
| 9161 * @protected | |
| 9162 */ | |
| 9163 _onIronResize: function() { | 6350 _onIronResize: function() { |
| 9164 if (this.opened && !this.__isAnimating) { | 6351 if (this.opened && !this.__isAnimating) { |
| 9165 this.__onNextAnimationFrame(this.refit); | 6352 this.__onNextAnimationFrame(this.refit); |
| 9166 } | 6353 } |
| 9167 }, | 6354 }, |
| 9168 | 6355 |
| 9169 /** | |
| 9170 * Will call notifyResize if overlay is opened. | |
| 9171 * Can be overridden in order to avoid multiple observers on the same node. | |
| 9172 * @protected | |
| 9173 */ | |
| 9174 _onNodesChange: function() { | 6356 _onNodesChange: function() { |
| 9175 if (this.opened && !this.__isAnimating) { | 6357 if (this.opened && !this.__isAnimating) { |
| 9176 this.notifyResize(); | 6358 this.notifyResize(); |
| 9177 } | 6359 } |
| 9178 }, | 6360 }, |
| 9179 | 6361 |
| 9180 /** | |
| 9181 * Tasks executed when opened changes: prepare for the opening, move the | |
| 9182 * focus, update the manager, render opened/closed. | |
| 9183 * @private | |
| 9184 */ | |
| 9185 __openedChanged: function() { | 6362 __openedChanged: function() { |
| 9186 if (this.opened) { | 6363 if (this.opened) { |
| 9187 // Make overlay visible, then add it to the manager. | |
| 9188 this._prepareRenderOpened(); | 6364 this._prepareRenderOpened(); |
| 9189 this._manager.addOverlay(this); | 6365 this._manager.addOverlay(this); |
| 9190 // Move the focus to the child node with [autofocus]. | |
| 9191 this._applyFocus(); | 6366 this._applyFocus(); |
| 9192 | 6367 |
| 9193 this._renderOpened(); | 6368 this._renderOpened(); |
| 9194 } else { | 6369 } else { |
| 9195 // Remove overlay, then restore the focus before actually closing. | |
| 9196 this._manager.removeOverlay(this); | 6370 this._manager.removeOverlay(this); |
| 9197 this._applyFocus(); | 6371 this._applyFocus(); |
| 9198 | 6372 |
| 9199 this._renderClosed(); | 6373 this._renderClosed(); |
| 9200 } | 6374 } |
| 9201 }, | 6375 }, |
| 9202 | 6376 |
| 9203 /** | |
| 9204 * Executes a callback on the next animation frame, overriding any previous | |
| 9205 * callback awaiting for the next animation frame. e.g. | |
| 9206 * `__onNextAnimationFrame(callback1) && __onNextAnimationFrame(callback2)`; | |
| 9207 * `callback1` will never be invoked. | |
| 9208 * @param {!Function} callback Its `this` parameter is the overlay itself. | |
| 9209 * @private | |
| 9210 */ | |
| 9211 __onNextAnimationFrame: function(callback) { | 6377 __onNextAnimationFrame: function(callback) { |
| 9212 if (this.__raf) { | 6378 if (this.__raf) { |
| 9213 window.cancelAnimationFrame(this.__raf); | 6379 window.cancelAnimationFrame(this.__raf); |
| 9214 } | 6380 } |
| 9215 var self = this; | 6381 var self = this; |
| 9216 this.__raf = window.requestAnimationFrame(function nextAnimationFrame() { | 6382 this.__raf = window.requestAnimationFrame(function nextAnimationFrame() { |
| 9217 self.__raf = null; | 6383 self.__raf = null; |
| 9218 callback.call(self); | 6384 callback.call(self); |
| 9219 }); | 6385 }); |
| 9220 } | 6386 } |
| 9221 | 6387 |
| 9222 }; | 6388 }; |
| 9223 | 6389 |
| 9224 /** @polymerBehavior */ | 6390 /** @polymerBehavior */ |
| 9225 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; | 6391 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; |
| 9226 | 6392 |
| 9227 /** | |
| 9228 * Fired after the overlay opens. | |
| 9229 * @event iron-overlay-opened | |
| 9230 */ | |
| 9231 | 6393 |
| 9232 /** | |
| 9233 * Fired when the overlay is canceled, but before it is closed. | |
| 9234 * @event iron-overlay-canceled | |
| 9235 * @param {Event} event The closing of the overlay can be prevented | |
| 9236 * by calling `event.preventDefault()`. The `event.detail` is the original eve
nt that | |
| 9237 * originated the canceling (e.g. ESC keyboard event or click event outside th
e overlay). | |
| 9238 */ | |
| 9239 | 6394 |
| 9240 /** | |
| 9241 * Fired after the overlay closes. | |
| 9242 * @event iron-overlay-closed | |
| 9243 * @param {Event} event The `event.detail` is the `closingReason` property | |
| 9244 * (contains `canceled`, whether the overlay was canceled). | |
| 9245 */ | |
| 9246 | 6395 |
| 9247 })(); | 6396 })(); |
| 9248 /** | |
| 9249 * `Polymer.NeonAnimatableBehavior` is implemented by elements containing anim
ations for use with | |
| 9250 * elements implementing `Polymer.NeonAnimationRunnerBehavior`. | |
| 9251 * @polymerBehavior | |
| 9252 */ | |
| 9253 Polymer.NeonAnimatableBehavior = { | 6397 Polymer.NeonAnimatableBehavior = { |
| 9254 | 6398 |
| 9255 properties: { | 6399 properties: { |
| 9256 | 6400 |
| 9257 /** | |
| 9258 * Animation configuration. See README for more info. | |
| 9259 */ | |
| 9260 animationConfig: { | 6401 animationConfig: { |
| 9261 type: Object | 6402 type: Object |
| 9262 }, | 6403 }, |
| 9263 | 6404 |
| 9264 /** | |
| 9265 * Convenience property for setting an 'entry' animation. Do not set `anim
ationConfig.entry` | |
| 9266 * manually if using this. The animated node is set to `this` if using thi
s property. | |
| 9267 */ | |
| 9268 entryAnimation: { | 6405 entryAnimation: { |
| 9269 observer: '_entryAnimationChanged', | 6406 observer: '_entryAnimationChanged', |
| 9270 type: String | 6407 type: String |
| 9271 }, | 6408 }, |
| 9272 | 6409 |
| 9273 /** | |
| 9274 * Convenience property for setting an 'exit' animation. Do not set `anima
tionConfig.exit` | |
| 9275 * manually if using this. The animated node is set to `this` if using thi
s property. | |
| 9276 */ | |
| 9277 exitAnimation: { | 6410 exitAnimation: { |
| 9278 observer: '_exitAnimationChanged', | 6411 observer: '_exitAnimationChanged', |
| 9279 type: String | 6412 type: String |
| 9280 } | 6413 } |
| 9281 | 6414 |
| 9282 }, | 6415 }, |
| 9283 | 6416 |
| 9284 _entryAnimationChanged: function() { | 6417 _entryAnimationChanged: function() { |
| 9285 this.animationConfig = this.animationConfig || {}; | 6418 this.animationConfig = this.animationConfig || {}; |
| 9286 this.animationConfig['entry'] = [{ | 6419 this.animationConfig['entry'] = [{ |
| 9287 name: this.entryAnimation, | 6420 name: this.entryAnimation, |
| 9288 node: this | 6421 node: this |
| 9289 }]; | 6422 }]; |
| 9290 }, | 6423 }, |
| 9291 | 6424 |
| 9292 _exitAnimationChanged: function() { | 6425 _exitAnimationChanged: function() { |
| 9293 this.animationConfig = this.animationConfig || {}; | 6426 this.animationConfig = this.animationConfig || {}; |
| 9294 this.animationConfig['exit'] = [{ | 6427 this.animationConfig['exit'] = [{ |
| 9295 name: this.exitAnimation, | 6428 name: this.exitAnimation, |
| 9296 node: this | 6429 node: this |
| 9297 }]; | 6430 }]; |
| 9298 }, | 6431 }, |
| 9299 | 6432 |
| 9300 _copyProperties: function(config1, config2) { | 6433 _copyProperties: function(config1, config2) { |
| 9301 // shallowly copy properties from config2 to config1 | |
| 9302 for (var property in config2) { | 6434 for (var property in config2) { |
| 9303 config1[property] = config2[property]; | 6435 config1[property] = config2[property]; |
| 9304 } | 6436 } |
| 9305 }, | 6437 }, |
| 9306 | 6438 |
| 9307 _cloneConfig: function(config) { | 6439 _cloneConfig: function(config) { |
| 9308 var clone = { | 6440 var clone = { |
| 9309 isClone: true | 6441 isClone: true |
| 9310 }; | 6442 }; |
| 9311 this._copyProperties(clone, config); | 6443 this._copyProperties(clone, config); |
| 9312 return clone; | 6444 return clone; |
| 9313 }, | 6445 }, |
| 9314 | 6446 |
| 9315 _getAnimationConfigRecursive: function(type, map, allConfigs) { | 6447 _getAnimationConfigRecursive: function(type, map, allConfigs) { |
| 9316 if (!this.animationConfig) { | 6448 if (!this.animationConfig) { |
| 9317 return; | 6449 return; |
| 9318 } | 6450 } |
| 9319 | 6451 |
| 9320 if(this.animationConfig.value && typeof this.animationConfig.value === 'fu
nction') { | 6452 if(this.animationConfig.value && typeof this.animationConfig.value === 'fu
nction') { |
| 9321 this._warn(this._logf('playAnimation', "Please put 'animationConfig' ins
ide of your components 'properties' object instead of outside of it.")); | 6453 this._warn(this._logf('playAnimation', "Please put 'animationConfig' ins
ide of your components 'properties' object instead of outside of it.")); |
| 9322 return; | 6454 return; |
| 9323 } | 6455 } |
| 9324 | 6456 |
| 9325 // type is optional | |
| 9326 var thisConfig; | 6457 var thisConfig; |
| 9327 if (type) { | 6458 if (type) { |
| 9328 thisConfig = this.animationConfig[type]; | 6459 thisConfig = this.animationConfig[type]; |
| 9329 } else { | 6460 } else { |
| 9330 thisConfig = this.animationConfig; | 6461 thisConfig = this.animationConfig; |
| 9331 } | 6462 } |
| 9332 | 6463 |
| 9333 if (!Array.isArray(thisConfig)) { | 6464 if (!Array.isArray(thisConfig)) { |
| 9334 thisConfig = [thisConfig]; | 6465 thisConfig = [thisConfig]; |
| 9335 } | 6466 } |
| 9336 | 6467 |
| 9337 // iterate animations and recurse to process configurations from child nod
es | |
| 9338 if (thisConfig) { | 6468 if (thisConfig) { |
| 9339 for (var config, index = 0; config = thisConfig[index]; index++) { | 6469 for (var config, index = 0; config = thisConfig[index]; index++) { |
| 9340 if (config.animatable) { | 6470 if (config.animatable) { |
| 9341 config.animatable._getAnimationConfigRecursive(config.type || type,
map, allConfigs); | 6471 config.animatable._getAnimationConfigRecursive(config.type || type,
map, allConfigs); |
| 9342 } else { | 6472 } else { |
| 9343 if (config.id) { | 6473 if (config.id) { |
| 9344 var cachedConfig = map[config.id]; | 6474 var cachedConfig = map[config.id]; |
| 9345 if (cachedConfig) { | 6475 if (cachedConfig) { |
| 9346 // merge configurations with the same id, making a clone lazily | |
| 9347 if (!cachedConfig.isClone) { | 6476 if (!cachedConfig.isClone) { |
| 9348 map[config.id] = this._cloneConfig(cachedConfig) | 6477 map[config.id] = this._cloneConfig(cachedConfig) |
| 9349 cachedConfig = map[config.id]; | 6478 cachedConfig = map[config.id]; |
| 9350 } | 6479 } |
| 9351 this._copyProperties(cachedConfig, config); | 6480 this._copyProperties(cachedConfig, config); |
| 9352 } else { | 6481 } else { |
| 9353 // put any configs with an id into a map | |
| 9354 map[config.id] = config; | 6482 map[config.id] = config; |
| 9355 } | 6483 } |
| 9356 } else { | 6484 } else { |
| 9357 allConfigs.push(config); | 6485 allConfigs.push(config); |
| 9358 } | 6486 } |
| 9359 } | 6487 } |
| 9360 } | 6488 } |
| 9361 } | 6489 } |
| 9362 }, | 6490 }, |
| 9363 | 6491 |
| 9364 /** | |
| 9365 * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this
method to configure | |
| 9366 * an animation with an optional type. Elements implementing `Polymer.NeonAn
imatableBehavior` | |
| 9367 * should define the property `animationConfig`, which is either a configura
tion object | |
| 9368 * or a map of animation type to array of configuration objects. | |
| 9369 */ | |
| 9370 getAnimationConfig: function(type) { | 6492 getAnimationConfig: function(type) { |
| 9371 var map = {}; | 6493 var map = {}; |
| 9372 var allConfigs = []; | 6494 var allConfigs = []; |
| 9373 this._getAnimationConfigRecursive(type, map, allConfigs); | 6495 this._getAnimationConfigRecursive(type, map, allConfigs); |
| 9374 // append the configurations saved in the map to the array | |
| 9375 for (var key in map) { | 6496 for (var key in map) { |
| 9376 allConfigs.push(map[key]); | 6497 allConfigs.push(map[key]); |
| 9377 } | 6498 } |
| 9378 return allConfigs; | 6499 return allConfigs; |
| 9379 } | 6500 } |
| 9380 | 6501 |
| 9381 }; | 6502 }; |
| 9382 /** | |
| 9383 * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations. | |
| 9384 * | |
| 9385 * @polymerBehavior Polymer.NeonAnimationRunnerBehavior | |
| 9386 */ | |
| 9387 Polymer.NeonAnimationRunnerBehaviorImpl = { | 6503 Polymer.NeonAnimationRunnerBehaviorImpl = { |
| 9388 | 6504 |
| 9389 _configureAnimations: function(configs) { | 6505 _configureAnimations: function(configs) { |
| 9390 var results = []; | 6506 var results = []; |
| 9391 if (configs.length > 0) { | 6507 if (configs.length > 0) { |
| 9392 for (var config, index = 0; config = configs[index]; index++) { | 6508 for (var config, index = 0; config = configs[index]; index++) { |
| 9393 var neonAnimation = document.createElement(config.name); | 6509 var neonAnimation = document.createElement(config.name); |
| 9394 // is this element actually a neon animation? | |
| 9395 if (neonAnimation.isNeonAnimation) { | 6510 if (neonAnimation.isNeonAnimation) { |
| 9396 var result = null; | 6511 var result = null; |
| 9397 // configuration or play could fail if polyfills aren't loaded | |
| 9398 try { | 6512 try { |
| 9399 result = neonAnimation.configure(config); | 6513 result = neonAnimation.configure(config); |
| 9400 // Check if we have an Effect rather than an Animation | |
| 9401 if (typeof result.cancel != 'function') { | 6514 if (typeof result.cancel != 'function') { |
| 9402 result = document.timeline.play(result); | 6515 result = document.timeline.play(result); |
| 9403 } | 6516 } |
| 9404 } catch (e) { | 6517 } catch (e) { |
| 9405 result = null; | 6518 result = null; |
| 9406 console.warn('Couldnt play', '(', config.name, ').', e); | 6519 console.warn('Couldnt play', '(', config.name, ').', e); |
| 9407 } | 6520 } |
| 9408 if (result) { | 6521 if (result) { |
| 9409 results.push({ | 6522 results.push({ |
| 9410 neonAnimation: neonAnimation, | 6523 neonAnimation: neonAnimation, |
| (...skipping 22 matching lines...) Expand all Loading... |
| 9433 | 6546 |
| 9434 _complete: function(activeEntries) { | 6547 _complete: function(activeEntries) { |
| 9435 for (var i = 0; i < activeEntries.length; i++) { | 6548 for (var i = 0; i < activeEntries.length; i++) { |
| 9436 activeEntries[i].neonAnimation.complete(activeEntries[i].config); | 6549 activeEntries[i].neonAnimation.complete(activeEntries[i].config); |
| 9437 } | 6550 } |
| 9438 for (var i = 0; i < activeEntries.length; i++) { | 6551 for (var i = 0; i < activeEntries.length; i++) { |
| 9439 activeEntries[i].animation.cancel(); | 6552 activeEntries[i].animation.cancel(); |
| 9440 } | 6553 } |
| 9441 }, | 6554 }, |
| 9442 | 6555 |
| 9443 /** | |
| 9444 * Plays an animation with an optional `type`. | |
| 9445 * @param {string=} type | |
| 9446 * @param {!Object=} cookie | |
| 9447 */ | |
| 9448 playAnimation: function(type, cookie) { | 6556 playAnimation: function(type, cookie) { |
| 9449 var configs = this.getAnimationConfig(type); | 6557 var configs = this.getAnimationConfig(type); |
| 9450 if (!configs) { | 6558 if (!configs) { |
| 9451 return; | 6559 return; |
| 9452 } | 6560 } |
| 9453 this._active = this._active || {}; | 6561 this._active = this._active || {}; |
| 9454 if (this._active[type]) { | 6562 if (this._active[type]) { |
| 9455 this._complete(this._active[type]); | 6563 this._complete(this._active[type]); |
| 9456 delete this._active[type]; | 6564 delete this._active[type]; |
| 9457 } | 6565 } |
| (...skipping 11 matching lines...) Expand all Loading... |
| 9469 activeEntries[i].animation.onfinish = function() { | 6577 activeEntries[i].animation.onfinish = function() { |
| 9470 if (this._shouldComplete(activeEntries)) { | 6578 if (this._shouldComplete(activeEntries)) { |
| 9471 this._complete(activeEntries); | 6579 this._complete(activeEntries); |
| 9472 delete this._active[type]; | 6580 delete this._active[type]; |
| 9473 this.fire('neon-animation-finish', cookie, {bubbles: false}); | 6581 this.fire('neon-animation-finish', cookie, {bubbles: false}); |
| 9474 } | 6582 } |
| 9475 }.bind(this); | 6583 }.bind(this); |
| 9476 } | 6584 } |
| 9477 }, | 6585 }, |
| 9478 | 6586 |
| 9479 /** | |
| 9480 * Cancels the currently running animations. | |
| 9481 */ | |
| 9482 cancelAnimation: function() { | 6587 cancelAnimation: function() { |
| 9483 for (var k in this._animations) { | 6588 for (var k in this._animations) { |
| 9484 this._animations[k].cancel(); | 6589 this._animations[k].cancel(); |
| 9485 } | 6590 } |
| 9486 this._animations = {}; | 6591 this._animations = {}; |
| 9487 } | 6592 } |
| 9488 }; | 6593 }; |
| 9489 | 6594 |
| 9490 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ | 6595 /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ |
| 9491 Polymer.NeonAnimationRunnerBehavior = [ | 6596 Polymer.NeonAnimationRunnerBehavior = [ |
| 9492 Polymer.NeonAnimatableBehavior, | 6597 Polymer.NeonAnimatableBehavior, |
| 9493 Polymer.NeonAnimationRunnerBehaviorImpl | 6598 Polymer.NeonAnimationRunnerBehaviorImpl |
| 9494 ]; | 6599 ]; |
| 9495 /** | |
| 9496 * Use `Polymer.NeonAnimationBehavior` to implement an animation. | |
| 9497 * @polymerBehavior | |
| 9498 */ | |
| 9499 Polymer.NeonAnimationBehavior = { | 6600 Polymer.NeonAnimationBehavior = { |
| 9500 | 6601 |
| 9501 properties: { | 6602 properties: { |
| 9502 | 6603 |
| 9503 /** | |
| 9504 * Defines the animation timing. | |
| 9505 */ | |
| 9506 animationTiming: { | 6604 animationTiming: { |
| 9507 type: Object, | 6605 type: Object, |
| 9508 value: function() { | 6606 value: function() { |
| 9509 return { | 6607 return { |
| 9510 duration: 500, | 6608 duration: 500, |
| 9511 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', | 6609 easing: 'cubic-bezier(0.4, 0, 0.2, 1)', |
| 9512 fill: 'both' | 6610 fill: 'both' |
| 9513 } | 6611 } |
| 9514 } | 6612 } |
| 9515 } | 6613 } |
| 9516 | 6614 |
| 9517 }, | 6615 }, |
| 9518 | 6616 |
| 9519 /** | |
| 9520 * Can be used to determine that elements implement this behavior. | |
| 9521 */ | |
| 9522 isNeonAnimation: true, | 6617 isNeonAnimation: true, |
| 9523 | 6618 |
| 9524 /** | |
| 9525 * Do any animation configuration here. | |
| 9526 */ | |
| 9527 // configure: function(config) { | |
| 9528 // }, | |
| 9529 | 6619 |
| 9530 /** | |
| 9531 * Returns the animation timing by mixing in properties from `config` to the
defaults defined | |
| 9532 * by the animation. | |
| 9533 */ | |
| 9534 timingFromConfig: function(config) { | 6620 timingFromConfig: function(config) { |
| 9535 if (config.timing) { | 6621 if (config.timing) { |
| 9536 for (var property in config.timing) { | 6622 for (var property in config.timing) { |
| 9537 this.animationTiming[property] = config.timing[property]; | 6623 this.animationTiming[property] = config.timing[property]; |
| 9538 } | 6624 } |
| 9539 } | 6625 } |
| 9540 return this.animationTiming; | 6626 return this.animationTiming; |
| 9541 }, | 6627 }, |
| 9542 | 6628 |
| 9543 /** | |
| 9544 * Sets `transform` and `transformOrigin` properties along with the prefixed
versions. | |
| 9545 */ | |
| 9546 setPrefixedProperty: function(node, property, value) { | 6629 setPrefixedProperty: function(node, property, value) { |
| 9547 var map = { | 6630 var map = { |
| 9548 'transform': ['webkitTransform'], | 6631 'transform': ['webkitTransform'], |
| 9549 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] | 6632 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] |
| 9550 }; | 6633 }; |
| 9551 var prefixes = map[property]; | 6634 var prefixes = map[property]; |
| 9552 for (var prefix, index = 0; prefix = prefixes[index]; index++) { | 6635 for (var prefix, index = 0; prefix = prefixes[index]; index++) { |
| 9553 node.style[prefix] = value; | 6636 node.style[prefix] = value; |
| 9554 } | 6637 } |
| 9555 node.style[property] = value; | 6638 node.style[property] = value; |
| 9556 }, | 6639 }, |
| 9557 | 6640 |
| 9558 /** | |
| 9559 * Called when the animation finishes. | |
| 9560 */ | |
| 9561 complete: function() {} | 6641 complete: function() {} |
| 9562 | 6642 |
| 9563 }; | 6643 }; |
| 9564 Polymer({ | 6644 Polymer({ |
| 9565 | 6645 |
| 9566 is: 'opaque-animation', | 6646 is: 'opaque-animation', |
| 9567 | 6647 |
| 9568 behaviors: [ | 6648 behaviors: [ |
| 9569 Polymer.NeonAnimationBehavior | 6649 Polymer.NeonAnimationBehavior |
| 9570 ], | 6650 ], |
| 9571 | 6651 |
| 9572 configure: function(config) { | 6652 configure: function(config) { |
| 9573 var node = config.node; | 6653 var node = config.node; |
| 9574 this._effect = new KeyframeEffect(node, [ | 6654 this._effect = new KeyframeEffect(node, [ |
| 9575 {'opacity': '1'}, | 6655 {'opacity': '1'}, |
| 9576 {'opacity': '1'} | 6656 {'opacity': '1'} |
| 9577 ], this.timingFromConfig(config)); | 6657 ], this.timingFromConfig(config)); |
| 9578 node.style.opacity = '0'; | 6658 node.style.opacity = '0'; |
| 9579 return this._effect; | 6659 return this._effect; |
| 9580 }, | 6660 }, |
| 9581 | 6661 |
| 9582 complete: function(config) { | 6662 complete: function(config) { |
| 9583 config.node.style.opacity = ''; | 6663 config.node.style.opacity = ''; |
| 9584 } | 6664 } |
| 9585 | 6665 |
| 9586 }); | 6666 }); |
| 9587 (function() { | 6667 (function() { |
| 9588 'use strict'; | 6668 'use strict'; |
| 9589 // Used to calculate the scroll direction during touch events. | |
| 9590 var LAST_TOUCH_POSITION = { | 6669 var LAST_TOUCH_POSITION = { |
| 9591 pageX: 0, | 6670 pageX: 0, |
| 9592 pageY: 0 | 6671 pageY: 0 |
| 9593 }; | 6672 }; |
| 9594 // Used to avoid computing event.path and filter scrollable nodes (better pe
rf). | |
| 9595 var ROOT_TARGET = null; | 6673 var ROOT_TARGET = null; |
| 9596 var SCROLLABLE_NODES = []; | 6674 var SCROLLABLE_NODES = []; |
| 9597 | 6675 |
| 9598 /** | |
| 9599 * The IronDropdownScrollManager is intended to provide a central source | |
| 9600 * of authority and control over which elements in a document are currently | |
| 9601 * allowed to scroll. | |
| 9602 */ | |
| 9603 | 6676 |
| 9604 Polymer.IronDropdownScrollManager = { | 6677 Polymer.IronDropdownScrollManager = { |
| 9605 | 6678 |
| 9606 /** | |
| 9607 * The current element that defines the DOM boundaries of the | |
| 9608 * scroll lock. This is always the most recently locking element. | |
| 9609 */ | |
| 9610 get currentLockingElement() { | 6679 get currentLockingElement() { |
| 9611 return this._lockingElements[this._lockingElements.length - 1]; | 6680 return this._lockingElements[this._lockingElements.length - 1]; |
| 9612 }, | 6681 }, |
| 9613 | 6682 |
| 9614 /** | |
| 9615 * Returns true if the provided element is "scroll locked", which is to | |
| 9616 * say that it cannot be scrolled via pointer or keyboard interactions. | |
| 9617 * | |
| 9618 * @param {HTMLElement} element An HTML element instance which may or may | |
| 9619 * not be scroll locked. | |
| 9620 */ | |
| 9621 elementIsScrollLocked: function(element) { | 6683 elementIsScrollLocked: function(element) { |
| 9622 var currentLockingElement = this.currentLockingElement; | 6684 var currentLockingElement = this.currentLockingElement; |
| 9623 | 6685 |
| 9624 if (currentLockingElement === undefined) | 6686 if (currentLockingElement === undefined) |
| 9625 return false; | 6687 return false; |
| 9626 | 6688 |
| 9627 var scrollLocked; | 6689 var scrollLocked; |
| 9628 | 6690 |
| 9629 if (this._hasCachedLockedElement(element)) { | 6691 if (this._hasCachedLockedElement(element)) { |
| 9630 return true; | 6692 return true; |
| 9631 } | 6693 } |
| 9632 | 6694 |
| 9633 if (this._hasCachedUnlockedElement(element)) { | 6695 if (this._hasCachedUnlockedElement(element)) { |
| 9634 return false; | 6696 return false; |
| 9635 } | 6697 } |
| 9636 | 6698 |
| 9637 scrollLocked = !!currentLockingElement && | 6699 scrollLocked = !!currentLockingElement && |
| 9638 currentLockingElement !== element && | 6700 currentLockingElement !== element && |
| 9639 !this._composedTreeContains(currentLockingElement, element); | 6701 !this._composedTreeContains(currentLockingElement, element); |
| 9640 | 6702 |
| 9641 if (scrollLocked) { | 6703 if (scrollLocked) { |
| 9642 this._lockedElementCache.push(element); | 6704 this._lockedElementCache.push(element); |
| 9643 } else { | 6705 } else { |
| 9644 this._unlockedElementCache.push(element); | 6706 this._unlockedElementCache.push(element); |
| 9645 } | 6707 } |
| 9646 | 6708 |
| 9647 return scrollLocked; | 6709 return scrollLocked; |
| 9648 }, | 6710 }, |
| 9649 | 6711 |
| 9650 /** | |
| 9651 * Push an element onto the current scroll lock stack. The most recently | |
| 9652 * pushed element and its children will be considered scrollable. All | |
| 9653 * other elements will not be scrollable. | |
| 9654 * | |
| 9655 * Scroll locking is implemented as a stack so that cases such as | |
| 9656 * dropdowns within dropdowns are handled well. | |
| 9657 * | |
| 9658 * @param {HTMLElement} element The element that should lock scroll. | |
| 9659 */ | |
| 9660 pushScrollLock: function(element) { | 6712 pushScrollLock: function(element) { |
| 9661 // Prevent pushing the same element twice | |
| 9662 if (this._lockingElements.indexOf(element) >= 0) { | 6713 if (this._lockingElements.indexOf(element) >= 0) { |
| 9663 return; | 6714 return; |
| 9664 } | 6715 } |
| 9665 | 6716 |
| 9666 if (this._lockingElements.length === 0) { | 6717 if (this._lockingElements.length === 0) { |
| 9667 this._lockScrollInteractions(); | 6718 this._lockScrollInteractions(); |
| 9668 } | 6719 } |
| 9669 | 6720 |
| 9670 this._lockingElements.push(element); | 6721 this._lockingElements.push(element); |
| 9671 | 6722 |
| 9672 this._lockedElementCache = []; | 6723 this._lockedElementCache = []; |
| 9673 this._unlockedElementCache = []; | 6724 this._unlockedElementCache = []; |
| 9674 }, | 6725 }, |
| 9675 | 6726 |
| 9676 /** | |
| 9677 * Remove an element from the scroll lock stack. The element being | |
| 9678 * removed does not need to be the most recently pushed element. However, | |
| 9679 * the scroll lock constraints only change when the most recently pushed | |
| 9680 * element is removed. | |
| 9681 * | |
| 9682 * @param {HTMLElement} element The element to remove from the scroll | |
| 9683 * lock stack. | |
| 9684 */ | |
| 9685 removeScrollLock: function(element) { | 6727 removeScrollLock: function(element) { |
| 9686 var index = this._lockingElements.indexOf(element); | 6728 var index = this._lockingElements.indexOf(element); |
| 9687 | 6729 |
| 9688 if (index === -1) { | 6730 if (index === -1) { |
| 9689 return; | 6731 return; |
| 9690 } | 6732 } |
| 9691 | 6733 |
| 9692 this._lockingElements.splice(index, 1); | 6734 this._lockingElements.splice(index, 1); |
| 9693 | 6735 |
| 9694 this._lockedElementCache = []; | 6736 this._lockedElementCache = []; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 9707 | 6749 |
| 9708 _hasCachedLockedElement: function(element) { | 6750 _hasCachedLockedElement: function(element) { |
| 9709 return this._lockedElementCache.indexOf(element) > -1; | 6751 return this._lockedElementCache.indexOf(element) > -1; |
| 9710 }, | 6752 }, |
| 9711 | 6753 |
| 9712 _hasCachedUnlockedElement: function(element) { | 6754 _hasCachedUnlockedElement: function(element) { |
| 9713 return this._unlockedElementCache.indexOf(element) > -1; | 6755 return this._unlockedElementCache.indexOf(element) > -1; |
| 9714 }, | 6756 }, |
| 9715 | 6757 |
| 9716 _composedTreeContains: function(element, child) { | 6758 _composedTreeContains: function(element, child) { |
| 9717 // NOTE(cdata): This method iterates over content elements and their | |
| 9718 // corresponding distributed nodes to implement a contains-like method | |
| 9719 // that pierces through the composed tree of the ShadowDOM. Results of | |
| 9720 // this operation are cached (elsewhere) on a per-scroll-lock basis, to | |
| 9721 // guard against potentially expensive lookups happening repeatedly as | |
| 9722 // a user scrolls / touchmoves. | |
| 9723 var contentElements; | 6759 var contentElements; |
| 9724 var distributedNodes; | 6760 var distributedNodes; |
| 9725 var contentIndex; | 6761 var contentIndex; |
| 9726 var nodeIndex; | 6762 var nodeIndex; |
| 9727 | 6763 |
| 9728 if (element.contains(child)) { | 6764 if (element.contains(child)) { |
| 9729 return true; | 6765 return true; |
| 9730 } | 6766 } |
| 9731 | 6767 |
| 9732 contentElements = Polymer.dom(element).querySelectorAll('content'); | 6768 contentElements = Polymer.dom(element).querySelectorAll('content'); |
| 9733 | 6769 |
| 9734 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { | 6770 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { |
| 9735 | 6771 |
| 9736 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); | 6772 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); |
| 9737 | 6773 |
| 9738 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ | 6774 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ |
| 9739 | 6775 |
| 9740 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ | 6776 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ |
| 9741 return true; | 6777 return true; |
| 9742 } | 6778 } |
| 9743 } | 6779 } |
| 9744 } | 6780 } |
| 9745 | 6781 |
| 9746 return false; | 6782 return false; |
| 9747 }, | 6783 }, |
| 9748 | 6784 |
| 9749 _scrollInteractionHandler: function(event) { | 6785 _scrollInteractionHandler: function(event) { |
| 9750 // Avoid canceling an event with cancelable=false, e.g. scrolling is in | |
| 9751 // progress and cannot be interrupted. | |
| 9752 if (event.cancelable && this._shouldPreventScrolling(event)) { | 6786 if (event.cancelable && this._shouldPreventScrolling(event)) { |
| 9753 event.preventDefault(); | 6787 event.preventDefault(); |
| 9754 } | 6788 } |
| 9755 // If event has targetTouches (touch event), update last touch position. | |
| 9756 if (event.targetTouches) { | 6789 if (event.targetTouches) { |
| 9757 var touch = event.targetTouches[0]; | 6790 var touch = event.targetTouches[0]; |
| 9758 LAST_TOUCH_POSITION.pageX = touch.pageX; | 6791 LAST_TOUCH_POSITION.pageX = touch.pageX; |
| 9759 LAST_TOUCH_POSITION.pageY = touch.pageY; | 6792 LAST_TOUCH_POSITION.pageY = touch.pageY; |
| 9760 } | 6793 } |
| 9761 }, | 6794 }, |
| 9762 | 6795 |
| 9763 _lockScrollInteractions: function() { | 6796 _lockScrollInteractions: function() { |
| 9764 this._boundScrollHandler = this._boundScrollHandler || | 6797 this._boundScrollHandler = this._boundScrollHandler || |
| 9765 this._scrollInteractionHandler.bind(this); | 6798 this._scrollInteractionHandler.bind(this); |
| 9766 // Modern `wheel` event for mouse wheel scrolling: | |
| 9767 document.addEventListener('wheel', this._boundScrollHandler, true); | 6799 document.addEventListener('wheel', this._boundScrollHandler, true); |
| 9768 // Older, non-standard `mousewheel` event for some FF: | |
| 9769 document.addEventListener('mousewheel', this._boundScrollHandler, true); | 6800 document.addEventListener('mousewheel', this._boundScrollHandler, true); |
| 9770 // IE: | |
| 9771 document.addEventListener('DOMMouseScroll', this._boundScrollHandler, tr
ue); | 6801 document.addEventListener('DOMMouseScroll', this._boundScrollHandler, tr
ue); |
| 9772 // Save the SCROLLABLE_NODES on touchstart, to be used on touchmove. | |
| 9773 document.addEventListener('touchstart', this._boundScrollHandler, true); | 6802 document.addEventListener('touchstart', this._boundScrollHandler, true); |
| 9774 // Mobile devices can scroll on touch move: | |
| 9775 document.addEventListener('touchmove', this._boundScrollHandler, true); | 6803 document.addEventListener('touchmove', this._boundScrollHandler, true); |
| 9776 }, | 6804 }, |
| 9777 | 6805 |
| 9778 _unlockScrollInteractions: function() { | 6806 _unlockScrollInteractions: function() { |
| 9779 document.removeEventListener('wheel', this._boundScrollHandler, true); | 6807 document.removeEventListener('wheel', this._boundScrollHandler, true); |
| 9780 document.removeEventListener('mousewheel', this._boundScrollHandler, tru
e); | 6808 document.removeEventListener('mousewheel', this._boundScrollHandler, tru
e); |
| 9781 document.removeEventListener('DOMMouseScroll', this._boundScrollHandler,
true); | 6809 document.removeEventListener('DOMMouseScroll', this._boundScrollHandler,
true); |
| 9782 document.removeEventListener('touchstart', this._boundScrollHandler, tru
e); | 6810 document.removeEventListener('touchstart', this._boundScrollHandler, tru
e); |
| 9783 document.removeEventListener('touchmove', this._boundScrollHandler, true
); | 6811 document.removeEventListener('touchmove', this._boundScrollHandler, true
); |
| 9784 }, | 6812 }, |
| 9785 | 6813 |
| 9786 /** | |
| 9787 * Returns true if the event causes scroll outside the current locking | |
| 9788 * element, e.g. pointer/keyboard interactions, or scroll "leaking" | |
| 9789 * outside the locking element when it is already at its scroll boundaries
. | |
| 9790 * @param {!Event} event | |
| 9791 * @return {boolean} | |
| 9792 * @private | |
| 9793 */ | |
| 9794 _shouldPreventScrolling: function(event) { | 6814 _shouldPreventScrolling: function(event) { |
| 9795 | 6815 |
| 9796 // Update if root target changed. For touch events, ensure we don't | |
| 9797 // update during touchmove. | |
| 9798 var target = Polymer.dom(event).rootTarget; | 6816 var target = Polymer.dom(event).rootTarget; |
| 9799 if (event.type !== 'touchmove' && ROOT_TARGET !== target) { | 6817 if (event.type !== 'touchmove' && ROOT_TARGET !== target) { |
| 9800 ROOT_TARGET = target; | 6818 ROOT_TARGET = target; |
| 9801 SCROLLABLE_NODES = this._getScrollableNodes(Polymer.dom(event).path); | 6819 SCROLLABLE_NODES = this._getScrollableNodes(Polymer.dom(event).path); |
| 9802 } | 6820 } |
| 9803 | 6821 |
| 9804 // Prevent event if no scrollable nodes. | |
| 9805 if (!SCROLLABLE_NODES.length) { | 6822 if (!SCROLLABLE_NODES.length) { |
| 9806 return true; | 6823 return true; |
| 9807 } | 6824 } |
| 9808 // Don't prevent touchstart event inside the locking element when it has | |
| 9809 // scrollable nodes. | |
| 9810 if (event.type === 'touchstart') { | 6825 if (event.type === 'touchstart') { |
| 9811 return false; | 6826 return false; |
| 9812 } | 6827 } |
| 9813 // Get deltaX/Y. | |
| 9814 var info = this._getScrollInfo(event); | 6828 var info = this._getScrollInfo(event); |
| 9815 // Prevent if there is no child that can scroll. | |
| 9816 return !this._getScrollingNode(SCROLLABLE_NODES, info.deltaX, info.delta
Y); | 6829 return !this._getScrollingNode(SCROLLABLE_NODES, info.deltaX, info.delta
Y); |
| 9817 }, | 6830 }, |
| 9818 | 6831 |
| 9819 /** | |
| 9820 * Returns an array of scrollable nodes up to the current locking element, | |
| 9821 * which is included too if scrollable. | |
| 9822 * @param {!Array<Node>} nodes | |
| 9823 * @return {Array<Node>} scrollables | |
| 9824 * @private | |
| 9825 */ | |
| 9826 _getScrollableNodes: function(nodes) { | 6832 _getScrollableNodes: function(nodes) { |
| 9827 var scrollables = []; | 6833 var scrollables = []; |
| 9828 var lockingIndex = nodes.indexOf(this.currentLockingElement); | 6834 var lockingIndex = nodes.indexOf(this.currentLockingElement); |
| 9829 // Loop from root target to locking element (included). | |
| 9830 for (var i = 0; i <= lockingIndex; i++) { | 6835 for (var i = 0; i <= lockingIndex; i++) { |
| 9831 var node = nodes[i]; | 6836 var node = nodes[i]; |
| 9832 // Skip document fragments. | |
| 9833 if (node.nodeType === 11) { | 6837 if (node.nodeType === 11) { |
| 9834 continue; | 6838 continue; |
| 9835 } | 6839 } |
| 9836 // Check inline style before checking computed style. | |
| 9837 var style = node.style; | 6840 var style = node.style; |
| 9838 if (style.overflow !== 'scroll' && style.overflow !== 'auto') { | 6841 if (style.overflow !== 'scroll' && style.overflow !== 'auto') { |
| 9839 style = window.getComputedStyle(node); | 6842 style = window.getComputedStyle(node); |
| 9840 } | 6843 } |
| 9841 if (style.overflow === 'scroll' || style.overflow === 'auto') { | 6844 if (style.overflow === 'scroll' || style.overflow === 'auto') { |
| 9842 scrollables.push(node); | 6845 scrollables.push(node); |
| 9843 } | 6846 } |
| 9844 } | 6847 } |
| 9845 return scrollables; | 6848 return scrollables; |
| 9846 }, | 6849 }, |
| 9847 | 6850 |
| 9848 /** | |
| 9849 * Returns the node that is scrolling. If there is no scrolling, | |
| 9850 * returns undefined. | |
| 9851 * @param {!Array<Node>} nodes | |
| 9852 * @param {number} deltaX Scroll delta on the x-axis | |
| 9853 * @param {number} deltaY Scroll delta on the y-axis | |
| 9854 * @return {Node|undefined} | |
| 9855 * @private | |
| 9856 */ | |
| 9857 _getScrollingNode: function(nodes, deltaX, deltaY) { | 6851 _getScrollingNode: function(nodes, deltaX, deltaY) { |
| 9858 // No scroll. | |
| 9859 if (!deltaX && !deltaY) { | 6852 if (!deltaX && !deltaY) { |
| 9860 return; | 6853 return; |
| 9861 } | 6854 } |
| 9862 // Check only one axis according to where there is more scroll. | |
| 9863 // Prefer vertical to horizontal. | |
| 9864 var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX); | 6855 var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX); |
| 9865 for (var i = 0; i < nodes.length; i++) { | 6856 for (var i = 0; i < nodes.length; i++) { |
| 9866 var node = nodes[i]; | 6857 var node = nodes[i]; |
| 9867 var canScroll = false; | 6858 var canScroll = false; |
| 9868 if (verticalScroll) { | 6859 if (verticalScroll) { |
| 9869 // delta < 0 is scroll up, delta > 0 is scroll down. | |
| 9870 canScroll = deltaY < 0 ? node.scrollTop > 0 : | 6860 canScroll = deltaY < 0 ? node.scrollTop > 0 : |
| 9871 node.scrollTop < node.scrollHeight - node.clientHeight; | 6861 node.scrollTop < node.scrollHeight - node.clientHeight; |
| 9872 } else { | 6862 } else { |
| 9873 // delta < 0 is scroll left, delta > 0 is scroll right. | |
| 9874 canScroll = deltaX < 0 ? node.scrollLeft > 0 : | 6863 canScroll = deltaX < 0 ? node.scrollLeft > 0 : |
| 9875 node.scrollLeft < node.scrollWidth - node.clientWidth; | 6864 node.scrollLeft < node.scrollWidth - node.clientWidth; |
| 9876 } | 6865 } |
| 9877 if (canScroll) { | 6866 if (canScroll) { |
| 9878 return node; | 6867 return node; |
| 9879 } | 6868 } |
| 9880 } | 6869 } |
| 9881 }, | 6870 }, |
| 9882 | 6871 |
| 9883 /** | |
| 9884 * Returns scroll `deltaX` and `deltaY`. | |
| 9885 * @param {!Event} event The scroll event | |
| 9886 * @return {{ | |
| 9887 * deltaX: number The x-axis scroll delta (positive: scroll right, | |
| 9888 * negative: scroll left, 0: no scroll), | |
| 9889 * deltaY: number The y-axis scroll delta (positive: scroll down, | |
| 9890 * negative: scroll up, 0: no scroll) | |
| 9891 * }} info | |
| 9892 * @private | |
| 9893 */ | |
| 9894 _getScrollInfo: function(event) { | 6872 _getScrollInfo: function(event) { |
| 9895 var info = { | 6873 var info = { |
| 9896 deltaX: event.deltaX, | 6874 deltaX: event.deltaX, |
| 9897 deltaY: event.deltaY | 6875 deltaY: event.deltaY |
| 9898 }; | 6876 }; |
| 9899 // Already available. | |
| 9900 if ('deltaX' in event) { | 6877 if ('deltaX' in event) { |
| 9901 // do nothing, values are already good. | |
| 9902 } | 6878 } |
| 9903 // Safari has scroll info in `wheelDeltaX/Y`. | |
| 9904 else if ('wheelDeltaX' in event) { | 6879 else if ('wheelDeltaX' in event) { |
| 9905 info.deltaX = -event.wheelDeltaX; | 6880 info.deltaX = -event.wheelDeltaX; |
| 9906 info.deltaY = -event.wheelDeltaY; | 6881 info.deltaY = -event.wheelDeltaY; |
| 9907 } | 6882 } |
| 9908 // Firefox has scroll info in `detail` and `axis`. | |
| 9909 else if ('axis' in event) { | 6883 else if ('axis' in event) { |
| 9910 info.deltaX = event.axis === 1 ? event.detail : 0; | 6884 info.deltaX = event.axis === 1 ? event.detail : 0; |
| 9911 info.deltaY = event.axis === 2 ? event.detail : 0; | 6885 info.deltaY = event.axis === 2 ? event.detail : 0; |
| 9912 } | 6886 } |
| 9913 // On mobile devices, calculate scroll direction. | |
| 9914 else if (event.targetTouches) { | 6887 else if (event.targetTouches) { |
| 9915 var touch = event.targetTouches[0]; | 6888 var touch = event.targetTouches[0]; |
| 9916 // Touch moves from right to left => scrolling goes right. | |
| 9917 info.deltaX = LAST_TOUCH_POSITION.pageX - touch.pageX; | 6889 info.deltaX = LAST_TOUCH_POSITION.pageX - touch.pageX; |
| 9918 // Touch moves from down to up => scrolling goes down. | |
| 9919 info.deltaY = LAST_TOUCH_POSITION.pageY - touch.pageY; | 6890 info.deltaY = LAST_TOUCH_POSITION.pageY - touch.pageY; |
| 9920 } | 6891 } |
| 9921 return info; | 6892 return info; |
| 9922 } | 6893 } |
| 9923 }; | 6894 }; |
| 9924 })(); | 6895 })(); |
| 9925 (function() { | 6896 (function() { |
| 9926 'use strict'; | 6897 'use strict'; |
| 9927 | 6898 |
| 9928 Polymer({ | 6899 Polymer({ |
| 9929 is: 'iron-dropdown', | 6900 is: 'iron-dropdown', |
| 9930 | 6901 |
| 9931 behaviors: [ | 6902 behaviors: [ |
| 9932 Polymer.IronControlState, | 6903 Polymer.IronControlState, |
| 9933 Polymer.IronA11yKeysBehavior, | 6904 Polymer.IronA11yKeysBehavior, |
| 9934 Polymer.IronOverlayBehavior, | 6905 Polymer.IronOverlayBehavior, |
| 9935 Polymer.NeonAnimationRunnerBehavior | 6906 Polymer.NeonAnimationRunnerBehavior |
| 9936 ], | 6907 ], |
| 9937 | 6908 |
| 9938 properties: { | 6909 properties: { |
| 9939 /** | |
| 9940 * The orientation against which to align the dropdown content | |
| 9941 * horizontally relative to the dropdown trigger. | |
| 9942 * Overridden from `Polymer.IronFitBehavior`. | |
| 9943 */ | |
| 9944 horizontalAlign: { | 6910 horizontalAlign: { |
| 9945 type: String, | 6911 type: String, |
| 9946 value: 'left', | 6912 value: 'left', |
| 9947 reflectToAttribute: true | 6913 reflectToAttribute: true |
| 9948 }, | 6914 }, |
| 9949 | 6915 |
| 9950 /** | |
| 9951 * The orientation against which to align the dropdown content | |
| 9952 * vertically relative to the dropdown trigger. | |
| 9953 * Overridden from `Polymer.IronFitBehavior`. | |
| 9954 */ | |
| 9955 verticalAlign: { | 6916 verticalAlign: { |
| 9956 type: String, | 6917 type: String, |
| 9957 value: 'top', | 6918 value: 'top', |
| 9958 reflectToAttribute: true | 6919 reflectToAttribute: true |
| 9959 }, | 6920 }, |
| 9960 | 6921 |
| 9961 /** | |
| 9962 * An animation config. If provided, this will be used to animate the | |
| 9963 * opening of the dropdown. | |
| 9964 */ | |
| 9965 openAnimationConfig: { | 6922 openAnimationConfig: { |
| 9966 type: Object | 6923 type: Object |
| 9967 }, | 6924 }, |
| 9968 | 6925 |
| 9969 /** | |
| 9970 * An animation config. If provided, this will be used to animate the | |
| 9971 * closing of the dropdown. | |
| 9972 */ | |
| 9973 closeAnimationConfig: { | 6926 closeAnimationConfig: { |
| 9974 type: Object | 6927 type: Object |
| 9975 }, | 6928 }, |
| 9976 | 6929 |
| 9977 /** | |
| 9978 * If provided, this will be the element that will be focused when | |
| 9979 * the dropdown opens. | |
| 9980 */ | |
| 9981 focusTarget: { | 6930 focusTarget: { |
| 9982 type: Object | 6931 type: Object |
| 9983 }, | 6932 }, |
| 9984 | 6933 |
| 9985 /** | |
| 9986 * Set to true to disable animations when opening and closing the | |
| 9987 * dropdown. | |
| 9988 */ | |
| 9989 noAnimations: { | 6934 noAnimations: { |
| 9990 type: Boolean, | 6935 type: Boolean, |
| 9991 value: false | 6936 value: false |
| 9992 }, | 6937 }, |
| 9993 | 6938 |
| 9994 /** | |
| 9995 * By default, the dropdown will constrain scrolling on the page | |
| 9996 * to itself when opened. | |
| 9997 * Set to true in order to prevent scroll from being constrained | |
| 9998 * to the dropdown when it opens. | |
| 9999 */ | |
| 10000 allowOutsideScroll: { | 6939 allowOutsideScroll: { |
| 10001 type: Boolean, | 6940 type: Boolean, |
| 10002 value: false | 6941 value: false |
| 10003 }, | 6942 }, |
| 10004 | 6943 |
| 10005 /** | |
| 10006 * Callback for scroll events. | |
| 10007 * @type {Function} | |
| 10008 * @private | |
| 10009 */ | |
| 10010 _boundOnCaptureScroll: { | 6944 _boundOnCaptureScroll: { |
| 10011 type: Function, | 6945 type: Function, |
| 10012 value: function() { | 6946 value: function() { |
| 10013 return this._onCaptureScroll.bind(this); | 6947 return this._onCaptureScroll.bind(this); |
| 10014 } | 6948 } |
| 10015 } | 6949 } |
| 10016 }, | 6950 }, |
| 10017 | 6951 |
| 10018 listeners: { | 6952 listeners: { |
| 10019 'neon-animation-finish': '_onNeonAnimationFinish' | 6953 'neon-animation-finish': '_onNeonAnimationFinish' |
| 10020 }, | 6954 }, |
| 10021 | 6955 |
| 10022 observers: [ | 6956 observers: [ |
| 10023 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign
, verticalOffset, horizontalOffset)' | 6957 '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign
, verticalOffset, horizontalOffset)' |
| 10024 ], | 6958 ], |
| 10025 | 6959 |
| 10026 /** | |
| 10027 * The element that is contained by the dropdown, if any. | |
| 10028 */ | |
| 10029 get containedElement() { | 6960 get containedElement() { |
| 10030 return Polymer.dom(this.$.content).getDistributedNodes()[0]; | 6961 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 10031 }, | 6962 }, |
| 10032 | 6963 |
| 10033 /** | |
| 10034 * The element that should be focused when the dropdown opens. | |
| 10035 * @deprecated | |
| 10036 */ | |
| 10037 get _focusTarget() { | 6964 get _focusTarget() { |
| 10038 return this.focusTarget || this.containedElement; | 6965 return this.focusTarget || this.containedElement; |
| 10039 }, | 6966 }, |
| 10040 | 6967 |
| 10041 ready: function() { | 6968 ready: function() { |
| 10042 // Memoized scrolling position, used to block scrolling outside. | |
| 10043 this._scrollTop = 0; | 6969 this._scrollTop = 0; |
| 10044 this._scrollLeft = 0; | 6970 this._scrollLeft = 0; |
| 10045 // Used to perform a non-blocking refit on scroll. | |
| 10046 this._refitOnScrollRAF = null; | 6971 this._refitOnScrollRAF = null; |
| 10047 }, | 6972 }, |
| 10048 | 6973 |
| 10049 detached: function() { | 6974 detached: function() { |
| 10050 this.cancelAnimation(); | 6975 this.cancelAnimation(); |
| 10051 Polymer.IronDropdownScrollManager.removeScrollLock(this); | 6976 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
| 10052 }, | 6977 }, |
| 10053 | 6978 |
| 10054 /** | |
| 10055 * Called when the value of `opened` changes. | |
| 10056 * Overridden from `IronOverlayBehavior` | |
| 10057 */ | |
| 10058 _openedChanged: function() { | 6979 _openedChanged: function() { |
| 10059 if (this.opened && this.disabled) { | 6980 if (this.opened && this.disabled) { |
| 10060 this.cancel(); | 6981 this.cancel(); |
| 10061 } else { | 6982 } else { |
| 10062 this.cancelAnimation(); | 6983 this.cancelAnimation(); |
| 10063 this.sizingTarget = this.containedElement || this.sizingTarget; | 6984 this.sizingTarget = this.containedElement || this.sizingTarget; |
| 10064 this._updateAnimationConfig(); | 6985 this._updateAnimationConfig(); |
| 10065 this._saveScrollPosition(); | 6986 this._saveScrollPosition(); |
| 10066 if (this.opened) { | 6987 if (this.opened) { |
| 10067 document.addEventListener('scroll', this._boundOnCaptureScroll); | 6988 document.addEventListener('scroll', this._boundOnCaptureScroll); |
| 10068 !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.push
ScrollLock(this); | 6989 !this.allowOutsideScroll && Polymer.IronDropdownScrollManager.push
ScrollLock(this); |
| 10069 } else { | 6990 } else { |
| 10070 document.removeEventListener('scroll', this._boundOnCaptureScroll)
; | 6991 document.removeEventListener('scroll', this._boundOnCaptureScroll)
; |
| 10071 Polymer.IronDropdownScrollManager.removeScrollLock(this); | 6992 Polymer.IronDropdownScrollManager.removeScrollLock(this); |
| 10072 } | 6993 } |
| 10073 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); | 6994 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments
); |
| 10074 } | 6995 } |
| 10075 }, | 6996 }, |
| 10076 | 6997 |
| 10077 /** | |
| 10078 * Overridden from `IronOverlayBehavior`. | |
| 10079 */ | |
| 10080 _renderOpened: function() { | 6998 _renderOpened: function() { |
| 10081 if (!this.noAnimations && this.animationConfig.open) { | 6999 if (!this.noAnimations && this.animationConfig.open) { |
| 10082 this.$.contentWrapper.classList.add('animating'); | 7000 this.$.contentWrapper.classList.add('animating'); |
| 10083 this.playAnimation('open'); | 7001 this.playAnimation('open'); |
| 10084 } else { | 7002 } else { |
| 10085 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; | 7003 Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments)
; |
| 10086 } | 7004 } |
| 10087 }, | 7005 }, |
| 10088 | 7006 |
| 10089 /** | |
| 10090 * Overridden from `IronOverlayBehavior`. | |
| 10091 */ | |
| 10092 _renderClosed: function() { | 7007 _renderClosed: function() { |
| 10093 | 7008 |
| 10094 if (!this.noAnimations && this.animationConfig.close) { | 7009 if (!this.noAnimations && this.animationConfig.close) { |
| 10095 this.$.contentWrapper.classList.add('animating'); | 7010 this.$.contentWrapper.classList.add('animating'); |
| 10096 this.playAnimation('close'); | 7011 this.playAnimation('close'); |
| 10097 } else { | 7012 } else { |
| 10098 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; | 7013 Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments)
; |
| 10099 } | 7014 } |
| 10100 }, | 7015 }, |
| 10101 | 7016 |
| 10102 /** | |
| 10103 * Called when animation finishes on the dropdown (when opening or | |
| 10104 * closing). Responsible for "completing" the process of opening or | |
| 10105 * closing the dropdown by positioning it or setting its display to | |
| 10106 * none. | |
| 10107 */ | |
| 10108 _onNeonAnimationFinish: function() { | 7017 _onNeonAnimationFinish: function() { |
| 10109 this.$.contentWrapper.classList.remove('animating'); | 7018 this.$.contentWrapper.classList.remove('animating'); |
| 10110 if (this.opened) { | 7019 if (this.opened) { |
| 10111 this._finishRenderOpened(); | 7020 this._finishRenderOpened(); |
| 10112 } else { | 7021 } else { |
| 10113 this._finishRenderClosed(); | 7022 this._finishRenderClosed(); |
| 10114 } | 7023 } |
| 10115 }, | 7024 }, |
| 10116 | 7025 |
| 10117 _onCaptureScroll: function() { | 7026 _onCaptureScroll: function() { |
| 10118 if (!this.allowOutsideScroll) { | 7027 if (!this.allowOutsideScroll) { |
| 10119 this._restoreScrollPosition(); | 7028 this._restoreScrollPosition(); |
| 10120 } else { | 7029 } else { |
| 10121 this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnS
crollRAF); | 7030 this._refitOnScrollRAF && window.cancelAnimationFrame(this._refitOnS
crollRAF); |
| 10122 this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bin
d(this)); | 7031 this._refitOnScrollRAF = window.requestAnimationFrame(this.refit.bin
d(this)); |
| 10123 } | 7032 } |
| 10124 }, | 7033 }, |
| 10125 | 7034 |
| 10126 /** | |
| 10127 * Memoizes the scroll position of the outside scrolling element. | |
| 10128 * @private | |
| 10129 */ | |
| 10130 _saveScrollPosition: function() { | 7035 _saveScrollPosition: function() { |
| 10131 if (document.scrollingElement) { | 7036 if (document.scrollingElement) { |
| 10132 this._scrollTop = document.scrollingElement.scrollTop; | 7037 this._scrollTop = document.scrollingElement.scrollTop; |
| 10133 this._scrollLeft = document.scrollingElement.scrollLeft; | 7038 this._scrollLeft = document.scrollingElement.scrollLeft; |
| 10134 } else { | 7039 } else { |
| 10135 // Since we don't know if is the body or html, get max. | |
| 10136 this._scrollTop = Math.max(document.documentElement.scrollTop, docum
ent.body.scrollTop); | 7040 this._scrollTop = Math.max(document.documentElement.scrollTop, docum
ent.body.scrollTop); |
| 10137 this._scrollLeft = Math.max(document.documentElement.scrollLeft, doc
ument.body.scrollLeft); | 7041 this._scrollLeft = Math.max(document.documentElement.scrollLeft, doc
ument.body.scrollLeft); |
| 10138 } | 7042 } |
| 10139 }, | 7043 }, |
| 10140 | 7044 |
| 10141 /** | |
| 10142 * Resets the scroll position of the outside scrolling element. | |
| 10143 * @private | |
| 10144 */ | |
| 10145 _restoreScrollPosition: function() { | 7045 _restoreScrollPosition: function() { |
| 10146 if (document.scrollingElement) { | 7046 if (document.scrollingElement) { |
| 10147 document.scrollingElement.scrollTop = this._scrollTop; | 7047 document.scrollingElement.scrollTop = this._scrollTop; |
| 10148 document.scrollingElement.scrollLeft = this._scrollLeft; | 7048 document.scrollingElement.scrollLeft = this._scrollLeft; |
| 10149 } else { | 7049 } else { |
| 10150 // Since we don't know if is the body or html, set both. | |
| 10151 document.documentElement.scrollTop = this._scrollTop; | 7050 document.documentElement.scrollTop = this._scrollTop; |
| 10152 document.documentElement.scrollLeft = this._scrollLeft; | 7051 document.documentElement.scrollLeft = this._scrollLeft; |
| 10153 document.body.scrollTop = this._scrollTop; | 7052 document.body.scrollTop = this._scrollTop; |
| 10154 document.body.scrollLeft = this._scrollLeft; | 7053 document.body.scrollLeft = this._scrollLeft; |
| 10155 } | 7054 } |
| 10156 }, | 7055 }, |
| 10157 | 7056 |
| 10158 /** | |
| 10159 * Constructs the final animation config from different properties used | |
| 10160 * to configure specific parts of the opening and closing animations. | |
| 10161 */ | |
| 10162 _updateAnimationConfig: function() { | 7057 _updateAnimationConfig: function() { |
| 10163 var animations = (this.openAnimationConfig || []).concat(this.closeAni
mationConfig || []); | 7058 var animations = (this.openAnimationConfig || []).concat(this.closeAni
mationConfig || []); |
| 10164 for (var i = 0; i < animations.length; i++) { | 7059 for (var i = 0; i < animations.length; i++) { |
| 10165 animations[i].node = this.containedElement; | 7060 animations[i].node = this.containedElement; |
| 10166 } | 7061 } |
| 10167 this.animationConfig = { | 7062 this.animationConfig = { |
| 10168 open: this.openAnimationConfig, | 7063 open: this.openAnimationConfig, |
| 10169 close: this.closeAnimationConfig | 7064 close: this.closeAnimationConfig |
| 10170 }; | 7065 }; |
| 10171 }, | 7066 }, |
| 10172 | 7067 |
| 10173 /** | |
| 10174 * Updates the overlay position based on configured horizontal | |
| 10175 * and vertical alignment. | |
| 10176 */ | |
| 10177 _updateOverlayPosition: function() { | 7068 _updateOverlayPosition: function() { |
| 10178 if (this.isAttached) { | 7069 if (this.isAttached) { |
| 10179 // This triggers iron-resize, and iron-overlay-behavior will call re
fit if needed. | |
| 10180 this.notifyResize(); | 7070 this.notifyResize(); |
| 10181 } | 7071 } |
| 10182 }, | 7072 }, |
| 10183 | 7073 |
| 10184 /** | |
| 10185 * Apply focus to focusTarget or containedElement | |
| 10186 */ | |
| 10187 _applyFocus: function () { | 7074 _applyFocus: function () { |
| 10188 var focusTarget = this.focusTarget || this.containedElement; | 7075 var focusTarget = this.focusTarget || this.containedElement; |
| 10189 if (focusTarget && this.opened && !this.noAutoFocus) { | 7076 if (focusTarget && this.opened && !this.noAutoFocus) { |
| 10190 focusTarget.focus(); | 7077 focusTarget.focus(); |
| 10191 } else { | 7078 } else { |
| 10192 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); | 7079 Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments); |
| 10193 } | 7080 } |
| 10194 } | 7081 } |
| 10195 }); | 7082 }); |
| 10196 })(); | 7083 })(); |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 10326 'use strict'; | 7213 'use strict'; |
| 10327 | 7214 |
| 10328 var config = { | 7215 var config = { |
| 10329 ANIMATION_CUBIC_BEZIER: 'cubic-bezier(.3,.95,.5,1)', | 7216 ANIMATION_CUBIC_BEZIER: 'cubic-bezier(.3,.95,.5,1)', |
| 10330 MAX_ANIMATION_TIME_MS: 400 | 7217 MAX_ANIMATION_TIME_MS: 400 |
| 10331 }; | 7218 }; |
| 10332 | 7219 |
| 10333 var PaperMenuButton = Polymer({ | 7220 var PaperMenuButton = Polymer({ |
| 10334 is: 'paper-menu-button', | 7221 is: 'paper-menu-button', |
| 10335 | 7222 |
| 10336 /** | |
| 10337 * Fired when the dropdown opens. | |
| 10338 * | |
| 10339 * @event paper-dropdown-open | |
| 10340 */ | |
| 10341 | 7223 |
| 10342 /** | |
| 10343 * Fired when the dropdown closes. | |
| 10344 * | |
| 10345 * @event paper-dropdown-close | |
| 10346 */ | |
| 10347 | 7224 |
| 10348 behaviors: [ | 7225 behaviors: [ |
| 10349 Polymer.IronA11yKeysBehavior, | 7226 Polymer.IronA11yKeysBehavior, |
| 10350 Polymer.IronControlState | 7227 Polymer.IronControlState |
| 10351 ], | 7228 ], |
| 10352 | 7229 |
| 10353 properties: { | 7230 properties: { |
| 10354 /** | |
| 10355 * True if the content is currently displayed. | |
| 10356 */ | |
| 10357 opened: { | 7231 opened: { |
| 10358 type: Boolean, | 7232 type: Boolean, |
| 10359 value: false, | 7233 value: false, |
| 10360 notify: true, | 7234 notify: true, |
| 10361 observer: '_openedChanged' | 7235 observer: '_openedChanged' |
| 10362 }, | 7236 }, |
| 10363 | 7237 |
| 10364 /** | |
| 10365 * The orientation against which to align the menu dropdown | |
| 10366 * horizontally relative to the dropdown trigger. | |
| 10367 */ | |
| 10368 horizontalAlign: { | 7238 horizontalAlign: { |
| 10369 type: String, | 7239 type: String, |
| 10370 value: 'left', | 7240 value: 'left', |
| 10371 reflectToAttribute: true | 7241 reflectToAttribute: true |
| 10372 }, | 7242 }, |
| 10373 | 7243 |
| 10374 /** | |
| 10375 * The orientation against which to align the menu dropdown | |
| 10376 * vertically relative to the dropdown trigger. | |
| 10377 */ | |
| 10378 verticalAlign: { | 7244 verticalAlign: { |
| 10379 type: String, | 7245 type: String, |
| 10380 value: 'top', | 7246 value: 'top', |
| 10381 reflectToAttribute: true | 7247 reflectToAttribute: true |
| 10382 }, | 7248 }, |
| 10383 | 7249 |
| 10384 /** | |
| 10385 * If true, the `horizontalAlign` and `verticalAlign` properties will | |
| 10386 * be considered preferences instead of strict requirements when | |
| 10387 * positioning the dropdown and may be changed if doing so reduces | |
| 10388 * the area of the dropdown falling outside of `fitInto`. | |
| 10389 */ | |
| 10390 dynamicAlign: { | 7250 dynamicAlign: { |
| 10391 type: Boolean | 7251 type: Boolean |
| 10392 }, | 7252 }, |
| 10393 | 7253 |
| 10394 /** | |
| 10395 * A pixel value that will be added to the position calculated for the | |
| 10396 * given `horizontalAlign`. Use a negative value to offset to the | |
| 10397 * left, or a positive value to offset to the right. | |
| 10398 */ | |
| 10399 horizontalOffset: { | 7254 horizontalOffset: { |
| 10400 type: Number, | 7255 type: Number, |
| 10401 value: 0, | 7256 value: 0, |
| 10402 notify: true | 7257 notify: true |
| 10403 }, | 7258 }, |
| 10404 | 7259 |
| 10405 /** | |
| 10406 * A pixel value that will be added to the position calculated for the | |
| 10407 * given `verticalAlign`. Use a negative value to offset towards the | |
| 10408 * top, or a positive value to offset towards the bottom. | |
| 10409 */ | |
| 10410 verticalOffset: { | 7260 verticalOffset: { |
| 10411 type: Number, | 7261 type: Number, |
| 10412 value: 0, | 7262 value: 0, |
| 10413 notify: true | 7263 notify: true |
| 10414 }, | 7264 }, |
| 10415 | 7265 |
| 10416 /** | |
| 10417 * If true, the dropdown will be positioned so that it doesn't overlap | |
| 10418 * the button. | |
| 10419 */ | |
| 10420 noOverlap: { | 7266 noOverlap: { |
| 10421 type: Boolean | 7267 type: Boolean |
| 10422 }, | 7268 }, |
| 10423 | 7269 |
| 10424 /** | |
| 10425 * Set to true to disable animations when opening and closing the | |
| 10426 * dropdown. | |
| 10427 */ | |
| 10428 noAnimations: { | 7270 noAnimations: { |
| 10429 type: Boolean, | 7271 type: Boolean, |
| 10430 value: false | 7272 value: false |
| 10431 }, | 7273 }, |
| 10432 | 7274 |
| 10433 /** | |
| 10434 * Set to true to disable automatically closing the dropdown after | |
| 10435 * a selection has been made. | |
| 10436 */ | |
| 10437 ignoreSelect: { | 7275 ignoreSelect: { |
| 10438 type: Boolean, | 7276 type: Boolean, |
| 10439 value: false | 7277 value: false |
| 10440 }, | 7278 }, |
| 10441 | 7279 |
| 10442 /** | |
| 10443 * Set to true to enable automatically closing the dropdown after an | |
| 10444 * item has been activated, even if the selection did not change. | |
| 10445 */ | |
| 10446 closeOnActivate: { | 7280 closeOnActivate: { |
| 10447 type: Boolean, | 7281 type: Boolean, |
| 10448 value: false | 7282 value: false |
| 10449 }, | 7283 }, |
| 10450 | 7284 |
| 10451 /** | |
| 10452 * An animation config. If provided, this will be used to animate the | |
| 10453 * opening of the dropdown. | |
| 10454 */ | |
| 10455 openAnimationConfig: { | 7285 openAnimationConfig: { |
| 10456 type: Object, | 7286 type: Object, |
| 10457 value: function() { | 7287 value: function() { |
| 10458 return [{ | 7288 return [{ |
| 10459 name: 'fade-in-animation', | 7289 name: 'fade-in-animation', |
| 10460 timing: { | 7290 timing: { |
| 10461 delay: 100, | 7291 delay: 100, |
| 10462 duration: 200 | 7292 duration: 200 |
| 10463 } | 7293 } |
| 10464 }, { | 7294 }, { |
| 10465 name: 'paper-menu-grow-width-animation', | 7295 name: 'paper-menu-grow-width-animation', |
| 10466 timing: { | 7296 timing: { |
| 10467 delay: 100, | 7297 delay: 100, |
| 10468 duration: 150, | 7298 duration: 150, |
| 10469 easing: config.ANIMATION_CUBIC_BEZIER | 7299 easing: config.ANIMATION_CUBIC_BEZIER |
| 10470 } | 7300 } |
| 10471 }, { | 7301 }, { |
| 10472 name: 'paper-menu-grow-height-animation', | 7302 name: 'paper-menu-grow-height-animation', |
| 10473 timing: { | 7303 timing: { |
| 10474 delay: 100, | 7304 delay: 100, |
| 10475 duration: 275, | 7305 duration: 275, |
| 10476 easing: config.ANIMATION_CUBIC_BEZIER | 7306 easing: config.ANIMATION_CUBIC_BEZIER |
| 10477 } | 7307 } |
| 10478 }]; | 7308 }]; |
| 10479 } | 7309 } |
| 10480 }, | 7310 }, |
| 10481 | 7311 |
| 10482 /** | |
| 10483 * An animation config. If provided, this will be used to animate the | |
| 10484 * closing of the dropdown. | |
| 10485 */ | |
| 10486 closeAnimationConfig: { | 7312 closeAnimationConfig: { |
| 10487 type: Object, | 7313 type: Object, |
| 10488 value: function() { | 7314 value: function() { |
| 10489 return [{ | 7315 return [{ |
| 10490 name: 'fade-out-animation', | 7316 name: 'fade-out-animation', |
| 10491 timing: { | 7317 timing: { |
| 10492 duration: 150 | 7318 duration: 150 |
| 10493 } | 7319 } |
| 10494 }, { | 7320 }, { |
| 10495 name: 'paper-menu-shrink-width-animation', | 7321 name: 'paper-menu-shrink-width-animation', |
| 10496 timing: { | 7322 timing: { |
| 10497 delay: 100, | 7323 delay: 100, |
| 10498 duration: 50, | 7324 duration: 50, |
| 10499 easing: config.ANIMATION_CUBIC_BEZIER | 7325 easing: config.ANIMATION_CUBIC_BEZIER |
| 10500 } | 7326 } |
| 10501 }, { | 7327 }, { |
| 10502 name: 'paper-menu-shrink-height-animation', | 7328 name: 'paper-menu-shrink-height-animation', |
| 10503 timing: { | 7329 timing: { |
| 10504 duration: 200, | 7330 duration: 200, |
| 10505 easing: 'ease-in' | 7331 easing: 'ease-in' |
| 10506 } | 7332 } |
| 10507 }]; | 7333 }]; |
| 10508 } | 7334 } |
| 10509 }, | 7335 }, |
| 10510 | 7336 |
| 10511 /** | |
| 10512 * By default, the dropdown will constrain scrolling on the page | |
| 10513 * to itself when opened. | |
| 10514 * Set to true in order to prevent scroll from being constrained | |
| 10515 * to the dropdown when it opens. | |
| 10516 */ | |
| 10517 allowOutsideScroll: { | 7337 allowOutsideScroll: { |
| 10518 type: Boolean, | 7338 type: Boolean, |
| 10519 value: false | 7339 value: false |
| 10520 }, | 7340 }, |
| 10521 | 7341 |
| 10522 /** | |
| 10523 * Whether focus should be restored to the button when the menu closes
. | |
| 10524 */ | |
| 10525 restoreFocusOnClose: { | 7342 restoreFocusOnClose: { |
| 10526 type: Boolean, | 7343 type: Boolean, |
| 10527 value: true | 7344 value: true |
| 10528 }, | 7345 }, |
| 10529 | 7346 |
| 10530 /** | |
| 10531 * This is the element intended to be bound as the focus target | |
| 10532 * for the `iron-dropdown` contained by `paper-menu-button`. | |
| 10533 */ | |
| 10534 _dropdownContent: { | 7347 _dropdownContent: { |
| 10535 type: Object | 7348 type: Object |
| 10536 } | 7349 } |
| 10537 }, | 7350 }, |
| 10538 | 7351 |
| 10539 hostAttributes: { | 7352 hostAttributes: { |
| 10540 role: 'group', | 7353 role: 'group', |
| 10541 'aria-haspopup': 'true' | 7354 'aria-haspopup': 'true' |
| 10542 }, | 7355 }, |
| 10543 | 7356 |
| 10544 listeners: { | 7357 listeners: { |
| 10545 'iron-activate': '_onIronActivate', | 7358 'iron-activate': '_onIronActivate', |
| 10546 'iron-select': '_onIronSelect' | 7359 'iron-select': '_onIronSelect' |
| 10547 }, | 7360 }, |
| 10548 | 7361 |
| 10549 /** | |
| 10550 * The content element that is contained by the menu button, if any. | |
| 10551 */ | |
| 10552 get contentElement() { | 7362 get contentElement() { |
| 10553 return Polymer.dom(this.$.content).getDistributedNodes()[0]; | 7363 return Polymer.dom(this.$.content).getDistributedNodes()[0]; |
| 10554 }, | 7364 }, |
| 10555 | 7365 |
| 10556 /** | |
| 10557 * Toggles the drowpdown content between opened and closed. | |
| 10558 */ | |
| 10559 toggle: function() { | 7366 toggle: function() { |
| 10560 if (this.opened) { | 7367 if (this.opened) { |
| 10561 this.close(); | 7368 this.close(); |
| 10562 } else { | 7369 } else { |
| 10563 this.open(); | 7370 this.open(); |
| 10564 } | 7371 } |
| 10565 }, | 7372 }, |
| 10566 | 7373 |
| 10567 /** | |
| 10568 * Make the dropdown content appear as an overlay positioned relative | |
| 10569 * to the dropdown trigger. | |
| 10570 */ | |
| 10571 open: function() { | 7374 open: function() { |
| 10572 if (this.disabled) { | 7375 if (this.disabled) { |
| 10573 return; | 7376 return; |
| 10574 } | 7377 } |
| 10575 | 7378 |
| 10576 this.$.dropdown.open(); | 7379 this.$.dropdown.open(); |
| 10577 }, | 7380 }, |
| 10578 | 7381 |
| 10579 /** | |
| 10580 * Hide the dropdown content. | |
| 10581 */ | |
| 10582 close: function() { | 7382 close: function() { |
| 10583 this.$.dropdown.close(); | 7383 this.$.dropdown.close(); |
| 10584 }, | 7384 }, |
| 10585 | 7385 |
| 10586 /** | |
| 10587 * When an `iron-select` event is received, the dropdown should | |
| 10588 * automatically close on the assumption that a value has been chosen. | |
| 10589 * | |
| 10590 * @param {CustomEvent} event A CustomEvent instance with type | |
| 10591 * set to `"iron-select"`. | |
| 10592 */ | |
| 10593 _onIronSelect: function(event) { | 7386 _onIronSelect: function(event) { |
| 10594 if (!this.ignoreSelect) { | 7387 if (!this.ignoreSelect) { |
| 10595 this.close(); | 7388 this.close(); |
| 10596 } | 7389 } |
| 10597 }, | 7390 }, |
| 10598 | 7391 |
| 10599 /** | |
| 10600 * Closes the dropdown when an `iron-activate` event is received if | |
| 10601 * `closeOnActivate` is true. | |
| 10602 * | |
| 10603 * @param {CustomEvent} event A CustomEvent of type 'iron-activate'. | |
| 10604 */ | |
| 10605 _onIronActivate: function(event) { | 7392 _onIronActivate: function(event) { |
| 10606 if (this.closeOnActivate) { | 7393 if (this.closeOnActivate) { |
| 10607 this.close(); | 7394 this.close(); |
| 10608 } | 7395 } |
| 10609 }, | 7396 }, |
| 10610 | 7397 |
| 10611 /** | |
| 10612 * When the dropdown opens, the `paper-menu-button` fires `paper-open`. | |
| 10613 * When the dropdown closes, the `paper-menu-button` fires `paper-close`
. | |
| 10614 * | |
| 10615 * @param {boolean} opened True if the dropdown is opened, otherwise fal
se. | |
| 10616 * @param {boolean} oldOpened The previous value of `opened`. | |
| 10617 */ | |
| 10618 _openedChanged: function(opened, oldOpened) { | 7398 _openedChanged: function(opened, oldOpened) { |
| 10619 if (opened) { | 7399 if (opened) { |
| 10620 // TODO(cdata): Update this when we can measure changes in distribut
ed | |
| 10621 // children in an idiomatic way. | |
| 10622 // We poke this property in case the element has changed. This will | |
| 10623 // cause the focus target for the `iron-dropdown` to be updated as | |
| 10624 // necessary: | |
| 10625 this._dropdownContent = this.contentElement; | 7400 this._dropdownContent = this.contentElement; |
| 10626 this.fire('paper-dropdown-open'); | 7401 this.fire('paper-dropdown-open'); |
| 10627 } else if (oldOpened != null) { | 7402 } else if (oldOpened != null) { |
| 10628 this.fire('paper-dropdown-close'); | 7403 this.fire('paper-dropdown-close'); |
| 10629 } | 7404 } |
| 10630 }, | 7405 }, |
| 10631 | 7406 |
| 10632 /** | |
| 10633 * If the dropdown is open when disabled becomes true, close the | |
| 10634 * dropdown. | |
| 10635 * | |
| 10636 * @param {boolean} disabled True if disabled, otherwise false. | |
| 10637 */ | |
| 10638 _disabledChanged: function(disabled) { | 7407 _disabledChanged: function(disabled) { |
| 10639 Polymer.IronControlState._disabledChanged.apply(this, arguments); | 7408 Polymer.IronControlState._disabledChanged.apply(this, arguments); |
| 10640 if (disabled && this.opened) { | 7409 if (disabled && this.opened) { |
| 10641 this.close(); | 7410 this.close(); |
| 10642 } | 7411 } |
| 10643 }, | 7412 }, |
| 10644 | 7413 |
| 10645 __onIronOverlayCanceled: function(event) { | 7414 __onIronOverlayCanceled: function(event) { |
| 10646 var uiEvent = event.detail; | 7415 var uiEvent = event.detail; |
| 10647 var target = Polymer.dom(uiEvent).rootTarget; | 7416 var target = Polymer.dom(uiEvent).rootTarget; |
| 10648 var trigger = this.$.trigger; | 7417 var trigger = this.$.trigger; |
| 10649 var path = Polymer.dom(uiEvent).path; | 7418 var path = Polymer.dom(uiEvent).path; |
| 10650 | 7419 |
| 10651 if (path.indexOf(trigger) > -1) { | 7420 if (path.indexOf(trigger) > -1) { |
| 10652 event.preventDefault(); | 7421 event.preventDefault(); |
| 10653 } | 7422 } |
| 10654 } | 7423 } |
| 10655 }); | 7424 }); |
| 10656 | 7425 |
| 10657 Object.keys(config).forEach(function (key) { | 7426 Object.keys(config).forEach(function (key) { |
| 10658 PaperMenuButton[key] = config[key]; | 7427 PaperMenuButton[key] = config[key]; |
| 10659 }); | 7428 }); |
| 10660 | 7429 |
| 10661 Polymer.PaperMenuButton = PaperMenuButton; | 7430 Polymer.PaperMenuButton = PaperMenuButton; |
| 10662 })(); | 7431 })(); |
| 10663 /** | |
| 10664 * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has k
eyboard focus. | |
| 10665 * | |
| 10666 * @polymerBehavior Polymer.PaperInkyFocusBehavior | |
| 10667 */ | |
| 10668 Polymer.PaperInkyFocusBehaviorImpl = { | 7432 Polymer.PaperInkyFocusBehaviorImpl = { |
| 10669 observers: [ | 7433 observers: [ |
| 10670 '_focusedChanged(receivedFocusFromKeyboard)' | 7434 '_focusedChanged(receivedFocusFromKeyboard)' |
| 10671 ], | 7435 ], |
| 10672 | 7436 |
| 10673 _focusedChanged: function(receivedFocusFromKeyboard) { | 7437 _focusedChanged: function(receivedFocusFromKeyboard) { |
| 10674 if (receivedFocusFromKeyboard) { | 7438 if (receivedFocusFromKeyboard) { |
| 10675 this.ensureRipple(); | 7439 this.ensureRipple(); |
| 10676 } | 7440 } |
| 10677 if (this.hasRipple()) { | 7441 if (this.hasRipple()) { |
| (...skipping 23 matching lines...) Expand all Loading... |
| 10701 hostAttributes: { | 7465 hostAttributes: { |
| 10702 role: 'button', | 7466 role: 'button', |
| 10703 tabindex: '0' | 7467 tabindex: '0' |
| 10704 }, | 7468 }, |
| 10705 | 7469 |
| 10706 behaviors: [ | 7470 behaviors: [ |
| 10707 Polymer.PaperInkyFocusBehavior | 7471 Polymer.PaperInkyFocusBehavior |
| 10708 ], | 7472 ], |
| 10709 | 7473 |
| 10710 properties: { | 7474 properties: { |
| 10711 /** | |
| 10712 * The URL of an image for the icon. If the src property is specified, | |
| 10713 * the icon property should not be. | |
| 10714 */ | |
| 10715 src: { | 7475 src: { |
| 10716 type: String | 7476 type: String |
| 10717 }, | 7477 }, |
| 10718 | 7478 |
| 10719 /** | |
| 10720 * Specifies the icon name or index in the set of icons available in | |
| 10721 * the icon's icon set. If the icon property is specified, | |
| 10722 * the src property should not be. | |
| 10723 */ | |
| 10724 icon: { | 7479 icon: { |
| 10725 type: String | 7480 type: String |
| 10726 }, | 7481 }, |
| 10727 | 7482 |
| 10728 /** | |
| 10729 * Specifies the alternate text for the button, for accessibility. | |
| 10730 */ | |
| 10731 alt: { | 7483 alt: { |
| 10732 type: String, | 7484 type: String, |
| 10733 observer: "_altChanged" | 7485 observer: "_altChanged" |
| 10734 } | 7486 } |
| 10735 }, | 7487 }, |
| 10736 | 7488 |
| 10737 _altChanged: function(newValue, oldValue) { | 7489 _altChanged: function(newValue, oldValue) { |
| 10738 var label = this.getAttribute('aria-label'); | 7490 var label = this.getAttribute('aria-label'); |
| 10739 | 7491 |
| 10740 // Don't stomp over a user-set aria-label. | |
| 10741 if (!label || oldValue == label) { | 7492 if (!label || oldValue == label) { |
| 10742 this.setAttribute('aria-label', newValue); | 7493 this.setAttribute('aria-label', newValue); |
| 10743 } | 7494 } |
| 10744 } | 7495 } |
| 10745 }); | 7496 }); |
| 10746 // Copyright 2016 The Chromium Authors. All rights reserved. | 7497 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 10747 // Use of this source code is governed by a BSD-style license that can be | 7498 // Use of this source code is governed by a BSD-style license that can be |
| 10748 // found in the LICENSE file. | 7499 // found in the LICENSE file. |
| 10749 | 7500 |
| 10750 /** | |
| 10751 * Implements an incremental search field which can be shown and hidden. | |
| 10752 * Canonical implementation is <cr-search-field>. | |
| 10753 * @polymerBehavior | |
| 10754 */ | |
| 10755 var CrSearchFieldBehavior = { | 7501 var CrSearchFieldBehavior = { |
| 10756 properties: { | 7502 properties: { |
| 10757 label: { | 7503 label: { |
| 10758 type: String, | 7504 type: String, |
| 10759 value: '', | 7505 value: '', |
| 10760 }, | 7506 }, |
| 10761 | 7507 |
| 10762 clearLabel: { | 7508 clearLabel: { |
| 10763 type: String, | 7509 type: String, |
| 10764 value: '', | 7510 value: '', |
| 10765 }, | 7511 }, |
| 10766 | 7512 |
| 10767 showingSearch: { | 7513 showingSearch: { |
| 10768 type: Boolean, | 7514 type: Boolean, |
| 10769 value: false, | 7515 value: false, |
| 10770 notify: true, | 7516 notify: true, |
| 10771 observer: 'showingSearchChanged_', | 7517 observer: 'showingSearchChanged_', |
| 10772 reflectToAttribute: true | 7518 reflectToAttribute: true |
| 10773 }, | 7519 }, |
| 10774 | 7520 |
| 10775 /** @private */ | 7521 /** @private */ |
| 10776 lastValue_: { | 7522 lastValue_: { |
| 10777 type: String, | 7523 type: String, |
| 10778 value: '', | 7524 value: '', |
| 10779 }, | 7525 }, |
| 10780 }, | 7526 }, |
| 10781 | 7527 |
| 10782 /** | |
| 10783 * @abstract | |
| 10784 * @return {!HTMLInputElement} The input field element the behavior should | |
| 10785 * use. | |
| 10786 */ | |
| 10787 getSearchInput: function() {}, | 7528 getSearchInput: function() {}, |
| 10788 | 7529 |
| 10789 /** | |
| 10790 * @return {string} The value of the search field. | |
| 10791 */ | |
| 10792 getValue: function() { | 7530 getValue: function() { |
| 10793 return this.getSearchInput().value; | 7531 return this.getSearchInput().value; |
| 10794 }, | 7532 }, |
| 10795 | 7533 |
| 10796 /** | |
| 10797 * Sets the value of the search field. | |
| 10798 * @param {string} value | |
| 10799 */ | |
| 10800 setValue: function(value) { | 7534 setValue: function(value) { |
| 10801 // Use bindValue when setting the input value so that changes propagate | |
| 10802 // correctly. | |
| 10803 this.getSearchInput().bindValue = value; | 7535 this.getSearchInput().bindValue = value; |
| 10804 this.onValueChanged_(value); | 7536 this.onValueChanged_(value); |
| 10805 }, | 7537 }, |
| 10806 | 7538 |
| 10807 showAndFocus: function() { | 7539 showAndFocus: function() { |
| 10808 this.showingSearch = true; | 7540 this.showingSearch = true; |
| 10809 this.focus_(); | 7541 this.focus_(); |
| 10810 }, | 7542 }, |
| 10811 | 7543 |
| 10812 /** @private */ | 7544 /** @private */ |
| 10813 focus_: function() { | 7545 focus_: function() { |
| 10814 this.getSearchInput().focus(); | 7546 this.getSearchInput().focus(); |
| 10815 }, | 7547 }, |
| 10816 | 7548 |
| 10817 onSearchTermSearch: function() { | 7549 onSearchTermSearch: function() { |
| 10818 this.onValueChanged_(this.getValue()); | 7550 this.onValueChanged_(this.getValue()); |
| 10819 }, | 7551 }, |
| 10820 | 7552 |
| 10821 /** | |
| 10822 * Updates the internal state of the search field based on a change that has | |
| 10823 * already happened. | |
| 10824 * @param {string} newValue | |
| 10825 * @private | |
| 10826 */ | |
| 10827 onValueChanged_: function(newValue) { | 7553 onValueChanged_: function(newValue) { |
| 10828 if (newValue == this.lastValue_) | 7554 if (newValue == this.lastValue_) |
| 10829 return; | 7555 return; |
| 10830 | 7556 |
| 10831 this.fire('search-changed', newValue); | 7557 this.fire('search-changed', newValue); |
| 10832 this.lastValue_ = newValue; | 7558 this.lastValue_ = newValue; |
| 10833 }, | 7559 }, |
| 10834 | 7560 |
| 10835 onSearchTermKeydown: function(e) { | 7561 onSearchTermKeydown: function(e) { |
| 10836 if (e.key == 'Escape') | 7562 if (e.key == 'Escape') |
| (...skipping 12 matching lines...) Expand all Loading... |
| 10849 } | 7575 } |
| 10850 }; | 7576 }; |
| 10851 (function() { | 7577 (function() { |
| 10852 'use strict'; | 7578 'use strict'; |
| 10853 | 7579 |
| 10854 Polymer.IronA11yAnnouncer = Polymer({ | 7580 Polymer.IronA11yAnnouncer = Polymer({ |
| 10855 is: 'iron-a11y-announcer', | 7581 is: 'iron-a11y-announcer', |
| 10856 | 7582 |
| 10857 properties: { | 7583 properties: { |
| 10858 | 7584 |
| 10859 /** | |
| 10860 * The value of mode is used to set the `aria-live` attribute | |
| 10861 * for the element that will be announced. Valid values are: `off`, | |
| 10862 * `polite` and `assertive`. | |
| 10863 */ | |
| 10864 mode: { | 7585 mode: { |
| 10865 type: String, | 7586 type: String, |
| 10866 value: 'polite' | 7587 value: 'polite' |
| 10867 }, | 7588 }, |
| 10868 | 7589 |
| 10869 _text: { | 7590 _text: { |
| 10870 type: String, | 7591 type: String, |
| 10871 value: '' | 7592 value: '' |
| 10872 } | 7593 } |
| 10873 }, | 7594 }, |
| 10874 | 7595 |
| 10875 created: function() { | 7596 created: function() { |
| 10876 if (!Polymer.IronA11yAnnouncer.instance) { | 7597 if (!Polymer.IronA11yAnnouncer.instance) { |
| 10877 Polymer.IronA11yAnnouncer.instance = this; | 7598 Polymer.IronA11yAnnouncer.instance = this; |
| 10878 } | 7599 } |
| 10879 | 7600 |
| 10880 document.body.addEventListener('iron-announce', this._onIronAnnounce.b
ind(this)); | 7601 document.body.addEventListener('iron-announce', this._onIronAnnounce.b
ind(this)); |
| 10881 }, | 7602 }, |
| 10882 | 7603 |
| 10883 /** | |
| 10884 * Cause a text string to be announced by screen readers. | |
| 10885 * | |
| 10886 * @param {string} text The text that should be announced. | |
| 10887 */ | |
| 10888 announce: function(text) { | 7604 announce: function(text) { |
| 10889 this._text = ''; | 7605 this._text = ''; |
| 10890 this.async(function() { | 7606 this.async(function() { |
| 10891 this._text = text; | 7607 this._text = text; |
| 10892 }, 100); | 7608 }, 100); |
| 10893 }, | 7609 }, |
| 10894 | 7610 |
| 10895 _onIronAnnounce: function(event) { | 7611 _onIronAnnounce: function(event) { |
| 10896 if (event.detail && event.detail.text) { | 7612 if (event.detail && event.detail.text) { |
| 10897 this.announce(event.detail.text); | 7613 this.announce(event.detail.text); |
| 10898 } | 7614 } |
| 10899 } | 7615 } |
| 10900 }); | 7616 }); |
| 10901 | 7617 |
| 10902 Polymer.IronA11yAnnouncer.instance = null; | 7618 Polymer.IronA11yAnnouncer.instance = null; |
| 10903 | 7619 |
| 10904 Polymer.IronA11yAnnouncer.requestAvailability = function() { | 7620 Polymer.IronA11yAnnouncer.requestAvailability = function() { |
| 10905 if (!Polymer.IronA11yAnnouncer.instance) { | 7621 if (!Polymer.IronA11yAnnouncer.instance) { |
| 10906 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y
-announcer'); | 7622 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y
-announcer'); |
| 10907 } | 7623 } |
| 10908 | 7624 |
| 10909 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); | 7625 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); |
| 10910 }; | 7626 }; |
| 10911 })(); | 7627 })(); |
| 10912 /** | |
| 10913 * Singleton IronMeta instance. | |
| 10914 */ | |
| 10915 Polymer.IronValidatableBehaviorMeta = null; | 7628 Polymer.IronValidatableBehaviorMeta = null; |
| 10916 | 7629 |
| 10917 /** | |
| 10918 * `Use Polymer.IronValidatableBehavior` to implement an element that validate
s user input. | |
| 10919 * Use the related `Polymer.IronValidatorBehavior` to add custom validation lo
gic to an iron-input. | |
| 10920 * | |
| 10921 * By default, an `<iron-form>` element validates its fields when the user pre
sses the submit button. | |
| 10922 * To validate a form imperatively, call the form's `validate()` method, which
in turn will | |
| 10923 * call `validate()` on all its children. By using `Polymer.IronValidatableBeh
avior`, your | |
| 10924 * custom element will get a public `validate()`, which | |
| 10925 * will return the validity of the element, and a corresponding `invalid` attr
ibute, | |
| 10926 * which can be used for styling. | |
| 10927 * | |
| 10928 * To implement the custom validation logic of your element, you must override | |
| 10929 * the protected `_getValidity()` method of this behaviour, rather than `valid
ate()`. | |
| 10930 * See [this](https://github.com/PolymerElements/iron-form/blob/master/demo/si
mple-element.html) | |
| 10931 * for an example. | |
| 10932 * | |
| 10933 * ### Accessibility | |
| 10934 * | |
| 10935 * Changing the `invalid` property, either manually or by calling `validate()`
will update the | |
| 10936 * `aria-invalid` attribute. | |
| 10937 * | |
| 10938 * @demo demo/index.html | |
| 10939 * @polymerBehavior | |
| 10940 */ | |
| 10941 Polymer.IronValidatableBehavior = { | 7630 Polymer.IronValidatableBehavior = { |
| 10942 | 7631 |
| 10943 properties: { | 7632 properties: { |
| 10944 | 7633 |
| 10945 /** | |
| 10946 * Name of the validator to use. | |
| 10947 */ | |
| 10948 validator: { | 7634 validator: { |
| 10949 type: String | 7635 type: String |
| 10950 }, | 7636 }, |
| 10951 | 7637 |
| 10952 /** | |
| 10953 * True if the last call to `validate` is invalid. | |
| 10954 */ | |
| 10955 invalid: { | 7638 invalid: { |
| 10956 notify: true, | 7639 notify: true, |
| 10957 reflectToAttribute: true, | 7640 reflectToAttribute: true, |
| 10958 type: Boolean, | 7641 type: Boolean, |
| 10959 value: false | 7642 value: false |
| 10960 }, | 7643 }, |
| 10961 | 7644 |
| 10962 /** | |
| 10963 * This property is deprecated and should not be used. Use the global | |
| 10964 * validator meta singleton, `Polymer.IronValidatableBehaviorMeta` instead
. | |
| 10965 */ | |
| 10966 _validatorMeta: { | 7645 _validatorMeta: { |
| 10967 type: Object | 7646 type: Object |
| 10968 }, | 7647 }, |
| 10969 | 7648 |
| 10970 /** | |
| 10971 * Namespace for this validator. This property is deprecated and should | |
| 10972 * not be used. For all intents and purposes, please consider it a | |
| 10973 * read-only, config-time property. | |
| 10974 */ | |
| 10975 validatorType: { | 7649 validatorType: { |
| 10976 type: String, | 7650 type: String, |
| 10977 value: 'validator' | 7651 value: 'validator' |
| 10978 }, | 7652 }, |
| 10979 | 7653 |
| 10980 _validator: { | 7654 _validator: { |
| 10981 type: Object, | 7655 type: Object, |
| 10982 computed: '__computeValidator(validator)' | 7656 computed: '__computeValidator(validator)' |
| 10983 } | 7657 } |
| 10984 }, | 7658 }, |
| 10985 | 7659 |
| 10986 observers: [ | 7660 observers: [ |
| 10987 '_invalidChanged(invalid)' | 7661 '_invalidChanged(invalid)' |
| 10988 ], | 7662 ], |
| 10989 | 7663 |
| 10990 registered: function() { | 7664 registered: function() { |
| 10991 Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validat
or'}); | 7665 Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validat
or'}); |
| 10992 }, | 7666 }, |
| 10993 | 7667 |
| 10994 _invalidChanged: function() { | 7668 _invalidChanged: function() { |
| 10995 if (this.invalid) { | 7669 if (this.invalid) { |
| 10996 this.setAttribute('aria-invalid', 'true'); | 7670 this.setAttribute('aria-invalid', 'true'); |
| 10997 } else { | 7671 } else { |
| 10998 this.removeAttribute('aria-invalid'); | 7672 this.removeAttribute('aria-invalid'); |
| 10999 } | 7673 } |
| 11000 }, | 7674 }, |
| 11001 | 7675 |
| 11002 /** | |
| 11003 * @return {boolean} True if the validator `validator` exists. | |
| 11004 */ | |
| 11005 hasValidator: function() { | 7676 hasValidator: function() { |
| 11006 return this._validator != null; | 7677 return this._validator != null; |
| 11007 }, | 7678 }, |
| 11008 | 7679 |
| 11009 /** | |
| 11010 * Returns true if the `value` is valid, and updates `invalid`. If you want | |
| 11011 * your element to have custom validation logic, do not override this method
; | |
| 11012 * override `_getValidity(value)` instead. | |
| 11013 | |
| 11014 * @param {Object} value The value to be validated. By default, it is passed | |
| 11015 * to the validator's `validate()` function, if a validator is set. | |
| 11016 * @return {boolean} True if `value` is valid. | |
| 11017 */ | |
| 11018 validate: function(value) { | 7680 validate: function(value) { |
| 11019 this.invalid = !this._getValidity(value); | 7681 this.invalid = !this._getValidity(value); |
| 11020 return !this.invalid; | 7682 return !this.invalid; |
| 11021 }, | 7683 }, |
| 11022 | 7684 |
| 11023 /** | |
| 11024 * Returns true if `value` is valid. By default, it is passed | |
| 11025 * to the validator's `validate()` function, if a validator is set. You | |
| 11026 * should override this method if you want to implement custom validity | |
| 11027 * logic for your element. | |
| 11028 * | |
| 11029 * @param {Object} value The value to be validated. | |
| 11030 * @return {boolean} True if `value` is valid. | |
| 11031 */ | |
| 11032 | 7685 |
| 11033 _getValidity: function(value) { | 7686 _getValidity: function(value) { |
| 11034 if (this.hasValidator()) { | 7687 if (this.hasValidator()) { |
| 11035 return this._validator.validate(value); | 7688 return this._validator.validate(value); |
| 11036 } | 7689 } |
| 11037 return true; | 7690 return true; |
| 11038 }, | 7691 }, |
| 11039 | 7692 |
| 11040 __computeValidator: function() { | 7693 __computeValidator: function() { |
| 11041 return Polymer.IronValidatableBehaviorMeta && | 7694 return Polymer.IronValidatableBehaviorMeta && |
| 11042 Polymer.IronValidatableBehaviorMeta.byKey(this.validator); | 7695 Polymer.IronValidatableBehaviorMeta.byKey(this.validator); |
| 11043 } | 7696 } |
| 11044 }; | 7697 }; |
| 11045 /* | |
| 11046 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal
idatorBehavior` | |
| 11047 to `<input>`. | |
| 11048 | |
| 11049 ### Two-way binding | |
| 11050 | |
| 11051 By default you can only get notified of changes to an `input`'s `value` due to u
ser input: | |
| 11052 | |
| 11053 <input value="{{myValue::input}}"> | |
| 11054 | |
| 11055 `iron-input` adds the `bind-value` property that mirrors the `value` property, a
nd can be used | |
| 11056 for two-way data binding. `bind-value` will notify if it is changed either by us
er input or by script. | |
| 11057 | |
| 11058 <input is="iron-input" bind-value="{{myValue}}"> | |
| 11059 | |
| 11060 ### Custom validators | |
| 11061 | |
| 11062 You can use custom validators that implement `Polymer.IronValidatorBehavior` wit
h `<iron-input>`. | |
| 11063 | |
| 11064 <input is="iron-input" validator="my-custom-validator"> | |
| 11065 | |
| 11066 ### Stopping invalid input | |
| 11067 | |
| 11068 It may be desirable to only allow users to enter certain characters. You can use
the | |
| 11069 `prevent-invalid-input` and `allowed-pattern` attributes together to accomplish
this. This feature | |
| 11070 is separate from validation, and `allowed-pattern` does not affect how the input
is validated. | |
| 11071 | |
| 11072 \x3c!-- only allow characters that match [0-9] --\x3e | |
| 11073 <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> | |
| 11074 | |
| 11075 @hero hero.svg | |
| 11076 @demo demo/index.html | |
| 11077 */ | |
| 11078 | 7698 |
| 11079 Polymer({ | 7699 Polymer({ |
| 11080 | 7700 |
| 11081 is: 'iron-input', | 7701 is: 'iron-input', |
| 11082 | 7702 |
| 11083 extends: 'input', | 7703 extends: 'input', |
| 11084 | 7704 |
| 11085 behaviors: [ | 7705 behaviors: [ |
| 11086 Polymer.IronValidatableBehavior | 7706 Polymer.IronValidatableBehavior |
| 11087 ], | 7707 ], |
| 11088 | 7708 |
| 11089 properties: { | 7709 properties: { |
| 11090 | 7710 |
| 11091 /** | |
| 11092 * Use this property instead of `value` for two-way data binding. | |
| 11093 */ | |
| 11094 bindValue: { | 7711 bindValue: { |
| 11095 observer: '_bindValueChanged', | 7712 observer: '_bindValueChanged', |
| 11096 type: String | 7713 type: String |
| 11097 }, | 7714 }, |
| 11098 | 7715 |
| 11099 /** | |
| 11100 * Set to true to prevent the user from entering invalid input. If `allowe
dPattern` is set, | |
| 11101 * any character typed by the user will be matched against that pattern, a
nd rejected if it's not a match. | |
| 11102 * Pasted input will have each character checked individually; if any char
acter | |
| 11103 * doesn't match `allowedPattern`, the entire pasted string will be reject
ed. | |
| 11104 * If `allowedPattern` is not set, it will use the `type` attribute (only
supported for `type=number`). | |
| 11105 */ | |
| 11106 preventInvalidInput: { | 7716 preventInvalidInput: { |
| 11107 type: Boolean | 7717 type: Boolean |
| 11108 }, | 7718 }, |
| 11109 | 7719 |
| 11110 /** | |
| 11111 * Regular expression that list the characters allowed as input. | |
| 11112 * This pattern represents the allowed characters for the field; as the us
er inputs text, | |
| 11113 * each individual character will be checked against the pattern (rather t
han checking | |
| 11114 * the entire value as a whole). The recommended format should be a list o
f allowed characters; | |
| 11115 * for example, `[a-zA-Z0-9.+-!;:]` | |
| 11116 */ | |
| 11117 allowedPattern: { | 7720 allowedPattern: { |
| 11118 type: String, | 7721 type: String, |
| 11119 observer: "_allowedPatternChanged" | 7722 observer: "_allowedPatternChanged" |
| 11120 }, | 7723 }, |
| 11121 | 7724 |
| 11122 _previousValidInput: { | 7725 _previousValidInput: { |
| 11123 type: String, | 7726 type: String, |
| 11124 value: '' | 7727 value: '' |
| 11125 }, | 7728 }, |
| 11126 | 7729 |
| 11127 _patternAlreadyChecked: { | 7730 _patternAlreadyChecked: { |
| 11128 type: Boolean, | 7731 type: Boolean, |
| 11129 value: false | 7732 value: false |
| 11130 } | 7733 } |
| 11131 | 7734 |
| 11132 }, | 7735 }, |
| 11133 | 7736 |
| 11134 listeners: { | 7737 listeners: { |
| 11135 'input': '_onInput', | 7738 'input': '_onInput', |
| 11136 'keypress': '_onKeypress' | 7739 'keypress': '_onKeypress' |
| 11137 }, | 7740 }, |
| 11138 | 7741 |
| 11139 /** @suppress {checkTypes} */ | 7742 /** @suppress {checkTypes} */ |
| 11140 registered: function() { | 7743 registered: function() { |
| 11141 // Feature detect whether we need to patch dispatchEvent (i.e. on FF and I
E). | |
| 11142 if (!this._canDispatchEventOnDisabled()) { | 7744 if (!this._canDispatchEventOnDisabled()) { |
| 11143 this._origDispatchEvent = this.dispatchEvent; | 7745 this._origDispatchEvent = this.dispatchEvent; |
| 11144 this.dispatchEvent = this._dispatchEventFirefoxIE; | 7746 this.dispatchEvent = this._dispatchEventFirefoxIE; |
| 11145 } | 7747 } |
| 11146 }, | 7748 }, |
| 11147 | 7749 |
| 11148 created: function() { | 7750 created: function() { |
| 11149 Polymer.IronA11yAnnouncer.requestAvailability(); | 7751 Polymer.IronA11yAnnouncer.requestAvailability(); |
| 11150 }, | 7752 }, |
| 11151 | 7753 |
| 11152 _canDispatchEventOnDisabled: function() { | 7754 _canDispatchEventOnDisabled: function() { |
| 11153 var input = document.createElement('input'); | 7755 var input = document.createElement('input'); |
| 11154 var canDispatch = false; | 7756 var canDispatch = false; |
| 11155 input.disabled = true; | 7757 input.disabled = true; |
| 11156 | 7758 |
| 11157 input.addEventListener('feature-check-dispatch-event', function() { | 7759 input.addEventListener('feature-check-dispatch-event', function() { |
| 11158 canDispatch = true; | 7760 canDispatch = true; |
| 11159 }); | 7761 }); |
| 11160 | 7762 |
| 11161 try { | 7763 try { |
| 11162 input.dispatchEvent(new Event('feature-check-dispatch-event')); | 7764 input.dispatchEvent(new Event('feature-check-dispatch-event')); |
| 11163 } catch(e) {} | 7765 } catch(e) {} |
| 11164 | 7766 |
| 11165 return canDispatch; | 7767 return canDispatch; |
| 11166 }, | 7768 }, |
| 11167 | 7769 |
| 11168 _dispatchEventFirefoxIE: function() { | 7770 _dispatchEventFirefoxIE: function() { |
| 11169 // Due to Firefox bug, events fired on disabled form controls can throw | |
| 11170 // errors; furthermore, neither IE nor Firefox will actually dispatch | |
| 11171 // events from disabled form controls; as such, we toggle disable around | |
| 11172 // the dispatch to allow notifying properties to notify | |
| 11173 // See issue #47 for details | |
| 11174 var disabled = this.disabled; | 7771 var disabled = this.disabled; |
| 11175 this.disabled = false; | 7772 this.disabled = false; |
| 11176 this._origDispatchEvent.apply(this, arguments); | 7773 this._origDispatchEvent.apply(this, arguments); |
| 11177 this.disabled = disabled; | 7774 this.disabled = disabled; |
| 11178 }, | 7775 }, |
| 11179 | 7776 |
| 11180 get _patternRegExp() { | 7777 get _patternRegExp() { |
| 11181 var pattern; | 7778 var pattern; |
| 11182 if (this.allowedPattern) { | 7779 if (this.allowedPattern) { |
| 11183 pattern = new RegExp(this.allowedPattern); | 7780 pattern = new RegExp(this.allowedPattern); |
| 11184 } else { | 7781 } else { |
| 11185 switch (this.type) { | 7782 switch (this.type) { |
| 11186 case 'number': | 7783 case 'number': |
| 11187 pattern = /[0-9.,e-]/; | 7784 pattern = /[0-9.,e-]/; |
| 11188 break; | 7785 break; |
| 11189 } | 7786 } |
| 11190 } | 7787 } |
| 11191 return pattern; | 7788 return pattern; |
| 11192 }, | 7789 }, |
| 11193 | 7790 |
| 11194 ready: function() { | 7791 ready: function() { |
| 11195 this.bindValue = this.value; | 7792 this.bindValue = this.value; |
| 11196 }, | 7793 }, |
| 11197 | 7794 |
| 11198 /** | |
| 11199 * @suppress {checkTypes} | |
| 11200 */ | |
| 11201 _bindValueChanged: function() { | 7795 _bindValueChanged: function() { |
| 11202 if (this.value !== this.bindValue) { | 7796 if (this.value !== this.bindValue) { |
| 11203 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue
=== false) ? '' : this.bindValue; | 7797 this.value = !(this.bindValue || this.bindValue === 0 || this.bindValue
=== false) ? '' : this.bindValue; |
| 11204 } | 7798 } |
| 11205 // manually notify because we don't want to notify until after setting val
ue | |
| 11206 this.fire('bind-value-changed', {value: this.bindValue}); | 7799 this.fire('bind-value-changed', {value: this.bindValue}); |
| 11207 }, | 7800 }, |
| 11208 | 7801 |
| 11209 _allowedPatternChanged: function() { | 7802 _allowedPatternChanged: function() { |
| 11210 // Force to prevent invalid input when an `allowed-pattern` is set | |
| 11211 this.preventInvalidInput = this.allowedPattern ? true : false; | 7803 this.preventInvalidInput = this.allowedPattern ? true : false; |
| 11212 }, | 7804 }, |
| 11213 | 7805 |
| 11214 _onInput: function() { | 7806 _onInput: function() { |
| 11215 // Need to validate each of the characters pasted if they haven't | |
| 11216 // been validated inside `_onKeypress` already. | |
| 11217 if (this.preventInvalidInput && !this._patternAlreadyChecked) { | 7807 if (this.preventInvalidInput && !this._patternAlreadyChecked) { |
| 11218 var valid = this._checkPatternValidity(); | 7808 var valid = this._checkPatternValidity(); |
| 11219 if (!valid) { | 7809 if (!valid) { |
| 11220 this._announceInvalidCharacter('Invalid string of characters not enter
ed.'); | 7810 this._announceInvalidCharacter('Invalid string of characters not enter
ed.'); |
| 11221 this.value = this._previousValidInput; | 7811 this.value = this._previousValidInput; |
| 11222 } | 7812 } |
| 11223 } | 7813 } |
| 11224 | 7814 |
| 11225 this.bindValue = this.value; | 7815 this.bindValue = this.value; |
| 11226 this._previousValidInput = this.value; | 7816 this._previousValidInput = this.value; |
| 11227 this._patternAlreadyChecked = false; | 7817 this._patternAlreadyChecked = false; |
| 11228 }, | 7818 }, |
| 11229 | 7819 |
| 11230 _isPrintable: function(event) { | 7820 _isPrintable: function(event) { |
| 11231 // What a control/printable character is varies wildly based on the browse
r. | |
| 11232 // - most control characters (arrows, backspace) do not send a `keypress`
event | |
| 11233 // in Chrome, but the *do* on Firefox | |
| 11234 // - in Firefox, when they do send a `keypress` event, control chars have | |
| 11235 // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) | |
| 11236 // - printable characters always send a keypress event. | |
| 11237 // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the
keyCode | |
| 11238 // always matches the charCode. | |
| 11239 // None of this makes any sense. | |
| 11240 | 7821 |
| 11241 // For these keys, ASCII code == browser keycode. | |
| 11242 var anyNonPrintable = | 7822 var anyNonPrintable = |
| 11243 (event.keyCode == 8) || // backspace | 7823 (event.keyCode == 8) || // backspace |
| 11244 (event.keyCode == 9) || // tab | 7824 (event.keyCode == 9) || // tab |
| 11245 (event.keyCode == 13) || // enter | 7825 (event.keyCode == 13) || // enter |
| 11246 (event.keyCode == 27); // escape | 7826 (event.keyCode == 27); // escape |
| 11247 | 7827 |
| 11248 // For these keys, make sure it's a browser keycode and not an ASCII code. | |
| 11249 var mozNonPrintable = | 7828 var mozNonPrintable = |
| 11250 (event.keyCode == 19) || // pause | 7829 (event.keyCode == 19) || // pause |
| 11251 (event.keyCode == 20) || // caps lock | 7830 (event.keyCode == 20) || // caps lock |
| 11252 (event.keyCode == 45) || // insert | 7831 (event.keyCode == 45) || // insert |
| 11253 (event.keyCode == 46) || // delete | 7832 (event.keyCode == 46) || // delete |
| 11254 (event.keyCode == 144) || // num lock | 7833 (event.keyCode == 144) || // num lock |
| 11255 (event.keyCode == 145) || // scroll lock | 7834 (event.keyCode == 145) || // scroll lock |
| 11256 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho
me, arrows | 7835 (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, ho
me, arrows |
| 11257 (event.keyCode > 111 && event.keyCode < 124); // fn keys | 7836 (event.keyCode > 111 && event.keyCode < 124); // fn keys |
| 11258 | 7837 |
| 11259 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); | 7838 return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); |
| 11260 }, | 7839 }, |
| 11261 | 7840 |
| 11262 _onKeypress: function(event) { | 7841 _onKeypress: function(event) { |
| 11263 if (!this.preventInvalidInput && this.type !== 'number') { | 7842 if (!this.preventInvalidInput && this.type !== 'number') { |
| 11264 return; | 7843 return; |
| 11265 } | 7844 } |
| 11266 var regexp = this._patternRegExp; | 7845 var regexp = this._patternRegExp; |
| 11267 if (!regexp) { | 7846 if (!regexp) { |
| 11268 return; | 7847 return; |
| 11269 } | 7848 } |
| 11270 | 7849 |
| 11271 // Handle special keys and backspace | |
| 11272 if (event.metaKey || event.ctrlKey || event.altKey) | 7850 if (event.metaKey || event.ctrlKey || event.altKey) |
| 11273 return; | 7851 return; |
| 11274 | 7852 |
| 11275 // Check the pattern either here or in `_onInput`, but not in both. | |
| 11276 this._patternAlreadyChecked = true; | 7853 this._patternAlreadyChecked = true; |
| 11277 | 7854 |
| 11278 var thisChar = String.fromCharCode(event.charCode); | 7855 var thisChar = String.fromCharCode(event.charCode); |
| 11279 if (this._isPrintable(event) && !regexp.test(thisChar)) { | 7856 if (this._isPrintable(event) && !regexp.test(thisChar)) { |
| 11280 event.preventDefault(); | 7857 event.preventDefault(); |
| 11281 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not e
ntered.'); | 7858 this._announceInvalidCharacter('Invalid character ' + thisChar + ' not e
ntered.'); |
| 11282 } | 7859 } |
| 11283 }, | 7860 }, |
| 11284 | 7861 |
| 11285 _checkPatternValidity: function() { | 7862 _checkPatternValidity: function() { |
| 11286 var regexp = this._patternRegExp; | 7863 var regexp = this._patternRegExp; |
| 11287 if (!regexp) { | 7864 if (!regexp) { |
| 11288 return true; | 7865 return true; |
| 11289 } | 7866 } |
| 11290 for (var i = 0; i < this.value.length; i++) { | 7867 for (var i = 0; i < this.value.length; i++) { |
| 11291 if (!regexp.test(this.value[i])) { | 7868 if (!regexp.test(this.value[i])) { |
| 11292 return false; | 7869 return false; |
| 11293 } | 7870 } |
| 11294 } | 7871 } |
| 11295 return true; | 7872 return true; |
| 11296 }, | 7873 }, |
| 11297 | 7874 |
| 11298 /** | |
| 11299 * Returns true if `value` is valid. The validator provided in `validator` w
ill be used first, | |
| 11300 * then any constraints. | |
| 11301 * @return {boolean} True if the value is valid. | |
| 11302 */ | |
| 11303 validate: function() { | 7875 validate: function() { |
| 11304 // First, check what the browser thinks. Some inputs (like type=number) | |
| 11305 // behave weirdly and will set the value to "" if something invalid is | |
| 11306 // entered, but will set the validity correctly. | |
| 11307 var valid = this.checkValidity(); | 7876 var valid = this.checkValidity(); |
| 11308 | 7877 |
| 11309 // Only do extra checking if the browser thought this was valid. | |
| 11310 if (valid) { | 7878 if (valid) { |
| 11311 // Empty, required input is invalid | |
| 11312 if (this.required && this.value === '') { | 7879 if (this.required && this.value === '') { |
| 11313 valid = false; | 7880 valid = false; |
| 11314 } else if (this.hasValidator()) { | 7881 } else if (this.hasValidator()) { |
| 11315 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value
); | 7882 valid = Polymer.IronValidatableBehavior.validate.call(this, this.value
); |
| 11316 } | 7883 } |
| 11317 } | 7884 } |
| 11318 | 7885 |
| 11319 this.invalid = !valid; | 7886 this.invalid = !valid; |
| 11320 this.fire('iron-input-validate'); | 7887 this.fire('iron-input-validate'); |
| 11321 return valid; | 7888 return valid; |
| 11322 }, | 7889 }, |
| 11323 | 7890 |
| 11324 _announceInvalidCharacter: function(message) { | 7891 _announceInvalidCharacter: function(message) { |
| 11325 this.fire('iron-announce', { text: message }); | 7892 this.fire('iron-announce', { text: message }); |
| 11326 } | 7893 } |
| 11327 }); | 7894 }); |
| 11328 | 7895 |
| 11329 /* | |
| 11330 The `iron-input-validate` event is fired whenever `validate()` is called. | |
| 11331 @event iron-input-validate | |
| 11332 */ | |
| 11333 Polymer({ | 7896 Polymer({ |
| 11334 is: 'paper-input-container', | 7897 is: 'paper-input-container', |
| 11335 | 7898 |
| 11336 properties: { | 7899 properties: { |
| 11337 /** | |
| 11338 * Set to true to disable the floating label. The label disappears when th
e input value is | |
| 11339 * not null. | |
| 11340 */ | |
| 11341 noLabelFloat: { | 7900 noLabelFloat: { |
| 11342 type: Boolean, | 7901 type: Boolean, |
| 11343 value: false | 7902 value: false |
| 11344 }, | 7903 }, |
| 11345 | 7904 |
| 11346 /** | |
| 11347 * Set to true to always float the floating label. | |
| 11348 */ | |
| 11349 alwaysFloatLabel: { | 7905 alwaysFloatLabel: { |
| 11350 type: Boolean, | 7906 type: Boolean, |
| 11351 value: false | 7907 value: false |
| 11352 }, | 7908 }, |
| 11353 | 7909 |
| 11354 /** | |
| 11355 * The attribute to listen for value changes on. | |
| 11356 */ | |
| 11357 attrForValue: { | 7910 attrForValue: { |
| 11358 type: String, | 7911 type: String, |
| 11359 value: 'bind-value' | 7912 value: 'bind-value' |
| 11360 }, | 7913 }, |
| 11361 | 7914 |
| 11362 /** | |
| 11363 * Set to true to auto-validate the input value when it changes. | |
| 11364 */ | |
| 11365 autoValidate: { | 7915 autoValidate: { |
| 11366 type: Boolean, | 7916 type: Boolean, |
| 11367 value: false | 7917 value: false |
| 11368 }, | 7918 }, |
| 11369 | 7919 |
| 11370 /** | |
| 11371 * True if the input is invalid. This property is set automatically when t
he input value | |
| 11372 * changes if auto-validating, or when the `iron-input-validate` event is
heard from a child. | |
| 11373 */ | |
| 11374 invalid: { | 7920 invalid: { |
| 11375 observer: '_invalidChanged', | 7921 observer: '_invalidChanged', |
| 11376 type: Boolean, | 7922 type: Boolean, |
| 11377 value: false | 7923 value: false |
| 11378 }, | 7924 }, |
| 11379 | 7925 |
| 11380 /** | |
| 11381 * True if the input has focus. | |
| 11382 */ | |
| 11383 focused: { | 7926 focused: { |
| 11384 readOnly: true, | 7927 readOnly: true, |
| 11385 type: Boolean, | 7928 type: Boolean, |
| 11386 value: false, | 7929 value: false, |
| 11387 notify: true | 7930 notify: true |
| 11388 }, | 7931 }, |
| 11389 | 7932 |
| 11390 _addons: { | 7933 _addons: { |
| 11391 type: Array | 7934 type: Array |
| 11392 // do not set a default value here intentionally - it will be initialize
d lazily when a | |
| 11393 // distributed child is attached, which may occur before configuration f
or this element | |
| 11394 // in polyfill. | |
| 11395 }, | 7935 }, |
| 11396 | 7936 |
| 11397 _inputHasContent: { | 7937 _inputHasContent: { |
| 11398 type: Boolean, | 7938 type: Boolean, |
| 11399 value: false | 7939 value: false |
| 11400 }, | 7940 }, |
| 11401 | 7941 |
| 11402 _inputSelector: { | 7942 _inputSelector: { |
| 11403 type: String, | 7943 type: String, |
| 11404 value: 'input,textarea,.paper-input-input' | 7944 value: 'input,textarea,.paper-input-input' |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11462 this.addEventListener('blur', this._boundOnBlur, true); | 8002 this.addEventListener('blur', this._boundOnBlur, true); |
| 11463 }, | 8003 }, |
| 11464 | 8004 |
| 11465 attached: function() { | 8005 attached: function() { |
| 11466 if (this.attrForValue) { | 8006 if (this.attrForValue) { |
| 11467 this._inputElement.addEventListener(this._valueChangedEvent, this._bound
ValueChanged); | 8007 this._inputElement.addEventListener(this._valueChangedEvent, this._bound
ValueChanged); |
| 11468 } else { | 8008 } else { |
| 11469 this.addEventListener('input', this._onInput); | 8009 this.addEventListener('input', this._onInput); |
| 11470 } | 8010 } |
| 11471 | 8011 |
| 11472 // Only validate when attached if the input already has a value. | |
| 11473 if (this._inputElementValue != '') { | 8012 if (this._inputElementValue != '') { |
| 11474 this._handleValueAndAutoValidate(this._inputElement); | 8013 this._handleValueAndAutoValidate(this._inputElement); |
| 11475 } else { | 8014 } else { |
| 11476 this._handleValue(this._inputElement); | 8015 this._handleValue(this._inputElement); |
| 11477 } | 8016 } |
| 11478 }, | 8017 }, |
| 11479 | 8018 |
| 11480 _onAddonAttached: function(event) { | 8019 _onAddonAttached: function(event) { |
| 11481 if (!this._addons) { | 8020 if (!this._addons) { |
| 11482 this._addons = []; | 8021 this._addons = []; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 11503 this._handleValueAndAutoValidate(event.target); | 8042 this._handleValueAndAutoValidate(event.target); |
| 11504 }, | 8043 }, |
| 11505 | 8044 |
| 11506 _onValueChanged: function(event) { | 8045 _onValueChanged: function(event) { |
| 11507 this._handleValueAndAutoValidate(event.target); | 8046 this._handleValueAndAutoValidate(event.target); |
| 11508 }, | 8047 }, |
| 11509 | 8048 |
| 11510 _handleValue: function(inputElement) { | 8049 _handleValue: function(inputElement) { |
| 11511 var value = this._inputElementValue; | 8050 var value = this._inputElementValue; |
| 11512 | 8051 |
| 11513 // type="number" hack needed because this.value is empty until it's valid | |
| 11514 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme
nt.checkValidity())) { | 8052 if (value || value === 0 || (inputElement.type === 'number' && !inputEleme
nt.checkValidity())) { |
| 11515 this._inputHasContent = true; | 8053 this._inputHasContent = true; |
| 11516 } else { | 8054 } else { |
| 11517 this._inputHasContent = false; | 8055 this._inputHasContent = false; |
| 11518 } | 8056 } |
| 11519 | 8057 |
| 11520 this.updateAddons({ | 8058 this.updateAddons({ |
| 11521 inputElement: inputElement, | 8059 inputElement: inputElement, |
| 11522 value: value, | 8060 value: value, |
| 11523 invalid: this.invalid | 8061 invalid: this.invalid |
| 11524 }); | 8062 }); |
| 11525 }, | 8063 }, |
| 11526 | 8064 |
| 11527 _handleValueAndAutoValidate: function(inputElement) { | 8065 _handleValueAndAutoValidate: function(inputElement) { |
| 11528 if (this.autoValidate) { | 8066 if (this.autoValidate) { |
| 11529 var valid; | 8067 var valid; |
| 11530 if (inputElement.validate) { | 8068 if (inputElement.validate) { |
| 11531 valid = inputElement.validate(this._inputElementValue); | 8069 valid = inputElement.validate(this._inputElementValue); |
| 11532 } else { | 8070 } else { |
| 11533 valid = inputElement.checkValidity(); | 8071 valid = inputElement.checkValidity(); |
| 11534 } | 8072 } |
| 11535 this.invalid = !valid; | 8073 this.invalid = !valid; |
| 11536 } | 8074 } |
| 11537 | 8075 |
| 11538 // Call this last to notify the add-ons. | |
| 11539 this._handleValue(inputElement); | 8076 this._handleValue(inputElement); |
| 11540 }, | 8077 }, |
| 11541 | 8078 |
| 11542 _onIronInputValidate: function(event) { | 8079 _onIronInputValidate: function(event) { |
| 11543 this.invalid = this._inputElement.invalid; | 8080 this.invalid = this._inputElement.invalid; |
| 11544 }, | 8081 }, |
| 11545 | 8082 |
| 11546 _invalidChanged: function() { | 8083 _invalidChanged: function() { |
| 11547 if (this._addons) { | 8084 if (this._addons) { |
| 11548 this.updateAddons({invalid: this.invalid}); | 8085 this.updateAddons({invalid: this.invalid}); |
| 11549 } | 8086 } |
| 11550 }, | 8087 }, |
| 11551 | 8088 |
| 11552 /** | |
| 11553 * Call this to update the state of add-ons. | |
| 11554 * @param {Object} state Add-on state. | |
| 11555 */ | |
| 11556 updateAddons: function(state) { | 8089 updateAddons: function(state) { |
| 11557 for (var addon, index = 0; addon = this._addons[index]; index++) { | 8090 for (var addon, index = 0; addon = this._addons[index]; index++) { |
| 11558 addon.update(state); | 8091 addon.update(state); |
| 11559 } | 8092 } |
| 11560 }, | 8093 }, |
| 11561 | 8094 |
| 11562 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused,
invalid, _inputHasContent) { | 8095 _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused,
invalid, _inputHasContent) { |
| 11563 var cls = 'input-content'; | 8096 var cls = 'input-content'; |
| 11564 if (!noLabelFloat) { | 8097 if (!noLabelFloat) { |
| 11565 var label = this.querySelector('label'); | 8098 var label = this.querySelector('label'); |
| 11566 | 8099 |
| 11567 if (alwaysFloatLabel || _inputHasContent) { | 8100 if (alwaysFloatLabel || _inputHasContent) { |
| 11568 cls += ' label-is-floating'; | 8101 cls += ' label-is-floating'; |
| 11569 // If the label is floating, ignore any offsets that may have been | |
| 11570 // applied from a prefix element. | |
| 11571 this.$.labelAndInputContainer.style.position = 'static'; | 8102 this.$.labelAndInputContainer.style.position = 'static'; |
| 11572 | 8103 |
| 11573 if (invalid) { | 8104 if (invalid) { |
| 11574 cls += ' is-invalid'; | 8105 cls += ' is-invalid'; |
| 11575 } else if (focused) { | 8106 } else if (focused) { |
| 11576 cls += " label-is-highlighted"; | 8107 cls += " label-is-highlighted"; |
| 11577 } | 8108 } |
| 11578 } else { | 8109 } else { |
| 11579 // When the label is not floating, it should overlap the input element
. | |
| 11580 if (label) { | 8110 if (label) { |
| 11581 this.$.labelAndInputContainer.style.position = 'relative'; | 8111 this.$.labelAndInputContainer.style.position = 'relative'; |
| 11582 } | 8112 } |
| 11583 } | 8113 } |
| 11584 } else { | 8114 } else { |
| 11585 if (_inputHasContent) { | 8115 if (_inputHasContent) { |
| 11586 cls += ' label-is-hidden'; | 8116 cls += ' label-is-hidden'; |
| 11587 } | 8117 } |
| 11588 } | 8118 } |
| 11589 return cls; | 8119 return cls; |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11640 }); | 8170 }); |
| 11641 // Copyright 2015 The Chromium Authors. All rights reserved. | 8171 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 11642 // Use of this source code is governed by a BSD-style license that can be | 8172 // Use of this source code is governed by a BSD-style license that can be |
| 11643 // found in the LICENSE file. | 8173 // found in the LICENSE file. |
| 11644 | 8174 |
| 11645 cr.define('downloads', function() { | 8175 cr.define('downloads', function() { |
| 11646 var Toolbar = Polymer({ | 8176 var Toolbar = Polymer({ |
| 11647 is: 'downloads-toolbar', | 8177 is: 'downloads-toolbar', |
| 11648 | 8178 |
| 11649 attached: function() { | 8179 attached: function() { |
| 11650 // isRTL() only works after i18n_template.js runs to set <html dir>. | |
| 11651 this.overflowAlign_ = isRTL() ? 'left' : 'right'; | 8180 this.overflowAlign_ = isRTL() ? 'left' : 'right'; |
| 11652 }, | 8181 }, |
| 11653 | 8182 |
| 11654 properties: { | 8183 properties: { |
| 11655 downloadsShowing: { | 8184 downloadsShowing: { |
| 11656 reflectToAttribute: true, | 8185 reflectToAttribute: true, |
| 11657 type: Boolean, | 8186 type: Boolean, |
| 11658 value: false, | 8187 value: false, |
| 11659 observer: 'downloadsShowingChanged_', | 8188 observer: 'downloadsShowingChanged_', |
| 11660 }, | 8189 }, |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11698 onClearAllTap_: function() { | 8227 onClearAllTap_: function() { |
| 11699 assert(this.canClearAll()); | 8228 assert(this.canClearAll()); |
| 11700 downloads.ActionService.getInstance().clearAll(); | 8229 downloads.ActionService.getInstance().clearAll(); |
| 11701 }, | 8230 }, |
| 11702 | 8231 |
| 11703 /** @private */ | 8232 /** @private */ |
| 11704 onPaperDropdownClose_: function() { | 8233 onPaperDropdownClose_: function() { |
| 11705 window.removeEventListener('resize', assert(this.boundClose_)); | 8234 window.removeEventListener('resize', assert(this.boundClose_)); |
| 11706 }, | 8235 }, |
| 11707 | 8236 |
| 11708 /** | |
| 11709 * @param {!Event} e | |
| 11710 * @private | |
| 11711 */ | |
| 11712 onItemBlur_: function(e) { | 8237 onItemBlur_: function(e) { |
| 11713 var menu = /** @type {PaperMenuElement} */(this.$$('paper-menu')); | 8238 var menu = /** @type {PaperMenuElement} */(this.$$('paper-menu')); |
| 11714 if (menu.items.indexOf(e.relatedTarget) >= 0) | 8239 if (menu.items.indexOf(e.relatedTarget) >= 0) |
| 11715 return; | 8240 return; |
| 11716 | 8241 |
| 11717 this.$.more.restoreFocusOnClose = false; | 8242 this.$.more.restoreFocusOnClose = false; |
| 11718 this.closeMoreActions_(); | 8243 this.closeMoreActions_(); |
| 11719 this.$.more.restoreFocusOnClose = true; | 8244 this.$.more.restoreFocusOnClose = true; |
| 11720 }, | 8245 }, |
| 11721 | 8246 |
| 11722 /** @private */ | 8247 /** @private */ |
| 11723 onPaperDropdownOpen_: function() { | 8248 onPaperDropdownOpen_: function() { |
| 11724 this.boundClose_ = this.boundClose_ || this.closeMoreActions_.bind(this); | 8249 this.boundClose_ = this.boundClose_ || this.closeMoreActions_.bind(this); |
| 11725 window.addEventListener('resize', this.boundClose_); | 8250 window.addEventListener('resize', this.boundClose_); |
| 11726 }, | 8251 }, |
| 11727 | 8252 |
| 11728 /** | |
| 11729 * @param {!CustomEvent} event | |
| 11730 * @private | |
| 11731 */ | |
| 11732 onSearchChanged_: function(event) { | 8253 onSearchChanged_: function(event) { |
| 11733 downloads.ActionService.getInstance().search( | 8254 downloads.ActionService.getInstance().search( |
| 11734 /** @type {string} */ (event.detail)); | 8255 /** @type {string} */ (event.detail)); |
| 11735 this.updateClearAll_(); | 8256 this.updateClearAll_(); |
| 11736 }, | 8257 }, |
| 11737 | 8258 |
| 11738 /** @private */ | 8259 /** @private */ |
| 11739 onOpenDownloadsFolderTap_: function() { | 8260 onOpenDownloadsFolderTap_: function() { |
| 11740 downloads.ActionService.getInstance().openDownloadsFolder(); | 8261 downloads.ActionService.getInstance().openDownloadsFolder(); |
| 11741 }, | 8262 }, |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 11794 if (this.hasDownloads_) { | 8315 if (this.hasDownloads_) { |
| 11795 this.$['downloads-list'].fire('iron-resize'); | 8316 this.$['downloads-list'].fire('iron-resize'); |
| 11796 } else { | 8317 } else { |
| 11797 var isSearching = downloads.ActionService.getInstance().isSearching(); | 8318 var isSearching = downloads.ActionService.getInstance().isSearching(); |
| 11798 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; | 8319 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; |
| 11799 this.$['no-downloads'].querySelector('span').textContent = | 8320 this.$['no-downloads'].querySelector('span').textContent = |
| 11800 loadTimeData.getString(messageToShow); | 8321 loadTimeData.getString(messageToShow); |
| 11801 } | 8322 } |
| 11802 }, | 8323 }, |
| 11803 | 8324 |
| 11804 /** | |
| 11805 * @param {number} index | |
| 11806 * @param {!Array<!downloads.Data>} list | |
| 11807 * @private | |
| 11808 */ | |
| 11809 insertItems_: function(index, list) { | 8325 insertItems_: function(index, list) { |
| 11810 this.splice.apply(this, ['items_', index, 0].concat(list)); | 8326 this.splice.apply(this, ['items_', index, 0].concat(list)); |
| 11811 this.updateHideDates_(index, index + list.length); | 8327 this.updateHideDates_(index, index + list.length); |
| 11812 this.removeAttribute('loading'); | 8328 this.removeAttribute('loading'); |
| 11813 }, | 8329 }, |
| 11814 | 8330 |
| 11815 /** @private */ | 8331 /** @private */ |
| 11816 itemsChanged_: function() { | 8332 itemsChanged_: function() { |
| 11817 this.hasDownloads_ = this.items_.length > 0; | 8333 this.hasDownloads_ = this.items_.length > 0; |
| 11818 }, | 8334 }, |
| 11819 | 8335 |
| 11820 /** | |
| 11821 * @param {Event} e | |
| 11822 * @private | |
| 11823 */ | |
| 11824 onCanExecute_: function(e) { | 8336 onCanExecute_: function(e) { |
| 11825 e = /** @type {cr.ui.CanExecuteEvent} */(e); | 8337 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
| 11826 switch (e.command.id) { | 8338 switch (e.command.id) { |
| 11827 case 'undo-command': | 8339 case 'undo-command': |
| 11828 e.canExecute = this.$.toolbar.canUndo(); | 8340 e.canExecute = this.$.toolbar.canUndo(); |
| 11829 break; | 8341 break; |
| 11830 case 'clear-all-command': | 8342 case 'clear-all-command': |
| 11831 e.canExecute = this.$.toolbar.canClearAll(); | 8343 e.canExecute = this.$.toolbar.canClearAll(); |
| 11832 break; | 8344 break; |
| 11833 case 'find-command': | 8345 case 'find-command': |
| 11834 e.canExecute = true; | 8346 e.canExecute = true; |
| 11835 break; | 8347 break; |
| 11836 } | 8348 } |
| 11837 }, | 8349 }, |
| 11838 | 8350 |
| 11839 /** | |
| 11840 * @param {Event} e | |
| 11841 * @private | |
| 11842 */ | |
| 11843 onCommand_: function(e) { | 8351 onCommand_: function(e) { |
| 11844 if (e.command.id == 'clear-all-command') | 8352 if (e.command.id == 'clear-all-command') |
| 11845 downloads.ActionService.getInstance().clearAll(); | 8353 downloads.ActionService.getInstance().clearAll(); |
| 11846 else if (e.command.id == 'undo-command') | 8354 else if (e.command.id == 'undo-command') |
| 11847 downloads.ActionService.getInstance().undo(); | 8355 downloads.ActionService.getInstance().undo(); |
| 11848 else if (e.command.id == 'find-command') | 8356 else if (e.command.id == 'find-command') |
| 11849 this.$.toolbar.onFindCommand(); | 8357 this.$.toolbar.onFindCommand(); |
| 11850 }, | 8358 }, |
| 11851 | 8359 |
| 11852 /** @private */ | 8360 /** @private */ |
| 11853 onListScroll_: function() { | 8361 onListScroll_: function() { |
| 11854 var list = this.$['downloads-list']; | 8362 var list = this.$['downloads-list']; |
| 11855 if (list.scrollHeight - list.scrollTop - list.offsetHeight <= 100) { | 8363 if (list.scrollHeight - list.scrollTop - list.offsetHeight <= 100) { |
| 11856 // Approaching the end of the scrollback. Attempt to load more items. | |
| 11857 downloads.ActionService.getInstance().loadMore(); | 8364 downloads.ActionService.getInstance().loadMore(); |
| 11858 } | 8365 } |
| 11859 }, | 8366 }, |
| 11860 | 8367 |
| 11861 /** @private */ | 8368 /** @private */ |
| 11862 onLoad_: function() { | 8369 onLoad_: function() { |
| 11863 cr.ui.decorate('command', cr.ui.Command); | 8370 cr.ui.decorate('command', cr.ui.Command); |
| 11864 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | 8371 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
| 11865 document.addEventListener('command', this.onCommand_.bind(this)); | 8372 document.addEventListener('command', this.onCommand_.bind(this)); |
| 11866 | 8373 |
| 11867 downloads.ActionService.getInstance().loadMore(); | 8374 downloads.ActionService.getInstance().loadMore(); |
| 11868 }, | 8375 }, |
| 11869 | 8376 |
| 11870 /** | |
| 11871 * @param {number} index | |
| 11872 * @private | |
| 11873 */ | |
| 11874 removeItem_: function(index) { | 8377 removeItem_: function(index) { |
| 11875 this.splice('items_', index, 1); | 8378 this.splice('items_', index, 1); |
| 11876 this.updateHideDates_(index, index); | 8379 this.updateHideDates_(index, index); |
| 11877 this.onListScroll_(); | 8380 this.onListScroll_(); |
| 11878 }, | 8381 }, |
| 11879 | 8382 |
| 11880 /** | |
| 11881 * @param {number} start | |
| 11882 * @param {number} end | |
| 11883 * @private | |
| 11884 */ | |
| 11885 updateHideDates_: function(start, end) { | 8383 updateHideDates_: function(start, end) { |
| 11886 for (var i = start; i <= end; ++i) { | 8384 for (var i = start; i <= end; ++i) { |
| 11887 var current = this.items_[i]; | 8385 var current = this.items_[i]; |
| 11888 if (!current) | 8386 if (!current) |
| 11889 continue; | 8387 continue; |
| 11890 var prev = this.items_[i - 1]; | 8388 var prev = this.items_[i - 1]; |
| 11891 current.hideDate = !!prev && prev.date_string == current.date_string; | 8389 current.hideDate = !!prev && prev.date_string == current.date_string; |
| 11892 } | 8390 } |
| 11893 }, | 8391 }, |
| 11894 | 8392 |
| 11895 /** | |
| 11896 * @param {number} index | |
| 11897 * @param {!downloads.Data} data | |
| 11898 * @private | |
| 11899 */ | |
| 11900 updateItem_: function(index, data) { | 8393 updateItem_: function(index, data) { |
| 11901 this.set('items_.' + index, data); | 8394 this.set('items_.' + index, data); |
| 11902 this.updateHideDates_(index, index); | 8395 this.updateHideDates_(index, index); |
| 11903 var list = /** @type {!IronListElement} */(this.$['downloads-list']); | 8396 var list = /** @type {!IronListElement} */(this.$['downloads-list']); |
| 11904 list.updateSizeForItem(index); | 8397 list.updateSizeForItem(index); |
| 11905 }, | 8398 }, |
| 11906 }); | 8399 }); |
| 11907 | 8400 |
| 11908 Manager.clearAll = function() { | 8401 Manager.clearAll = function() { |
| 11909 Manager.get().clearAll_(); | 8402 Manager.get().clearAll_(); |
| (...skipping 21 matching lines...) Expand all Loading... |
| 11931 Manager.get().updateItem_(index, data); | 8424 Manager.get().updateItem_(index, data); |
| 11932 }; | 8425 }; |
| 11933 | 8426 |
| 11934 return {Manager: Manager}; | 8427 return {Manager: Manager}; |
| 11935 }); | 8428 }); |
| 11936 // Copyright 2015 The Chromium Authors. All rights reserved. | 8429 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 11937 // Use of this source code is governed by a BSD-style license that can be | 8430 // Use of this source code is governed by a BSD-style license that can be |
| 11938 // found in the LICENSE file. | 8431 // found in the LICENSE file. |
| 11939 | 8432 |
| 11940 window.addEventListener('load', downloads.Manager.onLoad); | 8433 window.addEventListener('load', downloads.Manager.onLoad); |
| OLD | NEW |