1 |
|
2 |
|
3 | import type {MatchMediaType} from '../config';
|
4 | import type {PluginConfig, PluginResult} from './index';
|
5 |
|
6 | let _windowMatchMedia;
|
7 | function _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 |
|
18 | function _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 |
|
31 | function _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 |
|
43 | function _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 |
|
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 |
|
77 | function _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 |
|
105 | export 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 |
|
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 |
|
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 | }
|