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.github.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/CONTRIBUTORS.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/PATENTS.txt
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">
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 implement a variety
22 of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once.
24 ### Closing and canceling
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.
35 By default the element is sized and positioned to fit and centered inside the window. You can
36 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
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
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 parent stacking
48 context. You should place this element as a child of `<body>` whenever possible.
51 @polymerBehavior Polymer.IronOverlayBehavior
54 Polymer
.IronOverlayBehaviorImpl
= {
59 * True if the overlay is currently displayed.
62 observer
: '_openedChanged',
69 * True if the overlay was canceled when it was last closed.
72 observer
: '_canceledChanged',
79 * Set to true to display a backdrop behind the overlay.
87 * Set to true to disable auto-focusing the overlay or child nodes with
88 * the `autofocus` attribute` when the overlay is opened.
96 * Set to true to disable canceling the overlay with the ESC key.
104 * Set to true to disable canceling the overlay by clicking outside it.
106 noCancelOnOutsideClick
: {
112 * Returns the reason this dialog was last closed.
115 // was a getter before, but needs to be a property so other
116 // behaviors can override this.
122 value
: Polymer
.IronOverlayManager
125 _boundOnCaptureClick
: {
128 return this._onCaptureClick
.bind(this);
132 _boundOnCaptureKeydown
: {
135 return this._onCaptureKeydown
.bind(this);
142 * Fired after the `iron-overlay` opens.
143 * @event iron-overlay-opened
147 * Fired after the `iron-overlay` closes.
148 * @event iron-overlay-closed {{canceled: boolean}} detail -
149 * canceled: True if the overlay was canceled.
154 'iron-resize': '_onIronResize'
158 * The backdrop element.
161 get backdropElement() {
162 return this._backdrop
;
166 return Polymer
.dom(this).querySelector('[autofocus]') || this;
169 registered: function() {
170 this._backdrop
= document
.createElement('iron-overlay-backdrop');
175 if (this._callOpenedWhenReady
) {
176 this._openedChanged();
180 detached: function() {
182 this._completeBackdrop();
183 this._manager
.removeOverlay(this);
187 * Toggle the opened state of the overlay.
190 this.opened
= !this.opened
;
198 this.closingReason
= {canceled
: false};
206 this._setCanceled(false);
210 * Cancels the overlay.
214 this._setCanceled(true);
217 _ensureSetup: function() {
218 if (this._overlaySetup
) {
221 this._overlaySetup
= true;
222 this.style
.outline
= 'none';
223 this.style
.display
= 'none';
226 _openedChanged: function() {
228 this.removeAttribute('aria-hidden');
230 this.setAttribute('aria-hidden', 'true');
233 // wait to call after ready only if we're initially open
234 if (!this._overlaySetup
) {
235 this._callOpenedWhenReady
= this.opened
;
238 if (this._openChangedAsync
) {
239 this.cancelAsync(this._openChangedAsync
);
242 this._toggleListeners();
245 this._prepareRenderOpened();
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
255 this._renderOpened();
257 this._renderClosed();
259 this._openChangedAsync
= null;
264 _canceledChanged: function() {
265 this.closingReason
= this.closingReason
|| {};
266 this.closingReason
.canceled
= this.canceled
;
269 _toggleListener: function(enable
, node
, event
, boundListener
, capture
) {
271 node
.addEventListener(event
, boundListener
, capture
);
273 node
.removeEventListener(event
, boundListener
, capture
);
277 _toggleListeners: function() {
278 if (this._toggleListenersAsync
) {
279 this.cancelAsync(this._toggleListenersAsync
);
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._boundOnCaptureClick
, true);
284 this._toggleListener(this.opened
, document
, 'keydown', this._boundOnCaptureKeydown
, true);
285 this._toggleListenersAsync
= null;
289 // tasks which must occur before opening; e.g. making the element visible
290 _prepareRenderOpened: function() {
291 this._manager
.addOverlay(this);
293 if (this.withBackdrop
) {
294 this.backdropElement
.prepare();
295 this._manager
.trackBackdrop(this);
298 this._preparePositioning();
300 this._finishPositioning();
303 // tasks which cause the overlay to actually open; typically play an
305 _renderOpened: function() {
306 if (this.withBackdrop
) {
307 this.backdropElement
.open();
309 this._finishRenderOpened();
312 _renderClosed: function() {
313 if (this.withBackdrop
) {
314 this.backdropElement
.close();
316 this._finishRenderClosed();
319 _onTransitionend: function(event
) {
320 // make sure this is our transition event.
321 if (event
&& event
.target
!== this) {
325 this._finishRenderOpened();
327 this._finishRenderClosed();
331 _finishRenderOpened: function() {
332 // focus the child node with [autofocus]
333 if (!this.noAutoFocus
) {
334 this._focusNode
.focus();
337 this.fire('iron-overlay-opened');
339 this._squelchNextResize
= true;
340 this.async(this.notifyResize
);
343 _finishRenderClosed: function() {
344 // hide the overlay and remove the backdrop
346 this.style
.display
= 'none';
347 this._completeBackdrop();
348 this._manager
.removeOverlay(this);
350 this._focusNode
.blur();
351 // focus the next overlay, if there is one
352 this._manager
.focusOverlay();
354 this.fire('iron-overlay-closed', this.closingReason
);
356 this._squelchNextResize
= true;
357 this.async(this.notifyResize
);
360 _completeBackdrop: function() {
361 if (this.withBackdrop
) {
362 this._manager
.trackBackdrop(this);
363 this.backdropElement
.complete();
367 _preparePositioning: function() {
368 this.style
.transition
= this.style
.webkitTransition
= 'none';
369 this.style
.transform
= this.style
.webkitTransform
= 'none';
370 this.style
.display
= '';
373 _finishPositioning: function() {
374 this.style
.display
= 'none';
375 this.style
.transform
= this.style
.webkitTransform
= '';
376 // force layout to avoid application of transform
378 this.style
.transition
= this.style
.webkitTransition
= '';
381 _applyFocus: function() {
383 if (!this.noAutoFocus
) {
384 this._focusNode
.focus();
387 this._focusNode
.blur();
388 this._manager
.focusOverlay();
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 event.target is not
396 if (!this.noCancelOnOutsideClick
&& (this._manager
.currentOverlay() == this)) {
397 this._cancelJob
= this.async(function() {
403 _onClick: function(event
) {
404 if (this._cancelJob
) {
405 this.cancelAsync(this._cancelJob
);
406 this._cancelJob
= null;
410 _onCaptureKeydown: function(event
) {
412 if (!this.noCancelOnEscKey
&& (event
.keyCode
=== ESC
)) {
414 event
.stopPropagation();
418 _onIronResize: function() {
419 if (this._squelchNextResize
) {
420 this._squelchNextResize
= false;
430 /** @polymerBehavior */
431 Polymer
.IronOverlayBehavior
= [Polymer
.IronFitBehavior
, Polymer
.IronResizableBehavior
, Polymer
.IronOverlayBehaviorImpl
];