// @flow
import {
RuleList,
type Plugin,
type RuleOptions,
type ContainerRule,
type StyleRule,
type BaseRule
} from 'jss'
const at = '@global'
const atPrefix = '@global '
class GlobalContainerRule implements ContainerRule {
type = 'global'
at: string = at
rules: RuleList
options: RuleOptions
key: string
isProcessed: boolean = false
constructor(key, styles, options: RuleOptions) {
this.key = key
this.options = options
this.rules = new RuleList({
...options,
parent: this
})
for (const selector in styles) {
this.rules.add(selector, styles[selector])
}
this.rules.process()
}
/**
* Get a rule.
*/
getRule(name) {
return this.rules.get(name)
}
/**
* Create and register rule, run plugins.
*/
addRule(name, style, options) {
const rule = this.rules.add(name, style, options)
this.options.jss.plugins.onProcessRule(rule)
return rule
}
/**
* Get index of a rule.
*/
indexOf(rule) {
return this.rules.indexOf(rule)
}
/**
* Generates a CSS string.
*/
toString() {
return this.rules.toString()
}
}
class GlobalPrefixedRule implements BaseRule {
type = 'global'
at: string = at
options: RuleOptions
rule: BaseRule | null
isProcessed: boolean = false
key: string
constructor(key, style, options) {
this.key = key
this.options = options
const selector = key.substr(atPrefix.length)
this.rule = options.jss.createRule(selector, style, {
...options,
parent: this
})
}
toString(options) {
return this.rule ? this.rule.toString(options) : ''
}
}
const separatorRegExp = /\s*,\s*/g
function addScope(selector, scope) {
const parts = selector.split(separatorRegExp)
let scoped = ''
for (let i = 0; i < parts.length; i++) {
scoped += `${scope} ${parts[i].trim()}`
if (parts[i + 1]) scoped += ', '
}
return scoped
}
function handleNestedGlobalContainerRule(rule) {
const {options, style} = rule
const rules = style ? style[at] : null
if (!rules) return
for (const name in rules) {
options.sheet.addRule(name, rules[name], {
...options,
selector: addScope(name, rule.selector)
})
}
delete style[at]
}
function handlePrefixedGlobalRule(rule) {
const {options, style} = rule
for (const prop in style) {
if (prop[0] !== '@' || prop.substr(0, at.length) !== at) continue
const selector = addScope(prop.substr(at.length), rule.selector)
options.sheet.addRule(selector, style[prop], {
...options,
selector
})
delete style[prop]
}
}
/**
* Convert nested rules to separate, remove them from original styles.
*
* @param {Rule} rule
* @api public
*/
export default function jssGlobal(): Plugin {
function onCreateRule(name, styles, options) {
if (!name) return null
if (name === at) {
return new GlobalContainerRule(name, styles, options)
}
if (name[0] === '@' && name.substr(0, atPrefix.length) === atPrefix) {
return new GlobalPrefixedRule(name, styles, options)
}
const {parent} = options
if (parent) {
if (
parent.type === 'global' ||
(parent.options.parent && parent.options.parent.type === 'global')
) {
options.scoped = false
}
}
if (options.scoped === false) {
options.selector = name
}
return null
}
function onProcessRule(rule) {
if (rule.type !== 'style') return
handleNestedGlobalContainerRule(((rule: any): StyleRule))
handlePrefixedGlobalRule(((rule: any): StyleRule))
}
return {onCreateRule, onProcessRule}
}