| 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 notify: true | |
| 66 }, | |
| 67 | |
| 68 /** | |
| 69 * True if the overlay was canceled when it was last closed. | |
| 70 */ | |
| 71 canceled: { | |
| 72 observer: '_canceledChanged', | |
| 73 readOnly: true, | |
| 74 type: Boolean, | |
| 75 value: false | |
| 76 }, | |
| 77 | |
| 78 /** | |
| 79 * Set to true to display a backdrop behind the overlay. | |
| 80 */ | |
| 81 withBackdrop: { | |
| 82 type: Boolean, | |
| 83 value: false | |
| 84 }, | |
| 85 | |
| 86 /** | |
| 87 * Set to true to disable auto-focusing the overlay or child nodes with | |
| 88 * the `autofocus` attribute` when the overlay is opened. | |
| 89 */ | |
| 90 noAutoFocus: { | |
| 91 type: Boolean, | |
| 92 value: false | |
| 93 }, | |
| 94 | |
| 95 /** | |
| 96 * Set to true to disable canceling the overlay with the ESC key. | |
| 97 */ | |
| 98 noCancelOnEscKey: { | |
| 99 type: Boolean, | |
| 100 value: false | |
| 101 }, | |
| 102 | |
| 103 /** | |
| 104 * Set to true to disable canceling the overlay by clicking outside it. | |
| 105 */ | |
| 106 noCancelOnOutsideClick: { | |
| 107 type: Boolean, | |
| 108 value: false | |
| 109 }, | |
| 110 | |
| 111 /** | |
| 112 * Returns the reason this dialog was last closed. | |
| 113 */ | |
| 114 closingReason: { | |
| 115 // was a getter before, but needs to be a property so other | |
| 116 // behaviors can override this. | |
| 117 type: Object | |
| 118 }, | |
| 119 | |
| 120 _manager: { | |
| 121 type: Object, | |
| 122 value: Polymer.IronOverlayManager | |
| 123 }, | |
| 124 | |
| 125 _boundOnCaptureClick: { | |
| 126 type: Function, | |
| 127 value: function() { | |
| 128 return this._onCaptureClick.bind(this); | |
| 129 } | |
| 130 }, | |
| 131 | |
| 132 _boundOnCaptureKeydown: { | |
| 133 type: Function, | |
| 134 value: function() { | |
| 135 return this._onCaptureKeydown.bind(this); | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 }, | |
| 140 | |
| 141 /** | |
| 142 * Fired after the `iron-overlay` opens. | |
| 143 * @event iron-overlay-opened | |
| 144 */ | |
| 145 | |
| 146 /** | |
| 147 * Fired after the `iron-overlay` closes. | |
| 148 * @event iron-overlay-closed {{canceled: boolean}} detail - | |
| 149 * canceled: True if the overlay was canceled. | |
| 150 */ | |
| 151 | |
| 152 listeners: { | |
| 153 'click': '_onClick', | |
| 154 'iron-resize': '_onIronResize' | |
| 155 }, | |
| 156 | |
| 157 /** | |
| 158 * The backdrop element. | |
| 159 * @type Node | |
| 160 */ | |
| 161 get backdropElement() { | |
| 162 return this._backdrop; | |
| 163 }, | |
| 164 | |
| 165 get _focusNode() { | |
| 166 return Polymer.dom(this).querySelector('[autofocus]') || this; | |
| 167 }, | |
| 168 | |
| 169 registered: function() { | |
| 170 this._backdrop = document.createElement('iron-overlay-backdrop'); | |
| 171 }, | |
| 172 | |
| 173 ready: function() { | |
| 174 this._ensureSetup(); | |
| 175 if (this._callOpenedWhenReady) { | |
| 176 this._openedChanged(); | |
| 177 } | |
| 178 }, | |
| 179 | |
| 180 detached: function() { | |
| 181 this.opened = false; | |
| 182 this._completeBackdrop(); | |
| 183 this._manager.removeOverlay(this); | |
| 184 }, | |
| 185 | |
| 186 /** | |
| 187 * Toggle the opened state of the overlay. | |
| 188 */ | |
| 189 toggle: function() { | |
| 190 this.opened = !this.opened; | |
| 191 }, | |
| 192 | |
| 193 /** | |
| 194 * Open the overlay. | |
| 195 */ | |
| 196 open: function() { | |
| 197 this.opened = true; | |
| 198 this.closingReason = {canceled: false}; | |
| 199 }, | |
| 200 | |
| 201 /** | |
| 202 * Close the overlay. | |
| 203 */ | |
| 204 close: function() { | |
| 205 this.opened = false; | |
| 206 this._setCanceled(false); | |
| 207 }, | |
| 208 | |
| 209 /** | |
| 210 * Cancels the overlay. | |
| 211 */ | |
| 212 cancel: function() { | |
| 213 this.opened = false, | |
| 214 this._setCanceled(true); | |
| 215 }, | |
| 216 | |
| 217 _ensureSetup: function() { | |
| 218 if (this._overlaySetup) { | |
| 219 return; | |
| 220 } | |
| 221 this._overlaySetup = true; | |
| 222 this.style.outline = 'none'; | |
| 223 this.style.display = 'none'; | |
| 224 }, | |
| 225 | |
| 226 _openedChanged: function() { | |
| 227 if (this.opened) { | |
| 228 this.removeAttribute('aria-hidden'); | |
| 229 } else { | |
| 230 this.setAttribute('aria-hidden', 'true'); | |
| 231 } | |
| 232 | |
| 233 // wait to call after ready only if we're initially open | |
| 234 if (!this._overlaySetup) { | |
| 235 this._callOpenedWhenReady = this.opened; | |
| 236 return; | |
| 237 } | |
| 238 if (this._openChangedAsync) { | |
| 239 this.cancelAsync(this._openChangedAsync); | |
| 240 } | |
| 241 | |
| 242 this._toggleListeners(); | |
| 243 | |
| 244 if (this.opened) { | |
| 245 this._prepareRenderOpened(); | |
| 246 } | |
| 247 | |
| 248 // async here to allow overlay layer to become visible. | |
| 249 this._openChangedAsync = this.async(function() { | |
| 250 // overlay becomes visible here | |
| 251 this.style.display = ''; | |
| 252 // force layout to ensure transitions will go | |
| 253 this.offsetWidth; | |
| 254 if (this.opened) { | |
| 255 this._renderOpened(); | |
| 256 } else { | |
| 257 this._renderClosed(); | |
| 258 } | |
| 259 this._openChangedAsync = null; | |
| 260 }); | |
| 261 | |
| 262 }, | |
| 263 | |
| 264 _canceledChanged: function() { | |
| 265 this.closingReason = this.closingReason || {}; | |
| 266 this.closingReason.canceled = this.canceled; | |
| 267 }, | |
| 268 | |
| 269 _toggleListener: function(enable, node, event, boundListener, capture) { | |
| 270 if (enable) { | |
| 271 node.addEventListener(event, boundListener, capture); | |
| 272 } else { | |
| 273 node.removeEventListener(event, boundListener, capture); | |
| 274 } | |
| 275 }, | |
| 276 | |
| 277 _toggleListeners: function() { | |
| 278 if (this._toggleListenersAsync) { | |
| 279 this.cancelAsync(this._toggleListenersAsync); | |
| 280 } | |
| 281 // async so we don't auto-close immediately via a click. | |
| 282 this._toggleListenersAsync = this.async(function() { | |
| 283 this._toggleListener(this.opened, document, 'click', this._boundOnCaptur
eClick, true); | |
| 284 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt
ureKeydown, true); | |
| 285 this._toggleListenersAsync = null; | |
| 286 }); | |
| 287 }, | |
| 288 | |
| 289 // tasks which must occur before opening; e.g. making the element visible | |
| 290 _prepareRenderOpened: function() { | |
| 291 this._manager.addOverlay(this); | |
| 292 | |
| 293 if (this.withBackdrop) { | |
| 294 this.backdropElement.prepare(); | |
| 295 this._manager.trackBackdrop(this); | |
| 296 } | |
| 297 | |
| 298 this._preparePositioning(); | |
| 299 this.fit(); | |
| 300 this._finishPositioning(); | |
| 301 }, | |
| 302 | |
| 303 // tasks which cause the overlay to actually open; typically play an | |
| 304 // animation | |
| 305 _renderOpened: function() { | |
| 306 if (this.withBackdrop) { | |
| 307 this.backdropElement.open(); | |
| 308 } | |
| 309 this._finishRenderOpened(); | |
| 310 }, | |
| 311 | |
| 312 _renderClosed: function() { | |
| 313 if (this.withBackdrop) { | |
| 314 this.backdropElement.close(); | |
| 315 } | |
| 316 this._finishRenderClosed(); | |
| 317 }, | |
| 318 | |
| 319 _onTransitionend: function(event) { | |
| 320 // make sure this is our transition event. | |
| 321 if (event && event.target !== this) { | |
| 322 return; | |
| 323 } | |
| 324 if (this.opened) { | |
| 325 this._finishRenderOpened(); | |
| 326 } else { | |
| 327 this._finishRenderClosed(); | |
| 328 } | |
| 329 }, | |
| 330 | |
| 331 _finishRenderOpened: function() { | |
| 332 // focus the child node with [autofocus] | |
| 333 if (!this.noAutoFocus) { | |
| 334 this._focusNode.focus(); | |
| 335 } | |
| 336 | |
| 337 this.fire('iron-overlay-opened'); | |
| 338 | |
| 339 this._squelchNextResize = true; | |
| 340 this.async(this.notifyResize); | |
| 341 }, | |
| 342 | |
| 343 _finishRenderClosed: function() { | |
| 344 // hide the overlay and remove the backdrop | |
| 345 this.resetFit(); | |
| 346 this.style.display = 'none'; | |
| 347 this._completeBackdrop(); | |
| 348 this._manager.removeOverlay(this); | |
| 349 | |
| 350 this._focusNode.blur(); | |
| 351 // focus the next overlay, if there is one | |
| 352 this._manager.focusOverlay(); | |
| 353 | |
| 354 this.fire('iron-overlay-closed', this.closingReason); | |
| 355 | |
| 356 this._squelchNextResize = true; | |
| 357 this.async(this.notifyResize); | |
| 358 }, | |
| 359 | |
| 360 _completeBackdrop: function() { | |
| 361 if (this.withBackdrop) { | |
| 362 this._manager.trackBackdrop(this); | |
| 363 this.backdropElement.complete(); | |
| 364 } | |
| 365 }, | |
| 366 | |
| 367 _preparePositioning: function() { | |
| 368 this.style.transition = this.style.webkitTransition = 'none'; | |
| 369 this.style.transform = this.style.webkitTransform = 'none'; | |
| 370 this.style.display = ''; | |
| 371 }, | |
| 372 | |
| 373 _finishPositioning: function() { | |
| 374 this.style.display = 'none'; | |
| 375 this.style.transform = this.style.webkitTransform = ''; | |
| 376 // force layout to avoid application of transform | |
| 377 this.offsetWidth; | |
| 378 this.style.transition = this.style.webkitTransition = ''; | |
| 379 }, | |
| 380 | |
| 381 _applyFocus: function() { | |
| 382 if (this.opened) { | |
| 383 if (!this.noAutoFocus) { | |
| 384 this._focusNode.focus(); | |
| 385 } | |
| 386 } else { | |
| 387 this._focusNode.blur(); | |
| 388 this._manager.focusOverlay(); | |
| 389 } | |
| 390 }, | |
| 391 | |
| 392 _onCaptureClick: function(event) { | |
| 393 // attempt to close asynchronously and prevent the close of a tap event is
immediately heard | |
| 394 // on target. This is because in shadow dom due to event retargetting even
t.target is not | |
| 395 // useful. | |
| 396 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi
s)) { | |
| 397 this._cancelJob = this.async(function() { | |
| 398 this.cancel(); | |
| 399 }, 10); | |
| 400 } | |
| 401 }, | |
| 402 | |
| 403 _onClick: function(event) { | |
| 404 if (this._cancelJob) { | |
| 405 this.cancelAsync(this._cancelJob); | |
| 406 this._cancelJob = null; | |
| 407 } | |
| 408 }, | |
| 409 | |
| 410 _onCaptureKeydown: function(event) { | |
| 411 var ESC = 27; | |
| 412 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { | |
| 413 this.cancel(); | |
| 414 event.stopPropagation(); | |
| 415 } | |
| 416 }, | |
| 417 | |
| 418 _onIronResize: function() { | |
| 419 if (this._squelchNextResize) { | |
| 420 this._squelchNextResize = false; | |
| 421 return; | |
| 422 } | |
| 423 if (this.opened) { | |
| 424 this.refit(); | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 }; | |
| 429 | |
| 430 /** @polymerBehavior */ | |
| 431 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; | |
| 432 | |
| 433 | |
| 434 </script> | |
| OLD | NEW |