Comparing version 1.7.1 to 2.0.0-0
@@ -0,1 +1,53 @@ | ||
# 2.0.0 | ||
Aesthetic has been rewritten to properly support specificity, new at-rules, and global styles. | ||
Styles are no longer transformed on mount and will now be transformed on render using a new | ||
stylesheet layer. Furthermore, unified syntax now supports most common at-rules, and a new | ||
`@font-face` structure. | ||
[View the migration guide!](../../MIGRATE_2.0.md) | ||
#### 💥 Breaking | ||
* Requires IE 11+. | ||
* Requires `WeakMap` support. | ||
* Removed React Native support (it was finicky and only supported by 1 adapter). | ||
* Removed `aesthetic-utils` package (any remaining helpers were moved to core). | ||
* Removed the `classes` function. | ||
* Use the `transform` function provided by `createStyler` instead. | ||
* Removed `ClassNamesPropType` and `ClassOrStylesPropType` prop types. | ||
* Use the `StylesPropType` instead. | ||
* Refactored `Aesthetic#transformStyles` and `Adapter#transform` to now require an array of style | ||
declarations. | ||
* Will now return a single combined class name for increased specificity. | ||
* Refactored `createStyler` to return 2 functions, `style` and `transform`. | ||
* The `style` function is an HOC factory and works like the original 1.0 return value. | ||
* The `transform` function is now required to generate class names from style declarations. | ||
* Renamed the HOC `wrappedComponent` static property to `WrappedComponent`. | ||
* Renamed the HOC `theme` prop (to toggle themes) to `themeName`. | ||
* Inherited and parent styles are no longer passed as the HOC styler callback 2nd argument. | ||
* Unified Syntax | ||
* The `@font-face` unified syntax rule has been rewritten to support multiple variations of the | ||
same font family. | ||
* The object key is now the font family name, instead of a random name. | ||
* The object value can now be an array of font face style declarations. | ||
* The `srcPaths` property, an array of paths, is now required (instead of `src`). | ||
#### 🚀 New | ||
* Added a new adapter, `TypeStyle`. | ||
* Added `Aesthetic#createStyleSheet` for converting a component's styles into an adapter | ||
specific stylesheet. | ||
* The component's current props are passed as the 2nd argument to the HOC styler callback. | ||
* Inherited and parent styles are now automatically deep merged when extending. | ||
* Added `Adapter#create` for creating and adapting stylesheets. | ||
* Updated `Aesthetic#registerTheme` to use the new global styles system. | ||
* The current theme declarations will be passed to styled components under the `theme` prop. | ||
* The previous `theme` prop was renamed to `themeName`. | ||
* The `Aesthetic` `themePropName` option now controls this new prop. | ||
* Unified Syntax | ||
* Added new `@charset`, `@global`, `@import`, `@namespace`, `@page`, `@supports`, and `@viewport` | ||
at-rules (varies between adapters). | ||
* Added a new property `local` for use within `@font-face` (the source `local()` value). | ||
#### 🛠 Internal | ||
* Rewritten Flowtype definitions. | ||
# 1.7.1 - 11/10/17 | ||
@@ -2,0 +54,0 @@ #### 🛠 Internal |
@@ -11,4 +11,2 @@ import _extends from 'babel-runtime/helpers/extends'; | ||
this.bypassNativeStyleSheet = false; | ||
this.native = false; | ||
this.options = {}; | ||
@@ -20,4 +18,9 @@ | ||
_createClass(Adapter, [{ | ||
key: 'create', | ||
value: function create(styleSheet) { | ||
return styleSheet; | ||
} | ||
}, { | ||
key: 'transform', | ||
value: function transform(styleName, declarations) { | ||
value: function transform() { | ||
throw new Error(this.constructor.name + ' must define the `transform` method.'); | ||
@@ -24,0 +27,0 @@ } |
@@ -0,1 +1,2 @@ | ||
import _toConsumableArray from 'babel-runtime/helpers/toConsumableArray'; | ||
import _extends from 'babel-runtime/helpers/extends'; | ||
@@ -10,5 +11,7 @@ import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; | ||
import { isObject } from 'aesthetic-utils'; | ||
import deepMerge from 'lodash.merge'; | ||
import isObject from './helpers/isObject'; | ||
import stripClassPrefix from './helpers/stripClassPrefix'; | ||
import Adapter from './Adapter'; | ||
import _withStyles from './style'; | ||
@@ -21,4 +24,3 @@ var Aesthetic = function () { | ||
this.cache = {}; | ||
this.native = false; | ||
this.cache = new WeakMap(); | ||
this.options = { | ||
@@ -28,3 +30,3 @@ defaultTheme: '', | ||
pure: false, | ||
stylesPropName: 'classNames', | ||
stylesPropName: 'styles', | ||
themePropName: 'theme' | ||
@@ -42,19 +44,12 @@ }; | ||
_createClass(Aesthetic, [{ | ||
key: 'extendTheme', | ||
value: function extendTheme(parentThemeName, themeName) { | ||
var theme = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var globals = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
return this.registerTheme(themeName, deepMerge({}, this.getTheme(parentThemeName), theme), globals); | ||
} | ||
}, { | ||
key: 'getStyles', | ||
value: function getStyles(styleName) { | ||
key: 'createStyleSheet', | ||
value: function createStyleSheet(styleName) { | ||
var themeName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; | ||
var props = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var parentStyleName = this.parents[styleName]; | ||
var declarations = this.styles[styleName]; | ||
var styleSheet = this.styles[styleName]; | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (!declarations) { | ||
if (!styleSheet) { | ||
throw new Error('Styles do not exist for "' + styleName + '".'); | ||
@@ -64,9 +59,21 @@ } | ||
if (typeof declarations !== 'function') { | ||
return declarations; | ||
if (typeof styleSheet === 'function') { | ||
styleSheet = styleSheet(themeName ? this.getTheme(themeName) : {}, props); | ||
} | ||
return declarations(themeName ? this.getTheme(themeName) : {}, parentStyleName ? this.getStyles(parentStyleName, themeName) : {}); | ||
if (parentStyleName) { | ||
styleSheet = deepMerge({}, this.createStyleSheet(parentStyleName, themeName, props), styleSheet); | ||
} | ||
return this.adapter.create(styleSheet); | ||
} | ||
}, { | ||
key: 'extendTheme', | ||
value: function extendTheme(parentThemeName, themeName) { | ||
var theme = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var globals = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
return this.registerTheme(themeName, deepMerge({}, this.getTheme(parentThemeName), theme), globals); | ||
} | ||
}, { | ||
key: 'getTheme', | ||
@@ -110,4 +117,6 @@ value: function getTheme() { | ||
this.adapter.transform(':root', globals); | ||
var globalStyleSheet = this.adapter.create(globals); | ||
this.transformStyles(Object.values(globalStyleSheet)); | ||
return this; | ||
@@ -119,3 +128,2 @@ } | ||
if (adapter instanceof Adapter || adapter && typeof adapter.transform === 'function') { | ||
adapter.native = this.native; | ||
this.adapter = adapter; | ||
@@ -130,3 +138,3 @@ } else if ("production" !== process.env.NODE_ENV) { | ||
key: 'setStyles', | ||
value: function setStyles(styleName, declarations) { | ||
value: function setStyles(styleName, styleSheet) { | ||
var extendFrom = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; | ||
@@ -137,3 +145,3 @@ | ||
throw new Error('Styles have already been set for "' + styleName + '".'); | ||
} else if (!isObject(declarations) && typeof declarations !== 'function') { | ||
} else if (!isObject(styleSheet) && typeof styleSheet !== 'function') { | ||
throw new TypeError('Styles defined for "' + styleName + '" must be an object or function.'); | ||
@@ -143,3 +151,3 @@ } | ||
this.styles[styleName] = declarations; | ||
this.styles[styleName] = styleSheet; | ||
@@ -162,48 +170,42 @@ if (extendFrom) { | ||
key: 'transformStyles', | ||
value: function transformStyles(styleName, themeName) { | ||
var _this = this; | ||
var fallbackThemeName = themeName || this.options.defaultTheme || ''; | ||
var cacheKey = styleName + ':' + fallbackThemeName; | ||
if (this.cache[cacheKey]) { | ||
return this.cache[cacheKey]; | ||
value: function transformStyles(styles) { | ||
if (this.cache.has(styles)) { | ||
return this.cache.get(styles); | ||
} | ||
var declarations = this.getStyles(styleName, fallbackThemeName); | ||
var toTransform = {}; | ||
var output = {}; | ||
var setCount = 0; | ||
var classNames = []; | ||
var toTransform = []; | ||
Object.keys(declarations).forEach(function (setName) { | ||
if (typeof declarations[setName] === 'string') { | ||
output[setName] = _this.native ? {} : declarations[setName]; | ||
} else { | ||
toTransform[setName] = declarations[setName]; | ||
setCount += 1; | ||
styles.forEach(function (style) { | ||
if (!style) { | ||
return; | ||
} else if (typeof style === 'string' || typeof style === 'number') { | ||
classNames.push.apply(classNames, _toConsumableArray(String(style).split(' ').map(function (s) { | ||
return stripClassPrefix(s).trim(); | ||
}))); | ||
} else if (isObject(style)) { | ||
toTransform.push(style); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error('Unsupported style type to transform.'); | ||
} | ||
}); | ||
if (setCount > 0) { | ||
var transformedOutput = this.adapter.transform(styleName, toTransform); | ||
if (toTransform.length > 0) { | ||
var _adapter; | ||
Object.keys(transformedOutput).forEach(function (setName) { | ||
output[setName] = _this.validateTransform(styleName, setName, transformedOutput[setName]); | ||
}); | ||
classNames.push((_adapter = this.adapter).transform.apply(_adapter, toTransform)); | ||
} | ||
this.cache[cacheKey] = output; | ||
var className = classNames.join(' ').trim(); | ||
return output; | ||
this.cache.set(styles, className); | ||
return className; | ||
} | ||
}, { | ||
key: 'validateTransform', | ||
value: function validateTransform(styleName, setName, value) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (typeof value !== 'string') { | ||
throw new TypeError('`' + this.adapter.constructor.name + '` must return a mapping of CSS class names. ' + ('"' + styleName + '@' + setName + '" is not a valid string.')); | ||
} | ||
} | ||
key: 'withStyles', | ||
value: function withStyles(styleSheet) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return value; | ||
return _withStyles(this, styleSheet, options); | ||
} | ||
@@ -210,0 +212,0 @@ }]); |
@@ -32,14 +32,20 @@ import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; | ||
key: 'transform', | ||
value: function transform(styleName, declarations) { | ||
var classNames = {}; | ||
value: function transform() { | ||
var _this2 = this; | ||
Object.keys(declarations).forEach(function (setName) { | ||
if (typeof declarations[setName] === 'string') { | ||
classNames[setName] = declarations[setName]; | ||
var classNames = []; | ||
for (var _len2 = arguments.length, styles = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
styles[_key2] = arguments[_key2]; | ||
} | ||
styles.forEach(function (style) { | ||
if (style && typeof style === 'string') { | ||
classNames.push(style); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new TypeError('`ClassNameAdapter` expects valid CSS class names; ' + ('non-string provided for "' + setName + '".')); | ||
throw new TypeError(_this2.constructor.name + ' expects valid CSS class names.'); | ||
} | ||
}); | ||
return classNames; | ||
return classNames.join(' '); | ||
} | ||
@@ -46,0 +52,0 @@ }]); |
@@ -8,3 +8,2 @@ /** | ||
import Aesthetic from './Aesthetic'; | ||
import style from './style'; | ||
@@ -18,8 +17,16 @@ export default function createStyler(aesthetic) { | ||
return function styler() { | ||
var defaultStyles = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return { | ||
style: function style(styleSheet) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return style(aesthetic, defaultStyles, options); | ||
return aesthetic.withStyles(styleSheet, options); | ||
}, | ||
transform: function transform() { | ||
for (var _len = arguments.length, styles = Array(_len), _key = 0; _key < _len; _key++) { | ||
styles[_key] = arguments[_key]; | ||
} | ||
return aesthetic.transformStyles(styles); | ||
} | ||
}; | ||
} |
/** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* | ||
*/ | ||
@@ -12,9 +13,6 @@ | ||
import createStyler from './createStyler'; | ||
import classes from './classes'; | ||
export var ClassNamesPropType = PropTypes.objectOf(PropTypes.string); | ||
export var StylesPropType = PropTypes.objectOf(PropTypes.object); | ||
export var ClassOrStylesPropType = PropTypes.oneOfType([PropTypes.objectOf(PropTypes.string), PropTypes.objectOf(PropTypes.object)]); | ||
export { Adapter, ClassNameAdapter, ThemeProvider, createStyler, classes }; | ||
export { Adapter, ClassNameAdapter, ThemeProvider, createStyler }; | ||
export default Aesthetic; |
@@ -21,3 +21,3 @@ import _extends from 'babel-runtime/helpers/extends'; | ||
export default function style(aesthetic) { | ||
var styles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var styleSheet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
@@ -54,3 +54,3 @@ | ||
aesthetic.setStyles(styleName, styles, extendFrom); | ||
aesthetic.setStyles(styleName, styleSheet, extendFrom); | ||
@@ -61,5 +61,15 @@ var StyledComponent = function (_ParentComponent) { | ||
function StyledComponent() { | ||
var _ref, _this$state; | ||
var _temp, _this, _ret; | ||
_classCallCheck(this, StyledComponent); | ||
return _possibleConstructorReturn(this, (StyledComponent.__proto__ || Object.getPrototypeOf(StyledComponent)).apply(this, arguments)); | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = StyledComponent.__proto__ || Object.getPrototypeOf(StyledComponent)).call.apply(_ref, [this].concat(args))), _this), _this.state = (_this$state = { | ||
firstMount: true | ||
}, _defineProperty(_this$state, stylesPropName, {}), _defineProperty(_this$state, 'themeName', ''), _defineProperty(_this$state, themePropName, {}), _this$state), _temp), _possibleConstructorReturn(_this, _ret); | ||
} | ||
@@ -70,3 +80,3 @@ | ||
value: function componentWillMount() { | ||
this.transformStyles(this.getTheme(this.props)); | ||
this.transformStyles(this.props); | ||
} | ||
@@ -76,19 +86,21 @@ }, { | ||
value: function componentWillReceiveProps(nextProps) { | ||
var theme = this.getTheme(nextProps); | ||
if (theme !== this.state[themePropName]) { | ||
this.transformStyles(theme); | ||
} | ||
this.transformStyles(nextProps); | ||
} | ||
}, { | ||
key: 'getTheme', | ||
value: function getTheme(props) { | ||
return props[themePropName] || this.context.themeName || aesthetic.options.defaultTheme || ''; | ||
key: 'getThemeName', | ||
value: function getThemeName(props) { | ||
return props.themeName || this.context.themeName || aesthetic.options.defaultTheme || ''; | ||
} | ||
}, { | ||
key: 'transformStyles', | ||
value: function transformStyles(theme) { | ||
var _setState; | ||
value: function transformStyles(props) { | ||
var themeName = this.getThemeName(props); | ||
this.setState((_setState = {}, _defineProperty(_setState, stylesPropName, aesthetic.transformStyles(styleName, theme)), _defineProperty(_setState, themePropName, theme), _setState)); | ||
if (this.state.firstMount || themeName !== this.state.themeName) { | ||
var _setState; | ||
this.setState((_setState = { | ||
firstMount: false | ||
}, _defineProperty(_setState, stylesPropName, aesthetic.createStyleSheet(styleName, themeName, props)), _defineProperty(_setState, 'themeName', themeName), _defineProperty(_setState, themePropName, themeName ? aesthetic.getTheme(themeName) : {}), _setState)); | ||
} | ||
} | ||
@@ -102,3 +114,4 @@ }, { | ||
key: 'extendStyles', | ||
value: function extendStyles(customStyles) { | ||
value: function extendStyles() { | ||
var customStyleSheet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var extendOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -112,3 +125,3 @@ | ||
return style(aesthetic, customStyles, _extends({}, options, extendOptions, { | ||
return style(aesthetic, customStyleSheet, _extends({}, options, extendOptions, { | ||
extendFrom: styleName | ||
@@ -124,7 +137,12 @@ }))(Component); | ||
StyledComponent.styleName = styleName; | ||
StyledComponent.wrappedComponent = Component; | ||
StyledComponent.propTypes = _defineProperty({}, themePropName, PropTypes.string); | ||
StyledComponent.WrappedComponent = Component; | ||
StyledComponent.contextTypes = { | ||
themeName: PropTypes.string | ||
}; | ||
StyledComponent.propTypes = { | ||
themeName: PropTypes.string | ||
}; | ||
StyledComponent.defaultProps = { | ||
themeName: '' | ||
}; | ||
@@ -131,0 +149,0 @@ |
@@ -11,8 +11,10 @@ import _toConsumableArray from 'babel-runtime/helpers/toConsumableArray'; | ||
import { isObject } from 'aesthetic-utils'; | ||
import formatFontFace from './helpers/formatFontFace'; | ||
import isObject from './helpers/isObject'; | ||
import toArray from './helpers/toArray'; | ||
export var LOCAL = 'local'; | ||
export var GLOBAL = 'global'; | ||
export var AT_RULES = ['@fallbacks', '@font-face', '@keyframes', '@media']; | ||
export var GLOBAL_RULES = ['@charset', '@font-face', '@global', '@import', '@keyframes', '@namespace', '@page', '@viewport']; | ||
export var LOCAL_RULES = ['@fallbacks', '@media', '@supports']; | ||
var UnifiedSyntax = function () { | ||
@@ -23,64 +25,214 @@ function UnifiedSyntax() { | ||
this.events = {}; | ||
this.fallbacks = {}; | ||
this.fontFaces = {}; | ||
this.fontFaceNames = {}; | ||
this.fontFacesCache = {}; | ||
this.keyframes = {}; | ||
this.keyframeNames = {}; | ||
this.mediaQueries = {}; | ||
this.keyframesCache = {}; | ||
this.handleProperty = function (declaration, style, property) { | ||
declaration[property] = style; | ||
}; | ||
this.on('property', this.handleProperty).on('@charset', this.handleCharset).on('@fallbacks', this.handleFallbacks).on('@font-face', this.handleFontFace).on('@global', this.handleGlobal).on('@import', this.handleImport).on('@keyframes', this.handleKeyframes).on('@media', this.handleMedia).on('@namespace', this.handleNamespace).on('@page', this.handlePage).on('@supports', this.handleSupports).on('@viewport', this.handleViewport); | ||
} | ||
_createClass(UnifiedSyntax, [{ | ||
key: 'checkBlock', | ||
value: function checkBlock(value) { | ||
if (isObject(value)) { | ||
return value; | ||
} | ||
throw new Error('Must be a style declaration.'); | ||
} | ||
}, { | ||
key: 'convert', | ||
value: function convert(declarations) { | ||
value: function convert(styleSheet) { | ||
var _this = this; | ||
this.resetLocalCache(); | ||
this.emit('converting'); | ||
var prevStyleSheet = _extends({}, styleSheet); | ||
var nextStyleSheet = {}; | ||
var adaptedDeclarations = _extends({}, declarations); | ||
GLOBAL_RULES.forEach(function (rule) { | ||
if (!prevStyleSheet[rule]) { | ||
delete prevStyleSheet[rule]; | ||
AT_RULES.forEach(function (atRule) { | ||
if (atRule in adaptedDeclarations) { | ||
_this.extract(':root', atRule, adaptedDeclarations[atRule], GLOBAL); | ||
return; | ||
} | ||
delete adaptedDeclarations[atRule]; | ||
switch (rule) { | ||
case '@charset': | ||
case '@import': | ||
case '@namespace': | ||
{ | ||
var path = prevStyleSheet[rule]; | ||
if (typeof path === 'string') { | ||
_this.emit(rule, [nextStyleSheet, path]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error(rule + ' value must be a string.'); | ||
} | ||
break; | ||
} | ||
case '@font-face': | ||
{ | ||
var faces = prevStyleSheet['@font-face']; | ||
Object.keys(_this.checkBlock(faces)).forEach(function (fontFamily) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (_this.fontFaces[fontFamily]) { | ||
throw new Error('@font-face "' + fontFamily + '" already exists.'); | ||
} | ||
} | ||
_this.fontFaces[fontFamily] = toArray(faces[fontFamily]).map(function (font) { | ||
return _extends({}, font, { | ||
fontFamily: fontFamily | ||
}); | ||
}); | ||
_this.emit(rule, [nextStyleSheet, _this.fontFaces[fontFamily], fontFamily]); | ||
}); | ||
break; | ||
} | ||
case '@global': | ||
{ | ||
var globals = prevStyleSheet['@global']; | ||
Object.keys(_this.checkBlock(globals)).forEach(function (selector) { | ||
if (isObject(globals[selector])) { | ||
_this.emit(rule, [nextStyleSheet, _this.convertDeclaration(selector, globals[selector]), selector]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error('Invalid @global selector style declaration.'); | ||
} | ||
}); | ||
break; | ||
} | ||
case '@keyframes': | ||
{ | ||
var frames = prevStyleSheet['@keyframes']; | ||
Object.keys(_this.checkBlock(frames)).forEach(function (animationName) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (_this.keyframes[animationName]) { | ||
throw new Error('@keyframes "' + animationName + '" already exists.'); | ||
} | ||
} | ||
_this.keyframes[animationName] = _this.checkBlock(frames[animationName]); | ||
_this.emit(rule, [nextStyleSheet, _this.keyframes[animationName], animationName]); | ||
}); | ||
break; | ||
} | ||
case '@page': | ||
case '@viewport': | ||
{ | ||
var style = prevStyleSheet[rule]; | ||
if (isObject(style)) { | ||
_this.emit(rule, [nextStyleSheet, style]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error(rule + ' must be a style object.'); | ||
} | ||
break; | ||
} | ||
default: | ||
break; | ||
} | ||
delete prevStyleSheet[rule]; | ||
}); | ||
Object.keys(adaptedDeclarations).forEach(function (setName) { | ||
var declaration = declarations[setName]; | ||
Object.keys(prevStyleSheet).forEach(function (selector) { | ||
var declaration = prevStyleSheet[selector]; | ||
if (typeof declaration !== 'string') { | ||
adaptedDeclarations[setName] = _this.convertDeclaration(setName, declaration); | ||
delete prevStyleSheet[selector]; | ||
if (!declaration) { | ||
return; | ||
} | ||
if (selector.charAt(0) === '@') { | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new SyntaxError('Unsupported global at-rule "' + selector + '".'); | ||
} | ||
} else if (typeof declaration === 'string') { | ||
nextStyleSheet[selector] = declaration; | ||
} else if (isObject(declaration)) { | ||
nextStyleSheet[selector] = _this.convertDeclaration(selector, declaration); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error('Invalid style declaration for "' + selector + '".'); | ||
} | ||
}); | ||
this.emit('converted'); | ||
return adaptedDeclarations; | ||
return nextStyleSheet; | ||
} | ||
}, { | ||
key: 'convertDeclaration', | ||
value: function convertDeclaration(setName, properties) { | ||
value: function convertDeclaration(selector, declaration) { | ||
var _this2 = this; | ||
var nextProperties = _extends({}, properties); | ||
var prevDeclaration = _extends({}, declaration); | ||
var nextDeclaration = {}; | ||
AT_RULES.forEach(function (atRule) { | ||
if (atRule in nextProperties) { | ||
_this2.extract(setName, atRule, nextProperties[atRule], LOCAL); | ||
Object.keys(prevDeclaration).forEach(function (key) { | ||
if (key.charAt(0) !== '@') { | ||
_this2.emit('property', [nextDeclaration, prevDeclaration[key], key]); | ||
delete nextProperties[atRule]; | ||
delete prevDeclaration[key]; | ||
} | ||
}); | ||
this.emit('declaration', [setName, nextProperties]); | ||
LOCAL_RULES.forEach(function (rule) { | ||
var style = prevDeclaration[rule]; | ||
return nextProperties; | ||
delete prevDeclaration[rule]; | ||
if (!style || !isObject(style)) { | ||
return; | ||
} | ||
if (rule === '@fallbacks') { | ||
Object.keys(style).forEach(function (property) { | ||
_this2.emit(rule, [nextDeclaration, toArray(style[property]), property]); | ||
}); | ||
} else if (rule === '@media' || rule === '@supports') { | ||
Object.keys(style).forEach(function (condition) { | ||
if (isObject(style[condition])) { | ||
_this2.emit(rule, [nextDeclaration, style[condition], condition]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error(rule + ' ' + condition + ' must be a mapping of conditions to style objects.'); | ||
} | ||
}); | ||
} | ||
}); | ||
if ("production" !== process.env.NODE_ENV) { | ||
Object.keys(prevDeclaration).forEach(function (key) { | ||
throw new SyntaxError('Unsupported local at-rule "' + key + '".'); | ||
}); | ||
} | ||
return nextDeclaration; | ||
} | ||
}, { | ||
key: 'createUnsupportedHandler', | ||
value: function createUnsupportedHandler(rule) { | ||
return function () { | ||
throw new Error('Adapter does not support "' + rule + '".'); | ||
}; | ||
} | ||
}, { | ||
key: 'emit', | ||
value: function emit(eventName) { | ||
var args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
value: function emit(eventName, args) { | ||
if (this.events[eventName]) { | ||
@@ -95,111 +247,87 @@ var _events; | ||
}, { | ||
key: 'extract', | ||
value: function extract(setName, atRule, rules, fromScope) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (!isObject(rules)) { | ||
throw new SyntaxError('At-rule declaration "' + atRule + '" must be an object.'); | ||
} | ||
} | ||
switch (atRule) { | ||
case '@fallbacks': | ||
this.extractFallbacks(setName, rules, fromScope); | ||
break; | ||
case '@font-face': | ||
this.extractFontFaces(setName, rules, fromScope); | ||
break; | ||
case '@keyframes': | ||
this.extractKeyframes(setName, rules, fromScope); | ||
break; | ||
case '@media': | ||
this.extractMediaQueries(setName, rules, fromScope); | ||
break; | ||
default: | ||
{ | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new SyntaxError('Unsupported at-rule "' + atRule + '".'); | ||
} | ||
} | ||
} | ||
key: 'handleCharset', | ||
value: function handleCharset(styleSheet, style) { | ||
styleSheet['@charset'] = style; | ||
} | ||
}, { | ||
key: 'extractFallbacks', | ||
value: function extractFallbacks(setName, properties, fromScope) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === GLOBAL) { | ||
throw new SyntaxError('Property fallbacks must be defined locally to an element.'); | ||
} | ||
} | ||
this.fallbacks[setName] = properties; | ||
this.emit('fallback', [setName, properties]); | ||
key: 'handleFallbacks', | ||
value: function handleFallbacks(declaration, style, property) { | ||
declaration[property] = [declaration[property]].concat(_toConsumableArray(style)).filter(Boolean); | ||
} | ||
}, { | ||
key: 'extractFontFaces', | ||
value: function extractFontFaces(setName, rules, fromScope) { | ||
var _this3 = this; | ||
key: 'handleFontFace', | ||
value: function handleFontFace(styleSheet, style, fontFamily) { | ||
if (Array.isArray(styleSheet['@font-face'])) { | ||
var _styleSheet$FontFac; | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === LOCAL) { | ||
throw new SyntaxError('Font faces must be declared in the global scope.'); | ||
} | ||
(_styleSheet$FontFac = styleSheet['@font-face']).push.apply(_styleSheet$FontFac, _toConsumableArray(style)); | ||
} else { | ||
styleSheet['@font-face'] = style; | ||
} | ||
Object.keys(rules).forEach(function (name) { | ||
var familyName = String(rules[name].fontFamily); | ||
if (_this3.fontFaces[familyName]) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new TypeError('Font face "' + familyName + '" has already been defined.'); | ||
} | ||
} else { | ||
_this3.fontFaces[familyName] = rules[name]; | ||
} | ||
_this3.emit('fontFace', [setName, familyName, rules[name]]); | ||
}); | ||
} | ||
}, { | ||
key: 'extractKeyframes', | ||
value: function extractKeyframes(setName, rules, fromScope) { | ||
var _this4 = this; | ||
key: 'handleGlobal', | ||
value: function handleGlobal(styleSheet, declaration, selector) {} | ||
}, { | ||
key: 'handleImport', | ||
value: function handleImport(styleSheet, style) { | ||
styleSheet['@import'] = style; | ||
} | ||
}, { | ||
key: 'handleKeyframes', | ||
value: function handleKeyframes(styleSheet, style, animationName) { | ||
styleSheet['@keyframes ' + animationName] = style; | ||
} | ||
}, { | ||
key: 'handleMedia', | ||
value: function handleMedia(declaration, style, condition) { | ||
declaration['@media ' + condition] = style; | ||
} | ||
}, { | ||
key: 'handleNamespace', | ||
value: function handleNamespace(styleSheet, style) { | ||
styleSheet['@namespace'] = style; | ||
} | ||
}, { | ||
key: 'handlePage', | ||
value: function handlePage(styleSheet, style) { | ||
styleSheet['@page'] = style; | ||
} | ||
}, { | ||
key: 'handleSupports', | ||
value: function handleSupports(declaration, style, condition) { | ||
declaration['@supports ' + condition] = style; | ||
} | ||
}, { | ||
key: 'handleViewport', | ||
value: function handleViewport(styleSheet, style) { | ||
styleSheet['@viewport'] = style; | ||
} | ||
}, { | ||
key: 'injectFontFaces', | ||
value: function injectFontFaces(value, cache) { | ||
var fontFaces = []; | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === LOCAL) { | ||
throw new SyntaxError('Animation keyframes must be declared in the global scope.'); | ||
} | ||
} | ||
String(value).split(',').forEach(function (name) { | ||
var familyName = name.trim(); | ||
var fonts = cache[familyName]; | ||
Object.keys(rules).forEach(function (name) { | ||
if (_this4.keyframes[name]) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new TypeError('Animation keyframe "' + name + '" has already been defined.'); | ||
} | ||
if (Array.isArray(fonts)) { | ||
fonts.forEach(function (font) { | ||
fontFaces.push(formatFontFace(font)); | ||
}); | ||
} else { | ||
_this4.keyframes[name] = rules[name]; | ||
fontFaces.push(familyName); | ||
} | ||
}); | ||
_this4.emit('keyframe', [setName, name, rules[name]]); | ||
}); | ||
return fontFaces; | ||
} | ||
}, { | ||
key: 'extractMediaQueries', | ||
value: function extractMediaQueries(setName, rules, fromScope) { | ||
var _this5 = this; | ||
key: 'injectKeyframes', | ||
value: function injectKeyframes(value, cache) { | ||
return String(value).split(',').map(function (name) { | ||
var animationName = name.trim(); | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === GLOBAL) { | ||
throw new SyntaxError('Media queries must be defined locally to an element.'); | ||
} | ||
} | ||
this.mediaQueries[setName] = rules; | ||
Object.keys(rules).forEach(function (query) { | ||
_this5.emit('mediaQuery', [setName, query, rules[query]]); | ||
return cache[animationName] || animationName; | ||
}); | ||
@@ -221,14 +349,2 @@ } | ||
} | ||
}, { | ||
key: 'resetGlobalCache', | ||
value: function resetGlobalCache() { | ||
this.fontFaces = {}; | ||
this.keyframes = {}; | ||
} | ||
}, { | ||
key: 'resetLocalCache', | ||
value: function resetLocalCache() { | ||
this.fallbacks = {}; | ||
this.mediaQueries = {}; | ||
} | ||
}]); | ||
@@ -239,4 +355,2 @@ | ||
UnifiedSyntax.LOCAL = LOCAL; | ||
UnifiedSyntax.GLOBAL = GLOBAL; | ||
export default UnifiedSyntax; |
@@ -25,4 +25,2 @@ 'use strict'; | ||
(0, _classCallCheck3.default)(this, Adapter); | ||
this.bypassNativeStyleSheet = false; | ||
this.native = false; | ||
this.options = {}; | ||
@@ -34,4 +32,9 @@ | ||
(0, _createClass3.default)(Adapter, [{ | ||
key: 'create', | ||
value: function create(styleSheet) { | ||
return styleSheet; | ||
} | ||
}, { | ||
key: 'transform', | ||
value: function transform(styleName, declarations) { | ||
value: function transform() { | ||
throw new Error(this.constructor.name + ' must define the `transform` method.'); | ||
@@ -38,0 +41,0 @@ } |
@@ -7,2 +7,6 @@ 'use strict'; | ||
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); | ||
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); | ||
var _extends2 = require('babel-runtime/helpers/extends'); | ||
@@ -20,4 +24,2 @@ | ||
var _aestheticUtils = require('aesthetic-utils'); | ||
var _lodash = require('lodash.merge'); | ||
@@ -27,2 +29,10 @@ | ||
var _isObject = require('./helpers/isObject'); | ||
var _isObject2 = _interopRequireDefault(_isObject); | ||
var _stripClassPrefix = require('./helpers/stripClassPrefix'); | ||
var _stripClassPrefix2 = _interopRequireDefault(_stripClassPrefix); | ||
var _Adapter = require('./Adapter'); | ||
@@ -32,2 +42,6 @@ | ||
var _style = require('./style'); | ||
var _style2 = _interopRequireDefault(_style); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -39,4 +53,3 @@ | ||
(0, _classCallCheck3.default)(this, Aesthetic); | ||
this.cache = {}; | ||
this.native = false; | ||
this.cache = new WeakMap(); | ||
this.options = { | ||
@@ -46,3 +59,3 @@ defaultTheme: '', | ||
pure: false, | ||
stylesPropName: 'classNames', | ||
stylesPropName: 'styles', | ||
themePropName: 'theme' | ||
@@ -60,19 +73,12 @@ }; | ||
(0, _createClass3.default)(Aesthetic, [{ | ||
key: 'extendTheme', | ||
value: function extendTheme(parentThemeName, themeName) { | ||
var theme = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var globals = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
return this.registerTheme(themeName, (0, _lodash2.default)({}, this.getTheme(parentThemeName), theme), globals); | ||
} | ||
}, { | ||
key: 'getStyles', | ||
value: function getStyles(styleName) { | ||
key: 'createStyleSheet', | ||
value: function createStyleSheet(styleName) { | ||
var themeName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; | ||
var props = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var parentStyleName = this.parents[styleName]; | ||
var declarations = this.styles[styleName]; | ||
var styleSheet = this.styles[styleName]; | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (!declarations) { | ||
if (!styleSheet) { | ||
throw new Error('Styles do not exist for "' + styleName + '".'); | ||
@@ -82,9 +88,21 @@ } | ||
if (typeof declarations !== 'function') { | ||
return declarations; | ||
if (typeof styleSheet === 'function') { | ||
styleSheet = styleSheet(themeName ? this.getTheme(themeName) : {}, props); | ||
} | ||
return declarations(themeName ? this.getTheme(themeName) : {}, parentStyleName ? this.getStyles(parentStyleName, themeName) : {}); | ||
if (parentStyleName) { | ||
styleSheet = (0, _lodash2.default)({}, this.createStyleSheet(parentStyleName, themeName, props), styleSheet); | ||
} | ||
return this.adapter.create(styleSheet); | ||
} | ||
}, { | ||
key: 'extendTheme', | ||
value: function extendTheme(parentThemeName, themeName) { | ||
var theme = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var globals = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
return this.registerTheme(themeName, (0, _lodash2.default)({}, this.getTheme(parentThemeName), theme), globals); | ||
} | ||
}, { | ||
key: 'getTheme', | ||
@@ -119,5 +137,5 @@ value: function getTheme() { | ||
throw new Error('Theme "' + themeName + '" already exists.'); | ||
} else if (!(0, _aestheticUtils.isObject)(theme)) { | ||
} else if (!(0, _isObject2.default)(theme)) { | ||
throw new TypeError('Theme "' + themeName + '" must be a style object.'); | ||
} else if (!(0, _aestheticUtils.isObject)(globals)) { | ||
} else if (!(0, _isObject2.default)(globals)) { | ||
throw new TypeError('Global styles for "' + themeName + '" must be an object.'); | ||
@@ -129,4 +147,6 @@ } | ||
this.adapter.transform(':root', globals); | ||
var globalStyleSheet = this.adapter.create(globals); | ||
this.transformStyles(Object.values(globalStyleSheet)); | ||
return this; | ||
@@ -138,3 +158,2 @@ } | ||
if (adapter instanceof _Adapter2.default || adapter && typeof adapter.transform === 'function') { | ||
adapter.native = this.native; | ||
this.adapter = adapter; | ||
@@ -149,3 +168,3 @@ } else if ("production" !== process.env.NODE_ENV) { | ||
key: 'setStyles', | ||
value: function setStyles(styleName, declarations) { | ||
value: function setStyles(styleName, styleSheet) { | ||
var extendFrom = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; | ||
@@ -156,3 +175,3 @@ | ||
throw new Error('Styles have already been set for "' + styleName + '".'); | ||
} else if (!(0, _aestheticUtils.isObject)(declarations) && typeof declarations !== 'function') { | ||
} else if (!(0, _isObject2.default)(styleSheet) && typeof styleSheet !== 'function') { | ||
throw new TypeError('Styles defined for "' + styleName + '" must be an object or function.'); | ||
@@ -162,3 +181,3 @@ } | ||
this.styles[styleName] = declarations; | ||
this.styles[styleName] = styleSheet; | ||
@@ -181,48 +200,42 @@ if (extendFrom) { | ||
key: 'transformStyles', | ||
value: function transformStyles(styleName, themeName) { | ||
var _this = this; | ||
var fallbackThemeName = themeName || this.options.defaultTheme || ''; | ||
var cacheKey = styleName + ':' + fallbackThemeName; | ||
if (this.cache[cacheKey]) { | ||
return this.cache[cacheKey]; | ||
value: function transformStyles(styles) { | ||
if (this.cache.has(styles)) { | ||
return this.cache.get(styles); | ||
} | ||
var declarations = this.getStyles(styleName, fallbackThemeName); | ||
var toTransform = {}; | ||
var output = {}; | ||
var setCount = 0; | ||
var classNames = []; | ||
var toTransform = []; | ||
Object.keys(declarations).forEach(function (setName) { | ||
if (typeof declarations[setName] === 'string') { | ||
output[setName] = _this.native ? {} : declarations[setName]; | ||
} else { | ||
toTransform[setName] = declarations[setName]; | ||
setCount += 1; | ||
styles.forEach(function (style) { | ||
if (!style) { | ||
return; | ||
} else if (typeof style === 'string' || typeof style === 'number') { | ||
classNames.push.apply(classNames, (0, _toConsumableArray3.default)(String(style).split(' ').map(function (s) { | ||
return (0, _stripClassPrefix2.default)(s).trim(); | ||
}))); | ||
} else if ((0, _isObject2.default)(style)) { | ||
toTransform.push(style); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error('Unsupported style type to transform.'); | ||
} | ||
}); | ||
if (setCount > 0) { | ||
var transformedOutput = this.adapter.transform(styleName, toTransform); | ||
if (toTransform.length > 0) { | ||
var _adapter; | ||
Object.keys(transformedOutput).forEach(function (setName) { | ||
output[setName] = _this.validateTransform(styleName, setName, transformedOutput[setName]); | ||
}); | ||
classNames.push((_adapter = this.adapter).transform.apply(_adapter, toTransform)); | ||
} | ||
this.cache[cacheKey] = output; | ||
var className = classNames.join(' ').trim(); | ||
return output; | ||
this.cache.set(styles, className); | ||
return className; | ||
} | ||
}, { | ||
key: 'validateTransform', | ||
value: function validateTransform(styleName, setName, value) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (typeof value !== 'string') { | ||
throw new TypeError('`' + this.adapter.constructor.name + '` must return a mapping of CSS class names. ' + ('"' + styleName + '@' + setName + '" is not a valid string.')); | ||
} | ||
} | ||
key: 'withStyles', | ||
value: function withStyles(styleSheet) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return value; | ||
return (0, _style2.default)(this, styleSheet, options); | ||
} | ||
@@ -229,0 +242,0 @@ }]); |
@@ -48,14 +48,20 @@ 'use strict'; | ||
key: 'transform', | ||
value: function transform(styleName, declarations) { | ||
var classNames = {}; | ||
value: function transform() { | ||
var _this2 = this; | ||
Object.keys(declarations).forEach(function (setName) { | ||
if (typeof declarations[setName] === 'string') { | ||
classNames[setName] = declarations[setName]; | ||
var classNames = []; | ||
for (var _len2 = arguments.length, styles = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
styles[_key2] = arguments[_key2]; | ||
} | ||
styles.forEach(function (style) { | ||
if (style && typeof style === 'string') { | ||
classNames.push(style); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new TypeError('`ClassNameAdapter` expects valid CSS class names; ' + ('non-string provided for "' + setName + '".')); | ||
throw new TypeError(_this2.constructor.name + ' expects valid CSS class names.'); | ||
} | ||
}); | ||
return classNames; | ||
return classNames.join(' '); | ||
} | ||
@@ -62,0 +68,0 @@ }]); |
@@ -12,14 +12,4 @@ 'use strict'; | ||
var _style = require('./style'); | ||
var _style2 = _interopRequireDefault(_style); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* | ||
*/ | ||
function createStyler(aesthetic) { | ||
@@ -32,8 +22,20 @@ if ("production" !== process.env.NODE_ENV) { | ||
return function styler() { | ||
var defaultStyles = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return { | ||
style: function style(styleSheet) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return (0, _style2.default)(aesthetic, defaultStyles, options); | ||
return aesthetic.withStyles(styleSheet, options); | ||
}, | ||
transform: function transform() { | ||
for (var _len = arguments.length, styles = Array(_len), _key = 0; _key < _len; _key++) { | ||
styles[_key] = arguments[_key]; | ||
} | ||
return aesthetic.transformStyles(styles); | ||
} | ||
}; | ||
} | ||
} /** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* | ||
*/ |
@@ -6,3 +6,3 @@ 'use strict'; | ||
}); | ||
exports.classes = exports.createStyler = exports.ThemeProvider = exports.ClassNameAdapter = exports.Adapter = exports.ClassOrStylesPropType = exports.StylesPropType = exports.ClassNamesPropType = undefined; | ||
exports.createStyler = exports.ThemeProvider = exports.ClassNameAdapter = exports.Adapter = exports.StylesPropType = undefined; | ||
@@ -33,15 +33,11 @@ var _propTypes = require('prop-types'); | ||
var _classes = require('./classes'); | ||
var _classes2 = _interopRequireDefault(_classes); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var ClassNamesPropType = exports.ClassNamesPropType = _propTypes2.default.objectOf(_propTypes2.default.string); /** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
*/ | ||
/** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* | ||
*/ | ||
var StylesPropType = exports.StylesPropType = _propTypes2.default.objectOf(_propTypes2.default.object); | ||
var ClassOrStylesPropType = exports.ClassOrStylesPropType = _propTypes2.default.oneOfType([_propTypes2.default.objectOf(_propTypes2.default.string), _propTypes2.default.objectOf(_propTypes2.default.object)]); | ||
@@ -52,3 +48,2 @@ exports.Adapter = _Adapter2.default; | ||
exports.createStyler = _createStyler2.default; | ||
exports.classes = _classes2.default; | ||
exports.default = _Aesthetic2.default; |
@@ -60,3 +60,3 @@ 'use strict'; | ||
function style(aesthetic) { | ||
var styles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var styleSheet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
@@ -93,3 +93,3 @@ | ||
aesthetic.setStyles(styleName, styles, extendFrom); | ||
aesthetic.setStyles(styleName, styleSheet, extendFrom); | ||
@@ -100,4 +100,15 @@ var StyledComponent = function (_ParentComponent) { | ||
function StyledComponent() { | ||
var _ref, _this$state; | ||
var _temp, _this, _ret; | ||
(0, _classCallCheck3.default)(this, StyledComponent); | ||
return (0, _possibleConstructorReturn3.default)(this, (StyledComponent.__proto__ || Object.getPrototypeOf(StyledComponent)).apply(this, arguments)); | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = StyledComponent.__proto__ || Object.getPrototypeOf(StyledComponent)).call.apply(_ref, [this].concat(args))), _this), _this.state = (_this$state = { | ||
firstMount: true | ||
}, (0, _defineProperty3.default)(_this$state, stylesPropName, {}), (0, _defineProperty3.default)(_this$state, 'themeName', ''), (0, _defineProperty3.default)(_this$state, themePropName, {}), _this$state), _temp), (0, _possibleConstructorReturn3.default)(_this, _ret); | ||
} | ||
@@ -108,3 +119,3 @@ | ||
value: function componentWillMount() { | ||
this.transformStyles(this.getTheme(this.props)); | ||
this.transformStyles(this.props); | ||
} | ||
@@ -114,19 +125,21 @@ }, { | ||
value: function componentWillReceiveProps(nextProps) { | ||
var theme = this.getTheme(nextProps); | ||
if (theme !== this.state[themePropName]) { | ||
this.transformStyles(theme); | ||
} | ||
this.transformStyles(nextProps); | ||
} | ||
}, { | ||
key: 'getTheme', | ||
value: function getTheme(props) { | ||
return props[themePropName] || this.context.themeName || aesthetic.options.defaultTheme || ''; | ||
key: 'getThemeName', | ||
value: function getThemeName(props) { | ||
return props.themeName || this.context.themeName || aesthetic.options.defaultTheme || ''; | ||
} | ||
}, { | ||
key: 'transformStyles', | ||
value: function transformStyles(theme) { | ||
var _setState; | ||
value: function transformStyles(props) { | ||
var themeName = this.getThemeName(props); | ||
this.setState((_setState = {}, (0, _defineProperty3.default)(_setState, stylesPropName, aesthetic.transformStyles(styleName, theme)), (0, _defineProperty3.default)(_setState, themePropName, theme), _setState)); | ||
if (this.state.firstMount || themeName !== this.state.themeName) { | ||
var _setState; | ||
this.setState((_setState = { | ||
firstMount: false | ||
}, (0, _defineProperty3.default)(_setState, stylesPropName, aesthetic.createStyleSheet(styleName, themeName, props)), (0, _defineProperty3.default)(_setState, 'themeName', themeName), (0, _defineProperty3.default)(_setState, themePropName, themeName ? aesthetic.getTheme(themeName) : {}), _setState)); | ||
} | ||
} | ||
@@ -140,3 +153,4 @@ }, { | ||
key: 'extendStyles', | ||
value: function extendStyles(customStyles) { | ||
value: function extendStyles() { | ||
var customStyleSheet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var extendOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -150,3 +164,3 @@ | ||
return style(aesthetic, customStyles, (0, _extends3.default)({}, options, extendOptions, { | ||
return style(aesthetic, customStyleSheet, (0, _extends3.default)({}, options, extendOptions, { | ||
extendFrom: styleName | ||
@@ -161,7 +175,12 @@ }))(Component); | ||
StyledComponent.styleName = styleName; | ||
StyledComponent.wrappedComponent = Component; | ||
StyledComponent.propTypes = (0, _defineProperty3.default)({}, themePropName, _propTypes2.default.string); | ||
StyledComponent.WrappedComponent = Component; | ||
StyledComponent.contextTypes = { | ||
themeName: _propTypes2.default.string | ||
}; | ||
StyledComponent.propTypes = { | ||
themeName: _propTypes2.default.string | ||
}; | ||
StyledComponent.defaultProps = { | ||
themeName: '' | ||
}; | ||
@@ -168,0 +187,0 @@ |
@@ -6,3 +6,3 @@ 'use strict'; | ||
}); | ||
exports.AT_RULES = exports.GLOBAL = exports.LOCAL = undefined; | ||
exports.LOCAL_RULES = exports.GLOBAL_RULES = undefined; | ||
@@ -25,14 +25,23 @@ var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); | ||
var _aestheticUtils = require('aesthetic-utils'); | ||
var _formatFontFace = require('./helpers/formatFontFace'); | ||
var _formatFontFace2 = _interopRequireDefault(_formatFontFace); | ||
var _isObject = require('./helpers/isObject'); | ||
var _isObject2 = _interopRequireDefault(_isObject); | ||
var _toArray = require('./helpers/toArray'); | ||
var _toArray2 = _interopRequireDefault(_toArray); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var LOCAL = exports.LOCAL = 'local'; /** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* | ||
*/ | ||
var GLOBAL_RULES = exports.GLOBAL_RULES = ['@charset', '@font-face', '@global', '@import', '@keyframes', '@namespace', '@page', '@viewport']; /** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* | ||
*/ | ||
var GLOBAL = exports.GLOBAL = 'global'; | ||
var AT_RULES = exports.AT_RULES = ['@fallbacks', '@font-face', '@keyframes', '@media']; | ||
var LOCAL_RULES = exports.LOCAL_RULES = ['@fallbacks', '@media', '@supports']; | ||
@@ -43,64 +52,214 @@ var UnifiedSyntax = function () { | ||
this.events = {}; | ||
this.fallbacks = {}; | ||
this.fontFaces = {}; | ||
this.fontFaceNames = {}; | ||
this.fontFacesCache = {}; | ||
this.keyframes = {}; | ||
this.keyframeNames = {}; | ||
this.mediaQueries = {}; | ||
this.keyframesCache = {}; | ||
this.handleProperty = function (declaration, style, property) { | ||
declaration[property] = style; | ||
}; | ||
this.on('property', this.handleProperty).on('@charset', this.handleCharset).on('@fallbacks', this.handleFallbacks).on('@font-face', this.handleFontFace).on('@global', this.handleGlobal).on('@import', this.handleImport).on('@keyframes', this.handleKeyframes).on('@media', this.handleMedia).on('@namespace', this.handleNamespace).on('@page', this.handlePage).on('@supports', this.handleSupports).on('@viewport', this.handleViewport); | ||
} | ||
(0, _createClass3.default)(UnifiedSyntax, [{ | ||
key: 'checkBlock', | ||
value: function checkBlock(value) { | ||
if ((0, _isObject2.default)(value)) { | ||
return value; | ||
} | ||
throw new Error('Must be a style declaration.'); | ||
} | ||
}, { | ||
key: 'convert', | ||
value: function convert(declarations) { | ||
value: function convert(styleSheet) { | ||
var _this = this; | ||
this.resetLocalCache(); | ||
this.emit('converting'); | ||
var prevStyleSheet = (0, _extends3.default)({}, styleSheet); | ||
var nextStyleSheet = {}; | ||
var adaptedDeclarations = (0, _extends3.default)({}, declarations); | ||
GLOBAL_RULES.forEach(function (rule) { | ||
if (!prevStyleSheet[rule]) { | ||
delete prevStyleSheet[rule]; | ||
AT_RULES.forEach(function (atRule) { | ||
if (atRule in adaptedDeclarations) { | ||
_this.extract(':root', atRule, adaptedDeclarations[atRule], GLOBAL); | ||
return; | ||
} | ||
delete adaptedDeclarations[atRule]; | ||
switch (rule) { | ||
case '@charset': | ||
case '@import': | ||
case '@namespace': | ||
{ | ||
var path = prevStyleSheet[rule]; | ||
if (typeof path === 'string') { | ||
_this.emit(rule, [nextStyleSheet, path]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error(rule + ' value must be a string.'); | ||
} | ||
break; | ||
} | ||
case '@font-face': | ||
{ | ||
var faces = prevStyleSheet['@font-face']; | ||
Object.keys(_this.checkBlock(faces)).forEach(function (fontFamily) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (_this.fontFaces[fontFamily]) { | ||
throw new Error('@font-face "' + fontFamily + '" already exists.'); | ||
} | ||
} | ||
_this.fontFaces[fontFamily] = (0, _toArray2.default)(faces[fontFamily]).map(function (font) { | ||
return (0, _extends3.default)({}, font, { | ||
fontFamily: fontFamily | ||
}); | ||
}); | ||
_this.emit(rule, [nextStyleSheet, _this.fontFaces[fontFamily], fontFamily]); | ||
}); | ||
break; | ||
} | ||
case '@global': | ||
{ | ||
var globals = prevStyleSheet['@global']; | ||
Object.keys(_this.checkBlock(globals)).forEach(function (selector) { | ||
if ((0, _isObject2.default)(globals[selector])) { | ||
_this.emit(rule, [nextStyleSheet, _this.convertDeclaration(selector, globals[selector]), selector]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error('Invalid @global selector style declaration.'); | ||
} | ||
}); | ||
break; | ||
} | ||
case '@keyframes': | ||
{ | ||
var frames = prevStyleSheet['@keyframes']; | ||
Object.keys(_this.checkBlock(frames)).forEach(function (animationName) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (_this.keyframes[animationName]) { | ||
throw new Error('@keyframes "' + animationName + '" already exists.'); | ||
} | ||
} | ||
_this.keyframes[animationName] = _this.checkBlock(frames[animationName]); | ||
_this.emit(rule, [nextStyleSheet, _this.keyframes[animationName], animationName]); | ||
}); | ||
break; | ||
} | ||
case '@page': | ||
case '@viewport': | ||
{ | ||
var style = prevStyleSheet[rule]; | ||
if ((0, _isObject2.default)(style)) { | ||
_this.emit(rule, [nextStyleSheet, style]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error(rule + ' must be a style object.'); | ||
} | ||
break; | ||
} | ||
default: | ||
break; | ||
} | ||
delete prevStyleSheet[rule]; | ||
}); | ||
Object.keys(adaptedDeclarations).forEach(function (setName) { | ||
var declaration = declarations[setName]; | ||
Object.keys(prevStyleSheet).forEach(function (selector) { | ||
var declaration = prevStyleSheet[selector]; | ||
if (typeof declaration !== 'string') { | ||
adaptedDeclarations[setName] = _this.convertDeclaration(setName, declaration); | ||
delete prevStyleSheet[selector]; | ||
if (!declaration) { | ||
return; | ||
} | ||
if (selector.charAt(0) === '@') { | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new SyntaxError('Unsupported global at-rule "' + selector + '".'); | ||
} | ||
} else if (typeof declaration === 'string') { | ||
nextStyleSheet[selector] = declaration; | ||
} else if ((0, _isObject2.default)(declaration)) { | ||
nextStyleSheet[selector] = _this.convertDeclaration(selector, declaration); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error('Invalid style declaration for "' + selector + '".'); | ||
} | ||
}); | ||
this.emit('converted'); | ||
return adaptedDeclarations; | ||
return nextStyleSheet; | ||
} | ||
}, { | ||
key: 'convertDeclaration', | ||
value: function convertDeclaration(setName, properties) { | ||
value: function convertDeclaration(selector, declaration) { | ||
var _this2 = this; | ||
var nextProperties = (0, _extends3.default)({}, properties); | ||
var prevDeclaration = (0, _extends3.default)({}, declaration); | ||
var nextDeclaration = {}; | ||
AT_RULES.forEach(function (atRule) { | ||
if (atRule in nextProperties) { | ||
_this2.extract(setName, atRule, nextProperties[atRule], LOCAL); | ||
Object.keys(prevDeclaration).forEach(function (key) { | ||
if (key.charAt(0) !== '@') { | ||
_this2.emit('property', [nextDeclaration, prevDeclaration[key], key]); | ||
delete nextProperties[atRule]; | ||
delete prevDeclaration[key]; | ||
} | ||
}); | ||
this.emit('declaration', [setName, nextProperties]); | ||
LOCAL_RULES.forEach(function (rule) { | ||
var style = prevDeclaration[rule]; | ||
return nextProperties; | ||
delete prevDeclaration[rule]; | ||
if (!style || !(0, _isObject2.default)(style)) { | ||
return; | ||
} | ||
if (rule === '@fallbacks') { | ||
Object.keys(style).forEach(function (property) { | ||
_this2.emit(rule, [nextDeclaration, (0, _toArray2.default)(style[property]), property]); | ||
}); | ||
} else if (rule === '@media' || rule === '@supports') { | ||
Object.keys(style).forEach(function (condition) { | ||
if ((0, _isObject2.default)(style[condition])) { | ||
_this2.emit(rule, [nextDeclaration, style[condition], condition]); | ||
} else if ("production" !== process.env.NODE_ENV) { | ||
throw new Error(rule + ' ' + condition + ' must be a mapping of conditions to style objects.'); | ||
} | ||
}); | ||
} | ||
}); | ||
if ("production" !== process.env.NODE_ENV) { | ||
Object.keys(prevDeclaration).forEach(function (key) { | ||
throw new SyntaxError('Unsupported local at-rule "' + key + '".'); | ||
}); | ||
} | ||
return nextDeclaration; | ||
} | ||
}, { | ||
key: 'createUnsupportedHandler', | ||
value: function createUnsupportedHandler(rule) { | ||
return function () { | ||
throw new Error('Adapter does not support "' + rule + '".'); | ||
}; | ||
} | ||
}, { | ||
key: 'emit', | ||
value: function emit(eventName) { | ||
var args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
value: function emit(eventName, args) { | ||
if (this.events[eventName]) { | ||
@@ -115,111 +274,87 @@ var _events; | ||
}, { | ||
key: 'extract', | ||
value: function extract(setName, atRule, rules, fromScope) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (!(0, _aestheticUtils.isObject)(rules)) { | ||
throw new SyntaxError('At-rule declaration "' + atRule + '" must be an object.'); | ||
} | ||
} | ||
switch (atRule) { | ||
case '@fallbacks': | ||
this.extractFallbacks(setName, rules, fromScope); | ||
break; | ||
case '@font-face': | ||
this.extractFontFaces(setName, rules, fromScope); | ||
break; | ||
case '@keyframes': | ||
this.extractKeyframes(setName, rules, fromScope); | ||
break; | ||
case '@media': | ||
this.extractMediaQueries(setName, rules, fromScope); | ||
break; | ||
default: | ||
{ | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new SyntaxError('Unsupported at-rule "' + atRule + '".'); | ||
} | ||
} | ||
} | ||
key: 'handleCharset', | ||
value: function handleCharset(styleSheet, style) { | ||
styleSheet['@charset'] = style; | ||
} | ||
}, { | ||
key: 'extractFallbacks', | ||
value: function extractFallbacks(setName, properties, fromScope) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === GLOBAL) { | ||
throw new SyntaxError('Property fallbacks must be defined locally to an element.'); | ||
} | ||
} | ||
this.fallbacks[setName] = properties; | ||
this.emit('fallback', [setName, properties]); | ||
key: 'handleFallbacks', | ||
value: function handleFallbacks(declaration, style, property) { | ||
declaration[property] = [declaration[property]].concat((0, _toConsumableArray3.default)(style)).filter(Boolean); | ||
} | ||
}, { | ||
key: 'extractFontFaces', | ||
value: function extractFontFaces(setName, rules, fromScope) { | ||
var _this3 = this; | ||
key: 'handleFontFace', | ||
value: function handleFontFace(styleSheet, style, fontFamily) { | ||
if (Array.isArray(styleSheet['@font-face'])) { | ||
var _styleSheet$FontFac; | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === LOCAL) { | ||
throw new SyntaxError('Font faces must be declared in the global scope.'); | ||
} | ||
(_styleSheet$FontFac = styleSheet['@font-face']).push.apply(_styleSheet$FontFac, (0, _toConsumableArray3.default)(style)); | ||
} else { | ||
styleSheet['@font-face'] = style; | ||
} | ||
Object.keys(rules).forEach(function (name) { | ||
var familyName = String(rules[name].fontFamily); | ||
if (_this3.fontFaces[familyName]) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new TypeError('Font face "' + familyName + '" has already been defined.'); | ||
} | ||
} else { | ||
_this3.fontFaces[familyName] = rules[name]; | ||
} | ||
_this3.emit('fontFace', [setName, familyName, rules[name]]); | ||
}); | ||
} | ||
}, { | ||
key: 'extractKeyframes', | ||
value: function extractKeyframes(setName, rules, fromScope) { | ||
var _this4 = this; | ||
key: 'handleGlobal', | ||
value: function handleGlobal(styleSheet, declaration, selector) {} | ||
}, { | ||
key: 'handleImport', | ||
value: function handleImport(styleSheet, style) { | ||
styleSheet['@import'] = style; | ||
} | ||
}, { | ||
key: 'handleKeyframes', | ||
value: function handleKeyframes(styleSheet, style, animationName) { | ||
styleSheet['@keyframes ' + animationName] = style; | ||
} | ||
}, { | ||
key: 'handleMedia', | ||
value: function handleMedia(declaration, style, condition) { | ||
declaration['@media ' + condition] = style; | ||
} | ||
}, { | ||
key: 'handleNamespace', | ||
value: function handleNamespace(styleSheet, style) { | ||
styleSheet['@namespace'] = style; | ||
} | ||
}, { | ||
key: 'handlePage', | ||
value: function handlePage(styleSheet, style) { | ||
styleSheet['@page'] = style; | ||
} | ||
}, { | ||
key: 'handleSupports', | ||
value: function handleSupports(declaration, style, condition) { | ||
declaration['@supports ' + condition] = style; | ||
} | ||
}, { | ||
key: 'handleViewport', | ||
value: function handleViewport(styleSheet, style) { | ||
styleSheet['@viewport'] = style; | ||
} | ||
}, { | ||
key: 'injectFontFaces', | ||
value: function injectFontFaces(value, cache) { | ||
var fontFaces = []; | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === LOCAL) { | ||
throw new SyntaxError('Animation keyframes must be declared in the global scope.'); | ||
} | ||
} | ||
String(value).split(',').forEach(function (name) { | ||
var familyName = name.trim(); | ||
var fonts = cache[familyName]; | ||
Object.keys(rules).forEach(function (name) { | ||
if (_this4.keyframes[name]) { | ||
if ("production" !== process.env.NODE_ENV) { | ||
throw new TypeError('Animation keyframe "' + name + '" has already been defined.'); | ||
} | ||
if (Array.isArray(fonts)) { | ||
fonts.forEach(function (font) { | ||
fontFaces.push((0, _formatFontFace2.default)(font)); | ||
}); | ||
} else { | ||
_this4.keyframes[name] = rules[name]; | ||
fontFaces.push(familyName); | ||
} | ||
}); | ||
_this4.emit('keyframe', [setName, name, rules[name]]); | ||
}); | ||
return fontFaces; | ||
} | ||
}, { | ||
key: 'extractMediaQueries', | ||
value: function extractMediaQueries(setName, rules, fromScope) { | ||
var _this5 = this; | ||
key: 'injectKeyframes', | ||
value: function injectKeyframes(value, cache) { | ||
return String(value).split(',').map(function (name) { | ||
var animationName = name.trim(); | ||
if ("production" !== process.env.NODE_ENV) { | ||
if (fromScope === GLOBAL) { | ||
throw new SyntaxError('Media queries must be defined locally to an element.'); | ||
} | ||
} | ||
this.mediaQueries[setName] = rules; | ||
Object.keys(rules).forEach(function (query) { | ||
_this5.emit('mediaQuery', [setName, query, rules[query]]); | ||
return cache[animationName] || animationName; | ||
}); | ||
@@ -241,14 +376,2 @@ } | ||
} | ||
}, { | ||
key: 'resetGlobalCache', | ||
value: function resetGlobalCache() { | ||
this.fontFaces = {}; | ||
this.keyframes = {}; | ||
} | ||
}, { | ||
key: 'resetLocalCache', | ||
value: function resetLocalCache() { | ||
this.fallbacks = {}; | ||
this.mediaQueries = {}; | ||
} | ||
}]); | ||
@@ -258,4 +381,2 @@ return UnifiedSyntax; | ||
UnifiedSyntax.LOCAL = LOCAL; | ||
UnifiedSyntax.GLOBAL = GLOBAL; | ||
exports.default = UnifiedSyntax; |
{ | ||
"name": "aesthetic", | ||
"version": "1.7.1", | ||
"version": "2.0.0-0", | ||
"description": "Aesthetic is a powerful React library for styling components through the use of adapters.", | ||
@@ -8,3 +8,2 @@ "keywords": [ | ||
"react", | ||
"react-native", | ||
"native", | ||
@@ -28,3 +27,2 @@ "style", | ||
"dependencies": { | ||
"aesthetic-utils": "^1.6.2", | ||
"babel-runtime": "^6.26.0", | ||
@@ -39,5 +37,5 @@ "hoist-non-react-statics": "^2.3.1", | ||
"devDependencies": { | ||
"@milesj/build-tool-config": "^0.41.2", | ||
"react": "^16.1.0" | ||
"@milesj/build-tool-config": "^0.44.0", | ||
"react": "^16.2.0" | ||
} | ||
} |
139
README.md
@@ -19,3 +19,3 @@ # Aesthetic | ||
children: PropTypes.node, | ||
classNames: ClassNamesPropType, | ||
classNames: ClassNamesPropType.isRequired, | ||
}; | ||
@@ -32,6 +32,6 @@ | ||
role="tablist" | ||
className={classes({ | ||
className={classes( | ||
classNames.carousel, | ||
animating && classNames.carousel__animating, | ||
})} | ||
)} | ||
> | ||
@@ -130,2 +130,3 @@ <ul className={classNames.list}> | ||
* [Media Queries](#media-queries) | ||
* [Supports](#supports) | ||
* [Font Faces](#font-faces) | ||
@@ -137,3 +138,2 @@ * [Animations](#animations) | ||
* [Adapters](#adapters) | ||
* [React Native Support](#react-native-support) | ||
@@ -174,12 +174,14 @@ ### Initial Setup | ||
| Adapter | Unified Syntax | Pseudos | Fallbacks | Fonts | Animations | Media Queries | React Native | | ||
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | | ||
| [CSS class names](#external-classes) | | ✓ | ✓ | ✓ | ✓ | ✓ | | | ||
| [CSS modules][css-modules] | | ✓ | ✓ | ✓ | ✓ | ✓ | | | ||
| [Aphrodite][aphrodite] | ✓ | ✓ | | ✓ | ✓ | ✓ | | | ||
| [Fela][fela] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
| [Glamor][glamor] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | ||
| [JSS][jss] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | ||
| [React Native][react-native] | | | | ✓ | ✓ | | ✓ | | ||
| Adapter | Unified Syntax | Globals | Pseudos | Fallbacks | Fonts | Animations | Media Queries | Supports | | ||
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | | ||
| [CSS class names](#external-classes) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
| [CSS modules][css-modules] | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
| [Aphrodite][aphrodite] | ✓ | ✓¹ | ✓ | | ✓ | ✓ | ✓ | | | ||
| [Fela][fela] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
| [Glamor][glamor] | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
| [JSS][jss] | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
| [TypeStyle][typestyle] | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
> 1. Only supports `@font-face` and `@keyframes`. | ||
The following libraries are currently not supported. | ||
@@ -223,3 +225,3 @@ | ||
object is passed to. Defaults to `classNames`. | ||
* `themePropName` (string) - Name of the prop in which the theme name is passed to. | ||
* `themePropName` (string) - Name of the prop in which the theme style declaration is passed to. | ||
Defaults to `theme`. | ||
@@ -279,3 +281,3 @@ * `pure` (boolean) - When true, the higher-order-component will extend `React.PureComponent` | ||
children: PropTypes.node, | ||
classNames: ClassNamesPropType, | ||
classNames: ClassNamesPropType.isRequired, | ||
icon: PropTypes.node, | ||
@@ -367,3 +369,3 @@ }; | ||
as the first argument to the [styler function](#creating-a-styler). This object | ||
represents a mapping of elements (and modifiers) to declarations. For example: | ||
represents a mapping of selectors (and modifiers) to declarations. For example: | ||
@@ -450,11 +452,10 @@ ```javascript | ||
spacing: 5, | ||
font: 'Roboto', | ||
font: 'Open Sans', | ||
bgColor: 'darkgray', | ||
}, { | ||
'@font-face': { | ||
roboto: { | ||
fontFamily: 'Roboto', | ||
'Open Sans': { | ||
fontStyle: 'normal', | ||
fontWeight: 'normal', | ||
src: "url('roboto.woff2') format('roboto')", | ||
src: ['fonts/OpenSans.woff'], | ||
}, | ||
@@ -495,2 +496,4 @@ }, | ||
> The theme style declaration can be accessed within a component via the `theme` prop. | ||
#### Activating Themes | ||
@@ -513,6 +516,6 @@ | ||
Or by passing a `theme` prop to an individual component. | ||
Or by passing a `themeName` prop to an individual component. | ||
```javascript | ||
<Button theme="dark">Save</Button> | ||
<Button themeName="dark">Save</Button> | ||
``` | ||
@@ -575,3 +578,3 @@ | ||
* Supports camel case property names. | ||
* Units can be written is literal numbers. | ||
* Units can be written as literal numbers. | ||
@@ -600,3 +603,3 @@ ```javascript | ||
Pseudo elements and classes are defined inside an element as nested objects. | ||
Pseudo elements and classes are defined inside a selector as nested objects. | ||
@@ -642,3 +645,4 @@ ```javascript | ||
Media queries are defined inside an element using a `@media` object. | ||
Media queries are defined inside a selector using a `@media` object, | ||
with the query conditional as the key, and style declarations as the value. | ||
@@ -657,14 +661,33 @@ ```javascript | ||
> JSS requires the `jss-nested` plugin. | ||
#### Supports | ||
Feature queries are defined inside a selector using a `@supports` object, | ||
with the feature conditional as the key, and style declarations as the value. | ||
```javascript | ||
grid: { | ||
// ... | ||
float: 'left', | ||
'@supports': { | ||
'(display: flex)': { | ||
float: 'none', | ||
display: 'flex', | ||
}, | ||
}, | ||
}, | ||
``` | ||
#### Font Faces | ||
Font faces are defined outside the element using a `@font-face` object | ||
and are referenced by font family name. | ||
Font faces are defined outside the selector (in the root) using a `@font-face` object | ||
and are referenced by the font family name (the object key). | ||
```javascript | ||
'@font-face': { | ||
roboto: { | ||
fontFamily: 'Roboto', | ||
'Open Sans': { | ||
fontStyle: 'normal', | ||
fontWeight: 'normal', | ||
src: "url('roboto.woff2') format('roboto')", | ||
src: ['fonts/OpenSans.woff2', 'fonts/OpenSans.ttf'], | ||
}, | ||
@@ -674,13 +697,52 @@ }, | ||
// ... | ||
fontFamily: 'Roboto', | ||
fontFamily: 'Open Sans', | ||
}, | ||
tooltip: { | ||
// ... | ||
fontFamily: 'Roboto, sans-serif', | ||
fontFamily: 'Open Sans, sans-serif', | ||
}, | ||
``` | ||
> The `fontFamily` property can be omitted as it'll be inherited from the property name. | ||
To support multiple font variations, like bold and italics, pass an array of properties. | ||
```javascript | ||
'@font-face': { | ||
'Open Sans': [ | ||
{ | ||
fontStyle: 'normal', | ||
fontWeight: 'normal', | ||
src: ['fonts/OpenSans.woff2', 'fonts/OpenSans.ttf'], | ||
}, | ||
{ | ||
fontStyle: 'italic', | ||
fontWeight: 'normal', | ||
src: ['fonts/OpenSans-Italic.woff2', 'fonts/OpenSans-Italic.ttf'], | ||
}, | ||
{ | ||
fontStyle: 'normal', | ||
fontWeight: 'bold', | ||
src: ['fonts/OpenSans-Bold.woff2', 'fonts/OpenSans-Bold.ttf'], | ||
}, | ||
], | ||
}, | ||
``` | ||
Lastly, to define `local()` source aliases, pass an array of strings to a `local` property. | ||
```javascript | ||
'@font-face': { | ||
'Open Sans': { | ||
fontStyle: 'normal', | ||
fontWeight: 'normal', | ||
local: ['OpenSans', 'Open-Sans'], | ||
src: ['fonts/OpenSans.ttf'], | ||
}, | ||
}, | ||
``` | ||
#### Animations | ||
Animation keyframes are defined outside the element using a `@keyframes` object | ||
Animation keyframes are defined outside the selector (in the root) using a `@keyframes` object | ||
and are referenced by animation name (the object key). | ||
@@ -705,3 +767,3 @@ | ||
Parent, child, and sibling selectors are purposefully not supported. Use unique and | ||
isolated element names and style declarations instead. | ||
isolated element selectors and style declarations instead. | ||
@@ -733,9 +795,4 @@ ### Competitors Comparison | ||
| [JSS][jss] | ✓ | ✓ | | | | ||
| [React Native][react-native] | ✓ | ✓ | | || | ||
| [TypeStyle][typestyle] | ✓ | | | | | ||
### React Native Support | ||
Please refer to the [aesthetic-native][react-native] package for more information on how | ||
to integrate React Native with Aesthetic. | ||
[css-modules]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-adapter-css-modules | ||
@@ -746,5 +803,5 @@ [aphrodite]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-adapter-aphrodite | ||
[jss]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-adapter-jss | ||
[typestyle]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-adapter-typestyle | ||
[radium]: https://github.com/FormidableLabs/radium | ||
[react-native]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-native | ||
[react-with-styles]: https://github.com/airbnb/react-with-styles | ||
[styled-components]: https://github.com/styled-components/styled-components |
@@ -7,9 +7,5 @@ /** | ||
import type { StyleDeclarationMap, TransformedStylesMap } from '../../types'; | ||
import type { ClassName, StyleDeclaration, StyleSheet } from '../../types'; | ||
export default class Adapter { | ||
bypassNativeStyleSheet: boolean = false; | ||
native: boolean = false; | ||
options: Object = {}; | ||
@@ -22,7 +18,14 @@ | ||
/** | ||
* Transform the unified or native syntax using the registered adapter. | ||
* Create a stylesheet from a component's style styleSheet. | ||
*/ | ||
transform(styleName: string, declarations: StyleDeclarationMap): TransformedStylesMap { | ||
create(styleSheet: StyleSheet): StyleSheet { | ||
return styleSheet; | ||
} | ||
/** | ||
* Transform the style declarations using the registered adapter. | ||
*/ | ||
transform(...styles: StyleDeclaration[]): ClassName { | ||
throw new Error(`${this.constructor.name} must define the \`transform\` method.`); | ||
} | ||
} |
@@ -7,13 +7,17 @@ /** | ||
import { isObject } from 'aesthetic-utils'; | ||
import deepMerge from 'lodash.merge'; | ||
import isObject from './helpers/isObject'; | ||
import stripClassPrefix from './helpers/stripClassPrefix'; | ||
import Adapter from './Adapter'; | ||
import withStyles from './style'; | ||
import type { | ||
AestheticOptions, | ||
ClassName, | ||
HOCOptions, | ||
HOCWrapper, | ||
StyleDeclaration, | ||
StyleDeclarationMap, | ||
StyleDeclarationOrCallback, | ||
TransformedStylesMap, | ||
CSSStyle, | ||
StyleSheet, | ||
StyleSheetCallback, | ||
ThemeSheet, | ||
} from '../../types'; | ||
@@ -24,6 +28,4 @@ | ||
cache: { [styleName: string]: TransformedStylesMap } = {}; | ||
cache: WeakMap<StyleDeclaration[], ClassName> = new WeakMap(); | ||
native: boolean = false; | ||
options: AestheticOptions = { | ||
@@ -33,3 +35,3 @@ defaultTheme: '', | ||
pure: false, | ||
stylesPropName: 'classNames', | ||
stylesPropName: 'styles', | ||
themePropName: 'theme', | ||
@@ -40,5 +42,5 @@ }; | ||
styles: { [styleName: string]: StyleDeclarationOrCallback } = {}; | ||
styles: { [styleName: string]: StyleSheet | StyleSheetCallback } = {}; | ||
themes: { [themeName: string]: CSSStyle } = {}; | ||
themes: { [themeName: string]: ThemeSheet } = {}; | ||
@@ -55,27 +57,11 @@ constructor(adapter: Adapter, options?: Object = {}) { | ||
/** | ||
* Register a theme by extending and merging with a previously defined theme. | ||
*/ | ||
extendTheme( | ||
parentThemeName: string, | ||
themeName: string, | ||
theme?: CSSStyle = {}, | ||
globals?: StyleDeclarationMap = {}, | ||
): this { | ||
return this.registerTheme( | ||
themeName, | ||
deepMerge({}, this.getTheme(parentThemeName), theme), | ||
globals, | ||
); | ||
} | ||
/** | ||
* Extract the defined style declarations. If the declaratin is a function, | ||
* execute it while passing the current theme and previous inherited styles. | ||
* execute it while passing the current theme and React props. | ||
*/ | ||
getStyles(styleName: string, themeName?: string = ''): StyleDeclarationMap { | ||
createStyleSheet(styleName: string, themeName?: string = '', props?: Object = {}): StyleSheet { | ||
const parentStyleName = this.parents[styleName]; | ||
const declarations = this.styles[styleName]; | ||
let styleSheet = this.styles[styleName]; | ||
if (__DEV__) { | ||
if (!declarations) { | ||
if (!styleSheet) { | ||
throw new Error(`Styles do not exist for "${styleName}".`); | ||
@@ -85,9 +71,32 @@ } | ||
if (typeof declarations !== 'function') { | ||
return declarations; | ||
// Extract styleSheet from callback | ||
if (typeof styleSheet === 'function') { | ||
styleSheet = styleSheet(themeName ? this.getTheme(themeName) : {}, props); | ||
} | ||
return declarations( | ||
themeName ? this.getTheme(themeName) : {}, | ||
parentStyleName ? this.getStyles(parentStyleName, themeName) : {}, | ||
// Merge from parent | ||
if (parentStyleName) { | ||
styleSheet = deepMerge( | ||
{}, | ||
this.createStyleSheet(parentStyleName, themeName, props), | ||
styleSheet, | ||
); | ||
} | ||
return this.adapter.create(styleSheet); | ||
} | ||
/** | ||
* Register a theme by extending and merging with a previously defined theme. | ||
*/ | ||
extendTheme( | ||
parentThemeName: string, | ||
themeName: string, | ||
theme?: ThemeSheet = {}, | ||
globals?: StyleSheet = {}, | ||
): this { | ||
return this.registerTheme( | ||
themeName, | ||
deepMerge({}, this.getTheme(parentThemeName), theme), | ||
globals, | ||
); | ||
@@ -99,3 +108,3 @@ } | ||
*/ | ||
getTheme(themeName?: string = ''): CSSStyle { | ||
getTheme(themeName?: string = ''): ThemeSheet { | ||
const { defaultTheme } = this.options; | ||
@@ -123,4 +132,4 @@ | ||
themeName: string, | ||
theme?: CSSStyle = {}, | ||
globals?: StyleDeclarationMap = {}, | ||
theme?: ThemeSheet = {}, | ||
globals?: StyleSheet = {}, | ||
): this { | ||
@@ -142,5 +151,8 @@ if (__DEV__) { | ||
// Transform the global styles | ||
this.adapter.transform(':root', globals); | ||
// Create global styles | ||
const globalStyleSheet = this.adapter.create(globals); | ||
// $FlowIgnore | ||
this.transformStyles(Object.values(globalStyleSheet)); | ||
return this; | ||
@@ -154,3 +166,2 @@ } | ||
if (adapter instanceof Adapter || (adapter && typeof adapter.transform === 'function')) { | ||
adapter.native = this.native; // eslint-disable-line | ||
this.adapter = adapter; | ||
@@ -170,3 +181,3 @@ | ||
styleName: string, | ||
declarations: StyleDeclarationOrCallback, | ||
styleSheet: StyleSheet | StyleSheetCallback, | ||
extendFrom?: string = '', | ||
@@ -178,3 +189,3 @@ ): this { | ||
} else if (!isObject(declarations) && typeof declarations !== 'function') { | ||
} else if (!isObject(styleSheet) && typeof styleSheet !== 'function') { | ||
throw new TypeError(`Styles defined for "${styleName}" must be an object or function.`); | ||
@@ -184,3 +195,3 @@ } | ||
this.styles[styleName] = declarations; | ||
this.styles[styleName] = styleSheet; | ||
@@ -204,58 +215,48 @@ if (extendFrom) { | ||
/** | ||
* Execute the adapter transformer on the set of style declarations for the | ||
* defined component. Optionally support a custom theme. | ||
* Execute the adapter transformer on the list of style declarations. | ||
*/ | ||
transformStyles(styleName: string, themeName?: string): TransformedStylesMap { | ||
const fallbackThemeName = themeName || this.options.defaultTheme || ''; | ||
const cacheKey = `${styleName}:${fallbackThemeName}`; | ||
if (this.cache[cacheKey]) { | ||
return this.cache[cacheKey]; | ||
transformStyles(styles: StyleDeclaration[]): ClassName { | ||
if (this.cache.has(styles)) { | ||
// $FlowIgnore We check return swith has() | ||
return this.cache.get(styles); | ||
} | ||
const declarations = this.getStyles(styleName, fallbackThemeName); | ||
const toTransform = {}; | ||
const output = {}; | ||
let setCount = 0; | ||
const classNames = []; | ||
const toTransform = []; | ||
// Separate style objects from class names | ||
Object.keys(declarations).forEach((setName: string) => { | ||
if (typeof declarations[setName] === 'string') { | ||
output[setName] = this.native ? {} : declarations[setName]; | ||
} else { | ||
toTransform[setName] = declarations[setName]; | ||
setCount += 1; | ||
styles.forEach((style) => { | ||
// Empty value or failed condition | ||
if (!style) { | ||
return; // eslint-disable-line | ||
// Acceptable class names | ||
} else if (typeof style === 'string' || typeof style === 'number') { | ||
classNames.push(...String(style).split(' ').map(s => stripClassPrefix(s).trim())); | ||
// Style objects | ||
} else if (isObject(style)) { | ||
toTransform.push(style); | ||
} else if (__DEV__) { | ||
throw new Error('Unsupported style type to transform.'); | ||
} | ||
}); | ||
// Transform the styles into a map of class names | ||
if (setCount > 0) { | ||
const transformedOutput = this.adapter.transform(styleName, toTransform); | ||
Object.keys(transformedOutput).forEach((setName: string) => { | ||
output[setName] = this.validateTransform(styleName, setName, transformedOutput[setName]); | ||
}); | ||
if (toTransform.length > 0) { | ||
classNames.push(this.adapter.transform(...toTransform)); | ||
} | ||
// Cache the values | ||
this.cache[cacheKey] = output; | ||
const className = classNames.join(' ').trim(); | ||
return output; | ||
this.cache.set(styles, className); | ||
return className; | ||
} | ||
/** | ||
* Validate the object returned contains valid strings. | ||
* Utility method for wrapping a component with a styles HOC. | ||
*/ | ||
validateTransform(styleName: string, setName: string, value: StyleDeclaration): StyleDeclaration { | ||
if (__DEV__) { | ||
if (typeof value !== 'string') { | ||
throw new TypeError( | ||
`\`${this.adapter.constructor.name}\` must return a mapping of CSS class names. ` + | ||
`"${styleName}@${setName}" is not a valid string.`, | ||
); | ||
} | ||
} | ||
return value; | ||
withStyles(styleSheet: StyleSheet | StyleSheetCallback, options?: HOCOptions = {}): HOCWrapper { | ||
return withStyles(this, styleSheet, options); | ||
} | ||
} |
@@ -9,3 +9,3 @@ /** | ||
import type { StyleDeclarationMap, TransformedStylesMap } from '../../types'; | ||
import type { ClassName, StyleDeclaration } from '../../types'; | ||
@@ -15,18 +15,15 @@ export default class ClassNameAdapter extends Adapter { | ||
transform(styleName: string, declarations: StyleDeclarationMap): TransformedStylesMap { | ||
const classNames = {}; | ||
transform(...styles: StyleDeclaration[]): ClassName { | ||
const classNames = []; | ||
Object.keys(declarations).forEach((setName: string) => { | ||
if (typeof declarations[setName] === 'string') { | ||
classNames[setName] = declarations[setName]; | ||
styles.forEach((style) => { | ||
if (style && typeof style === 'string') { | ||
classNames.push(style); | ||
} else if (__DEV__) { | ||
throw new TypeError( | ||
'`ClassNameAdapter` expects valid CSS class names; ' + | ||
`non-string provided for "${setName}".`, | ||
); | ||
throw new TypeError(`${this.constructor.name} expects valid CSS class names.`); | ||
} | ||
}); | ||
return classNames; | ||
return classNames.join(' '); | ||
} | ||
} |
@@ -8,12 +8,16 @@ /** | ||
import Aesthetic from './Aesthetic'; | ||
import style from './style'; | ||
import type { | ||
StyleDeclarationOrCallback, | ||
HOCComponent, | ||
ClassName, | ||
HOCOptions, | ||
WrappedComponent, | ||
HOCWrapper, | ||
StyleDeclaration, | ||
StyleSheet, | ||
StyleSheetCallback, | ||
} from '../../types'; | ||
export default function createStyler(aesthetic: Aesthetic): * { | ||
export default function createStyler(aesthetic: Aesthetic): { | ||
style: (styleSheet: StyleSheet | StyleSheetCallback, options?: HOCOptions) => HOCWrapper, | ||
transform: (...styles: StyleDeclaration[]) => ClassName, | ||
} { | ||
if (__DEV__) { | ||
@@ -25,8 +29,10 @@ if (!(aesthetic instanceof Aesthetic)) { | ||
return function styler( | ||
defaultStyles?: StyleDeclarationOrCallback = {}, | ||
options?: HOCOptions = {}, | ||
): (WrappedComponent) => HOCComponent { | ||
return style(aesthetic, defaultStyles, options); | ||
return { | ||
style(styleSheet: StyleSheet | StyleSheetCallback, options?: HOCOptions = {}): HOCWrapper { | ||
return aesthetic.withStyles(styleSheet, options); | ||
}, | ||
transform(...styles: StyleDeclaration[]): ClassName { | ||
return aesthetic.transformStyles(styles); | ||
}, | ||
}; | ||
} |
/** | ||
* @copyright 2017, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
* @flow | ||
*/ | ||
@@ -12,12 +13,6 @@ | ||
import createStyler from './createStyler'; | ||
import classes from './classes'; | ||
export const ClassNamesPropType = PropTypes.objectOf(PropTypes.string); | ||
export const StylesPropType = PropTypes.objectOf(PropTypes.object); | ||
export const ClassOrStylesPropType = PropTypes.oneOfType([ | ||
PropTypes.objectOf(PropTypes.string), | ||
PropTypes.objectOf(PropTypes.object), | ||
]); | ||
export { Adapter, ClassNameAdapter, ThemeProvider, createStyler, classes }; | ||
export { Adapter, ClassNameAdapter, ThemeProvider, createStyler }; | ||
export default Aesthetic; |
115
src/style.js
@@ -7,4 +7,2 @@ /** | ||
/* eslint-disable react/require-default-props */ | ||
import React from 'react'; | ||
@@ -16,18 +14,19 @@ import PropTypes from 'prop-types'; | ||
import type { | ||
TransformedStylesMap, | ||
StyleDeclarationOrCallback, | ||
WrappedComponent, | ||
HOCComponent, | ||
HOCOptions, | ||
HOCWrappedComponent, | ||
HOCWrapper, | ||
StyleSheet, | ||
StyleSheetCallback, | ||
ThemeSheet, | ||
} from '../../types'; | ||
type StyleProps = { | ||
theme?: string, | ||
[key: string]: *, | ||
themeName: string, | ||
}; | ||
type StyleState = { | ||
classNames?: TransformedStylesMap, | ||
theme?: string, | ||
[key: string]: *, | ||
styles?: StyleSheet, | ||
theme?: ThemeSheet, | ||
themeName: string, | ||
}; | ||
@@ -40,12 +39,11 @@ | ||
aesthetic: Aesthetic, | ||
styles: StyleDeclarationOrCallback = {}, | ||
options: HOCOptions = {}, | ||
): (WrappedComponent) => HOCComponent { | ||
return function wrapStyles(Component: WrappedComponent): HOCComponent { | ||
styleSheet: StyleSheet | StyleSheetCallback = {}, | ||
options?: HOCOptions = {}, | ||
): HOCWrapper { | ||
return function wrapStyles(Component: HOCWrappedComponent): HOCComponent { | ||
let styleName = options.styleName || Component.displayName || Component.name; | ||
/* | ||
* Function/constructor name aren't always available when code is minified, | ||
* so only use it in development. | ||
*/ | ||
// Function/constructor name aren't always available when code is minified, | ||
// so only use it in development. | ||
/* istanbul ignore else */ | ||
if (__DEV__) { | ||
@@ -69,7 +67,5 @@ if (!(aesthetic instanceof Aesthetic)) { | ||
/* | ||
* When in production, we should generate a random string to use as the style name. | ||
* If we don't do this, any minifiers that mangle function names would break | ||
* Aesthetic's caching layer. | ||
*/ | ||
// When in production, we should generate a random string to use as the style name. | ||
// If we don't do this, any minifiers that mangle function names would break | ||
// Aesthetic's caching layer. | ||
} else { | ||
@@ -90,5 +86,5 @@ instanceID += 1; | ||
// Set base styles | ||
aesthetic.setStyles(styleName, styles, extendFrom); | ||
aesthetic.setStyles(styleName, styleSheet, extendFrom); | ||
// $FlowIgnore | ||
// $FlowIgnore Silence polymorphic errors | ||
class StyledComponent extends ParentComponent<StyleProps, StyleState> { | ||
@@ -99,15 +95,19 @@ static displayName: ?string = `Aesthetic(${styleName})`; | ||
static wrappedComponent: WrappedComponent = Component; | ||
static WrappedComponent: HOCWrappedComponent = Component; | ||
static propTypes = { | ||
[themePropName]: PropTypes.string, | ||
static contextTypes = { | ||
themeName: PropTypes.string, | ||
}; | ||
static contextTypes = { | ||
static propTypes = { | ||
themeName: PropTypes.string, | ||
}; | ||
static defaultProps = { | ||
themeName: '', | ||
}; | ||
// Allow consumers to customize styles | ||
static extendStyles( | ||
customStyles: StyleDeclarationOrCallback, | ||
customStyleSheet?: StyleSheet | StyleSheetCallback = {}, | ||
extendOptions?: HOCOptions = {}, | ||
@@ -121,29 +121,26 @@ ): HOCComponent { | ||
return style( | ||
aesthetic, | ||
customStyles, | ||
{ | ||
...options, | ||
...extendOptions, | ||
extendFrom: styleName, | ||
}, | ||
)(Component); | ||
return style(aesthetic, customStyleSheet, { | ||
...options, | ||
...extendOptions, | ||
extendFrom: styleName, | ||
})(Component); | ||
} | ||
// Start transforming styles before we mount | ||
state = { | ||
firstMount: true, | ||
[stylesPropName]: {}, | ||
themeName: '', | ||
[themePropName]: {}, | ||
}; | ||
componentWillMount() { | ||
this.transformStyles(this.getTheme(this.props)); | ||
this.transformStyles(this.props); | ||
} | ||
// Re-transform if the theme changes | ||
componentWillReceiveProps(nextProps: StyleProps) { | ||
const theme = this.getTheme(nextProps); | ||
if (theme !== this.state[themePropName]) { | ||
this.transformStyles(theme); | ||
} | ||
this.transformStyles(nextProps); | ||
} | ||
getTheme(props: StyleProps): string { | ||
return props[themePropName] || | ||
getThemeName(props: StyleProps): string { | ||
return props.themeName || | ||
this.context.themeName || | ||
@@ -154,13 +151,17 @@ aesthetic.options.defaultTheme || | ||
transformStyles(theme: string) { | ||
this.setState({ | ||
[stylesPropName]: aesthetic.transformStyles(styleName, theme), | ||
[themePropName]: theme, | ||
}); | ||
transformStyles(props: Object) { | ||
const themeName = this.getThemeName(props); | ||
if (this.state.firstMount || themeName !== this.state.themeName) { | ||
this.setState({ | ||
firstMount: false, | ||
[stylesPropName]: aesthetic.createStyleSheet(styleName, themeName, props), | ||
themeName, | ||
[themePropName]: themeName ? aesthetic.getTheme(themeName) : {}, | ||
}); | ||
} | ||
} | ||
render(): React$Node { | ||
return ( | ||
<Component {...this.props} {...this.state} /> | ||
); | ||
return <Component {...this.props} {...this.state} />; | ||
} | ||
@@ -167,0 +168,0 @@ } |
@@ -7,98 +7,280 @@ /** | ||
import { isObject } from 'aesthetic-utils'; | ||
/* eslint-disable no-param-reassign */ | ||
import formatFontFace from './helpers/formatFontFace'; | ||
import isObject from './helpers/isObject'; | ||
import toArray from './helpers/toArray'; | ||
import type { | ||
StyleDeclarationMap, | ||
CSSStyle, | ||
AtRuleSet, | ||
AtRuleMap, | ||
AtRuleCache, | ||
AtRule, | ||
EventCallback, | ||
FallbackMap, | ||
Style, | ||
StyleBlock, | ||
StyleDeclaration, | ||
StyleSheet, | ||
} from '../../types'; | ||
export const LOCAL = 'local'; | ||
export const GLOBAL = 'global'; | ||
export const AT_RULES = ['@fallbacks', '@font-face', '@keyframes', '@media']; | ||
export const GLOBAL_RULES: AtRule[] = [ | ||
'@charset', | ||
'@font-face', | ||
'@global', | ||
'@import', | ||
'@keyframes', | ||
'@namespace', | ||
'@page', | ||
'@viewport', | ||
]; | ||
export const LOCAL_RULES: AtRule[] = [ | ||
'@fallbacks', | ||
'@media', | ||
'@supports', | ||
]; | ||
export default class UnifiedSyntax { | ||
events: { [eventName: string]: EventCallback } = {}; | ||
// Local | ||
fallbacks: FallbackMap = {}; | ||
fontFaces: { [fontFamily: string]: StyleBlock[] } = {}; | ||
// Global | ||
fontFaces: AtRuleMap = {}; | ||
fontFacesCache: { [fontFamily: string]: string } = {}; | ||
fontFaceNames: AtRuleCache = {}; | ||
keyframes: { [animationName: string]: StyleBlock } = {}; | ||
// Global | ||
keyframes: AtRuleMap = {}; | ||
keyframesCache: { [animationName: string]: string } = {}; | ||
keyframeNames: AtRuleCache = {}; | ||
constructor() { | ||
this | ||
.on('property', this.handleProperty) | ||
.on('@charset', this.handleCharset) | ||
.on('@fallbacks', this.handleFallbacks) | ||
.on('@font-face', this.handleFontFace) | ||
.on('@global', this.handleGlobal) | ||
.on('@import', this.handleImport) | ||
.on('@keyframes', this.handleKeyframes) | ||
.on('@media', this.handleMedia) | ||
.on('@namespace', this.handleNamespace) | ||
.on('@page', this.handlePage) | ||
.on('@supports', this.handleSupports) | ||
.on('@viewport', this.handleViewport); | ||
} | ||
// Local | ||
mediaQueries: AtRuleSet = {}; | ||
/** | ||
* Check that a value is a style declaration block. | ||
*/ | ||
checkBlock(value: *): Object { | ||
if (isObject(value)) { | ||
return value; | ||
} | ||
static LOCAL: string = LOCAL; | ||
throw new Error('Must be a style declaration.'); | ||
} | ||
static GLOBAL: string = GLOBAL; | ||
/** | ||
* Convert the unified syntax to adapter specific syntax | ||
* by extracting at-rules and applying conversions at each level. | ||
* Convert a mapping of style declarations to their native syntax. | ||
*/ | ||
convert(declarations: StyleDeclarationMap): StyleDeclarationMap { | ||
this.resetLocalCache(); | ||
this.emit('converting'); | ||
convert(styleSheet: StyleSheet): StyleSheet { | ||
const prevStyleSheet = { ...styleSheet }; | ||
const nextStyleSheet = {}; | ||
const adaptedDeclarations = { ...declarations }; | ||
// Extract global at-rules first | ||
// eslint-disable-next-line complexity | ||
GLOBAL_RULES.forEach((rule) => { | ||
if (!prevStyleSheet[rule]) { | ||
delete prevStyleSheet[rule]; | ||
// Extract at-rules first so that they are available for properties | ||
AT_RULES.forEach((atRule: string) => { | ||
if (atRule in adaptedDeclarations) { | ||
this.extract(':root', atRule, adaptedDeclarations[atRule], GLOBAL); | ||
return; | ||
} | ||
delete adaptedDeclarations[atRule]; | ||
switch (rule) { | ||
case '@charset': | ||
case '@import': | ||
case '@namespace': { | ||
const path = prevStyleSheet[rule]; | ||
if (typeof path === 'string') { | ||
this.emit(rule, [nextStyleSheet, path]); | ||
} else if (__DEV__) { | ||
throw new Error(`${rule} value must be a string.`); | ||
} | ||
break; | ||
} | ||
case '@font-face': { | ||
const faces = prevStyleSheet['@font-face']; | ||
Object.keys(this.checkBlock(faces)).forEach((fontFamily) => { | ||
if (__DEV__) { | ||
if (this.fontFaces[fontFamily]) { | ||
throw new Error(`@font-face "${fontFamily}" already exists.`); | ||
} | ||
} | ||
// $FlowIgnore | ||
this.fontFaces[fontFamily] = toArray(faces[fontFamily]) | ||
.map(font => ({ | ||
...font, | ||
fontFamily, | ||
})); | ||
this.emit(rule, [nextStyleSheet, this.fontFaces[fontFamily], fontFamily]); | ||
}); | ||
break; | ||
} | ||
case '@global': { | ||
const globals = prevStyleSheet['@global']; | ||
Object.keys(this.checkBlock(globals)).forEach((selector) => { | ||
if (isObject(globals[selector])) { | ||
this.emit(rule, [ | ||
nextStyleSheet, | ||
this.convertDeclaration(selector, globals[selector]), | ||
selector, | ||
]); | ||
} else if (__DEV__) { | ||
throw new Error('Invalid @global selector style declaration.'); | ||
} | ||
}); | ||
break; | ||
} | ||
case '@keyframes': { | ||
const frames = prevStyleSheet['@keyframes']; | ||
Object.keys(this.checkBlock(frames)).forEach((animationName) => { | ||
if (__DEV__) { | ||
if (this.keyframes[animationName]) { | ||
throw new Error(`@keyframes "${animationName}" already exists.`); | ||
} | ||
} | ||
this.keyframes[animationName] = this.checkBlock(frames[animationName]); | ||
this.emit(rule, [nextStyleSheet, this.keyframes[animationName], animationName]); | ||
}); | ||
break; | ||
} | ||
case '@page': | ||
case '@viewport': { | ||
const style = prevStyleSheet[rule]; | ||
if (isObject(style)) { | ||
this.emit(rule, [nextStyleSheet, style]); | ||
} else if (__DEV__) { | ||
throw new Error(`${rule} must be a style object.`); | ||
} | ||
break; | ||
} | ||
/* istanbul ignore next */ | ||
default: | ||
break; | ||
} | ||
delete prevStyleSheet[rule]; | ||
}); | ||
// Apply conversion to properties | ||
Object.keys(adaptedDeclarations).forEach((setName: string) => { | ||
const declaration = declarations[setName]; | ||
// Convert declarations last | ||
Object.keys(prevStyleSheet).forEach((selector) => { | ||
const declaration = prevStyleSheet[selector]; | ||
if (typeof declaration !== 'string') { | ||
adaptedDeclarations[setName] = this.convertDeclaration(setName, declaration); | ||
delete prevStyleSheet[selector]; | ||
if (!declaration) { | ||
return; | ||
} | ||
// At-rule | ||
if (selector.charAt(0) === '@') { | ||
if (__DEV__) { | ||
throw new SyntaxError(`Unsupported global at-rule "${selector}".`); | ||
} | ||
// Class name | ||
} else if (typeof declaration === 'string') { | ||
nextStyleSheet[selector] = declaration; | ||
// Style object | ||
} else if (isObject(declaration)) { | ||
nextStyleSheet[selector] = this.convertDeclaration(selector, declaration); | ||
} else if (__DEV__) { | ||
throw new Error(`Invalid style declaration for "${selector}".`); | ||
} | ||
}); | ||
this.emit('converted'); | ||
return adaptedDeclarations; | ||
return nextStyleSheet; | ||
} | ||
/** | ||
* Convert an object of properties by extracting local at-rules | ||
* and parsing fallbacks. | ||
* Convert a style declaration including local at-rules and properties. | ||
*/ | ||
convertDeclaration(setName: string, properties: CSSStyle): CSSStyle { | ||
const nextProperties = { ...properties }; | ||
convertDeclaration(selector: string, declaration: StyleDeclaration): StyleDeclaration { | ||
const prevDeclaration = { ...declaration }; | ||
const nextDeclaration = {}; | ||
AT_RULES.forEach((atRule: string) => { | ||
if (atRule in nextProperties) { | ||
this.extract(setName, atRule, nextProperties[atRule], LOCAL); | ||
// Convert properties first | ||
Object.keys(prevDeclaration).forEach((key) => { | ||
if (key.charAt(0) !== '@') { | ||
this.emit('property', [nextDeclaration, prevDeclaration[key], key]); | ||
delete nextProperties[atRule]; | ||
delete prevDeclaration[key]; | ||
} | ||
}); | ||
this.emit('declaration', [setName, nextProperties]); | ||
// Extract local at-rules first | ||
LOCAL_RULES.forEach((rule) => { | ||
const style = prevDeclaration[rule]; | ||
return nextProperties; | ||
delete prevDeclaration[rule]; | ||
if (!style || !isObject(style)) { | ||
return; | ||
} | ||
if (rule === '@fallbacks') { | ||
Object.keys(style).forEach((property) => { | ||
this.emit(rule, [nextDeclaration, toArray(style[property]), property]); | ||
}); | ||
} else if (rule === '@media' || rule === '@supports') { | ||
Object.keys(style).forEach((condition) => { | ||
if (isObject(style[condition])) { | ||
this.emit(rule, [nextDeclaration, style[condition], condition]); | ||
} else if (__DEV__) { | ||
throw new Error(`${rule} ${condition} must be a mapping of conditions to style objects.`); | ||
} | ||
}); | ||
} | ||
}); | ||
// Error for unknown at-rules | ||
if (__DEV__) { | ||
Object.keys(prevDeclaration).forEach((key) => { | ||
throw new SyntaxError(`Unsupported local at-rule "${key}".`); | ||
}); | ||
} | ||
return nextDeclaration; | ||
} | ||
/** | ||
* Create a noop function that throws an error for unsupported features. | ||
*/ | ||
createUnsupportedHandler(rule: AtRule): () => void { | ||
return () => { | ||
throw new Error(`Adapter does not support "${rule}".`); | ||
}; | ||
} | ||
/** | ||
* Execute the defined event listener with the arguments. | ||
*/ | ||
emit(eventName: string, args: *[] = []): this { | ||
emit(eventName: string, args: *[]): this { | ||
if (this.events[eventName]) { | ||
@@ -112,114 +294,119 @@ this.events[eventName](...args); | ||
/** | ||
* Extract at-rules and parser rules from both the global and local levels. | ||
* Handle @charset. | ||
*/ | ||
extract(setName: string, atRule: string, rules: *, fromScope: string) { | ||
if (__DEV__) { | ||
if (!isObject(rules)) { | ||
throw new SyntaxError(`At-rule declaration "${atRule}" must be an object.`); | ||
} | ||
} | ||
handleCharset(styleSheet: StyleSheet, style: string) { | ||
styleSheet['@charset'] = style; | ||
} | ||
switch (atRule) { | ||
case '@fallbacks': | ||
this.extractFallbacks(setName, (rules: CSSStyle), fromScope); | ||
break; | ||
/** | ||
* Handle fallback properties. | ||
*/ | ||
handleFallbacks(declaration: StyleDeclaration, style: Style[], property: string) { | ||
declaration[property] = [declaration[property], ...style].filter(Boolean); | ||
} | ||
case '@font-face': | ||
this.extractFontFaces(setName, (rules: AtRuleMap), fromScope); | ||
break; | ||
/** | ||
* Handle @font-face. | ||
*/ | ||
handleFontFace(styleSheet: StyleSheet, style: StyleBlock[], fontFamily: string) { | ||
if (Array.isArray(styleSheet['@font-face'])) { | ||
styleSheet['@font-face'].push(...style); | ||
} else { | ||
styleSheet['@font-face'] = style; | ||
} | ||
} | ||
case '@keyframes': | ||
this.extractKeyframes(setName, (rules: AtRuleMap), fromScope); | ||
break; | ||
/** | ||
* Handle global styles (like body, html, etc). | ||
*/ | ||
handleGlobal(styleSheet: StyleSheet, declaration: StyleDeclaration, selector: string) { | ||
// Do nothing | ||
} | ||
case '@media': | ||
this.extractMediaQueries(setName, (rules: AtRuleMap), fromScope); | ||
break; | ||
/** | ||
* Handle @namespace. | ||
*/ | ||
handleImport(styleSheet: StyleSheet, style: string) { | ||
styleSheet['@import'] = style; | ||
} | ||
default: { | ||
if (__DEV__) { | ||
throw new SyntaxError(`Unsupported at-rule "${atRule}".`); | ||
} | ||
} | ||
} | ||
/** | ||
* Handle @keyframes. | ||
*/ | ||
handleKeyframes(styleSheet: StyleSheet, style: StyleBlock, animationName: string) { | ||
styleSheet[`@keyframes ${animationName}`] = style; | ||
} | ||
/** | ||
* Extract property fallbacks. | ||
* Handle @media. | ||
*/ | ||
extractFallbacks(setName: string, properties: CSSStyle, fromScope: string) { | ||
if (__DEV__) { | ||
if (fromScope === GLOBAL) { | ||
throw new SyntaxError('Property fallbacks must be defined locally to an element.'); | ||
} | ||
} | ||
handleMedia(declaration: StyleDeclaration, style: StyleBlock, condition: string) { | ||
declaration[`@media ${condition}`] = style; | ||
} | ||
this.fallbacks[setName] = properties; | ||
/** | ||
* Handle @namespace. | ||
*/ | ||
handleNamespace(styleSheet: StyleSheet, style: string) { | ||
styleSheet['@namespace'] = style; | ||
} | ||
this.emit('fallback', [setName, properties]); | ||
/** | ||
* Handle @page. | ||
*/ | ||
handlePage(styleSheet: StyleSheet, style: StyleBlock) { | ||
styleSheet['@page'] = style; | ||
} | ||
/** | ||
* Extract font face at-rules. | ||
* Handle CSS properties. | ||
*/ | ||
extractFontFaces(setName: string, rules: AtRuleMap, fromScope: string) { | ||
if (__DEV__) { | ||
if (fromScope === LOCAL) { | ||
throw new SyntaxError('Font faces must be declared in the global scope.'); | ||
} | ||
} | ||
handleProperty = (declaration: StyleDeclaration, style: Style, property: string) => { | ||
declaration[property] = style; | ||
}; | ||
Object.keys(rules).forEach((name: string) => { | ||
// Use the family name so raw CSS can reference it | ||
const familyName = String(rules[name].fontFamily); | ||
/** | ||
* Handle @supports. | ||
*/ | ||
handleSupports(declaration: StyleDeclaration, style: StyleBlock, condition: string) { | ||
declaration[`@supports ${condition}`] = style; | ||
} | ||
if (this.fontFaces[familyName]) { | ||
if (__DEV__) { | ||
throw new TypeError(`Font face "${familyName}" has already been defined.`); | ||
} | ||
} else { | ||
this.fontFaces[familyName] = rules[name]; | ||
} | ||
this.emit('fontFace', [setName, familyName, rules[name]]); | ||
}); | ||
/** | ||
* Handle @viewport. | ||
*/ | ||
handleViewport(styleSheet: StyleSheet, style: StyleBlock) { | ||
styleSheet['@viewport'] = style; | ||
} | ||
/** | ||
* Extract animation keyframes at-rules. | ||
* Replace a `fontFamily` property with font face objects of the same name. | ||
*/ | ||
extractKeyframes(setName: string, rules: AtRuleMap, fromScope: string) { | ||
if (__DEV__) { | ||
if (fromScope === LOCAL) { | ||
throw new SyntaxError('Animation keyframes must be declared in the global scope.'); | ||
} | ||
} | ||
injectFontFaces(value: Style, cache: Object): Style[] { | ||
const fontFaces = []; | ||
Object.keys(rules).forEach((name: string) => { | ||
if (this.keyframes[name]) { | ||
if (__DEV__) { | ||
throw new TypeError(`Animation keyframe "${name}" has already been defined.`); | ||
} | ||
String(value).split(',').forEach((name) => { | ||
const familyName = name.trim(); | ||
const fonts = cache[familyName]; | ||
if (Array.isArray(fonts)) { | ||
fonts.forEach((font) => { | ||
fontFaces.push(formatFontFace(font)); | ||
}); | ||
} else { | ||
this.keyframes[name] = rules[name]; | ||
fontFaces.push(familyName); | ||
} | ||
}); | ||
this.emit('keyframe', [setName, name, rules[name]]); | ||
}); | ||
return fontFaces; | ||
} | ||
/** | ||
* Extract media query at-rules. | ||
* Replace a `animationName` property with keyframe objects of the same name. | ||
*/ | ||
extractMediaQueries(setName: string, rules: AtRuleMap, fromScope: string) { | ||
if (__DEV__) { | ||
if (fromScope === GLOBAL) { | ||
throw new SyntaxError('Media queries must be defined locally to an element.'); | ||
} | ||
} | ||
injectKeyframes(value: Style, cache: Object): Style[] { | ||
return String(value).split(',').map((name) => { | ||
const animationName = name.trim(); | ||
this.mediaQueries[setName] = rules; | ||
Object.keys(rules).forEach((query: string) => { | ||
this.emit('mediaQuery', [setName, query, rules[query]]); | ||
return cache[animationName] || animationName; | ||
}); | ||
@@ -245,18 +432,2 @@ } | ||
} | ||
/** | ||
* Reset cached global at-rules. | ||
*/ | ||
resetGlobalCache() { | ||
this.fontFaces = {}; | ||
this.keyframes = {}; | ||
} | ||
/** | ||
* Reset cached local at-rules. | ||
*/ | ||
resetLocalCache() { | ||
this.fallbacks = {}; | ||
this.mediaQueries = {}; | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
131533
5
45
2607
789
2
43
- Removedaesthetic-utils@^1.6.2
- Removedaesthetic-utils@1.6.2(transitive)