( config: InterpolatorConfig<string> )
| 38 | * "rotate(0deg) translate(2px, 3px)" // CSS transforms |
| 39 | */ |
| 40 | export const createStringInterpolator = ( |
| 41 | config: InterpolatorConfig<string> |
| 42 | ) => { |
| 43 | if (!namedColorRegex) |
| 44 | namedColorRegex = G.colors |
| 45 | ? // match color names, ignore partial matches |
| 46 | new RegExp(`(${Object.keys(G.colors).join('|')})(?!\\w)`, 'g') |
| 47 | : // never match |
| 48 | /^\b$/ |
| 49 | |
| 50 | // Convert colors to rgba(...) |
| 51 | const output = config.output.map(value => { |
| 52 | return getFluidValue(value) |
| 53 | .replace(cssVariableRegex, variableToRgba) |
| 54 | .replace(colorRegex, colorToRgba) |
| 55 | .replace(namedColorRegex, colorToRgba) |
| 56 | }) |
| 57 | |
| 58 | // Convert ["1px 2px", "0px 0px"] into [[1, 2], [0, 0]] |
| 59 | const keyframes = output.map(value => getNumbers(value).map(Number)) |
| 60 | |
| 61 | // Convert ["1px 2px", "0px 0px"] into [[1, 0], [2, 0]] |
| 62 | const outputRanges = keyframes[0].map((_, i) => |
| 63 | keyframes.map(values => { |
| 64 | if (!(i in values)) { |
| 65 | throw Error('The arity of each "output" value must be equal') |
| 66 | } |
| 67 | return values[i] |
| 68 | }) |
| 69 | ) |
| 70 | |
| 71 | // Create an interpolator for each animated number |
| 72 | const interpolators = outputRanges.map(output => |
| 73 | createInterpolator({ ...config, output }) |
| 74 | ) |
| 75 | |
| 76 | const inputRange = config.range || [0, 1] |
| 77 | |
| 78 | // Per number-position, the shared fractional-digit count across all |
| 79 | // keyframes — or `null` when keyframes disagree or every keyframe is |
| 80 | // a whole number. Whole-number keyframes are left untouched so that |
| 81 | // values like the alpha channel in `rgba(…, 0)` → `rgba(…, 1)` keep |
| 82 | // their natural sub-frame precision. |
| 83 | const allTokens = output.map(value => getNumbers(value)) |
| 84 | const decimalCounts = allTokens[0].map((_, pos) => { |
| 85 | const counts = allTokens.map(tokens => { |
| 86 | const token = tokens[pos] |
| 87 | const dot = token.indexOf('.') |
| 88 | return dot === -1 ? 0 : token.length - dot - 1 |
| 89 | }) |
| 90 | return counts.every(c => c === counts[0]) && counts[0] > 0 |
| 91 | ? counts[0] |
| 92 | : null |
| 93 | }) |
| 94 | |
| 95 | // Use the first `output` as a template for each call |
| 96 | return (input: number) => { |
| 97 | // When `input` lands exactly on a keyframe, return that keyframe's |
no test coverage detected
searching dependent graphs…