PageRenderTime 52ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/bower_components/iron-overlay-behavior/iron-overlay-manager.html

https://gitlab.com/Andreezzy/messenger
HTML | 362 lines | 316 code | 37 blank | 9 comment | 0 complexity | 39ab6a0dca8aa638e63bb7d995f5cb7f MD5 | raw file
  1. <!--
  2. @license
  3. Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
  4. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
  5. The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
  6. The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
  7. Code distributed by Google as part of the polymer project is also
  8. subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
  9. -->
  10. <link rel="import" href="../polymer/polymer.html">
  11. <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
  12. <link rel="import" href="iron-overlay-backdrop.html">
  13. <script>
  14. /**
  15. * @struct
  16. * @constructor
  17. * @private
  18. */
  19. Polymer.IronOverlayManagerClass = function() {
  20. /**
  21. * Used to keep track of the opened overlays.
  22. * @private {Array<Element>}
  23. */
  24. this._overlays = [];
  25. /**
  26. * iframes have a default z-index of 100,
  27. * so this default should be at least that.
  28. * @private {number}
  29. */
  30. this._minimumZ = 101;
  31. /**
  32. * Memoized backdrop element.
  33. * @private {Element|null}
  34. */
  35. this._backdropElement = null;
  36. // Enable document-wide tap recognizer.
  37. Polymer.Gestures.add(document, 'tap', this._onCaptureClick.bind(this));
  38. document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
  39. document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true);
  40. };
  41. Polymer.IronOverlayManagerClass.prototype = {
  42. constructor: Polymer.IronOverlayManagerClass,
  43. /**
  44. * The shared backdrop element.
  45. * @type {!Element} backdropElement
  46. */
  47. get backdropElement() {
  48. if (!this._backdropElement) {
  49. this._backdropElement = document.createElement('iron-overlay-backdrop');
  50. }
  51. return this._backdropElement;
  52. },
  53. /**
  54. * The deepest active element.
  55. * @type {!Element} activeElement the active element
  56. */
  57. get deepActiveElement() {
  58. // document.activeElement can be null
  59. // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
  60. // In case of null, default it to document.body.
  61. var active = document.activeElement || document.body;
  62. while (active.root && Polymer.dom(active.root).activeElement) {
  63. active = Polymer.dom(active.root).activeElement;
  64. }
  65. return active;
  66. },
  67. /**
  68. * Brings the overlay at the specified index to the front.
  69. * @param {number} i
  70. * @private
  71. */
  72. _bringOverlayAtIndexToFront: function(i) {
  73. var overlay = this._overlays[i];
  74. if (!overlay) {
  75. return;
  76. }
  77. var lastI = this._overlays.length - 1;
  78. var currentOverlay = this._overlays[lastI];
  79. // Ensure always-on-top overlay stays on top.
  80. if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)) {
  81. lastI--;
  82. }
  83. // If already the top element, return.
  84. if (i >= lastI) {
  85. return;
  86. }
  87. // Update z-index to be on top.
  88. var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
  89. if (this._getZ(overlay) <= minimumZ) {
  90. this._applyOverlayZ(overlay, minimumZ);
  91. }
  92. // Shift other overlays behind the new on top.
  93. while (i < lastI) {
  94. this._overlays[i] = this._overlays[i + 1];
  95. i++;
  96. }
  97. this._overlays[lastI] = overlay;
  98. },
  99. /**
  100. * Adds the overlay and updates its z-index if it's opened, or removes it if it's closed.
  101. * Also updates the backdrop z-index.
  102. * @param {!Element} overlay
  103. */
  104. addOrRemoveOverlay: function(overlay) {
  105. if (overlay.opened) {
  106. this.addOverlay(overlay);
  107. } else {
  108. this.removeOverlay(overlay);
  109. }
  110. },
  111. /**
  112. * Tracks overlays for z-index and focus management.
  113. * Ensures the last added overlay with always-on-top remains on top.
  114. * @param {!Element} overlay
  115. */
  116. addOverlay: function(overlay) {
  117. var i = this._overlays.indexOf(overlay);
  118. if (i >= 0) {
  119. this._bringOverlayAtIndexToFront(i);
  120. this.trackBackdrop();
  121. return;
  122. }
  123. var insertionIndex = this._overlays.length;
  124. var currentOverlay = this._overlays[insertionIndex - 1];
  125. var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ);
  126. var newZ = this._getZ(overlay);
  127. // Ensure always-on-top overlay stays on top.
  128. if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)) {
  129. // This bumps the z-index of +2.
  130. this._applyOverlayZ(currentOverlay, minimumZ);
  131. insertionIndex--;
  132. // Update minimumZ to match previous overlay's z-index.
  133. var previousOverlay = this._overlays[insertionIndex - 1];
  134. minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ);
  135. }
  136. // Update z-index and insert overlay.
  137. if (newZ <= minimumZ) {
  138. this._applyOverlayZ(overlay, minimumZ);
  139. }
  140. this._overlays.splice(insertionIndex, 0, overlay);
  141. this.trackBackdrop();
  142. },
  143. /**
  144. * @param {!Element} overlay
  145. */
  146. removeOverlay: function(overlay) {
  147. var i = this._overlays.indexOf(overlay);
  148. if (i === -1) {
  149. return;
  150. }
  151. this._overlays.splice(i, 1);
  152. this.trackBackdrop();
  153. },
  154. /**
  155. * Returns the current overlay.
  156. * @return {Element|undefined}
  157. */
  158. currentOverlay: function() {
  159. var i = this._overlays.length - 1;
  160. return this._overlays[i];
  161. },
  162. /**
  163. * Returns the current overlay z-index.
  164. * @return {number}
  165. */
  166. currentOverlayZ: function() {
  167. return this._getZ(this.currentOverlay());
  168. },
  169. /**
  170. * Ensures that the minimum z-index of new overlays is at least `minimumZ`.
  171. * This does not effect the z-index of any existing overlays.
  172. * @param {number} minimumZ
  173. */
  174. ensureMinimumZ: function(minimumZ) {
  175. this._minimumZ = Math.max(this._minimumZ, minimumZ);
  176. },
  177. focusOverlay: function() {
  178. var current = /** @type {?} */ (this.currentOverlay());
  179. if (current) {
  180. current._applyFocus();
  181. }
  182. },
  183. /**
  184. * Updates the backdrop z-index.
  185. */
  186. trackBackdrop: function() {
  187. var overlay = this._overlayWithBackdrop();
  188. // Avoid creating the backdrop if there is no overlay with backdrop.
  189. if (!overlay && !this._backdropElement) {
  190. return;
  191. }
  192. this.backdropElement.style.zIndex = this._getZ(overlay) - 1;
  193. this.backdropElement.opened = !!overlay;
  194. },
  195. /**
  196. * @return {Array<Element>}
  197. */
  198. getBackdrops: function() {
  199. var backdrops = [];
  200. for (var i = 0; i < this._overlays.length; i++) {
  201. if (this._overlays[i].withBackdrop) {
  202. backdrops.push(this._overlays[i]);
  203. }
  204. }
  205. return backdrops;
  206. },
  207. /**
  208. * Returns the z-index for the backdrop.
  209. * @return {number}
  210. */
  211. backdropZ: function() {
  212. return this._getZ(this._overlayWithBackdrop()) - 1;
  213. },
  214. /**
  215. * Returns the first opened overlay that has a backdrop.
  216. * @return {Element|undefined}
  217. * @private
  218. */
  219. _overlayWithBackdrop: function() {
  220. for (var i = 0; i < this._overlays.length; i++) {
  221. if (this._overlays[i].withBackdrop) {
  222. return this._overlays[i];
  223. }
  224. }
  225. },
  226. /**
  227. * Calculates the minimum z-index for the overlay.
  228. * @param {Element=} overlay
  229. * @private
  230. */
  231. _getZ: function(overlay) {
  232. var z = this._minimumZ;
  233. if (overlay) {
  234. var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay).zIndex);
  235. // Check if is a number
  236. // Number.isNaN not supported in IE 10+
  237. if (z1 === z1) {
  238. z = z1;
  239. }
  240. }
  241. return z;
  242. },
  243. /**
  244. * @param {!Element} element
  245. * @param {number|string} z
  246. * @private
  247. */
  248. _setZ: function(element, z) {
  249. element.style.zIndex = z;
  250. },
  251. /**
  252. * @param {!Element} overlay
  253. * @param {number} aboveZ
  254. * @private
  255. */
  256. _applyOverlayZ: function(overlay, aboveZ) {
  257. this._setZ(overlay, aboveZ + 2);
  258. },
  259. /**
  260. * Returns the deepest overlay in the path.
  261. * @param {Array<Element>=} path
  262. * @return {Element|undefined}
  263. * @suppress {missingProperties}
  264. * @private
  265. */
  266. _overlayInPath: function(path) {
  267. path = path || [];
  268. for (var i = 0; i < path.length; i++) {
  269. if (path[i]._manager === this) {
  270. return path[i];
  271. }
  272. }
  273. },
  274. /**
  275. * Ensures the click event is delegated to the right overlay.
  276. * @param {!Event} event
  277. * @private
  278. */
  279. _onCaptureClick: function(event) {
  280. var overlay = /** @type {?} */ (this.currentOverlay());
  281. // Check if clicked outside of top overlay.
  282. if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) {
  283. overlay._onCaptureClick(event);
  284. }
  285. },
  286. /**
  287. * Ensures the focus event is delegated to the right overlay.
  288. * @param {!Event} event
  289. * @private
  290. */
  291. _onCaptureFocus: function(event) {
  292. var overlay = /** @type {?} */ (this.currentOverlay());
  293. if (overlay) {
  294. overlay._onCaptureFocus(event);
  295. }
  296. },
  297. /**
  298. * Ensures TAB and ESC keyboard events are delegated to the right overlay.
  299. * @param {!Event} event
  300. * @private
  301. */
  302. _onCaptureKeyDown: function(event) {
  303. var overlay = /** @type {?} */ (this.currentOverlay());
  304. if (overlay) {
  305. if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
  306. overlay._onCaptureEsc(event);
  307. } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) {
  308. overlay._onCaptureTab(event);
  309. }
  310. }
  311. },
  312. /**
  313. * Returns if the overlay1 should be behind overlay2.
  314. * @param {!Element} overlay1
  315. * @param {!Element} overlay2
  316. * @return {boolean}
  317. * @suppress {missingProperties}
  318. * @private
  319. */
  320. _shouldBeBehindOverlay: function(overlay1, overlay2) {
  321. return !overlay1.alwaysOnTop && overlay2.alwaysOnTop;
  322. }
  323. };
  324. Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass();
  325. </script>