|
| 1 | +import { useCallback, useMemo, useRef } from "react"; |
| 2 | +import { useSyncExternalStore } from "."; |
| 3 | +import { useResponsiveBreakpoints, useResponsiveKeys } from "../models/useResponsive.model"; |
| 4 | + |
| 5 | +const listeners = new Set<() => void>(); |
| 6 | + |
| 7 | +const handler = () => listeners.forEach(l => l()); |
| 8 | + |
| 9 | +const defaultConfig: useResponsiveBreakpoints<"xs" | "sm" | "md" | "lg" | "xl"> = { |
| 10 | + xs: { |
| 11 | + value: 576, |
| 12 | + condition: "<" |
| 13 | + }, |
| 14 | + sm: { |
| 15 | + value: 576, |
| 16 | + condition: ">=" |
| 17 | + }, |
| 18 | + md: { |
| 19 | + value: 768, |
| 20 | + condition: ">=" |
| 21 | + }, |
| 22 | + lg: { |
| 23 | + value: 992, |
| 24 | + condition: ">=" |
| 25 | + }, |
| 26 | + xl: { |
| 27 | + value: 1200, |
| 28 | + condition: ">=" |
| 29 | + } |
| 30 | +}; |
| 31 | + |
| 32 | +function calcResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in keyof typeof defaultConfig]: boolean } | { [s in useResponsiveKeys<T>]: boolean } { |
| 33 | + const width = window.innerWidth; |
| 34 | + const conf = {}; |
| 35 | + const target = config ?? defaultConfig; |
| 36 | + const keys = Object.keys(target) as ((keyof typeof conf)[]); |
| 37 | + for (const key of keys) { |
| 38 | + if (Reflect.get(target, key)) { |
| 39 | + const point = Reflect.get(target, key); |
| 40 | + const { value, condition } = typeof point === "number" ? { value: point, condition: ">" } : point; |
| 41 | + Reflect.set(conf, key, eval(`${width}${condition}${value}`) as boolean); |
| 42 | + } |
| 43 | + } |
| 44 | + return conf as typeof config extends undefined ? { [k in keyof typeof defaultConfig]: boolean } : { [k in useResponsiveKeys<T>]: boolean }; |
| 45 | +} |
| 46 | + |
| 47 | +/** |
| 48 | + * **`useResponsive`**: Hook for getting responsive window size. It receives an optional param __config__ to manually setting breakpoint keys. __config__ can have a keys subset and value can be a number or an object with _value_ and _condition_ properties. If _value_ is a number, the condition will be ">". By default Breakpoints are: |
| 49 | + * |
| 50 | + * - xs: { value: 576, condition: "<" } |
| 51 | + * - sm: { value: 576, condition: ">=" } |
| 52 | + * - md: { value: 768, condition: ">=" } |
| 53 | + * - lg: { value: 992, condition: ">=" } |
| 54 | + * - xl: { value: 1200, condition: ">=" } |
| 55 | + * @param {useResponsiveBreakpoints} [config] - custom breakpoint object. |
| 56 | + * @returns {keyof useResponsiveBreakpoints} breakpoint key - returns the __size key__ of the __config__, parameter if passed otherwise __default config__, corresponding to the size of the window. |
| 57 | + */ |
| 58 | +function useResponsive(config?: undefined): { [s in (keyof typeof defaultConfig)]: boolean }; |
| 59 | +function useResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in useResponsiveKeys<T>]: boolean }; |
| 60 | +function useResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in (keyof typeof defaultConfig)]: boolean } | { [s in useResponsiveKeys<T>]: boolean } { |
| 61 | + const configCache = useRef(() => { |
| 62 | + if (config === undefined) { |
| 63 | + return calcResponsive(defaultConfig); |
| 64 | + } else { |
| 65 | + return calcResponsive<T>(config); |
| 66 | + } |
| 67 | + }); |
| 68 | + return useSyncExternalStore( |
| 69 | + useCallback(notif => { |
| 70 | + listeners.size === 0 && window.addEventListener('resize', handler); |
| 71 | + listeners.add(notif); |
| 72 | + return () => { |
| 73 | + listeners.delete(notif); |
| 74 | + listeners.size === 0 && window.removeEventListener("resize", handler); |
| 75 | + } |
| 76 | + }, []), |
| 77 | + useMemo(() => { |
| 78 | + let cache = configCache.current(); |
| 79 | + return () => { |
| 80 | + const result = configCache.current(); |
| 81 | + const keys = Object.keys(result); |
| 82 | + for (const key of keys) { |
| 83 | + if (Reflect.get(cache, key) !== Reflect.get(result, key)) { |
| 84 | + cache = result; |
| 85 | + break; |
| 86 | + } |
| 87 | + } |
| 88 | + return cache; |
| 89 | + } |
| 90 | + }, []), |
| 91 | + ); |
| 92 | +} |
| 93 | + |
| 94 | +export { useResponsive }; |
0 commit comments