UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

242 lines 7.37 kB
import { warn, isChildOfElement, checkIfHasScrollbar } from '../../shared/component-helper'; const isServer = () => typeof window === 'undefined'; const detectOS = ua => { ua = ua || navigator.userAgent; const ipad = /(iPad).*OS\s([\d_]+)/.test(ua); const iphone = !ipad && /(iPhone\sOS)\s([\d_]+)/.test(ua); const android = /(Android);?[\s/]+([\d.]+)?/.test(ua); const ios = iphone || ipad; return { ios, android }; }; let lockedNum = 0; let initialClientY = 0; let initialClientX = 0; let callbackUnlock = null; let documentListenerAdded = false; const lockedElements = []; const eventListenerOptions = getEventListenerOptions({ passive: false }); function getEventListenerOptions(options) { if (isServer()) { return; } let isSupportOptions = true; const { capture } = options; return isSupportOptions ? options : typeof capture !== 'undefined' ? capture : false; } const setOverflowHidden = () => { try { const htmlElement = document.documentElement; const bodyElement = document.body; const htmlStyle = Object.assign({}, htmlElement.style); const bodyStyle = Object.assign({}, bodyElement.style); const scrollBarWidth = window.innerWidth - (bodyElement.clientWidth || window.innerWidth); htmlElement.style.overflow = 'hidden'; htmlElement.style.height = '100%'; htmlElement.style.setProperty('--scrollbar-width', `${scrollBarWidth}px`); bodyElement.style.overflow = 'hidden'; bodyElement.style.height = 'auto'; bodyElement.style.boxSizing = 'border-box'; bodyElement.style.marginRight = `${scrollBarWidth}px`; return () => { try { ; ['overflow', 'height'].forEach(x => { htmlElement.style[x] = htmlStyle[x] || ''; }); ['overflow', 'height', 'boxSizing', 'margin'].forEach(x => { bodyElement.style[x] = bodyStyle[x] || ''; }); htmlElement.style.removeProperty('--scrollbar-width'); } catch (e) { warn(e); } }; } catch (e) { warn(e); } }; const setOverflowHiddenIOS = targetElement => { if (targetElement) { const elementArray = Array.isArray(targetElement) ? targetElement : [targetElement]; elementArray.forEach(element => { if (element && lockedElements.indexOf(element) === -1) { element.ontouchstart = event => { initialClientY = event.targetTouches[0].clientY; initialClientX = event.targetTouches[0].clientX; }; element.ontouchmove = event => { if (event.targetTouches.length !== 1) { return; } handleScroll(event, element); }; lockedElements.push(element); } }); } if (!documentListenerAdded) { document.addEventListener('touchmove', preventDefault, eventListenerOptions); documentListenerAdded = true; } return () => { if (targetElement) { const elementArray = Array.isArray(targetElement) ? targetElement : [targetElement]; elementArray.forEach(element => { const index = lockedElements.indexOf(element); if (index !== -1) { element.ontouchmove = null; element.ontouchstart = null; lockedElements.splice(index, 1); } }); } if (documentListenerAdded) { document.removeEventListener('touchmove', preventDefault, eventListenerOptions); documentListenerAdded = false; } }; }; const setOverflowHiddenAndroid = () => { try { const unbind = setOverflowHidden(); const htmlElement = document.documentElement; const bodyElement = document.body; const scrollTop = htmlElement.scrollTop || bodyElement.scrollTop; const bodyStyle = Object.assign({}, bodyElement.style); bodyElement.style.position = 'fixed'; bodyElement.style.top = `-${scrollTop}px`; bodyElement.style.left = '0'; bodyElement.style.right = '0'; return () => { unbind(); try { ; ['position', 'top', 'right', 'left'].forEach(x => { bodyElement.style[x] = bodyStyle[x] || ''; }); const scrollBehavior = window.getComputedStyle(htmlElement).scrollBehavior; htmlElement.style.scrollBehavior = 'auto'; htmlElement.scrollTop = scrollTop; htmlElement.style.scrollBehavior = scrollBehavior; } catch (e) { warn(e); } }; } catch (e) { warn(e); } }; const preventDefault = event => { const found = lockedElements.find(targetElement => { return isChildOfElement(event.target, targetElement); }); if (found || !event.cancelable) { return; } event.preventDefault(); }; const handleScroll = (event, targetElement) => { try { if (targetElement) { const { scrollTop, scrollLeft, scrollWidth, scrollHeight, clientWidth, clientHeight } = targetElement; const clientX = event.targetTouches[0].clientX - initialClientX; const clientY = event.targetTouches[0].clientY - initialClientY; const isVertical = Math.abs(clientY) > Math.abs(clientX); const isOnTop = clientY > 0 && scrollTop === 0; const isOnLeft = clientX > 0 && scrollLeft === 0; const isOnRight = clientX < 0 && scrollLeft + clientWidth + 1 >= scrollWidth; const isOnBottom = clientY < 0 && scrollTop + clientHeight + 1 >= scrollHeight; if (isVertical && (isOnTop || isOnBottom) || !isVertical && (isOnLeft || isOnRight)) { const hasScrollbar = isChildOfElement(event.target, targetElement, checkIfHasScrollbar); if (hasScrollbar && hasScrollbar !== targetElement) { return true; } return event.cancelable && event.preventDefault(); } } event.stopPropagation(); return true; } catch (e) { warn(e); } }; const checkTargetElement = targetElement => { if (targetElement) return; if (targetElement === null) return; warn(`If scrolling is also required in the floating layer, ` + `the target element must be provided.`); }; export const disableBodyScroll = targetElement => { if (isServer()) { return; } checkTargetElement(targetElement); try { if (detectOS().ios) { callbackUnlock = setOverflowHiddenIOS(targetElement); } else if (lockedNum <= 0) { if (detectOS().android) { callbackUnlock = setOverflowHiddenAndroid(); } else { callbackUnlock = setOverflowHidden(); } } lockedNum += 1; } catch (e) { warn(e); } }; export const enableBodyScroll = targetElement => { if (isServer()) { return; } checkTargetElement(targetElement); try { lockedNum -= 1; if (lockedNum > 0) { return; } if (typeof callbackUnlock === 'function') { callbackUnlock(); } } catch (e) { warn(e); } }; export const clearAllBodyScrollLocks = () => { if (isServer()) { return; } try { lockedNum = 0; if (typeof callbackUnlock === 'function') { callbackUnlock(); } if (detectOS().ios) { if (lockedElements && lockedElements.length) { let element = lockedElements.pop(); while (element) { element.ontouchmove = null; element.ontouchstart = null; element = lockedElements.pop(); } } } } catch (e) { warn(e); } }; //# sourceMappingURL=bodyScrollLock.js.map