OLD | NEW |
| (Empty) |
1 <!-- | |
2 @license | |
3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. | |
4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt | |
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | |
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt | |
7 Code distributed by Google as part of the polymer project is also | |
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt | |
9 --> | |
10 | |
11 <link rel="import" href="../polymer/polymer.html"> | |
12 <link rel="import" href="../iron-fit-behavior/iron-fit-behavior.html"> | |
13 <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html
"> | |
14 <link rel="import" href="iron-overlay-backdrop.html"> | |
15 <link rel="import" href="iron-overlay-manager.html"> | |
16 | |
17 <script> | |
18 | |
19 /* | |
20 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays | |
21 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety | |
22 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. | |
23 | |
24 ### Closing and canceling | |
25 | |
26 A dialog may be hidden by closing or canceling. The difference between close and
cancel is user | |
27 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, | |
28 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is | |
29 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. | |
30 `close()` should be called explicitly by the implementer when the user interacts
with a control | |
31 in the overlay element. | |
32 | |
33 ### Positioning | |
34 | |
35 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can | |
36 position and size it manually using CSS. See `Polymer.IronFitBehavior`. | |
37 | |
38 ### Backdrop | |
39 | |
40 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is | |
41 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling | |
42 options. | |
43 | |
44 ### Limitations | |
45 | |
46 The element is styled to appear on top of other content by setting its `z-index`
property. You | |
47 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking | |
48 context. You should place this element as a child of `<body>` whenever possible. | |
49 | |
50 @demo demo/index.html | |
51 @polymerBehavior Polymer.IronOverlayBehavior | |
52 */ | |
53 | |
54 Polymer.IronOverlayBehaviorImpl = { | |
55 | |
56 properties: { | |
57 | |
58 /** | |
59 * True if the overlay is currently displayed. | |
60 */ | |
61 opened: { | |
62 observer: '_openedChanged', | |
63 type: Boolean, | |
64 value: false | |
65 }, | |
66 | |
67 /** | |
68 * True if the overlay was canceled when it was last closed. | |
69 */ | |
70 canceled: { | |
71 observer: '_canceledChanged', | |
72 readOnly: true, | |
73 type: Boolean, | |
74 value: false | |
75 }, | |
76 | |
77 /** | |
78 * Set to true to display a backdrop behind the overlay. | |
79 */ | |
80 withBackdrop: { | |
81 type: Boolean, | |
82 value: false | |
83 }, | |
84 | |
85 /** | |
86 * Set to true to disable auto-focusing the overlay or child nodes with | |
87 * the `autofocus` attribute` when the overlay is opened. | |
88 */ | |
89 noAutoFocus: { | |
90 type: Boolean, | |
91 value: false | |
92 }, | |
93 | |
94 /** | |
95 * Set to true to disable canceling the overlay with the ESC key. | |
96 */ | |
97 noCancelOnEscKey: { | |
98 type: Boolean, | |
99 value: false | |
100 }, | |
101 | |
102 /** | |
103 * Set to true to disable canceling the overlay by clicking outside it. | |
104 */ | |
105 noCancelOnOutsideClick: { | |
106 type: Boolean, | |
107 value: false | |
108 }, | |
109 | |
110 /** | |
111 * Returns the reason this dialog was last closed. | |
112 */ | |
113 closingReason: { | |
114 // was a getter before, but needs to be a property so other | |
115 // behaviors can override this. | |
116 type: Object | |
117 }, | |
118 | |
119 _manager: { | |
120 type: Object, | |
121 value: Polymer.IronOverlayManager | |
122 }, | |
123 | |
124 _boundOnCaptureClick: { | |
125 type: Function, | |
126 value: function() { | |
127 return this._onCaptureClick.bind(this); | |
128 } | |
129 }, | |
130 | |
131 _boundOnCaptureKeydown: { | |
132 type: Function, | |
133 value: function() { | |
134 return this._onCaptureKeydown.bind(this); | |
135 } | |
136 } | |
137 | |
138 }, | |
139 | |
140 listeners: { | |
141 'click': '_onClick', | |
142 'iron-resize': '_onIronResize' | |
143 }, | |
144 | |
145 /** | |
146 * The backdrop element. | |
147 * @type Node | |
148 */ | |
149 get backdropElement() { | |
150 return this._backdrop; | |
151 }, | |
152 | |
153 get _focusNode() { | |
154 return Polymer.dom(this).querySelector('[autofocus]') || this; | |
155 }, | |
156 | |
157 registered: function() { | |
158 this._backdrop = document.createElement('iron-overlay-backdrop'); | |
159 }, | |
160 | |
161 ready: function() { | |
162 this._ensureSetup(); | |
163 if (this._callOpenedWhenReady) { | |
164 this._openedChanged(); | |
165 } | |
166 }, | |
167 | |
168 detached: function() { | |
169 this.opened = false; | |
170 this._completeBackdrop(); | |
171 this._manager.removeOverlay(this); | |
172 }, | |
173 | |
174 /** | |
175 * Toggle the opened state of the overlay. | |
176 */ | |
177 toggle: function() { | |
178 this.opened = !this.opened; | |
179 }, | |
180 | |
181 /** | |
182 * Open the overlay. | |
183 */ | |
184 open: function() { | |
185 this.opened = true; | |
186 this.closingReason = {canceled: false}; | |
187 }, | |
188 | |
189 /** | |
190 * Close the overlay. | |
191 */ | |
192 close: function() { | |
193 this.opened = false; | |
194 this._setCanceled(false); | |
195 }, | |
196 | |
197 /** | |
198 * Cancels the overlay. | |
199 */ | |
200 cancel: function() { | |
201 this.opened = false, | |
202 this._setCanceled(true); | |
203 }, | |
204 | |
205 _ensureSetup: function() { | |
206 if (this._overlaySetup) { | |
207 return; | |
208 } | |
209 this._overlaySetup = true; | |
210 this.style.outline = 'none'; | |
211 this.style.display = 'none'; | |
212 }, | |
213 | |
214 _openedChanged: function() { | |
215 if (this.opened) { | |
216 this.removeAttribute('aria-hidden'); | |
217 } else { | |
218 this.setAttribute('aria-hidden', 'true'); | |
219 } | |
220 | |
221 // wait to call after ready only if we're initially open | |
222 if (!this._overlaySetup) { | |
223 this._callOpenedWhenReady = this.opened; | |
224 return; | |
225 } | |
226 if (this._openChangedAsync) { | |
227 this.cancelAsync(this._openChangedAsync); | |
228 } | |
229 | |
230 this._toggleListeners(); | |
231 | |
232 if (this.opened) { | |
233 this._prepareRenderOpened(); | |
234 } | |
235 | |
236 // async here to allow overlay layer to become visible. | |
237 this._openChangedAsync = this.async(function() { | |
238 // overlay becomes visible here | |
239 this.style.display = ''; | |
240 // force layout to ensure transitions will go | |
241 this.offsetWidth; | |
242 if (this.opened) { | |
243 this._renderOpened(); | |
244 } else { | |
245 this._renderClosed(); | |
246 } | |
247 this._openChangedAsync = null; | |
248 }); | |
249 | |
250 }, | |
251 | |
252 _canceledChanged: function() { | |
253 this.closingReason = this.closingReason || {}; | |
254 this.closingReason.canceled = this.canceled; | |
255 }, | |
256 | |
257 _toggleListener: function(enable, node, event, boundListener, capture) { | |
258 if (enable) { | |
259 node.addEventListener(event, boundListener, capture); | |
260 } else { | |
261 node.removeEventListener(event, boundListener, capture); | |
262 } | |
263 }, | |
264 | |
265 _toggleListeners: function() { | |
266 if (this._toggleListenersAsync) { | |
267 this.cancelAsync(this._toggleListenersAsync); | |
268 } | |
269 // async so we don't auto-close immediately via a click. | |
270 this._toggleListenersAsync = this.async(function() { | |
271 this._toggleListener(this.opened, document, 'click', this._boundOnCaptur
eClick, true); | |
272 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt
ureKeydown, true); | |
273 this._toggleListenersAsync = null; | |
274 }); | |
275 }, | |
276 | |
277 // tasks which must occur before opening; e.g. making the element visible | |
278 _prepareRenderOpened: function() { | |
279 this._manager.addOverlay(this); | |
280 | |
281 if (this.withBackdrop) { | |
282 this.backdropElement.prepare(); | |
283 this._manager.trackBackdrop(this); | |
284 } | |
285 | |
286 this._preparePositioning(); | |
287 this.fit(); | |
288 this._finishPositioning(); | |
289 }, | |
290 | |
291 // tasks which cause the overlay to actually open; typically play an | |
292 // animation | |
293 _renderOpened: function() { | |
294 if (this.withBackdrop) { | |
295 this.backdropElement.open(); | |
296 } | |
297 this._finishRenderOpened(); | |
298 }, | |
299 | |
300 _renderClosed: function() { | |
301 if (this.withBackdrop) { | |
302 this.backdropElement.close(); | |
303 } | |
304 this._finishRenderClosed(); | |
305 }, | |
306 | |
307 _onTransitionend: function(event) { | |
308 // make sure this is our transition event. | |
309 if (event && event.target !== this) { | |
310 return; | |
311 } | |
312 if (this.opened) { | |
313 this._finishRenderOpened(); | |
314 } else { | |
315 this._finishRenderClosed(); | |
316 } | |
317 }, | |
318 | |
319 _finishRenderOpened: function() { | |
320 // focus the child node with [autofocus] | |
321 if (!this.noAutoFocus) { | |
322 this._focusNode.focus(); | |
323 } | |
324 | |
325 this.fire('iron-overlay-opened'); | |
326 | |
327 this._squelchNextResize = true; | |
328 this.async(this.notifyResize); | |
329 }, | |
330 | |
331 _finishRenderClosed: function() { | |
332 // hide the overlay and remove the backdrop | |
333 this.resetFit(); | |
334 this.style.display = 'none'; | |
335 this._completeBackdrop(); | |
336 this._manager.removeOverlay(this); | |
337 | |
338 this._focusNode.blur(); | |
339 // focus the next overlay, if there is one | |
340 this._manager.focusOverlay(); | |
341 | |
342 this.fire('iron-overlay-closed', this.closingReason); | |
343 | |
344 this._squelchNextResize = true; | |
345 this.async(this.notifyResize); | |
346 }, | |
347 | |
348 _completeBackdrop: function() { | |
349 if (this.withBackdrop) { | |
350 this._manager.trackBackdrop(this); | |
351 this.backdropElement.complete(); | |
352 } | |
353 }, | |
354 | |
355 _preparePositioning: function() { | |
356 this.style.transition = this.style.webkitTransition = 'none'; | |
357 this.style.transform = this.style.webkitTransform = 'none'; | |
358 this.style.display = ''; | |
359 }, | |
360 | |
361 _finishPositioning: function(target) { | |
362 this.style.display = 'none'; | |
363 this.style.transform = this.style.webkitTransform = ''; | |
364 // force layout to avoid application of transform | |
365 this.offsetWidth; | |
366 this.style.transition = this.style.webkitTransition = ''; | |
367 }, | |
368 | |
369 _applyFocus: function() { | |
370 if (this.opened) { | |
371 if (!this.noAutoFocus) { | |
372 this._focusNode.focus(); | |
373 } | |
374 } else { | |
375 this._focusNode.blur(); | |
376 this._manager.focusOverlay(); | |
377 } | |
378 }, | |
379 | |
380 _onCaptureClick: function(event) { | |
381 // attempt to close asynchronously and prevent the close of a tap event is
immediately heard | |
382 // on target. This is because in shadow dom due to event retargetting even
t.target is not | |
383 // useful. | |
384 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi
s)) { | |
385 this._cancelJob = this.async(function() { | |
386 this.cancel(); | |
387 }, 10); | |
388 } | |
389 }, | |
390 | |
391 _onClick: function(event) { | |
392 if (this._cancelJob) { | |
393 this.cancelAsync(this._cancelJob); | |
394 this._cancelJob = null; | |
395 } | |
396 }, | |
397 | |
398 _onCaptureKeydown: function(event) { | |
399 var ESC = 27; | |
400 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { | |
401 this.cancel(); | |
402 event.stopPropagation(); | |
403 } | |
404 }, | |
405 | |
406 _onIronResize: function() { | |
407 if (this._squelchNextResize) { | |
408 this._squelchNextResize = false; | |
409 return; | |
410 } | |
411 if (this.opened) { | |
412 this.refit(); | |
413 } | |
414 } | |
415 | |
416 }; | |
417 | |
418 /** @polymerBehavior */ | |
419 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; | |
420 | |
421 /* | |
422 * Fired after the `iron-overlay` opens. | |
423 * @event iron-overlay-opened | |
424 */ | |
425 | |
426 /* | |
427 * Fired after the `iron-overlay` closes. | |
428 * @event iron-overlay-closed {{canceled: boolean}} detail - | |
429 * canceled: True if the overlay was canceled. | |
430 */ | |
431 | |
432 </script> | |
OLD | NEW |