{"version":3,"sources":["../../../../src/sensors/useScrollLock/core.ts"],"sourcesContent":["import { observable, ObservableHint, type Observable, type OpaqueObject } from \"@legendapp/state\";\nimport { type DeepMaybeObservable, get, createObserve, onUnmount } from \"@usels/core\";\nimport type { MaybeObservable } from \"@usels/core\";\nimport type { MaybeEventTarget } from \"../../types\";\nimport { resolveWindowSource, type ConfigurableWindow } from \"@shared/configurable\";\nimport { createEventListener } from \"../../browser/useEventListener/core\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseScrollLockReturn {\n  /** Current scroll lock state */\n  isLocked$: Observable<boolean>;\n  /** Lock scrolling */\n  lock: () => void;\n  /** Unlock scrolling */\n  unlock: () => void;\n  /** Toggle lock state */\n  toggle: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getScrollbarWidth(win: Window | null | undefined): number {\n  if (!win) return 0;\n  return win.innerWidth - win.document.documentElement.clientWidth;\n}\n\nfunction resolveInitialValue(initialState?: MaybeObservable<boolean>): boolean {\n  if (initialState == null) return false;\n  if (typeof initialState === \"boolean\") return initialState;\n  if (typeof initialState === \"object\" && \"peek\" in initialState) {\n    return (initialState as Observable<boolean>).peek();\n  }\n  return false;\n}\n\n// ---------------------------------------------------------------------------\n// Core\n// ---------------------------------------------------------------------------\n\n/**\n * Framework-agnostic reactive scroll-lock controller. Applies\n * `overflow: hidden` and scrollbar-width padding to the target element (or\n * `document.body` when no target is provided), and toggles the `touchmove`\n * preventDefault listener for iOS Safari support.\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function createScrollLock(\n  element?: MaybeEventTarget<HTMLElement>,\n  initialState?: MaybeObservable<boolean>,\n  options?: DeepMaybeObservable<ConfigurableWindow>\n): UseScrollLockReturn {\n  const isLocked$ = observable<boolean>(resolveInitialValue(initialState));\n  let savedOverflow = \"\";\n  let savedPaddingRight = \"\";\n  let lockedElement: HTMLElement | null = null;\n\n  const opts$ = observable(options);\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- window field type varies by hint map\n  const window$ = resolveWindowSource(opts$.window as any);\n\n  // Derive document from resolved window.\n  const doc$ = observable<OpaqueObject<Document> | null>(() => {\n    const win = window$.get();\n    return win ? (ObservableHint.opaque(win.document) as OpaqueObject<Document>) : null;\n  });\n\n  const applyLock = (el: HTMLElement) => {\n    if (lockedElement === el) return;\n    if (lockedElement) {\n      lockedElement.style.overflow = savedOverflow;\n      lockedElement.style.paddingRight = savedPaddingRight;\n    }\n    savedOverflow = el.style.overflow;\n    savedPaddingRight = el.style.paddingRight;\n\n    const scrollbarWidth = getScrollbarWidth(window$.peek());\n    el.style.overflow = \"hidden\";\n    if (scrollbarWidth > 0) {\n      el.style.paddingRight = `${scrollbarWidth}px`;\n    }\n    lockedElement = el;\n  };\n\n  const removeLock = () => {\n    if (!lockedElement) return;\n    lockedElement.style.overflow = savedOverflow;\n    lockedElement.style.paddingRight = savedPaddingRight;\n    lockedElement = null;\n  };\n\n  const lock = () => {\n    isLocked$.set(true);\n  };\n\n  const unlock = () => {\n    isLocked$.set(false);\n  };\n\n  const toggle = () => {\n    isLocked$.set((v) => !v);\n  };\n\n  // React to isLocked$ changes and element lifecycle.\n  createObserve(() => {\n    const el = get(element) as HTMLElement | null;\n    const hasExplicitElement = element != null;\n    const target = hasExplicitElement ? el : (el ?? window$.peek()?.document.body ?? null);\n    const locked = isLocked$.get();\n\n    if (locked && target) {\n      applyLock(target);\n    } else {\n      removeLock();\n    }\n  });\n\n  // iOS Safari: prevent touchmove while locked.\n  const onTouchMove = (e: TouchEvent) => {\n    if (isLocked$.peek()) e.preventDefault();\n  };\n\n  createEventListener(\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- element fallback to document\n    (element ?? (doc$ as any)) as MaybeEventTarget,\n    \"touchmove\",\n    onTouchMove,\n    { passive: false }\n  );\n\n  onUnmount(removeLock);\n\n  return { isLocked$, lock, unlock, toggle };\n}\n"],"mappings":"AAAA,SAAS,YAAY,sBAA0D;AAC/E,SAAmC,KAAK,eAAe,iBAAiB;AAGxE,SAAS,2BAAoD;AAC7D,SAAS,2BAA2B;AAqBpC,SAAS,kBAAkB,KAAwC;AACjE,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,aAAa,IAAI,SAAS,gBAAgB;AACvD;AAEA,SAAS,oBAAoB,cAAkD;AAC7E,MAAI,gBAAgB,KAAM,QAAO;AACjC,MAAI,OAAO,iBAAiB,UAAW,QAAO;AAC9C,MAAI,OAAO,iBAAiB,YAAY,UAAU,cAAc;AAC9D,WAAQ,aAAqC,KAAK;AAAA,EACpD;AACA,SAAO;AACT;AAAA;AAaO,SAAS,iBACd,SACA,cACA,SACqB;AACrB,QAAM,YAAY,WAAoB,oBAAoB,YAAY,CAAC;AACvE,MAAI,gBAAgB;AACpB,MAAI,oBAAoB;AACxB,MAAI,gBAAoC;AAExC,QAAM,QAAQ,WAAW,OAAO;AAEhC,QAAM,UAAU,oBAAoB,MAAM,MAAa;AAGvD,QAAM,OAAO,WAA0C,MAAM;AAC3D,UAAM,MAAM,QAAQ,IAAI;AACxB,WAAO,MAAO,eAAe,OAAO,IAAI,QAAQ,IAA+B;AAAA,EACjF,CAAC;AAED,QAAM,YAAY,CAAC,OAAoB;AACrC,QAAI,kBAAkB,GAAI;AAC1B,QAAI,eAAe;AACjB,oBAAc,MAAM,WAAW;AAC/B,oBAAc,MAAM,eAAe;AAAA,IACrC;AACA,oBAAgB,GAAG,MAAM;AACzB,wBAAoB,GAAG,MAAM;AAE7B,UAAM,iBAAiB,kBAAkB,QAAQ,KAAK,CAAC;AACvD,OAAG,MAAM,WAAW;AACpB,QAAI,iBAAiB,GAAG;AACtB,SAAG,MAAM,eAAe,GAAG,cAAc;AAAA,IAC3C;AACA,oBAAgB;AAAA,EAClB;AAEA,QAAM,aAAa,MAAM;AACvB,QAAI,CAAC,cAAe;AACpB,kBAAc,MAAM,WAAW;AAC/B,kBAAc,MAAM,eAAe;AACnC,oBAAgB;AAAA,EAClB;AAEA,QAAM,OAAO,MAAM;AACjB,cAAU,IAAI,IAAI;AAAA,EACpB;AAEA,QAAM,SAAS,MAAM;AACnB,cAAU,IAAI,KAAK;AAAA,EACrB;AAEA,QAAM,SAAS,MAAM;AACnB,cAAU,IAAI,CAAC,MAAM,CAAC,CAAC;AAAA,EACzB;AAGA,gBAAc,MAAM;AAClB,UAAM,KAAK,IAAI,OAAO;AACtB,UAAM,qBAAqB,WAAW;AACtC,UAAM,SAAS,qBAAqB,KAAM,MAAM,QAAQ,KAAK,GAAG,SAAS,QAAQ;AACjF,UAAM,SAAS,UAAU,IAAI;AAE7B,QAAI,UAAU,QAAQ;AACpB,gBAAU,MAAM;AAAA,IAClB,OAAO;AACL,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AAGD,QAAM,cAAc,CAAC,MAAkB;AACrC,QAAI,UAAU,KAAK,EAAG,GAAE,eAAe;AAAA,EACzC;AAEA;AAAA;AAAA,IAEG,WAAY;AAAA,IACb;AAAA,IACA;AAAA,IACA,EAAE,SAAS,MAAM;AAAA,EACnB;AAEA,YAAU,UAAU;AAEpB,SAAO,EAAE,WAAW,MAAM,QAAQ,OAAO;AAC3C;","names":[]}