diff options
Diffstat (limited to 'catapult/third_party/polymer/components/iron-overlay-behavior/iron-overlay-manager.html')
-rw-r--r-- | catapult/third_party/polymer/components/iron-overlay-behavior/iron-overlay-manager.html | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/catapult/third_party/polymer/components/iron-overlay-behavior/iron-overlay-manager.html b/catapult/third_party/polymer/components/iron-overlay-behavior/iron-overlay-manager.html new file mode 100644 index 00000000..53fb265b --- /dev/null +++ b/catapult/third_party/polymer/components/iron-overlay-behavior/iron-overlay-manager.html @@ -0,0 +1,366 @@ +<!-- +@license +Copyright (c) 2015 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="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html"> +<link rel="import" href="iron-overlay-backdrop.html"> + +<script> + + /** + * @struct + * @constructor + * @private + */ + Polymer.IronOverlayManagerClass = function() { + /** + * Used to keep track of the opened overlays. + * @private {Array<Element>} + */ + this._overlays = []; + + /** + * iframes have a default z-index of 100, + * so this default should be at least that. + * @private {number} + */ + this._minimumZ = 101; + + /** + * Memoized backdrop element. + * @private {Element|null} + */ + this._backdropElement = null; + + // Enable document-wide tap recognizer. + // NOTE: Use useCapture=true to avoid accidentally prevention of the closing + // of an overlay via event.stopPropagation(). The only way to prevent + // closing of an overlay should be through its APIs. + // NOTE: enable tap on <html> to workaround Polymer/polymer#4459 + Polymer.Gestures.add(document.documentElement, 'tap', null); + document.addEventListener('tap', this._onCaptureClick.bind(this), true); + document.addEventListener('focus', this._onCaptureFocus.bind(this), true); + document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true); + }; + + Polymer.IronOverlayManagerClass.prototype = { + + constructor: Polymer.IronOverlayManagerClass, + + /** + * The shared backdrop element. + * @type {!Element} backdropElement + */ + get backdropElement() { + if (!this._backdropElement) { + this._backdropElement = document.createElement('iron-overlay-backdrop'); + } + return this._backdropElement; + }, + + /** + * The deepest active element. + * @type {!Element} activeElement the active element + */ + get deepActiveElement() { + // document.activeElement can be null + // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement + // In case of null, default it to document.body. + var active = document.activeElement || document.body; + while (active.root && Polymer.dom(active.root).activeElement) { + active = Polymer.dom(active.root).activeElement; + } + return active; + }, + + /** + * Brings the overlay at the specified index to the front. + * @param {number} i + * @private + */ + _bringOverlayAtIndexToFront: function(i) { + var overlay = this._overlays[i]; + if (!overlay) { + return; + } + var lastI = this._overlays.length - 1; + var currentOverlay = this._overlays[lastI]; + // Ensure always-on-top overlay stays on top. + if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)) { + lastI--; + } + // If already the top element, return. + if (i >= lastI) { + return; + } + // Update z-index to be on top. + var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); + if (this._getZ(overlay) <= minimumZ) { + this._applyOverlayZ(overlay, minimumZ); + } + + // Shift other overlays behind the new on top. + while (i < lastI) { + this._overlays[i] = this._overlays[i + 1]; + i++; + } + this._overlays[lastI] = overlay; + }, + + /** + * Adds the overlay and updates its z-index if it's opened, or removes it if it's closed. + * Also updates the backdrop z-index. + * @param {!Element} overlay + */ + addOrRemoveOverlay: function(overlay) { + if (overlay.opened) { + this.addOverlay(overlay); + } else { + this.removeOverlay(overlay); + } + }, + + /** + * Tracks overlays for z-index and focus management. + * Ensures the last added overlay with always-on-top remains on top. + * @param {!Element} overlay + */ + addOverlay: function(overlay) { + var i = this._overlays.indexOf(overlay); + if (i >= 0) { + this._bringOverlayAtIndexToFront(i); + this.trackBackdrop(); + return; + } + var insertionIndex = this._overlays.length; + var currentOverlay = this._overlays[insertionIndex - 1]; + var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); + var newZ = this._getZ(overlay); + + // Ensure always-on-top overlay stays on top. + if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)) { + // This bumps the z-index of +2. + this._applyOverlayZ(currentOverlay, minimumZ); + insertionIndex--; + // Update minimumZ to match previous overlay's z-index. + var previousOverlay = this._overlays[insertionIndex - 1]; + minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); + } + + // Update z-index and insert overlay. + if (newZ <= minimumZ) { + this._applyOverlayZ(overlay, minimumZ); + } + this._overlays.splice(insertionIndex, 0, overlay); + + this.trackBackdrop(); + }, + + /** + * @param {!Element} overlay + */ + removeOverlay: function(overlay) { + var i = this._overlays.indexOf(overlay); + if (i === -1) { + return; + } + this._overlays.splice(i, 1); + + this.trackBackdrop(); + }, + + /** + * Returns the current overlay. + * @return {Element|undefined} + */ + currentOverlay: function() { + var i = this._overlays.length - 1; + return this._overlays[i]; + }, + + /** + * Returns the current overlay z-index. + * @return {number} + */ + currentOverlayZ: function() { + return this._getZ(this.currentOverlay()); + }, + + /** + * Ensures that the minimum z-index of new overlays is at least `minimumZ`. + * This does not effect the z-index of any existing overlays. + * @param {number} minimumZ + */ + ensureMinimumZ: function(minimumZ) { + this._minimumZ = Math.max(this._minimumZ, minimumZ); + }, + + focusOverlay: function() { + var current = /** @type {?} */ (this.currentOverlay()); + if (current) { + current._applyFocus(); + } + }, + + /** + * Updates the backdrop z-index. + */ + trackBackdrop: function() { + var overlay = this._overlayWithBackdrop(); + // Avoid creating the backdrop if there is no overlay with backdrop. + if (!overlay && !this._backdropElement) { + return; + } + this.backdropElement.style.zIndex = this._getZ(overlay) - 1; + this.backdropElement.opened = !!overlay; + }, + + /** + * @return {Array<Element>} + */ + getBackdrops: function() { + var backdrops = []; + for (var i = 0; i < this._overlays.length; i++) { + if (this._overlays[i].withBackdrop) { + backdrops.push(this._overlays[i]); + } + } + return backdrops; + }, + + /** + * Returns the z-index for the backdrop. + * @return {number} + */ + backdropZ: function() { + return this._getZ(this._overlayWithBackdrop()) - 1; + }, + + /** + * Returns the first opened overlay that has a backdrop. + * @return {Element|undefined} + * @private + */ + _overlayWithBackdrop: function() { + for (var i = 0; i < this._overlays.length; i++) { + if (this._overlays[i].withBackdrop) { + return this._overlays[i]; + } + } + }, + + /** + * Calculates the minimum z-index for the overlay. + * @param {Element=} overlay + * @private + */ + _getZ: function(overlay) { + var z = this._minimumZ; + if (overlay) { + var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay).zIndex); + // Check if is a number + // Number.isNaN not supported in IE 10+ + if (z1 === z1) { + z = z1; + } + } + return z; + }, + + /** + * @param {!Element} element + * @param {number|string} z + * @private + */ + _setZ: function(element, z) { + element.style.zIndex = z; + }, + + /** + * @param {!Element} overlay + * @param {number} aboveZ + * @private + */ + _applyOverlayZ: function(overlay, aboveZ) { + this._setZ(overlay, aboveZ + 2); + }, + + /** + * Returns the deepest overlay in the path. + * @param {Array<Element>=} path + * @return {Element|undefined} + * @suppress {missingProperties} + * @private + */ + _overlayInPath: function(path) { + path = path || []; + for (var i = 0; i < path.length; i++) { + if (path[i]._manager === this) { + return path[i]; + } + } + }, + + /** + * Ensures the click event is delegated to the right overlay. + * @param {!Event} event + * @private + */ + _onCaptureClick: function(event) { + var overlay = /** @type {?} */ (this.currentOverlay()); + // Check if clicked outside of top overlay. + if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { + overlay._onCaptureClick(event); + } + }, + + /** + * Ensures the focus event is delegated to the right overlay. + * @param {!Event} event + * @private + */ + _onCaptureFocus: function(event) { + var overlay = /** @type {?} */ (this.currentOverlay()); + if (overlay) { + overlay._onCaptureFocus(event); + } + }, + + /** + * Ensures TAB and ESC keyboard events are delegated to the right overlay. + * @param {!Event} event + * @private + */ + _onCaptureKeyDown: function(event) { + var overlay = /** @type {?} */ (this.currentOverlay()); + if (overlay) { + if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) { + overlay._onCaptureEsc(event); + } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) { + overlay._onCaptureTab(event); + } + } + }, + + /** + * Returns if the overlay1 should be behind overlay2. + * @param {!Element} overlay1 + * @param {!Element} overlay2 + * @return {boolean} + * @suppress {missingProperties} + * @private + */ + _shouldBeBehindOverlay: function(overlay1, overlay2) { + return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; + } + }; + + Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); +</script> |