{"version":3,"file":"body-scroll-lock.mjs","names":[],"sources":["../src/body-scroll-lock.ts"],"sourcesContent":["import { IN_WINDOW } from '@fastkit/helpers';\n\nexport interface BodyScrollOptions {\n  reserveScrollBarGap?: boolean;\n  allowTouchMove?: (el: EventTarget) => boolean;\n}\n\ninterface Lock {\n  targetElement: any;\n  options: BodyScrollOptions;\n}\n\nconst isIosDevice =\n  IN_WINDOW &&\n  !!window.navigator &&\n  !!window.navigator.platform &&\n  /iP(ad|hone|od)/.test(window.navigator.platform);\ntype HandleScrollEvent = TouchEvent;\n\nlet locks: Array<Lock> = [];\nlet documentListenerAdded = false;\nlet initialClientY = -1;\nlet previousBodyOverflowSetting: string | undefined;\nlet previousBodyPaddingRight: string | undefined;\n\n// returns true if `el` should be allowed to receive touchmove events.\nconst allowTouchMove = (el: EventTarget): boolean =>\n  locks.some((lock) => {\n    if (lock.options.allowTouchMove && lock.options.allowTouchMove(el)) {\n      return true;\n    }\n\n    return false;\n  });\n\nconst preventDefault = (rawEvent: HandleScrollEvent): boolean => {\n  const e = rawEvent || window.event;\n\n  // For the case whereby consumers adds a touchmove event listener to document.\n  // Recall that we do document.addEventListener('touchmove', preventDefault, { passive: false })\n  // in disableBodyScroll - so if we provide this opportunity to allowTouchMove, then\n  // the touchmove event on document will break.\n  if (allowTouchMove((e as any).target)) {\n    return true;\n  }\n\n  // Do not prevent if the event has more than one touch (usually meaning this is a multi touch gesture like pinch to zoom).\n  if (e.touches.length > 1) return true;\n\n  if (e.preventDefault) e.preventDefault();\n\n  return false;\n};\n\nconst setOverflowHidden = (options?: BodyScrollOptions) => {\n  // Setting overflow on body/documentElement synchronously in Desktop Safari slows down\n  // the responsiveness for some reason. Setting within a setTimeout fixes this.\n  setTimeout(() => {\n    // If previousBodyPaddingRight is already set, don't set it again.\n    if (previousBodyPaddingRight === undefined) {\n      const reserveScrollBarGap =\n        !!options && options.reserveScrollBarGap === true;\n      const scrollBarGap =\n        window.innerWidth - document.documentElement.clientWidth;\n\n      if (reserveScrollBarGap && scrollBarGap > 0) {\n        previousBodyPaddingRight = document.body.style.paddingRight;\n        document.body.style.paddingRight = `${scrollBarGap}px`;\n      }\n    }\n\n    // If previousBodyOverflowSetting is already set, don't set it again.\n    if (previousBodyOverflowSetting === undefined) {\n      previousBodyOverflowSetting = document.body.style.overflow;\n      document.body.style.overflow = 'hidden';\n    }\n  });\n};\n\nconst restoreOverflowSetting = () => {\n  // Setting overflow on body/documentElement synchronously in Desktop Safari slows down\n  // the responsiveness for some reason. Setting within a setTimeout fixes this.\n  setTimeout(() => {\n    if (previousBodyPaddingRight !== undefined) {\n      document.body.style.paddingRight = previousBodyPaddingRight;\n\n      // Restore previousBodyPaddingRight to undefined so setOverflowHidden knows it\n      // can be set again.\n      previousBodyPaddingRight = undefined;\n    }\n\n    if (previousBodyOverflowSetting !== undefined) {\n      document.body.style.overflow = previousBodyOverflowSetting;\n\n      // Restore previousBodyOverflowSetting to undefined\n      // so setOverflowHidden knows it can be set again.\n      previousBodyOverflowSetting = undefined;\n    }\n  });\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions\nconst isTargetElementTotallyScrolled = (targetElement: any): boolean =>\n  targetElement\n    ? targetElement.scrollHeight - targetElement.scrollTop <=\n      targetElement.clientHeight\n    : false;\n\nconst handleScroll = (\n  event: HandleScrollEvent,\n  targetElement: any,\n): boolean => {\n  const clientY = event.targetTouches[0].clientY - initialClientY;\n\n  if (allowTouchMove(event.target as any)) {\n    return false;\n  }\n\n  if (targetElement && targetElement.scrollTop === 0 && clientY > 0) {\n    // element is at the top of its scroll.\n    return preventDefault(event);\n  }\n\n  if (isTargetElementTotallyScrolled(targetElement) && clientY < 0) {\n    // element is at the top of its scroll.\n    return preventDefault(event);\n  }\n\n  event.stopPropagation();\n  return true;\n};\n\nexport const disableBodyScroll = (\n  targetElement: any,\n  options?: BodyScrollOptions,\n): void => {\n  if (!IN_WINDOW) return;\n\n  if (isIosDevice) {\n    // targetElement must be provided, and disableBodyScroll must not have been\n    // called on this targetElement before.\n    if (!targetElement) {\n      // eslint-disable-next-line no-console\n      console.error(\n        'disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.',\n      );\n      return;\n    }\n\n    if (\n      targetElement &&\n      !locks.some((lock) => lock.targetElement === targetElement)\n    ) {\n      const lock = {\n        targetElement,\n        options: options || {},\n      };\n\n      locks = [...locks, lock];\n\n      targetElement.ontouchstart = (event: HandleScrollEvent) => {\n        if (event.targetTouches.length === 1) {\n          // detect single touch.\n          initialClientY = event.targetTouches[0].clientY;\n        }\n      };\n      targetElement.ontouchmove = (event: HandleScrollEvent) => {\n        if (event.targetTouches.length === 1) {\n          // detect single touch.\n          handleScroll(event, targetElement);\n        }\n      };\n\n      if (!documentListenerAdded) {\n        document.addEventListener('touchmove', preventDefault, {\n          passive: false,\n        });\n        documentListenerAdded = true;\n      }\n    }\n  } else {\n    setOverflowHidden(options);\n    const lock = {\n      targetElement,\n      options: options || {},\n    };\n\n    locks = [...locks, lock];\n  }\n};\n\nexport const clearAllBodyScrollLocks = (): void => {\n  if (!IN_WINDOW) return;\n\n  if (isIosDevice) {\n    // Clear all locks ontouchstart/ontouchmove handlers, and the references.\n    locks.forEach((lock: Lock) => {\n      lock.targetElement.ontouchstart = null;\n      lock.targetElement.ontouchmove = null;\n    });\n\n    if (documentListenerAdded) {\n      document.removeEventListener(\n        'touchmove',\n        preventDefault as any,\n        { passive: false } as any,\n      );\n      documentListenerAdded = false;\n    }\n\n    locks = [];\n\n    // Reset initial clientY.\n    initialClientY = -1;\n  } else {\n    restoreOverflowSetting();\n    locks = [];\n  }\n};\n\nexport const enableBodyScroll = (targetElement: any): void => {\n  if (!IN_WINDOW) return;\n\n  if (isIosDevice) {\n    if (!targetElement) {\n      // eslint-disable-next-line no-console\n      console.error(\n        'enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.',\n      );\n      return;\n    }\n\n    targetElement.ontouchstart = null;\n    targetElement.ontouchmove = null;\n\n    locks = locks.filter((lock) => lock.targetElement !== targetElement);\n\n    if (documentListenerAdded && locks.length === 0) {\n      document.removeEventListener(\n        'touchmove',\n        preventDefault as any,\n        { passive: false } as any,\n      );\n\n      documentListenerAdded = false;\n    }\n  } else {\n    locks = locks.filter((lock) => lock.targetElement !== targetElement);\n    if (!locks.length) {\n      restoreOverflowSetting();\n    }\n  }\n};\n"],"mappings":"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"}