afrododi
Version:
Framework-agnostic async-compatible CSS-in-JS with support for server-side rendering, browser prefixing, and minimum CSS generation
1,229 lines (1,068 loc) • 52.6 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var stringHash = _interopDefault(require('string-hash'));
var calc = _interopDefault(require('inline-style-prefixer/static/plugins/calc'));
var crossFade = _interopDefault(require('inline-style-prefixer/static/plugins/crossFade'));
var cursor = _interopDefault(require('inline-style-prefixer/static/plugins/cursor'));
var filter = _interopDefault(require('inline-style-prefixer/static/plugins/filter'));
var flex = _interopDefault(require('inline-style-prefixer/static/plugins/flex'));
var flexboxIE = _interopDefault(require('inline-style-prefixer/static/plugins/flexboxIE'));
var flexboxOld = _interopDefault(require('inline-style-prefixer/static/plugins/flexboxOld'));
var gradient = _interopDefault(require('inline-style-prefixer/static/plugins/gradient'));
var imageSet = _interopDefault(require('inline-style-prefixer/static/plugins/imageSet'));
var position = _interopDefault(require('inline-style-prefixer/static/plugins/position'));
var sizing = _interopDefault(require('inline-style-prefixer/static/plugins/sizing'));
var transition = _interopDefault(require('inline-style-prefixer/static/plugins/transition'));
var createPrefixer = _interopDefault(require('inline-style-prefixer/static/createPrefixer'));
var asap = _interopDefault(require('asap'));
var React = _interopDefault(require('react'));
var hoistNonReactStatics = _interopDefault(require('hoist-non-react-statics'));
/* ::
type ObjectMap = { [id:string]: any };
*/
var UPPERCASE_RE = /([A-Z])/g;
var UPPERCASE_RE_TO_KEBAB = function UPPERCASE_RE_TO_KEBAB(match /* : string */) {
return (/* : string */'-' + String(match.toLowerCase())
);
};
var kebabifyStyleName = function kebabifyStyleName(string /* : string */) /* : string */{
var result = string.replace(UPPERCASE_RE, UPPERCASE_RE_TO_KEBAB);
if (result[0] === 'm' && result[1] === 's' && result[2] === '-') {
return '-' + String(result);
}
return result;
};
/**
* CSS properties which accept numbers but are not in units of "px".
* Taken from React's CSSProperty.js
*/
var isUnitlessNumber = {
animationIterationCount: true,
borderImageOutset: true,
borderImageSlice: true,
borderImageWidth: true,
boxFlex: true,
boxFlexGroup: true,
boxOrdinalGroup: true,
columnCount: true,
flex: true,
flexGrow: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
flexOrder: true,
gridRow: true,
gridColumn: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
opacity: true,
order: true,
orphans: true,
tabSize: true,
widows: true,
zIndex: true,
zoom: true,
// SVG-related properties
fillOpacity: true,
floodOpacity: true,
stopOpacity: true,
strokeDasharray: true,
strokeDashoffset: true,
strokeMiterlimit: true,
strokeOpacity: true,
strokeWidth: true
};
/**
* Taken from React's CSSProperty.js
*
* @param {string} prefix vendor-specific prefix, eg: Webkit
* @param {string} key style name, eg: transitionDuration
* @return {string} style name prefixed with `prefix`, properly camelCased, eg:
* WebkitTransitionDuration
*/
function prefixKey(prefix, key) {
return prefix + key.charAt(0).toUpperCase() + key.substring(1);
}
/**
* Support style names that may come passed in prefixed by adding permutations
* of vendor prefixes.
* Taken from React's CSSProperty.js
*/
var prefixes = ['Webkit', 'ms', 'Moz', 'O'];
// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
// infinite loop, because it iterates over the newly added props too.
// Taken from React's CSSProperty.js
Object.keys(isUnitlessNumber).forEach(function (prop) {
prefixes.forEach(function (prefix) {
isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
});
});
var stringifyValue = function stringifyValue(key /* : string */
, prop /* : any */
) /* : string */{
if (typeof prop === "number") {
if (isUnitlessNumber[key]) {
return "" + prop;
} else {
return prop + "px";
}
} else {
return '' + prop;
}
};
var stringifyAndImportantifyValue = function stringifyAndImportantifyValue(key /* : string */
, prop /* : any */
) {
return (/* : string */importantify(stringifyValue(key, prop))
);
};
// Turn a string into a hash string of base-36 values (using letters and numbers)
// eslint-disable-next-line no-unused-vars
var hashString = function hashString(string /* : string */, key /* : ?string */) {
return (/* string */stringHash(string).toString(36)
);
};
// Hash a javascript object using JSON.stringify. This is very fast, about 3
// microseconds on my computer for a sample object:
// http://jsperf.com/test-hashfnv32a-hash/5
//
// Note that this uses JSON.stringify to stringify the objects so in order for
// this to produce consistent hashes browsers need to have a consistent
// ordering of objects. Ben Alpert says that Facebook depends on this, so we
// can probably depend on this too.
var hashObject = function hashObject(object /* : ObjectMap */) {
return (/* : string */hashString(JSON.stringify(object))
);
};
// Given a single style value string like the "b" from "a: b;", adds !important
// to generate "b !important".
var importantify = function importantify(string /* : string */) {
return (/* : string */
// Bracket string character access is very fast, and in the default case we
// normally don't expect there to be "!important" at the end of the string
// so we can use this simple check to take an optimized path. If there
// happens to be a "!" in this position, we follow up with a more thorough
// check.
string[string.length - 10] === '!' && string.slice(-11) === ' !important' ? string : String(string) + ' !important'
);
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var MAP_EXISTS = typeof Map !== 'undefined';
var OrderedElements = function () {
/* ::
elements: {[string]: any};
keyOrder: string[];
*/
function OrderedElements() {
_classCallCheck(this, OrderedElements);
this.elements = {};
this.keyOrder = [];
}
_createClass(OrderedElements, [{
key: 'forEach',
value: function () {
function forEach(callback /* : (string, any) => void */) {
for (var i = 0; i < this.keyOrder.length; i++) {
// (value, key) to match Map's API
callback(this.elements[this.keyOrder[i]], this.keyOrder[i]);
}
}
return forEach;
}()
}, {
key: 'set',
value: function () {
function set(key /* : string */, value /* : any */, shouldReorder /* : ?boolean */) {
if (!this.elements.hasOwnProperty(key)) {
this.keyOrder.push(key);
} else if (shouldReorder) {
var index = this.keyOrder.indexOf(key);
this.keyOrder.splice(index, 1);
this.keyOrder.push(key);
}
if (value == null) {
this.elements[key] = value;
return;
}
if (MAP_EXISTS && value instanceof Map || value instanceof OrderedElements) {
// We have found a nested Map, so we need to recurse so that all
// of the nested objects and Maps are merged properly.
var nested = this.elements.hasOwnProperty(key) ? this.elements[key] : new OrderedElements();
value.forEach(function (value, key) {
nested.set(key, value, shouldReorder);
});
this.elements[key] = nested;
return;
}
if (!Array.isArray(value) && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object') {
// We have found a nested object, so we need to recurse so that all
// of the nested objects and Maps are merged properly.
var _nested = this.elements.hasOwnProperty(key) ? this.elements[key] : new OrderedElements();
var keys = Object.keys(value);
for (var i = 0; i < keys.length; i += 1) {
_nested.set(keys[i], value[keys[i]], shouldReorder);
}
this.elements[key] = _nested;
return;
}
this.elements[key] = value;
}
return set;
}()
}, {
key: 'get',
value: function () {
function get(key /* : string */) /* : any */{
return this.elements[key];
}
return get;
}()
}, {
key: 'has',
value: function () {
function has(key /* : string */) /* : boolean */{
return this.elements.hasOwnProperty(key);
}
return has;
}()
}, {
key: 'addStyleType',
value: function () {
function addStyleType(styleType /* : any */) /* : void */{
var _this = this;
if (MAP_EXISTS && styleType instanceof Map || styleType instanceof OrderedElements) {
styleType.forEach(function (value, key) {
_this.set(key, value, true);
});
} else {
var keys = Object.keys(styleType);
for (var i = 0; i < keys.length; i++) {
this.set(keys[i], styleType[keys[i]], true);
}
}
}
return addStyleType;
}()
}]);
return OrderedElements;
}();
var w = ["Webkit"];
var m = ["Moz"];
var ms = ["ms"];
var wm = ["Webkit", "Moz"];
var wms = ["Webkit", "ms"];
var wmms = ["Webkit", "Moz", "ms"];
var staticPrefixData = {
plugins: [calc, crossFade, cursor, filter, flex, flexboxIE, flexboxOld, gradient, imageSet, position, sizing, transition],
prefixMap: { "transform": wms, "transformOrigin": wms, "transformOriginX": wms, "transformOriginY": wms, "backfaceVisibility": w, "perspective": w, "perspectiveOrigin": w, "transformStyle": w, "transformOriginZ": w, "animation": w, "animationDelay": w, "animationDirection": w, "animationFillMode": w, "animationDuration": w, "animationIterationCount": w, "animationName": w, "animationPlayState": w, "animationTimingFunction": w, "appearance": wm, "userSelect": wmms, "fontKerning": w, "textEmphasisPosition": w, "textEmphasis": w, "textEmphasisStyle": w, "textEmphasisColor": w, "boxDecorationBreak": w, "clipPath": w, "maskImage": w, "maskMode": w, "maskRepeat": w, "maskPosition": w, "maskClip": w, "maskOrigin": w, "maskSize": w, "maskComposite": w, "mask": w, "maskBorderSource": w, "maskBorderMode": w, "maskBorderSlice": w, "maskBorderWidth": w, "maskBorderOutset": w, "maskBorderRepeat": w, "maskBorder": w, "maskType": w, "textDecorationStyle": wm, "textDecorationSkip": wm, "textDecorationLine": wm, "textDecorationColor": wm, "filter": w, "fontFeatureSettings": wm, "breakAfter": wmms, "breakBefore": wmms, "breakInside": wmms, "columnCount": wm, "columnFill": wm, "columnGap": wm, "columnRule": wm, "columnRuleColor": wm, "columnRuleStyle": wm, "columnRuleWidth": wm, "columns": wm, "columnSpan": wm, "columnWidth": wm, "writingMode": wms, "flex": wms, "flexBasis": w, "flexDirection": wms, "flexGrow": w, "flexFlow": wms, "flexShrink": w, "flexWrap": wms, "alignContent": w, "alignItems": w, "alignSelf": w, "justifyContent": w, "order": w, "transitionDelay": w, "transitionDuration": w, "transitionProperty": w, "transitionTimingFunction": w, "backdropFilter": w, "scrollSnapType": wms, "scrollSnapPointsX": wms, "scrollSnapPointsY": wms, "scrollSnapDestination": wms, "scrollSnapCoordinate": wms, "shapeImageThreshold": w, "shapeImageMargin": w, "shapeImageOutside": w, "hyphens": wmms, "flowInto": wms, "flowFrom": wms, "regionFragment": wms, "textOrientation": w, "boxSizing": m, "textAlignLast": m, "tabSize": m, "wrapFlow": ms, "wrapThrough": ms, "wrapMargin": ms, "touchAction": ms, "gridTemplateColumns": ms, "gridTemplateRows": ms, "gridTemplateAreas": ms, "gridTemplate": ms, "gridAutoColumns": ms, "gridAutoRows": ms, "gridAutoFlow": ms, "grid": ms, "gridRowStart": ms, "gridColumnStart": ms, "gridRowEnd": ms, "gridRow": ms, "gridColumn": ms, "gridColumnEnd": ms, "gridColumnGap": ms, "gridRowGap": ms, "gridArea": ms, "gridGap": ms, "textSizeAdjust": ["ms", "Webkit"], "borderImage": w, "borderImageOutset": w, "borderImageRepeat": w, "borderImageSlice": w, "borderImageSource": w, "borderImageWidth": w }
};
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var prefixAll = createPrefixer(staticPrefixData);
/* ::
import type { SheetDefinition } from './index.js';
type StringHandlers = { [id:string]: Function };
type SelectorCallback = (selector: string) => string[];
export type SelectorHandler = (
selector: string,
baseSelector: string,
callback: SelectorCallback
) => string[] | string | null;
*/
/**
* `selectorHandlers` are functions which handle special selectors which act
* differently than normal style definitions. These functions look at the
* current selector and can generate CSS for the styles in their subtree by
* calling the callback with a new selector.
*
* For example, when generating styles with a base selector of '.foo' and the
* following styles object:
*
* {
* ':nth-child(2n)': {
* ':hover': {
* color: 'red'
* }
* }
* }
*
* when we reach the ':hover' style, we would call our selector handlers like
*
* handler(':hover', '.foo:nth-child(2n)', callback)
*
* Since our `pseudoSelectors` handles ':hover' styles, that handler would call
* the callback like
*
* callback('.foo:nth-child(2n):hover')
*
* to generate its subtree `{ color: 'red' }` styles with a
* '.foo:nth-child(2n):hover' selector. The callback would return an array of CSS
* rules like
*
* ['.foo:nth-child(2n):hover{color:red !important;}']
*
* and the handler would then return that resulting CSS.
*
* `defaultSelectorHandlers` is the list of default handlers used in a call to
* `generateCSS`.
*
* @name SelectorHandler
* @function
* @param {string} selector: The currently inspected selector. ':hover' in the
* example above.
* @param {string} baseSelector: The selector of the parent styles.
* '.foo:nth-child(2n)' in the example above.
* @param {function} generateSubtreeStyles: A function which can be called to
* generate CSS for the subtree of styles corresponding to the selector.
* Accepts a new baseSelector to use for generating those styles.
* @returns {string[] | string | null} The generated CSS for this selector, or
* null if we don't handle this selector.
*/
var defaultSelectorHandlers /* : SelectorHandler[] */ = [
// Handle pseudo-selectors, like :hover and :nth-child(3n)
function () {
function pseudoSelectors(selector, baseSelector, generateSubtreeStyles) {
if (selector[0] !== ":") {
return null;
}
return generateSubtreeStyles(baseSelector + selector);
}
return pseudoSelectors;
}(),
// Handle media queries (or font-faces)
function () {
function mediaQueries(selector, baseSelector, generateSubtreeStyles) {
if (selector[0] !== "@") {
return null;
}
// Generate the styles normally, and then wrap them in the media query.
var generated = generateSubtreeStyles(baseSelector);
return [String(selector) + '{' + String(generated.join('')) + '}'];
}
return mediaQueries;
}()];
/**
* Generate CSS for a selector and some styles.
*
* This function handles the media queries and pseudo selectors that can be used
* in afrododi styles.
*
* @param {string} selector: A base CSS selector for the styles to be generated
* with.
* @param {Object} styleTypes: A list of properties of the return type of
* StyleSheet.create, e.g. [styles.red, styles.blue].
* @param {Array.<SelectorHandler>} selectorHandlers: A list of selector
* handlers to use for handling special selectors. See
* `defaultSelectorHandlers`.
* @param stringHandlers: See `generateCSSRuleset`
* @param useImportant: See `generateCSSRuleset`
*
* To actually generate the CSS special-construct-less styles are passed to
* `generateCSSRuleset`.
*
* For instance, a call to
*
* generateCSS(".foo", [{
* color: "red",
* "@media screen": {
* height: 20,
* ":hover": {
* backgroundColor: "black"
* }
* },
* ":active": {
* fontWeight: "bold"
* }
* }], defaultSelectorHandlers);
*
* with the default `selectorHandlers` will make 5 calls to
* `generateCSSRuleset`:
*
* generateCSSRuleset(".foo", { color: "red" }, ...)
* generateCSSRuleset(".foo:active", { fontWeight: "bold" }, ...)
* // These 2 will be wrapped in @media screen {}
* generateCSSRuleset(".foo", { height: 20 }, ...)
* generateCSSRuleset(".foo:hover", { backgroundColor: "black" }, ...)
*/
var generateCSS = function generateCSS(selector /* : string */
, styleTypes /* : SheetDefinition[] */
, selectorHandlers /* : SelectorHandler[] */
, stringHandlers /* : StringHandlers */
, useImportant /* : boolean */
) /* : string[] */{
var merged = new OrderedElements();
for (var i = 0; i < styleTypes.length; i++) {
merged.addStyleType(styleTypes[i]);
}
var plainDeclarations = new OrderedElements();
var generatedStyles = [];
// TODO(emily): benchmark this to see if a plain for loop would be faster.
merged.forEach(function (val, key) {
// For each key, see if one of the selector handlers will handle these
// styles.
var foundHandler = selectorHandlers.some(function (handler) {
var result = handler(key, selector, function (newSelector) {
return generateCSS(newSelector, [val], selectorHandlers, stringHandlers, useImportant);
});
if (result != null) {
// If the handler returned something, add it to the generated
// CSS and stop looking for another handler.
if (Array.isArray(result)) {
generatedStyles.push.apply(generatedStyles, _toConsumableArray(result));
} else {
// eslint-disable-next-line
console.warn('WARNING: Selector handlers should return an array of rules.' + 'Returning a string containing multiple rules is deprecated.', handler);
generatedStyles.push('@media all {' + String(result) + '}');
}
return true;
}
});
// If none of the handlers handled it, add it to the list of plain
// style declarations.
if (!foundHandler) {
plainDeclarations.set(key, val, true);
}
});
var generatedRuleset = generateCSSRuleset(selector, plainDeclarations, stringHandlers, useImportant, selectorHandlers);
if (generatedRuleset) {
generatedStyles.unshift(generatedRuleset);
}
return generatedStyles;
};
/**
* Helper method of generateCSSRuleset to facilitate custom handling of certain
* CSS properties. Used for e.g. font families.
*
* See generateCSSRuleset for usage and documentation of paramater types.
*/
var runStringHandlers = function runStringHandlers(declarations /* : OrderedElements */
, stringHandlers /* : StringHandlers */
, selectorHandlers /* : SelectorHandler[] */
) /* : void */{
if (!stringHandlers) {
return;
}
var stringHandlerKeys = Object.keys(stringHandlers);
for (var i = 0; i < stringHandlerKeys.length; i++) {
var key = stringHandlerKeys[i];
if (declarations.has(key)) {
// A declaration exists for this particular string handler, so we
// need to let the string handler interpret the declaration first
// before proceeding.
//
// TODO(emily): Pass in a callback which generates CSS, similar to
// how our selector handlers work, instead of passing in
// `selectorHandlers` and have them make calls to `generateCSS`
// themselves. Right now, this is impractical because our string
// handlers are very specialized and do complex things.
declarations.set(key, stringHandlers[key](declarations.get(key), selectorHandlers),
// Preserve order here, since we are really replacing an
// unprocessed style with a processed style, not overriding an
// earlier style
false);
}
}
};
var transformRule = function transformRule(key /* : string */
, value /* : string */
, transformValue /* : function */
) {
return (/* : string */String(kebabifyStyleName(key)) + ':' + String(transformValue(key, value)) + ';'
);
};
var arrayToObjectKeysReducer = function arrayToObjectKeysReducer(acc, val) {
acc[val] = true;
return acc;
};
/**
* Generate a CSS ruleset with the selector and containing the declarations.
*
* This function assumes that the given declarations don't contain any special
* children (such as media queries, pseudo-selectors, or descendant styles).
*
* Note that this method does not deal with nesting used for e.g.
* psuedo-selectors or media queries. That responsibility is left to the
* `generateCSS` function.
*
* @param {string} selector: the selector associated with the ruleset
* @param {Object} declarations: a map from camelCased CSS property name to CSS
* property value.
* @param {Object.<string, function>} stringHandlers: a map from camelCased CSS
* property name to a function which will map the given value to the value
* that is output.
* @param {bool} useImportant: A boolean saying whether to append "!important"
* to each of the CSS declarations.
* @returns {string} A string of raw CSS.
*
* Examples:
*
* generateCSSRuleset(".blah", { color: "red" })
* -> ".blah{color: red !important;}"
* generateCSSRuleset(".blah", { color: "red" }, {}, false)
* -> ".blah{color: red}"
* generateCSSRuleset(".blah", { color: "red" }, {color: c => c.toUpperCase})
* -> ".blah{color: RED}"
* generateCSSRuleset(".blah:hover", { color: "red" })
* -> ".blah:hover{color: red}"
*/
var generateCSSRuleset = function generateCSSRuleset(selector /* : string */
, declarations /* : OrderedElements */
, stringHandlers /* : StringHandlers */
, useImportant /* : boolean */
, selectorHandlers /* : SelectorHandler[] */
) /* : string */{
// Mutates declarations
runStringHandlers(declarations, stringHandlers, selectorHandlers);
var originalElements = Object.keys(declarations.elements).reduce(arrayToObjectKeysReducer, Object.create(null));
// NOTE(emily): This mutates handledDeclarations.elements.
var prefixedElements = prefixAll(declarations.elements);
var elementNames = Object.keys(prefixedElements);
if (elementNames.length !== declarations.keyOrder.length) {
// There are some prefixed values, so we need to figure out how to sort
// them.
//
// Loop through prefixedElements, looking for anything that is not in
// sortOrder, which means it was added by prefixAll. This means that we
// need to figure out where it should appear in the sortOrder.
for (var i = 0; i < elementNames.length; i++) {
if (!originalElements[elementNames[i]]) {
// This element is not in the sortOrder, which means it is a prefixed
// value that was added by prefixAll. Let's try to figure out where it
// goes.
var originalStyle = void 0;
if (elementNames[i][0] === 'W') {
// This is a Webkit-prefixed style, like "WebkitTransition". Let's
// find its original style's sort order.
originalStyle = elementNames[i][6].toLowerCase() + elementNames[i].slice(7);
} else if (elementNames[i][1] === 'o') {
// This is a Moz-prefixed style, like "MozTransition". We check
// the second character to avoid colliding with Ms-prefixed
// styles. Let's find its original style's sort order.
originalStyle = elementNames[i][3].toLowerCase() + elementNames[i].slice(4);
} else {
// if (elementNames[i][1] === 's') {
// This is a Ms-prefixed style, like "MsTransition".
originalStyle = elementNames[i][2].toLowerCase() + elementNames[i].slice(3);
}
if (originalStyle && originalElements[originalStyle]) {
var originalIndex = declarations.keyOrder.indexOf(originalStyle);
declarations.keyOrder.splice(originalIndex, 0, elementNames[i]);
} else {
// We don't know what the original style was, so sort it to
// top. This can happen for styles that are added that don't
// have the same base name as the original style.
declarations.keyOrder.unshift(elementNames[i]);
}
}
}
}
var transformValue = useImportant === false ? stringifyValue : stringifyAndImportantifyValue;
var rules = [];
for (var _i = 0; _i < declarations.keyOrder.length; _i++) {
var key = declarations.keyOrder[_i];
var value = prefixedElements[key];
if (Array.isArray(value)) {
// inline-style-prefixer returns an array when there should be
// multiple rules for the same key. Here we flatten to multiple
// pairs with the same key.
for (var j = 0; j < value.length; j++) {
rules.push(transformRule(key, value[j], transformValue));
}
} else {
rules.push(transformRule(key, value, transformValue));
}
}
if (rules.length) {
return String(selector) + '{' + String(rules.join("")) + '}';
} else {
return "";
}
};
var _typeof$1 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
function _toConsumableArray$1(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
/* ::
import type { SheetDefinition, SheetDefinitions } from './index.js';
import type { MaybeSheetDefinition } from './exports.js';
import type { SelectorHandler } from './generate.js';
export type StyleContext = {
styleTag?: HTMLStyleElement,
alreadyInjected: { [string]: boolean },
injectionBuffer: string[],
isBuffering: boolean,
};
*/
var createContext = function createContext() {
return (/* : StyleContext */{
// The current <style> tag we are inserting into, or null if we haven't
// inserted anything yet. We could find this each time using
// `document.querySelector("style[data-afrododi"])`, but holding onto it is
// faster.
styleTag: undefined,
// This is a map from afrododi's generated class names to `true` (acting as a
// set of class names)
alreadyInjected: {},
// This is the buffer of styles which have not yet been flushed.
injectionBuffer: [],
// A flag to tell if we are already buffering styles. This could happen either
// because we scheduled a flush call already, so newly added styles will
// already be flushed, or because we are statically buffering on the server.
isBuffering: false
}
);
};
// Inject a set of rules into a <style> tag in the head of the document. This
// will automatically create a style tag and then continue to use it for
// multiple injections. It will also use a style tag with the `data-afrododi`
// tag on it if that exists in the DOM. This could be used for e.g. reusing the
// same style tag that server-side rendering inserts.
var injectStyleTag = function injectStyleTag(context /* : StyleContext */, cssRules /* : string[] */) {
if (context.styleTag == null) {
// Try to find a style tag with the `data-afrododi` attribute first.
context.styleTag = document.querySelector("style[data-afrododi]") /* : any */;
// If that doesn't work, generate a new style tag.
if (context.styleTag == null) {
// Taken from
// http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript
var head = document.head || document.getElementsByTagName('head')[0];
context.styleTag = document.createElement('style');
context.styleTag.type = 'text/css';
context.styleTag.setAttribute("data-afrododi", "");
if (context.styleTag) head.appendChild(context.styleTag);
}
}
// $FlowFixMe
var sheet = context.styleTag.styleSheet || context.styleTag.sheet /* : any */;
if (sheet.insertRule) {
var numRules = sheet.cssRules.length;
cssRules.forEach(function (rule) {
try {
sheet.insertRule(rule, numRules);
numRules += 1;
} catch (e) {
// The selector for this rule wasn't compatible with the browser
}
});
} else if (context.styleTag) {
// $FlowFixMe
context.styleTag.innerText = (context.styleTag.innerText || '') + cssRules.join('');
}
};
// Custom handlers for stringifying CSS values that have side effects
// (such as fontFamily, which can cause @font-face rules to be injected)
var stringHandlers = {
// With fontFamily we look for objects that are passed in and interpret
// them as @font-face rules that we need to inject. The value of fontFamily
// can either be a string (as normal), an object (a single font face), or
// an array of objects and strings.
fontFamily: function () {
function fontFamily(context /* : StyleContext */, val) {
if (Array.isArray(val)) {
var nameMap = {};
val.forEach(function (v) {
nameMap[fontFamily(v)] = true;
});
return Object.keys(nameMap).join(",");
} else if ((typeof val === 'undefined' ? 'undefined' : _typeof$1(val)) === "object") {
injectStyleOnce(context, val.src, "@font-face", [val], false);
return '"' + String(val.fontFamily) + '"';
} else {
return val;
}
}
return fontFamily;
}(),
// With animationName we look for an object that contains keyframes and
// inject them as an `@keyframes` block, returning a uniquely generated
// name. The keyframes object should look like
// animationName: {
// from: {
// left: 0,
// top: 0,
// },
// '50%': {
// left: 15,
// top: 5,
// },
// to: {
// left: 20,
// top: 20,
// }
// }
// TODO(emily): `stringHandlers` doesn't let us rename the key, so I have
// to use `animationName` here. Improve that so we can call this
// `animation` instead of `animationName`.
animationName: function () {
function animationName(context /* : StyleContext */, val, selectorHandlers) {
if (Array.isArray(val)) {
return val.map(function (v) {
return animationName(v, selectorHandlers);
}).join(",");
} else if ((typeof val === 'undefined' ? 'undefined' : _typeof$1(val)) === "object") {
// Generate a unique name based on the hash of the object. We can't
// just use the hash because the name can't start with a number.
// TODO(emily): this probably makes debugging hard, allow a custom
// name?
var name = 'keyframe_' + String(hashObject(val));
// Since keyframes need 3 layers of nesting, we use `generateCSS` to
// build the inner layers and wrap it in `@keyframes` ourselves.
var finalVal = '@keyframes ' + name + '{';
// TODO see if we can find a way where checking for OrderedElements
// here is not necessary. Alternatively, perhaps we should have a
// utility method that can iterate over either a plain object, an
// instance of OrderedElements, or a Map, and then use that here and
// elsewhere.
if (val instanceof OrderedElements) {
val.forEach(function (valVal, valKey) {
finalVal += generateCSS(valKey, [valVal], selectorHandlers, stringHandlers, false).join('');
});
} else {
Object.keys(val).forEach(function (key) {
finalVal += generateCSS(key, [val[key]], selectorHandlers, stringHandlers, false).join('');
});
}
finalVal += '}';
injectGeneratedCSSOnce(context, name, [finalVal]);
return name;
} else {
return val;
}
}
return animationName;
}()
};
var injectGeneratedCSSOnce = function injectGeneratedCSSOnce(context /* : StyleContext */, key, generatedCSS) {
var _context$injectionBuf;
if (context.alreadyInjected[key]) {
return;
}
if (!context.isBuffering) {
// We should never be automatically buffering on the server (or any
// place without a document), so guard against that.
if (typeof document === "undefined") {
throw new Error("Cannot automatically buffer without a document");
}
// If we're not already buffering, schedule a call to flush the
// current styles.
context.isBuffering = true;
asap(function () {
return flushToStyleTag(context);
});
}
(_context$injectionBuf = context.injectionBuffer).push.apply(_context$injectionBuf, _toConsumableArray$1(generatedCSS));
context.alreadyInjected[key] = true;
};
var injectStyleOnce = function injectStyleOnce(context /* : StyleContext */
, key /* : string */
, selector /* : string */
, definitions /* : SheetDefinition[] */
, useImportant /* : boolean */
) {
var selectorHandlers /* : SelectorHandler[] */ = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : [];
if (context.alreadyInjected[key]) {
return;
}
var generated = generateCSS(selector, definitions, selectorHandlers, stringHandlers, useImportant);
injectGeneratedCSSOnce(context, key, generated);
};
var getBufferedStyles = function getBufferedStyles(context /* : StyleContext */) {
return context.injectionBuffer;
};
var startBuffering = function startBuffering() /* : StyleContext */{
var context = createContext();
context.isBuffering = true;
return context;
};
var flushToArray = function flushToArray(context /* : StyleContext */) {
context.isBuffering = false;
var ret = context.injectionBuffer;
context.injectionBuffer = [];
return ret;
};
var flushToString = function flushToString(context /* : StyleContext */) {
return flushToArray(context).join('');
};
var flushToStyleTag = function flushToStyleTag(context /* : StyleContext */) {
var cssRules = flushToArray(context);
if (cssRules.length > 0) {
injectStyleTag(context, cssRules);
}
};
var getRenderedClassNames = function getRenderedClassNames(context /* : StyleContext */) /* : string[] */{
return Object.keys(context.alreadyInjected);
};
var addRenderedClassNames = function addRenderedClassNames(context /* : StyleContext */, classNames /* : string[] */) {
classNames.forEach(function (className) {
context.alreadyInjected[className] = true;
});
};
var processStyleDefinitions = function processStyleDefinitions(styleDefinitions /* : any[] */
, classNameBits /* : string[] */
, definitionBits /* : Object[] */
, length /* : number */
) /* : number */{
for (var i = 0; i < styleDefinitions.length; i += 1) {
// Filter out falsy values from the input, to allow for
// `css(a, test && c)`
if (styleDefinitions[i]) {
if (Array.isArray(styleDefinitions[i])) {
// We've encountered an array, so let's recurse
length += processStyleDefinitions(styleDefinitions[i], classNameBits, definitionBits, length);
} else {
classNameBits.push(styleDefinitions[i]._name);
definitionBits.push(styleDefinitions[i]._definition);
length += styleDefinitions[i]._len;
}
}
}
return length;
};
/**
* Inject styles associated with the passed style definition objects, and return
* an associated CSS class name.
*
* @param {boolean} useImportant If true, will append !important to generated
* CSS output. e.g. {color: red} -> "color: red !important".
* @param {(Object|Object[])[]} styleDefinitions style definition objects, or
* arbitrarily nested arrays of them, as returned as properties of the
* return value of StyleSheet.create().
*/
var injectAndGetClassName = function injectAndGetClassName(context /* : StyleContext */
, useImportant /* : boolean */
, styleDefinitions /* : MaybeSheetDefinition[] */
, selectorHandlers /* : SelectorHandler[] */
) /* : string */{
var classNameBits = [];
var definitionBits = [];
// Mutates classNameBits and definitionBits and returns a length which we
// will append to the hash to decrease the chance of hash collisions.
var length = processStyleDefinitions(styleDefinitions, classNameBits, definitionBits, 0);
// Break if there aren't any valid styles.
if (classNameBits.length === 0) {
return "";
}
var className = void 0;
if (process.env.NODE_ENV === 'production') {
className = classNameBits.length === 1 ? '_' + String(classNameBits[0]) : '_' + String(hashString(classNameBits.join())) + String((length % 36).toString(36));
} else {
className = classNameBits.join("-o_O-");
}
injectStyleOnce(context, className, '.' + String(className), definitionBits, useImportant, selectorHandlers);
return className;
};
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass$1 = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck$1(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/* ::
import type {ComponentType, Node} from 'react';
import type {StyleContext} from './inject.js';
export type Context = {
Consumer: ComponentType < {
children: (value: StyleContext) => Node
} > ,
Provider: ComponentType < {
value: StyleContext
} >
};
*/
function getDisplayName(WrappedComponent /* : ComponentType */) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
var CSSContext /* : Context */ = React.createContext();
function withCSS(WrappedComponent /* : ComponentType */) {
var withDisplayName = 'withCSS(' + String(getDisplayName(WrappedComponent)) + ')';
var Wrapper = function (_React$Component) {
_inherits(Wrapper, _React$Component);
function Wrapper(props) {
_classCallCheck$1(this, Wrapper);
var _this = _possibleConstructorReturn(this, (Wrapper.__proto__ || Object.getPrototypeOf(Wrapper)).call(this, props));
_this.renderContext = _this.renderContext.bind(_this);
_this.css = _this.css.bind(_this);
return _this;
}
_createClass$1(Wrapper, [{
key: 'css',
value: function () {
function css(context /* : StyleContext */) {
// Avoid a circular import
var _require = require('./index'),
css = _require.css;
return function () /* : MaybeSheetDefinition[] */{
for (var _len = arguments.length, styleDefinitions = Array(_len), _key = 0; _key < _len; _key++) {
styleDefinitions[_key] = arguments[_key];
}
return css(context, styleDefinitions);
};
}
return css;
}()
}, {
key: 'render',
value: function () {
function render() {
return React.createElement(
CSSContext.Consumer,
null,
this.renderContext
);
}
return render;
}()
}, {
key: 'renderContext',
value: function () {
function renderContext(context /* : StyleContext */) {
return React.createElement(WrappedComponent, _extends({}, this.props, { css: this.css(context) }));
}
return renderContext;
}()
}]);
return Wrapper;
}(React.Component);
Wrapper.displayName = withDisplayName;
Wrapper.WrappedComponent = WrappedComponent;
return hoistNonReactStatics(Wrapper, WrappedComponent, {});
}
var CSSProvider = CSSContext.Provider;
/* ::
import type { SelectorHandler } from './generate.js';
import type { StyleContext } from './inject.js';
export type SheetDefinition = { [id:string]: any };
export type SheetDefinitions = SheetDefinition | SheetDefinition[];
type RenderFunction = (context: StyleContext) => string;
type AsyncRenderFunction = (context: StyleContext) => Promise<string>;
type Extension = {
selectorHandler: SelectorHandler
};
export type MaybeSheetDefinition = SheetDefinition | false | null | void
*/
var unminifiedHashFn = function unminifiedHashFn(str /* : string */, key /* : string */) {
return String(key) + '_' + String(hashString(str));
};
// StyleSheet.create is in a hot path so we want to keep as much logic out of it
// as possible. So, we figure out which hash function to use once, and only
// switch it out via minify() as necessary.
//
// This is in an exported function to make it easier to test.
var initialHashFn = function initialHashFn() {
return process.env.NODE_ENV === 'production' ? hashString : unminifiedHashFn;
};
var hashFn = initialHashFn();
var StyleSheet = {
create: function () {
function create(sheetDefinition /* : SheetDefinition */) /* : Object */{
var mappedSheetDefinition = {};
var keys = Object.keys(sheetDefinition);
for (var i = 0; i < keys.length; i += 1) {
var key = keys[i];
var val = sheetDefinition[key];
var stringVal = JSON.stringify(val);
mappedSheetDefinition[key] = {
_len: stringVal.length,
_name: hashFn(stringVal, key),
_definition: val
};
}
return mappedSheetDefinition;
}
return create;
}(),
createContext: createContext,
rehydrate: function () {
function rehydrate(context /* : StyleContext */) {
var renderedClassNames /* : string[] */ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
addRenderedClassNames(context, renderedClassNames);
}
return rehydrate;
}()
};
/**
* Utilities for using afrododi server-side.
*
* This can be minified out in client-only bundles by replacing `typeof window`
* with `"object"`, e.g. via Webpack's DefinePlugin:
*
* new webpack.DefinePlugin({
* "typeof window": JSON.stringify("object")
* })
*/
var StyleSheetServer = typeof window !== 'undefined' ? null : {
renderStatic: function () {
function renderStatic(renderFunc /* : RenderFunction */) {
var context = startBuffering();
var html = renderFunc(context);
var cssContent = flushToString(context);
return {
html: html,
css: {
content: cssContent,
renderedClassNames: getRenderedClassNames(context)
}
};
}
return renderStatic;
}(),
renderStaticAsync: function () {
function renderStaticAsync(renderFunc /* : AsyncRenderFunction */) {
var context = startBuffering();
return renderFunc(context).then(function (html /* : string */) {
var cssContent = flushToString(context);
return {
html: html,
css: {
content: cssContent,
renderedClassNames: getRenderedClassNames(context)
}
};
});
}
return renderStaticAsync;
}()
};
/**
* Utilities for using afrododi in tests.
*
* Not meant to be used in production.
*/
var StyleSheetTestUtils = process.env.NODE_ENV === 'production' ? null : {
/**
* Gets a new StyleContext instance to use during testing
*
* @returns {object} StyleContext instance for use during testing
*/
getContext: function () {
function getContext() /* : StyleContext */{
return startBuffering();
}
return getContext;
}(),
/**
* Returns a string of buffered styles which have not been flushed
*
* @returns {string} Buffer of styles which have not yet been flushed.
*/
getBufferedStyles: function () {
function getBufferedStyles$$1(context /* : StyleContext */) /* : string[] */{
return getBufferedStyles(context);
}
return getBufferedStyles$$1;
}()
};
/**
* Generate the afrododi API exports, with given `selectorHandlers` and
* `useImportant` state.
*/
function makeExports(useImportant /* : boolean */
) {
var selectorHandlers /* : SelectorHandler[] */ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultSelectorHandlers;
return {
StyleSheet: Object.assign({}, StyleSheet, {
/**
* Returns a version of the exports of afrododi (i.e. an object
* with `css` and `StyleSheet` properties) which have some
* extensions included.
*
* @param {Array.<Object>} extensions: An array o