OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="../polymer/polymer.html"> |
| 12 <link rel="import" href="../iron-fit-behavior/iron-fit-behavior.html"> |
| 13 <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html
"> |
| 14 <link rel="import" href="iron-overlay-backdrop.html"> |
| 15 <link rel="import" href="iron-overlay-manager.html"> |
| 16 |
| 17 <script> |
| 18 |
| 19 /* |
| 20 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays |
| 21 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety |
| 22 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. |
| 23 |
| 24 ### Closing and canceling |
| 25 |
| 26 A dialog may be hidden by closing or canceling. The difference between close and
cancel is user |
| 27 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, |
| 28 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is |
| 29 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. |
| 30 `close()` should be called explicitly by the implementer when the user interacts
with a control |
| 31 in the overlay element. |
| 32 |
| 33 ### Positioning |
| 34 |
| 35 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can |
| 36 position and size it manually using CSS. See `Polymer.IronFitBehavior`. |
| 37 |
| 38 ### Backdrop |
| 39 |
| 40 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is |
| 41 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling |
| 42 options. |
| 43 |
| 44 ### Limitations |
| 45 |
| 46 The element is styled to appear on top of other content by setting its `z-index`
property. You |
| 47 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking |
| 48 context. You should place this element as a child of `<body>` whenever possible. |
| 49 |
| 50 @demo demo/index.html |
| 51 @polymerBehavior Polymer.IronOverlayBehavior |
| 52 */ |
| 53 |
| 54 Polymer.IronOverlayBehaviorImpl = { |
| 55 |
| 56 properties: { |
| 57 |
| 58 /** |
| 59 * True if the overlay is currently displayed. |
| 60 */ |
| 61 opened: { |
| 62 observer: '_openedChanged', |
| 63 type: Boolean, |
| 64 value: false |
| 65 }, |
| 66 |
| 67 /** |
| 68 * True if the overlay was canceled when it was last closed. |
| 69 */ |
| 70 canceled: { |
| 71 observer: '_canceledChanged', |
| 72 readOnly: true, |
| 73 type: Boolean, |
| 74 value: false |
| 75 }, |
| 76 |
| 77 /** |
| 78 * Set to true to display a backdrop behind the overlay. |
| 79 */ |
| 80 withBackdrop: { |
| 81 type: Boolean, |
| 82 value: false |
| 83 }, |
| 84 |
| 85 /** |
| 86 * Set to true to disable auto-focusing the overlay or child nodes with |
| 87 * the `autofocus` attribute` when the overlay is opened. |
| 88 */ |
| 89 noAutoFocus: { |
| 90 type: Boolean, |
| 91 value: false |
| 92 }, |
| 93 |
| 94 /** |
| 95 * Set to true to disable canceling the overlay with the ESC key. |
| 96 */ |
| 97 noCancelOnEscKey: { |
| 98 type: Boolean, |
| 99 value: false |
| 100 }, |
| 101 |
| 102 /** |
| 103 * Set to true to disable canceling the overlay by clicking outside it. |
| 104 */ |
| 105 noCancelOnOutsideClick: { |
| 106 type: Boolean, |
| 107 value: false |
| 108 }, |
| 109 |
| 110 /** |
| 111 * Returns the reason this dialog was last closed. |
| 112 */ |
| 113 closingReason: { |
| 114 // was a getter before, but needs to be a property so other |
| 115 // behaviors can override this. |
| 116 type: Object |
| 117 }, |
| 118 |
| 119 _manager: { |
| 120 type: Object, |
| 121 value: Polymer.IronOverlayManager |
| 122 }, |
| 123 |
| 124 _boundOnCaptureClick: { |
| 125 type: Function, |
| 126 value: function() { |
| 127 return this._onCaptureClick.bind(this); |
| 128 } |
| 129 }, |
| 130 |
| 131 _boundOnCaptureKeydown: { |
| 132 type: Function, |
| 133 value: function() { |
| 134 return this._onCaptureKeydown.bind(this); |
| 135 } |
| 136 } |
| 137 |
| 138 }, |
| 139 |
| 140 listeners: { |
| 141 'click': '_onClick', |
| 142 'iron-resize': '_onIronResize' |
| 143 }, |
| 144 |
| 145 /** |
| 146 * The backdrop element. |
| 147 * @type Node |
| 148 */ |
| 149 get backdropElement() { |
| 150 return this._backdrop; |
| 151 }, |
| 152 |
| 153 get _focusNode() { |
| 154 return Polymer.dom(this).querySelector('[autofocus]') || this; |
| 155 }, |
| 156 |
| 157 registered: function() { |
| 158 this._backdrop = document.createElement('iron-overlay-backdrop'); |
| 159 }, |
| 160 |
| 161 ready: function() { |
| 162 this._ensureSetup(); |
| 163 if (this._callOpenedWhenReady) { |
| 164 this._openedChanged(); |
| 165 } |
| 166 }, |
| 167 |
| 168 detached: function() { |
| 169 this.opened = false; |
| 170 this._completeBackdrop(); |
| 171 this._manager.removeOverlay(this); |
| 172 }, |
| 173 |
| 174 /** |
| 175 * Toggle the opened state of the overlay. |
| 176 */ |
| 177 toggle: function() { |
| 178 this.opened = !this.opened; |
| 179 }, |
| 180 |
| 181 /** |
| 182 * Open the overlay. |
| 183 */ |
| 184 open: function() { |
| 185 this.opened = true; |
| 186 this.closingReason = {canceled: false}; |
| 187 }, |
| 188 |
| 189 /** |
| 190 * Close the overlay. |
| 191 */ |
| 192 close: function() { |
| 193 this.opened = false; |
| 194 this._setCanceled(false); |
| 195 }, |
| 196 |
| 197 /** |
| 198 * Cancels the overlay. |
| 199 */ |
| 200 cancel: function() { |
| 201 this.opened = false, |
| 202 this._setCanceled(true); |
| 203 }, |
| 204 |
| 205 _ensureSetup: function() { |
| 206 if (this._overlaySetup) { |
| 207 return; |
| 208 } |
| 209 this._overlaySetup = true; |
| 210 this.style.outline = 'none'; |
| 211 this.style.display = 'none'; |
| 212 }, |
| 213 |
| 214 _openedChanged: function() { |
| 215 if (this.opened) { |
| 216 this.removeAttribute('aria-hidden'); |
| 217 } else { |
| 218 this.setAttribute('aria-hidden', 'true'); |
| 219 } |
| 220 |
| 221 // wait to call after ready only if we're initially open |
| 222 if (!this._overlaySetup) { |
| 223 this._callOpenedWhenReady = this.opened; |
| 224 return; |
| 225 } |
| 226 if (this._openChangedAsync) { |
| 227 this.cancelAsync(this._openChangedAsync); |
| 228 } |
| 229 |
| 230 this._toggleListeners(); |
| 231 |
| 232 if (this.opened) { |
| 233 this._prepareRenderOpened(); |
| 234 } |
| 235 |
| 236 // async here to allow overlay layer to become visible. |
| 237 this._openChangedAsync = this.async(function() { |
| 238 // overlay becomes visible here |
| 239 this.style.display = ''; |
| 240 // force layout to ensure transitions will go |
| 241 this.offsetWidth; |
| 242 if (this.opened) { |
| 243 this._renderOpened(); |
| 244 } else { |
| 245 this._renderClosed(); |
| 246 } |
| 247 this._openChangedAsync = null; |
| 248 }); |
| 249 |
| 250 }, |
| 251 |
| 252 _canceledChanged: function() { |
| 253 this.closingReason = this.closingReason || {}; |
| 254 this.closingReason.canceled = this.canceled; |
| 255 }, |
| 256 |
| 257 _toggleListener: function(enable, node, event, boundListener, capture) { |
| 258 if (enable) { |
| 259 node.addEventListener(event, boundListener, capture); |
| 260 } else { |
| 261 node.removeEventListener(event, boundListener, capture); |
| 262 } |
| 263 }, |
| 264 |
| 265 _toggleListeners: function() { |
| 266 if (this._toggleListenersAsync) { |
| 267 this.cancelAsync(this._toggleListenersAsync); |
| 268 } |
| 269 // async so we don't auto-close immediately via a click. |
| 270 this._toggleListenersAsync = this.async(function() { |
| 271 this._toggleListener(this.opened, document, 'click', this._boundOnCaptur
eClick, true); |
| 272 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt
ureKeydown, true); |
| 273 this._toggleListenersAsync = null; |
| 274 }); |
| 275 }, |
| 276 |
| 277 // tasks which must occur before opening; e.g. making the element visible |
| 278 _prepareRenderOpened: function() { |
| 279 this._manager.addOverlay(this); |
| 280 |
| 281 if (this.withBackdrop) { |
| 282 this.backdropElement.prepare(); |
| 283 this._manager.trackBackdrop(this); |
| 284 } |
| 285 |
| 286 this._preparePositioning(); |
| 287 this.fit(); |
| 288 this._finishPositioning(); |
| 289 }, |
| 290 |
| 291 // tasks which cause the overlay to actually open; typically play an |
| 292 // animation |
| 293 _renderOpened: function() { |
| 294 if (this.withBackdrop) { |
| 295 this.backdropElement.open(); |
| 296 } |
| 297 this._finishRenderOpened(); |
| 298 }, |
| 299 |
| 300 _renderClosed: function() { |
| 301 if (this.withBackdrop) { |
| 302 this.backdropElement.close(); |
| 303 } |
| 304 this._finishRenderClosed(); |
| 305 }, |
| 306 |
| 307 _onTransitionend: function(event) { |
| 308 // make sure this is our transition event. |
| 309 if (event && event.target !== this) { |
| 310 return; |
| 311 } |
| 312 if (this.opened) { |
| 313 this._finishRenderOpened(); |
| 314 } else { |
| 315 this._finishRenderClosed(); |
| 316 } |
| 317 }, |
| 318 |
| 319 _finishRenderOpened: function() { |
| 320 // focus the child node with [autofocus] |
| 321 if (!this.noAutoFocus) { |
| 322 this._focusNode.focus(); |
| 323 } |
| 324 |
| 325 this.fire('iron-overlay-opened'); |
| 326 |
| 327 this._squelchNextResize = true; |
| 328 this.async(this.notifyResize); |
| 329 }, |
| 330 |
| 331 _finishRenderClosed: function() { |
| 332 // hide the overlay and remove the backdrop |
| 333 this.resetFit(); |
| 334 this.style.display = 'none'; |
| 335 this._completeBackdrop(); |
| 336 this._manager.removeOverlay(this); |
| 337 |
| 338 this._focusNode.blur(); |
| 339 // focus the next overlay, if there is one |
| 340 this._manager.focusOverlay(); |
| 341 |
| 342 this.fire('iron-overlay-closed', this.closingReason); |
| 343 |
| 344 this._squelchNextResize = true; |
| 345 this.async(this.notifyResize); |
| 346 }, |
| 347 |
| 348 _completeBackdrop: function() { |
| 349 if (this.withBackdrop) { |
| 350 this._manager.trackBackdrop(this); |
| 351 this.backdropElement.complete(); |
| 352 } |
| 353 }, |
| 354 |
| 355 _preparePositioning: function() { |
| 356 this.style.transition = this.style.webkitTransition = 'none'; |
| 357 this.style.transform = this.style.webkitTransform = 'none'; |
| 358 this.style.display = ''; |
| 359 }, |
| 360 |
| 361 _finishPositioning: function(target) { |
| 362 this.style.display = 'none'; |
| 363 this.style.transform = this.style.webkitTransform = ''; |
| 364 // force layout to avoid application of transform |
| 365 this.offsetWidth; |
| 366 this.style.transition = this.style.webkitTransition = ''; |
| 367 }, |
| 368 |
| 369 _applyFocus: function() { |
| 370 if (this.opened) { |
| 371 if (!this.noAutoFocus) { |
| 372 this._focusNode.focus(); |
| 373 } |
| 374 } else { |
| 375 this._focusNode.blur(); |
| 376 this._manager.focusOverlay(); |
| 377 } |
| 378 }, |
| 379 |
| 380 _onCaptureClick: function(event) { |
| 381 // attempt to close asynchronously and prevent the close of a tap event is
immediately heard |
| 382 // on target. This is because in shadow dom due to event retargetting even
t.target is not |
| 383 // useful. |
| 384 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi
s)) { |
| 385 this._cancelJob = this.async(function() { |
| 386 this.cancel(); |
| 387 }, 10); |
| 388 } |
| 389 }, |
| 390 |
| 391 _onClick: function(event) { |
| 392 if (this._cancelJob) { |
| 393 this.cancelAsync(this._cancelJob); |
| 394 this._cancelJob = null; |
| 395 } |
| 396 }, |
| 397 |
| 398 _onCaptureKeydown: function(event) { |
| 399 var ESC = 27; |
| 400 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { |
| 401 this.cancel(); |
| 402 event.stopPropagation(); |
| 403 } |
| 404 }, |
| 405 |
| 406 _onIronResize: function() { |
| 407 if (this._squelchNextResize) { |
| 408 this._squelchNextResize = false; |
| 409 return; |
| 410 } |
| 411 if (this.opened) { |
| 412 this.refit(); |
| 413 } |
| 414 } |
| 415 |
| 416 }; |
| 417 |
| 418 /** @polymerBehavior */ |
| 419 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; |
| 420 |
| 421 /* |
| 422 * Fired after the `iron-overlay` opens. |
| 423 * @event iron-overlay-opened |
| 424 */ |
| 425 |
| 426 /* |
| 427 * Fired after the `iron-overlay` closes. |
| 428 * @event iron-overlay-closed {{canceled: boolean}} detail - |
| 429 * canceled: True if the overlay was canceled. |
| 430 */ |
| 431 |
| 432 </script> |
OLD | NEW |