@@ -84,7 +84,7 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
8484 getNamespacedTheme ( props ) {
8585 const { themeNamespace, theme } = props
8686 if ( ! themeNamespace ) return theme
87- if ( themeNamespace && ! theme ) throw new Error ( 'Invalid themeNamespace use in react-css-themr. ' +
87+ if ( themeNamespace && ! theme ) throw new Error ( 'Invalid themeNamespace use in react-css-themr. ' +
8888 'themeNamespace prop should be used only with theme prop.' )
8989
9090 return Object . keys ( theme )
@@ -153,57 +153,99 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
153153}
154154
155155/**
156- * Merges two themes by concatenating values with the same keys
157- * @param {TReactCSSThemrTheme } [original] - Original theme object
158- * @param {TReactCSSThemrTheme } [mixin] - Mixing theme object
159- * @returns {TReactCSSThemrTheme } - Merged resulting theme
156+ * Merges passed themes by concatenating string keys and processing nested themes
157+ * @param {...TReactCSSThemrTheme } themes - Themes
158+ * @returns {TReactCSSThemrTheme } - Resulting theme
160159 */
161- export function themeable ( original = { } , mixin ) {
162- //don't merge if no mixin is passed
163- if ( ! mixin ) return original
164-
165- //merge themes by concatenating values with the same keys
166- return Object . keys ( mixin ) . reduce (
167-
168- //merging reducer
169- ( result , key ) => {
170-
171- const originalValue = typeof original [ key ] !== 'function'
172- ? ( original [ key ] || '' )
173- : ''
174- const mixinValue = typeof mixin [ key ] !== 'function'
175- ? ( mixin [ key ] || '' )
176- : ''
177-
178- let newValue
160+ export function themeable ( ...themes ) {
161+ return themes . reduce ( ( acc , theme ) => merge ( acc , theme ) , { } )
162+ }
179163
180- //when you are mixing an string with a object it should fail
181- invariant ( ! ( typeof originalValue === 'string' && typeof mixinValue === 'object' ) ,
182- `You are merging a string "${ originalValue } " with an Object,` +
183- 'Make sure you are passing the proper theme descriptors.'
184- )
164+ /**
165+ * @param {TReactCSSThemrTheme } [original] - Original theme
166+ * @param {TReactCSSThemrTheme } [mixin] - Mixin theme
167+ * @returns {TReactCSSThemrTheme } - resulting theme
168+ */
169+ function merge ( original = { } , mixin = { } ) {
170+ //make a copy to avoid mutations of nested objects
171+ //also strip all functions injected by isomorphic-style-loader
172+ const result = Object . keys ( original ) . reduce ( ( acc , key ) => {
173+ const value = original [ key ]
174+ if ( typeof value !== 'function' ) {
175+ acc [ key ] = value
176+ }
177+ return acc
178+ } , { } )
179+
180+ //traverse mixin keys and merge them to resulting theme
181+ Object . keys ( mixin ) . forEach ( key => {
182+ //there's no need to set any defaults here
183+ const originalValue = result [ key ]
184+ const mixinValue = mixin [ key ]
185+
186+ switch ( typeof mixinValue ) {
187+ case 'object' : {
188+ //possibly nested theme object
189+ switch ( typeof originalValue ) {
190+ case 'object' : {
191+ //exactly nested theme object - go recursive
192+ result [ key ] = merge ( originalValue , mixinValue )
193+ break
194+ }
195+
196+ case 'undefined' : {
197+ //original does not contain this nested key - just take it as is
198+ result [ key ] = mixinValue
199+ break
200+ }
201+
202+ default : {
203+ //can't merge an object with a non-object
204+ throw new Error ( `You are merging object ${ key } with a non-object ${ originalValue } ` )
205+ }
206+ }
207+ break
208+ }
185209
186- //check if values are nested objects
187- if ( typeof originalValue === 'object' && typeof mixinValue === 'object' ) {
188- //go recursive
189- newValue = themeable ( originalValue , mixinValue )
190- } else {
191- //either concat or take mixin value
192- newValue = originalValue . split ( ' ' )
193- . concat ( mixinValue . split ( ' ' ) )
194- . filter ( ( item , pos , self ) => self . indexOf ( item ) === pos && item !== '' )
195- . join ( ' ' )
210+ case 'undefined' : //fallthrough - handles accidentally unset values which may come from props
211+ case 'function' : {
212+ //this handles issue when isomorphic-style-loader addes helper functions to css-module
213+ break //just skip
196214 }
197215
198- return {
199- ...result ,
200- [ key ] : newValue
216+ default : {
217+ //plain values
218+ switch ( typeof originalValue ) {
219+ case 'object' : {
220+ //can't merge a non-object with an object
221+ throw new Error ( `You are merging non-object ${ mixinValue } with an object ${ key } ` )
222+ }
223+
224+ case 'undefined' : {
225+ //mixin key is new to original theme - take it as is
226+ result [ key ] = mixinValue
227+ break
228+ }
229+ case 'function' : {
230+ //this handles issue when isomorphic-style-loader addes helper functions to css-module
231+ break //just skip
232+ }
233+
234+ default : {
235+ //finally we can merge
236+ result [ key ] = originalValue . split ( ' ' )
237+ . concat ( mixinValue . split ( ' ' ) )
238+ . filter ( ( item , pos , self ) => self . indexOf ( item ) === pos && item !== '' )
239+ . join ( ' ' )
240+ break
241+ }
242+ }
243+ break
201244 }
202- } ,
245+ }
246+ } )
203247
204- //use original theme as an acc
205- original
206- )
248+ return result
207249}
208250
209251/**
0 commit comments