-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathparse.js
More file actions
146 lines (127 loc) · 4.44 KB
/
parse.js
File metadata and controls
146 lines (127 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import {
compact,
isArray,
map,
} from 'lodash'
// css properties that can have values joined by spaces
const spaceCombinedProps = [
'-moz-outline-radius',
'-webkit-text-stroke',
'background',
'border',
'border-bottom',
'border-color',
'border-left',
'border-radius',
'border-right',
'border-spacing',
'border-top',
'border-width',
'margin',
'outline',
'padding',
'transform',
]
// splits a css property value into multiple values
export const split = (key, value) => {
if (spaceCombinedProps.indexOf(key) >= 0) {
const arr = value.split(' ')
return arr.length === 1 ? arr[0] : arr
}
return value
}
// combines multiple values to a single css property value
export const combine = (key, value) => {
return isArray(value) && spaceCombinedProps.indexOf(key) >= 0
? value.join(' ')
: value
}
// this splits css numbers from units.
//
// according to the css spec, a number can either be an integer or it can be
// zero or more digits followed by a dot followed by one or more digits.
// assuming the unit can be any sequence of lowercase letters (including none)
//
// returns an object with `unit`, `value`, and `wrapper` properties.
// `wrapper` is a string if the value is parenthesized,
// ie `translate(30px)` would make `wrapper === 'translate'`.
export const parseNumber = (number) => {
const regex = /^(?:([a-zA-Z]+)\()?([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)\)?$/
const [, wrapper, value, unit] = `${number}`.match(regex) || []
return value ? { unit, value: Number(value), wrapper } : undefined
}
// check if a string is a hex color. returns an array of three integers
// between 0 and 255 for the three rgb components if it is.
export const parseHexColor = (color) => {
let [ , hex ] = `${color}`.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i) || []
if (hex) {
hex = hex.length === 3 ? map(hex, (v) => `${v}${v}`).join('') : hex
return map(hex.match(/.{1,2}/g), (v) => parseInt(v, 16))
}
}
// parses css startValue and endValue.
//
// returns an object consisting of start and end values for interpolation
// and an additional unit if the start and end values are numeric. in cases
// of unchanged values, returns the fixed value
export const parseValues = (startValue, endValue) => {
// when both values are equal, the value is fixed
if (startValue === endValue) {
return { fixed: startValue }
}
// check if both values are numeric with optional unit
const numericStart = parseNumber(startValue)
const numericEnd = parseNumber(endValue)
if (numericStart && numericEnd) {
const startUnit = numericStart.unit
const endUnit = numericEnd.unit
// when start unit is the same as end unit or one of them is unitless
if (startUnit === endUnit || !startUnit || !endUnit) {
return {
unit: startUnit || endUnit,
start: numericStart.value,
end: numericEnd.value,
// for parenthesized values, like 'translate(30px)'
wrapper: numericStart.wrapper,
}
}
}
// check of both values are hex rgb colors
const colorStart = parseHexColor(startValue)
const colorEnd = parseHexColor(endValue)
if (colorStart && colorEnd) {
return { rgb: [ colorStart, colorEnd ]}
}
}
// returns an object that lists the property, unit, start and end values of
// the animatable properties based on the given arguments.
//
// to be animatable, a property has to be present on both `startStyles` and
// `endProps` with a numeric value and same unit for both or unitless for one
// of them which will then take the unit of the other.
export const parseStyles = (startStyles, endStyles) => {
let result = []
for (let prop in startStyles) {
// only animate props that exist in both start and end styles
if (!(prop in endStyles)) {
break
}
// in case of combined values, split them!
const startValues = [].concat(split(prop, startStyles[prop]))
const endValues = [].concat(split(prop, endStyles[prop]))
// only animate props that have the same number of values
if (startValues.length !== endValues.length) {
break
}
// parse start and end value combinations
const parsedValues = compact(map(startValues, (value, key) => {
const parsed = parseValues(value, endValues[key])
return parsed ? { prop, ...parsed } : null
}))
// when parsing was successful for every combination, use the results
if (parsedValues.length === startValues.length) {
result = result.concat(parsedValues)
}
}
return result
}