UNPKG

react-keyboard-avoiding-container

Version:

A React component that provides smooth keyboard interactions for mobile web apps, handling viewport adjustments, scroll behavior, and input field focus with a sticky footer layout

196 lines (189 loc) 8.63 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var KeyboardAvoidingContainer = function (_a) { var body = _a.body, footer = _a.footer, _b = _a.transitionDuration, transitionDuration = _b === void 0 ? 280 : _b; var _c = React.useState({ visualHeight: window.innerHeight, keyboardHeight: 0, }), viewport = _c[0], setViewport = _c[1]; var _d = React.useState(false), isTransitioning = _d[0], setIsTransitioning = _d[1]; var containerRef = React.useRef(null); var bodyRef = React.useRef(null); var lastFocusedElementRef = React.useRef(null); var resizeTimeoutRef = React.useRef(); var prevHeightRef = React.useRef(window.innerHeight); var updateViewport = function (immediate) { if (immediate === void 0) { immediate = false; } var update = function () { var _a; var newVisualHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || window.innerHeight; var newKeyboardHeight = window.innerHeight - newVisualHeight; var isKeyboardOpen = newKeyboardHeight > 50; // threshold to detect keyboard if (newVisualHeight !== prevHeightRef.current) { setIsTransitioning(true); if (resizeTimeoutRef.current) { clearTimeout(resizeTimeoutRef.current); } resizeTimeoutRef.current = setTimeout(function () { setIsTransitioning(false); }, transitionDuration); prevHeightRef.current = newVisualHeight; } requestAnimationFrame(function () { window.scrollTo(0, 0); document.documentElement.scrollTop = 0; document.body.scrollTop = 0; }); setViewport({ visualHeight: newVisualHeight, keyboardHeight: newKeyboardHeight, }); if (lastFocusedElementRef.current && isKeyboardOpen) { handleFocusScroll(lastFocusedElementRef.current); } }; if (immediate) { update(); } else { requestAnimationFrame(update); } }; React.useEffect(function () { var _a, _b; var preventDefault = function (e) { e.preventDefault(); e.stopPropagation(); }; var events = [ "scroll", "touchmove", "mousewheel", "DOMMouseScroll", "wheel", ]; events.forEach(function (event) { document.addEventListener(event, preventDefault, { passive: false }); document.body.addEventListener(event, preventDefault, { passive: false }); document.documentElement.addEventListener(event, preventDefault, { passive: false, }); }); var styles = { position: "fixed", width: "100%", height: "100%", overflow: "hidden", maxHeight: "100%", margin: "0", padding: "0", top: "0", left: "0", right: "0", bottom: "0", }; Object.assign(document.body.style, styles); Object.assign(document.documentElement.style, styles); if (containerRef.current) { containerRef.current.addEventListener("scroll", function (e) { return e.stopPropagation(); }); containerRef.current.addEventListener("touchmove", function (e) { return e.stopPropagation(); }); } var handleResize = function () { return updateViewport(); }; var handleOrientationChange = function () { setIsTransitioning(true); setTimeout(function () { updateViewport(true); setTimeout(function () { return setIsTransitioning(false); }, transitionDuration); }, 100); }; (_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.addEventListener("resize", handleResize); (_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.addEventListener("scroll", handleResize); window.addEventListener("orientationchange", handleOrientationChange); updateViewport(true); return function () { var _a, _b; if (resizeTimeoutRef.current) { clearTimeout(resizeTimeoutRef.current); } events.forEach(function (event) { document.removeEventListener(event, preventDefault); document.body.removeEventListener(event, preventDefault); document.documentElement.removeEventListener(event, preventDefault); }); (_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.removeEventListener("resize", handleResize); (_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.removeEventListener("scroll", handleResize); window.removeEventListener("orientationchange", handleOrientationChange); document.body.style.cssText = ""; document.documentElement.style.cssText = ""; }; }, []); var handleFocusScroll = function (element) { if (!containerRef.current || !bodyRef.current || !element) return; var elementRect = element.getBoundingClientRect(); var containerRect = containerRef.current.getBoundingClientRect(); var scrollTop = elementRect.top - containerRect.top + containerRef.current.scrollTop - 60; requestAnimationFrame(function () { if (containerRef.current) { containerRef.current.scrollTo({ top: Math.max(0, scrollTop), behavior: "smooth", }); } window.scrollTo(0, 0); document.documentElement.scrollTop = 0; document.body.scrollTop = 0; }); }; var handleFocus = function (e) { if (e.target instanceof HTMLElement) { lastFocusedElementRef.current = e.target; handleFocusScroll(e.target); updateViewport(true); } }; var handleBlur = function () { setTimeout(function () { if (!document.activeElement || document.activeElement === document.body) { lastFocusedElementRef.current = null; updateViewport(true); } }, 100); }; var containerStyle = { height: "".concat(viewport.visualHeight, "px"), WebkitOverflowScrolling: "touch", transition: isTransitioning ? "height ".concat(transitionDuration, "ms cubic-bezier(0.4, 0, 0.2, 1)") : "none", willChange: isTransitioning ? "height" : "auto", }; var bodyStyle = { transition: isTransitioning ? "all ".concat(transitionDuration, "ms cubic-bezier(0.4, 0, 0.2, 1)") : "none", willChange: isTransitioning ? "min-height, padding" : "auto", }; return (React__default["default"].createElement("div", { className: "fixed inset-0 w-full overflow-hidden", style: { height: "100vh", WebkitOverflowScrolling: "touch", } }, React__default["default"].createElement("div", { ref: containerRef, className: "absolute inset-0 w-full overflow-auto overscroll-none", style: containerStyle }, React__default["default"].createElement("div", { ref: bodyRef, className: "flex flex-col min-h-full px-6 pt-6", style: bodyStyle }, React__default["default"].createElement("div", { className: "flex-1 pb-10", onFocus: handleFocus, onBlur: handleBlur }, body), React__default["default"].createElement("div", { className: "sticky bottom-0 left-0 right-0 w-full bg-white mt-auto pb-2", style: { transition: isTransitioning ? "transform ".concat(transitionDuration, "ms cubic-bezier(0.4, 0, 0.2, 1)") : "none", willChange: isTransitioning ? "transform" : "auto", } }, footer))))); }; exports.KeyboardAvoidingContainer = KeyboardAvoidingContainer; //# sourceMappingURL=index.js.map