layeshifter-fela
Version:
State-Driven Styling in JavaScript
241 lines (193 loc) • 9.49 kB
JavaScript
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; };
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
import cssifyDeclaration from 'css-in-js-utils/lib/cssifyDeclaration';
import arrayEach from 'fast-loops/lib/arrayEach';
import isPlainObject from 'isobject';
import { generateCombinedMediaQuery, generateCSSSelector, isMediaQuery, isNestedSelector, isUndefinedValue, isSupport, normalizeNestedProperty, processStyleWithPlugins, STATIC_TYPE, RULE_TYPE, KEYFRAME_TYPE, FONT_TYPE, CLEAR_TYPE } from 'fela-utils';
import cssifyFontFace from './cssifyFontFace';
import cssifyKeyframe from './cssifyKeyframe';
import cssifyStaticStyle from './cssifyStaticStyle';
import generateAnimationName from './generateAnimationName';
import generateClassName from './generateClassName';
import generateFontSource from './generateFontSource';
import generateStaticReference from './generateStaticReference';
import getFontLocals from './getFontLocals';
import isSafeClassName from './isSafeClassName';
import toCSSString from './toCSSString';
import validateSelectorPrefix from './validateSelectorPrefix';
export default function createRenderer() {
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var renderer = {
listeners: [],
keyframePrefixes: config.keyframePrefixes || ['-webkit-', '-moz-'],
plugins: config.plugins || [],
mediaQueryOrder: config.mediaQueryOrder || [],
supportQueryOrder: config.supportQueryOrder || [],
ruleOrder: [/^:link/, /^:visited/, /^:hover/, /^:focus-within/, /^:focus/, /^:active/],
rendererId: validateSelectorPrefix(config.rendererId),
selectorPrefix: validateSelectorPrefix(config.selectorPrefix),
filterClassName: config.filterClassName || isSafeClassName,
devMode: config.devMode || false,
uniqueRuleIdentifier: 0,
uniqueKeyframeIdentifier: 0,
nodes: {},
scoreIndex: {},
// use a flat cache object with pure string references
// to achieve maximal lookup performance and memoization speed
cache: {},
getNextRuleIdentifier: function getNextRuleIdentifier() {
return ++renderer.uniqueRuleIdentifier;
},
renderRule: function renderRule(rule) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return renderer._renderStyle(rule(props, renderer), props);
},
renderKeyframe: function renderKeyframe(keyframe) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var resolvedKeyframe = keyframe(props, renderer);
var keyframeReference = JSON.stringify(resolvedKeyframe);
if (!renderer.cache.hasOwnProperty(keyframeReference)) {
// use another unique identifier to ensure minimal css markup
var animationName = generateAnimationName(++renderer.uniqueKeyframeIdentifier, renderer.rendererId);
var processedKeyframe = processStyleWithPlugins(renderer, resolvedKeyframe, KEYFRAME_TYPE, props);
var cssKeyframe = cssifyKeyframe(processedKeyframe, animationName, renderer.keyframePrefixes);
var change = {
type: KEYFRAME_TYPE,
keyframe: cssKeyframe,
name: animationName
};
renderer.cache[keyframeReference] = change;
renderer._emitChange(change);
}
return renderer.cache[keyframeReference].name;
},
renderFont: function renderFont(family, files) {
var properties = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var localAlias = properties.localAlias,
otherProperties = _objectWithoutProperties(properties, ['localAlias']);
var fontReference = family + JSON.stringify(properties);
var fontLocals = getFontLocals(localAlias);
if (!renderer.cache.hasOwnProperty(fontReference)) {
var fontFamily = toCSSString(family);
var fontFace = _extends({}, otherProperties, {
src: generateFontSource(files, fontLocals),
fontFamily: fontFamily
});
var cssFontFace = cssifyFontFace(fontFace);
var change = {
type: FONT_TYPE,
fontFace: cssFontFace,
fontFamily: fontFamily
};
renderer.cache[fontReference] = change;
renderer._emitChange(change);
}
return renderer.cache[fontReference].fontFamily;
},
renderStatic: function renderStatic(staticStyle, selector) {
var staticReference = generateStaticReference(staticStyle, selector);
if (!renderer.cache.hasOwnProperty(staticReference)) {
var cssDeclarations = cssifyStaticStyle(staticStyle, renderer);
var change = {
type: STATIC_TYPE,
css: cssDeclarations,
selector: selector
};
renderer.cache[staticReference] = change;
renderer._emitChange(change);
}
},
subscribe: function subscribe(callback) {
renderer.listeners.push(callback);
return {
unsubscribe: function unsubscribe() {
return renderer.listeners.splice(renderer.listeners.indexOf(callback), 1);
}
};
},
clear: function clear() {
renderer.uniqueRuleIdentifier = 0;
renderer.uniqueKeyframeIdentifier = 0;
renderer.cache = {};
renderer._emitChange({
type: CLEAR_TYPE
});
},
_renderStyle: function _renderStyle() {
var style = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var processedStyle = processStyleWithPlugins(renderer, style, RULE_TYPE, props);
return renderer._renderStyleToClassNames(processedStyle).slice(1);
},
_renderStyleToClassNames: function _renderStyleToClassNames(_ref) {
var pseudo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var media = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
var support = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
var _className = _ref._className,
style = _objectWithoutProperties(_ref, ['_className']);
var classNames = _className ? ' ' + _className : '';
for (var property in style) {
var value = style[property];
if (isPlainObject(value)) {
if (isNestedSelector(property)) {
classNames += renderer._renderStyleToClassNames(value, pseudo + normalizeNestedProperty(property), media, support);
} else if (isMediaQuery(property)) {
var combinedMediaQuery = generateCombinedMediaQuery(media, property.slice(6).trim());
classNames += renderer._renderStyleToClassNames(value, pseudo, combinedMediaQuery, support);
} else if (isSupport(property)) {
var combinedSupport = generateCombinedMediaQuery(support, property.slice(9).trim());
classNames += renderer._renderStyleToClassNames(value, pseudo, media, combinedSupport);
} else {
console.warn('The object key "' + property + '" is not a valid nested key in Fela. \nMaybe you forgot to add a plugin to resolve it? \nCheck http://fela.js.org/docs/basics/Rules.html#styleobject for more information.');
}
} else {
var declarationReference = support + media + pseudo + property + value;
if (!renderer.cache.hasOwnProperty(declarationReference)) {
// we remove undefined values to enable
// usage of optional props without side-effects
if (isUndefinedValue(value)) {
renderer.cache[declarationReference] = {
className: ''
/* eslint-disable no-continue */
};continue;
/* eslint-enable */
}
var className = renderer.selectorPrefix + generateClassName(renderer.getNextRuleIdentifier, renderer.filterClassName);
var declaration = cssifyDeclaration(property, value);
var selector = generateCSSSelector(className, pseudo);
var change = {
type: RULE_TYPE,
className: className,
selector: selector,
declaration: declaration,
pseudo: pseudo,
media: media,
support: support
};
renderer.cache[declarationReference] = change;
renderer._emitChange(change);
}
var cachedClassName = renderer.cache[declarationReference].className;
// only append if we got a class cached
if (cachedClassName) {
classNames += ' ' + cachedClassName;
}
}
}
return classNames;
},
_emitChange: function _emitChange(change) {
arrayEach(renderer.listeners, function (listener) {
return listener(change);
});
}
};
// initial setup
renderer.keyframePrefixes.push('');
if (config.enhancers) {
arrayEach(config.enhancers, function (enhancer) {
renderer = enhancer(renderer);
});
}
return renderer;
}