// @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} }