Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-overlay-behavior / iron-overlay-behavior-extracted.js
blob586f7ceb8f5c98c762a3167c39c635e93a8b428e
3 /**
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.
17 ### Positioning
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`.
22 ### Backdrop
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
26 options.
28 ### Limitations
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.
34 @demo demo/index.html
35 @polymerBehavior Polymer.IronOverlayBehavior
38   Polymer.IronOverlayBehaviorImpl = {
40     properties: {
42       /**
43        * True if the overlay is currently displayed.
44        */
45       opened: {
46         observer: '_openedChanged',
47         type: Boolean,
48         value: false,
49         notify: true
50       },
52       /**
53        * True if the overlay was canceled when it was last closed.
54        */
55       canceled: {
56         observer: '_canceledChanged',
57         readOnly: true,
58         type: Boolean,
59         value: false
60       },
62       /**
63        * Set to true to display a backdrop behind the overlay.
64        */
65       withBackdrop: {
66         type: Boolean,
67         value: false
68       },
70       /**
71        * Set to true to disable auto-focusing the overlay or child nodes with
72        * the `autofocus` attribute` when the overlay is opened.
73        */
74       noAutoFocus: {
75         type: Boolean,
76         value: false
77       },
79       /**
80        * Set to true to disable canceling the overlay with the ESC key.
81        */
82       noCancelOnEscKey: {
83         type: Boolean,
84         value: false
85       },
87       /**
88        * Set to true to disable canceling the overlay by clicking outside it.
89        */
90       noCancelOnOutsideClick: {
91         type: Boolean,
92         value: false
93       },
95       /**
96        * Returns the reason this dialog was last closed.
97        */
98       closingReason: {
99         // was a getter before, but needs to be a property so other
100         // behaviors can override this.
101         type: Object
102       },
104       _manager: {
105         type: Object,
106         value: Polymer.IronOverlayManager
107       },
109       _boundOnCaptureClick: {
110         type: Function,
111         value: function() {
112           return this._onCaptureClick.bind(this);
113         }
114       },
116       _boundOnCaptureKeydown: {
117         type: Function,
118         value: function() {
119           return this._onCaptureKeydown.bind(this);
120         }
121       }
123     },
126  * Fired after the `iron-overlay` opens.
127  * @event iron-overlay-opened
128  */
131  * Fired after the `iron-overlay` closes.
132  * @event iron-overlay-closed {{canceled: boolean}} detail -
133  *     canceled: True if the overlay was canceled.
134  */
136     listeners: {
137       'click': '_onClick',
138       'iron-resize': '_onIronResize'
139     },
141     /**
142      * The backdrop element.
143      * @type Node
144      */
145     get backdropElement() {
146       return this._backdrop;
147     },
149     get _focusNode() {
150       return Polymer.dom(this).querySelector('[autofocus]') || this;
151     },
153     registered: function() {
154       this._backdrop = document.createElement('iron-overlay-backdrop');
155     },
157     ready: function() {
158       this._ensureSetup();
159       if (this._callOpenedWhenReady) {
160         this._openedChanged();
161       }
162     },
164     detached: function() {
165       this.opened = false;
166       this._completeBackdrop();
167       this._manager.removeOverlay(this);
168     },
170     /**
171      * Toggle the opened state of the overlay.
172      */
173     toggle: function() {
174       this.opened = !this.opened;
175     },
177     /**
178      * Open the overlay.
179      */
180     open: function() {
181       this.opened = true;
182       this.closingReason = {canceled: false};
183     },
185     /**
186      * Close the overlay.
187      */
188     close: function() {
189       this.opened = false;
190       this._setCanceled(false);
191     },
193     /**
194      * Cancels the overlay.
195      */
196     cancel: function() {
197       this.opened = false,
198       this._setCanceled(true);
199     },
201     _ensureSetup: function() {
202       if (this._overlaySetup) {
203         return;
204       }
205       this._overlaySetup = true;
206       this.style.outline = 'none';
207       this.style.display = 'none';
208     },
210     _openedChanged: function() {
211       if (this.opened) {
212         this.removeAttribute('aria-hidden');
213       } else {
214         this.setAttribute('aria-hidden', 'true');
215       }
217       // wait to call after ready only if we're initially open
218       if (!this._overlaySetup) {
219         this._callOpenedWhenReady = this.opened;
220         return;
221       }
222       if (this._openChangedAsync) {
223         this.cancelAsync(this._openChangedAsync);
224       }
226       this._toggleListeners();
228       if (this.opened) {
229         this._prepareRenderOpened();
230       }
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
237         this.offsetWidth;
238         if (this.opened) {
239           this._renderOpened();
240         } else {
241           this._renderClosed();
242         }
243         this._openChangedAsync = null;
244       });
246     },
248     _canceledChanged: function() {
249       this.closingReason = this.closingReason || {};
250       this.closingReason.canceled = this.canceled;
251     },
253     _toggleListener: function(enable, node, event, boundListener, capture) {
254       if (enable) {
255         node.addEventListener(event, boundListener, capture);
256       } else {
257         node.removeEventListener(event, boundListener, capture);
258       }
259     },
261     _toggleListeners: function() {
262       if (this._toggleListenersAsync) {
263         this.cancelAsync(this._toggleListenersAsync);
264       }
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;
270       });
271     },
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);
280       }
282       this._preparePositioning();
283       this.fit();
284       this._finishPositioning();
285     },
287     // tasks which cause the overlay to actually open; typically play an
288     // animation
289     _renderOpened: function() {
290       if (this.withBackdrop) {
291         this.backdropElement.open();
292       }
293       this._finishRenderOpened();
294     },
296     _renderClosed: function() {
297       if (this.withBackdrop) {
298         this.backdropElement.close();
299       }
300       this._finishRenderClosed();
301     },
303     _onTransitionend: function(event) {
304       // make sure this is our transition event.
305       if (event && event.target !== this) {
306         return;
307       }
308       if (this.opened) {
309         this._finishRenderOpened();
310       } else {
311         this._finishRenderClosed();
312       }
313     },
315     _finishRenderOpened: function() {
316       // focus the child node with [autofocus]
317       if (!this.noAutoFocus) {
318         this._focusNode.focus();
319       }
321       this.fire('iron-overlay-opened');
323       this._squelchNextResize = true;
324       this.async(this.notifyResize);
325     },
327     _finishRenderClosed: function() {
328       // hide the overlay and remove the backdrop
329       this.resetFit();
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);
342     },
344     _completeBackdrop: function() {
345       if (this.withBackdrop) {
346         this._manager.trackBackdrop(this);
347         this.backdropElement.complete();
348       }
349     },
351     _preparePositioning: function() {
352       this.style.transition = this.style.webkitTransition = 'none';
353       this.style.transform = this.style.webkitTransform = 'none';
354       this.style.display = '';
355     },
357     _finishPositioning: function() {
358       this.style.display = 'none';
359       this.style.transform = this.style.webkitTransform = '';
360       // force layout to avoid application of transform
361       this.offsetWidth;
362       this.style.transition = this.style.webkitTransition = '';
363     },
365     _applyFocus: function() {
366       if (this.opened) {
367         if (!this.noAutoFocus) {
368           this._focusNode.focus();
369         }
370       } else {
371         this._focusNode.blur();
372         this._manager.focusOverlay();
373       }
374     },
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
379       // useful.
380       if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
381         this._cancelJob = this.async(function() {
382           this.cancel();
383         }, 10);
384       }
385     },
387     _onClick: function(event) {
388       if (this._cancelJob) {
389         this.cancelAsync(this._cancelJob);
390         this._cancelJob = null;
391       }
392     },
394     _onCaptureKeydown: function(event) {
395       var ESC = 27;
396       if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
397         this.cancel();
398         event.stopPropagation();
399       }
400     },
402     _onIronResize: function() {
403       if (this._squelchNextResize) {
404         this._squelchNextResize = false;
405         return;
406       }
407       if (this.opened) {
408         this.refit();
409       }
410     }
412   };
414   /** @polymerBehavior */
415   Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];