UNPKG

react-ionicons

Version:

A React SVG ionicon component

172 lines (140 loc) 5.03 kB
// @flow import React from 'react' import BrowserStyleSheet from './BrowserStyleSheet' import ServerStyleSheet from './ServerStyleSheet' export const SC_ATTR = 'data-styled-components' export const LOCAL_ATTR = 'data-styled-components-is-local' export const CONTEXT_KEY = '__styled-components-stylesheet__' export interface Tag { isLocal: boolean, components: { [string]: Object }, isFull(): boolean, addComponent(componentId: string): void, inject(componentId: string, css: string, name: ?string): void, toHTML(): string, toReactElement(key: string): React.Element<*>, clone(): Tag, } let instance = null // eslint-disable-next-line no-use-before-define export const clones: Array<StyleSheet> = [] export default class StyleSheet { tagConstructor: (boolean) => Tag tags: Array<Tag> names: { [string]: boolean } hashes: { [string]: string } = {} deferredInjections: { [string]: string } = {} componentTags: { [string]: Tag } // helper for `ComponentStyle` to know when it cache static styles. // staticly styled-component can not safely cache styles on the server // without all `ComponentStyle` instances saving a reference to the // the styleSheet instance they last rendered with, // or listening to creation / reset events. otherwise you might create // a component with one stylesheet and render it another api response // with another, losing styles on from your server-side render. stylesCacheable = typeof document !== 'undefined' constructor(tagConstructor: (boolean) => Tag, tags: Array<Tag> = [], names: { [string]: boolean } = {}, ) { this.tagConstructor = tagConstructor this.tags = tags this.names = names this.constructComponentTagMap() } constructComponentTagMap() { this.componentTags = {} this.tags.forEach(tag => { Object.keys(tag.components).forEach(componentId => { this.componentTags[componentId] = tag }) }) } /* Best level of caching—get the name from the hash straight away. */ getName(hash: any) { return this.hashes[hash.toString()] } /* Second level of caching—if the name is already in the dom, don't * inject anything and record the hash for getName next time. */ alreadyInjected(hash: any, name: string) { if (!this.names[name]) return false this.hashes[hash.toString()] = name return true } /* Third type of caching—don't inject components' componentId twice. */ hasInjectedComponent(componentId: string) { return !!this.componentTags[componentId] } deferredInject(componentId: string, isLocal: boolean, css: string) { if (this === instance) { clones.forEach(clone => { clone.deferredInject(componentId, isLocal, css) }) } this.getOrCreateTag(componentId, isLocal) this.deferredInjections[componentId] = css } inject(componentId: string, isLocal: boolean, css: string, hash: ?any, name: ?string) { if (this === instance) { clones.forEach(clone => { clone.inject(componentId, isLocal, css) }) } const tag = this.getOrCreateTag(componentId, isLocal) const deferredInjection = this.deferredInjections[componentId] if (deferredInjection) { tag.inject(componentId, deferredInjection) delete this.deferredInjections[componentId] } tag.inject(componentId, css, name) if (hash && name) { this.hashes[hash.toString()] = name } } toHTML() { return this.tags.map(tag => tag.toHTML()).join('') } toReactElements() { return this.tags.map((tag, i) => tag.toReactElement(`sc-${i}`)) } getOrCreateTag(componentId: string, isLocal: boolean) { const existingTag = this.componentTags[componentId] if (existingTag) { return existingTag } const lastTag = this.tags[this.tags.length - 1] const componentTag = (!lastTag || lastTag.isFull() || lastTag.isLocal !== isLocal) ? this.createNewTag(isLocal) : lastTag this.componentTags[componentId] = componentTag componentTag.addComponent(componentId) return componentTag } createNewTag(isLocal: boolean) { const newTag = this.tagConstructor(isLocal) this.tags.push(newTag) return newTag } static get instance() { return instance || (instance = StyleSheet.create()) } static reset(isServer: ?boolean) { instance = StyleSheet.create(isServer) } /* We can make isServer totally implicit once Jest 20 drops and we * can change environment on a per-test basis. */ static create(isServer: ?boolean = typeof document === 'undefined') { return (isServer ? ServerStyleSheet : BrowserStyleSheet).create() } static clone(oldSheet: StyleSheet) { const newSheet = new StyleSheet( oldSheet.tagConstructor, oldSheet.tags.map(tag => tag.clone()), { ...oldSheet.names }, ) newSheet.hashes = { ...oldSheet.hashes } newSheet.deferredInjections = { ...oldSheet.deferredInjections } clones.push(newSheet) return newSheet } }