Fix OOP <webview> resize and autosize.
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-overlay-behavior / iron-overlay-behavior-extracted.js
blobeaf45191f6fc75a632695305c9cfc6217b8be05b
1 /**
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.
15 ### Positioning
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`.
20 ### Backdrop
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
24 options.
26 ### Limitations
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.
32 @demo demo/index.html
33 @polymerBehavior Polymer.IronOverlayBehavior
36   Polymer.IronOverlayBehaviorImpl = {
38     properties: {
40       /**
41        * True if the overlay is currently displayed.
42        */
43       opened: {
44         observer: '_openedChanged',
45         type: Boolean,
46         value: false,
47         notify: true
48       },
50       /**
51        * True if the overlay was canceled when it was last closed.
52        */
53       canceled: {
54         observer: '_canceledChanged',
55         readOnly: true,
56         type: Boolean,
57         value: false
58       },
60       /**
61        * Set to true to display a backdrop behind the overlay.
62        */
63       withBackdrop: {
64         type: Boolean,
65         value: false
66       },
68       /**
69        * Set to true to disable auto-focusing the overlay or child nodes with
70        * the `autofocus` attribute` when the overlay is opened.
71        */
72       noAutoFocus: {
73         type: Boolean,
74         value: false
75       },
77       /**
78        * Set to true to disable canceling the overlay with the ESC key.
79        */
80       noCancelOnEscKey: {
81         type: Boolean,
82         value: false
83       },
85       /**
86        * Set to true to disable canceling the overlay by clicking outside it.
87        */
88       noCancelOnOutsideClick: {
89         type: Boolean,
90         value: false
91       },
93       /**
94        * Returns the reason this dialog was last closed.
95        */
96       closingReason: {
97         // was a getter before, but needs to be a property so other
98         // behaviors can override this.
99         type: Object
100       },
102       _manager: {
103         type: Object,
104         value: Polymer.IronOverlayManager
105       },
107       _boundOnCaptureClick: {
108         type: Function,
109         value: function() {
110           return this._onCaptureClick.bind(this);
111         }
112       },
114       _boundOnCaptureKeydown: {
115         type: Function,
116         value: function() {
117           return this._onCaptureKeydown.bind(this);
118         }
119       }
121     },
123     listeners: {
124       'click': '_onClick',
125       'iron-resize': '_onIronResize'
126     },
128     /**
129      * The backdrop element.
130      * @type Node
131      */
132     get backdropElement() {
133       return this._backdrop;
134     },
136     get _focusNode() {
137       return Polymer.dom(this).querySelector('[autofocus]') || this;
138     },
140     registered: function() {
141       this._backdrop = document.createElement('iron-overlay-backdrop');
142     },
144     ready: function() {
145       this._ensureSetup();
146       if (this._callOpenedWhenReady) {
147         this._openedChanged();
148       }
149     },
151     detached: function() {
152       this.opened = false;
153       this._completeBackdrop();
154       this._manager.removeOverlay(this);
155     },
157     /**
158      * Toggle the opened state of the overlay.
159      */
160     toggle: function() {
161       this.opened = !this.opened;
162     },
164     /**
165      * Open the overlay.
166      */
167     open: function() {
168       this.opened = true;
169       this.closingReason = {canceled: false};
170     },
172     /**
173      * Close the overlay.
174      */
175     close: function() {
176       this.opened = false;
177       this._setCanceled(false);
178     },
180     /**
181      * Cancels the overlay.
182      */
183     cancel: function() {
184       this.opened = false,
185       this._setCanceled(true);
186     },
188     _ensureSetup: function() {
189       if (this._overlaySetup) {
190         return;
191       }
192       this._overlaySetup = true;
193       this.style.outline = 'none';
194       this.style.display = 'none';
195     },
197     _openedChanged: function() {
198       if (this.opened) {
199         this.removeAttribute('aria-hidden');
200       } else {
201         this.setAttribute('aria-hidden', 'true');
202       }
204       // wait to call after ready only if we're initially open
205       if (!this._overlaySetup) {
206         this._callOpenedWhenReady = this.opened;
207         return;
208       }
209       if (this._openChangedAsync) {
210         this.cancelAsync(this._openChangedAsync);
211       }
213       this._toggleListeners();
215       if (this.opened) {
216         this._prepareRenderOpened();
217       }
219       // async here to allow overlay layer to become visible.
220       this._openChangedAsync = this.async(function() {
221         // overlay becomes visible here
222         this.style.display = '';
223         // force layout to ensure transitions will go
224         /** @suppress {suspiciousCode} */ this.offsetWidth;
225         if (this.opened) {
226           this._renderOpened();
227         } else {
228           this._renderClosed();
229         }
230         this._openChangedAsync = null;
231       });
233     },
235     _canceledChanged: function() {
236       this.closingReason = this.closingReason || {};
237       this.closingReason.canceled = this.canceled;
238     },
240     _toggleListener: function(enable, node, event, boundListener, capture) {
241       if (enable) {
242         node.addEventListener(event, boundListener, capture);
243       } else {
244         node.removeEventListener(event, boundListener, capture);
245       }
246     },
248     _toggleListeners: function() {
249       if (this._toggleListenersAsync) {
250         this.cancelAsync(this._toggleListenersAsync);
251       }
252       // async so we don't auto-close immediately via a click.
253       this._toggleListenersAsync = this.async(function() {
254         this._toggleListener(this.opened, document, 'click', this._boundOnCaptureClick, true);
255         this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true);
256         this._toggleListenersAsync = null;
257       });
258     },
260     // tasks which must occur before opening; e.g. making the element visible
261     _prepareRenderOpened: function() {
262       this._manager.addOverlay(this);
264       if (this.withBackdrop) {
265         this.backdropElement.prepare();
266         this._manager.trackBackdrop(this);
267       }
269       this._preparePositioning();
270       this.fit();
271       this._finishPositioning();
272     },
274     // tasks which cause the overlay to actually open; typically play an
275     // animation
276     _renderOpened: function() {
277       if (this.withBackdrop) {
278         this.backdropElement.open();
279       }
280       this._finishRenderOpened();
281     },
283     _renderClosed: function() {
284       if (this.withBackdrop) {
285         this.backdropElement.close();
286       }
287       this._finishRenderClosed();
288     },
290     _onTransitionend: function(event) {
291       // make sure this is our transition event.
292       if (event && event.target !== this) {
293         return;
294       }
295       if (this.opened) {
296         this._finishRenderOpened();
297       } else {
298         this._finishRenderClosed();
299       }
300     },
302     _finishRenderOpened: function() {
303       // focus the child node with [autofocus]
304       if (!this.noAutoFocus) {
305         this._focusNode.focus();
306       }
308       this.fire('iron-overlay-opened');
310       this._squelchNextResize = true;
311       this.async(this.notifyResize);
312     },
314     _finishRenderClosed: function() {
315       // hide the overlay and remove the backdrop
316       this.resetFit();
317       this.style.display = 'none';
318       this._completeBackdrop();
319       this._manager.removeOverlay(this);
321       this._focusNode.blur();
322       // focus the next overlay, if there is one
323       this._manager.focusOverlay();
325       this.fire('iron-overlay-closed', this.closingReason);
327       this._squelchNextResize = true;
328       this.async(this.notifyResize);
329     },
331     _completeBackdrop: function() {
332       if (this.withBackdrop) {
333         this._manager.trackBackdrop(this);
334         this.backdropElement.complete();
335       }
336     },
338     _preparePositioning: function() {
339       this.style.transition = this.style.webkitTransition = 'none';
340       this.style.transform = this.style.webkitTransform = 'none';
341       this.style.display = '';
342     },
344     _finishPositioning: function() {
345       this.style.display = 'none';
346       this.style.transform = this.style.webkitTransform = '';
347       // force layout to avoid application of transform
348       /** @suppress {suspiciousCode} */ this.offsetWidth;
349       this.style.transition = this.style.webkitTransition = '';
350     },
352     _applyFocus: function() {
353       if (this.opened) {
354         if (!this.noAutoFocus) {
355           this._focusNode.focus();
356         }
357       } else {
358         this._focusNode.blur();
359         this._manager.focusOverlay();
360       }
361     },
363     _onCaptureClick: function(event) {
364       // attempt to close asynchronously and prevent the close of a tap event is immediately heard
365       // on target. This is because in shadow dom due to event retargetting event.target is not
366       // useful.
367       if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
368         this._cancelJob = this.async(function() {
369           this.cancel();
370         }, 10);
371       }
372     },
374     _onClick: function(event) {
375       if (this._cancelJob) {
376         this.cancelAsync(this._cancelJob);
377         this._cancelJob = null;
378       }
379     },
381     _onCaptureKeydown: function(event) {
382       var ESC = 27;
383       if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
384         this.cancel();
385         event.stopPropagation();
386       }
387     },
389     _onIronResize: function() {
390       if (this._squelchNextResize) {
391         this._squelchNextResize = false;
392         return;
393       }
394       if (this.opened) {
395         this.refit();
396       }
397     }
400  * Fired after the `iron-overlay` opens.
401  * @event iron-overlay-opened
402  */
405  * Fired after the `iron-overlay` closes.
406  * @event iron-overlay-closed
407  * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
408  */
409   };
411   /** @polymerBehavior */
412   Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];