circuit-bricks
Version:
A modular, Lego-style SVG circuit component system for React (ALPHA - Not for production use)
254 lines (252 loc) • 6.17 kB
JavaScript
"use client";
import { useEffect, useState } from "react";
//#region src/utils/ssrUtils.ts
/**
* Check if code is running in a browser environment
*/
const isBrowser = () => {
return typeof window !== "undefined" && typeof document !== "undefined";
};
/**
* Check if code is running on the server
*/
const isServer = () => {
return !isBrowser();
};
/**
* Safe access to window object
*/
const safeWindow = () => {
return isBrowser() ? window : null;
};
/**
* Safe access to document object
*/
const safeDocument = () => {
return isBrowser() ? document : null;
};
/**
* Hook to detect when component has hydrated on the client
* This prevents hydration mismatches by ensuring client-only code
* only runs after hydration is complete.
*/
const useIsClient = () => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient;
};
/**
* Hook to safely access browser APIs after hydration
* Returns null during SSR and initial render, then the actual value after hydration
*/
const useSafeBrowserAPI = (getBrowserValue, fallbackValue = null) => {
const isClient = useIsClient();
const [value, setValue] = useState(fallbackValue);
useEffect(() => {
if (isClient) try {
setValue(getBrowserValue());
} catch (error) {
console.warn("Error accessing browser API:", error);
setValue(fallbackValue);
}
}, [
isClient,
getBrowserValue,
fallbackValue
]);
return isClient ? value : fallbackValue;
};
/**
* SSR-safe localStorage access
*/
const safeLocalStorage = {
getItem: (key) => {
if (!isBrowser()) return null;
try {
return localStorage.getItem(key);
} catch (error) {
console.warn("Error accessing localStorage:", error);
return null;
}
},
setItem: (key, value) => {
if (!isBrowser()) return false;
try {
localStorage.setItem(key, value);
return true;
} catch (error) {
console.warn("Error setting localStorage:", error);
return false;
}
},
removeItem: (key) => {
if (!isBrowser()) return false;
try {
localStorage.removeItem(key);
return true;
} catch (error) {
console.warn("Error removing from localStorage:", error);
return false;
}
}
};
/**
* SSR-safe sessionStorage access
*/
const safeSessionStorage = {
getItem: (key) => {
if (!isBrowser()) return null;
try {
return sessionStorage.getItem(key);
} catch (error) {
console.warn("Error accessing sessionStorage:", error);
return null;
}
},
setItem: (key, value) => {
if (!isBrowser()) return false;
try {
sessionStorage.setItem(key, value);
return true;
} catch (error) {
console.warn("Error setting sessionStorage:", error);
return false;
}
},
removeItem: (key) => {
if (!isBrowser()) return false;
try {
sessionStorage.removeItem(key);
return true;
} catch (error) {
console.warn("Error removing from sessionStorage:", error);
return false;
}
}
};
/**
* SSR-safe DOM query selector
*/
const safeQuerySelector = (selector) => {
if (!isBrowser()) return null;
try {
return document.querySelector(selector);
} catch (error) {
console.warn("Error querying DOM:", error);
return null;
}
};
/**
* SSR-safe DOM query selector all
*/
const safeQuerySelectorAll = (selector) => {
if (!isBrowser()) return null;
try {
return document.querySelectorAll(selector);
} catch (error) {
console.warn("Error querying DOM:", error);
return null;
}
};
/**
* SSR-safe event listener management
*/
const safeEventListener = {
add: (element, event, handler, options) => {
if (!isBrowser() || !element) return false;
try {
element.addEventListener(event, handler, options);
return true;
} catch (error) {
console.warn("Error adding event listener:", error);
return false;
}
},
remove: (element, event, handler, options) => {
if (!isBrowser() || !element) return false;
try {
element.removeEventListener(event, handler, options);
return true;
} catch (error) {
console.warn("Error removing event listener:", error);
return false;
}
}
};
/**
* SSR-safe viewport dimensions
*/
const useViewportDimensions = () => {
const [dimensions, setDimensions] = useState({
width: 1024,
height: 768
});
useEffect(() => {
if (!isBrowser()) return;
const updateDimensions = () => {
setDimensions({
width: window.innerWidth,
height: window.innerHeight
});
};
updateDimensions();
window.addEventListener("resize", updateDimensions);
return () => {
window.removeEventListener("resize", updateDimensions);
};
}, []);
return dimensions;
};
/**
* SSR-safe media query hook
*/
const useMediaQuery = (query) => {
const [matches, setMatches] = useState(false);
useEffect(() => {
if (!isBrowser()) return;
const mediaQuery = window.matchMedia(query);
setMatches(mediaQuery.matches);
const handler = (event) => {
setMatches(event.matches);
};
mediaQuery.addEventListener("change", handler);
return () => {
mediaQuery.removeEventListener("change", handler);
};
}, [query]);
return matches;
};
/**
* SSR-safe user agent detection
*/
const useUserAgent = () => {
const [userAgent, setUserAgent] = useState("");
useEffect(() => {
if (isBrowser()) setUserAgent(navigator.userAgent);
}, []);
return userAgent;
};
/**
* Detect mobile devices
*/
const useIsMobile = () => {
const userAgent = useUserAgent();
const isSmallScreen = useMediaQuery("(max-width: 768px)");
const isMobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
return isMobileUA || isSmallScreen;
};
/**
* Detect touch support
*/
const useHasTouchSupport = () => {
const [hasTouch, setHasTouch] = useState(false);
useEffect(() => {
if (isBrowser()) setHasTouch("ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0);
}, []);
return hasTouch;
};
//#endregion
export { isBrowser, isServer, safeDocument, safeEventListener, safeLocalStorage, safeQuerySelector, safeQuerySelectorAll, safeSessionStorage, safeWindow, useHasTouchSupport, useIsClient, useIsMobile, useMediaQuery, useSafeBrowserAPI, useUserAgent, useViewportDimensions };
//# sourceMappingURL=ssrUtils-Bmt4TsFY.mjs.map