OLD | NEW |
(Empty) | |
| 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.g
ithub.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/CONTRI
BUTORS.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/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="../polymer/polymer.html"> |
| 12 <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html
"> |
| 13 |
| 14 <script> |
| 15 (function() { |
| 16 'use strict'; |
| 17 |
| 18 /** |
| 19 * The IronDropdownScrollManager is intended to provide a central source |
| 20 * of authority and control over which elements in a document are currently |
| 21 * allowed to scroll. |
| 22 */ |
| 23 |
| 24 Polymer.IronDropdownScrollManager = { |
| 25 |
| 26 /** |
| 27 * The current element that defines the DOM boundaries of the |
| 28 * scroll lock. This is always the most recently locking element. |
| 29 */ |
| 30 get currentLockingElement() { |
| 31 return this._lockingElements[this._lockingElements.length - 1]; |
| 32 }, |
| 33 |
| 34 |
| 35 /** |
| 36 * Returns true if the provided element is "scroll locked," which is to |
| 37 * say that it cannot be scrolled via pointer or keyboard interactions. |
| 38 * |
| 39 * @param {HTMLElement} element An HTML element instance which may or may |
| 40 * not be scroll locked. |
| 41 */ |
| 42 elementIsScrollLocked: function(element) { |
| 43 var currentLockingElement = this.currentLockingElement; |
| 44 var scrollLocked; |
| 45 |
| 46 if (this._hasCachedLockedElement(element)) { |
| 47 return true; |
| 48 } |
| 49 |
| 50 if (this._hasCachedUnlockedElement(element)) { |
| 51 return false; |
| 52 } |
| 53 |
| 54 scrollLocked = !!currentLockingElement && |
| 55 currentLockingElement !== element && |
| 56 !this._composedTreeContains(currentLockingElement, element); |
| 57 |
| 58 if (scrollLocked) { |
| 59 this._lockedElementCache.push(element); |
| 60 } else { |
| 61 this._unlockedElementCache.push(element); |
| 62 } |
| 63 |
| 64 return scrollLocked; |
| 65 }, |
| 66 |
| 67 /** |
| 68 * Push an element onto the current scroll lock stack. The most recently |
| 69 * pushed element and its children will be considered scrollable. All |
| 70 * other elements will not be scrollable. |
| 71 * |
| 72 * Scroll locking is implemented as a stack so that cases such as |
| 73 * dropdowns within dropdowns are handled well. |
| 74 * |
| 75 * @param {HTMLElement} element The element that should lock scroll. |
| 76 */ |
| 77 pushScrollLock: function(element) { |
| 78 if (this._lockingElements.length === 0) { |
| 79 this._lockScrollInteractions(); |
| 80 } |
| 81 |
| 82 this._lockingElements.push(element); |
| 83 |
| 84 this._lockedElementCache = []; |
| 85 this._unlockedElementCache = []; |
| 86 }, |
| 87 |
| 88 /** |
| 89 * Remove an element from the scroll lock stack. The element being |
| 90 * removed does not need to be the most recently pushed element. However, |
| 91 * the scroll lock constraints only change when the most recently pushed |
| 92 * element is removed. |
| 93 * |
| 94 * @param {HTMLElement} element The element to remove from the scroll |
| 95 * lock stack. |
| 96 */ |
| 97 removeScrollLock: function(element) { |
| 98 var index = this._lockingElements.indexOf(element); |
| 99 |
| 100 if (index === -1) { |
| 101 return; |
| 102 } |
| 103 |
| 104 this._lockingElements.splice(index, 1); |
| 105 |
| 106 this._lockedElementCache = []; |
| 107 this._unlockedElementCache = []; |
| 108 |
| 109 if (this._lockingElements.length === 0) { |
| 110 this._unlockScrollInteractions(); |
| 111 } |
| 112 }, |
| 113 |
| 114 _lockingElements: [], |
| 115 |
| 116 _lockedElementCache: null, |
| 117 |
| 118 _unlockedElementCache: null, |
| 119 |
| 120 _originalBodyStyles: {}, |
| 121 |
| 122 _isScrollingKeypress: function(event) { |
| 123 return Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys( |
| 124 event, 'pageup pagedown home end up left down right'); |
| 125 }, |
| 126 |
| 127 _hasCachedLockedElement: function(element) { |
| 128 return this._lockedElementCache.indexOf(element) > -1; |
| 129 }, |
| 130 |
| 131 _hasCachedUnlockedElement: function(element) { |
| 132 return this._unlockedElementCache.indexOf(element) > -1; |
| 133 }, |
| 134 |
| 135 _composedTreeContains: function(element, child) { |
| 136 // NOTE(cdata): This method iterates over content elements and their |
| 137 // corresponding distributed nodes to implement a contains-like method |
| 138 // that pierces through the composed tree of the ShadowDOM. Results of |
| 139 // this operation are cached (elsewhere) on a per-scroll-lock basis, to |
| 140 // guard against potentially expensive lookups happening repeatedly as |
| 141 // a user scrolls / touchmoves. |
| 142 var contentElements; |
| 143 var distributedNodes; |
| 144 var contentIndex; |
| 145 var nodeIndex; |
| 146 |
| 147 if (element.contains(child)) { |
| 148 return true; |
| 149 } |
| 150 |
| 151 contentElements = Polymer.dom(element).querySelectorAll('content'); |
| 152 |
| 153 for (contentIndex = 0; contentIndex < contentElements.length; ++contentI
ndex) { |
| 154 |
| 155 distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistr
ibutedNodes(); |
| 156 |
| 157 for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex)
{ |
| 158 |
| 159 if (this._composedTreeContains(distributedNodes[nodeIndex], child))
{ |
| 160 return true; |
| 161 } |
| 162 } |
| 163 } |
| 164 |
| 165 return false; |
| 166 }, |
| 167 |
| 168 _scrollInteractionHandler: function(event) { |
| 169 if (Polymer |
| 170 .IronDropdownScrollManager |
| 171 .elementIsScrollLocked(event.target)) { |
| 172 if (event.type === 'keydown' && |
| 173 !Polymer.IronDropdownScrollManager._isScrollingKeypress(event)) { |
| 174 return; |
| 175 } |
| 176 |
| 177 event.preventDefault(); |
| 178 } |
| 179 }, |
| 180 |
| 181 _lockScrollInteractions: function() { |
| 182 // Memoize body inline styles: |
| 183 this._originalBodyStyles.overflow = document.body.style.overflow; |
| 184 this._originalBodyStyles.overflowX = document.body.style.overflowX; |
| 185 this._originalBodyStyles.overflowY = document.body.style.overflowY; |
| 186 |
| 187 // Disable overflow scrolling on body: |
| 188 // TODO(cdata): It is technically not sufficient to hide overflow on |
| 189 // body alone. A better solution might be to traverse all ancestors of |
| 190 // the current scroll locking element and hide overflow on them. This |
| 191 // becomes expensive, though, as it would have to be redone every time |
| 192 // a new scroll locking element is added. |
| 193 document.body.style.overflow = 'hidden'; |
| 194 document.body.style.overflowX = 'hidden'; |
| 195 document.body.style.overflowY = 'hidden'; |
| 196 |
| 197 // Modern `wheel` event for mouse wheel scrolling: |
| 198 window.addEventListener('wheel', this._scrollInteractionHandler, true); |
| 199 // Older, non-standard `mousewheel` event for some FF: |
| 200 window.addEventListener('mousewheel', this._scrollInteractionHandler, tr
ue); |
| 201 // IE: |
| 202 window.addEventListener('DOMMouseScroll', this._scrollInteractionHandler
, true); |
| 203 // Mobile devices can scroll on touch move: |
| 204 window.addEventListener('touchmove', this._scrollInteractionHandler, tru
e); |
| 205 // Capture keydown to prevent scrolling keys (pageup, pagedown etc.) |
| 206 document.addEventListener('keydown', this._scrollInteractionHandler, tru
e); |
| 207 }, |
| 208 |
| 209 _unlockScrollInteractions: function() { |
| 210 document.body.style.overflow = this._originalBodyStyles.overflow; |
| 211 document.body.style.overflowX = this._originalBodyStyles.overflowX; |
| 212 document.body.style.overflowY = this._originalBodyStyles.overflowY; |
| 213 |
| 214 window.removeEventListener('wheel', this._scrollInteractionHandler, true
); |
| 215 window.removeEventListener('mousewheel', this._scrollInteractionHandler,
true); |
| 216 window.removeEventListener('DOMMouseScroll', this._scrollInteractionHand
ler, true); |
| 217 window.removeEventListener('touchmove', this._scrollInteractionHandler,
true); |
| 218 document.removeEventListener('keydown', this._scrollInteractionHandler,
true); |
| 219 } |
| 220 }; |
| 221 })(); |
| 222 </script> |
OLD | NEW |