UNPKG

@react-hook/media-query

Version:

React hooks that update when media queries change between matched and unmatched states.

1 lines 7.26 kB
{"version":3,"file":"index.mjs","sources":["../../src/index.tsx"],"sourcesContent":["import * as React from 'react'\n\nfunction queriesDidChange(\n prevQueries: MediaQueries<unknown>,\n nextQueries: MediaQueries<unknown>\n): boolean {\n if (nextQueries === prevQueries) return false\n const nextQueriesArr = Object.values(nextQueries)\n const prevQueriesArr = Object.values(prevQueries)\n if (nextQueriesArr.length !== prevQueriesArr.length) return true\n if (nextQueriesArr.some((q, i) => q !== prevQueriesArr[i])) return true\n const prevKeys = Object.keys(prevQueries)\n return Object.keys(nextQueries).some((n, i) => n !== prevKeys[i])\n}\n\nfunction init<T>(queries: MediaQueries<T>): State<T> {\n const queryKeys = Object.keys(queries) as (keyof MediaQueries<T>)[]\n /* istanbul ignore next */\n if (typeof window === 'undefined')\n return queryKeys.reduce(\n (curr: State<any>, key) => {\n curr.matches[key] = false\n curr.mediaQueries[key] = {} as MediaQueryList\n return curr\n },\n {mediaQueries: {}, matches: {}}\n )\n\n return queryKeys.reduce(\n (state: State<any>, name): State<T> => {\n const mql = window.matchMedia(queries[name])\n state.mediaQueries[name] = mql\n state.matches[name] = mql.matches\n return state\n },\n {mediaQueries: {}, matches: {}}\n )\n}\n\nfunction reducer<T>(state: State<T>, action: Action<T>): State<T> {\n switch (action.type) {\n case 'updateMatches':\n return {\n matches: Object.keys(state.mediaQueries).reduce(\n (prev: Matches<any>, key: string) => {\n prev[key] = state.mediaQueries[key as keyof T].matches\n return prev\n },\n {}\n ),\n mediaQueries: state.mediaQueries,\n }\n\n case 'setQueries':\n return init(action.queries)\n }\n}\n\n/**\n * A hook that returns a [`MediaQueryMatches`](#mediaquerymatches) object which will\n * tell you if specific media queries matched, all media queries matched, or\n * any media queries matched. Matches in this hook will always return `false` when\n * rendering on the server.\n *\n * @param queryMap The media queries you want to match against e.g. `{screen: \"screen\", width: \"(min-width: 12em)\"}`\n */\nexport function useMediaQueries<T>(\n queryMap: MediaQueries<T>\n): MediaQueryMatches<T> {\n const prevQueries = React.useRef<MediaQueries<T>>(queryMap)\n const [state, dispatch] = React.useReducer<\n React.Reducer<State<T>, Action<T>>,\n MediaQueries<T>\n >(reducer, queryMap, init)\n\n React.useEffect(() => {\n if (queriesDidChange(queryMap, prevQueries.current)) {\n dispatch({type: 'setQueries', queries: queryMap})\n prevQueries.current = queryMap\n }\n }, [queryMap])\n\n React.useEffect(() => {\n const queries: MediaQueryList[] = Object.values(state.mediaQueries)\n const callbacks: (() => void)[] = queries.map((mq) => {\n const callback = () => dispatch({type: 'updateMatches'})\n if (typeof mq.addListener !== 'undefined') mq.addListener(callback)\n else mq.addEventListener('change', callback)\n\n return callback\n })\n\n return () => {\n queries.forEach((mq: MediaQueryList, i: number) => {\n if (typeof mq.addListener !== 'undefined')\n mq.removeListener(callbacks[i])\n else mq.removeEventListener('change', callbacks[i])\n })\n }\n }, [state.mediaQueries])\n\n const {matches} = state\n const matchValues = React.useMemo<boolean[]>(() => Object.values(matches), [\n matches,\n ])\n\n return {\n matches,\n matchesAny: matchValues.some(Boolean),\n matchesAll: matchValues.length > 0 && matchValues.every(Boolean),\n }\n}\n\n/**\n * A hook that returns `true` if the media query matched and `false` if not. This\n * hook will always return `false` when rendering on the server.\n *\n * @param query The media query you want to match against e.g. `\"only screen and (min-width: 12em)\"`\n */\nexport function useMediaQuery(query: string) {\n return useMediaQueries(getObj(query)).matchesAll\n}\n\nconst cache: Record<string, MediaQueries<{default: string}>> = {}\n\nfunction getObj(query: string): MediaQueries<{default: string}> {\n if (cache[query] === void 0) cache[query] = {default: query}\n return cache[query]\n}\n\nexport type MediaQueries<T> = {\n [Name in keyof T]: string\n}\n\nexport type Matches<T> = {\n [Name in keyof T]: boolean\n}\n\ninterface State<T> {\n mediaQueries: {[Name in keyof T]: MediaQueryList}\n matches: Matches<T>\n}\n\ntype UpdateMatchesAction = {\n type: 'updateMatches'\n}\ntype SetQueriesAction<T> = {\n type: 'setQueries'\n queries: MediaQueries<T>\n}\ntype Action<T> = UpdateMatchesAction | SetQueriesAction<T>\n\nexport interface MediaQueryMatches<T> {\n /**\n * Returns a map of query key/didMatch pairs\n */\n matches: Matches<T>\n /**\n * `true` if any of the media queries matched\n */\n matchesAny: boolean\n /**\n * `true` if all of the media queries matched\n */\n matchesAll: boolean\n}\n"],"names":["curr","key","matches","mediaQueries","init","queries","queryKeys","Object","keys","window","reduce","state","name","mql","matchMedia","reducer","action","type","prev","useMediaQueries","queryMap","dispatch","mq","callback","addListener","addEventListener","prevQueries","React","nextQueries","nextQueriesArr","values","prevQueriesArr","length","some","q","i","prevKeys","n","queriesDidChange","current","removeListener","callbacks","removeEventListener","map","forEach","matchValues","matchesAny","Boolean","matchesAll","every","useMediaQuery","query","cache","default","getObj"],"mappings":"AAoBM,WAACA,EAAkBC,UACjBD,EAAKE,QAAQD,GAAO,EACpBD,EAAKG,aAAaF,GAAO,GAClBD,EARf,SAASI,EAAQC,OACTC,EAAYC,OAAOC,KAAKH,SAER,oBAAXI,OACFH,EAAUI,SAMf,CAACP,aAAc,GAAID,QAAS,KAGzBI,EAAUI,OACf,CAACC,EAAmBC,SACZC,EAAMJ,OAAOK,WAAWT,EAAQO,WACtCD,EAAMR,aAAaS,GAAQC,EAC3BF,EAAMT,QAAQU,GAAQC,EAAIX,QACnBS,GAET,CAACR,aAAc,GAAID,QAAS,KAIhC,SAASa,EAAWJ,EAAiBK,UAC3BA,EAAOC,UACR,sBACI,CACLf,QAASK,OAAOC,KAAKG,EAAMR,cAAcO,iBACtCQ,EAAoBjB,UACnBiB,EAAKjB,GAAOU,EAAMR,aAAaF,GAAgBC,QACxCgB,IAET,IAEFf,aAAcQ,EAAMR,kBAGnB,oBACIC,EAAKY,EAAOX,UAYlB,SAASc,EACdC,uBAkB2BC,EAAS,CAACJ,KAAM,6BADMK,OACvCC,gBACwB,IAAnBD,EAAGE,YAA6BF,EAAGE,YAAYD,GACrDD,EAAGG,iBAAiB,SAAUF,GAE5BA,MApBLG,EAAcC,EAA8BP,IAC3CT,EAAOU,GAAYM,EAGxBZ,EAASK,EAAUhB,GAErBuB,EAAgB,MAzElB,SACED,EACAE,MAEIA,IAAgBF,EAAa,OAAO,MAClCG,EAAiBtB,OAAOuB,OAAOF,GAC/BG,EAAiBxB,OAAOuB,OAAOJ,MACjCG,EAAeG,SAAWD,EAAeC,OAAQ,OAAO,KACxDH,EAAeI,KAAK,CAACC,EAAGC,IAAMD,IAAMH,EAAeI,IAAK,OAAO,MAC7DC,EAAW7B,OAAOC,KAAKkB,UACtBnB,OAAOC,KAAKoB,GAAaK,KAAK,CAACI,EAAGF,IAAME,IAAMD,EAASD,KAgExDG,CAAiBlB,EAAUM,EAAYa,WACzClB,EAAS,CAACJ,KAAM,aAAcZ,QAASe,IACvCM,EAAYa,QAAUnB,IAEvB,CAACA,IAEJO,EAAgB,gBAWKL,EAAoBa,QACL,IAAnBb,EAAGE,YACZF,EAAGkB,eAAeC,EAAUN,IACzBb,EAAGoB,oBAAoB,SAAUD,EAAUN,QAb9C9B,EAA4BE,OAAOuB,OAAOnB,EAAMR,cAChDsC,EAA4BpC,EAAQsC,aAQnC,KACLtC,EAAQuC,aAMT,CAACjC,EAAMR,mBAEJD,QAACA,GAAWS,EACZkC,EAAclB,EAAyB,IAAMpB,OAAOuB,OAAO5B,GAAU,CACzEA,UAGK,CACLA,QAAAA,EACA4C,WAAYD,EAAYZ,KAAKc,SAC7BC,WAAYH,EAAYb,OAAS,GAAKa,EAAYI,MAAMF,UAUrD,SAASG,EAAcC,UACrBhC,EAKT,SAAgBgC,eACO,IAAjBC,EAAMD,KAAmBC,EAAMD,GAAS,CAACE,QAASF,IAC/CC,EAAMD,GAPUG,CAAOH,IAAQH,sFAGxC,IAAMI,EAAyD"}