react-jsx-style
Version:
create dynamic injected styles with react component or hooks
125 lines (124 loc) • 4.23 kB
JavaScript
// To simulate the behavior of react inline style, reuse some code from react internal.
// reference: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/CSSProperty.js
/**
* CSS properties which accept numbers but are not in units of "px".
*/
const unitlessPropertyDict = {
animationIterationCount: true,
borderImageOutset: true,
borderImageSlice: true,
borderImageWidth: true,
boxFlex: true,
boxFlexGroup: true,
boxOrdinalGroup: true,
columnCount: true,
columns: true,
flex: true,
flexGrow: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
flexOrder: true,
gridArea: true,
gridRow: true,
gridRowEnd: true,
gridRowSpan: true,
gridRowStart: true,
gridColumn: true,
gridColumnEnd: true,
gridColumnSpan: true,
gridColumnStart: 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,
};
/**
* @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.
*/
const 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.
Object.keys(unitlessPropertyDict).forEach(function (prop) {
prefixes.forEach(function (prefix) {
unitlessPropertyDict[prefixKey(prefix, prop)] = unitlessPropertyDict[prop];
});
});
function isUnitlessNumber(name) {
return !!(unitlessPropertyDict.hasOwnProperty(name) && unitlessPropertyDict[name]);
}
function dangerousStyleValue(name, value, isCustomProperty) {
// Note that we've removed escapeTextForBrowser() calls here since the
// whole string will be escaped when the attribute is injected into
// the markup. If you provide unsafe user data here they can inject
// arbitrary CSS which may be problematic (I couldn't repro this):
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
// http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
// This is not an XSS hole but instead a potential CSS injection issue
// which has lead to a greater discussion about how we're going to
// trust URLs moving forward. See #2115901
const isEmpty = value == null || typeof value === 'boolean' || value === '';
if (isEmpty) {
return '';
}
if (!isCustomProperty &&
typeof value === 'number' &&
value !== 0 &&
!isUnitlessNumber(name)) {
return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers
}
return ('' + value).trim();
}
const uppercasePattern = /([A-Z])/g;
const msPattern = /^ms-/;
function hyphenateStyleName(name) {
return name
.replace(uppercasePattern, '-$1')
.toLowerCase()
.replace(msPattern, '-ms-');
}
function createCssRules(styles) {
const rules = [];
for (const styleName in styles) {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
const isCustomProperty = styleName.indexOf('--') === 0;
const styleValue = styles[styleName];
if (styleValue != null) {
const propertyName = isCustomProperty ? styleName : hyphenateStyleName(styleName);
const propertyValue = dangerousStyleValue(styleName, styleValue, isCustomProperty);
rules.push({
name: propertyName,
value: propertyValue,
});
}
}
return rules;
}
export { createCssRules };