4 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
5 on top of other content. It includes an optional backdrop, and can be used to implement a variety
6 of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once.
8 ### Closing and canceling
10 A dialog may be hidden by closing or canceling. The difference between close and cancel is user
11 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
12 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
13 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties.
14 `close()` should be called explicitly by the implementer when the user interacts with a control
15 in the overlay element.
19 By default the element is sized and positioned to fit and centered inside the window. You can
20 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
24 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
25 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
30 The element is styled to appear on top of other content by setting its `z-index` property. You
31 must ensure no element has a stacking context with a higher `z-index` than its parent stacking
32 context. You should place this element as a child of `<body>` whenever possible.
35 @polymerBehavior Polymer.IronOverlayBehavior
38 Polymer.IronOverlayBehaviorImpl = {
43 * True if the overlay is currently displayed.
46 observer: '_openedChanged',
53 * True if the overlay was canceled when it was last closed.
56 observer: '_canceledChanged',
63 * Set to true to display a backdrop behind the overlay.
71 * Set to true to disable auto-focusing the overlay or child nodes with
72 * the `autofocus` attribute` when the overlay is opened.
80 * Set to true to disable canceling the overlay with the ESC key.
88 * Set to true to disable canceling the overlay by clicking outside it.
90 noCancelOnOutsideClick: {
96 * Returns the reason this dialog was last closed.
99 // was a getter before, but needs to be a property so other
100 // behaviors can override this.
106 value: Polymer.IronOverlayManager
109 _boundOnCaptureClick: {
112 return this._onCaptureClick.bind(this);
116 _boundOnCaptureKeydown: {
119 return this._onCaptureKeydown.bind(this);
126 * Fired after the `iron-overlay` opens.
127 * @event iron-overlay-opened
131 * Fired after the `iron-overlay` closes.
132 * @event iron-overlay-closed {{canceled: boolean}} detail -
133 * canceled: True if the overlay was canceled.
138 'iron-resize': '_onIronResize'
142 * The backdrop element.
145 get backdropElement() {
146 return this._backdrop;
150 return Polymer.dom(this).querySelector('[autofocus]') || this;
153 registered: function() {
154 this._backdrop = document.createElement('iron-overlay-backdrop');
159 if (this._callOpenedWhenReady) {
160 this._openedChanged();
164 detached: function() {
166 this._completeBackdrop();
167 this._manager.removeOverlay(this);
171 * Toggle the opened state of the overlay.
174 this.opened = !this.opened;
182 this.closingReason = {canceled: false};
190 this._setCanceled(false);
194 * Cancels the overlay.
198 this._setCanceled(true);
201 _ensureSetup: function() {
202 if (this._overlaySetup) {
205 this._overlaySetup = true;
206 this.style.outline = 'none';
207 this.style.display = 'none';
210 _openedChanged: function() {
212 this.removeAttribute('aria-hidden');
214 this.setAttribute('aria-hidden', 'true');
217 // wait to call after ready only if we're initially open
218 if (!this._overlaySetup) {
219 this._callOpenedWhenReady = this.opened;
222 if (this._openChangedAsync) {
223 this.cancelAsync(this._openChangedAsync);
226 this._toggleListeners();
229 this._prepareRenderOpened();
232 // async here to allow overlay layer to become visible.
233 this._openChangedAsync = this.async(function() {
234 // overlay becomes visible here
235 this.style.display = '';
236 // force layout to ensure transitions will go
239 this._renderOpened();
241 this._renderClosed();
243 this._openChangedAsync = null;
248 _canceledChanged: function() {
249 this.closingReason = this.closingReason || {};
250 this.closingReason.canceled = this.canceled;
253 _toggleListener: function(enable, node, event, boundListener, capture) {
255 node.addEventListener(event, boundListener, capture);
257 node.removeEventListener(event, boundListener, capture);
261 _toggleListeners: function() {
262 if (this._toggleListenersAsync) {
263 this.cancelAsync(this._toggleListenersAsync);
265 // async so we don't auto-close immediately via a click.
266 this._toggleListenersAsync = this.async(function() {
267 this._toggleListener(this.opened, document, 'click', this._boundOnCaptureClick, true);
268 this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true);
269 this._toggleListenersAsync = null;
273 // tasks which must occur before opening; e.g. making the element visible
274 _prepareRenderOpened: function() {
275 this._manager.addOverlay(this);
277 if (this.withBackdrop) {
278 this.backdropElement.prepare();
279 this._manager.trackBackdrop(this);
282 this._preparePositioning();
284 this._finishPositioning();
287 // tasks which cause the overlay to actually open; typically play an
289 _renderOpened: function() {
290 if (this.withBackdrop) {
291 this.backdropElement.open();
293 this._finishRenderOpened();
296 _renderClosed: function() {
297 if (this.withBackdrop) {
298 this.backdropElement.close();
300 this._finishRenderClosed();
303 _onTransitionend: function(event) {
304 // make sure this is our transition event.
305 if (event && event.target !== this) {
309 this._finishRenderOpened();
311 this._finishRenderClosed();
315 _finishRenderOpened: function() {
316 // focus the child node with [autofocus]
317 if (!this.noAutoFocus) {
318 this._focusNode.focus();
321 this.fire('iron-overlay-opened');
323 this._squelchNextResize = true;
324 this.async(this.notifyResize);
327 _finishRenderClosed: function() {
328 // hide the overlay and remove the backdrop
330 this.style.display = 'none';
331 this._completeBackdrop();
332 this._manager.removeOverlay(this);
334 this._focusNode.blur();
335 // focus the next overlay, if there is one
336 this._manager.focusOverlay();
338 this.fire('iron-overlay-closed', this.closingReason);
340 this._squelchNextResize = true;
341 this.async(this.notifyResize);
344 _completeBackdrop: function() {
345 if (this.withBackdrop) {
346 this._manager.trackBackdrop(this);
347 this.backdropElement.complete();
351 _preparePositioning: function() {
352 this.style.transition = this.style.webkitTransition = 'none';
353 this.style.transform = this.style.webkitTransform = 'none';
354 this.style.display = '';
357 _finishPositioning: function() {
358 this.style.display = 'none';
359 this.style.transform = this.style.webkitTransform = '';
360 // force layout to avoid application of transform
362 this.style.transition = this.style.webkitTransition = '';
365 _applyFocus: function() {
367 if (!this.noAutoFocus) {
368 this._focusNode.focus();
371 this._focusNode.blur();
372 this._manager.focusOverlay();
376 _onCaptureClick: function(event) {
377 // attempt to close asynchronously and prevent the close of a tap event is immediately heard
378 // on target. This is because in shadow dom due to event retargetting event.target is not
380 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
381 this._cancelJob = this.async(function() {
387 _onClick: function(event) {
388 if (this._cancelJob) {
389 this.cancelAsync(this._cancelJob);
390 this._cancelJob = null;
394 _onCaptureKeydown: function(event) {
396 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
398 event.stopPropagation();
402 _onIronResize: function() {
403 if (this._squelchNextResize) {
404 this._squelchNextResize = false;
414 /** @polymerBehavior */
415 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];