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

Side by Side Diff: third_party/polymer/components-chromium/core-overlay/core-overlay-extracted.js

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

Powered by Google App Engine
This is Rietveld 408576698