/* @flow */ import isInBrowser from 'is-in-browser' import StyleSheet from './StyleSheet' import PluginsRegistry from './PluginsRegistry' import sheets from './sheets' import {plugins as internalPlugins} from './plugins/index' import createGenerateIdDefault from './utils/createGenerateId' import createRule from './utils/createRule' import DomRenderer from './DomRenderer' import type { Rule, RuleFactoryOptions, RuleOptions, StyleSheetFactoryOptions, Plugin, JssOptions, InternalJssOptions, JssStyle } from './types' import type {GenerateId} from './utils/createGenerateId' let instanceCounter = 0 export default class Jss { id = instanceCounter++ version = process.env.VERSION plugins = new PluginsRegistry() options: InternalJssOptions = { id: {minify: false}, createGenerateId: createGenerateIdDefault, Renderer: isInBrowser ? DomRenderer : null, plugins: [] } generateId: GenerateId = createGenerateIdDefault({minify: false}) constructor(options?: JssOptions) { for (let i = 0; i < internalPlugins.length; i++) { this.plugins.use(internalPlugins[i], {queue: 'internal'}) } this.setup(options) } /** * Prepares various options, applies plugins. * Should not be used twice on the same instance, because there is no plugins * deduplication logic. */ setup(options?: JssOptions = {}): this { if (options.createGenerateId) { this.options.createGenerateId = options.createGenerateId } if (options.id) { this.options.id = { ...this.options.id, ...options.id } } if (options.createGenerateId || options.id) { this.generateId = this.options.createGenerateId(this.options.id) } if (options.insertionPoint != null) this.options.insertionPoint = options.insertionPoint if ('Renderer' in options) { this.options.Renderer = options.Renderer } // eslint-disable-next-line prefer-spread if (options.plugins) this.use.apply(this, options.plugins) return this } /** * Create a Style Sheet. */ createStyleSheet(styles: Object, options: StyleSheetFactoryOptions = {}): StyleSheet { let {index} = options if (typeof index !== 'number') { index = sheets.index === 0 ? 0 : sheets.index + 1 } const sheet = new StyleSheet(styles, { ...options, jss: this, generateId: options.generateId || this.generateId, insertionPoint: this.options.insertionPoint, Renderer: this.options.Renderer, index }) this.plugins.onProcessSheet(sheet) return sheet } /** * Detach the Style Sheet and remove it from the registry. */ removeStyleSheet(sheet: StyleSheet): this { sheet.detach() sheets.remove(sheet) return this } /** * Create a rule without a Style Sheet. */ createRule(name?: string, style?: JssStyle = {}, options?: RuleFactoryOptions = {}): Rule | null { // Enable rule without name for inline styles. if (typeof name === 'object') { return this.createRule(undefined, name, style) } const ruleOptions: RuleOptions = {...options, jss: this, Renderer: this.options.Renderer} if (!ruleOptions.generateId) ruleOptions.generateId = this.generateId if (!ruleOptions.classes) ruleOptions.classes = {} if (!ruleOptions.keyframes) ruleOptions.keyframes = {} const rule = createRule(name, style, ruleOptions) if (rule) this.plugins.onProcessRule(rule) return rule } /** * Register plugin. Passed function will be invoked with a rule instance. */ use(...plugins: Array<Plugin>): this { plugins.forEach(plugin => { this.plugins.use(plugin) }) return this } }