OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 Copyright 2013 The Polymer Authors. All rights reserved. |
| 3 Use of this source code is governed by a BSD-style |
| 4 license that can be found in the LICENSE file. |
| 5 --> |
| 6 <link rel="import" href="../polymer/polymer.html"> |
| 7 |
| 8 <link rel="import" href="../polymer-key-helper/polymer-key-helper.html"> |
| 9 |
| 10 <!-- |
| 11 /** |
| 12 * @module Polymer Elements |
| 13 */ |
| 14 /** |
| 15 * polymer-overlay displays overlayed on top of other content. It starts |
| 16 * out hidden and is displayed by setting it's opened property to true. |
| 17 * A polymer-overlay's opened state can be toggled by calling the toggle |
| 18 * method. |
| 19 * |
| 20 * It's common to want a polymer-overlay to animate to its opened |
| 21 * position. A number of helper css classes provide some basic open/close |
| 22 * animations. For example, assigning the class polymer-overlay-fade to a |
| 23 * polymer-overlay will make it fade into and out of view as it opens and |
| 24 * closes. Note, if multiple polymer-overlay's are opened, they should |
| 25 * stack on top of each other. |
| 26 * |
| 27 * Styling: The size and position of a polymer-overlay should be setup |
| 28 * via css. |
| 29 * polymer-overlay is natually sized around its content. When a |
| 30 * polymer-overlay is opened it is shown and the 'opened' class is added |
| 31 * to it. This is typically where css transitions and animations are |
| 32 * applied. When the polymer-overlay is closed, the 'opened' class is |
| 33 * removed and a 'closing' class is added. Use 'closing' to customize |
| 34 * the closing animation. |
| 35 * |
| 36 * Classes for animating polymer-overlay: |
| 37 * |
| 38 * * polymer-overlay-fade: fade in/out when opened/closed |
| 39 * * polymer-overlay-scale-slideup: open: fade in and shrink; |
| 40 * close: slide up |
| 41 * * polymer-overlay-shake: open: fly in and shake; close: shake and |
| 42 * fly out. |
| 43 * |
| 44 * It's common to use polymer-overlay to gather user input, for example |
| 45 * a login dialog. To facilitate this, polymer-overlay supports automatic |
| 46 * focusing of a specific element when it's opened. The element to be |
| 47 * focused should be given an autofocus attribute. |
| 48 * |
| 49 * An element that should close the polymer-overlay will automatically |
| 50 * do so if it is given the overlay-toggle attribute. Please note that |
| 51 * polymer-overlay will close whenever the user taps outside it or |
| 52 * presses the escape key. The behavior can be turned off via the |
| 53 * autoCloseDisabled property. |
| 54 * |
| 55 * <div> |
| 56 * <polymer-overlay></polymer-overlay> |
| 57 * <h2>Dialog</h2> |
| 58 * <input placeholder="say something..." autofocus> |
| 59 * <div>I agree with this wholeheartedly.</div> |
| 60 * <button overlay-toggle>OK</button> |
| 61 * </div> |
| 62 * |
| 63 * @class polymer-overlay |
| 64 */ |
| 65 /** |
| 66 * Fired when the polymer-overlay opened property is set. |
| 67 * |
| 68 * @event polymer-overlay-open |
| 69 * @param {Object} inDetail |
| 70 * @param {Object} inDetail.opened the opened state |
| 71 */ |
| 72 --> |
| 73 <polymer-element name="polymer-overlay" attributes="target opened autoCloseDisab
led transitions"> |
| 74 <template> |
| 75 <link rel="stylesheet" polymer-scope="controller" href="polymer-overlay.css"
> |
| 76 <link rel="stylesheet" href="polymer-overlay.css"> |
| 77 <polymer-key-helper id="keyHelper"></polymer-key-helper> |
| 78 </template> |
| 79 <script> |
| 80 (function() { |
| 81 // track overlays for z-index and focus managemant |
| 82 var overlays = []; |
| 83 var trackOverlays = function(inOverlay) { |
| 84 if (inOverlay.opened) { |
| 85 //var overlayZ = window.getComputedStyle(inOverlay.target).zIndex; |
| 86 //var z0 = Math.max(currentOverlayZ(), overlayZ); |
| 87 var z0 = currentOverlayZ(); |
| 88 overlays.push(inOverlay); |
| 89 var z1 = currentOverlayZ(); |
| 90 if (z1 <= z0) { |
| 91 applyOverlayZ(inOverlay, z0); |
| 92 } |
| 93 } else { |
| 94 var i = overlays.indexOf(inOverlay); |
| 95 if (i >= 0) { |
| 96 overlays.splice(i, 1); |
| 97 setZ(inOverlay, null); |
| 98 } |
| 99 } |
| 100 } |
| 101 |
| 102 var applyOverlayZ = function(inOverlay, inAboveZ) { |
| 103 setZ(inOverlay.target, inAboveZ + 2); |
| 104 } |
| 105 |
| 106 var setZ = function(inNode, inZ) { |
| 107 inNode.style.zIndex = inZ; |
| 108 } |
| 109 |
| 110 var currentOverlay = function() { |
| 111 return overlays[overlays.length-1]; |
| 112 } |
| 113 |
| 114 var DEFAULT_Z = 10; |
| 115 |
| 116 var currentOverlayZ = function() { |
| 117 var z; |
| 118 var current = currentOverlay(); |
| 119 if (current) { |
| 120 var z1 = window.getComputedStyle(current.target).zIndex; |
| 121 if (!isNaN(z1)) { |
| 122 z = Number(z1); |
| 123 } |
| 124 } |
| 125 return z || DEFAULT_Z; |
| 126 } |
| 127 |
| 128 var focusOverlay = function() { |
| 129 var current = currentOverlay(); |
| 130 if (current) { |
| 131 current.applyFocus(); |
| 132 } |
| 133 } |
| 134 |
| 135 Polymer('polymer-overlay', { |
| 136 /** |
| 137 * The target element. |
| 138 * |
| 139 * @attribute target |
| 140 * @type Object |
| 141 */ |
| 142 target: null, |
| 143 /** |
| 144 * Set opened to true to show an overlay and to false to hide it. |
| 145 * A polymer-overlay may be made intially opened by setting its |
| 146 * opened attribute. |
| 147 * @attribute opened |
| 148 * @type boolean |
| 149 * @default false |
| 150 */ |
| 151 opened: false, |
| 152 /** |
| 153 * By default an overlay will close automatically if the user |
| 154 * taps outside it or presses the escape key. Disable this |
| 155 * behavior by setting the autoCloseDisabled property to true. |
| 156 * @attribute autoCloseDisabled |
| 157 * @type boolean |
| 158 * @default false |
| 159 */ |
| 160 autoCloseDisabled: false, |
| 161 /** |
| 162 * This property specifies the animation to play when the overlay is |
| 163 * opened/closed. It can be an array of two animations |
| 164 * [opening, closing], a single animation, an array of two strings, or |
| 165 * a string. The strings should the tag names of custom elements |
| 166 * descending from a polymer-animation. In the case of a single |
| 167 * animation the closing animation is the opening animation played |
| 168 * backwards. |
| 169 * @attribute transitions |
| 170 * @type polymer-animation |
| 171 * @type Array<polymer-animation> |
| 172 * @type string |
| 173 * @type Array<string> |
| 174 */ |
| 175 transitions: null, |
| 176 timeout: 1000, |
| 177 captureEventType: 'tap', |
| 178 ready: function() { |
| 179 if (this.tabIndex === undefined) { |
| 180 this.tabIndex = -1; |
| 181 } |
| 182 this.setAttribute('touch-action', 'none'); |
| 183 }, |
| 184 enteredView: function() { |
| 185 this.installControllerStyles(); |
| 186 }, |
| 187 /** |
| 188 * Toggle the opened state of the overlay. |
| 189 * @method toggle |
| 190 */ |
| 191 toggle: function() { |
| 192 this.opened = !this.opened; |
| 193 }, |
| 194 targetChanged: function(old) { |
| 195 if (this.target) { |
| 196 if (this.target.tabIndex === undefined) { |
| 197 this.target.tabIndex = -1; |
| 198 } |
| 199 this.target.classList.add('polymer-overlay'); |
| 200 this.addListeners(this.target); |
| 201 } |
| 202 if (old) { |
| 203 old.classList.remove('polymer-overlay'); |
| 204 this.removeListeners(old); |
| 205 } |
| 206 }, |
| 207 listeners: { |
| 208 'tap': 'tapHandler', |
| 209 'keydown': 'keydownHandler' |
| 210 }, |
| 211 addListeners: function(node) { |
| 212 for (e in this.listeners) { |
| 213 node.addEventListener(e, this[this.listeners[e]].bind(this)); |
| 214 } |
| 215 }, |
| 216 removeListeners: function(node) { |
| 217 for (e in this.listeners) { |
| 218 node.removeEventListener(e, this[this.listeners[e]].bind(this)); |
| 219 } |
| 220 }, |
| 221 openedChanged: function() { |
| 222 this.renderOpened(); |
| 223 trackOverlays(this); |
| 224 this.async(function() { |
| 225 if (!this.autoCloseDisabled) { |
| 226 this.enableCaptureHandler(this.opened); |
| 227 } |
| 228 }); |
| 229 this.enableResizeHandler(this.opened); |
| 230 this.fire('polymer-overlay-open', this.opened); |
| 231 }, |
| 232 enableHandler: function(inEnable, inMethodName, inNode, inEventName, inC
apture) { |
| 233 var m = 'bound' + inMethodName; |
| 234 this[m] = this[m] || this[inMethodName].bind(this); |
| 235 |
| 236 inNode[inEnable ? 'addEventListener' : 'removeEventListener']( |
| 237 inEventName, this[m], inCapture); |
| 238 }, |
| 239 enableResizeHandler: function(inEnable) { |
| 240 this.enableHandler(inEnable, 'resizeHandler', window, |
| 241 'resize'); |
| 242 }, |
| 243 enableCaptureHandler: function(inEnable) { |
| 244 this.enableHandler(inEnable, 'captureHandler', document, |
| 245 this.captureEventType, true); |
| 246 }, |
| 247 getFocusNode: function() { |
| 248 return this.target.querySelector('[autofocus]') || this.target; |
| 249 }, |
| 250 // TODO(sorvell): nodes stay focused when they become un-focusable |
| 251 // due to an ancestory becoming display: none; file bug. |
| 252 applyFocus: function() { |
| 253 var focusNode = this.getFocusNode(); |
| 254 if (this.opened) { |
| 255 focusNode.focus(); |
| 256 } else { |
| 257 focusNode.blur(); |
| 258 focusOverlay(); |
| 259 } |
| 260 }, |
| 261 positionTarget: function() { |
| 262 if (this.opened) { |
| 263 // vertically and horizontally center if not positioned |
| 264 var computedStyle = getComputedStyle(this.target); |
| 265 if (computedStyle.top === 'auto' && computedStyle.bottom === 'auto')
{ |
| 266 this.target.style.top = ((window.innerHeight - this.target.getBoun
dingClientRect().height) / 2) + 'px'; |
| 267 } |
| 268 if (computedStyle.left === 'auto' && computedStyle.right === 'auto')
{ |
| 269 this.target.style.left = ((window.innerWidth - this.target.getBoun
dingClientRect().width) / 2) + 'px'; |
| 270 } |
| 271 } |
| 272 }, |
| 273 resetTargetPosition: function() { |
| 274 this.target.style.top = this.target.style.left = null; |
| 275 }, |
| 276 get transition() { |
| 277 return (!Array.isArray(this.transitions) && this.transitions |
| 278 || this.opened && this.transitions && this.transitions[0] |
| 279 || !this.opened && this.transitions && this.transitions[1]); |
| 280 }, |
| 281 applyTransition: function() { |
| 282 var animation = typeof this.transition === 'string' ? |
| 283 document.createElement(this.transition) : this.transition; |
| 284 // FIXME: Apply a default duration. |
| 285 if ((!animation.duration || animation.duration === 'auto') && !animati
on.type) { |
| 286 animation.duration = 0.3; |
| 287 } |
| 288 if (!animation.hasTarget()) { |
| 289 animation.target = this.target; |
| 290 } |
| 291 // Make the overlay visible while the animation is running. |
| 292 var transition = new ParGroup([ |
| 293 animation.apply(), |
| 294 new Animation(this.target, [{'visibility': 'visible', 'display':'blo
ck'}]) |
| 295 ], {fill: 'none'}); |
| 296 transition.onend = this.completeOpening.bind(this); |
| 297 this.target.classList.add('animating'); |
| 298 document.timeline.play(transition); |
| 299 }, |
| 300 renderOpened: function() { |
| 301 this.target.classList.add('revealed'); |
| 302 // continue styling after delay so display state can change |
| 303 // without aborting transitions |
| 304 this.async('continueRenderOpened'); |
| 305 }, |
| 306 continueRenderOpened: function() { |
| 307 this.positionTarget(); |
| 308 if (this.transition) { |
| 309 this.applyTransition(); |
| 310 // FIXME: Apply the class after the animation starts playing to |
| 311 // prevent a flicker at the end of the animation. Should be handled |
| 312 // in polymer-animation-start event but not working in polyfill |
| 313 this.async(function() { |
| 314 this.target.classList.toggle('opened', this.opened); |
| 315 }, null, 100); |
| 316 } else { |
| 317 this.target.classList.toggle('opened', this.opened); |
| 318 this.async('completeOpening'); |
| 319 } |
| 320 }, |
| 321 completeOpening: function() { |
| 322 this.target.classList.remove('animating'); |
| 323 this.target.classList.toggle('revealed', this.opened); |
| 324 if (!this.opened) { |
| 325 this.resetTargetPosition(); |
| 326 } |
| 327 this.applyFocus(); |
| 328 }, |
| 329 tapHandler: function(e) { |
| 330 if (e.target && e.target.hasAttribute('overlay-toggle')) { |
| 331 this.toggle(); |
| 332 } else { |
| 333 if (this.autoCloseJob) { |
| 334 this.autoCloseJob.stop(); |
| 335 this.autoCloseJob = null; |
| 336 } |
| 337 } |
| 338 }, |
| 339 // TODO(sorvell): This approach will not work with modal. For |
| 340 // this we need a scrim. |
| 341 captureHandler: function(e) { |
| 342 if (!this.autoCloseDisabled && (currentOverlay() == this) && (this |
| 343 != e.target) && !(this.contains(e.target))) { |
| 344 this.autoCloseJob = this.job(this.autoCloseJob, function() { |
| 345 this.opened = false; |
| 346 }); |
| 347 } |
| 348 }, |
| 349 keydownHandler: function(e) { |
| 350 if (!this.autoCloseDisabled && (e.keyCode == this.$.keyHelper.ESCAPE_K
EY)) { |
| 351 this.opened = false; |
| 352 e.stopPropagation(); |
| 353 e.cancelBubble = true; |
| 354 } |
| 355 }, |
| 356 /** |
| 357 * Extensions of polymer-overlay should implement the resizeHandler |
| 358 * method to adjust the size and position of the overlay when the |
| 359 * browser window resizes. |
| 360 * @method resizeHandler |
| 361 */ |
| 362 resizeHandler: function() { |
| 363 } |
| 364 }); |
| 365 })(); |
| 366 </script> |
| 367 </polymer-element> |
OLD | NEW |