|
import * as util from '../util'; |
|
import * as is from '../is'; |
|
import * as math from '../math'; |
|
|
|
let styfn = {}; |
|
|
|
|
|
styfn.parse = function( name, value, propIsBypass, propIsFlat ){ |
|
let self = this; |
|
|
|
|
|
if( is.fn( value ) ){ |
|
return self.parseImplWarn( name, value, propIsBypass, propIsFlat ); |
|
} |
|
|
|
let flatKey = ( propIsFlat === 'mapping' || propIsFlat === true || propIsFlat === false || propIsFlat == null ) ? 'dontcare' : propIsFlat; |
|
let bypassKey = propIsBypass ? 't' : 'f'; |
|
let valueKey = '' + value; |
|
let argHash = util.hashStrings( name, valueKey, bypassKey, flatKey ); |
|
let propCache = self.propCache = self.propCache || []; |
|
let ret; |
|
|
|
if( !(ret = propCache[ argHash ]) ){ |
|
ret = propCache[ argHash ] = self.parseImplWarn( name, value, propIsBypass, propIsFlat ); |
|
} |
|
|
|
|
|
|
|
if( propIsBypass || propIsFlat === 'mapping' ){ |
|
|
|
ret = util.copy( ret ); |
|
|
|
if( ret ){ |
|
ret.value = util.copy( ret.value ); |
|
} |
|
} |
|
|
|
return ret; |
|
}; |
|
|
|
styfn.parseImplWarn = function( name, value, propIsBypass, propIsFlat ){ |
|
let prop = this.parseImpl( name, value, propIsBypass, propIsFlat ); |
|
|
|
if( !prop && value != null ){ |
|
util.warn(`The style property \`${name}: ${value}\` is invalid`); |
|
} |
|
|
|
if( prop && (prop.name === 'width' || prop.name === 'height') && value === 'label' ){ |
|
util.warn('The style value of `label` is deprecated for `' + prop.name + '`'); |
|
} |
|
|
|
return prop; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
styfn.parseImpl = function( name, value, propIsBypass, propIsFlat ){ |
|
let self = this; |
|
|
|
name = util.camel2dash( name ); |
|
|
|
let property = self.properties[ name ]; |
|
let passedValue = value; |
|
let types = self.types; |
|
|
|
if( !property ){ return null; } |
|
if( value === undefined ){ return null; } |
|
|
|
|
|
if( property.alias ){ |
|
property = property.pointsTo; |
|
name = property.name; |
|
} |
|
|
|
let valueIsString = is.string( value ); |
|
if( valueIsString ){ |
|
value = value.trim(); |
|
} |
|
|
|
let type = property.type; |
|
if( !type ){ return null; } |
|
|
|
|
|
if( propIsBypass && (value === '' || value === null) ){ |
|
return { |
|
name: name, |
|
value: value, |
|
bypass: true, |
|
deleteBypass: true |
|
}; |
|
} |
|
|
|
|
|
if( is.fn( value ) ){ |
|
return { |
|
name: name, |
|
value: value, |
|
strValue: 'fn', |
|
mapped: types.fn, |
|
bypass: propIsBypass |
|
}; |
|
} |
|
|
|
|
|
let data, mapData; |
|
if( !valueIsString || propIsFlat || value.length < 7 || value[1] !== 'a' ){ |
|
|
|
|
|
} else if(value.length >= 7 && value[0] === 'd' && ( data = new RegExp( types.data.regex ).exec( value ) )){ |
|
if( propIsBypass ){ return false; } |
|
|
|
let mapped = types.data; |
|
|
|
return { |
|
name: name, |
|
value: data, |
|
strValue: '' + value, |
|
mapped: mapped, |
|
field: data[1], |
|
bypass: propIsBypass |
|
}; |
|
|
|
} else if(value.length >= 10 && value[0] === 'm' && ( mapData = new RegExp( types.mapData.regex ).exec( value ) )){ |
|
if( propIsBypass ){ return false; } |
|
if( type.multiple ){ return false; } |
|
|
|
let mapped = types.mapData; |
|
|
|
|
|
if( !(type.color || type.number) ){ return false; } |
|
|
|
let valueMin = this.parse( name, mapData[4] ); |
|
if( !valueMin || valueMin.mapped ){ return false; } |
|
|
|
let valueMax = this.parse( name, mapData[5] ); |
|
if( !valueMax || valueMax.mapped ){ return false; } |
|
|
|
|
|
if( valueMin.pfValue === valueMax.pfValue || valueMin.strValue === valueMax.strValue ){ |
|
util.warn('`' + name + ': ' + value + '` is not a valid mapper because the output range is zero; converting to `' + name + ': ' + valueMin.strValue + '`'); |
|
|
|
return this.parse(name, valueMin.strValue); |
|
|
|
} else if( type.color ){ |
|
let c1 = valueMin.value; |
|
let c2 = valueMax.value; |
|
|
|
let same = c1[0] === c2[0] |
|
&& c1[1] === c2[1] |
|
&& c1[2] === c2[2] |
|
&& ( |
|
c1[3] === c2[3] |
|
|| ( |
|
(c1[3] == null || c1[3] === 1) |
|
&& |
|
(c2[3] == null || c2[3] === 1) |
|
) |
|
) |
|
; |
|
|
|
if( same ){ return false; } |
|
} |
|
|
|
return { |
|
name: name, |
|
value: mapData, |
|
strValue: '' + value, |
|
mapped: mapped, |
|
field: mapData[1], |
|
fieldMin: parseFloat( mapData[2] ), |
|
fieldMax: parseFloat( mapData[3] ), |
|
valueMin: valueMin.value, |
|
valueMax: valueMax.value, |
|
bypass: propIsBypass |
|
}; |
|
} |
|
|
|
if( type.multiple && propIsFlat !== 'multiple' ){ |
|
let vals; |
|
|
|
if( valueIsString ){ |
|
vals = value.split( /\s+/ ); |
|
} else if( is.array( value ) ){ |
|
vals = value; |
|
} else { |
|
vals = [ value ]; |
|
} |
|
|
|
if( type.evenMultiple && vals.length % 2 !== 0 ){ return null; } |
|
|
|
let valArr = []; |
|
let unitsArr = []; |
|
let pfValArr = []; |
|
let strVal = ''; |
|
let hasEnum = false; |
|
|
|
for( let i = 0; i < vals.length; i++ ){ |
|
let p = self.parse( name, vals[i], propIsBypass, 'multiple' ); |
|
|
|
hasEnum = hasEnum || is.string( p.value ); |
|
|
|
valArr.push( p.value ); |
|
pfValArr.push( p.pfValue != null ? p.pfValue : p.value ); |
|
unitsArr.push( p.units ); |
|
strVal += (i > 0 ? ' ' : '') + p.strValue; |
|
} |
|
|
|
if( type.validate && !type.validate( valArr, unitsArr ) ){ |
|
return null; |
|
} |
|
|
|
if( type.singleEnum && hasEnum ){ |
|
if( valArr.length === 1 && is.string( valArr[0] ) ){ |
|
return { |
|
name: name, |
|
value: valArr[0], |
|
strValue: valArr[0], |
|
bypass: propIsBypass |
|
}; |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
return { |
|
name: name, |
|
value: valArr, |
|
pfValue: pfValArr, |
|
strValue: strVal, |
|
bypass: propIsBypass, |
|
units: unitsArr |
|
}; |
|
} |
|
|
|
|
|
let checkEnums = function(){ |
|
for( let i = 0; i < type.enums.length; i++ ){ |
|
let en = type.enums[ i ]; |
|
|
|
if( en === value ){ |
|
return { |
|
name: name, |
|
value: value, |
|
strValue: '' + value, |
|
bypass: propIsBypass |
|
}; |
|
} |
|
} |
|
|
|
return null; |
|
}; |
|
|
|
|
|
if( type.number ){ |
|
let units; |
|
let implicitUnits = 'px'; |
|
|
|
if( type.units ){ |
|
units = type.units; |
|
} |
|
|
|
if( type.implicitUnits ){ |
|
implicitUnits = type.implicitUnits; |
|
} |
|
|
|
if( !type.unitless ){ |
|
if( valueIsString ){ |
|
let unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : ''); |
|
if( units ){ unitsRegex = units; } |
|
let match = value.match( '^(' + util.regex.number + ')(' + unitsRegex + ')?' + '$' ); |
|
|
|
if( match ){ |
|
value = match[1]; |
|
units = match[2] || implicitUnits; |
|
} |
|
|
|
} else if( !units || type.implicitUnits ){ |
|
units = implicitUnits; |
|
} |
|
} |
|
|
|
value = parseFloat( value ); |
|
|
|
|
|
if( isNaN( value ) && type.enums === undefined ){ |
|
return null; |
|
} |
|
|
|
|
|
|
|
if( isNaN( value ) && type.enums !== undefined ){ |
|
value = passedValue; |
|
|
|
return checkEnums(); |
|
} |
|
|
|
|
|
if( type.integer && !is.integer( value ) ){ |
|
return null; |
|
} |
|
|
|
|
|
if( ( type.min !== undefined && ( value < type.min || (type.strictMin && value === type.min) ) ) |
|
|| ( type.max !== undefined && ( value > type.max || (type.strictMax && value === type.max) ) ) |
|
){ |
|
return null; |
|
} |
|
|
|
let ret = { |
|
name: name, |
|
value: value, |
|
strValue: '' + value + (units ? units : ''), |
|
units: units, |
|
bypass: propIsBypass |
|
}; |
|
|
|
|
|
if( type.unitless || (units !== 'px' && units !== 'em') ){ |
|
ret.pfValue = value; |
|
} else { |
|
ret.pfValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) ); |
|
} |
|
|
|
|
|
if( units === 'ms' || units === 's' ){ |
|
ret.pfValue = units === 'ms' ? value : 1000 * value; |
|
} |
|
|
|
|
|
if( units === 'deg' || units === 'rad' ){ |
|
ret.pfValue = units === 'rad' ? value : math.deg2rad( value ); |
|
} |
|
|
|
|
|
if( units === '%' ){ |
|
ret.pfValue = value / 100; |
|
} |
|
|
|
return ret; |
|
|
|
} else if( type.propList ){ |
|
|
|
let props = []; |
|
let propsStr = '' + value; |
|
|
|
if( propsStr === 'none' ){ |
|
|
|
|
|
} else { |
|
|
|
let propsSplit = propsStr.split( /\s*,\s*|\s+/ ); |
|
for( let i = 0; i < propsSplit.length; i++ ){ |
|
let propName = propsSplit[ i ].trim(); |
|
|
|
if( self.properties[ propName ] ){ |
|
props.push( propName ); |
|
} else { |
|
util.warn('`' + propName + '` is not a valid property name'); |
|
} |
|
} |
|
|
|
if( props.length === 0 ){ return null; } |
|
} |
|
|
|
return { |
|
name: name, |
|
value: props, |
|
strValue: props.length === 0 ? 'none' : props.join(' '), |
|
bypass: propIsBypass |
|
}; |
|
|
|
} else if( type.color ){ |
|
let tuple = util.color2tuple( value ); |
|
|
|
if( !tuple ){ return null; } |
|
|
|
return { |
|
name: name, |
|
value: tuple, |
|
pfValue: tuple, |
|
strValue: 'rgb(' + tuple[0] + ',' + tuple[1] + ',' + tuple[2] + ')', |
|
bypass: propIsBypass |
|
}; |
|
|
|
} else if( type.regex || type.regexes ){ |
|
|
|
|
|
if( type.enums ){ |
|
let enumProp = checkEnums(); |
|
|
|
if( enumProp ){ return enumProp; } |
|
} |
|
|
|
let regexes = type.regexes ? type.regexes : [ type.regex ]; |
|
|
|
for( let i = 0; i < regexes.length; i++ ){ |
|
let regex = new RegExp( regexes[ i ] ); |
|
let m = regex.exec( value ); |
|
|
|
if( m ){ |
|
return { |
|
name: name, |
|
value: type.singleRegexMatchValue ? m[1] : m, |
|
strValue: '' + value, |
|
bypass: propIsBypass |
|
}; |
|
|
|
} |
|
} |
|
|
|
return null; |
|
|
|
} else if( type.string ){ |
|
|
|
return { |
|
name: name, |
|
value: '' + value, |
|
strValue: '' + value, |
|
bypass: propIsBypass |
|
}; |
|
|
|
} else if( type.enums ){ |
|
return checkEnums(); |
|
|
|
} else { |
|
return null; |
|
} |
|
|
|
}; |
|
|
|
export default styfn; |
|
|