UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

314 lines (311 loc) 13.1 kB
"use client"; import { jsx } from 'react/jsx-runtime'; import { useRef, useEffect, useCallback, useState } from 'react'; // Hook for detecting keyboard visibility and viewport changes function useKeyboardDetection() { const [keyboardState, setKeyboardState] = useState({ isVisible: false, height: 0, duration: 300, easing: "ease-out", }); const [viewportInfo, setViewportInfo] = useState({ width: 0, height: 0, visualHeight: 0, keyboardHeight: 0, }); useEffect(() => { if (typeof window === "undefined") return; let initialHeight = window.innerHeight; const updateViewport = () => { const currentHeight = window.innerHeight; const currentWidth = window.innerWidth; // Use Visual Viewport API if available let visualViewportHeight = currentHeight; if ("visualViewport" in window && window.visualViewport) { visualViewportHeight = window.visualViewport.height; } const keyboardHeight = Math.max(0, initialHeight - visualViewportHeight); const keyboardVisible = keyboardHeight > 150; // Threshold for keyboard detection setKeyboardState((prev) => ({ ...prev, isVisible: keyboardVisible, height: keyboardHeight, })); setViewportInfo({ width: currentWidth, height: currentHeight, visualHeight: visualViewportHeight, keyboardHeight, }); }; // Handle orientation changes const handleOrientationChange = () => { // Wait for orientation change to complete setTimeout(() => { initialHeight = window.innerHeight; updateViewport(); }, 500); }; // Multiple event listeners for comprehensive coverage const events = ["resize", "orientationchange"]; events.forEach((event) => { window.addEventListener(event, updateViewport); }); // Special handling for orientation change window.addEventListener("orientationchange", handleOrientationChange); // Visual Viewport API listeners if ("visualViewport" in window && window.visualViewport) { window.visualViewport.addEventListener("resize", updateViewport); window.visualViewport.addEventListener("scroll", updateViewport); } // iOS specific handling if (/iPhone|iPad|iPod/.test(navigator.userAgent)) { // iOS specific events document.addEventListener("focusin", updateViewport); document.addEventListener("focusout", updateViewport); } // Initial update updateViewport(); return () => { events.forEach((event) => { window.removeEventListener(event, updateViewport); }); window.removeEventListener("orientationchange", handleOrientationChange); if ("visualViewport" in window && window.visualViewport) { window.visualViewport.removeEventListener("resize", updateViewport); window.visualViewport.removeEventListener("scroll", updateViewport); } if (/iPhone|iPad|iPod/.test(navigator.userAgent)) { document.removeEventListener("focusin", updateViewport); document.removeEventListener("focusout", updateViewport); } }; }, []); return { ...keyboardState, ...viewportInfo }; } // Hook for managing input focus and keyboard behavior function useMobileInputFocus() { const scrollToInput = useCallback((inputElement) => { if (!inputElement) return; // Prevent default zoom behavior on iOS const originalFontSize = inputElement.style.fontSize; document.querySelector('meta[name="viewport"]'); // Temporarily set font size to prevent zoom inputElement.style.fontSize = "16px"; // Scroll input into view setTimeout(() => { inputElement.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest", }); // Focus the input inputElement.focus(); // Restore original font size if (originalFontSize) { inputElement.style.fontSize = originalFontSize; } }, 100); }, []); const preventZoom = useCallback((inputElement) => { // Prevent zoom on input focus (iOS) const currentFontSize = window.getComputedStyle(inputElement).fontSize; const fontSize = parseFloat(currentFontSize); if (fontSize < 16) { inputElement.style.fontSize = "16px"; inputElement.style.transform = `scale(${fontSize / 16})`; inputElement.style.transformOrigin = "left center"; } }, []); const handleInputFocus = useCallback((event) => { const input = event.target; preventZoom(input); scrollToInput(input); }, [preventZoom, scrollToInput]); return { scrollToInput, preventZoom, handleInputFocus }; } // Component for handling keyboard adjustments function MobileKeyboardHandler({ children, onKeyboardToggle, adjustmentStrategy = "resize", className = "", enableSafeArea = true, minVisibleHeight = 200, }) { const keyboard = useKeyboardDetection(); const containerRef = useRef(null); // Notify parent of keyboard changes useEffect(() => { onKeyboardToggle === null || onKeyboardToggle === void 0 ? void 0 : onKeyboardToggle(keyboard.isVisible, keyboard.height); }, [keyboard.isVisible, keyboard.height, onKeyboardToggle]); // Apply adjustments based on strategy const getContainerStyle = useCallback(() => { const baseStyle = { position: "relative", transition: `all ${keyboard.duration}ms ${keyboard.easing}`, width: "100%", }; if (!keyboard.isVisible) { return { ...baseStyle, height: "100vh", maxHeight: "100vh", }; } switch (adjustmentStrategy) { case "resize": return { ...baseStyle, height: `${keyboard.visualHeight}px`, maxHeight: `${keyboard.visualHeight}px`, overflow: "hidden", }; case "scroll": return { ...baseStyle, height: "100vh", paddingBottom: `${keyboard.keyboardHeight}px`, overflowY: "auto", }; case "shrink": const availableHeight = Math.max(keyboard.visualHeight, minVisibleHeight); return { ...baseStyle, height: `${availableHeight}px`, maxHeight: `${availableHeight}px`, transform: keyboard.isVisible ? `translateY(-${Math.max(0, keyboard.keyboardHeight - (keyboard.height - availableHeight))}px)` : "translateY(0)", }; default: return baseStyle; } }, [ keyboard.isVisible, keyboard.visualHeight, keyboard.keyboardHeight, keyboard.height, keyboard.duration, keyboard.easing, adjustmentStrategy, minVisibleHeight, ]); // Apply safe area styles for devices with notches const getSafeAreaStyle = useCallback(() => { if (!enableSafeArea) return {}; return { paddingTop: "env(safe-area-inset-top)", paddingLeft: "env(safe-area-inset-left)", paddingRight: "env(safe-area-inset-right)", paddingBottom: keyboard.isVisible ? "0" : "env(safe-area-inset-bottom)", }; }, [enableSafeArea, keyboard.isVisible]); return (jsx("div", { ref: containerRef, className: `mobile-keyboard-handler ${className}`, style: { ...getContainerStyle(), ...getSafeAreaStyle(), }, "data-keyboard-visible": keyboard.isVisible, "data-keyboard-height": keyboard.height, children: children })); } function KeyboardAwareInput({ value, onChange, onSubmit, placeholder, disabled = false, className = "", autoFocus = false, keepVisible = true, }) { const inputRef = useRef(null); const { handleInputFocus } = useMobileInputFocus(); const keyboard = useKeyboardDetection(); // Keep input visible when keyboard appears useEffect(() => { if (keepVisible && keyboard.isVisible && inputRef.current) { const input = inputRef.current; const inputRect = input.getBoundingClientRect(); const viewportHeight = keyboard.visualHeight; // Check if input is hidden by keyboard if (inputRect.bottom > viewportHeight) { input.scrollIntoView({ behavior: "smooth", block: "end", }); } } }, [keyboard.isVisible, keyboard.visualHeight, keepVisible]); const handleKeyPress = useCallback((e) => { if (e.key === "Enter" && onSubmit) { e.preventDefault(); onSubmit(); } }, [onSubmit]); return (jsx("input", { ref: inputRef, type: "text", value: value, onChange: (e) => onChange(e.target.value), onKeyPress: handleKeyPress, onFocus: handleInputFocus, placeholder: placeholder, disabled: disabled, autoFocus: autoFocus, className: className, style: { fontSize: "16px", // Prevent zoom on iOS minHeight: "48px", padding: "12px 16px", borderRadius: "24px", border: "2px solid #e1e5e9", outline: "none", width: "100%", backgroundColor: "#f8f9fa", transition: "border-color 0.2s ease", touchAction: "manipulation", WebkitAppearance: "none", // Remove iOS styling } })); } // Hook for back button handling (Android/Browser) function useBackButtonHandler(onBackPress) { useEffect(() => { if (typeof window === "undefined" || !onBackPress) return; // Handle browser back button const handlePopState = (event) => { event.preventDefault(); onBackPress(); }; // Push a state to handle back button window.history.pushState({ modal: true }, "", ""); window.addEventListener("popstate", handlePopState); return () => { var _a; window.removeEventListener("popstate", handlePopState); // Clean up history state if still on the same state if ((_a = window.history.state) === null || _a === void 0 ? void 0 : _a.modal) { window.history.back(); } }; }, [onBackPress]); } // Hook for preventing body scroll when modal is open function useScrollLock(isLocked) { useEffect(() => { if (typeof document === "undefined") return; const body = document.body; const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth; if (isLocked) { window.getComputedStyle(body); const originalTop = body.style.top; const originalPosition = body.style.position; const originalOverflow = body.style.overflow; const originalPaddingRight = body.style.paddingRight; // Lock scroll body.style.overflow = "hidden"; body.style.paddingRight = `${scrollBarWidth}px`; // iOS specific fixes if (/iPhone|iPad|iPod/.test(navigator.userAgent)) { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; body.style.position = "fixed"; body.style.top = `-${scrollTop}px`; body.style.left = "0"; body.style.right = "0"; } return () => { // Restore scroll body.style.overflow = originalOverflow; body.style.paddingRight = originalPaddingRight; if (/iPhone|iPad|iPod/.test(navigator.userAgent)) { body.style.position = originalPosition; body.style.top = originalTop; body.style.left = ""; body.style.right = ""; // Restore scroll position const top = parseInt(originalTop || "0", 10) * -1; window.scrollTo(0, top); } }; } }, [isLocked]); } export { KeyboardAwareInput, MobileKeyboardHandler, useBackButtonHandler, useKeyboardDetection, useMobileInputFocus, useScrollLock }; //# sourceMappingURL=mobile-keyboard-handler.js.map