Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Side by Side Diff: third_party/polymer/components/core-overlay/core-overlay.html

Issue 582873003: Polymer elements added to third_party/polymer. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!--
2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
3 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
5 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
6 Code distributed by Google as part of the polymer project is also
7 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
8 -->
9
10 <link rel="import" href="../polymer/polymer.html">
11 <link rel="import" href="../core-transition/core-transition.html">
12 <link rel="import" href="core-key-helper.html">
13 <link rel="import" href="core-overlay-layer.html">
14
15 <!--
16 The `core-overlay` element displays overlayed on top of other content. It starts
17 out hidden and is displayed by setting its `opened` property to true.
18 A `core-overlay's` opened state can be toggled by calling the `toggle`
19 method.
20
21 The `core-overlay` will, by default, show/hide itself when it's opened. The
22 `target` property may be set to another element to cause that element to
23 be shown when the overlay is opened.
24
25 It's common to want a `core-overlay` to animate to its opened
26 position. The `core-overlay` element uses a `core-transition` to handle
27 animation. The default transition is `core-transition-fade` which
28 causes the overlay to fade in when displayed. See
29 <a href="../core-transition/">`core-transition`</a> for more
30 information about customizing a `core-overlay's` opening animation. The
31 `backdrop` property can be set to true to show a backdrop behind the overlay
32 that will darken the rest of the window.
33
34 An element that should close the `core-overlay` will automatically
35 do so if it's given the `core-overlay-toggle` attribute. This attribute
36 can be customized with the `closeAttribute` property. You can also use
37 `closeSelector` if more general matching is needed.
38
39 By default `core-overlay` will close whenever the user taps outside it or
40 presses the escape key. This behavior can be turned off via the
41 `autoCloseDisabled` property.
42
43 <core-overlay>
44 <h2>Dialog</h2>
45 <input placeholder="say something..." autofocus>
46 <div>I agree with this wholeheartedly.</div>
47 <button core-overlay-toggle>OK</button>
48 </core-overlay>
49
50 `core-overlay` will automatically size and position itself according to the
51 following rules. If the target's style.top and style.left are unset, the
52 target will be centered. The size of the target is constrained to be no larger
53 than the window dimensions. The `margin` property specifies the extra amount
54 of space that should be reserved around the overlay. This can be used to ensure
55 that, for example, a drop shadow is always visible around the overlay.
56
57 @group Core Elements
58 @element core-overlay
59 @homepage github.io
60 -->
61 <!--
62 Fired when the `core-overlay`'s `opened` property changes.
63
64 @event core-overlay-open
65 @param {Object} detail
66 @param {Object} detail.opened the opened state
67 -->
68
69 <style>
70 .core-overlay-backdrop {
71 position: fixed;
72 top: 0;
73 left: 0;
74 width: 100vw;
75 height: 100vh;
76 background-color: black;
77 opacity: 0;
78 transition: opacity 0.2s;
79 }
80
81 .core-overlay-backdrop.core-opened {
82 opacity: 0.6;
83 }
84 </style>
85
86 <polymer-element name="core-overlay">
87 <script>
88 (function() {
89
90 Polymer('core-overlay', {
91
92 publish: {
93 /**
94 * The target element that will be shown when the overlay is
95 * opened. If unspecified, the core-overlay itself is the target.
96 *
97 * @attribute target
98 * @type Object
99 * @default the overlay element
100 */
101 target: null,
102
103
104 /**
105 * A `core-overlay`'s size is guaranteed to be
106 * constrained to the window size. To achieve this, the sizingElement
107 * is sized with a max-height/width. By default this element is the
108 * target element, but it can be specifically set to a specific element
109 * inside the target if that is more appropriate. This is useful, for
110 * example, when a region inside the overlay should scroll if needed.
111 *
112 * @attribute sizingTarget
113 * @type Object
114 * @default the target element
115 */
116 sizingTarget: null,
117
118 /**
119 * Set opened to true to show an overlay and to false to hide it.
120 * A `core-overlay` may be made initially opened by setting its
121 * `opened` attribute.
122 * @attribute opened
123 * @type boolean
124 * @default false
125 */
126 opened: false,
127
128 /**
129 * If true, the overlay has a backdrop darkening the rest of the screen.
130 * The backdrop element is attached to the document body and may be styled
131 * with the class `core-overlay-backdrop`. When opened the `core-opened`
132 * class is applied.
133 *
134 * @attribute backdrop
135 * @type boolean
136 * @default false
137 */
138 backdrop: false,
139
140 /**
141 * If true, the overlay is guaranteed to display above page content.
142 *
143 * @attribute layered
144 * @type boolean
145 * @default false
146 */
147 layered: false,
148
149 /**
150 * By default an overlay will close automatically if the user
151 * taps outside it or presses the escape key. Disable this
152 * behavior by setting the `autoCloseDisabled` property to true.
153 * @attribute autoCloseDisabled
154 * @type boolean
155 * @default false
156 */
157 autoCloseDisabled: false,
158
159 /**
160 * This property specifies an attribute on elements that should
161 * close the overlay on tap. Should not set `closeSelector` if this
162 * is set.
163 *
164 * @attribute closeAttribute
165 * @type string
166 * @default "core-overlay-toggle"
167 */
168 closeAttribute: 'core-overlay-toggle',
169
170 /**
171 * This property specifies a selector matching elements that should
172 * close the overlay on tap. Should not set `closeAttribute` if this
173 * is set.
174 *
175 * @attribute closeSelector
176 * @type string
177 * @default ""
178 */
179 closeSelector: '',
180
181 /**
182 * A `core-overlay` target's size is constrained to the window size.
183 * The `margin` property specifies a pixel amount around the overlay
184 * that will be reserved. It's useful for ensuring that, for example,
185 * a shadow displayed outside the target will always be visible.
186 *
187 * @attribute margin
188 * @type number
189 * @default 0
190 */
191 margin: 0,
192
193 /**
194 * The transition property specifies a string which identifies a
195 * <a href="../core-transition/">`core-transition`</a> element that
196 * will be used to help the overlay open and close. The default
197 * `core-transition-fade` will cause the overlay to fade in and out.
198 *
199 * @attribute transition
200 * @type string
201 * @default 'core-transition-fade'
202 */
203 transition: 'core-transition-fade'
204
205 },
206
207 captureEventName: 'tap',
208 targetListeners: {
209 'tap': 'tapHandler',
210 'keydown': 'keydownHandler',
211 'core-transitionend': 'transitionend'
212 },
213
214 registerCallback: function(element) {
215 this.layer = document.createElement('core-overlay-layer');
216 this.keyHelper = document.createElement('core-key-helper');
217 this.meta = document.createElement('core-transition');
218 this.scrim = document.createElement('div');
219 this.scrim.className = 'core-overlay-backdrop';
220 },
221
222 ready: function() {
223 this.target = this.target || this;
224 // flush to ensure styles are installed before paint
225 Platform.flush();
226 },
227
228 /**
229 * Toggle the opened state of the overlay.
230 * @method toggle
231 */
232 toggle: function() {
233 this.opened = !this.opened;
234 },
235
236 /**
237 * Open the overlay. This is equivalent to setting the `opened`
238 * property to true.
239 * @method open
240 */
241 open: function() {
242 this.opened = true;
243 },
244
245 /**
246 * Close the overlay. This is equivalent to setting the `opened`
247 * property to false.
248 * @method close
249 */
250 close: function() {
251 this.opened = false;
252 },
253
254 domReady: function() {
255 this.ensureTargetSetup();
256 },
257
258 targetChanged: function(old) {
259 if (this.target) {
260 // really make sure tabIndex is set
261 if (this.target.tabIndex < 0) {
262 this.target.tabIndex = -1;
263 }
264 this.addElementListenerList(this.target, this.targetListeners);
265 this.target.style.display = 'none';
266 }
267 if (old) {
268 this.removeElementListenerList(old, this.targetListeners);
269 var transition = this.getTransition();
270 if (transition) {
271 transition.teardown(old);
272 } else {
273 old.style.position = '';
274 old.style.outline = '';
275 }
276 old.style.display = '';
277 }
278 },
279
280 // NOTE: wait to call this until we're as sure as possible that target
281 // is styled.
282 ensureTargetSetup: function() {
283 if (!this.target || this.target.__overlaySetup) {
284 return;
285 }
286 this.target.__overlaySetup = true;
287 this.target.style.display = '';
288 var transition = this.getTransition();
289 if (transition) {
290 transition.setup(this.target);
291 }
292 var computed = getComputedStyle(this.target);
293 this.targetStyle = {
294 position: computed.position === 'static' ? 'fixed' :
295 computed.position
296 }
297 if (!transition) {
298 this.target.style.position = this.targetStyle.position;
299 this.target.style.outline = 'none';
300 }
301 this.target.style.display = 'none';
302 },
303
304 openedChanged: function() {
305 this.transitioning = true;
306 this.ensureTargetSetup();
307 this.prepareRenderOpened();
308 // continue styling after delay so display state can change
309 // without aborting transitions
310 // note: we wait a full frame so that transition changes executed
311 // during measuring do not cause transition
312 this.async(function() {
313 this.target.style.display = '';
314 this.async('renderOpened');
315 });
316 this.fire('core-overlay-open', this.opened);
317 },
318
319 // tasks which must occur before opening; e.g. making the element visible
320 prepareRenderOpened: function() {
321 if (this.opened) {
322 addOverlay(this);
323 }
324 this.prepareBackdrop();
325 // async so we don't auto-close immediately via a click.
326 this.async(function() {
327 if (!this.autoCloseDisabled) {
328 this.enableElementListener(this.opened, document,
329 this.captureEventName, 'captureHandler', true);
330 }
331 });
332 this.enableElementListener(this.opened, window, 'resize',
333 'resizeHandler');
334
335 if (this.opened) {
336 // TODO(sorvell): force SD Polyfill to render
337 forcePolyfillRender(this.target);
338 if (!this._shouldPosition) {
339 this.target.style.position = 'absolute';
340 var computed = getComputedStyle(this.target);
341 var t = (computed.top === 'auto' && computed.bottom === 'auto');
342 var l = (computed.left === 'auto' && computed.right === 'auto');
343 this.target.style.position = this.targetStyle.position;
344 this._shouldPosition = {top: t, left: l};
345 }
346 // if we are showing, then take care when measuring
347 this.prepareMeasure(this.target);
348 this.updateTargetDimensions();
349 this.finishMeasure(this.target);
350 if (this.layered) {
351 this.layer.addElement(this.target);
352 this.layer.opened = this.opened;
353 }
354 }
355 },
356
357 // tasks which cause the overlay to actually open; typically play an
358 // animation
359 renderOpened: function() {
360 var transition = this.getTransition();
361 if (transition) {
362 transition.go(this.target, {opened: this.opened});
363 } else {
364 this.transitionend();
365 }
366 this.renderBackdropOpened();
367 },
368
369 // finishing tasks; typically called via a transition
370 transitionend: function(e) {
371 // make sure this is our transition event.
372 if (e && e.target !== this.target) {
373 return;
374 }
375 this.transitioning = false;
376 if (!this.opened) {
377 this.resetTargetDimensions();
378 this.target.style.display = 'none';
379 this.completeBackdrop();
380 removeOverlay(this);
381 if (this.layered) {
382 if (!currentOverlay()) {
383 this.layer.opened = this.opened;
384 }
385 this.layer.removeElement(this.target);
386 }
387 }
388 this.applyFocus();
389 },
390
391 prepareBackdrop: function() {
392 if (this.backdrop && this.opened) {
393 if (!this.scrim.parentNode) {
394 document.body.appendChild(this.scrim);
395 this.scrim.style.zIndex = currentOverlayZ() - 1;
396 }
397 trackBackdrop(this);
398 }
399 },
400
401 renderBackdropOpened: function() {
402 if (this.backdrop && getBackdrops().length < 2) {
403 this.scrim.classList.toggle('core-opened', this.opened);
404 }
405 },
406
407 completeBackdrop: function() {
408 if (this.backdrop) {
409 trackBackdrop(this);
410 if (getBackdrops().length === 0) {
411 this.scrim.parentNode.removeChild(this.scrim);
412 }
413 }
414 },
415
416 prepareMeasure: function(target) {
417 target.style.transition = target.style.webkitTransition = 'none';
418 target.style.transform = target.style.webkitTransform = 'none';
419 target.style.display = '';
420 },
421
422 finishMeasure: function(target) {
423 target.style.display = 'none';
424 target.style.transform = target.style.webkitTransform = '';
425 target.style.transition = target.style.webkitTransition = '';
426 },
427
428 getTransition: function() {
429 return this.meta.byId(this.transition);
430 },
431
432 getFocusNode: function() {
433 return this.target.querySelector('[autofocus]') || this.target;
434 },
435
436 applyFocus: function() {
437 var focusNode = this.getFocusNode();
438 if (this.opened) {
439 focusNode.focus();
440 } else {
441 focusNode.blur();
442 if (currentOverlay() == this) {
443 console.warn('Current core-overlay is attempting to focus itself as ne xt! (bug)');
444 } else {
445 focusOverlay();
446 }
447 }
448 },
449
450 updateTargetDimensions: function() {
451 this.positionTarget();
452 this.sizeTarget();
453 //
454 if (this.layered) {
455 var rect = this.target.getBoundingClientRect();
456 this.target.style.top = rect.top + 'px';
457 this.target.style.left = rect.left + 'px';
458 this.target.style.right = this.target.style.bottom = 'auto';
459 }
460 },
461
462 sizeTarget: function() {
463 var sizer = this.sizingTarget || this.target;
464 var rect = sizer.getBoundingClientRect();
465 var mt = rect.top === this.margin ? this.margin : this.margin * 2;
466 var ml = rect.left === this.margin ? this.margin : this.margin * 2;
467 var h = window.innerHeight - rect.top - mt;
468 var w = window.innerWidth - rect.left - ml;
469 sizer.style.maxHeight = h + 'px';
470 sizer.style.maxWidth = w + 'px';
471 sizer.style.boxSizing = 'border-box';
472 },
473
474 positionTarget: function() {
475 // vertically and horizontally center if not positioned
476 if (this._shouldPosition.top) {
477 var t = Math.max((window.innerHeight -
478 this.target.offsetHeight - this.margin*2) / 2, this.margin);
479 this.target.style.top = t + 'px';
480 }
481 if (this._shouldPosition.left) {
482 var l = Math.max((window.innerWidth -
483 this.target.offsetWidth - this.margin*2) / 2, this.margin);
484 this.target.style.left = l + 'px';
485 }
486 },
487
488 resetTargetDimensions: function() {
489 this.target.style.top = this.target.style.left = '';
490 this.target.style.right = this.target.style.bottom = '';
491 this.target.style.width = this.target.style.height = '';
492 this._shouldPosition = null;
493 },
494
495 tapHandler: function(e) {
496 // closeSelector takes precedence since closeAttribute has a default non-n ull value.
497 if (e.target &&
498 (this.closeSelector && e.target.matches(this.closeSelector)) ||
499 (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) {
500 this.toggle();
501 } else {
502 if (this.autoCloseJob) {
503 this.autoCloseJob.stop();
504 this.autoCloseJob = null;
505 }
506 }
507 },
508
509 // We use the traditional approach of capturing events on document
510 // to to determine if the overlay needs to close. However, due to
511 // ShadowDOM event retargeting, the event target is not useful. Instead
512 // of using it, we attempt to close asynchronously and prevent the close
513 // if a tap event is immediately heard on the target.
514 // TODO(sorvell): This approach will not work with modal. For
515 // this we need a scrim.
516 captureHandler: function(e) {
517 if (!this.autoCloseDisabled && (currentOverlay() == this)) {
518 this.autoCloseJob = this.job(this.autoCloseJob, function() {
519 this.close();
520 });
521 }
522 },
523
524 keydownHandler: function(e) {
525 if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) {
526 this.close();
527 e.stopPropagation();
528 }
529 },
530
531 /**
532 * Extensions of core-overlay should implement the `resizeHandler`
533 * method to adjust the size and position of the overlay when the
534 * browser window resizes.
535 * @method resizeHandler
536 */
537 resizeHandler: function() {
538 this.updateTargetDimensions();
539 },
540
541 // TODO(sorvell): these utility methods should not be here.
542 addElementListenerList: function(node, events) {
543 for (var i in events) {
544 this.addElementListener(node, i, events[i]);
545 }
546 },
547
548 removeElementListenerList: function(node, events) {
549 for (var i in events) {
550 this.removeElementListener(node, i, events[i]);
551 }
552 },
553
554 enableElementListener: function(enable, node, event, methodName, capture) {
555 if (enable) {
556 this.addElementListener(node, event, methodName, capture);
557 } else {
558 this.removeElementListener(node, event, methodName, capture);
559 }
560 },
561
562 addElementListener: function(node, event, methodName, capture) {
563 var fn = this._makeBoundListener(methodName);
564 if (node && fn) {
565 Polymer.addEventListener(node, event, fn, capture);
566 }
567 },
568
569 removeElementListener: function(node, event, methodName, capture) {
570 var fn = this._makeBoundListener(methodName);
571 if (node && fn) {
572 Polymer.removeEventListener(node, event, fn, capture);
573 }
574 },
575
576 _makeBoundListener: function(methodName) {
577 var self = this, method = this[methodName];
578 if (!method) {
579 return;
580 }
581 var bound = '_bound' + methodName;
582 if (!this[bound]) {
583 this[bound] = function(e) {
584 method.call(self, e);
585 }
586 }
587 return this[bound];
588 },
589 });
590
591 function forcePolyfillRender(target) {
592 if (window.ShadowDOMPolyfill) {
593 target.offsetHeight;
594 }
595 }
596
597 // TODO(sorvell): This should be an element with private state so it can
598 // be independent of overlay.
599 // track overlays for z-index and focus managemant
600 var overlays = [];
601 function addOverlay(overlay) {
602 var z0 = currentOverlayZ();
603 overlays.push(overlay);
604 var z1 = currentOverlayZ();
605 if (z1 <= z0) {
606 applyOverlayZ(overlay, z0);
607 }
608 }
609
610 function removeOverlay(overlay) {
611 var i = overlays.indexOf(overlay);
612 if (i >= 0) {
613 overlays.splice(i, 1);
614 setZ(overlay, '');
615 }
616 }
617
618 function applyOverlayZ(overlay, aboveZ) {
619 setZ(overlay.target, aboveZ + 2);
620 }
621
622 function setZ(element, z) {
623 element.style.zIndex = z;
624 }
625
626 function currentOverlay() {
627 return overlays[overlays.length-1];
628 }
629
630 var DEFAULT_Z = 10;
631
632 function currentOverlayZ() {
633 var z;
634 var current = currentOverlay();
635 if (current) {
636 var z1 = window.getComputedStyle(current.target).zIndex;
637 if (!isNaN(z1)) {
638 z = Number(z1);
639 }
640 }
641 return z || DEFAULT_Z;
642 }
643
644 function focusOverlay() {
645 var current = currentOverlay();
646 // We have to be careful to focus the next overlay _after_ any current
647 // transitions are complete (due to the state being toggled prior to the
648 // transition). Otherwise, we risk infinite recursion when a transitioning
649 // (closed) overlay becomes the current overlay.
650 //
651 // NOTE: We make the assumption that any overlay that completes a transition
652 // will call into focusOverlay to kick the process back off. Currently:
653 // transitionend -> applyFocus -> focusOverlay.
654 if (current && !current.transitioning) {
655 current.applyFocus();
656 }
657 }
658
659 var backdrops = [];
660 function trackBackdrop(element) {
661 if (element.opened) {
662 backdrops.push(element);
663 } else {
664 var i = backdrops.indexOf(element);
665 if (i >= 0) {
666 backdrops.splice(i, 1);
667 }
668 }
669 }
670
671 function getBackdrops() {
672 return backdrops;
673 }
674 })();
675 </script>
676 </polymer-element>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698