| Index: pkg/polymer/lib/elements/polymer-overlay/polymer-overlay.html
|
| diff --git a/pkg/polymer/lib/elements/polymer-overlay/polymer-overlay.html b/pkg/polymer/lib/elements/polymer-overlay/polymer-overlay.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..09aa4048974335479b213fcc160b6fb5041b9da1
|
| --- /dev/null
|
| +++ b/pkg/polymer/lib/elements/polymer-overlay/polymer-overlay.html
|
| @@ -0,0 +1,367 @@
|
| +<!--
|
| +Copyright 2013 The Polymer Authors. All rights reserved.
|
| +Use of this source code is governed by a BSD-style
|
| +license that can be found in the LICENSE file.
|
| +-->
|
| +<link rel="import" href="../polymer/polymer.html">
|
| +
|
| +<link rel="import" href="../polymer-key-helper/polymer-key-helper.html">
|
| +
|
| +<!--
|
| +/**
|
| + * @module Polymer Elements
|
| + */
|
| +/**
|
| + * polymer-overlay displays overlayed on top of other content. It starts
|
| + * out hidden and is displayed by setting it's opened property to true.
|
| + * A polymer-overlay's opened state can be toggled by calling the toggle
|
| + * method.
|
| + *
|
| + * It's common to want a polymer-overlay to animate to its opened
|
| + * position. A number of helper css classes provide some basic open/close
|
| + * animations. For example, assigning the class polymer-overlay-fade to a
|
| + * polymer-overlay will make it fade into and out of view as it opens and
|
| + * closes. Note, if multiple polymer-overlay's are opened, they should
|
| + * stack on top of each other.
|
| + *
|
| + * Styling: The size and position of a polymer-overlay should be setup
|
| + * via css.
|
| + * polymer-overlay is natually sized around its content. When a
|
| + * polymer-overlay is opened it is shown and the 'opened' class is added
|
| + * to it. This is typically where css transitions and animations are
|
| + * applied. When the polymer-overlay is closed, the 'opened' class is
|
| + * removed and a 'closing' class is added. Use 'closing' to customize
|
| + * the closing animation.
|
| + *
|
| + * Classes for animating polymer-overlay:
|
| + *
|
| + * * polymer-overlay-fade: fade in/out when opened/closed
|
| + * * polymer-overlay-scale-slideup: open: fade in and shrink;
|
| + * close: slide up
|
| + * * polymer-overlay-shake: open: fly in and shake; close: shake and
|
| + * fly out.
|
| + *
|
| + * It's common to use polymer-overlay to gather user input, for example
|
| + * a login dialog. To facilitate this, polymer-overlay supports automatic
|
| + * focusing of a specific element when it's opened. The element to be
|
| + * focused should be given an autofocus attribute.
|
| + *
|
| + * An element that should close the polymer-overlay will automatically
|
| + * do so if it is given the overlay-toggle attribute. Please note that
|
| + * polymer-overlay will close whenever the user taps outside it or
|
| + * presses the escape key. The behavior can be turned off via the
|
| + * autoCloseDisabled property.
|
| + *
|
| + * <div>
|
| + * <polymer-overlay></polymer-overlay>
|
| + * <h2>Dialog</h2>
|
| + * <input placeholder="say something..." autofocus>
|
| + * <div>I agree with this wholeheartedly.</div>
|
| + * <button overlay-toggle>OK</button>
|
| + * </div>
|
| + *
|
| + * @class polymer-overlay
|
| + */
|
| +/**
|
| + * Fired when the polymer-overlay opened property is set.
|
| + *
|
| + * @event polymer-overlay-open
|
| + * @param {Object} inDetail
|
| + * @param {Object} inDetail.opened the opened state
|
| + */
|
| +-->
|
| +<polymer-element name="polymer-overlay" attributes="target opened autoCloseDisabled transitions">
|
| + <template>
|
| + <link rel="stylesheet" polymer-scope="controller" href="polymer-overlay.css">
|
| + <link rel="stylesheet" href="polymer-overlay.css">
|
| + <polymer-key-helper id="keyHelper"></polymer-key-helper>
|
| + </template>
|
| + <script>
|
| + (function() {
|
| + // track overlays for z-index and focus managemant
|
| + var overlays = [];
|
| + var trackOverlays = function(inOverlay) {
|
| + if (inOverlay.opened) {
|
| + //var overlayZ = window.getComputedStyle(inOverlay.target).zIndex;
|
| + //var z0 = Math.max(currentOverlayZ(), overlayZ);
|
| + var z0 = currentOverlayZ();
|
| + overlays.push(inOverlay);
|
| + var z1 = currentOverlayZ();
|
| + if (z1 <= z0) {
|
| + applyOverlayZ(inOverlay, z0);
|
| + }
|
| + } else {
|
| + var i = overlays.indexOf(inOverlay);
|
| + if (i >= 0) {
|
| + overlays.splice(i, 1);
|
| + setZ(inOverlay, null);
|
| + }
|
| + }
|
| + }
|
| +
|
| + var applyOverlayZ = function(inOverlay, inAboveZ) {
|
| + setZ(inOverlay.target, inAboveZ + 2);
|
| + }
|
| +
|
| + var setZ = function(inNode, inZ) {
|
| + inNode.style.zIndex = inZ;
|
| + }
|
| +
|
| + var currentOverlay = function() {
|
| + return overlays[overlays.length-1];
|
| + }
|
| +
|
| + var DEFAULT_Z = 10;
|
| +
|
| + var currentOverlayZ = function() {
|
| + var z;
|
| + var current = currentOverlay();
|
| + if (current) {
|
| + var z1 = window.getComputedStyle(current.target).zIndex;
|
| + if (!isNaN(z1)) {
|
| + z = Number(z1);
|
| + }
|
| + }
|
| + return z || DEFAULT_Z;
|
| + }
|
| +
|
| + var focusOverlay = function() {
|
| + var current = currentOverlay();
|
| + if (current) {
|
| + current.applyFocus();
|
| + }
|
| + }
|
| +
|
| + Polymer('polymer-overlay', {
|
| + /**
|
| + * The target element.
|
| + *
|
| + * @attribute target
|
| + * @type Object
|
| + */
|
| + target: null,
|
| + /**
|
| + * Set opened to true to show an overlay and to false to hide it.
|
| + * A polymer-overlay may be made intially opened by setting its
|
| + * opened attribute.
|
| + * @attribute opened
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + opened: 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 the animation to play when the overlay is
|
| + * opened/closed. It can be an array of two animations
|
| + * [opening, closing], a single animation, an array of two strings, or
|
| + * a string. The strings should the tag names of custom elements
|
| + * descending from a polymer-animation. In the case of a single
|
| + * animation the closing animation is the opening animation played
|
| + * backwards.
|
| + * @attribute transitions
|
| + * @type polymer-animation
|
| + * @type Array<polymer-animation>
|
| + * @type string
|
| + * @type Array<string>
|
| + */
|
| + transitions: null,
|
| + timeout: 1000,
|
| + captureEventType: 'tap',
|
| + ready: function() {
|
| + if (this.tabIndex === undefined) {
|
| + this.tabIndex = -1;
|
| + }
|
| + this.setAttribute('touch-action', 'none');
|
| + },
|
| + enteredView: function() {
|
| + this.installControllerStyles();
|
| + },
|
| + /**
|
| + * Toggle the opened state of the overlay.
|
| + * @method toggle
|
| + */
|
| + toggle: function() {
|
| + this.opened = !this.opened;
|
| + },
|
| + targetChanged: function(old) {
|
| + if (this.target) {
|
| + if (this.target.tabIndex === undefined) {
|
| + this.target.tabIndex = -1;
|
| + }
|
| + this.target.classList.add('polymer-overlay');
|
| + this.addListeners(this.target);
|
| + }
|
| + if (old) {
|
| + old.classList.remove('polymer-overlay');
|
| + this.removeListeners(old);
|
| + }
|
| + },
|
| + listeners: {
|
| + 'tap': 'tapHandler',
|
| + 'keydown': 'keydownHandler'
|
| + },
|
| + addListeners: function(node) {
|
| + for (e in this.listeners) {
|
| + node.addEventListener(e, this[this.listeners[e]].bind(this));
|
| + }
|
| + },
|
| + removeListeners: function(node) {
|
| + for (e in this.listeners) {
|
| + node.removeEventListener(e, this[this.listeners[e]].bind(this));
|
| + }
|
| + },
|
| + openedChanged: function() {
|
| + this.renderOpened();
|
| + trackOverlays(this);
|
| + this.async(function() {
|
| + if (!this.autoCloseDisabled) {
|
| + this.enableCaptureHandler(this.opened);
|
| + }
|
| + });
|
| + this.enableResizeHandler(this.opened);
|
| + this.fire('polymer-overlay-open', this.opened);
|
| + },
|
| + enableHandler: function(inEnable, inMethodName, inNode, inEventName, inCapture) {
|
| + var m = 'bound' + inMethodName;
|
| + this[m] = this[m] || this[inMethodName].bind(this);
|
| +
|
| + inNode[inEnable ? 'addEventListener' : 'removeEventListener'](
|
| + inEventName, this[m], inCapture);
|
| + },
|
| + enableResizeHandler: function(inEnable) {
|
| + this.enableHandler(inEnable, 'resizeHandler', window,
|
| + 'resize');
|
| + },
|
| + enableCaptureHandler: function(inEnable) {
|
| + this.enableHandler(inEnable, 'captureHandler', document,
|
| + this.captureEventType, true);
|
| + },
|
| + getFocusNode: function() {
|
| + return this.target.querySelector('[autofocus]') || this.target;
|
| + },
|
| + // TODO(sorvell): nodes stay focused when they become un-focusable
|
| + // due to an ancestory becoming display: none; file bug.
|
| + applyFocus: function() {
|
| + var focusNode = this.getFocusNode();
|
| + if (this.opened) {
|
| + focusNode.focus();
|
| + } else {
|
| + focusNode.blur();
|
| + focusOverlay();
|
| + }
|
| + },
|
| + positionTarget: function() {
|
| + if (this.opened) {
|
| + // vertically and horizontally center if not positioned
|
| + var computedStyle = getComputedStyle(this.target);
|
| + if (computedStyle.top === 'auto' && computedStyle.bottom === 'auto') {
|
| + this.target.style.top = ((window.innerHeight - this.target.getBoundingClientRect().height) / 2) + 'px';
|
| + }
|
| + if (computedStyle.left === 'auto' && computedStyle.right === 'auto') {
|
| + this.target.style.left = ((window.innerWidth - this.target.getBoundingClientRect().width) / 2) + 'px';
|
| + }
|
| + }
|
| + },
|
| + resetTargetPosition: function() {
|
| + this.target.style.top = this.target.style.left = null;
|
| + },
|
| + get transition() {
|
| + return (!Array.isArray(this.transitions) && this.transitions
|
| + || this.opened && this.transitions && this.transitions[0]
|
| + || !this.opened && this.transitions && this.transitions[1]);
|
| + },
|
| + applyTransition: function() {
|
| + var animation = typeof this.transition === 'string' ?
|
| + document.createElement(this.transition) : this.transition;
|
| + // FIXME: Apply a default duration.
|
| + if ((!animation.duration || animation.duration === 'auto') && !animation.type) {
|
| + animation.duration = 0.3;
|
| + }
|
| + if (!animation.hasTarget()) {
|
| + animation.target = this.target;
|
| + }
|
| + // Make the overlay visible while the animation is running.
|
| + var transition = new ParGroup([
|
| + animation.apply(),
|
| + new Animation(this.target, [{'visibility': 'visible', 'display':'block'}])
|
| + ], {fill: 'none'});
|
| + transition.onend = this.completeOpening.bind(this);
|
| + this.target.classList.add('animating');
|
| + document.timeline.play(transition);
|
| + },
|
| + renderOpened: function() {
|
| + this.target.classList.add('revealed');
|
| + // continue styling after delay so display state can change
|
| + // without aborting transitions
|
| + this.async('continueRenderOpened');
|
| + },
|
| + continueRenderOpened: function() {
|
| + this.positionTarget();
|
| + if (this.transition) {
|
| + this.applyTransition();
|
| + // FIXME: Apply the class after the animation starts playing to
|
| + // prevent a flicker at the end of the animation. Should be handled
|
| + // in polymer-animation-start event but not working in polyfill
|
| + this.async(function() {
|
| + this.target.classList.toggle('opened', this.opened);
|
| + }, null, 100);
|
| + } else {
|
| + this.target.classList.toggle('opened', this.opened);
|
| + this.async('completeOpening');
|
| + }
|
| + },
|
| + completeOpening: function() {
|
| + this.target.classList.remove('animating');
|
| + this.target.classList.toggle('revealed', this.opened);
|
| + if (!this.opened) {
|
| + this.resetTargetPosition();
|
| + }
|
| + this.applyFocus();
|
| + },
|
| + tapHandler: function(e) {
|
| + if (e.target && e.target.hasAttribute('overlay-toggle')) {
|
| + this.toggle();
|
| + } else {
|
| + if (this.autoCloseJob) {
|
| + this.autoCloseJob.stop();
|
| + this.autoCloseJob = null;
|
| + }
|
| + }
|
| + },
|
| + // TODO(sorvell): This approach will not work with modal. For
|
| + // this we need a scrim.
|
| + captureHandler: function(e) {
|
| + if (!this.autoCloseDisabled && (currentOverlay() == this) && (this
|
| + != e.target) && !(this.contains(e.target))) {
|
| + this.autoCloseJob = this.job(this.autoCloseJob, function() {
|
| + this.opened = false;
|
| + });
|
| + }
|
| + },
|
| + keydownHandler: function(e) {
|
| + if (!this.autoCloseDisabled && (e.keyCode == this.$.keyHelper.ESCAPE_KEY)) {
|
| + this.opened = false;
|
| + e.stopPropagation();
|
| + e.cancelBubble = true;
|
| + }
|
| + },
|
| + /**
|
| + * Extensions of polymer-overlay should implement the resizeHandler
|
| + * method to adjust the size and position of the overlay when the
|
| + * browser window resizes.
|
| + * @method resizeHandler
|
| + */
|
| + resizeHandler: function() {
|
| + }
|
| + });
|
| + })();
|
| + </script>
|
| +</polymer-element>
|
|
|