responsiveObserver.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import React from 'react';
  2. import { useToken } from '../theme/internal';
  3. import { addMediaQueryListener, removeMediaQueryListener } from './mediaQueryUtil';
  4. export const responsiveArray = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'];
  5. const getResponsiveMap = token => ({
  6. xs: `(max-width: ${token.screenXSMax}px)`,
  7. sm: `(min-width: ${token.screenSM}px)`,
  8. md: `(min-width: ${token.screenMD}px)`,
  9. lg: `(min-width: ${token.screenLG}px)`,
  10. xl: `(min-width: ${token.screenXL}px)`,
  11. xxl: `(min-width: ${token.screenXXL}px)`
  12. });
  13. /**
  14. * Ensures that the breakpoints token are valid, in good order
  15. * For each breakpoint : screenMin <= screen <= screenMax and screenMax <= nextScreenMin
  16. */
  17. const validateBreakpoints = token => {
  18. const indexableToken = token;
  19. const revBreakpoints = [].concat(responsiveArray).reverse();
  20. revBreakpoints.forEach((breakpoint, i) => {
  21. const breakpointUpper = breakpoint.toUpperCase();
  22. const screenMin = `screen${breakpointUpper}Min`;
  23. const screen = `screen${breakpointUpper}`;
  24. if (!(indexableToken[screenMin] <= indexableToken[screen])) {
  25. throw new Error(`${screenMin}<=${screen} fails : !(${indexableToken[screenMin]}<=${indexableToken[screen]})`);
  26. }
  27. if (i < revBreakpoints.length - 1) {
  28. const screenMax = `screen${breakpointUpper}Max`;
  29. if (!(indexableToken[screen] <= indexableToken[screenMax])) {
  30. throw new Error(`${screen}<=${screenMax} fails : !(${indexableToken[screen]}<=${indexableToken[screenMax]})`);
  31. }
  32. const nextBreakpointUpperMin = revBreakpoints[i + 1].toUpperCase();
  33. const nextScreenMin = `screen${nextBreakpointUpperMin}Min`;
  34. if (!(indexableToken[screenMax] <= indexableToken[nextScreenMin])) {
  35. throw new Error(`${screenMax}<=${nextScreenMin} fails : !(${indexableToken[screenMax]}<=${indexableToken[nextScreenMin]})`);
  36. }
  37. }
  38. });
  39. return token;
  40. };
  41. export const matchScreen = (screens, screenSizes) => {
  42. if (!screenSizes) {
  43. return;
  44. }
  45. for (const breakpoint of responsiveArray) {
  46. if (screens[breakpoint] && (screenSizes === null || screenSizes === void 0 ? void 0 : screenSizes[breakpoint]) !== undefined) {
  47. return screenSizes[breakpoint];
  48. }
  49. }
  50. };
  51. const useResponsiveObserver = () => {
  52. const [, token] = useToken();
  53. const responsiveMap = getResponsiveMap(validateBreakpoints(token));
  54. // To avoid repeat create instance, we add `useMemo` here.
  55. return React.useMemo(() => {
  56. const subscribers = new Map();
  57. let subUid = -1;
  58. let screens = {};
  59. return {
  60. responsiveMap,
  61. matchHandlers: {},
  62. dispatch(pointMap) {
  63. screens = pointMap;
  64. subscribers.forEach(func => func(screens));
  65. return subscribers.size >= 1;
  66. },
  67. subscribe(func) {
  68. if (!subscribers.size) {
  69. this.register();
  70. }
  71. subUid += 1;
  72. subscribers.set(subUid, func);
  73. func(screens);
  74. return subUid;
  75. },
  76. unsubscribe(paramToken) {
  77. subscribers.delete(paramToken);
  78. if (!subscribers.size) {
  79. this.unregister();
  80. }
  81. },
  82. register() {
  83. Object.entries(responsiveMap).forEach(([screen, mediaQuery]) => {
  84. const listener = ({
  85. matches
  86. }) => {
  87. this.dispatch(Object.assign(Object.assign({}, screens), {
  88. [screen]: matches
  89. }));
  90. };
  91. const mql = window.matchMedia(mediaQuery);
  92. addMediaQueryListener(mql, listener);
  93. this.matchHandlers[mediaQuery] = {
  94. mql,
  95. listener
  96. };
  97. listener(mql);
  98. });
  99. },
  100. unregister() {
  101. Object.values(responsiveMap).forEach(mediaQuery => {
  102. const handler = this.matchHandlers[mediaQuery];
  103. removeMediaQueryListener(handler === null || handler === void 0 ? void 0 : handler.mql, handler === null || handler === void 0 ? void 0 : handler.listener);
  104. });
  105. subscribers.clear();
  106. }
  107. };
  108. }, [token]);
  109. };
  110. export default useResponsiveObserver;