2 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
3 on top of other content. It includes an optional backdrop, and can be used to implement a variety
4 of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once.
6 ### Closing and canceling
8 A dialog may be hidden by closing or canceling. The difference between close and cancel is user
9 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
10 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
11 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties.
12 `close()` should be called explicitly by the implementer when the user interacts with a control
13 in the overlay element.
17 By default the element is sized and positioned to fit and centered inside the window. You can
18 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
22 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
23 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
28 The element is styled to appear on top of other content by setting its `z-index` property. You
29 must ensure no element has a stacking context with a higher `z-index` than its parent stacking
30 context. You should place this element as a child of `<body>` whenever possible.
33 @polymerBehavior Polymer.IronOverlayBehavior
36 Polymer.IronOverlayBehaviorImpl = {
41 * True if the overlay is currently displayed.
44 observer: '_openedChanged',
51 * True if the overlay was canceled when it was last closed.
54 observer: '_canceledChanged',
61 * Set to true to display a backdrop behind the overlay.
69 * Set to true to disable auto-focusing the overlay or child nodes with
70 * the `autofocus` attribute` when the overlay is opened.
78 * Set to true to disable canceling the overlay with the ESC key.
86 * Set to true to disable canceling the overlay by clicking outside it.
88 noCancelOnOutsideClick: {
94 * Returns the reason this dialog was last closed.
97 // was a getter before, but needs to be a property so other
98 // behaviors can override this.
104 value: Polymer.IronOverlayManager
107 _boundOnCaptureClick: {
110 return this._onCaptureClick.bind(this);
114 _boundOnCaptureKeydown: {
117 return this._onCaptureKeydown.bind(this);
124 * Fired after the `iron-overlay` opens.
125 * @event iron-overlay-opened
129 * Fired after the `iron-overlay` closes.
130 * @event iron-overlay-closed {{canceled: boolean}} detail -
131 * canceled: True if the overlay was canceled.
136 'iron-resize': '_onIronResize'
140 * The backdrop element.
143 get backdropElement() {
144 return this._backdrop;
148 return Polymer.dom(this).querySelector('[autofocus]') || this;
151 registered: function() {
152 this._backdrop = document.createElement('iron-overlay-backdrop');
157 if (this._callOpenedWhenReady) {
158 this._openedChanged();
162 detached: function() {
164 this._completeBackdrop();
165 this._manager.removeOverlay(this);
169 * Toggle the opened state of the overlay.
172 this.opened = !this.opened;
180 this.closingReason = {canceled: false};
188 this._setCanceled(false);
192 * Cancels the overlay.
196 this._setCanceled(true);
199 _ensureSetup: function() {
200 if (this._overlaySetup) {
203 this._overlaySetup = true;
204 this.style.outline = 'none';
205 this.style.display = 'none';
208 _openedChanged: function() {
210 this.removeAttribute('aria-hidden');
212 this.setAttribute('aria-hidden', 'true');
215 // wait to call after ready only if we're initially open
216 if (!this._overlaySetup) {
217 this._callOpenedWhenReady = this.opened;
220 if (this._openChangedAsync) {
221 this.cancelAsync(this._openChangedAsync);
224 this._toggleListeners();
227 this._prepareRenderOpened();
230 // async here to allow overlay layer to become visible.
231 this._openChangedAsync = this.async(function() {
232 // overlay becomes visible here
233 this.style.display = '';
234 // force layout to ensure transitions will go
237 this._renderOpened();
239 this._renderClosed();
241 this._openChangedAsync = null;
246 _canceledChanged: function() {
247 this.closingReason = this.closingReason || {};
248 this.closingReason.canceled = this.canceled;
251 _toggleListener: function(enable, node, event, boundListener, capture) {
253 node.addEventListener(event, boundListener, capture);
255 node.removeEventListener(event, boundListener, capture);
259 _toggleListeners: function() {
260 if (this._toggleListenersAsync) {
261 this.cancelAsync(this._toggleListenersAsync);
263 // async so we don't auto-close immediately via a click.
264 this._toggleListenersAsync = this.async(function() {
265 this._toggleListener(this.opened, document, 'click', this._boundOnCaptureClick, true);
266 this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true);
267 this._toggleListenersAsync = null;
271 // tasks which must occur before opening; e.g. making the element visible
272 _prepareRenderOpened: function() {
273 this._manager.addOverlay(this);
275 if (this.withBackdrop) {
276 this.backdropElement.prepare();
277 this._manager.trackBackdrop(this);
280 this._preparePositioning();
282 this._finishPositioning();
285 // tasks which cause the overlay to actually open; typically play an
287 _renderOpened: function() {
288 if (this.withBackdrop) {
289 this.backdropElement.open();
291 this._finishRenderOpened();
294 _renderClosed: function() {
295 if (this.withBackdrop) {
296 this.backdropElement.close();
298 this._finishRenderClosed();
301 _onTransitionend: function(event) {
302 // make sure this is our transition event.
303 if (event && event.target !== this) {
307 this._finishRenderOpened();
309 this._finishRenderClosed();
313 _finishRenderOpened: function() {
314 // focus the child node with [autofocus]
315 if (!this.noAutoFocus) {
316 this._focusNode.focus();
319 this.fire('iron-overlay-opened');
321 this._squelchNextResize = true;
322 this.async(this.notifyResize);
325 _finishRenderClosed: function() {
326 // hide the overlay and remove the backdrop
328 this.style.display = 'none';
329 this._completeBackdrop();
330 this._manager.removeOverlay(this);
332 this._focusNode.blur();
333 // focus the next overlay, if there is one
334 this._manager.focusOverlay();
336 this.fire('iron-overlay-closed', this.closingReason);
338 this._squelchNextResize = true;
339 this.async(this.notifyResize);
342 _completeBackdrop: function() {
343 if (this.withBackdrop) {
344 this._manager.trackBackdrop(this);
345 this.backdropElement.complete();
349 _preparePositioning: function() {
350 this.style.transition = this.style.webkitTransition = 'none';
351 this.style.transform = this.style.webkitTransform = 'none';
352 this.style.display = '';
355 _finishPositioning: function() {
356 this.style.display = 'none';
357 this.style.transform = this.style.webkitTransform = '';
358 // force layout to avoid application of transform
360 this.style.transition = this.style.webkitTransition = '';
363 _applyFocus: function() {
365 if (!this.noAutoFocus) {
366 this._focusNode.focus();
369 this._focusNode.blur();
370 this._manager.focusOverlay();
374 _onCaptureClick: function(event) {
375 // attempt to close asynchronously and prevent the close of a tap event is immediately heard
376 // on target. This is because in shadow dom due to event retargetting event.target is not
378 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
379 this._cancelJob = this.async(function() {
385 _onClick: function(event) {
386 if (this._cancelJob) {
387 this.cancelAsync(this._cancelJob);
388 this._cancelJob = null;
392 _onCaptureKeydown: function(event) {
394 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
396 event.stopPropagation();
400 _onIronResize: function() {
401 if (this._squelchNextResize) {
402 this._squelchNextResize = false;
412 /** @polymerBehavior */
413 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];