/** * Mobile scroll lock for the chat widget (no third-party deps). * * Two layers, applied together while the panel is open on a mobile viewport: * * 1. CSS lock on `` + `` — `overflow: hidden` plus `position: * fixed` on `` pinned at the current scroll offset. This freezes the * document scroller (the host app's confirmed scroll mode) and the offset * is restored on unlock so the page doesn't jump. * 2. Capture-phase, non-passive `touchmove` / `wheel` guards on `document` — * block iOS rubber-band / momentum and any non-document scroller, while * still allowing scrolling inside `.vt-messages` / `.vt-list-root` (and an * overflowing composer textarea) only when those surfaces actually * overflow and the gesture has room to scroll (edge rubber-band is * blocked so iOS cannot pan the visual viewport / drag the composer). * `composedPath()` is used so the allow-list works across the Shadow-DOM * boundary (iOS retargets `event.target` to the shadow host). */ export interface MobileBodyScrollTransition { nextWasOpen: boolean; shouldLock: boolean; shouldUnlock: boolean; } /** In-widget scroll surfaces that must keep touch / wheel scrolling. */ export declare const CHAT_SCROLLABLE_SELECTOR = ".vt-messages, .vt-list-root"; /** * Given the previous and next panel open state, decide whether to lock or * unlock host-page scroll. The lock is intentionally tied to the * mobile-fullscreen breakpoint (≤ 480px): only there does the panel cover the * whole viewport, so only there must the host page be frozen. On desktop the * panel is a corner window and the page stays scrollable. */ export declare function mobileBodyScrollTransition(wasOpen: boolean, open: boolean, isMobileViewport: boolean): MobileBodyScrollTransition; /** Shadow host for the chat widget, or the container when not in a shadow tree. */ export declare function getChatShadowHost(container: HTMLDivElement | null): HTMLElement | null; /** Sub-pixel / rounding slack when comparing scrollHeight vs clientHeight. */ export declare const SCROLL_OVERFLOW_EPSILON_PX = 1; /** True when `el` can scroll vertically (content taller than the viewport). */ export declare function elementOverflowsY(el: Element): boolean; /** * Wheel / touch → content-scroll delta (same sign as `WheelEvent.deltaY`): * positive = scroll content up (reveal lower), negative = scroll content down. * Touch uses `previousTouchY - currentY` so a finger drag upward matches wheel-down. */ export declare function verticalScrollDelta(event: Event, previousTouchY?: number | null): number | null; /** * Whether `el` still has room to scroll in the direction of `deltaY` * (wheel / content-scroll sign). Blocks rubber-band at the edges — that * bounce is what lets iOS pan the visual viewport and drag the composer. */ export declare function canScrollVertically(el: HTMLElement, deltaY: number): boolean; /** * Allow scroll gestures that originate on the message / channel list (via * composedPath — works across shadow boundaries). The composer and other * controls are intentionally NOT blanket-allow-listed: permitting touchmove * there lets iOS scroll the overlay / visual viewport and drag the input off * the keyboard. * * List surfaces are allow-listed only when they actually overflow AND the * gesture still has room to scroll (not rubber-banding past top/bottom). * With few messages `scrollHeight ≈ clientHeight`, so touches are blocked — * that was the "empty list still scrolls and moves the input" bug. An * overflowing multi-line textarea is still allow-listed the same way. */ export declare function shouldAllowChatScrollGesture(event: Event, host: HTMLElement | null, previousTouchY?: number | null): boolean; /** Freeze ``/`` at the current scroll offset. Exported for tests. */ export declare function applyMobileBodyScrollLock(html: HTMLElement, body: HTMLElement, scrollY: number): void; /** Undo `applyMobileBodyScrollLock` and restore the scroll offset. For tests. */ export declare function releaseMobileBodyScrollLock(html: HTMLElement, body: HTMLElement, scrollTo: (x: number, y: number) => void): void; /** * Freeze the host page: fix ``/`` at the current offset and block * any residual scroll gestures outside the widget. In-widget scroll surfaces * (message list, composer) stay interactive. */ export declare function lockMobileBodyScroll(container: HTMLDivElement | null): void; /** Restore host-page scrolling after the panel closes. */ export declare function unlockMobileBodyScroll(_container: HTMLDivElement | null): void; /** Safety net for unmount / teardown. */ export declare function clearMobileBodyScrollLocks(): void;