776 lines
24 KiB
HTML
776 lines
24 KiB
HTML
<!--
|
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
Code distributed by Google as part of the polymer project is also
|
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
-->
|
|
|
|
<link rel="import" href="../polymer/polymer.html">
|
|
<link rel="import" href="../core-transition/core-transition.html">
|
|
<link rel="import" href="../core-resizable/core-resizable.html">
|
|
<link rel="import" href="core-key-helper.html">
|
|
<link rel="import" href="core-overlay-layer.html">
|
|
|
|
<!--
|
|
The `core-overlay` element displays overlayed on top of other content. It starts
|
|
out hidden and is displayed by setting its `opened` property to true.
|
|
A `core-overlay's` opened state can be toggled by calling the `toggle`
|
|
method.
|
|
|
|
The `core-overlay` will, by default, show/hide itself when it's opened. The
|
|
`target` property may be set to another element to cause that element to
|
|
be shown when the overlay is opened.
|
|
|
|
It's common to want a `core-overlay` to animate to its opened
|
|
position. The `core-overlay` element uses a `core-transition` to handle
|
|
animation. The default transition is `core-transition-fade` which
|
|
causes the overlay to fade in when displayed. See
|
|
<a href="../core-transition/">`core-transition`</a> for more
|
|
information about customizing a `core-overlay's` opening animation. The
|
|
`backdrop` property can be set to true to show a backdrop behind the overlay
|
|
that will darken the rest of the window.
|
|
|
|
An element that should close the `core-overlay` will automatically
|
|
do so if it's given the `core-overlay-toggle` attribute. This attribute
|
|
can be customized with the `closeAttribute` property. You can also use
|
|
`closeSelector` if more general matching is needed.
|
|
|
|
By default `core-overlay` will close whenever the user taps outside it or
|
|
presses the escape key. This behavior can be turned off via the
|
|
`autoCloseDisabled` property.
|
|
|
|
<core-overlay>
|
|
<h2>Dialog</h2>
|
|
<input placeholder="say something..." autofocus>
|
|
<div>I agree with this wholeheartedly.</div>
|
|
<button core-overlay-toggle>OK</button>
|
|
</core-overlay>
|
|
|
|
`core-overlay` will automatically size and position itself according to the
|
|
following rules. The overlay's size is constrained such that it does not
|
|
overflow the screen. This is done by setting maxHeight/maxWidth on the
|
|
`sizingTarget`. If the `sizingTarget` already has a setting for one of these
|
|
properties, it will not be overridden. The overlay should
|
|
be positioned via css or imperatively using the `core-overlay-position` event.
|
|
If the overlay is not positioned vertically via setting `top` or `bottom`, it
|
|
will be centered vertically. The same is true horizontally via a setting to
|
|
`left` or `right`. In addition, css `margin` can be used to provide some space
|
|
around the overlay. This can be used to ensure
|
|
that, for example, a drop shadow is always visible around the overlay.
|
|
|
|
@group Core Elements
|
|
@element core-overlay
|
|
@mixins Polymer.CoreResizer https://github.com/polymer/core-resizable
|
|
@homepage github.io
|
|
-->
|
|
<!--
|
|
Fired when the `core-overlay`'s `opened` property changes.
|
|
|
|
@event core-overlay-open
|
|
@param {Object} detail
|
|
@param {Object} detail.opened the opened state
|
|
-->
|
|
<!--
|
|
Fired when the `core-overlay` has completely opened.
|
|
|
|
@event core-overlay-open-completed
|
|
-->
|
|
<!--
|
|
Fired when the `core-overlay` has completely closed.
|
|
|
|
@event core-overlay-close-completed
|
|
-->
|
|
<!--
|
|
Fired when the `core-overlay` needs to position itself. Optionally, implement
|
|
in order to position an overlay via code. If the overlay was not otherwise
|
|
positioned, it's important to indicate how the overlay has been positioned by
|
|
setting the `dimensions.position` object. For example, if the overlay has been
|
|
positioned via setting `right` and `top`, set dimensions.position to an
|
|
object like this: `{v: 'top', h: 'right'}`.
|
|
|
|
@event core-overlay-position
|
|
@param {Object} detail
|
|
@param {Object} detail.target the overlay target
|
|
@param {Object} detail.sizingTarget the overlay sizing target
|
|
@param {Object} detail.opened the opened state
|
|
-->
|
|
<style>
|
|
.core-overlay-backdrop {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background-color: black;
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.core-overlay-backdrop.core-opened {
|
|
opacity: 0.6;
|
|
}
|
|
</style>
|
|
|
|
<polymer-element name="core-overlay">
|
|
<script>
|
|
(function() {
|
|
|
|
Polymer(Polymer.mixin({
|
|
|
|
publish: {
|
|
/**
|
|
* The target element that will be shown when the overlay is
|
|
* opened. If unspecified, the core-overlay itself is the target.
|
|
*
|
|
* @attribute target
|
|
* @type Object
|
|
* @default the overlay element
|
|
*/
|
|
target: null,
|
|
|
|
|
|
/**
|
|
* A `core-overlay`'s size is guaranteed to be
|
|
* constrained to the window size. To achieve this, the sizingElement
|
|
* is sized with a max-height/width. By default this element is the
|
|
* target element, but it can be specifically set to a specific element
|
|
* inside the target if that is more appropriate. This is useful, for
|
|
* example, when a region inside the overlay should scroll if needed.
|
|
*
|
|
* @attribute sizingTarget
|
|
* @type Object
|
|
* @default the target element
|
|
*/
|
|
sizingTarget: null,
|
|
|
|
/**
|
|
* Set opened to true to show an overlay and to false to hide it.
|
|
* A `core-overlay` may be made initially opened by setting its
|
|
* `opened` attribute.
|
|
* @attribute opened
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
opened: false,
|
|
|
|
/**
|
|
* If true, the overlay has a backdrop darkening the rest of the screen.
|
|
* The backdrop element is attached to the document body and may be styled
|
|
* with the class `core-overlay-backdrop`. When opened the `core-opened`
|
|
* class is applied.
|
|
*
|
|
* @attribute backdrop
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
backdrop: false,
|
|
|
|
/**
|
|
* If true, the overlay is guaranteed to display above page content.
|
|
*
|
|
* @attribute layered
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
layered: false,
|
|
|
|
/**
|
|
* By default an overlay will close automatically if the user
|
|
* taps outside it or presses the escape key. Disable this
|
|
* behavior by setting the `autoCloseDisabled` property to true.
|
|
* @attribute autoCloseDisabled
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
autoCloseDisabled: false,
|
|
|
|
/**
|
|
* By default an overlay will focus its target or an element inside
|
|
* it with the `autoFocus` attribute. Disable this
|
|
* behavior by setting the `autoFocusDisabled` property to true.
|
|
* @attribute autoFocusDisabled
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
autoFocusDisabled: false,
|
|
|
|
/**
|
|
* This property specifies an attribute on elements that should
|
|
* close the overlay on tap. Should not set `closeSelector` if this
|
|
* is set.
|
|
*
|
|
* @attribute closeAttribute
|
|
* @type string
|
|
* @default "core-overlay-toggle"
|
|
*/
|
|
closeAttribute: 'core-overlay-toggle',
|
|
|
|
/**
|
|
* This property specifies a selector matching elements that should
|
|
* close the overlay on tap. Should not set `closeAttribute` if this
|
|
* is set.
|
|
*
|
|
* @attribute closeSelector
|
|
* @type string
|
|
* @default ""
|
|
*/
|
|
closeSelector: '',
|
|
|
|
/**
|
|
* The transition property specifies a string which identifies a
|
|
* <a href="../core-transition/">`core-transition`</a> element that
|
|
* will be used to help the overlay open and close. The default
|
|
* `core-transition-fade` will cause the overlay to fade in and out.
|
|
*
|
|
* @attribute transition
|
|
* @type string
|
|
* @default 'core-transition-fade'
|
|
*/
|
|
transition: 'core-transition-fade'
|
|
|
|
},
|
|
|
|
captureEventName: 'tap',
|
|
targetListeners: {
|
|
'tap': 'tapHandler',
|
|
'keydown': 'keydownHandler',
|
|
'core-transitionend': 'transitionend'
|
|
},
|
|
|
|
attached: function() {
|
|
this.resizerAttachedHandler();
|
|
},
|
|
|
|
detached: function() {
|
|
this.resizerDetachedHandler();
|
|
},
|
|
|
|
resizerShouldNotify: function() {
|
|
return this.opened;
|
|
},
|
|
|
|
registerCallback: function(element) {
|
|
this.layer = document.createElement('core-overlay-layer');
|
|
this.keyHelper = document.createElement('core-key-helper');
|
|
this.meta = document.createElement('core-transition');
|
|
this.scrim = document.createElement('div');
|
|
this.scrim.className = 'core-overlay-backdrop';
|
|
},
|
|
|
|
ready: function() {
|
|
this.target = this.target || this;
|
|
// flush to ensure styles are installed before paint
|
|
Polymer.flush();
|
|
},
|
|
|
|
/**
|
|
* Toggle the opened state of the overlay.
|
|
* @method toggle
|
|
*/
|
|
toggle: function() {
|
|
this.opened = !this.opened;
|
|
},
|
|
|
|
/**
|
|
* Open the overlay. This is equivalent to setting the `opened`
|
|
* property to true.
|
|
* @method open
|
|
*/
|
|
open: function() {
|
|
this.opened = true;
|
|
},
|
|
|
|
/**
|
|
* Close the overlay. This is equivalent to setting the `opened`
|
|
* property to false.
|
|
* @method close
|
|
*/
|
|
close: function() {
|
|
this.opened = false;
|
|
},
|
|
|
|
domReady: function() {
|
|
this.ensureTargetSetup();
|
|
},
|
|
|
|
targetChanged: function(old) {
|
|
if (this.target) {
|
|
// really make sure tabIndex is set
|
|
if (this.target.tabIndex < 0) {
|
|
this.target.tabIndex = -1;
|
|
}
|
|
this.addElementListenerList(this.target, this.targetListeners);
|
|
this.target.style.display = 'none';
|
|
this.target.__overlaySetup = false;
|
|
}
|
|
if (old) {
|
|
this.removeElementListenerList(old, this.targetListeners);
|
|
var transition = this.getTransition();
|
|
if (transition) {
|
|
transition.teardown(old);
|
|
} else {
|
|
old.style.position = '';
|
|
old.style.outline = '';
|
|
}
|
|
old.style.display = '';
|
|
}
|
|
},
|
|
|
|
transitionChanged: function(old) {
|
|
if (!this.target) {
|
|
return;
|
|
}
|
|
if (old) {
|
|
this.getTransition(old).teardown(this.target);
|
|
}
|
|
this.target.__overlaySetup = false;
|
|
},
|
|
|
|
// NOTE: wait to call this until we're as sure as possible that target
|
|
// is styled.
|
|
ensureTargetSetup: function() {
|
|
if (!this.target || this.target.__overlaySetup) {
|
|
return;
|
|
}
|
|
if (!this.sizingTarget) {
|
|
this.sizingTarget = this.target;
|
|
}
|
|
this.target.__overlaySetup = true;
|
|
this.target.style.display = '';
|
|
var transition = this.getTransition();
|
|
if (transition) {
|
|
transition.setup(this.target);
|
|
}
|
|
var style = this.target.style;
|
|
var computed = getComputedStyle(this.target);
|
|
if (computed.position === 'static') {
|
|
style.position = 'fixed';
|
|
}
|
|
style.outline = 'none';
|
|
style.display = 'none';
|
|
},
|
|
|
|
openedChanged: function() {
|
|
this.transitioning = true;
|
|
this.ensureTargetSetup();
|
|
this.prepareRenderOpened();
|
|
// async here to allow overlay layer to become visible.
|
|
this.async(function() {
|
|
this.target.style.display = '';
|
|
// force layout to ensure transitions will go
|
|
this.target.offsetWidth;
|
|
this.renderOpened();
|
|
});
|
|
this.fire('core-overlay-open', this.opened);
|
|
},
|
|
|
|
// tasks which must occur before opening; e.g. making the element visible
|
|
prepareRenderOpened: function() {
|
|
if (this.opened) {
|
|
addOverlay(this);
|
|
}
|
|
this.prepareBackdrop();
|
|
// async so we don't auto-close immediately via a click.
|
|
this.async(function() {
|
|
if (!this.autoCloseDisabled) {
|
|
this.enableElementListener(this.opened, document,
|
|
this.captureEventName, 'captureHandler', true);
|
|
}
|
|
});
|
|
this.enableElementListener(this.opened, window, 'resize',
|
|
'resizeHandler');
|
|
|
|
if (this.opened) {
|
|
// force layout so SD Polyfill renders
|
|
this.target.offsetHeight;
|
|
this.discoverDimensions();
|
|
// if we are showing, then take care when positioning
|
|
this.preparePositioning();
|
|
this.positionTarget();
|
|
this.updateTargetDimensions();
|
|
this.finishPositioning();
|
|
if (this.layered) {
|
|
this.layer.addElement(this.target);
|
|
this.layer.opened = this.opened;
|
|
}
|
|
}
|
|
},
|
|
|
|
// tasks which cause the overlay to actually open; typically play an
|
|
// animation
|
|
renderOpened: function() {
|
|
this.notifyResize();
|
|
var transition = this.getTransition();
|
|
if (transition) {
|
|
transition.go(this.target, {opened: this.opened});
|
|
} else {
|
|
this.transitionend();
|
|
}
|
|
this.renderBackdropOpened();
|
|
},
|
|
|
|
// finishing tasks; typically called via a transition
|
|
transitionend: function(e) {
|
|
// make sure this is our transition event.
|
|
if (e && e.target !== this.target) {
|
|
return;
|
|
}
|
|
this.transitioning = false;
|
|
if (!this.opened) {
|
|
this.resetTargetDimensions();
|
|
this.target.style.display = 'none';
|
|
this.completeBackdrop();
|
|
removeOverlay(this);
|
|
if (this.layered) {
|
|
if (!currentOverlay()) {
|
|
this.layer.opened = this.opened;
|
|
}
|
|
this.layer.removeElement(this.target);
|
|
}
|
|
}
|
|
this.fire('core-overlay-' + (this.opened ? 'open' : 'close') +
|
|
'-completed');
|
|
this.applyFocus();
|
|
},
|
|
|
|
prepareBackdrop: function() {
|
|
if (this.backdrop && this.opened) {
|
|
if (!this.scrim.parentNode) {
|
|
document.body.appendChild(this.scrim);
|
|
this.scrim.style.zIndex = currentOverlayZ() - 1;
|
|
}
|
|
trackBackdrop(this);
|
|
}
|
|
},
|
|
|
|
renderBackdropOpened: function() {
|
|
if (this.backdrop && getBackdrops().length < 2) {
|
|
this.scrim.classList.toggle('core-opened', this.opened);
|
|
}
|
|
},
|
|
|
|
completeBackdrop: function() {
|
|
if (this.backdrop) {
|
|
trackBackdrop(this);
|
|
if (getBackdrops().length === 0) {
|
|
this.scrim.parentNode.removeChild(this.scrim);
|
|
}
|
|
}
|
|
},
|
|
|
|
preparePositioning: function() {
|
|
this.target.style.transition = this.target.style.webkitTransition = 'none';
|
|
this.target.style.transform = this.target.style.webkitTransform = 'none';
|
|
this.target.style.display = '';
|
|
},
|
|
|
|
discoverDimensions: function() {
|
|
if (this.dimensions) {
|
|
return;
|
|
}
|
|
var target = getComputedStyle(this.target);
|
|
var sizer = getComputedStyle(this.sizingTarget);
|
|
this.dimensions = {
|
|
position: {
|
|
v: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
|
|
'bottom' : null),
|
|
h: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
|
|
'right' : null),
|
|
css: target.position
|
|
},
|
|
size: {
|
|
v: sizer.maxHeight !== 'none',
|
|
h: sizer.maxWidth !== 'none'
|
|
},
|
|
margin: {
|
|
top: parseInt(target.marginTop) || 0,
|
|
right: parseInt(target.marginRight) || 0,
|
|
bottom: parseInt(target.marginBottom) || 0,
|
|
left: parseInt(target.marginLeft) || 0
|
|
}
|
|
};
|
|
},
|
|
|
|
finishPositioning: function(target) {
|
|
this.target.style.display = 'none';
|
|
this.target.style.transform = this.target.style.webkitTransform = '';
|
|
// force layout to avoid application of transform
|
|
this.target.offsetWidth;
|
|
this.target.style.transition = this.target.style.webkitTransition = '';
|
|
},
|
|
|
|
getTransition: function(name) {
|
|
return this.meta.byId(name || this.transition);
|
|
},
|
|
|
|
getFocusNode: function() {
|
|
return this.target.querySelector('[autofocus]') || this.target;
|
|
},
|
|
|
|
applyFocus: function() {
|
|
var focusNode = this.getFocusNode();
|
|
if (this.opened) {
|
|
if (!this.autoFocusDisabled) {
|
|
focusNode.focus();
|
|
}
|
|
} else {
|
|
focusNode.blur();
|
|
if (currentOverlay() == this) {
|
|
console.warn('Current core-overlay is attempting to focus itself as next! (bug)');
|
|
} else {
|
|
focusOverlay();
|
|
}
|
|
}
|
|
},
|
|
|
|
positionTarget: function() {
|
|
// fire positioning event
|
|
this.fire('core-overlay-position', {target: this.target,
|
|
sizingTarget: this.sizingTarget, opened: this.opened});
|
|
if (!this.dimensions.position.v) {
|
|
this.target.style.top = '0px';
|
|
}
|
|
if (!this.dimensions.position.h) {
|
|
this.target.style.left = '0px';
|
|
}
|
|
},
|
|
|
|
updateTargetDimensions: function() {
|
|
this.sizeTarget();
|
|
this.repositionTarget();
|
|
},
|
|
|
|
sizeTarget: function() {
|
|
this.sizingTarget.style.boxSizing = 'border-box';
|
|
var dims = this.dimensions;
|
|
var rect = this.target.getBoundingClientRect();
|
|
if (!dims.size.v) {
|
|
this.sizeDimension(rect, dims.position.v, 'top', 'bottom', 'Height');
|
|
}
|
|
if (!dims.size.h) {
|
|
this.sizeDimension(rect, dims.position.h, 'left', 'right', 'Width');
|
|
}
|
|
},
|
|
|
|
sizeDimension: function(rect, positionedBy, start, end, extent) {
|
|
var dims = this.dimensions;
|
|
var flip = (positionedBy === end);
|
|
var m = flip ? start : end;
|
|
var ws = window['inner' + extent];
|
|
var o = dims.margin[m] + (flip ? ws - rect[end] :
|
|
rect[start]);
|
|
var offset = 'offset' + extent;
|
|
var o2 = this.target[offset] - this.sizingTarget[offset];
|
|
this.sizingTarget.style['max' + extent] = (ws - o - o2) + 'px';
|
|
},
|
|
|
|
// vertically and horizontally center if not positioned
|
|
repositionTarget: function() {
|
|
// only center if position fixed.
|
|
if (this.dimensions.position.css !== 'fixed') {
|
|
return;
|
|
}
|
|
if (!this.dimensions.position.v) {
|
|
var t = (window.innerHeight - this.target.offsetHeight) / 2;
|
|
t -= this.dimensions.margin.top;
|
|
this.target.style.top = t + 'px';
|
|
}
|
|
|
|
if (!this.dimensions.position.h) {
|
|
var l = (window.innerWidth - this.target.offsetWidth) / 2;
|
|
l -= this.dimensions.margin.left;
|
|
this.target.style.left = l + 'px';
|
|
}
|
|
},
|
|
|
|
resetTargetDimensions: function() {
|
|
if (!this.dimensions || !this.dimensions.size.v) {
|
|
this.sizingTarget.style.maxHeight = '';
|
|
this.target.style.top = '';
|
|
}
|
|
if (!this.dimensions || !this.dimensions.size.h) {
|
|
this.sizingTarget.style.maxWidth = '';
|
|
this.target.style.left = '';
|
|
}
|
|
this.dimensions = null;
|
|
},
|
|
|
|
tapHandler: function(e) {
|
|
// closeSelector takes precedence since closeAttribute has a default non-null value.
|
|
if (e.target &&
|
|
(this.closeSelector && e.target.matches(this.closeSelector)) ||
|
|
(this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) {
|
|
this.toggle();
|
|
} else {
|
|
if (this.autoCloseJob) {
|
|
this.autoCloseJob.stop();
|
|
this.autoCloseJob = null;
|
|
}
|
|
}
|
|
},
|
|
|
|
// We use the traditional approach of capturing events on document
|
|
// to to determine if the overlay needs to close. However, due to
|
|
// ShadowDOM event retargeting, the event target is not useful. Instead
|
|
// of using it, we attempt to close asynchronously and prevent the close
|
|
// if a tap event is immediately heard on the target.
|
|
// TODO(sorvell): This approach will not work with modal. For
|
|
// this we need a scrim.
|
|
captureHandler: function(e) {
|
|
if (!this.autoCloseDisabled && (currentOverlay() == this)) {
|
|
this.autoCloseJob = this.job(this.autoCloseJob, function() {
|
|
this.close();
|
|
});
|
|
}
|
|
},
|
|
|
|
keydownHandler: function(e) {
|
|
if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) {
|
|
this.close();
|
|
e.stopPropagation();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Extensions of core-overlay should implement the `resizeHandler`
|
|
* method to adjust the size and position of the overlay when the
|
|
* browser window resizes.
|
|
* @method resizeHandler
|
|
*/
|
|
resizeHandler: function() {
|
|
this.updateTargetDimensions();
|
|
},
|
|
|
|
// TODO(sorvell): these utility methods should not be here.
|
|
addElementListenerList: function(node, events) {
|
|
for (var i in events) {
|
|
this.addElementListener(node, i, events[i]);
|
|
}
|
|
},
|
|
|
|
removeElementListenerList: function(node, events) {
|
|
for (var i in events) {
|
|
this.removeElementListener(node, i, events[i]);
|
|
}
|
|
},
|
|
|
|
enableElementListener: function(enable, node, event, methodName, capture) {
|
|
if (enable) {
|
|
this.addElementListener(node, event, methodName, capture);
|
|
} else {
|
|
this.removeElementListener(node, event, methodName, capture);
|
|
}
|
|
},
|
|
|
|
addElementListener: function(node, event, methodName, capture) {
|
|
var fn = this._makeBoundListener(methodName);
|
|
if (node && fn) {
|
|
Polymer.addEventListener(node, event, fn, capture);
|
|
}
|
|
},
|
|
|
|
removeElementListener: function(node, event, methodName, capture) {
|
|
var fn = this._makeBoundListener(methodName);
|
|
if (node && fn) {
|
|
Polymer.removeEventListener(node, event, fn, capture);
|
|
}
|
|
},
|
|
|
|
_makeBoundListener: function(methodName) {
|
|
var self = this, method = this[methodName];
|
|
if (!method) {
|
|
return;
|
|
}
|
|
var bound = '_bound' + methodName;
|
|
if (!this[bound]) {
|
|
this[bound] = function(e) {
|
|
method.call(self, e);
|
|
};
|
|
}
|
|
return this[bound];
|
|
}
|
|
|
|
}, Polymer.CoreResizer));
|
|
|
|
// TODO(sorvell): This should be an element with private state so it can
|
|
// be independent of overlay.
|
|
// track overlays for z-index and focus managemant
|
|
var overlays = [];
|
|
function addOverlay(overlay) {
|
|
var z0 = currentOverlayZ();
|
|
overlays.push(overlay);
|
|
var z1 = currentOverlayZ();
|
|
if (z1 <= z0) {
|
|
applyOverlayZ(overlay, z0);
|
|
}
|
|
}
|
|
|
|
function removeOverlay(overlay) {
|
|
var i = overlays.indexOf(overlay);
|
|
if (i >= 0) {
|
|
overlays.splice(i, 1);
|
|
setZ(overlay, '');
|
|
}
|
|
}
|
|
|
|
function applyOverlayZ(overlay, aboveZ) {
|
|
setZ(overlay.target, aboveZ + 2);
|
|
}
|
|
|
|
function setZ(element, z) {
|
|
element.style.zIndex = z;
|
|
}
|
|
|
|
function currentOverlay() {
|
|
return overlays[overlays.length-1];
|
|
}
|
|
|
|
var DEFAULT_Z = 10;
|
|
|
|
function currentOverlayZ() {
|
|
var z;
|
|
var current = currentOverlay();
|
|
if (current) {
|
|
var z1 = window.getComputedStyle(current.target).zIndex;
|
|
if (!isNaN(z1)) {
|
|
z = Number(z1);
|
|
}
|
|
}
|
|
return z || DEFAULT_Z;
|
|
}
|
|
|
|
function focusOverlay() {
|
|
var current = currentOverlay();
|
|
// We have to be careful to focus the next overlay _after_ any current
|
|
// transitions are complete (due to the state being toggled prior to the
|
|
// transition). Otherwise, we risk infinite recursion when a transitioning
|
|
// (closed) overlay becomes the current overlay.
|
|
//
|
|
// NOTE: We make the assumption that any overlay that completes a transition
|
|
// will call into focusOverlay to kick the process back off. Currently:
|
|
// transitionend -> applyFocus -> focusOverlay.
|
|
if (current && !current.transitioning) {
|
|
current.applyFocus();
|
|
}
|
|
}
|
|
|
|
var backdrops = [];
|
|
function trackBackdrop(element) {
|
|
if (element.opened) {
|
|
backdrops.push(element);
|
|
} else {
|
|
var i = backdrops.indexOf(element);
|
|
if (i >= 0) {
|
|
backdrops.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getBackdrops() {
|
|
return backdrops;
|
|
}
|
|
})();
|
|
</script>
|
|
</polymer-element>
|