UNPKG

@brutalcomponent/react

Version:
300 lines (296 loc) 8.36 kB
'use strict'; var react = require('react'); /** * @brutalcomponent/react * (c) David Heffler (https://dvh.sh) * Licensed under MIT */ function useClickOutside(handler, enabled = true) { const ref = react.useRef(null); react.useEffect(() => { if (!enabled) return; const handleClick = (event) => { const el = ref.current; if (!el || el.contains(event.target)) { return; } handler(); }; document.addEventListener("mousedown", handleClick); document.addEventListener("touchstart", handleClick); return () => { document.removeEventListener("mousedown", handleClick); document.removeEventListener("touchstart", handleClick); }; }, [handler, enabled]); return ref; } function useDebounce(value, delay = 300) { const [debouncedValue, setDebouncedValue] = react.useState(value); react.useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } function useLocalStorage(key, initialValue) { const readValue = react.useCallback(() => { if (typeof window === "undefined") { return initialValue; } try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(`Error reading localStorage key "${key}":`, error); return initialValue; } }, [key, initialValue]); const [storedValue, setStoredValue] = react.useState(readValue); const setValue = react.useCallback( (value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); if (typeof window !== "undefined") { window.localStorage.setItem(key, JSON.stringify(valueToStore)); } } catch (error) { console.error(`Error setting localStorage key "${key}":`, error); } }, [key, storedValue] ); const remove = react.useCallback(() => { try { if (typeof window !== "undefined") { window.localStorage.removeItem(key); } setStoredValue(initialValue); } catch (error) { console.error(`Error removing localStorage key "${key}":`, error); } }, [key, initialValue]); react.useEffect(() => { const handleStorageChange = (e) => { if (e.key === key && e.newValue !== null) { try { setStoredValue(JSON.parse(e.newValue)); } catch { } } }; window.addEventListener("storage", handleStorageChange); return () => window.removeEventListener("storage", handleStorageChange); }, [key]); return [storedValue, setValue, remove]; } function useMediaQuery(query) { const [matches, setMatches] = react.useState(false); react.useEffect(() => { const media = window.matchMedia(query); setMatches(media.matches); const listener = (event) => { setMatches(event.matches); }; if (media.addEventListener) { media.addEventListener("change", listener); } else { media.addListener(listener); } return () => { if (media.removeEventListener) { media.removeEventListener("change", listener); } else { media.removeListener(listener); } }; }, [query]); return matches; } function useBreakpoint() { const isMobile = useMediaQuery("(max-width: 640px)"); const isTablet = useMediaQuery("(min-width: 641px) and (max-width: 1024px)"); const isDesktop = useMediaQuery("(min-width: 1025px)"); const isMd = useMediaQuery("(min-width: 768px)"); const isLg = useMediaQuery("(min-width: 1024px)"); const isXl = useMediaQuery("(min-width: 1280px)"); return { isMobile, isTablet, isDesktop, isMd, isLg, isXl }; } function useKeyPress(keys, handler, options = {}) { const { element = null, preventDefault = true, stopPropagation = false, enabled = true } = options; const savedHandler = react.useRef(handler); react.useEffect(() => { savedHandler.current = handler; }, [handler]); const eventHandler = react.useCallback( (event) => { const keysArray = Array.isArray(keys) ? keys : [keys]; if (keysArray.some((key) => { if (key.includes("+")) { const parts = key.split("+").map((p) => p.trim().toLowerCase()); const modifiers = parts.slice(0, -1); const mainKey = parts[parts.length - 1]; const modifierChecks = { ctrl: event.ctrlKey, cmd: event.metaKey, alt: event.altKey, shift: event.shiftKey }; const modifiersMatch = modifiers.every( (mod) => modifierChecks[mod] ); return modifiersMatch && event.key.toLowerCase() === mainKey; } return event.key === key; })) { if (preventDefault) event.preventDefault(); if (stopPropagation) event.stopPropagation(); savedHandler.current(event); } }, [keys, preventDefault, stopPropagation] ); react.useEffect(() => { if (!enabled) return; const targetElement = element || document; targetElement.addEventListener("keydown", eventHandler); return () => { targetElement.removeEventListener("keydown", eventHandler); }; }, [element, enabled, eventHandler]); } function useFocusTrap(enabled = true) { const ref = react.useRef(null); react.useEffect(() => { if (!enabled || !ref.current) return; const element = ref.current; const focusableElements = element.querySelectorAll( 'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select, [tabindex]:not([tabindex="-1"])' ); const firstFocusable = focusableElements[0]; const lastFocusable = focusableElements[focusableElements.length - 1]; const handleKeyDown = (e) => { if (e.key !== "Tab") return; if (e.shiftKey) { if (document.activeElement === firstFocusable) { e.preventDefault(); lastFocusable?.focus(); } } else { if (document.activeElement === lastFocusable) { e.preventDefault(); firstFocusable?.focus(); } } }; element.addEventListener("keydown", handleKeyDown); firstFocusable?.focus(); return () => { element.removeEventListener("keydown", handleKeyDown); }; }, [enabled]); return ref; } /** * @file src/hooks/useClickOutside.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hook for detecting clicks outside an element */ /** * @file src/hooks/useDebounce.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hook for debouncing values */ /** * @file src/hooks/useLocalStorage.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hook for persisting state in localStorage */ /** * @file src/hooks/useMediaQuery.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hook for responsive media queries */ /** * @file src/hooks/useKeyPress.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hook for handling keyboard shortcuts */ /** * @file src/hooks/useFocusTrap.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hook for trapping focus within an element (useful for modals) */ /** * @file src/hooks/index.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Hooks barrel export */ exports.useBreakpoint = useBreakpoint; exports.useClickOutside = useClickOutside; exports.useDebounce = useDebounce; exports.useFocusTrap = useFocusTrap; exports.useKeyPress = useKeyPress; exports.useLocalStorage = useLocalStorage; exports.useMediaQuery = useMediaQuery; //# sourceMappingURL=chunk-Q6XW3RCO.js.map //# sourceMappingURL=chunk-Q6XW3RCO.js.map