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

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

Issue 592593002: Inline scripts were extracted from Polymer elements. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: s/echo ""/echo/ 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 (function() {
3
4 Polymer('core-overlay', {
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 * This property specifies an attribute on elements that should
75 * close the overlay on tap. Should not set `closeSelector` if this
76 * is set.
77 *
78 * @attribute closeAttribute
79 * @type string
80 * @default "core-overlay-toggle"
81 */
82 closeAttribute: 'core-overlay-toggle',
83
84 /**
85 * This property specifies a selector matching elements that should
86 * close the overlay on tap. Should not set `closeAttribute` if this
87 * is set.
88 *
89 * @attribute closeSelector
90 * @type string
91 * @default ""
92 */
93 closeSelector: '',
94
95 /**
96 * A `core-overlay` target's size is constrained to the window size.
97 * The `margin` property specifies a pixel amount around the overlay
98 * that will be reserved. It's useful for ensuring that, for example,
99 * a shadow displayed outside the target will always be visible.
100 *
101 * @attribute margin
102 * @type number
103 * @default 0
104 */
105 margin: 0,
106
107 /**
108 * The transition property specifies a string which identifies a
109 * <a href="../core-transition/">`core-transition`</a> element that
110 * will be used to help the overlay open and close. The default
111 * `core-transition-fade` will cause the overlay to fade in and out.
112 *
113 * @attribute transition
114 * @type string
115 * @default 'core-transition-fade'
116 */
117 transition: 'core-transition-fade'
118
119 },
120
121 captureEventName: 'tap',
122 targetListeners: {
123 'tap': 'tapHandler',
124 'keydown': 'keydownHandler',
125 'core-transitionend': 'transitionend'
126 },
127
128 registerCallback: function(element) {
129 this.layer = document.createElement('core-overlay-layer');
130 this.keyHelper = document.createElement('core-key-helper');
131 this.meta = document.createElement('core-transition');
132 this.scrim = document.createElement('div');
133 this.scrim.className = 'core-overlay-backdrop';
134 },
135
136 ready: function() {
137 this.target = this.target || this;
138 // flush to ensure styles are installed before paint
139 Platform.flush();
140 },
141
142 /**
143 * Toggle the opened state of the overlay.
144 * @method toggle
145 */
146 toggle: function() {
147 this.opened = !this.opened;
148 },
149
150 /**
151 * Open the overlay. This is equivalent to setting the `opened`
152 * property to true.
153 * @method open
154 */
155 open: function() {
156 this.opened = true;
157 },
158
159 /**
160 * Close the overlay. This is equivalent to setting the `opened`
161 * property to false.
162 * @method close
163 */
164 close: function() {
165 this.opened = false;
166 },
167
168 domReady: function() {
169 this.ensureTargetSetup();
170 },
171
172 targetChanged: function(old) {
173 if (this.target) {
174 // really make sure tabIndex is set
175 if (this.target.tabIndex < 0) {
176 this.target.tabIndex = -1;
177 }
178 this.addElementListenerList(this.target, this.targetListeners);
179 this.target.style.display = 'none';
180 }
181 if (old) {
182 this.removeElementListenerList(old, this.targetListeners);
183 var transition = this.getTransition();
184 if (transition) {
185 transition.teardown(old);
186 } else {
187 old.style.position = '';
188 old.style.outline = '';
189 }
190 old.style.display = '';
191 }
192 },
193
194 // NOTE: wait to call this until we're as sure as possible that target
195 // is styled.
196 ensureTargetSetup: function() {
197 if (!this.target || this.target.__overlaySetup) {
198 return;
199 }
200 this.target.__overlaySetup = true;
201 this.target.style.display = '';
202 var transition = this.getTransition();
203 if (transition) {
204 transition.setup(this.target);
205 }
206 var computed = getComputedStyle(this.target);
207 this.targetStyle = {
208 position: computed.position === 'static' ? 'fixed' :
209 computed.position
210 }
211 if (!transition) {
212 this.target.style.position = this.targetStyle.position;
213 this.target.style.outline = 'none';
214 }
215 this.target.style.display = 'none';
216 },
217
218 openedChanged: function() {
219 this.transitioning = true;
220 this.ensureTargetSetup();
221 this.prepareRenderOpened();
222 // continue styling after delay so display state can change
223 // without aborting transitions
224 // note: we wait a full frame so that transition changes executed
225 // during measuring do not cause transition
226 this.async(function() {
227 this.target.style.display = '';
228 this.async('renderOpened');
229 });
230 this.fire('core-overlay-open', this.opened);
231 },
232
233 // tasks which must occur before opening; e.g. making the element visible
234 prepareRenderOpened: function() {
235 if (this.opened) {
236 addOverlay(this);
237 }
238 this.prepareBackdrop();
239 // async so we don't auto-close immediately via a click.
240 this.async(function() {
241 if (!this.autoCloseDisabled) {
242 this.enableElementListener(this.opened, document,
243 this.captureEventName, 'captureHandler', true);
244 }
245 });
246 this.enableElementListener(this.opened, window, 'resize',
247 'resizeHandler');
248
249 if (this.opened) {
250 // TODO(sorvell): force SD Polyfill to render
251 forcePolyfillRender(this.target);
252 if (!this._shouldPosition) {
253 this.target.style.position = 'absolute';
254 var computed = getComputedStyle(this.target);
255 var t = (computed.top === 'auto' && computed.bottom === 'auto');
256 var l = (computed.left === 'auto' && computed.right === 'auto');
257 this.target.style.position = this.targetStyle.position;
258 this._shouldPosition = {top: t, left: l};
259 }
260 // if we are showing, then take care when measuring
261 this.prepareMeasure(this.target);
262 this.updateTargetDimensions();
263 this.finishMeasure(this.target);
264 if (this.layered) {
265 this.layer.addElement(this.target);
266 this.layer.opened = this.opened;
267 }
268 }
269 },
270
271 // tasks which cause the overlay to actually open; typically play an
272 // animation
273 renderOpened: function() {
274 var transition = this.getTransition();
275 if (transition) {
276 transition.go(this.target, {opened: this.opened});
277 } else {
278 this.transitionend();
279 }
280 this.renderBackdropOpened();
281 },
282
283 // finishing tasks; typically called via a transition
284 transitionend: function(e) {
285 // make sure this is our transition event.
286 if (e && e.target !== this.target) {
287 return;
288 }
289 this.transitioning = false;
290 if (!this.opened) {
291 this.resetTargetDimensions();
292 this.target.style.display = 'none';
293 this.completeBackdrop();
294 removeOverlay(this);
295 if (this.layered) {
296 if (!currentOverlay()) {
297 this.layer.opened = this.opened;
298 }
299 this.layer.removeElement(this.target);
300 }
301 }
302 this.applyFocus();
303 },
304
305 prepareBackdrop: function() {
306 if (this.backdrop && this.opened) {
307 if (!this.scrim.parentNode) {
308 document.body.appendChild(this.scrim);
309 this.scrim.style.zIndex = currentOverlayZ() - 1;
310 }
311 trackBackdrop(this);
312 }
313 },
314
315 renderBackdropOpened: function() {
316 if (this.backdrop && getBackdrops().length < 2) {
317 this.scrim.classList.toggle('core-opened', this.opened);
318 }
319 },
320
321 completeBackdrop: function() {
322 if (this.backdrop) {
323 trackBackdrop(this);
324 if (getBackdrops().length === 0) {
325 this.scrim.parentNode.removeChild(this.scrim);
326 }
327 }
328 },
329
330 prepareMeasure: function(target) {
331 target.style.transition = target.style.webkitTransition = 'none';
332 target.style.transform = target.style.webkitTransform = 'none';
333 target.style.display = '';
334 },
335
336 finishMeasure: function(target) {
337 target.style.display = 'none';
338 target.style.transform = target.style.webkitTransform = '';
339 target.style.transition = target.style.webkitTransition = '';
340 },
341
342 getTransition: function() {
343 return this.meta.byId(this.transition);
344 },
345
346 getFocusNode: function() {
347 return this.target.querySelector('[autofocus]') || this.target;
348 },
349
350 applyFocus: function() {
351 var focusNode = this.getFocusNode();
352 if (this.opened) {
353 focusNode.focus();
354 } else {
355 focusNode.blur();
356 if (currentOverlay() == this) {
357 console.warn('Current core-overlay is attempting to focus itself as ne xt! (bug)');
358 } else {
359 focusOverlay();
360 }
361 }
362 },
363
364 updateTargetDimensions: function() {
365 this.positionTarget();
366 this.sizeTarget();
367 //
368 if (this.layered) {
369 var rect = this.target.getBoundingClientRect();
370 this.target.style.top = rect.top + 'px';
371 this.target.style.left = rect.left + 'px';
372 this.target.style.right = this.target.style.bottom = 'auto';
373 }
374 },
375
376 sizeTarget: function() {
377 var sizer = this.sizingTarget || this.target;
378 var rect = sizer.getBoundingClientRect();
379 var mt = rect.top === this.margin ? this.margin : this.margin * 2;
380 var ml = rect.left === this.margin ? this.margin : this.margin * 2;
381 var h = window.innerHeight - rect.top - mt;
382 var w = window.innerWidth - rect.left - ml;
383 sizer.style.maxHeight = h + 'px';
384 sizer.style.maxWidth = w + 'px';
385 sizer.style.boxSizing = 'border-box';
386 },
387
388 positionTarget: function() {
389 // vertically and horizontally center if not positioned
390 if (this._shouldPosition.top) {
391 var t = Math.max((window.innerHeight -
392 this.target.offsetHeight - this.margin*2) / 2, this.margin);
393 this.target.style.top = t + 'px';
394 }
395 if (this._shouldPosition.left) {
396 var l = Math.max((window.innerWidth -
397 this.target.offsetWidth - this.margin*2) / 2, this.margin);
398 this.target.style.left = l + 'px';
399 }
400 },
401
402 resetTargetDimensions: function() {
403 this.target.style.top = this.target.style.left = '';
404 this.target.style.right = this.target.style.bottom = '';
405 this.target.style.width = this.target.style.height = '';
406 this._shouldPosition = null;
407 },
408
409 tapHandler: function(e) {
410 // closeSelector takes precedence since closeAttribute has a default non-n ull value.
411 if (e.target &&
412 (this.closeSelector && e.target.matches(this.closeSelector)) ||
413 (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) {
414 this.toggle();
415 } else {
416 if (this.autoCloseJob) {
417 this.autoCloseJob.stop();
418 this.autoCloseJob = null;
419 }
420 }
421 },
422
423 // We use the traditional approach of capturing events on document
424 // to to determine if the overlay needs to close. However, due to
425 // ShadowDOM event retargeting, the event target is not useful. Instead
426 // of using it, we attempt to close asynchronously and prevent the close
427 // if a tap event is immediately heard on the target.
428 // TODO(sorvell): This approach will not work with modal. For
429 // this we need a scrim.
430 captureHandler: function(e) {
431 if (!this.autoCloseDisabled && (currentOverlay() == this)) {
432 this.autoCloseJob = this.job(this.autoCloseJob, function() {
433 this.close();
434 });
435 }
436 },
437
438 keydownHandler: function(e) {
439 if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) {
440 this.close();
441 e.stopPropagation();
442 }
443 },
444
445 /**
446 * Extensions of core-overlay should implement the `resizeHandler`
447 * method to adjust the size and position of the overlay when the
448 * browser window resizes.
449 * @method resizeHandler
450 */
451 resizeHandler: function() {
452 this.updateTargetDimensions();
453 },
454
455 // TODO(sorvell): these utility methods should not be here.
456 addElementListenerList: function(node, events) {
457 for (var i in events) {
458 this.addElementListener(node, i, events[i]);
459 }
460 },
461
462 removeElementListenerList: function(node, events) {
463 for (var i in events) {
464 this.removeElementListener(node, i, events[i]);
465 }
466 },
467
468 enableElementListener: function(enable, node, event, methodName, capture) {
469 if (enable) {
470 this.addElementListener(node, event, methodName, capture);
471 } else {
472 this.removeElementListener(node, event, methodName, capture);
473 }
474 },
475
476 addElementListener: function(node, event, methodName, capture) {
477 var fn = this._makeBoundListener(methodName);
478 if (node && fn) {
479 Polymer.addEventListener(node, event, fn, capture);
480 }
481 },
482
483 removeElementListener: function(node, event, methodName, capture) {
484 var fn = this._makeBoundListener(methodName);
485 if (node && fn) {
486 Polymer.removeEventListener(node, event, fn, capture);
487 }
488 },
489
490 _makeBoundListener: function(methodName) {
491 var self = this, method = this[methodName];
492 if (!method) {
493 return;
494 }
495 var bound = '_bound' + methodName;
496 if (!this[bound]) {
497 this[bound] = function(e) {
498 method.call(self, e);
499 }
500 }
501 return this[bound];
502 },
503 });
504
505 function forcePolyfillRender(target) {
506 if (window.ShadowDOMPolyfill) {
507 target.offsetHeight;
508 }
509 }
510
511 // TODO(sorvell): This should be an element with private state so it can
512 // be independent of overlay.
513 // track overlays for z-index and focus managemant
514 var overlays = [];
515 function addOverlay(overlay) {
516 var z0 = currentOverlayZ();
517 overlays.push(overlay);
518 var z1 = currentOverlayZ();
519 if (z1 <= z0) {
520 applyOverlayZ(overlay, z0);
521 }
522 }
523
524 function removeOverlay(overlay) {
525 var i = overlays.indexOf(overlay);
526 if (i >= 0) {
527 overlays.splice(i, 1);
528 setZ(overlay, '');
529 }
530 }
531
532 function applyOverlayZ(overlay, aboveZ) {
533 setZ(overlay.target, aboveZ + 2);
534 }
535
536 function setZ(element, z) {
537 element.style.zIndex = z;
538 }
539
540 function currentOverlay() {
541 return overlays[overlays.length-1];
542 }
543
544 var DEFAULT_Z = 10;
545
546 function currentOverlayZ() {
547 var z;
548 var current = currentOverlay();
549 if (current) {
550 var z1 = window.getComputedStyle(current.target).zIndex;
551 if (!isNaN(z1)) {
552 z = Number(z1);
553 }
554 }
555 return z || DEFAULT_Z;
556 }
557
558 function focusOverlay() {
559 var current = currentOverlay();
560 // We have to be careful to focus the next overlay _after_ any current
561 // transitions are complete (due to the state being toggled prior to the
562 // transition). Otherwise, we risk infinite recursion when a transitioning
563 // (closed) overlay becomes the current overlay.
564 //
565 // NOTE: We make the assumption that any overlay that completes a transition
566 // will call into focusOverlay to kick the process back off. Currently:
567 // transitionend -> applyFocus -> focusOverlay.
568 if (current && !current.transitioning) {
569 current.applyFocus();
570 }
571 }
572
573 var backdrops = [];
574 function trackBackdrop(element) {
575 if (element.opened) {
576 backdrops.push(element);
577 } else {
578 var i = backdrops.indexOf(element);
579 if (i >= 0) {
580 backdrops.splice(i, 1);
581 }
582 }
583 }
584
585 function getBackdrops() {
586 return backdrops;
587 }
588 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698