UNPKG

falcon-styler

Version:
820 lines (767 loc) 22.8 kB
var util = require('./util') // http://www.w3.org/TR/css3-color/#html4 var BASIC_COLOR_KEYWORDS = { black: '#000000', silver: '#C0C0C0', gray: '#808080', white: '#FFFFFF', maroon: '#800000', red: '#FF0000', purple: '#800080', fuchsia: '#FF00FF', green: '#008000', lime: '#00FF00', olive: '#808000', yellow: '#FFFF00', navy: '#000080', blue: '#0000FF', teal: '#008080', aqua: '#00FFFF' } // http://www.w3.org/TR/css3-color/#svg-color var EXTENDED_COLOR_KEYWORDS = { aliceblue: '#F0F8FF', antiquewhite: '#FAEBD7', aqua: '#00FFFF', aquamarine: '#7FFFD4', azure: '#F0FFFF', beige: '#F5F5DC', bisque: '#FFE4C4', black: '#000000', blanchedalmond: '#FFEBCD', blue: '#0000FF', blueviolet: '#8A2BE2', brown: '#A52A2A', burlywood: '#DEB887', cadetblue: '#5F9EA0', chartreuse: '#7FFF00', chocolate: '#D2691E', coral: '#FF7F50', cornflowerblue: '#6495ED', cornsilk: '#FFF8DC', crimson: '#DC143C', cyan: '#00FFFF', darkblue: '#00008B', darkcyan: '#008B8B', darkgoldenrod: '#B8860B', darkgray: '#A9A9A9', darkgreen: '#006400', darkgrey: '#A9A9A9', darkkhaki: '#BDB76B', darkmagenta: '#8B008B', darkolivegreen: '#556B2F', darkorange: '#FF8C00', darkorchid: '#9932CC', darkred: '#8B0000', darksalmon: '#E9967A', darkseagreen: '#8FBC8F', darkslateblue: '#483D8B', darkslategray: '#2F4F4F', darkslategrey: '#2F4F4F', darkturquoise: '#00CED1', darkviolet: '#9400D3', deeppink: '#FF1493', deepskyblue: '#00BFFF', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1E90FF', firebrick: '#B22222', floralwhite: '#FFFAF0', forestgreen: '#228B22', fuchsia: '#FF00FF', gainsboro: '#DCDCDC', ghostwhite: '#F8F8FF', gold: '#FFD700', goldenrod: '#DAA520', gray: '#808080', green: '#008000', greenyellow: '#ADFF2F', grey: '#808080', honeydew: '#F0FFF0', hotpink: '#FF69B4', indianred: '#CD5C5C', indigo: '#4B0082', ivory: '#FFFFF0', khaki: '#F0E68C', lavender: '#E6E6FA', lavenderblush: '#FFF0F5', lawngreen: '#7CFC00', lemonchiffon: '#FFFACD', lightblue: '#ADD8E6', lightcoral: '#F08080', lightcyan: '#E0FFFF', lightgoldenrodyellow: '#FAFAD2', lightgray: '#D3D3D3', lightgreen: '#90EE90', lightgrey: '#D3D3D3', lightpink: '#FFB6C1', lightsalmon: '#FFA07A', lightseagreen: '#20B2AA', lightskyblue: '#87CEFA', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#B0C4DE', lightyellow: '#FFFFE0', lime: '#00FF00', limegreen: '#32CD32', linen: '#FAF0E6', magenta: '#FF00FF', maroon: '#800000', mediumaquamarine: '#66CDAA', mediumblue: '#0000CD', mediumorchid: '#BA55D3', mediumpurple: '#9370DB', mediumseagreen: '#3CB371', mediumslateblue: '#7B68EE', mediumspringgreen: '#00FA9A', mediumturquoise: '#48D1CC', mediumvioletred: '#C71585', midnightblue: '#191970', mintcream: '#F5FFFA', mistyrose: '#FFE4E1', moccasin: '#FFE4B5', navajowhite: '#FFDEAD', navy: '#000080', oldlace: '#FDF5E6', olive: '#808000', olivedrab: '#6B8E23', orange: '#FFA500', orangered: '#FF4500', orchid: '#DA70D6', palegoldenrod: '#EEE8AA', palegreen: '#98FB98', paleturquoise: '#AFEEEE', palevioletred: '#DB7093', papayawhip: '#FFEFD5', peachpuff: '#FFDAB9', peru: '#CD853F', pink: '#FFC0CB', plum: '#DDA0DD', powderblue: '#B0E0E6', purple: '#800080', red: '#FF0000', rosybrown: '#BC8F8F', royalblue: '#4169E1', saddlebrown: '#8B4513', salmon: '#FA8072', sandybrown: '#F4A460', seagreen: '#2E8B57', seashell: '#FFF5EE', sienna: '#A0522D', silver: '#C0C0C0', skyblue: '#87CEEB', slateblue: '#6A5ACD', slategray: '#708090', slategrey: '#708090', snow: '#FFFAFA', springgreen: '#00FF7F', steelblue: '#4682B4', tan: '#D2B48C', teal: '#008080', thistle: '#D8BFD8', tomato: '#FF6347', turquoise: '#40E0D0', violet: '#EE82EE', wheat: '#F5DEB3', white: '#FFFFFF', whitesmoke: '#F5F5F5', yellow: '#FFFF00', yellowgreen: '#9ACD32' } var LENGTH_REGEXP = /^[-+]?\d*\.?\d+(\S*)$/ var SUPPORT_CSS_UNIT = ['px', 'pt', 'wx', 'rpx', 'vh', 'vw', '%'] var ANYTHING_VALIDATOR = function ANYTHING_VALIDATOR(v) { return { value: v } } /** * the values below is valid * - number * - number + 'px' * * @param {string} v * @return {function} a function to return * - value: number|null * - reason(k, v, result) */ var LENGTH_VALIDATOR = function LENGTH_VALIDATOR(v) { v = (v || '').toString() var match = v.match(LENGTH_REGEXP) if (match) { var unit = match[1] if (!unit) { return { value: parseFloat(v) } } else if (SUPPORT_CSS_UNIT.indexOf(unit) > -1) { return { value: v } } else { return { value: parseFloat(v), reason: function reason(k, v, result) { return 'NOTE: unit `' + unit + '` is not supported and property value `' + v + '` is autofixed to `' + result + '`' } } } } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only number and pixel values are supported)' } } } /** * the values below is valid * - number {1,4} * - number + 'px' {1,4} * * @param {string} v * @return {function} a function to return * - value: number|null * - reason(k, v, result) */ var SHORTHAND_LENGTH_VALIDATOR = function SHORTHAND_LENGTH_VALIDATOR(v) { v = (v || '').toString() var value = [] var reason = [] var results = v.split(/\s+/).map(LENGTH_VALIDATOR) for (var i = 0; i < results.length; ++i) { var res = results[i] if (res.value === null || res.value === undefined) { value = null reason = res.reason break } value.push(res.value) if (res.reason) { reason.push(res.reason) } } if (!value) { return { value: value, reason: reason } } else { const result = { value: value.join(' '), } if (reason.length > 0) { result.reason = function (k, v, result) { return reason.map(function (res) { if (typeof res === 'function') { return res(k, v, result) } }).join('\n') } } return result; } } /** * the values below is valid * - hex color value (#xxxxxx or #xxx) * - basic and extended color keywords in CSS spec * * @param {string} v * @return {function} a function to return * - value: string|null * - reason(k, v, result) */ var COLOR_VALIDATOR = function COLOR_VALIDATOR(v) { v = (v || '').toString() if (v.match(/^#[0-9a-fA-F]{6}$/)) { return { value: v } } if (v.match(/^#[0-9a-fA-F]{3}$/)) { return { value: '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3] // 去掉颜色值自动修复提醒 // reason: function reason(k, v, result) { // return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`' // } } } if (EXTENDED_COLOR_KEYWORDS[v]) { return { value: EXTENDED_COLOR_KEYWORDS[v] // 去掉颜色值自动修复提醒 // reason: function reason(k, v, result) { // return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`' // } } } var arrColor, r, g, b, a var RGB_REGEXP = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/gi var RGBA_REGEXP = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d*\.?\d+)\s*\)$/gi if (arrColor = RGB_REGEXP.exec(v)) { r = parseInt(arrColor[1]) g = parseInt(arrColor[2]) b = parseInt(arrColor[3]) if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { return { value: 'rgb(' + [r, g, b].join(',') + ')' } } } if (arrColor = RGBA_REGEXP.exec(v)) { r = parseInt(arrColor[1]) g = parseInt(arrColor[2]) b = parseInt(arrColor[3]) a = parseFloat(arrColor[4]) if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) { return { value: 'rgba(' + [r, g, b, a].join(',') + ')' } } } if (v === 'transparent') { return { value: 'rgba(0,0,0,0)' } } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not valid for `' + util.camelCaseToHyphened(k) + '`' } } } /** * only integer or float value is valid * * @param {string} v * @return {function} a function to return * - value: number|null * - reason(k, v, result) */ var NUMBER_VALIDATOR = function NUMBER_VALIDATOR(v) { v = (v || '').toString() var match = v.match(LENGTH_REGEXP) if (match && !match[1]) { return { value: parseFloat(v) } } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only number is supported)' } } } /** * only integer value is valid * * @param {string} v * @return {function} a function to return * - value: number|null * - reason(k, v, result) */ var INTEGER_VALIDATOR = function INTEGER_VALIDATOR(v) { v = (v || '').toString() if (v.match(/^[-+]?\d+$/)) { return { value: parseInt(v, 10) } } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only integer is supported)' } } } /** * transition-property: only css property is valid * * @param {string} v * @return {function} a function to return * - value: string|null * - reason(k, v, result) */ var TRANSITION_PROPERTY_VALIDATOR = function TRANSITION_PROPERTY_VALIDATOR(v) { v = (v || '').toString() v = v.split(/\s*,\s*/).map(util.hyphenedToCamelCase).join(',') if (v.split(/\s*,\s*/).every(p => !!validatorMap[p] || p === "all")) { return { value: v } } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only css property is valid)' } } } /** * transition-duration & transition-delay: only number of seconds or milliseconds is valid * * @param {string} v * @return {function} a function to return * - value: number|null * - reason(k, v, result) */ var TRANSITION_INTERVAL_VALIDATOR = function TRANSITION_INTERVAL_VALIDATOR(v) { v = (v || 0).toString() var match, num, ret if (match = v.match(/^\d*\.?\d+(ms|s)?$/)) { num = parseFloat(match[0]) if (!match[1]) { ret = { value: parseInt(num) } } else { if (match[1] === 's') { num *= 1000 } ret = { value: parseInt(num) // 能够自动修复的ms和s不提示 // reason: function reason(k, v, result) { // return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`' // } } } return ret } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (only number of seconds and milliseconds is valid)' } } } /** * transition-timing-function: only linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n) is valid * * @param {string} v * @return {function} a function to return * - value: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n)|null * - reason(k, v, result) */ var TRANSITION_TIMING_FUNCTION_VALIDATOR = function TRANSITION_TIMING_FUNCTION_VALIDATOR(v) { v = (v || '').toString() if (v.match(/^linear|ease|ease-in|ease-out|ease-in-out$/)) { return { value: v } } var match, ret var NUM_REGEXP = /^[-]?\d*\.?\d+$/ if (match = v.match(/^cubic-bezier\(\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*\)$/)) { /* istanbul ignore else */ if (match[1].match(NUM_REGEXP) && match[2].match(NUM_REGEXP) && match[3].match(NUM_REGEXP) && match[4].match(NUM_REGEXP)) { ret = [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]), parseFloat(match[4])].join(',') return { value: 'cubic-bezier(' + ret + ')' } } } return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported values are: `linear`|`ease`|`ease-in`|`ease-out`|`ease-in-out`|`cubic-bezier(n,n,n,n)`)' } } } //默认的校验,原样返回 var DEFAULT_VALIDATOR = function TRANSFORM_VALIDATOR(v) { return { value: v } } var TRANSFORM_VALIDATOR = function TRANSFORM_VALIDATOR(v) { // TODO return { value: v } } /** * generate a function to check whether a value is in `list` * - first value: default, could be removed * - not in `list`: error * * @param {Array} list * @return {function} a function(v) which returns a function to return * - value: string|null * - reason(k, v, result) */ function genEnumValidator(list) { return function ENUM_VALIDATOR(v) { var index = list.indexOf(v) if (index > 0) { return { value: v } } if (index === 0) { return { value: v, // 去掉默认值提醒 // reason: function reason(k, v, result) { // return 'NOTE: property value `' + v + '` is the DEFAULT value for `' + util.camelCaseToHyphened(k) + '` (could be removed)' // } } } else { return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported values are: `' + list.join('`|`') + '`)' } } } } } function BOX_SHADOW_VALIDATOR(v) { const items = v.split(/\s+/); if (items.length !== 4) { return { value: null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported like: `box-shadow:10px 10px 10px #000000`)' } } } let value = []; let reason = []; for (let i = 0; i < items.length; i++) { let res; if (i < 3) { res = LENGTH_VALIDATOR(items[i]); } else { res = COLOR_VALIDATOR([items[i]]); } if (res.value === null || res.value === undefined) { value = null reason = res.reason break } value.push(res.value) if (res.reason) { reason.push(res.reason) } } if (!value) { return { value: value, reason: reason } } else { const result = { value: value.join(' '), } if (reason.length > 0) { result.reason = function (k, v, result) { return reason.map(function (res) { if (typeof res === 'function') { return res(k, v, result) } }).join('\n') } } return result; } } function ANIMATION_ITERATION_VALIDATOR(value) { if (value === "infinite") { return { value } } return INTEGER_VALIDATOR(value); } // 支持 left/top/right/bottom/center以及百分比 function BACKGROUND_SIZE_VALIDATOR(value) { const reg = /^(left|top|right|bottom|center)$/; let values = value.split(/\s+/); let success = true; for(let i = 0; i < values.length; i++){ const v = values[i]; if(!reg.test(v)){ var match = v.match(LENGTH_REGEXP); if(match){ const unit = match[1]; success = unit === '%'; if(!success){ break; } } else { success = false; break; } } } if (success) { return { value: values.join(",") }; } else { return { value:null, reason: function reason(k, v, result) { return 'ERROR: property value `' + v + '` is not supported for `' + util.camelCaseToHyphened(k) + '` (supported like: `background-position:left top;support values are:`left`,`top`, `right`,`bottom`,`center`,`[number]%`)'; } } } } var PROP_NAME_GROUPS = { boxModel: { width: LENGTH_VALIDATOR, height: LENGTH_VALIDATOR, minWidth: LENGTH_VALIDATOR, minHeight: LENGTH_VALIDATOR, maxWidth: LENGTH_VALIDATOR, maxHeight: LENGTH_VALIDATOR, overflow: genEnumValidator(['visible', 'hidden']), padding: SHORTHAND_LENGTH_VALIDATOR, paddingLeft: LENGTH_VALIDATOR, paddingRight: LENGTH_VALIDATOR, paddingTop: LENGTH_VALIDATOR, paddingBottom: LENGTH_VALIDATOR, margin: SHORTHAND_LENGTH_VALIDATOR, marginLeft: LENGTH_VALIDATOR, marginRight: LENGTH_VALIDATOR, marginTop: LENGTH_VALIDATOR, marginBottom: LENGTH_VALIDATOR, borderWidth: LENGTH_VALIDATOR, borderLeftWidth: LENGTH_VALIDATOR, borderTopWidth: LENGTH_VALIDATOR, borderRightWidth: LENGTH_VALIDATOR, borderBottomWidth: LENGTH_VALIDATOR, borderColor: COLOR_VALIDATOR, borderLeftColor: COLOR_VALIDATOR, borderTopColor: COLOR_VALIDATOR, borderRightColor: COLOR_VALIDATOR, borderBottomColor: COLOR_VALIDATOR, borderStyle: genEnumValidator(['dotted', 'dashed', 'solid']), borderTopStyle: genEnumValidator(['dotted', 'dashed', 'solid']), borderRightStyle: genEnumValidator(['dotted', 'dashed', 'solid']), borderBottomStyle: genEnumValidator(['dotted', 'dashed', 'solid']), borderLeftStyle: genEnumValidator(['dotted', 'dashed', 'solid']), borderRadius: LENGTH_VALIDATOR, borderBottomLeftRadius: LENGTH_VALIDATOR, borderBottomRightRadius: LENGTH_VALIDATOR, borderTopLeftRadius: LENGTH_VALIDATOR, borderTopRightRadius: LENGTH_VALIDATOR, boxSizing: genEnumValidator(['border-box']), display: genEnumValidator(['flex']), boxShadow: BOX_SHADOW_VALIDATOR }, flexbox: { flex: NUMBER_VALIDATOR, flexWrap: genEnumValidator(['nowrap', 'wrap', 'wrap-reverse']), flexDirection: genEnumValidator(['column', 'row', 'column-reverse', 'row-reverse']), justifyContent: genEnumValidator(['flex-start', 'flex-end', 'center', 'space-between', 'space-around']), alignItems: genEnumValidator(['stretch', 'flex-start', 'flex-end', 'center']), alignSelf: genEnumValidator(['stretch', 'flex-start', 'flex-end', 'center']), alignContent: genEnumValidator(['auto', 'center', 'flex-start', 'flex-end', 'stretch']), }, position: { position: genEnumValidator(['relative', 'absolute', 'sticky', 'fixed']), top: LENGTH_VALIDATOR, bottom: LENGTH_VALIDATOR, left: LENGTH_VALIDATOR, right: LENGTH_VALIDATOR, zIndex: INTEGER_VALIDATOR }, common: { opacity: NUMBER_VALIDATOR, backgroundColor: COLOR_VALIDATOR, backgroundImage: ANYTHING_VALIDATOR, backgroundRepeat: genEnumValidator(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']), backgroundSize: genEnumValidator(['auto', 'contain', 'cover']), backgroundPosition: BACKGROUND_SIZE_VALIDATOR }, text: { lines: INTEGER_VALIDATOR, color: COLOR_VALIDATOR, fontSize: LENGTH_VALIDATOR, fontStyle: genEnumValidator(['normal', 'italic']), fontFamily: ANYTHING_VALIDATOR, fontWeight: genEnumValidator(['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']), textDecoration: genEnumValidator(['none', 'underline', 'line-through']), textAlign: genEnumValidator(['left', 'center', 'right']), textOverflow: genEnumValidator(['clip', 'ellipsis', 'unset', 'fade']), lineHeight: LENGTH_VALIDATOR, letterSpacing: LENGTH_VALIDATOR }, transition: { transitionProperty: TRANSITION_PROPERTY_VALIDATOR, transitionDuration: TRANSITION_INTERVAL_VALIDATOR, transitionDelay: TRANSITION_INTERVAL_VALIDATOR, transitionTimingFunction: TRANSITION_TIMING_FUNCTION_VALIDATOR }, transform: { transform: TRANSFORM_VALIDATOR, transformOrigin: SHORTHAND_LENGTH_VALIDATOR }, animation: { animationName: DEFAULT_VALIDATOR, animationDelay: TRANSITION_INTERVAL_VALIDATOR, animationDuration: TRANSITION_INTERVAL_VALIDATOR, animationDirection: genEnumValidator(['normal', 'reverse', 'alternate', 'alternate-reverse']), animationIterationCount: ANIMATION_ITERATION_VALIDATOR, animationTimingFunction: TRANSITION_TIMING_FUNCTION_VALIDATOR, animationFillMode: genEnumValidator(['forwards', 'backwards', 'both']), }, customized: { itemSize: LENGTH_VALIDATOR, itemColor: COLOR_VALIDATOR, itemSelectedColor: COLOR_VALIDATOR, textColor: COLOR_VALIDATOR, timeColor: COLOR_VALIDATOR, textHighlightColor: COLOR_VALIDATOR } } var SUGGESTED_PROP_NAME_GROUP = { background: 'backgroundColor' } var validatorMap = {} /** * flatten `PROP_NAME_GROUPS` to `validatorMap` */ function genValidatorMap() { var groupName, group, name for (groupName in PROP_NAME_GROUPS) { group = PROP_NAME_GROUPS[groupName] for (name in group) { validatorMap[name] = group[name] } } } genValidatorMap() /** * validate a CSS name/value pair * * @param {string} name camel cased * @param {string} value * @return {object} * - value:string or null * - log:{reason:string} or undefined */ function validate(name, value) { var result, log var validator = validatorMap[name] if (typeof validator === 'function') { if (typeof value !== 'function') { result = validator(value) } /* istanbul ignore else */ else { result = { value: value } } if (result.reason) { log = { reason: result.reason(name, value, result.value) } } } else { // ensure number type, no `px` /* istanbul ignore else */ if (typeof value !== 'function') { var match = value.match(LENGTH_REGEXP) if (match && (!match[1] || SUPPORT_CSS_UNIT.indexOf(match[1]) === -1)) { value = parseFloat(value) } } result = { value: value } var suggestedName = SUGGESTED_PROP_NAME_GROUP[name] var suggested = suggestedName ? ', suggest `' + util.camelCaseToHyphened(suggestedName) + '`' : '' log = { reason: 'WARNING: `' + util.camelCaseToHyphened(name) + '` is not a standard property name (may not be supported)' + suggested } } return { value: result.value, log: log } } module.exports = { BASIC_COLOR_KEYWORDS: BASIC_COLOR_KEYWORDS, EXTENDED_COLOR_KEYWORDS: EXTENDED_COLOR_KEYWORDS, LENGTH_VALIDATOR: LENGTH_VALIDATOR, COLOR_VALIDATOR: COLOR_VALIDATOR, NUMBER_VALIDATOR: NUMBER_VALIDATOR, INTEGER_VALIDATOR: INTEGER_VALIDATOR, genEnumValidator: genEnumValidator, TRANSITION_PROPERTY_VALIDATOR: TRANSITION_PROPERTY_VALIDATOR, TRANSITION_DURATION_VALIDATOR: TRANSITION_INTERVAL_VALIDATOR, TRANSITION_DELAY_VALIDATOR: TRANSITION_INTERVAL_VALIDATOR, TRANSITION_TIMING_FUNCTION_VALIDATOR: TRANSITION_TIMING_FUNCTION_VALIDATOR, PROP_NAME_GROUPS: PROP_NAME_GROUPS, map: validatorMap, validate: validate }