ApplicationImpl cleanup, part 1:
[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.
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);
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.
136 listeners: {
137 'click': '_onClick',
138 'iron-resize': '_onIronResize'
142 * The backdrop element.
143 * @type Node
145 get backdropElement() {
146 return this._backdrop;
149 get _focusNode() {
150 return Polymer.dom(this).querySelector('[autofocus]') || this;
153 registered: function() {
154 this._backdrop = document.createElement('iron-overlay-backdrop');
157 ready: function() {
158 this._ensureSetup();
159 if (this._callOpenedWhenReady) {
160 this._openedChanged();
164 detached: function() {
165 this.opened = false;
166 this._completeBackdrop();
167 this._manager.removeOverlay(this);
171 * Toggle the opened state of the overlay.
173 toggle: function() {
174 this.opened = !this.opened;
178 * Open the overlay.
180 open: function() {
181 this.opened = true;
182 this.closingReason = {canceled: false};
186 * Close the overlay.
188 close: function() {
189 this.opened = false;
190 this._setCanceled(false);
194 * Cancels the overlay.
196 cancel: function() {
197 this.opened = false,
198 this._setCanceled(true);
201 _ensureSetup: function() {
202 if (this._overlaySetup) {
203 return;
205 this._overlaySetup = true;
206 this.style.outline = 'none';
207 this.style.display = 'none';
210 _openedChanged: function() {
211 if (this.opened) {
212 this.removeAttribute('aria-hidden');
213 } else {
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;
220 return;
222 if (this._openChangedAsync) {
223 this.cancelAsync(this._openChangedAsync);
226 this._toggleListeners();
228 if (this.opened) {
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
237 this.offsetWidth;
238 if (this.opened) {
239 this._renderOpened();
240 } else {
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) {
254 if (enable) {
255 node.addEventListener(event, boundListener, capture);
256 } else {
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();
283 this.fit();
284 this._finishPositioning();
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();
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) {
306 return;
308 if (this.opened) {
309 this._finishRenderOpened();
310 } else {
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
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);
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
361 this.offsetWidth;
362 this.style.transition = this.style.webkitTransition = '';
365 _applyFocus: function() {
366 if (this.opened) {
367 if (!this.noAutoFocus) {
368 this._focusNode.focus();
370 } else {
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
379 // useful.
380 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == this)) {
381 this._cancelJob = this.async(function() {
382 this.cancel();
383 }, 10);
387 _onClick: function(event) {
388 if (this._cancelJob) {
389 this.cancelAsync(this._cancelJob);
390 this._cancelJob = null;
394 _onCaptureKeydown: function(event) {
395 var ESC = 27;
396 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) {
397 this.cancel();
398 event.stopPropagation();
402 _onIronResize: function() {
403 if (this._squelchNextResize) {
404 this._squelchNextResize = false;
405 return;
407 if (this.opened) {
408 this.refit();
414 /** @polymerBehavior */
415 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];