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 notify: true | |
66 }, | |
67 | |
68 /** | |
69 * True if the overlay was canceled when it was last closed. | |
70 */ | |
71 canceled: { | |
72 observer: '_canceledChanged', | |
73 readOnly: true, | |
74 type: Boolean, | |
75 value: false | |
76 }, | |
77 | |
78 /** | |
79 * Set to true to display a backdrop behind the overlay. | |
80 */ | |
81 withBackdrop: { | |
82 type: Boolean, | |
83 value: false | |
84 }, | |
85 | |
86 /** | |
87 * Set to true to disable auto-focusing the overlay or child nodes with | |
88 * the `autofocus` attribute` when the overlay is opened. | |
89 */ | |
90 noAutoFocus: { | |
91 type: Boolean, | |
92 value: false | |
93 }, | |
94 | |
95 /** | |
96 * Set to true to disable canceling the overlay with the ESC key. | |
97 */ | |
98 noCancelOnEscKey: { | |
99 type: Boolean, | |
100 value: false | |
101 }, | |
102 | |
103 /** | |
104 * Set to true to disable canceling the overlay by clicking outside it. | |
105 */ | |
106 noCancelOnOutsideClick: { | |
107 type: Boolean, | |
108 value: false | |
109 }, | |
110 | |
111 /** | |
112 * Returns the reason this dialog was last closed. | |
113 */ | |
114 closingReason: { | |
115 // was a getter before, but needs to be a property so other | |
116 // behaviors can override this. | |
117 type: Object | |
118 }, | |
119 | |
120 _manager: { | |
121 type: Object, | |
122 value: Polymer.IronOverlayManager | |
123 }, | |
124 | |
125 _boundOnCaptureClick: { | |
126 type: Function, | |
127 value: function() { | |
128 return this._onCaptureClick.bind(this); | |
129 } | |
130 }, | |
131 | |
132 _boundOnCaptureKeydown: { | |
133 type: Function, | |
134 value: function() { | |
135 return this._onCaptureKeydown.bind(this); | |
136 } | |
137 } | |
138 | |
139 }, | |
140 | |
141 /** | |
142 * Fired after the `iron-overlay` opens. | |
143 * @event iron-overlay-opened | |
144 */ | |
145 | |
146 /** | |
147 * Fired after the `iron-overlay` closes. | |
148 * @event iron-overlay-closed {{canceled: boolean}} detail - | |
149 * canceled: True if the overlay was canceled. | |
150 */ | |
151 | |
152 listeners: { | |
153 'click': '_onClick', | |
154 'iron-resize': '_onIronResize' | |
155 }, | |
156 | |
157 /** | |
158 * The backdrop element. | |
159 * @type Node | |
160 */ | |
161 get backdropElement() { | |
162 return this._backdrop; | |
163 }, | |
164 | |
165 get _focusNode() { | |
166 return Polymer.dom(this).querySelector('[autofocus]') || this; | |
167 }, | |
168 | |
169 registered: function() { | |
170 this._backdrop = document.createElement('iron-overlay-backdrop'); | |
171 }, | |
172 | |
173 ready: function() { | |
174 this._ensureSetup(); | |
175 if (this._callOpenedWhenReady) { | |
176 this._openedChanged(); | |
177 } | |
178 }, | |
179 | |
180 detached: function() { | |
181 this.opened = false; | |
182 this._completeBackdrop(); | |
183 this._manager.removeOverlay(this); | |
184 }, | |
185 | |
186 /** | |
187 * Toggle the opened state of the overlay. | |
188 */ | |
189 toggle: function() { | |
190 this.opened = !this.opened; | |
191 }, | |
192 | |
193 /** | |
194 * Open the overlay. | |
195 */ | |
196 open: function() { | |
197 this.opened = true; | |
198 this.closingReason = {canceled: false}; | |
199 }, | |
200 | |
201 /** | |
202 * Close the overlay. | |
203 */ | |
204 close: function() { | |
205 this.opened = false; | |
206 this._setCanceled(false); | |
207 }, | |
208 | |
209 /** | |
210 * Cancels the overlay. | |
211 */ | |
212 cancel: function() { | |
213 this.opened = false, | |
214 this._setCanceled(true); | |
215 }, | |
216 | |
217 _ensureSetup: function() { | |
218 if (this._overlaySetup) { | |
219 return; | |
220 } | |
221 this._overlaySetup = true; | |
222 this.style.outline = 'none'; | |
223 this.style.display = 'none'; | |
224 }, | |
225 | |
226 _openedChanged: function() { | |
227 if (this.opened) { | |
228 this.removeAttribute('aria-hidden'); | |
229 } else { | |
230 this.setAttribute('aria-hidden', 'true'); | |
231 } | |
232 | |
233 // wait to call after ready only if we're initially open | |
234 if (!this._overlaySetup) { | |
235 this._callOpenedWhenReady = this.opened; | |
236 return; | |
237 } | |
238 if (this._openChangedAsync) { | |
239 this.cancelAsync(this._openChangedAsync); | |
240 } | |
241 | |
242 this._toggleListeners(); | |
243 | |
244 if (this.opened) { | |
245 this._prepareRenderOpened(); | |
246 } | |
247 | |
248 // async here to allow overlay layer to become visible. | |
249 this._openChangedAsync = this.async(function() { | |
250 // overlay becomes visible here | |
251 this.style.display = ''; | |
252 // force layout to ensure transitions will go | |
253 this.offsetWidth; | |
254 if (this.opened) { | |
255 this._renderOpened(); | |
256 } else { | |
257 this._renderClosed(); | |
258 } | |
259 this._openChangedAsync = null; | |
260 }); | |
261 | |
262 }, | |
263 | |
264 _canceledChanged: function() { | |
265 this.closingReason = this.closingReason || {}; | |
266 this.closingReason.canceled = this.canceled; | |
267 }, | |
268 | |
269 _toggleListener: function(enable, node, event, boundListener, capture) { | |
270 if (enable) { | |
271 node.addEventListener(event, boundListener, capture); | |
272 } else { | |
273 node.removeEventListener(event, boundListener, capture); | |
274 } | |
275 }, | |
276 | |
277 _toggleListeners: function() { | |
278 if (this._toggleListenersAsync) { | |
279 this.cancelAsync(this._toggleListenersAsync); | |
280 } | |
281 // async so we don't auto-close immediately via a click. | |
282 this._toggleListenersAsync = this.async(function() { | |
283 this._toggleListener(this.opened, document, 'click', this._boundOnCaptur
eClick, true); | |
284 this._toggleListener(this.opened, document, 'keydown', this._boundOnCapt
ureKeydown, true); | |
285 this._toggleListenersAsync = null; | |
286 }); | |
287 }, | |
288 | |
289 // tasks which must occur before opening; e.g. making the element visible | |
290 _prepareRenderOpened: function() { | |
291 this._manager.addOverlay(this); | |
292 | |
293 if (this.withBackdrop) { | |
294 this.backdropElement.prepare(); | |
295 this._manager.trackBackdrop(this); | |
296 } | |
297 | |
298 this._preparePositioning(); | |
299 this.fit(); | |
300 this._finishPositioning(); | |
301 }, | |
302 | |
303 // tasks which cause the overlay to actually open; typically play an | |
304 // animation | |
305 _renderOpened: function() { | |
306 if (this.withBackdrop) { | |
307 this.backdropElement.open(); | |
308 } | |
309 this._finishRenderOpened(); | |
310 }, | |
311 | |
312 _renderClosed: function() { | |
313 if (this.withBackdrop) { | |
314 this.backdropElement.close(); | |
315 } | |
316 this._finishRenderClosed(); | |
317 }, | |
318 | |
319 _onTransitionend: function(event) { | |
320 // make sure this is our transition event. | |
321 if (event && event.target !== this) { | |
322 return; | |
323 } | |
324 if (this.opened) { | |
325 this._finishRenderOpened(); | |
326 } else { | |
327 this._finishRenderClosed(); | |
328 } | |
329 }, | |
330 | |
331 _finishRenderOpened: function() { | |
332 // focus the child node with [autofocus] | |
333 if (!this.noAutoFocus) { | |
334 this._focusNode.focus(); | |
335 } | |
336 | |
337 this.fire('iron-overlay-opened'); | |
338 | |
339 this._squelchNextResize = true; | |
340 this.async(this.notifyResize); | |
341 }, | |
342 | |
343 _finishRenderClosed: function() { | |
344 // hide the overlay and remove the backdrop | |
345 this.resetFit(); | |
346 this.style.display = 'none'; | |
347 this._completeBackdrop(); | |
348 this._manager.removeOverlay(this); | |
349 | |
350 this._focusNode.blur(); | |
351 // focus the next overlay, if there is one | |
352 this._manager.focusOverlay(); | |
353 | |
354 this.fire('iron-overlay-closed', this.closingReason); | |
355 | |
356 this._squelchNextResize = true; | |
357 this.async(this.notifyResize); | |
358 }, | |
359 | |
360 _completeBackdrop: function() { | |
361 if (this.withBackdrop) { | |
362 this._manager.trackBackdrop(this); | |
363 this.backdropElement.complete(); | |
364 } | |
365 }, | |
366 | |
367 _preparePositioning: function() { | |
368 this.style.transition = this.style.webkitTransition = 'none'; | |
369 this.style.transform = this.style.webkitTransform = 'none'; | |
370 this.style.display = ''; | |
371 }, | |
372 | |
373 _finishPositioning: function() { | |
374 this.style.display = 'none'; | |
375 this.style.transform = this.style.webkitTransform = ''; | |
376 // force layout to avoid application of transform | |
377 this.offsetWidth; | |
378 this.style.transition = this.style.webkitTransition = ''; | |
379 }, | |
380 | |
381 _applyFocus: function() { | |
382 if (this.opened) { | |
383 if (!this.noAutoFocus) { | |
384 this._focusNode.focus(); | |
385 } | |
386 } else { | |
387 this._focusNode.blur(); | |
388 this._manager.focusOverlay(); | |
389 } | |
390 }, | |
391 | |
392 _onCaptureClick: function(event) { | |
393 // attempt to close asynchronously and prevent the close of a tap event is
immediately heard | |
394 // on target. This is because in shadow dom due to event retargetting even
t.target is not | |
395 // useful. | |
396 if (!this.noCancelOnOutsideClick && (this._manager.currentOverlay() == thi
s)) { | |
397 this._cancelJob = this.async(function() { | |
398 this.cancel(); | |
399 }, 10); | |
400 } | |
401 }, | |
402 | |
403 _onClick: function(event) { | |
404 if (this._cancelJob) { | |
405 this.cancelAsync(this._cancelJob); | |
406 this._cancelJob = null; | |
407 } | |
408 }, | |
409 | |
410 _onCaptureKeydown: function(event) { | |
411 var ESC = 27; | |
412 if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { | |
413 this.cancel(); | |
414 event.stopPropagation(); | |
415 } | |
416 }, | |
417 | |
418 _onIronResize: function() { | |
419 if (this._squelchNextResize) { | |
420 this._squelchNextResize = false; | |
421 return; | |
422 } | |
423 if (this.opened) { | |
424 this.refit(); | |
425 } | |
426 } | |
427 | |
428 }; | |
429 | |
430 /** @polymerBehavior */ | |
431 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; | |
432 | |
433 | |
434 </script> | |
OLD | NEW |