150 lines
3.4 KiB
JavaScript
150 lines
3.4 KiB
JavaScript
/* @flow */
|
|
import warning from 'tiny-warning'
|
|
import RuleList from '../RuleList'
|
|
import type {
|
|
CSSKeyframesRule,
|
|
JssStyle,
|
|
RuleOptions,
|
|
ToCssOptions,
|
|
ContainerRule,
|
|
KeyframesMap,
|
|
Plugin
|
|
} from '../types'
|
|
import escape from '../utils/escape'
|
|
|
|
const defaultToStringOptions = {
|
|
indent: 1,
|
|
children: true
|
|
}
|
|
|
|
const nameRegExp = /@keyframes\s+([\w-]+)/
|
|
|
|
/**
|
|
* Rule for @keyframes
|
|
*/
|
|
export class KeyframesRule implements ContainerRule {
|
|
type = 'keyframes'
|
|
|
|
at: string = '@keyframes'
|
|
|
|
key: string
|
|
|
|
name: string
|
|
|
|
id: string
|
|
|
|
rules: RuleList
|
|
|
|
options: RuleOptions
|
|
|
|
isProcessed: boolean = false
|
|
|
|
renderable: ?CSSKeyframesRule
|
|
|
|
constructor(key: string, frames: Object, options: RuleOptions) {
|
|
const nameMatch = key.match(nameRegExp)
|
|
if (nameMatch && nameMatch[1]) {
|
|
this.name = nameMatch[1]
|
|
} else {
|
|
this.name = 'noname'
|
|
warning(false, `[JSS] Bad keyframes name ${key}`)
|
|
}
|
|
this.key = `${this.type}-${this.name}`
|
|
this.options = options
|
|
const {scoped, sheet, generateId} = options
|
|
this.id = scoped === false ? this.name : escape(generateId(this, sheet))
|
|
this.rules = new RuleList({...options, parent: this})
|
|
|
|
for (const name in frames) {
|
|
this.rules.add(name, frames[name], {
|
|
...options,
|
|
parent: this
|
|
})
|
|
}
|
|
|
|
this.rules.process()
|
|
}
|
|
|
|
/**
|
|
* Generates a CSS string.
|
|
*/
|
|
toString(options?: ToCssOptions = defaultToStringOptions): string {
|
|
if (options.indent == null) options.indent = defaultToStringOptions.indent
|
|
if (options.children == null) options.children = defaultToStringOptions.children
|
|
if (options.children === false) {
|
|
return `${this.at} ${this.id} {}`
|
|
}
|
|
let children = this.rules.toString(options)
|
|
if (children) children = `\n${children}\n`
|
|
return `${this.at} ${this.id} {${children}}`
|
|
}
|
|
}
|
|
|
|
const keyRegExp = /@keyframes\s+/
|
|
|
|
const refRegExp = /\$([\w-]+)/g
|
|
|
|
const findReferencedKeyframe = (val, keyframes) => {
|
|
if (typeof val === 'string') {
|
|
return val.replace(refRegExp, (match, name) => {
|
|
if (name in keyframes) {
|
|
return keyframes[name]
|
|
}
|
|
|
|
warning(false, `[JSS] Referenced keyframes rule "${name}" is not defined.`)
|
|
|
|
return match
|
|
})
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
/**
|
|
* Replace the reference for a animation name.
|
|
*/
|
|
const replaceRef = (style: JssStyle, prop: string, keyframes: KeyframesMap) => {
|
|
const value = style[prop]
|
|
const refKeyframe = findReferencedKeyframe(value, keyframes)
|
|
|
|
if (refKeyframe !== value) {
|
|
style[prop] = refKeyframe
|
|
}
|
|
}
|
|
|
|
const plugin: Plugin = {
|
|
onCreateRule(key, frames, options) {
|
|
return typeof key === 'string' && keyRegExp.test(key)
|
|
? new KeyframesRule(key, frames, options)
|
|
: null
|
|
},
|
|
|
|
// Animation name ref replacer.
|
|
onProcessStyle: (style, rule, sheet) => {
|
|
if (rule.type !== 'style' || !sheet) return style
|
|
|
|
if ('animation-name' in style) replaceRef(style, 'animation-name', sheet.keyframes)
|
|
if ('animation' in style) replaceRef(style, 'animation', sheet.keyframes)
|
|
return style
|
|
},
|
|
|
|
onChangeValue(val, prop, rule) {
|
|
const {sheet} = rule.options
|
|
|
|
if (!sheet) {
|
|
return val
|
|
}
|
|
|
|
switch (prop) {
|
|
case 'animation':
|
|
return findReferencedKeyframe(val, sheet.keyframes)
|
|
case 'animation-name':
|
|
return findReferencedKeyframe(val, sheet.keyframes)
|
|
default:
|
|
return val
|
|
}
|
|
}
|
|
}
|
|
|
|
export default plugin
|