tua-body-scroll-lock
Advanced tools
Comparing version 1.4.1 to 1.5.0
@@ -1,8 +0,6 @@ | ||
type Nullable<T> = T | Array<T> | null; | ||
export interface BSLOptions { | ||
overflowType?: 'hidden' | 'clip'; | ||
} | ||
declare const lock: (targetElement?: Nullable<HTMLElement>, options?: BSLOptions) => void; | ||
declare const unlock: (targetElement?: Nullable<HTMLElement>) => void; | ||
declare const clearBodyLocks: () => void; | ||
import type { BSLOptions, Nullable } from './types'; | ||
declare function lock(targetElement?: Nullable<HTMLElement>, options?: BSLOptions): void; | ||
declare function unlock(targetElement?: Nullable<HTMLElement>, options?: BSLOptions): void; | ||
declare function clearBodyLocks(options?: BSLOptions): void; | ||
export * from './types'; | ||
export { lock, unlock, clearBodyLocks }; |
/** | ||
* tua-body-scroll-lock v1.4.1 | ||
* tua-body-scroll-lock v1.5.0 | ||
* (c) 2024 Evinma, BuptStEve | ||
@@ -44,12 +44,74 @@ * @license MIT | ||
} | ||
function noticeRequiredTargetElement(targetElement) { | ||
if (targetElement) | ||
return false; | ||
if (targetElement === null) | ||
return false; | ||
/* istanbul ignore if */ | ||
{ | ||
console.warn('If scrolling is also required in the floating layer, ' + | ||
'the target element must be provided.'); | ||
} | ||
return true; | ||
} | ||
/** | ||
* Get global function that calls preventDefault | ||
*/ | ||
function getPreventEventDefault() { | ||
if ('__BSL_PREVENT_DEFAULT__' in window) { | ||
return window.__BSL_PREVENT_DEFAULT__; | ||
} | ||
window.__BSL_PREVENT_DEFAULT__ = function (event) { | ||
if (!event.cancelable) | ||
return; | ||
event.preventDefault(); | ||
}; | ||
return window.__BSL_PREVENT_DEFAULT__; | ||
} | ||
let lockedNum = 0; | ||
let initialClientY = 0; | ||
let initialClientX = 0; | ||
let unLockCallback = null; | ||
let documentListenerAdded = false; | ||
const lockedElements = []; | ||
const eventListenerOptions = getEventListenerOptions({ passive: false }); | ||
const supportsNativeSmoothScroll = !isServer() && 'scrollBehavior' in document.documentElement.style; | ||
const setOverflowHiddenPc = () => { | ||
const initialLockState = { | ||
lockedNum: 0, | ||
lockedElements: [], | ||
unLockCallback: null, | ||
documentListenerAdded: false, | ||
initialClientPos: { | ||
clientX: 0, | ||
clientY: 0, | ||
}, | ||
}; | ||
function getLockState(options) { | ||
if (isServer()) | ||
return initialLockState; | ||
/** use local lockState */ | ||
if (!(options === null || options === void 0 ? void 0 : options.useGlobalLockState)) | ||
return getLockState.lockState; | ||
/** use global lockState */ | ||
const lockState = '__BSL_LOCK_STATE__' in window | ||
? Object.assign(Object.assign({}, initialLockState), window.__BSL_LOCK_STATE__) : initialLockState; | ||
/** assign to global */ | ||
window.__BSL_LOCK_STATE__ = lockState; | ||
return lockState; | ||
} | ||
getLockState.lockState = initialLockState; | ||
function handleScroll(event, targetElement, initialClientPos) { | ||
if (targetElement) { | ||
const { scrollTop, scrollLeft, scrollWidth, scrollHeight, clientWidth, clientHeight, } = targetElement; | ||
const clientX = event.targetTouches[0].clientX - initialClientPos.clientX; | ||
const clientY = event.targetTouches[0].clientY - initialClientPos.clientY; | ||
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))) { | ||
return getPreventEventDefault()(event); | ||
} | ||
} | ||
event.stopPropagation(); | ||
return true; | ||
} | ||
function setOverflowHiddenPc() { | ||
const $html = document.documentElement; | ||
@@ -67,4 +129,4 @@ const htmlStyle = Object.assign({}, $html.style); | ||
}; | ||
}; | ||
const setOverflowHiddenMobile = (options) => { | ||
} | ||
function setOverflowHiddenMobile(options) { | ||
const $html = document.documentElement; | ||
@@ -88,43 +150,18 @@ const $body = document.body; | ||
}); | ||
const scrollToOptions = { top: scrollTop, behavior: 'instant' }; | ||
supportsNativeSmoothScroll | ||
? window.scrollTo(scrollToOptions) | ||
: window.scrollTo(0, scrollTop); | ||
const supportsNativeSmoothScroll = 'scrollBehavior' in document.documentElement.style; | ||
if (supportsNativeSmoothScroll) { | ||
window.scrollTo({ top: scrollTop, behavior: 'instant' }); | ||
} | ||
else { | ||
window.scrollTo(0, scrollTop); | ||
} | ||
}; | ||
}; | ||
const preventDefault = (event) => { | ||
if (!event.cancelable) | ||
return; | ||
event.preventDefault(); | ||
}; | ||
const handleScroll = (event, targetElement) => { | ||
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))) { | ||
return preventDefault(event); | ||
} | ||
} | ||
event.stopPropagation(); | ||
return true; | ||
}; | ||
const checkTargetElement = (targetElement) => { | ||
if (targetElement) | ||
return; | ||
if (targetElement === null) | ||
return; | ||
console.warn('If scrolling is also required in the floating layer, ' + | ||
'the target element must be provided.'); | ||
}; | ||
const lock = (targetElement, options) => { | ||
} | ||
const eventListenerOptions = getEventListenerOptions({ passive: false }); | ||
function lock(targetElement, options) { | ||
if (isServer()) | ||
return; | ||
checkTargetElement(targetElement); | ||
noticeRequiredTargetElement(targetElement); | ||
const lockState = getLockState(options); | ||
if (detectOS().ios) { | ||
@@ -135,6 +172,6 @@ // iOS | ||
elementArray.forEach((element) => { | ||
if (element && lockedElements.indexOf(element) === -1) { | ||
if (element && lockState.lockedElements.indexOf(element) === -1) { | ||
element.ontouchstart = (event) => { | ||
initialClientY = event.targetTouches[0].clientY; | ||
initialClientX = event.targetTouches[0].clientX; | ||
const { clientX, clientY } = event.targetTouches[0]; | ||
lockState.initialClientPos = { clientX, clientY }; | ||
}; | ||
@@ -144,30 +181,31 @@ element.ontouchmove = (event) => { | ||
return; | ||
handleScroll(event, element); | ||
handleScroll(event, element, lockState.initialClientPos); | ||
}; | ||
lockedElements.push(element); | ||
lockState.lockedElements.push(element); | ||
} | ||
}); | ||
} | ||
if (!documentListenerAdded) { | ||
document.addEventListener('touchmove', preventDefault, eventListenerOptions); | ||
documentListenerAdded = true; | ||
if (!lockState.documentListenerAdded) { | ||
document.addEventListener('touchmove', getPreventEventDefault(), eventListenerOptions); | ||
lockState.documentListenerAdded = true; | ||
} | ||
} | ||
else if (lockedNum <= 0) { | ||
unLockCallback = detectOS().android | ||
else if (lockState.lockedNum <= 0) { | ||
lockState.unLockCallback = detectOS().android | ||
? setOverflowHiddenMobile(options) | ||
: setOverflowHiddenPc(); | ||
} | ||
lockedNum += 1; | ||
}; | ||
const unlock = (targetElement) => { | ||
lockState.lockedNum += 1; | ||
} | ||
function unlock(targetElement, options) { | ||
if (isServer()) | ||
return; | ||
checkTargetElement(targetElement); | ||
lockedNum -= 1; | ||
if (lockedNum > 0) | ||
noticeRequiredTargetElement(targetElement); | ||
const lockState = getLockState(options); | ||
lockState.lockedNum -= 1; | ||
if (lockState.lockedNum > 0) | ||
return; | ||
if (!detectOS().ios && | ||
typeof unLockCallback === 'function') { | ||
unLockCallback(); | ||
typeof lockState.unLockCallback === 'function') { | ||
lockState.unLockCallback(); | ||
return; | ||
@@ -179,40 +217,41 @@ } | ||
elementArray.forEach((element) => { | ||
const index = lockedElements.indexOf(element); | ||
const index = lockState.lockedElements.indexOf(element); | ||
if (index !== -1) { | ||
element.ontouchmove = null; | ||
element.ontouchstart = null; | ||
lockedElements.splice(index, 1); | ||
lockState.lockedElements.splice(index, 1); | ||
} | ||
}); | ||
} | ||
if (documentListenerAdded) { | ||
document.removeEventListener('touchmove', preventDefault, eventListenerOptions); | ||
documentListenerAdded = false; | ||
if (lockState.documentListenerAdded) { | ||
document.removeEventListener('touchmove', getPreventEventDefault(), eventListenerOptions); | ||
lockState.documentListenerAdded = false; | ||
} | ||
}; | ||
const clearBodyLocks = () => { | ||
} | ||
function clearBodyLocks(options) { | ||
if (isServer()) | ||
return; | ||
lockedNum = 0; | ||
const lockState = getLockState(options); | ||
lockState.lockedNum = 0; | ||
if (!detectOS().ios && | ||
typeof unLockCallback === 'function') { | ||
unLockCallback(); | ||
typeof lockState.unLockCallback === 'function') { | ||
lockState.unLockCallback(); | ||
return; | ||
} | ||
// IOS | ||
if (lockedElements.length) { | ||
// iOS | ||
if (lockState.lockedElements.length) { | ||
// clear events | ||
let element = lockedElements.pop(); | ||
let element = lockState.lockedElements.pop(); | ||
while (element) { | ||
element.ontouchmove = null; | ||
element.ontouchstart = null; | ||
element = lockedElements.pop(); | ||
element = lockState.lockedElements.pop(); | ||
} | ||
} | ||
if (documentListenerAdded) { | ||
document.removeEventListener('touchmove', preventDefault, eventListenerOptions); | ||
documentListenerAdded = false; | ||
if (lockState.documentListenerAdded) { | ||
document.removeEventListener('touchmove', getPreventEventDefault(), eventListenerOptions); | ||
lockState.documentListenerAdded = false; | ||
} | ||
}; | ||
} | ||
export { clearBodyLocks, lock, unlock }; |
/** | ||
* tua-body-scroll-lock v1.4.1 | ||
* tua-body-scroll-lock v1.5.0 | ||
* (c) 2024 Evinma, BuptStEve | ||
* @license MIT | ||
*/ | ||
const e=()=>"undefined"==typeof window,t=e=>{e=e||navigator.userAgent;const t=/(iPad).*OS\s([\d_]+)/.test(e);return{ios:!t&&/(iPhone\sOS)\s([\d_]+)/.test(e)||t,android:/(Android);?[\s/]+([\d.]+)?/.test(e)}};let o=0,n=0,i=0,s=null,l=!1;const r=[],c=function(t){if(e())return!1;if(!t)throw new Error("options must be provided");let o=!1;const n={get passive(){o=!0}},i=()=>{},s="__TUA_BSL_TEST_PASSIVE__";window.addEventListener(s,i,n),window.removeEventListener(s,i,n);const{capture:l}=t;return o?t:void 0!==l&&l}({passive:!1}),d=!e()&&"scrollBehavior"in document.documentElement.style,h=e=>{e.cancelable&&e.preventDefault()},u=(u,a)=>{if(!e()){if(t().ios){if(u){(Array.isArray(u)?u:[u]).forEach((e=>{e&&-1===r.indexOf(e)&&(e.ontouchstart=e=>{n=e.targetTouches[0].clientY,i=e.targetTouches[0].clientX},e.ontouchmove=t=>{1===t.targetTouches.length&&((e,t)=>{if(t){const{scrollTop:o,scrollLeft:s,scrollWidth:l,scrollHeight:r,clientWidth:c,clientHeight:d}=t,u=e.targetTouches[0].clientX-i,a=e.targetTouches[0].clientY-n,f=Math.abs(a)>Math.abs(u);if(f&&(a>0&&0===o||a<0&&o+d+1>=r)||!f&&(u>0&&0===s||u<0&&s+c+1>=l))return h(e)}e.stopPropagation()})(t,e)},r.push(e))}))}l||(document.addEventListener("touchmove",h,c),l=!0)}else o<=0&&(s=t().android?(e=>{const t=document.documentElement,o=document.body,n=t.scrollTop||o.scrollTop,i=Object.assign({},t.style),s=Object.assign({},o.style);return t.style.height="100%",t.style.overflow="hidden",o.style.top=`-${n}px`,o.style.width="100%",o.style.height="auto",o.style.position="fixed",o.style.overflow=(null==e?void 0:e.overflowType)||"hidden",()=>{t.style.height=i.height||"",t.style.overflow=i.overflow||"",["top","width","height","overflow","position"].forEach((e=>{o.style[e]=s[e]||""}));const e={top:n,behavior:"instant"};d?window.scrollTo(e):window.scrollTo(0,n)}})(a):(()=>{const e=document.documentElement,t=Object.assign({},e.style),o=window.innerWidth-e.clientWidth,n=parseInt(window.getComputedStyle(e).paddingRight,10);return e.style.overflow="hidden",e.style.boxSizing="border-box",e.style.paddingRight=`${o+n}px`,()=>{["overflow","boxSizing","paddingRight"].forEach((o=>{e.style[o]=t[o]||""}))}})());o+=1}},a=n=>{if(!(e()||(o-=1,o>0)))if(t().ios||"function"!=typeof s){if(n){(Array.isArray(n)?n:[n]).forEach((e=>{const t=r.indexOf(e);-1!==t&&(e.ontouchmove=null,e.ontouchstart=null,r.splice(t,1))}))}l&&(document.removeEventListener("touchmove",h,c),l=!1)}else s()},f=()=>{if(!e())if(o=0,t().ios||"function"!=typeof s){if(r.length){let e=r.pop();for(;e;)e.ontouchmove=null,e.ontouchstart=null,e=r.pop()}l&&(document.removeEventListener("touchmove",h,c),l=!1)}else s()};export{f as clearBodyLocks,u as lock,a as unlock}; | ||
const e=()=>"undefined"==typeof window,t=e=>{e=e||navigator.userAgent;const t=/(iPad).*OS\s([\d_]+)/.test(e);return{ios:!t&&/(iPhone\sOS)\s([\d_]+)/.test(e)||t,android:/(Android);?[\s/]+([\d.]+)?/.test(e)}};function o(){return"__BSL_PREVENT_DEFAULT__"in window||(window.__BSL_PREVENT_DEFAULT__=function(e){e.cancelable&&e.preventDefault()}),window.__BSL_PREVENT_DEFAULT__}const n={lockedNum:0,lockedElements:[],unLockCallback:null,documentListenerAdded:!1,initialClientPos:{clientX:0,clientY:0}};function i(t){if(e())return n;if(!(null==t?void 0:t.useGlobalLockState))return i.lockState;const o="__BSL_LOCK_STATE__"in window?Object.assign(Object.assign({},n),window.__BSL_LOCK_STATE__):n;return window.__BSL_LOCK_STATE__=o,o}i.lockState=n;const l=function(t){if(e())return!1;if(!t)throw new Error("options must be provided");let o=!1;const n={get passive(){o=!0}},i=()=>{},l="__TUA_BSL_TEST_PASSIVE__";window.addEventListener(l,i,n),window.removeEventListener(l,i,n);const{capture:c}=t;return o?t:void 0!==c&&c}({passive:!1});function c(n,c){if(e())return;const s=i(c);if(t().ios){if(n){(Array.isArray(n)?n:[n]).forEach((e=>{e&&-1===s.lockedElements.indexOf(e)&&(e.ontouchstart=e=>{const{clientX:t,clientY:o}=e.targetTouches[0];s.initialClientPos={clientX:t,clientY:o}},e.ontouchmove=t=>{1===t.targetTouches.length&&function(e,t,n){if(t){const{scrollTop:i,scrollLeft:l,scrollWidth:c,scrollHeight:s,clientWidth:d,clientHeight:r}=t,u=e.targetTouches[0].clientX-n.clientX,a=e.targetTouches[0].clientY-n.clientY,f=Math.abs(a)>Math.abs(u);if(f&&(a>0&&0===i||a<0&&i+r+1>=s)||!f&&(u>0&&0===l||u<0&&l+d+1>=c))return o()(e)}e.stopPropagation()}(t,e,s.initialClientPos)},s.lockedElements.push(e))}))}s.documentListenerAdded||(document.addEventListener("touchmove",o(),l),s.documentListenerAdded=!0)}else s.lockedNum<=0&&(s.unLockCallback=t().android?function(e){const t=document.documentElement,o=document.body,n=t.scrollTop||o.scrollTop,i=Object.assign({},t.style),l=Object.assign({},o.style);return t.style.height="100%",t.style.overflow="hidden",o.style.top=`-${n}px`,o.style.width="100%",o.style.height="auto",o.style.position="fixed",o.style.overflow=(null==e?void 0:e.overflowType)||"hidden",()=>{t.style.height=i.height||"",t.style.overflow=i.overflow||"",["top","width","height","overflow","position"].forEach((e=>{o.style[e]=l[e]||""})),"scrollBehavior"in document.documentElement.style?window.scrollTo({top:n,behavior:"instant"}):window.scrollTo(0,n)}}(c):function(){const e=document.documentElement,t=Object.assign({},e.style),o=window.innerWidth-e.clientWidth,n=parseInt(window.getComputedStyle(e).paddingRight,10);return e.style.overflow="hidden",e.style.boxSizing="border-box",e.style.paddingRight=`${o+n}px`,()=>{["overflow","boxSizing","paddingRight"].forEach((o=>{e.style[o]=t[o]||""}))}}());s.lockedNum+=1}function s(n,c){if(e())return;const s=i(c);if(s.lockedNum-=1,!(s.lockedNum>0))if(t().ios||"function"!=typeof s.unLockCallback){if(n){(Array.isArray(n)?n:[n]).forEach((e=>{const t=s.lockedElements.indexOf(e);-1!==t&&(e.ontouchmove=null,e.ontouchstart=null,s.lockedElements.splice(t,1))}))}s.documentListenerAdded&&(document.removeEventListener("touchmove",o(),l),s.documentListenerAdded=!1)}else s.unLockCallback()}function d(n){if(e())return;const c=i(n);if(c.lockedNum=0,t().ios||"function"!=typeof c.unLockCallback){if(c.lockedElements.length){let e=c.lockedElements.pop();for(;e;)e.ontouchmove=null,e.ontouchstart=null,e=c.lockedElements.pop()}c.documentListenerAdded&&(document.removeEventListener("touchmove",o(),l),c.documentListenerAdded=!1)}else c.unLockCallback()}export{d as clearBodyLocks,c as lock,s as unlock}; |
/** | ||
* tua-body-scroll-lock v1.4.1 | ||
* tua-body-scroll-lock v1.5.0 | ||
* (c) 2024 Evinma, BuptStEve | ||
@@ -50,14 +50,71 @@ * @license MIT | ||
} | ||
function noticeRequiredTargetElement(targetElement) { | ||
if (targetElement) return false; | ||
if (targetElement === null) return false; | ||
/* istanbul ignore if */ | ||
{ | ||
console.warn('If scrolling is also required in the floating layer, ' + 'the target element must be provided.'); | ||
} | ||
return true; | ||
} | ||
/** | ||
* Get global function that calls preventDefault | ||
*/ | ||
function getPreventEventDefault() { | ||
if ('__BSL_PREVENT_DEFAULT__' in window) { | ||
return window.__BSL_PREVENT_DEFAULT__; | ||
} | ||
window.__BSL_PREVENT_DEFAULT__ = function (event) { | ||
if (!event.cancelable) return; | ||
event.preventDefault(); | ||
}; | ||
return window.__BSL_PREVENT_DEFAULT__; | ||
} | ||
var lockedNum = 0; | ||
var initialClientY = 0; | ||
var initialClientX = 0; | ||
var unLockCallback = null; | ||
var documentListenerAdded = false; | ||
var lockedElements = []; | ||
var eventListenerOptions = getEventListenerOptions({ | ||
passive: false | ||
}); | ||
var supportsNativeSmoothScroll = !isServer() && 'scrollBehavior' in document.documentElement.style; | ||
var setOverflowHiddenPc = function setOverflowHiddenPc() { | ||
var initialLockState = { | ||
lockedNum: 0, | ||
lockedElements: [], | ||
unLockCallback: null, | ||
documentListenerAdded: false, | ||
initialClientPos: { | ||
clientX: 0, | ||
clientY: 0 | ||
} | ||
}; | ||
function getLockState(options) { | ||
if (isServer()) return initialLockState; | ||
/** use local lockState */ | ||
if (!(options === null || options === void 0 ? void 0 : options.useGlobalLockState)) return getLockState.lockState; | ||
/** use global lockState */ | ||
var lockState = '__BSL_LOCK_STATE__' in window ? Object.assign(Object.assign({}, initialLockState), window.__BSL_LOCK_STATE__) : initialLockState; | ||
/** assign to global */ | ||
window.__BSL_LOCK_STATE__ = lockState; | ||
return lockState; | ||
} | ||
getLockState.lockState = initialLockState; | ||
function handleScroll(event, targetElement, initialClientPos) { | ||
if (targetElement) { | ||
var scrollTop = targetElement.scrollTop, | ||
scrollLeft = targetElement.scrollLeft, | ||
scrollWidth = targetElement.scrollWidth, | ||
scrollHeight = targetElement.scrollHeight, | ||
clientWidth = targetElement.clientWidth, | ||
clientHeight = targetElement.clientHeight; | ||
var clientX = event.targetTouches[0].clientX - initialClientPos.clientX; | ||
var clientY = event.targetTouches[0].clientY - initialClientPos.clientY; | ||
var isVertical = Math.abs(clientY) > Math.abs(clientX); | ||
var isOnTop = clientY > 0 && scrollTop === 0; | ||
var isOnLeft = clientX > 0 && scrollLeft === 0; | ||
var isOnRight = clientX < 0 && scrollLeft + clientWidth + 1 >= scrollWidth; | ||
var isOnBottom = clientY < 0 && scrollTop + clientHeight + 1 >= scrollHeight; | ||
if (isVertical && (isOnTop || isOnBottom) || !isVertical && (isOnLeft || isOnRight)) { | ||
return getPreventEventDefault()(event); | ||
} | ||
} | ||
event.stopPropagation(); | ||
return true; | ||
} | ||
function setOverflowHiddenPc() { | ||
var $html = document.documentElement; | ||
@@ -75,4 +132,4 @@ var htmlStyle = Object.assign({}, $html.style); | ||
}; | ||
}; | ||
var setOverflowHiddenMobile = function setOverflowHiddenMobile(options) { | ||
} | ||
function setOverflowHiddenMobile(options) { | ||
var $html = document.documentElement; | ||
@@ -96,43 +153,21 @@ var $body = document.body; | ||
}); | ||
var scrollToOptions = { | ||
top: scrollTop, | ||
behavior: 'instant' | ||
}; | ||
supportsNativeSmoothScroll ? window.scrollTo(scrollToOptions) : window.scrollTo(0, scrollTop); | ||
var supportsNativeSmoothScroll = ('scrollBehavior' in document.documentElement.style); | ||
if (supportsNativeSmoothScroll) { | ||
window.scrollTo({ | ||
top: scrollTop, | ||
behavior: 'instant' | ||
}); | ||
} else { | ||
window.scrollTo(0, scrollTop); | ||
} | ||
}; | ||
}; | ||
var preventDefault = function preventDefault(event) { | ||
if (!event.cancelable) return; | ||
event.preventDefault(); | ||
}; | ||
var handleScroll = function handleScroll(event, targetElement) { | ||
if (targetElement) { | ||
var scrollTop = targetElement.scrollTop, | ||
scrollLeft = targetElement.scrollLeft, | ||
scrollWidth = targetElement.scrollWidth, | ||
scrollHeight = targetElement.scrollHeight, | ||
clientWidth = targetElement.clientWidth, | ||
clientHeight = targetElement.clientHeight; | ||
var clientX = event.targetTouches[0].clientX - initialClientX; | ||
var clientY = event.targetTouches[0].clientY - initialClientY; | ||
var isVertical = Math.abs(clientY) > Math.abs(clientX); | ||
var isOnTop = clientY > 0 && scrollTop === 0; | ||
var isOnLeft = clientX > 0 && scrollLeft === 0; | ||
var isOnRight = clientX < 0 && scrollLeft + clientWidth + 1 >= scrollWidth; | ||
var isOnBottom = clientY < 0 && scrollTop + clientHeight + 1 >= scrollHeight; | ||
if (isVertical && (isOnTop || isOnBottom) || !isVertical && (isOnLeft || isOnRight)) { | ||
return preventDefault(event); | ||
} | ||
} | ||
event.stopPropagation(); | ||
return true; | ||
}; | ||
var checkTargetElement = function checkTargetElement(targetElement) { | ||
if (targetElement) return; | ||
if (targetElement === null) return; | ||
console.warn('If scrolling is also required in the floating layer, ' + 'the target element must be provided.'); | ||
}; | ||
var lock = function lock(targetElement, options) { | ||
} | ||
var eventListenerOptions = getEventListenerOptions({ | ||
passive: false | ||
}); | ||
function lock(targetElement, options) { | ||
if (isServer()) return; | ||
checkTargetElement(targetElement); | ||
noticeRequiredTargetElement(targetElement); | ||
var lockState = getLockState(options); | ||
if (detectOS().ios) { | ||
@@ -143,31 +178,37 @@ // iOS | ||
elementArray.forEach(function (element) { | ||
if (element && lockedElements.indexOf(element) === -1) { | ||
if (element && lockState.lockedElements.indexOf(element) === -1) { | ||
element.ontouchstart = function (event) { | ||
initialClientY = event.targetTouches[0].clientY; | ||
initialClientX = event.targetTouches[0].clientX; | ||
var _event$targetTouches$ = event.targetTouches[0], | ||
clientX = _event$targetTouches$.clientX, | ||
clientY = _event$targetTouches$.clientY; | ||
lockState.initialClientPos = { | ||
clientX: clientX, | ||
clientY: clientY | ||
}; | ||
}; | ||
element.ontouchmove = function (event) { | ||
if (event.targetTouches.length !== 1) return; | ||
handleScroll(event, element); | ||
handleScroll(event, element, lockState.initialClientPos); | ||
}; | ||
lockedElements.push(element); | ||
lockState.lockedElements.push(element); | ||
} | ||
}); | ||
} | ||
if (!documentListenerAdded) { | ||
document.addEventListener('touchmove', preventDefault, eventListenerOptions); | ||
documentListenerAdded = true; | ||
if (!lockState.documentListenerAdded) { | ||
document.addEventListener('touchmove', getPreventEventDefault(), eventListenerOptions); | ||
lockState.documentListenerAdded = true; | ||
} | ||
} else if (lockedNum <= 0) { | ||
unLockCallback = detectOS().android ? setOverflowHiddenMobile(options) : setOverflowHiddenPc(); | ||
} else if (lockState.lockedNum <= 0) { | ||
lockState.unLockCallback = detectOS().android ? setOverflowHiddenMobile(options) : setOverflowHiddenPc(); | ||
} | ||
lockedNum += 1; | ||
}; | ||
var unlock = function unlock(targetElement) { | ||
lockState.lockedNum += 1; | ||
} | ||
function unlock(targetElement, options) { | ||
if (isServer()) return; | ||
checkTargetElement(targetElement); | ||
lockedNum -= 1; | ||
if (lockedNum > 0) return; | ||
if (!detectOS().ios && typeof unLockCallback === 'function') { | ||
unLockCallback(); | ||
noticeRequiredTargetElement(targetElement); | ||
var lockState = getLockState(options); | ||
lockState.lockedNum -= 1; | ||
if (lockState.lockedNum > 0) return; | ||
if (!detectOS().ios && typeof lockState.unLockCallback === 'function') { | ||
lockState.unLockCallback(); | ||
return; | ||
@@ -179,37 +220,38 @@ } | ||
elementArray.forEach(function (element) { | ||
var index = lockedElements.indexOf(element); | ||
var index = lockState.lockedElements.indexOf(element); | ||
if (index !== -1) { | ||
element.ontouchmove = null; | ||
element.ontouchstart = null; | ||
lockedElements.splice(index, 1); | ||
lockState.lockedElements.splice(index, 1); | ||
} | ||
}); | ||
} | ||
if (documentListenerAdded) { | ||
document.removeEventListener('touchmove', preventDefault, eventListenerOptions); | ||
documentListenerAdded = false; | ||
if (lockState.documentListenerAdded) { | ||
document.removeEventListener('touchmove', getPreventEventDefault(), eventListenerOptions); | ||
lockState.documentListenerAdded = false; | ||
} | ||
}; | ||
var clearBodyLocks = function clearBodyLocks() { | ||
} | ||
function clearBodyLocks(options) { | ||
if (isServer()) return; | ||
lockedNum = 0; | ||
if (!detectOS().ios && typeof unLockCallback === 'function') { | ||
unLockCallback(); | ||
var lockState = getLockState(options); | ||
lockState.lockedNum = 0; | ||
if (!detectOS().ios && typeof lockState.unLockCallback === 'function') { | ||
lockState.unLockCallback(); | ||
return; | ||
} | ||
// IOS | ||
if (lockedElements.length) { | ||
// iOS | ||
if (lockState.lockedElements.length) { | ||
// clear events | ||
var element = lockedElements.pop(); | ||
var element = lockState.lockedElements.pop(); | ||
while (element) { | ||
element.ontouchmove = null; | ||
element.ontouchstart = null; | ||
element = lockedElements.pop(); | ||
element = lockState.lockedElements.pop(); | ||
} | ||
} | ||
if (documentListenerAdded) { | ||
document.removeEventListener('touchmove', preventDefault, eventListenerOptions); | ||
documentListenerAdded = false; | ||
if (lockState.documentListenerAdded) { | ||
document.removeEventListener('touchmove', getPreventEventDefault(), eventListenerOptions); | ||
lockState.documentListenerAdded = false; | ||
} | ||
}; | ||
} | ||
@@ -216,0 +258,0 @@ exports.clearBodyLocks = clearBodyLocks; |
/** | ||
* tua-body-scroll-lock v1.4.1 | ||
* tua-body-scroll-lock v1.5.0 | ||
* (c) 2024 Evinma, BuptStEve | ||
* @license MIT | ||
*/ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).bodyScrollLock={})}(this,(function(e){"use strict";var t=function(){return"undefined"==typeof window},o=function(e){e=e||navigator.userAgent;var t=/(iPad).*OS\s([\d_]+)/.test(e);return{ios:!t&&/(iPhone\sOS)\s([\d_]+)/.test(e)||t,android:/(Android);?[\s/]+([\d.]+)?/.test(e)}};var n=0,i=0,r=0,s=null,l=!1,c=[],d=function(e){if(t())return!1;if(!e)throw new Error("options must be provided");var o=!1,n={get passive(){o=!0}},i=function(){},r="__TUA_BSL_TEST_PASSIVE__";window.addEventListener(r,i,n),window.removeEventListener(r,i,n);var s=e.capture;return o?e:void 0!==s&&s}({passive:!1}),u=!t()&&"scrollBehavior"in document.documentElement.style,f=function(e){e.cancelable&&e.preventDefault()};e.clearBodyLocks=function(){if(!t())if(n=0,o().ios||"function"!=typeof s){if(c.length)for(var e=c.pop();e;)e.ontouchmove=null,e.ontouchstart=null,e=c.pop();l&&(document.removeEventListener("touchmove",f,d),l=!1)}else s()},e.lock=function(e,a){if(!t()){if(o().ios){if(e)(Array.isArray(e)?e:[e]).forEach((function(e){e&&-1===c.indexOf(e)&&(e.ontouchstart=function(e){i=e.targetTouches[0].clientY,r=e.targetTouches[0].clientX},e.ontouchmove=function(t){1===t.targetTouches.length&&function(e,t){if(t){var o=t.scrollTop,n=t.scrollLeft,s=t.scrollWidth,l=t.scrollHeight,c=t.clientWidth,d=t.clientHeight,u=e.targetTouches[0].clientX-r,a=e.targetTouches[0].clientY-i,h=Math.abs(a)>Math.abs(u);if(h&&(a>0&&0===o||a<0&&o+d+1>=l)||!h&&(u>0&&0===n||u<0&&n+c+1>=s))return f(e)}e.stopPropagation()}(t,e)},c.push(e))}));l||(document.addEventListener("touchmove",f,d),l=!0)}else n<=0&&(s=o().android?function(e){var t=document.documentElement,o=document.body,n=t.scrollTop||o.scrollTop,i=Object.assign({},t.style),r=Object.assign({},o.style);return t.style.height="100%",t.style.overflow="hidden",o.style.top="-".concat(n,"px"),o.style.width="100%",o.style.height="auto",o.style.position="fixed",o.style.overflow=(null==e?void 0:e.overflowType)||"hidden",function(){t.style.height=i.height||"",t.style.overflow=i.overflow||"",["top","width","height","overflow","position"].forEach((function(e){o.style[e]=r[e]||""}));var e={top:n,behavior:"instant"};u?window.scrollTo(e):window.scrollTo(0,n)}}(a):(h=document.documentElement,v=Object.assign({},h.style),p=window.innerWidth-h.clientWidth,y=parseInt(window.getComputedStyle(h).paddingRight,10),h.style.overflow="hidden",h.style.boxSizing="border-box",h.style.paddingRight="".concat(p+y,"px"),function(){["overflow","boxSizing","paddingRight"].forEach((function(e){h.style[e]=v[e]||""}))}));var h,v,p,y;n+=1}},e.unlock=function(e){if(!(t()||(n-=1)>0))if(o().ios||"function"!=typeof s){if(e)(Array.isArray(e)?e:[e]).forEach((function(e){var t=c.indexOf(e);-1!==t&&(e.ontouchmove=null,e.ontouchstart=null,c.splice(t,1))}));l&&(document.removeEventListener("touchmove",f,d),l=!1)}else s()}})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).bodyScrollLock={})}(this,(function(e){"use strict";var t=function(){return"undefined"==typeof window},n=function(e){e=e||navigator.userAgent;var t=/(iPad).*OS\s([\d_]+)/.test(e);return{ios:!t&&/(iPhone\sOS)\s([\d_]+)/.test(e)||t,android:/(Android);?[\s/]+([\d.]+)?/.test(e)}};function o(){return"__BSL_PREVENT_DEFAULT__"in window||(window.__BSL_PREVENT_DEFAULT__=function(e){e.cancelable&&e.preventDefault()}),window.__BSL_PREVENT_DEFAULT__}var i={lockedNum:0,lockedElements:[],unLockCallback:null,documentListenerAdded:!1,initialClientPos:{clientX:0,clientY:0}};function l(e){if(t())return i;if(!(null==e?void 0:e.useGlobalLockState))return l.lockState;var n="__BSL_LOCK_STATE__"in window?Object.assign(Object.assign({},i),window.__BSL_LOCK_STATE__):i;return window.__BSL_LOCK_STATE__=n,n}l.lockState=i;var c=function(e){if(t())return!1;if(!e)throw new Error("options must be provided");var n=!1,o={get passive(){n=!0}},i=function(){},l="__TUA_BSL_TEST_PASSIVE__";window.addEventListener(l,i,o),window.removeEventListener(l,i,o);var c=e.capture;return n?e:void 0!==c&&c}({passive:!1});e.clearBodyLocks=function(e){if(!t()){var i=l(e);if(i.lockedNum=0,n().ios||"function"!=typeof i.unLockCallback){if(i.lockedElements.length)for(var d=i.lockedElements.pop();d;)d.ontouchmove=null,d.ontouchstart=null,d=i.lockedElements.pop();i.documentListenerAdded&&(document.removeEventListener("touchmove",o(),c),i.documentListenerAdded=!1)}else i.unLockCallback()}},e.lock=function(e,i){if(!t()){var d,r,s,u,a=l(i);if(n().ios){if(e)(Array.isArray(e)?e:[e]).forEach((function(e){e&&-1===a.lockedElements.indexOf(e)&&(e.ontouchstart=function(e){var t=e.targetTouches[0],n=t.clientX,o=t.clientY;a.initialClientPos={clientX:n,clientY:o}},e.ontouchmove=function(t){1===t.targetTouches.length&&function(e,t,n){if(t){var i=t.scrollTop,l=t.scrollLeft,c=t.scrollWidth,d=t.scrollHeight,r=t.clientWidth,s=t.clientHeight,u=e.targetTouches[0].clientX-n.clientX,a=e.targetTouches[0].clientY-n.clientY,f=Math.abs(a)>Math.abs(u);if(f&&(a>0&&0===i||a<0&&i+s+1>=d)||!f&&(u>0&&0===l||u<0&&l+r+1>=c))return o()(e)}e.stopPropagation()}(t,e,a.initialClientPos)},a.lockedElements.push(e))}));a.documentListenerAdded||(document.addEventListener("touchmove",o(),c),a.documentListenerAdded=!0)}else a.lockedNum<=0&&(a.unLockCallback=n().android?function(e){var t=document.documentElement,n=document.body,o=t.scrollTop||n.scrollTop,i=Object.assign({},t.style),l=Object.assign({},n.style);return t.style.height="100%",t.style.overflow="hidden",n.style.top="-".concat(o,"px"),n.style.width="100%",n.style.height="auto",n.style.position="fixed",n.style.overflow=(null==e?void 0:e.overflowType)||"hidden",function(){t.style.height=i.height||"",t.style.overflow=i.overflow||"",["top","width","height","overflow","position"].forEach((function(e){n.style[e]=l[e]||""})),"scrollBehavior"in document.documentElement.style?window.scrollTo({top:o,behavior:"instant"}):window.scrollTo(0,o)}}(i):(d=document.documentElement,r=Object.assign({},d.style),s=window.innerWidth-d.clientWidth,u=parseInt(window.getComputedStyle(d).paddingRight,10),d.style.overflow="hidden",d.style.boxSizing="border-box",d.style.paddingRight="".concat(s+u,"px"),function(){["overflow","boxSizing","paddingRight"].forEach((function(e){d.style[e]=r[e]||""}))}));a.lockedNum+=1}},e.unlock=function(e,i){if(!t()){var d=l(i);if(d.lockedNum-=1,!(d.lockedNum>0))if(n().ios||"function"!=typeof d.unLockCallback){if(e)(Array.isArray(e)?e:[e]).forEach((function(e){var t=d.lockedElements.indexOf(e);-1!==t&&(e.ontouchmove=null,e.ontouchstart=null,d.lockedElements.splice(t,1))}));d.documentListenerAdded&&(document.removeEventListener("touchmove",o(),c),d.documentListenerAdded=!1)}else d.unLockCallback()}}})); |
@@ -0,1 +1,2 @@ | ||
import { Nullable } from './types'; | ||
export declare const isServer: () => boolean; | ||
@@ -8,1 +9,6 @@ export interface DetectOSResult { | ||
export declare function getEventListenerOptions(options: AddEventListenerOptions): AddEventListenerOptions | boolean; | ||
export declare function noticeRequiredTargetElement(targetElement?: Nullable<HTMLElement>): boolean; | ||
/** | ||
* Get global function that calls preventDefault | ||
*/ | ||
export declare function getPreventEventDefault(): (event: TouchEvent) => void; |
{ | ||
"name": "tua-body-scroll-lock", | ||
"version": "1.4.1", | ||
"version": "1.5.0", | ||
"packageManager": "pnpm@^7", | ||
@@ -16,3 +16,4 @@ "description": "🔐Body scroll locking that just works with everything", | ||
"require": "./dist/tua-bsl.umd.js" | ||
} | ||
}, | ||
"./dist/*": "./dist/*" | ||
}, | ||
@@ -58,40 +59,40 @@ "scripts": { | ||
"devDependencies": { | ||
"@babel/core": "^7.22.5", | ||
"@babel/preset-env": "^7.22.5", | ||
"@babel/preset-typescript": "^7.22.5", | ||
"@commitlint/cli": "^17.6.6", | ||
"@commitlint/config-conventional": "^17.6.6", | ||
"@rollup/plugin-babel": "^6.0.3", | ||
"@rollup/plugin-eslint": "^9.0.4", | ||
"@rollup/plugin-replace": "^5.0.2", | ||
"@rollup/plugin-terser": "^0.4.3", | ||
"@types/jest": "^29.5.2", | ||
"@types/node": "^20.3.3", | ||
"@typescript-eslint/eslint-plugin": "^5.60.1", | ||
"@typescript-eslint/parser": "^5.60.1", | ||
"@babel/core": "^7.24.0", | ||
"@babel/preset-env": "^7.24.0", | ||
"@babel/preset-typescript": "^7.23.3", | ||
"@commitlint/cli": "^17.8.1", | ||
"@commitlint/config-conventional": "^17.8.1", | ||
"@rollup/plugin-babel": "^6.0.4", | ||
"@rollup/plugin-eslint": "^9.0.5", | ||
"@rollup/plugin-replace": "^5.0.5", | ||
"@rollup/plugin-terser": "^0.4.4", | ||
"@types/jest": "^29.5.12", | ||
"@types/node": "^20.11.25", | ||
"@typescript-eslint/eslint-plugin": "^5.62.0", | ||
"@typescript-eslint/parser": "^5.62.0", | ||
"@vue/eslint-config-typescript": "^11.0.3", | ||
"all-contributors-cli": "^6.26.0", | ||
"all-contributors-cli": "^6.26.1", | ||
"babel-eslint": "^10.1.0", | ||
"babel-jest": "^29.5.0", | ||
"bumpp": "^9.1.1", | ||
"concurrently": "^8.2.0", | ||
"babel-jest": "^29.7.0", | ||
"bumpp": "^9.4.0", | ||
"concurrently": "^8.2.2", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^8.44.0", | ||
"eslint": "^8.57.0", | ||
"eslint-config-standard": "^17.1.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-n": "^16.0.1", | ||
"eslint-plugin-import": "^2.29.1", | ||
"eslint-plugin-n": "^16.6.2", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^6.1.1", | ||
"eslint-plugin-vue": "^9.15.1", | ||
"eslint-plugin-vue": "^9.22.0", | ||
"gh-pages": "^5.0.0", | ||
"husky": "^8.0.3", | ||
"is-ci": "^3.0.1", | ||
"jest": "^29.5.0", | ||
"jest-environment-jsdom": "^29.5.0", | ||
"lint-staged": "^13.2.3", | ||
"rimraf": "^5.0.1", | ||
"rollup": "^3.26.0", | ||
"jest": "^29.7.0", | ||
"jest-environment-jsdom": "^29.7.0", | ||
"lint-staged": "^13.3.0", | ||
"rimraf": "^5.0.5", | ||
"rollup": "^3.29.4", | ||
"rollup-plugin-typescript2": "^0.35.0", | ||
"typescript": "~5.0", | ||
"vue-eslint-parser": "^9.3.1" | ||
"typescript": "~5.0.4", | ||
"vue-eslint-parser": "^9.4.2" | ||
}, | ||
@@ -126,3 +127,13 @@ "repository": { | ||
"author": "Evinma, BuptStEve", | ||
"license": "MIT" | ||
"license": "MIT", | ||
"pnpm": { | ||
"overrides": { | ||
"postcss@<8.4.31": ">=8.4.31", | ||
"vite@>=4.4.0 <4.4.12": ">=4.4.12", | ||
"@babel/traverse@<7.23.2": ">=7.23.2", | ||
"vite@>=4.0.0 <=4.5.1": ">=4.5.2", | ||
"semver@<5.7.2": ">=5.7.2", | ||
"semver@>=6.0.0 <6.3.1": ">=6.3.1" | ||
} | ||
} | ||
} |
@@ -141,2 +141,8 @@ # tua-body-scroll-lock | ||
#### useGlobalLockState: boolean | ||
optional, default: false | ||
Whether to use global `lockState` for every BSL. It's useful when your page have multiple BSL instances. | ||
### TargetElement needs scrolling(iOS only) | ||
@@ -143,0 +149,0 @@ In some scenarios, when scrolling is prohibited, some elements still need to scroll, at this point, pass the targetElement. |
196
src/index.ts
@@ -0,124 +1,22 @@ | ||
import { getLockState } from './getLockState' | ||
import { handleScroll } from './handleScroll' | ||
import { setOverflowHiddenMobile, setOverflowHiddenPc } from './setOverflowHidden' | ||
import type { BSLOptions, Nullable } from './types' | ||
import { | ||
isServer, | ||
detectOS, | ||
getPreventEventDefault, | ||
getEventListenerOptions, | ||
noticeRequiredTargetElement, | ||
} from './utils' | ||
type Nullable<T> = T | Array<T> | null | ||
export interface BSLOptions { | ||
overflowType?: 'hidden' | 'clip' | ||
} | ||
let lockedNum = 0 | ||
let initialClientY = 0 | ||
let initialClientX = 0 | ||
let unLockCallback: null | (() => void) = null | ||
let documentListenerAdded = false | ||
const lockedElements: HTMLElement[] = [] | ||
const eventListenerOptions = getEventListenerOptions({ passive: false }) | ||
const supportsNativeSmoothScroll = !isServer() && 'scrollBehavior' in document.documentElement.style | ||
const setOverflowHiddenPc = () => { | ||
const $html = document.documentElement | ||
const htmlStyle = { ...$html.style } | ||
const scrollBarWidth = window.innerWidth - $html.clientWidth | ||
const previousPaddingRight = parseInt(window.getComputedStyle($html).paddingRight, 10) | ||
function lock (targetElement?: Nullable<HTMLElement>, options?: BSLOptions) { | ||
if (isServer()) return | ||
$html.style.overflow = 'hidden' | ||
$html.style.boxSizing = 'border-box' | ||
$html.style.paddingRight = `${scrollBarWidth + previousPaddingRight}px` | ||
noticeRequiredTargetElement(targetElement) | ||
return () => { | ||
(['overflow', 'boxSizing', 'paddingRight'] as const).forEach((x) => { | ||
$html.style[x] = htmlStyle[x] || '' | ||
}) | ||
} | ||
} | ||
const lockState = getLockState(options) | ||
const setOverflowHiddenMobile = (options?: BSLOptions) => { | ||
const $html = document.documentElement | ||
const $body = document.body | ||
const scrollTop = $html.scrollTop || $body.scrollTop | ||
const htmlStyle = { ...$html.style } | ||
const bodyStyle = { ...$body.style } | ||
$html.style.height = '100%' | ||
$html.style.overflow = 'hidden' | ||
$body.style.top = `-${scrollTop}px` | ||
$body.style.width = '100%' | ||
$body.style.height = 'auto' | ||
$body.style.position = 'fixed' | ||
$body.style.overflow = options?.overflowType || 'hidden' | ||
return () => { | ||
$html.style.height = htmlStyle.height || '' | ||
$html.style.overflow = htmlStyle.overflow || '' | ||
;(['top', 'width', 'height', 'overflow', 'position'] as const).forEach((x) => { | ||
$body.style[x] = bodyStyle[x] || '' | ||
}) | ||
const scrollToOptions = { top: scrollTop, behavior: 'instant' } | ||
supportsNativeSmoothScroll | ||
? window.scrollTo(scrollToOptions as unknown as ScrollToOptions) | ||
: window.scrollTo(0, scrollTop) | ||
} | ||
} | ||
const preventDefault = (event: TouchEvent) => { | ||
if (!event.cancelable) return | ||
event.preventDefault() | ||
} | ||
const handleScroll = (event: TouchEvent, targetElement: HTMLElement) => { | ||
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)) | ||
) { | ||
return preventDefault(event) | ||
} | ||
} | ||
event.stopPropagation() | ||
return true | ||
} | ||
const checkTargetElement = (targetElement?: Nullable<HTMLElement>) => { | ||
if (targetElement) return | ||
if (targetElement === null) return | ||
if (process.env.NODE_ENV === 'production') return | ||
console.warn( | ||
'If scrolling is also required in the floating layer, ' + | ||
'the target element must be provided.', | ||
) | ||
} | ||
const lock = (targetElement?: Nullable<HTMLElement>, options?: BSLOptions) => { | ||
if (isServer()) return | ||
checkTargetElement(targetElement) | ||
if (detectOS().ios) { | ||
@@ -130,6 +28,6 @@ // iOS | ||
elementArray.forEach((element) => { | ||
if (element && lockedElements.indexOf(element) === -1) { | ||
if (element && lockState.lockedElements.indexOf(element) === -1) { | ||
element.ontouchstart = (event) => { | ||
initialClientY = event.targetTouches[0].clientY | ||
initialClientX = event.targetTouches[0].clientX | ||
const { clientX, clientY } = event.targetTouches[0] | ||
lockState.initialClientPos = { clientX, clientY } | ||
} | ||
@@ -140,6 +38,6 @@ | ||
handleScroll(event, element) | ||
handleScroll(event, element, lockState.initialClientPos) | ||
} | ||
lockedElements.push(element) | ||
lockState.lockedElements.push(element) | ||
} | ||
@@ -149,8 +47,8 @@ }) | ||
if (!documentListenerAdded) { | ||
document.addEventListener('touchmove', preventDefault, eventListenerOptions) | ||
documentListenerAdded = true | ||
if (!lockState.documentListenerAdded) { | ||
document.addEventListener('touchmove', getPreventEventDefault(), eventListenerOptions) | ||
lockState.documentListenerAdded = true | ||
} | ||
} else if (lockedNum <= 0) { | ||
unLockCallback = detectOS().android | ||
} else if (lockState.lockedNum <= 0) { | ||
lockState.unLockCallback = detectOS().android | ||
? setOverflowHiddenMobile(options) | ||
@@ -160,17 +58,20 @@ : setOverflowHiddenPc() | ||
lockedNum += 1 | ||
lockState.lockedNum += 1 | ||
} | ||
const unlock = (targetElement?: Nullable<HTMLElement>) => { | ||
function unlock (targetElement?: Nullable<HTMLElement>, options?: BSLOptions) { | ||
if (isServer()) return | ||
checkTargetElement(targetElement) | ||
lockedNum -= 1 | ||
noticeRequiredTargetElement(targetElement) | ||
if (lockedNum > 0) return | ||
const lockState = getLockState(options) | ||
lockState.lockedNum -= 1 | ||
if (lockState.lockedNum > 0) return | ||
if ( | ||
!detectOS().ios && | ||
typeof unLockCallback === 'function' | ||
typeof lockState.unLockCallback === 'function' | ||
) { | ||
unLockCallback() | ||
lockState.unLockCallback() | ||
return | ||
@@ -184,3 +85,3 @@ } | ||
elementArray.forEach((element) => { | ||
const index = lockedElements.indexOf(element) | ||
const index = lockState.lockedElements.indexOf(element) | ||
@@ -190,3 +91,3 @@ if (index !== -1) { | ||
element.ontouchstart = null | ||
lockedElements.splice(index, 1) | ||
lockState.lockedElements.splice(index, 1) | ||
} | ||
@@ -196,23 +97,27 @@ }) | ||
if (documentListenerAdded) { | ||
document.removeEventListener('touchmove', preventDefault, eventListenerOptions) | ||
documentListenerAdded = false | ||
if (lockState.documentListenerAdded) { | ||
document.removeEventListener('touchmove', getPreventEventDefault(), eventListenerOptions) | ||
lockState.documentListenerAdded = false | ||
} | ||
} | ||
const clearBodyLocks = () => { | ||
function clearBodyLocks (options?: BSLOptions) { | ||
if (isServer()) return | ||
lockedNum = 0 | ||
const lockState = getLockState(options) | ||
lockState.lockedNum = 0 | ||
if ( | ||
!detectOS().ios && | ||
typeof unLockCallback === 'function' | ||
typeof lockState.unLockCallback === 'function' | ||
) { | ||
unLockCallback() | ||
lockState.unLockCallback() | ||
return | ||
} | ||
// IOS | ||
if (lockedElements.length) { | ||
// iOS | ||
if (lockState.lockedElements.length) { | ||
// clear events | ||
let element = lockedElements.pop() | ||
let element = lockState.lockedElements.pop() | ||
while (element) { | ||
@@ -222,12 +127,13 @@ element.ontouchmove = null | ||
element = lockedElements.pop() | ||
element = lockState.lockedElements.pop() | ||
} | ||
} | ||
if (documentListenerAdded) { | ||
document.removeEventListener('touchmove', preventDefault, eventListenerOptions) | ||
documentListenerAdded = false | ||
if (lockState.documentListenerAdded) { | ||
document.removeEventListener('touchmove', getPreventEventDefault(), eventListenerOptions) | ||
lockState.documentListenerAdded = false | ||
} | ||
} | ||
export * from './types' | ||
export { lock, unlock, clearBodyLocks } |
@@ -0,1 +1,3 @@ | ||
import { Nullable } from './types' | ||
export const isServer = () => typeof window === 'undefined' | ||
@@ -45,1 +47,33 @@ | ||
} | ||
export function noticeRequiredTargetElement (targetElement?: Nullable<HTMLElement>): boolean { | ||
if (targetElement) return false | ||
if (targetElement === null) return false | ||
/* istanbul ignore if */ | ||
if (process.env.NODE_ENV === 'production') return false | ||
/* istanbul ignore if */ | ||
if (process.env.NODE_ENV !== 'test') { | ||
console.warn( | ||
'If scrolling is also required in the floating layer, ' + | ||
'the target element must be provided.', | ||
) | ||
} | ||
return true | ||
} | ||
/** | ||
* Get global function that calls preventDefault | ||
*/ | ||
export function getPreventEventDefault () { | ||
if ('__BSL_PREVENT_DEFAULT__' in window) { | ||
return window.__BSL_PREVENT_DEFAULT__! | ||
} | ||
window.__BSL_PREVENT_DEFAULT__ = function (event: TouchEvent) { | ||
if (!event.cancelable) return | ||
event.preventDefault() | ||
} | ||
return window.__BSL_PREVENT_DEFAULT__ | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
68023
25
1201
207