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

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

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

Powered by Google App Engine
This is Rietveld 408576698