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