import { Orientation } from '@scandit/web-datacapture-core'; /** * Interface for global dependencies that can be injected for testing */ interface GlobalDependencies { readonly document: Document; readonly window: Window & typeof globalThis; readonly visualViewport: VisualViewport | null; } declare class KeyboardVisibilityChangedEvent extends CustomEvent<{ isKeyboardVisible: boolean; keyboardHeight: number; keyboardTop: number; orientation: Orientation; }> { constructor(isKeyboardVisible: boolean, keyboardHeight: number | undefined, keyboardTop: number | undefined, orientation: Orientation); } interface KeyboardVisibilityControllerEventMap { "keyboard-visibility-changed": KeyboardVisibilityChangedEvent; } /** * Global controller for keyboard visibility detection. * Uses focusin/focusout events to detect keyboard on both iOS and Android. * Waits for keyboard animation to complete before dispatching event. * Ensures listeners are added only once globally. * Extends EventTarget to dispatch keyboard visibility events. */ declare class KeyboardVisibilityController extends EventTarget { private static _instance; private _isKeyboardVisible; private _keyboardHeight; private _keyboardTop; private _isConnected; private _refCount; /** * Initial viewport height captured when first connecting. * Used as reference to detect keyboard in portrait mode. * * Critical for iOS Safari bug: when keyboard appears, BOTH window.innerHeight * and visualViewport.height shrink. We can't reliably use window.innerHeight * after keyboard appears, so we store the initial value before any keyboard. */ private _initialViewportHeight; /** * Initial viewport width captured when first connecting. * Used as reference to detect keyboard in landscape mode. * * Why we need this: In landscape, the portrait viewport width becomes the * landscape height reference. Since keyboard doesn't affect width in portrait, * we can safely use _initialViewportWidth as the landscape height reference. * * This solves the landscape orientation change problem: * - User starts in portrait: we capture height + width * - User rotates to landscape with keyboard visible * - Can't recalculate height (keyboard is visible, both dimensions shrunk) * - Solution: use stored portrait width as landscape height reference * * Special case - initial keyboard detection: * If an input is already focused on connect (auto-focus), we detect the keyboard * and estimate the true pre-keyboard width by comparing visualViewport.height to * window.innerHeight. This ensures the stored width is the full device width, * not a shrunk value. This is critical for correct rotation behavior. */ private _initialViewportWidth; private _handleFocusIn; private _handleFocusOut; private _handleVisualViewportResize; private _handleOrientationChange; private _orientationObserver; private readonly _globals; private static readonly KEYBOARD_HEIGHT_THRESHOLD; private static readonly VIEWPORT_RESIZE_DEBOUNCE; private constructor(); static getInstance(): KeyboardVisibilityController; /** * Create a new instance with custom global dependencies (mainly for testing). * This does NOT affect the singleton instance returned by getInstance(). */ static create(globals: GlobalDependencies): KeyboardVisibilityController; /** * Connect to keyboard visibility monitoring. * Listeners are added on first connect, removed on last disconnect. */ connect(): void; /** * Disconnect from keyboard visibility monitoring. * Listeners are removed when all consumers disconnect. */ disconnect(): void; private _setupListeners; private _removeListeners; /** * Calculate keyboard height using Visual Viewport API with orientation awareness. * * iOS Safari bug workaround (https://bugs.webkit.org/show_bug.cgi?id=195428): * When keyboard appears, both window.innerHeight and visualViewport.height shrink. * We can't use current window.innerHeight as reference. Instead, we use the * initial viewport dimensions captured before keyboard appeared. * * Orientation-aware logic: * - PORTRAIT: keyboard_height = _initialViewportHeight - visualViewport.height * (height is the dimension affected by keyboard) * * - LANDSCAPE: keyboard_height = _initialViewportWidth - visualViewport.height * (portrait width becomes landscape height; width unaffected by keyboard) * * This solves the landscape orientation change problem: * If user rotates to landscape while keyboard is visible, we can't recalculate * the initial height (both dimensions shrunk). But we have the stored portrait * width, which becomes the landscape height reference. */ private _calculateKeyboardHeight; /** * Calculate the Y position where keyboard starts (top edge of keyboard) * Accounts for both viewport shrinkage and scroll offset */ private _calculateKeyboardTop; /** * Handle visual viewport resize events * Called by debounced handler after viewport has stopped resizing (250ms debounce) */ private _onVisualViewportResize; private _onOrientationChange; private _onFocusIn; private _onFocusOut; isKeyboardVisible(): boolean; getKeyboardHeight(): number; getKeyboardTop(): number; /** * Add a listener for keyboard visibility changes. * @param type - Must be "keyboard-visibility-changed" * @param listener - Event listener callback */ addEventListener(type: K, listener: (event: KeyboardVisibilityControllerEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void; /** * Remove a listener for keyboard visibility changes. * @param type - Must be "keyboard-visibility-changed" * @param listener - Event listener callback */ removeEventListener(type: K, listener: (event: KeyboardVisibilityControllerEventMap[K]) => void, options?: boolean | EventListenerOptions): void; /** * Reset the controller (rarely needed, mainly for testing). * Disconnects all references and cleans up. * If resetInstance is true, also resets the singleton instance. */ reset(resetInstance?: boolean): void; } export { type GlobalDependencies, KeyboardVisibilityChangedEvent, KeyboardVisibilityController, type KeyboardVisibilityControllerEventMap };