|
import { useState, useEffect } from "react"; |
|
|
|
const makeObservable = (target) => { |
|
let listeners = []; |
|
let value = target; |
|
|
|
const getter = () => { |
|
return value; |
|
}; |
|
|
|
const count = () => { |
|
return listeners.length; |
|
}; |
|
|
|
const setter = (newValue) => { |
|
if (value === newValue) return; |
|
value = newValue; |
|
listeners.forEach((l) => l(value)); |
|
}; |
|
|
|
const subscribe = (listenerFunc) => { |
|
listeners.push(listenerFunc); |
|
return () => unsubscribe(listenerFunc); |
|
}; |
|
|
|
const unsubscribe = (listenerFunc) => { |
|
listeners = listeners.filter((l) => l !== listenerFunc); |
|
}; |
|
|
|
return { |
|
count, |
|
getter, |
|
setter, |
|
subscribe, |
|
}; |
|
}; |
|
|
|
const INVALID = -1; |
|
const ZERO = 0; |
|
const INF = 9007199254740991; |
|
|
|
const defaults = { |
|
"-1": { obs: { getter: () => false }, mq: null, el: null }, |
|
0: { obs: { getter: () => true }, mq: null, el: null }, |
|
9007199254740991: { obs: { getter: () => false }, mq: null, el: null }, |
|
}; |
|
const isDefault = (x) => { |
|
return !!defaults[x]; |
|
}; |
|
let subscriptions = {}; |
|
|
|
const useMinWidthBreakpoint = (breakpoint) => { |
|
const bp = Number(breakpoint) ?? INVALID; |
|
let tuple = defaults[bp] || subscriptions[bp]; |
|
if (!tuple) { |
|
const mq = window.matchMedia(`(min-width: ${bp}px)`); |
|
const obs = makeObservable(mq.matches); |
|
const el = mq.addEventListener("change", (e) => obs.setter(e.matches)); |
|
tuple = { obs, mq, el }; |
|
subscriptions[bp] = tuple; |
|
} |
|
|
|
const { obs, mq, el } = tuple; |
|
const [value, setValue] = useState(obs.getter()); |
|
useEffect(() => { |
|
if (isDefault(bp)) { |
|
return; |
|
} |
|
const unsub = obs.subscribe(setValue); |
|
return () => { |
|
unsub(); |
|
if (obs.count() < 1 && !isDefault(bp)) { |
|
mq.removeEventListener("change", el); |
|
subscriptions[bp] = null; |
|
} |
|
}; |
|
}, [bp, obs, mq, el]); |
|
return value; |
|
}; |
|
|
|
const Show = ({ children, from, upto }) => { |
|
const checkedFrom = Number.isInteger(from) ? from : ZERO; |
|
const checkedUpTo = Number.isInteger(upto) ? upto : INF; |
|
const fromBP = useMinWidthBreakpoint(checkedFrom); |
|
const uptoBP = useMinWidthBreakpoint(checkedUpTo); |
|
if (fromBP && !uptoBP) { |
|
return children; |
|
} else { |
|
return <></>; |
|
} |
|
}; |
|
|
|
export { useMinWidthBreakpoint, Show }; |