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
JavaScript
;
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