diff --git a/.flowconfig b/.flowconfig index 5921625..c454a8c 100644 --- a/.flowconfig +++ b/.flowconfig @@ -7,3 +7,4 @@ [options] all=true +suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe diff --git a/flow-typed/npm/raf_vx.x.x.js b/flow-typed/npm/raf_vx.x.x.js new file mode 100644 index 0000000..4930462 --- /dev/null +++ b/flow-typed/npm/raf_vx.x.x.js @@ -0,0 +1,52 @@ +// flow-typed signature: 4de6dbdd4439b8db48934b70acae04b6 +// flow-typed version: <>/raf_v3.3.0/flow_v0.44.2 + +/** + * This is an autogenerated libdef stub for: + * + * 'raf' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'raf' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'raf/polyfill' { + declare module.exports: any; +} + +declare module 'raf/test' { + declare module.exports: any; +} + +declare module 'raf/window' { + declare module.exports: any; +} + +// Filename aliases +declare module 'raf/index' { + declare module.exports: $Exports<'raf'>; +} +declare module 'raf/index.js' { + declare module.exports: $Exports<'raf'>; +} +declare module 'raf/polyfill.js' { + declare module.exports: $Exports<'raf/polyfill'>; +} +declare module 'raf/test.js' { + declare module.exports: $Exports<'raf/test'>; +} +declare module 'raf/window.js' { + declare module.exports: $Exports<'raf/window'>; +} diff --git a/package.json b/package.json index ebb03f1..9e50ea1 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "dependencies": { "is-react-prop": "^0.0.3", "jss": "^7.1.0", - "jss-preset-default": "^2.0.0" + "jss-preset-default": "^2.0.0", + "raf": "^3.3.0" }, "peerDependencies": { "react": "^15.5.4", diff --git a/src/createStyled.js b/src/createStyled.js index b079d8a..4a837dd 100644 --- a/src/createStyled.js +++ b/src/createStyled.js @@ -1,4 +1,5 @@ import styled from './styled' +import sheetsObserver from './utils/sheetsObserver' import type { BaseStylesType, @@ -9,11 +10,25 @@ import type { TagNameOrStyledElementType } from './types' +sheetsObserver.listen() + const createStyled = (jss: Function) => ( baseStyles: BaseStylesType = {} ): StyledType => { let staticSheet let dynamicSheet + let dynamicSheetId + + const addRule = (name: string, style: ComponentStyleType, data: Object) => { + if (data) { + dynamicSheet.detach().addRule(name, style) + dynamicSheet.update(name, data) + sheetsObserver.update(dynamicSheetId) + } + else { + staticSheet.addRule(name, style) + } + } const mountSheets = () => { if (!staticSheet) { @@ -25,6 +40,7 @@ const createStyled = (jss: Function) => ( link: true, meta: 'DynamicComponentSheet', }).attach() + dynamicSheetId = sheetsObserver.add(dynamicSheet) } return {staticSheet, dynamicSheet} @@ -40,7 +56,7 @@ const createStyled = (jss: Function) => ( const elementStyle = {...style, ...ownStyle} - return styled({tagName, baseStyles, elementStyle, mountSheets}) + return styled({tagName, baseStyles, elementStyle, mountSheets, addRule}) }, {mountSheets, styles: baseStyles}) } diff --git a/src/injectStyled.js b/src/injectStyled.js index c53d2d1..3e3174a 100644 --- a/src/injectStyled.js +++ b/src/injectStyled.js @@ -11,7 +11,7 @@ const injectStyled = (styled: StyledType) => (InnerComponent: ReactClass) = const classes = [...classNames] .reduce((acc, name) => ({ ...acc, - [name]: composeClasses(staticSheet.classes[name], dynamicSheet.classes[name]), + [name]: composeClasses([staticSheet.classes[name], dynamicSheet.classes[name]]), }), {}) return (props: Object) => createElement(InnerComponent, {classes, ...props}) diff --git a/src/styled.js b/src/styled.js index 4fba7c7..223d8e5 100644 --- a/src/styled.js +++ b/src/styled.js @@ -15,10 +15,11 @@ import type { type StyledArgs = { tagName: string, elementStyle: ComponentStyleType, - mountSheets: Function + mountSheets: Function, + addRule: Function } -const styled = ({tagName, elementStyle, mountSheets}: StyledArgs) => { +const styled = ({tagName, elementStyle, mountSheets, addRule}: StyledArgs) => { const dynamicStyle = getDynamicStyles(elementStyle) const staticTagName = generateTagName(tagName) @@ -43,17 +44,11 @@ const styled = ({tagName, elementStyle, mountSheets}: StyledArgs) => { Object.assign(this, mountSheets()) if (!this.staticSheet.getRule(staticTagName)) { - this.staticSheet.addRule(staticTagName, elementStyle) + addRule(staticTagName, elementStyle) } if (dynamicStyle && !this.dynamicSheet.getRule(this.dynamicTagName)) { - this.dynamicSheet - .detach() - .addRule(this.dynamicTagName, dynamicStyle) - this.dynamicSheet - .update(this.dynamicTagName, this.props) - .attach() - .link() + addRule(this.dynamicTagName, dynamicStyle, this.props) } } @@ -69,11 +64,11 @@ const styled = ({tagName, elementStyle, mountSheets}: StyledArgs) => { const {children, className, ...attrs} = this.props const props = filterProps(attrs) - const tagClass = composeClasses( + const tagClass = composeClasses([ this.staticSheet.classes[staticTagName], this.dynamicSheet.classes[this.dynamicTagName], className - ) + ]) return createElement(tagName, {...props, className: tagClass}, children) } diff --git a/src/tests/sheetsObserver.spec.js b/src/tests/sheetsObserver.spec.js new file mode 100644 index 0000000..f5dba3e --- /dev/null +++ b/src/tests/sheetsObserver.spec.js @@ -0,0 +1,47 @@ +import sheetsObserver, {observe, dynamicSheets} from '../utils/sheetsObserver' + +const Mocks = { + // $FlowFixMe: get/set are not supported yet by flow + get sheetMock() { + const mock = { + attach: jest.fn(), + link: jest.fn(), + } + mock.attach.mockReturnValue(mock) + mock.link.mockReturnValue(mock) + return mock + } +} + +it('should add sheet and return sheetId', () => { + const {sheetMock} = Mocks + const sheetId = sheetsObserver.add(sheetMock) + + expect(dynamicSheets).toEqual([sheetMock]) + expect(sheetId).toEqual(0) +}) + +it('should reattach updated sheet by observe call', () => { + const {sheetMock} = Mocks + const sheetId = sheetsObserver.add(sheetMock) + sheetsObserver.update(sheetId) + + observe() + + expect(sheetMock.attach).toHaveBeenCalled() + expect(sheetMock.link).toHaveBeenCalled() +}) + +it('should reattach updated sheet by listner', (done) => { + const {sheetMock} = Mocks + sheetsObserver.listen() + + const sheetId = sheetsObserver.add(sheetMock) + sheetsObserver.update(sheetId) + + setTimeout(() => { + expect(sheetMock.attach).toHaveBeenCalledTimes(1) + expect(sheetMock.link).toHaveBeenCalledTimes(1) + done() + }, 100) +}) diff --git a/src/utils/composeClasses.js b/src/utils/composeClasses.js index 03bd4e0..15c5a85 100644 --- a/src/utils/composeClasses.js +++ b/src/utils/composeClasses.js @@ -1 +1,8 @@ -export default (...args: any) => args.filter(Boolean).join(' ') +export default (classes: Array) => { + const filtered = [] + for (let len = classes.length, index = 0; index < len; index++) { + if (classes[index]) filtered.push(classes[index]) + } + + return filtered.join(' ') +} diff --git a/src/utils/sheetsObserver.js b/src/utils/sheetsObserver.js new file mode 100644 index 0000000..a52cca5 --- /dev/null +++ b/src/utils/sheetsObserver.js @@ -0,0 +1,33 @@ +import raf from 'raf' + +import type { + JssDynamicSheet +} from '../types' + +export const dynamicSheets = [] +let sheetsToUpdate = {} + +export const observe = () => { + const sheetIds = Object.keys(sheetsToUpdate) + if (sheetIds.length) { + sheetsToUpdate = {} + sheetIds.forEach(sheetId => + dynamicSheets[Number(sheetId)].attach().link()) + } +} + +const listen = () => { + observe() + raf(listen) +} + +export default { + listen, + add(sheet: JssDynamicSheet) { + dynamicSheets.push(sheet) + return dynamicSheets.length - 1 + }, + update(sheetId: number) { + sheetsToUpdate[sheetId] = true + } +} diff --git a/yarn.lock b/yarn.lock index f5c9caa..31d9d90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3187,7 +3187,7 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -performance-now@^0.2.0: +performance-now@^0.2.0, performance-now@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -3296,6 +3296,12 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +raf@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.3.0.tgz#93845eeffc773f8129039f677f80a36044eee2c3" + dependencies: + performance-now "~0.2.0" + randomatic@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb"