UNPKG

4.21 kBJavaScriptView Raw
1/** @flow */
2
3import type {MatchMediaType} from '../config';
4import type {PluginConfig, PluginResult} from './index';
5
6let _windowMatchMedia;
7function _getWindowMatchMedia(ExecutionEnvironment) {
8 if (_windowMatchMedia === undefined) {
9 _windowMatchMedia = (!!ExecutionEnvironment.canUseDOM &&
10 !!window &&
11 !!window.matchMedia &&
12 (mediaQueryString => window.matchMedia(mediaQueryString))) ||
13 null;
14 }
15 return _windowMatchMedia;
16}
17
18function _filterObject(
19 obj: Object,
20 predicate: (value: any, key: string) => boolean,
21): Object {
22 return Object.keys(obj).filter(key => predicate(obj[key], key)).reduce((
23 result,
24 key,
25 ) => {
26 result[key] = obj[key];
27 return result;
28 }, {});
29}
30
31function _removeMediaQueries(style) {
32 return Object.keys(style).reduce(
33 (styleWithoutMedia, key) => {
34 if (key.indexOf('@media') !== 0) {
35 styleWithoutMedia[key] = style[key];
36 }
37 return styleWithoutMedia;
38 },
39 {},
40 );
41}
42
43function _topLevelRulesToCSS(
44 {
45 addCSS,
46 appendImportantToEachValue,
47 cssRuleSetToString,
48 hash,
49 isNestedStyle,
50 style,
51 userAgent,
52 },
53) {
54 let className = '';
55 Object.keys(style).filter(name => name.indexOf('@media') === 0).map(query => {
56 const topLevelRules = appendImportantToEachValue(
57 _filterObject(style[query], value => !isNestedStyle(value)),
58 );
59
60 if (!Object.keys(topLevelRules).length) {
61 return;
62 }
63
64 const ruleCSS = cssRuleSetToString('', topLevelRules, userAgent);
65
66 // CSS classes cannot start with a number
67 const mediaQueryClassName = 'rmq-' + hash(query + ruleCSS);
68 const css = query + '{ .' + mediaQueryClassName + ruleCSS + '}';
69
70 addCSS(css);
71
72 className += (className ? ' ' : '') + mediaQueryClassName;
73 });
74 return className;
75}
76
77function _subscribeToMediaQuery(
78 {
79 listener,
80 listenersByQuery,
81 matchMedia,
82 mediaQueryListsByQuery,
83 query,
84 },
85) {
86 query = query.replace('@media ', '');
87
88 let mql = mediaQueryListsByQuery[query];
89 if (!mql && matchMedia) {
90 mediaQueryListsByQuery[query] = (mql = matchMedia(query));
91 }
92
93 if (!listenersByQuery || !listenersByQuery[query]) {
94 mql.addListener(listener);
95
96 listenersByQuery[query] = {
97 remove() {
98 mql.removeListener(listener);
99 },
100 };
101 }
102 return mql;
103}
104
105export default function resolveMediaQueries(
106 {
107 ExecutionEnvironment,
108 addCSS,
109 appendImportantToEachValue,
110 config,
111 cssRuleSetToString,
112 getComponentField,
113 getGlobalState,
114 hash,
115 isNestedStyle,
116 mergeStyles,
117 props,
118 setState,
119 style,
120 }: PluginConfig,
121): PluginResult {
122 // eslint-disable-line no-shadow
123 let newStyle = _removeMediaQueries(style);
124 const mediaQueryClassNames = _topLevelRulesToCSS({
125 addCSS,
126 appendImportantToEachValue,
127 cssRuleSetToString,
128 hash,
129 isNestedStyle,
130 style,
131 userAgent: config.userAgent,
132 });
133
134 const newProps = mediaQueryClassNames
135 ? {
136 className: mediaQueryClassNames +
137 (props.className ? ' ' + props.className : ''),
138 }
139 : null;
140
141 const matchMedia: ?MatchMediaType = config.matchMedia ||
142 _getWindowMatchMedia(ExecutionEnvironment);
143
144 if (!matchMedia) {
145 return {
146 props: newProps,
147 style: newStyle,
148 };
149 }
150
151 const listenersByQuery = {
152 ...getComponentField('_radiumMediaQueryListenersByQuery'),
153 };
154 const mediaQueryListsByQuery = getGlobalState('mediaQueryListsByQuery') || {};
155
156 Object.keys(style).filter(name => name.indexOf('@media') === 0).map(query => {
157 const nestedRules = _filterObject(style[query], isNestedStyle);
158
159 if (!Object.keys(nestedRules).length) {
160 return;
161 }
162
163 const mql = _subscribeToMediaQuery({
164 listener: () => setState(query, mql.matches, '_all'),
165 listenersByQuery,
166 matchMedia,
167 mediaQueryListsByQuery,
168 query,
169 });
170
171 // Apply media query states
172 if (mql.matches) {
173 newStyle = mergeStyles([newStyle, nestedRules]);
174 }
175 });
176
177 return {
178 componentFields: {
179 _radiumMediaQueryListenersByQuery: listenersByQuery,
180 },
181 globalState: {mediaQueryListsByQuery},
182 props: newProps,
183 style: newStyle,
184 };
185}