Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / iron-overlay-behavior / iron-overlay-behavior-extracted.js
blob380ab4259dfde48e0bfb59636c9da59bbbf0d4a2
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.
45 opened: {
46 observer: '_openedChanged',
47 type: Boolean,
48 value: false,
49 notify: true
52 /**
53 * True if the overlay was canceled when it was last closed.
55 canceled: {
56 observer: '_canceledChanged',
57 readOnly: true,
58 type: Boolean,
59 value: false
62 /**
63 * Set to true to display a backdrop behind the overlay.
65 withBackdrop: {
66 type: Boolean,
67 value: false
70 /**
71 * Set to true to disable auto-focusing the overlay or child nodes with
72 * the `autofocus` attribute` when the overlay is opened.
74 noAutoFocus: {
75 type: Boolean,
76 value: false
79 /**
80 * Set to true to disable canceling the overlay with the ESC key.
82 noCancelOnEscKey: {
83 type: Boolean,
84 value: false
87 /**
88 * Set to true to disable canceling the overlay by clicking outside it.
90 noCancelOnOutsideClick: {
91 type: Boolean,
92 value: false
95 /**
96 * Returns the reason this dialog was last closed.
98 closingReason: {
99 // was a getter before, but needs to be a property so other
100 // behaviors can override this.
101 type: Object
104 _manager: {
105 type: Object,
106 value: Polymer.IronOverlayManager
109 _boundOnCaptureClick: {
110 type: Function,
111 value: function() {
112 return this._onCaptureClick.bind(this);
116 _boundOnCaptureKeydown: {
117 type: Function,
118 value: function() {
119 return this._onCaptureKeydown.bind(this);
125 listeners: {
126 'click': '_onClick',
127 'iron-resize': '_onIronResize'
131 * The backdrop element.
132 * @type Node
134 get backdropElement() {
135 return this._backdrop;
138 get _focusNode() {
139 return Polymer.dom(this).querySelector('[autofocus]') || this;
142 registered: function() {
143 this._backdrop = document.createElement('iron-overlay-backdrop');
146 ready: function() {
147 this._ensureSetup();
148 if (this._callOpenedWhenReady) {
149 this._openedChanged();
153 detached: function() {
154 this.opened = false;
155 this._completeBackdrop();
156 this._manager.removeOverlay(this);
160 * Toggle the opened state of the overlay.
162 toggle: function() {
163 this.opened = !this.opened;
167 * Open the overlay.
169 open: function() {
170 this.opened = true;
171 this.closingReason = {canceled: false};
175 * Close the overlay.
177 close: function() {
178 this.opened = false;
179 this._setCanceled(false);
183 * Cancels the overlay.
185 cancel: function() {
186 this.opened = false,
187 this._setCanceled(true);
190 _ensureSetup: function() {
191 if (this._overlaySetup) {
192 return;
194 this._overlaySetup = true;
195 this.style.outline = 'none';
196 this.style.display = 'none';
199 _openedChanged: function() {
200 if (this.opened) {
201 this.removeAttribute('aria-hidden');
202 } else {
203 this.setAttribute('aria-hidden', 'true');
206 // wait to call after ready only if we're initially open
207 if (!this._overlaySetup) {
208 this._callOpenedWhenReady = this.opened;
209 return;
211 if (this._openChangedAsync) {
212 this.cancelAsync(this._openChangedAsync);
215 this._toggleListeners();
217 if (this.opened) {
218 this._prepareRenderOpened();
221 // async here to allow overlay layer to become visible.
222 this._openChangedAsync = this.async(function() {
223 // overlay becomes visible here
224 this.style.display = '';
225 // force layout to ensure transitions will go
226 /** @suppress {suspiciousCode} */ this.offsetWidth;
227 if (this.opened) {
228 this._renderOpened();
229 } else {
230 this._renderClosed();
232 this._openChangedAsync = null;
237 _canceledChanged: function() {
238 this.closingReason = this.closingReason || {};
239 this.closingReason.canceled = this.canceled;
242 _toggleListener: function(enable, node, event, boundListener, capture) {
243 if (enable) {
244 node.addEventListener(event, boundListener, capture);
245 } else {
246 node.removeEventListener(event, boundListener, capture);
250 _toggleListeners: function() {
251 if (this._toggleListenersAsync) {
252 this.cancelAsync(this._toggleListenersAsync);
254 // async so we don't auto-close immediately via a click.
255 this._toggleListenersAsync = this.async(function() {
256 this._toggleListener(this.opened, document, 'click', this._boundOnCaptureClick, true);
257 this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true);
258 this._toggleListenersAsync = null;
262 // tasks which must occur before opening; e.g. making the element visible
263 _prepareRenderOpened: function() {
264 this._manager.addOverlay(this);
266 if (this.withBackdrop) {
267 this.backdropElement.prepare();
268 this._manager.trackBackdrop(this);
271 this._preparePositioning();
272 this.fit();
273 this._finishPositioning();
276 // tasks which cause the overlay to actually open; typically play an
277 // animation
278 _renderOpened: function() {
279 if (this.withBackdrop) {
280 this.backdropElement.open();
282 this._finishRenderOpened();
285 _renderClosed: function() {
286 if (this.withBackdrop) {
287 this.backdropElement.close();
289 this._finishRenderClosed();
292 _onTransitionend: function(event) {
293 // make sure this is our transition event.
294 if (event && event.target !== this) {
295 return;
297 if (this.opened) {
298 this._finishRenderOpened();
299 } else {
300 this._finishRenderClosed();
304 _finishRenderOpened: function() {
305 // focus the child node with [autofocus]
306 if (!this.noAutoFocus) {
307 this._focusNode.focus();
310 this.fire('iron-overlay-opened');
312 this._squelchNextResize = true;
313 this.async(this.notifyResize);
316 _finishRenderClosed: function() {
317 // hide the overlay and remove the backdrop
318 this.resetFit();
319 this.style.display = 'none';
320 this._completeBackdrop();
321 this._manager.removeOverlay(this);
323 this._focusNode.blur();
324 // focus the next overlay, if there is one
325 this._manager.focusOverlay();
327 this.fire('iron-overlay-closed', this.closingReason);
329 this._squelchNextResize = true;
330 this.async(this.notifyResize);
333 _completeBackdrop: function() {
334 if (this.withBackdrop) {
335 this._manager.trackBackdrop(this);
336 this.backdropElement.complete();
340 _preparePositioning: function() {
341 this.style.transition = this.style.webkitTransition = 'none';
342 this.style.transform = this.style.webkitTransform = 'none';
343 this.style.display = '';
346 _finishPositioning: function() {
347 this.style.display = 'none';
348 this.style.transform = this.style.webkitTransform = '';
349 // force layout to avoid application of transform
350 /** @suppress {suspiciousCode} */ this.offsetWidth;
351 this.style.transition = this.style.webkitTransition = '';
354 _applyFocus: function() {
355 if (this.opened) {
356 if (!this.noAutoFocus) {
357 this._focusNode.focus();
359 } else {
360 this._focusNode.blur();
361 this._manager.focusOverlay();
365 _onCaptureClick: function(event) {
366 // attempt to close asynchronously and prevent the close of a tap event is immediately heard
367 // on target. This is because in shadow dom due to event retargetting event.target is not
368 // useful.
369 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
370 this._cancelJob = this.async(function() {
371 this.cancel();
372 }, 10);
376 _onClick: function(event) {
377 if (this._cancelJob) {
378 this.cancelAsync(this._cancelJob);
379 this._cancelJob = null;
383 _onCaptureKeydown: function(event) {
384 var ESC = 27;
385 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
386 this.cancel();
387 event.stopPropagation();
391 _onIronResize: function() {
392 if (this._squelchNextResize) {
393 this._squelchNextResize = false;
394 return;
396 if (this.opened) {
397 this.refit();
402 * Fired after the `iron-overlay` opens.
403 * @event iron-overlay-opened
407 * Fired after the `iron-overlay` closes.
408 * @event iron-overlay-closed
409 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
413 /** @polymerBehavior */
414 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];