| Index: third_party/polymer/components/core-overlay/core-overlay.html
|
| diff --git a/third_party/polymer/components/core-overlay/core-overlay.html b/third_party/polymer/components/core-overlay/core-overlay.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..af0136beb00e93e5c8515eb7fa78aff15668e734
|
| --- /dev/null
|
| +++ b/third_party/polymer/components/core-overlay/core-overlay.html
|
| @@ -0,0 +1,676 @@
|
| +<!--
|
| +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-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. If the target's style.top and style.left are unset, the
|
| +target will be centered. The size of the target is constrained to be no larger
|
| +than the window dimensions. The `margin` property specifies the extra amount
|
| +of space that should be reserved 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
|
| +@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
|
| +-->
|
| +
|
| +<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('core-overlay', {
|
| +
|
| + 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,
|
| +
|
| + /**
|
| + * 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: '',
|
| +
|
| + /**
|
| + * A `core-overlay` target's size is constrained to the window size.
|
| + * The `margin` property specifies a pixel amount around the overlay
|
| + * that will be reserved. It's useful for ensuring that, for example,
|
| + * a shadow displayed outside the target will always be visible.
|
| + *
|
| + * @attribute margin
|
| + * @type number
|
| + * @default 0
|
| + */
|
| + margin: 0,
|
| +
|
| + /**
|
| + * 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'
|
| + },
|
| +
|
| + 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
|
| + Platform.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';
|
| + }
|
| + 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 = '';
|
| + }
|
| + },
|
| +
|
| + // 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;
|
| + }
|
| + this.target.__overlaySetup = true;
|
| + this.target.style.display = '';
|
| + var transition = this.getTransition();
|
| + if (transition) {
|
| + transition.setup(this.target);
|
| + }
|
| + var computed = getComputedStyle(this.target);
|
| + this.targetStyle = {
|
| + position: computed.position === 'static' ? 'fixed' :
|
| + computed.position
|
| + }
|
| + if (!transition) {
|
| + this.target.style.position = this.targetStyle.position;
|
| + this.target.style.outline = 'none';
|
| + }
|
| + this.target.style.display = 'none';
|
| + },
|
| +
|
| + openedChanged: function() {
|
| + this.transitioning = true;
|
| + this.ensureTargetSetup();
|
| + this.prepareRenderOpened();
|
| + // continue styling after delay so display state can change
|
| + // without aborting transitions
|
| + // note: we wait a full frame so that transition changes executed
|
| + // during measuring do not cause transition
|
| + this.async(function() {
|
| + this.target.style.display = '';
|
| + this.async('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) {
|
| + // TODO(sorvell): force SD Polyfill to render
|
| + forcePolyfillRender(this.target);
|
| + if (!this._shouldPosition) {
|
| + this.target.style.position = 'absolute';
|
| + var computed = getComputedStyle(this.target);
|
| + var t = (computed.top === 'auto' && computed.bottom === 'auto');
|
| + var l = (computed.left === 'auto' && computed.right === 'auto');
|
| + this.target.style.position = this.targetStyle.position;
|
| + this._shouldPosition = {top: t, left: l};
|
| + }
|
| + // if we are showing, then take care when measuring
|
| + this.prepareMeasure(this.target);
|
| + this.updateTargetDimensions();
|
| + this.finishMeasure(this.target);
|
| + 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() {
|
| + 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.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);
|
| + }
|
| + }
|
| + },
|
| +
|
| + prepareMeasure: function(target) {
|
| + target.style.transition = target.style.webkitTransition = 'none';
|
| + target.style.transform = target.style.webkitTransform = 'none';
|
| + target.style.display = '';
|
| + },
|
| +
|
| + finishMeasure: function(target) {
|
| + target.style.display = 'none';
|
| + target.style.transform = target.style.webkitTransform = '';
|
| + target.style.transition = target.style.webkitTransition = '';
|
| + },
|
| +
|
| + getTransition: function() {
|
| + return this.meta.byId(this.transition);
|
| + },
|
| +
|
| + getFocusNode: function() {
|
| + return this.target.querySelector('[autofocus]') || this.target;
|
| + },
|
| +
|
| + applyFocus: function() {
|
| + var focusNode = this.getFocusNode();
|
| + if (this.opened) {
|
| + focusNode.focus();
|
| + } else {
|
| + focusNode.blur();
|
| + if (currentOverlay() == this) {
|
| + console.warn('Current core-overlay is attempting to focus itself as next! (bug)');
|
| + } else {
|
| + focusOverlay();
|
| + }
|
| + }
|
| + },
|
| +
|
| + updateTargetDimensions: function() {
|
| + this.positionTarget();
|
| + this.sizeTarget();
|
| + //
|
| + if (this.layered) {
|
| + var rect = this.target.getBoundingClientRect();
|
| + this.target.style.top = rect.top + 'px';
|
| + this.target.style.left = rect.left + 'px';
|
| + this.target.style.right = this.target.style.bottom = 'auto';
|
| + }
|
| + },
|
| +
|
| + sizeTarget: function() {
|
| + var sizer = this.sizingTarget || this.target;
|
| + var rect = sizer.getBoundingClientRect();
|
| + var mt = rect.top === this.margin ? this.margin : this.margin * 2;
|
| + var ml = rect.left === this.margin ? this.margin : this.margin * 2;
|
| + var h = window.innerHeight - rect.top - mt;
|
| + var w = window.innerWidth - rect.left - ml;
|
| + sizer.style.maxHeight = h + 'px';
|
| + sizer.style.maxWidth = w + 'px';
|
| + sizer.style.boxSizing = 'border-box';
|
| + },
|
| +
|
| + positionTarget: function() {
|
| + // vertically and horizontally center if not positioned
|
| + if (this._shouldPosition.top) {
|
| + var t = Math.max((window.innerHeight -
|
| + this.target.offsetHeight - this.margin*2) / 2, this.margin);
|
| + this.target.style.top = t + 'px';
|
| + }
|
| + if (this._shouldPosition.left) {
|
| + var l = Math.max((window.innerWidth -
|
| + this.target.offsetWidth - this.margin*2) / 2, this.margin);
|
| + this.target.style.left = l + 'px';
|
| + }
|
| + },
|
| +
|
| + resetTargetDimensions: function() {
|
| + this.target.style.top = this.target.style.left = '';
|
| + this.target.style.right = this.target.style.bottom = '';
|
| + this.target.style.width = this.target.style.height = '';
|
| + this._shouldPosition = 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];
|
| + },
|
| + });
|
| +
|
| + function forcePolyfillRender(target) {
|
| + if (window.ShadowDOMPolyfill) {
|
| + target.offsetHeight;
|
| + }
|
| + }
|
| +
|
| + // 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>
|
|
|