{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["interface BodyScrollOptions {\n  reserveScrollbarGutter?: boolean\n  /** @deprecated use reserveScrollbarGutter instead */\n  reserveScrollBarGap?: boolean\n  allowTouchMove?: (el: EventTarget) => boolean\n}\n\ninterface HtmlStyleType {\n  height: string\n  overflow: string\n}\n\ntype BodyStyleType = HtmlStyleType & {\n  position: string\n  top: string\n  left: string\n  width: string\n}\n\ntype HandleScrollEvent = TouchEvent\n\ninterface Lock {\n  targetElement: HTMLElement\n  options: BodyScrollOptions\n}\n\n/**\n * Determines the type of scroll lock required for the current device.\n * @returns {'overflow' | 'fixed'} The type of scroll lock required.\n *\n * Note: From iOS 16 onwards, using 'overflow: hidden' is possible and is a less complex solution.\n */\nconst getScrollLockType = (): 'overflow' | 'fixed' => {\n  if (typeof window === 'undefined' || !window.navigator) {\n    return 'overflow'\n  }\n\n  const isIosDevice = /iP(ad|hone|od)/.test(window.navigator.platform) && window.navigator.maxTouchPoints > 1\n  if (!isIosDevice) {\n    return 'overflow'\n  }\n\n  const match = window.navigator.userAgent.match(/Version\\/(\\d+\\.\\d+)/)\n  return match && parseInt(match[1]) >= 16 ? 'overflow' : 'fixed'\n}\n\nconst isPositionFixedRequired = getScrollLockType() === 'fixed'\n\n// Feature detect passive events\nlet hasPassiveEvents = false\nif (typeof window !== 'undefined') {\n  const passiveTestOptions: AddEventListenerOptions = {\n    get passive() {\n      hasPassiveEvents = true\n      return undefined\n    }\n  }\n\n  window.addEventListener('__BSL_TEST_PASSIVE__', () => ({}), passiveTestOptions)\n  window.removeEventListener('__BSL_TEST_PASSIVE__', () => ({}), passiveTestOptions)\n}\n\nlet locks: Array<Lock> = []\nconst locksIndex = new Map<HTMLElement, number>()\n\nlet documentListenerAdded = false\nlet initialClientY = -1\nlet htmlStyle: HtmlStyleType | undefined\nlet bodyStyle: BodyStyleType | undefined\nlet previousOverflowSetting: string | undefined\nlet previousScrollbarGutter: string | boolean | undefined\n\n/**\n * Determines if the element should allow touchmove events.\n * @param {EventTarget} el - The event target element.\n * @returns {boolean} True if the element should allow touchmove events, false otherwise.\n */\nconst allowTouchMove = (el: EventTarget): boolean => {\n  return locks.some((lock) => lock.options.allowTouchMove?.(el))\n}\n\n/**\n * Prevents the default behavior for touch events if necessary.\n * @param {HandleScrollEvent} rawEvent - The raw touch event.\n * @returns {boolean} True if default behavior should not be prevented, false otherwise.\n */\nconst preventDefault = (rawEvent: HandleScrollEvent): boolean => {\n  const event = rawEvent || window.event\n\n  if (allowTouchMove(event.target as Element)) {\n    return true\n  }\n  if (event.touches.length > 1) {\n    return true\n  }\n\n  if (event.preventDefault) {\n    event.preventDefault()\n  }\n  return false\n}\n\n/**\n * Sets overflow to hidden and optionally reserves scrollbar gap.\n * @param {BodyScrollOptions} [options] - The options for setting overflow hidden.\n */\nconst setOverflowHidden = (options?: BodyScrollOptions): void => {\n  const reserveScrollbarGutter = options?.reserveScrollbarGutter ?? options?.reserveScrollBarGap\n\n  if (reserveScrollbarGutter && previousScrollbarGutter === undefined) {\n    const scrollbarGutterPropertySupported = CSS.supports('scrollbar-gutter', 'stable')\n\n    if (scrollbarGutterPropertySupported) {\n      previousScrollbarGutter = true\n      document.documentElement.style.scrollbarGutter = 'stable'\n    } else {\n      const scrollBarGap = window.innerWidth - document.documentElement.getBoundingClientRect().width\n\n      if (scrollBarGap > 0) {\n        const computedBodyPaddingRight = parseInt(window.getComputedStyle(document.body).getPropertyValue('padding-right'), 10)\n        previousScrollbarGutter = document.body.style.paddingRight\n        document.body.style.paddingRight = `${computedBodyPaddingRight + scrollBarGap}px`\n      }\n    }\n  }\n\n  if (previousOverflowSetting === undefined) {\n    previousOverflowSetting = document.body.style.overflow\n    document.body.style.overflow = 'hidden'\n  }\n}\n\n/**\n * Restores the previous overflow settings.\n */\nconst restoreOverflowSetting = (): void => {\n  if (previousScrollbarGutter !== undefined) {\n    const scrollbarGutterPropertySupported = CSS.supports('scrollbar-gutter', 'stable')\n\n    if (scrollbarGutterPropertySupported) {\n      document.documentElement.style.scrollbarGutter = ''\n    } else {\n      document.body.style.paddingRight = previousScrollbarGutter as string\n    }\n\n    previousScrollbarGutter = undefined\n  }\n\n  if (previousOverflowSetting !== undefined) {\n    document.body.style.overflow = previousOverflowSetting\n    previousOverflowSetting = undefined\n  }\n}\n\n/**\n * Sets the body position to fixed and locks the scroll.\n */\nconst setPositionFixed = (): void => {\n  window.requestAnimationFrame(() => {\n    const $html = document.documentElement\n    const $body = document.body\n\n    if (bodyStyle === undefined) {\n      htmlStyle = { ...$html.style }\n      bodyStyle = { ...$body.style }\n\n      const { scrollY, scrollX, innerHeight } = window\n\n      $html.style.height = '100%'\n      $html.style.overflow = 'hidden'\n      $html.style.setProperty('--bsl-top', `${scrollY}px`)\n\n      $body.style.position = 'fixed'\n      $body.style.top = `${-scrollY}px`\n      $body.style.left = `${-scrollX}px`\n      $body.style.width = '100%'\n      $body.style.height = 'auto'\n      $body.style.overflow = 'hidden'\n\n      setTimeout(() => window.requestAnimationFrame(() => {\n        const bottomBarHeight = innerHeight - window.innerHeight\n        if (bottomBarHeight && scrollY >= innerHeight) {\n          $body.style.top = `${-(scrollY + bottomBarHeight)}px`\n        }\n      }), 300)\n    }\n  })\n}\n\n/**\n * Restores the previous body and html position settings.\n */\nconst restorePositionSetting = (): void => {\n  if (bodyStyle !== undefined) {\n    const $html = document.documentElement\n    const $body = document.body\n\n    const y = -parseInt($body.style.top, 10)\n    const x = -parseInt($body.style.left, 10)\n\n    $html.style.height = htmlStyle?.height ?? ''\n    $html.style.overflow = htmlStyle?.overflow ?? ''\n    $html.style.removeProperty('--bsl-top')\n\n    $body.style.position = bodyStyle.position || ''\n    $body.style.top = bodyStyle.top || ''\n    $body.style.left = bodyStyle.left || ''\n    $body.style.width = bodyStyle.width || ''\n    $body.style.height = bodyStyle.height || ''\n    $body.style.overflow = bodyStyle.overflow || ''\n\n    const scrollBehaviorPropertySupported = CSS.supports('scroll-behavior', 'smooth')\n    if (scrollBehaviorPropertySupported) {\n      window.scrollTo({ left: x, top: y, behavior: 'instant' })\n    } else {\n      window.scrollTo(x, y)\n    }\n\n    bodyStyle = undefined\n  }\n}\n\n/**\n * Checks if the target element is totally scrolled.\n * @param {HTMLElement} targetElement - The target element.\n * @returns {boolean} True if the target element is totally scrolled, false otherwise.\n */\nconst isTargetElementTotallyScrolled = (targetElement: HTMLElement): boolean => {\n  return targetElement ? targetElement.scrollHeight - targetElement.scrollTop <= targetElement.clientHeight : false\n}\n\n/**\n * Handles the scroll event for the target element.\n * @param {HandleScrollEvent} event - The scroll event.\n * @param {HTMLElement} targetElement - The target element.\n * @returns {boolean} True if the event should not be prevented, false otherwise.\n */\nconst handleScroll = (event: HandleScrollEvent, targetElement: HTMLElement): boolean => {\n  const clientY = event.targetTouches[0].clientY - initialClientY\n\n  if (allowTouchMove(event.target!)) {\n    return false\n  }\n\n  if (targetElement && targetElement.scrollTop === 0 && clientY > 0) {\n    return preventDefault(event)\n  }\n\n  if (isTargetElementTotallyScrolled(targetElement) && clientY < 0) {\n    return preventDefault(event)\n  }\n\n  event.stopPropagation()\n  return true\n}\n\n/**\n * Disables body scroll for the target element.\n * @param {HTMLElement} targetElement - The target element.\n * @param {BodyScrollOptions} [options] - The options for disabling body scroll.\n */\nexport const disableBodyScroll = (targetElement: HTMLElement, options?: BodyScrollOptions): void => {\n  if (!targetElement) {\n    console.error('disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll.')\n    return\n  }\n\n  locksIndex.set(targetElement, (locksIndex.get(targetElement) ?? 0) + 1)\n\n  if (locks.some((lock) => lock.targetElement === targetElement)) {\n    return\n  }\n\n  const lock = { targetElement, options: options ?? {} }\n  locks = [...locks, lock]\n\n  if (isPositionFixedRequired) {\n    setPositionFixed()\n  } else {\n    setOverflowHidden(options)\n  }\n\n  if (isPositionFixedRequired) {\n    targetElement.ontouchstart = (event: HandleScrollEvent) => {\n      if (event.targetTouches.length === 1) {\n        initialClientY = event.targetTouches[0].clientY\n      }\n    }\n    targetElement.ontouchmove = (event: HandleScrollEvent) => {\n      if (event.targetTouches.length === 1) {\n        handleScroll(event, targetElement)\n      }\n    }\n\n    if (!documentListenerAdded) {\n      document.addEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined)\n      documentListenerAdded = true\n    }\n  }\n}\n\n/**\n * Clears all body scroll locks.\n */\nexport const clearAllBodyScrollLocks = (): void => {\n  if (isPositionFixedRequired) {\n    locks.forEach((lock) => {\n      lock.targetElement.ontouchstart = null\n      lock.targetElement.ontouchmove = null\n    })\n\n    if (documentListenerAdded) {\n      document.removeEventListener('touchmove', preventDefault)\n      documentListenerAdded = false\n    }\n\n    initialClientY = -1\n  }\n\n  if (isPositionFixedRequired) {\n    restorePositionSetting()\n  } else {\n    restoreOverflowSetting()\n  }\n\n  locks = []\n  locksIndex.clear()\n}\n\n/**\n * Enables body scroll for the target element.\n * @param {HTMLElement} targetElement - The target element.\n */\nexport const enableBodyScroll = (targetElement: HTMLElement): void => {\n  if (!targetElement) {\n    console.error('enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll.')\n    return\n  }\n\n  locksIndex.set(targetElement, (locksIndex.get(targetElement) ?? 0) - 1)\n\n  if (locksIndex.get(targetElement) === 0) {\n    locks = locks.filter((lock) => lock.targetElement !== targetElement)\n    locksIndex.delete(targetElement)\n  }\n\n  if (isPositionFixedRequired) {\n    targetElement.ontouchstart = null\n    targetElement.ontouchmove = null\n\n    if (documentListenerAdded && locks.length === 0) {\n      document.removeEventListener('touchmove', preventDefault)\n      documentListenerAdded = false\n    }\n  }\n\n  if (locks.length === 0) {\n    if (isPositionFixedRequired) {\n      restorePositionSetting()\n    } else {\n      restoreOverflowSetting()\n    }\n  }\n}\n\n/**\n * Toggles body scroll lock for the target element based on the value parameter in options.\n * @param {HTMLElement} targetElement - The target element.\n * @param {BodyScrollOptions & { toggleValue?: boolean }} [options] - The options for toggling body scroll lock, including the toggleValue parameter.\n */\nexport const toggleBodyScrollLock = (targetElement: HTMLElement, options?: BodyScrollOptions & { toggleValue?: boolean }): void => {\n  const { toggleValue, ...bodyScrollOptions } = options ?? {}\n\n  if (toggleValue === undefined) {\n    if (locks.some((lock) => lock.targetElement === targetElement)) {\n      enableBodyScroll(targetElement)\n    } else {\n      disableBodyScroll(targetElement, bodyScrollOptions)\n    }\n  } else if (toggleValue) {\n    disableBodyScroll(targetElement, bodyScrollOptions)\n  } else {\n    enableBodyScroll(targetElement)\n  }\n}\n"],"names":["getScrollLockType","match","isPositionFixedRequired","hasPassiveEvents","passiveTestOptions","locks","locksIndex","documentListenerAdded","initialClientY","htmlStyle","bodyStyle","previousOverflowSetting","previousScrollbarGutter","allowTouchMove","el","lock","preventDefault","rawEvent","event","setOverflowHidden","options","scrollBarGap","computedBodyPaddingRight","restoreOverflowSetting","setPositionFixed","$html","$body","scrollY","scrollX","innerHeight","bottomBarHeight","restorePositionSetting","y","x","isTargetElementTotallyScrolled","targetElement","handleScroll","clientY","toggleValue","bodyScrollOptions"],"mappings":"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"}