Skip to content

Commit

Permalink
feat: change cssToJs to return empty string for non-animatable proper…
Browse files Browse the repository at this point in the history
…ties

BREAKING CHANGE: cssToJs now returns empty string for non-animatable properties
  • Loading branch information
apasov committed May 13, 2020
1 parent 63cf062 commit b43e2a6
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 54 deletions.
2 changes: 1 addition & 1 deletion dist/animatable.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/animatable.js.map

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@ export default {
sourcemap: true,
},
plugins: [
commonjs(),
commonjs({
namedExports: {
[require.resolve('css-tree')]: [
'parse', //
'walk',
'generate',
'createCustomError',
'lexer',
],
},
}),
json(),
resolve(),
terser({
Expand Down
7 changes: 3 additions & 4 deletions src/arrays.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import properties from './properties'
import functions from './functions'

export default {
properties: properties.map((val) => functions.cssToJs(val)),
propertiesCSS: properties,
propertiesJS: properties.map((val) => functions.cssToJs(val, false)),
properties: functions.properties.map((val) => functions.cssToJs(val)),
propertiesCSS: functions.properties,
propertiesJS: functions.properties.map((val) => functions.cssToJs(val, false)),
}
238 changes: 215 additions & 23 deletions src/functions.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,224 @@
import properties from './properties'
import isNumber from 'lodash.isnumber'
import isPlainObject from 'lodash.isplainobject'
import isString from 'lodash.isstring'
import { parse, walk, lexer } from 'css-tree'
import nonAnimatable from './nonAnimatable.json'
import popularIndex from './popularIndex.json'
import mdnProperties from 'css-tree/node_modules/mdn-data/css/properties.json'
import mdnSyntaxes from 'css-tree/node_modules/mdn-data/css/syntaxes.json'
import patch from 'css-tree/data/patch.json'

export default {
cssToJs: (value, webAnimationsAPI = true) => {
let property = value.toLowerCase()
if (webAnimationsAPI) {
if (property === 'float' || property === 'offset') {
property = 'css-' + property
const properties = []
Object.keys(lexer.properties).forEach((key) => {
if (!nonAnimatable.includes(key)) {
properties.push(key)
}
})

const isAnimatable = (value, returnCssProperty = false) => {
let property = value.toLowerCase().trim()
if (property === 'cssoffset') {
property = property.substr(3)
}
if (properties.includes(property)) {
return returnCssProperty ? property : true
}
if (property.includes('-')) {
return returnCssProperty ? '' : false
}
property = property.replace(/-/g, '')
for (const prop of properties) {
if (property === prop.replace(/-/g, '')) {
return returnCssProperty ? prop : true
}
}
return returnCssProperty ? '' : false
}

const cssToJs = (value, webAnimationsAPI = true) => {
let property = isAnimatable(value, true)
if (!property) {
return ''
}
if (webAnimationsAPI) {
if (property === 'float' || property === 'offset') {
property = 'css-' + property
}
}
return property.replace(/-([a-z])/g, (match, letter) => {
return letter.toUpperCase()
})
}

const jsToCss = (value) => {
return isAnimatable(value, true)
}

const popular = (start = 0, end = 10) => {
const popular = []
let notInPopular
if (end >= popularIndex.length) {
notInPopular = properties.filter((property) => !popularIndex.includes(properties.indexOf(property)))
}
for (let i = start; i < end; i++) {
if (popularIndex[i] || popularIndex[i] === 0) {
popular.push(properties[popularIndex[i]])
} else {
const property = notInPopular[i - popularIndex.length]
if (!property) {
break
}
popular.push(property)
}
return property.replace(/-([a-z])/g, (match, letter) => {
return letter.toUpperCase()
}
return popular
}

const syntaxes = { main: '', links: {}, order: [] }

const getSyntaxes = (syntax) => {
const propertyMatches = syntax.match(/<'[a-zA-Z0-9-()]+'>/g)
if (Array.isArray(propertyMatches)) {
propertyMatches.forEach((key) => {
const prop = key.slice(2, -2)
const propertySyntax = mdnProperties[prop]
syntaxes[key] = propertySyntax.syntax
syntaxes.order.push(key)
getSyntaxes(propertySyntax.syntax)
})
}
const syntaxMatches = syntax.match(/<[a-zA-Z0-9-()]+>/g)
if (Array.isArray(syntaxMatches)) {
syntaxMatches.forEach((key) => {
const prop = key.slice(1, -1)
if (!syntaxes[key]) {
const syntaxSyntax = mdnSyntaxes[prop]
const patchSyntax = patch.syntaxes[prop]
if (syntaxSyntax) {
syntaxes[key] = syntaxSyntax.syntax
syntaxes.order.push(key)
getSyntaxes(syntaxSyntax.syntax)
} else if (patchSyntax) {
syntaxes[key] = patchSyntax.syntax
syntaxes.order.push(key)
getSyntaxes(patchSyntax.syntax)
} else if (!['url-token', 'any-value', 'function-token', 'hex-color', 'declaration-value', 'hash-token', 'zero'].includes(prop)) {
syntaxes.links[key] = 'https://developer.mozilla.org/docs/Web/CSS/' + prop.replace(/flex/, 'flex_value')
}
}
})
},
}
}

isAnimatable: (value, returnCssProperty = false) => {
let property = value.toLowerCase()
if (property.substr(0, 3) === 'css') {
property = property.substr(3)
}
if (properties.includes(property)) {
return returnCssProperty ? property : true
const syntax = (value) => {
for (const member in syntaxes) delete syntaxes[member]
syntaxes.main = ''
syntaxes.links = {}
syntaxes.order = []
const property = isAnimatable(value, true)
if (property) {
const syntax = mdnProperties[property].syntax
syntaxes.main = syntax
getSyntaxes(syntax)
} else {
syntaxes.main = `Error: ${value} is not an animatable CSS property.`
}
return syntaxes
}

const validate = (obj, returnError = true) => {
const errors = []
if (!isPlainObject(obj)) {
errors.push({ name: 'TypeError', message: 'Argument is not a plain object.' })
} else {
for (let [key, value] of Object.entries(obj)) {
if (!isString(value) && !isNumber(value)) {
errors.push({ name: 'TypeError', message: 'Property value must be a string or a number. ' + typeof value + ' given.' })
} else {
const property = isAnimatable(key, true)
if (!property) {
errors.push({ name: 'MatchError', message: key + ' is not an animatable CSS property.' })
} else {
let ast
try {
ast = parse(`${property}: ${value}`, {
context: 'declaration',
onParseError: (error) => {
errors.push(error)
},
})
walk(ast, {
visit: 'Declaration',
enter: function (node) {
const error = lexer.matchDeclaration(node).error
if (error) {
errors.push(error)
}
},
})
} catch (error) {
errors.push(error)
}
}
}
}
property = property.replace(/-/g, '')
for (const prop of properties) {
if (property === prop.replace(/-/g, '')) {
return returnCssProperty ? prop : true
}
return !errors.length ? true : returnError ? errors[errors.length - 1] : false
}

const propertyFormat = (property, format) => {
switch (format) {
case 'css':
property = jsToCss(property)
break
case 'js':
property = cssToJs(property, false)
break
default:
property = cssToJs(property)
}
return property
}

const sanitize = (obj, format = 'waapi') => {
format = format.toString().toLowerCase()
let sanitized = {}
if (Array.isArray(obj)) {
sanitized = []
obj.forEach((key) => {
const property = isAnimatable(key, true)
if (property) {
sanitized.push(propertyFormat(property, format))
}
})
} else if (isPlainObject(obj)) {
for (let [key, value] of Object.entries(obj)) {
const object = {}
object[key] = value
if (validate(object, false)) {
sanitized[propertyFormat(key, format)] = value
}
}
return returnCssProperty ? '' : false
},
} else if (isString(obj)) {
switch (format) {
case 'css':
return jsToCss(obj)
case 'js':
return cssToJs(obj, false)
default:
return cssToJs(obj)
}
}
return sanitized
}

export default {
properties: properties,
cssToJs: cssToJs,
jsToCss: jsToCss,
isAnimatable: isAnimatable,
popular: popular,
syntax: syntax,
validate: validate,
sanitize: sanitize,
}
4 changes: 1 addition & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ export const properties = arrays.properties
export const propertiesCSS = arrays.propertiesCSS
export const propertiesJS = arrays.propertiesJS
export const cssToJs = functions.cssToJs
export const jsToCss = (value) => {
return functions.isAnimatable(value, true)
}
export const jsToCss = functions.jsToCss
export const isAnimatable = functions.isAnimatable
export const popular = functions.popular
export const syntax = functions.syntax
Expand Down
Loading

0 comments on commit b43e2a6

Please sign in to comment.