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

Side by Side Diff: bower_components/core-overlay/core-overlay.html

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

Powered by Google App Engine
This is Rietveld 408576698