Skip to content

Commit

Permalink
support observable values #442
Browse files Browse the repository at this point in the history
  • Loading branch information
kof committed Sep 16, 2017
1 parent 217d27c commit f02abf8
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 12 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Fixed linker, which didn't work if selectors were escaped (#557).
- In production `createGenerateClassName()` option will now produce short selectors and warn about memory leaks. (#546)
- Update flow to v0.54.1.
- Support observable values (#442).

## 8.1.0 / 2017-07-12

Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@
"pre-commit": "^1.1.3",
"rimraf": "^2.5.4",
"size-limit": "^0.2.0",
"webpack": "^2.3.3"
"webpack": "^2.3.3",
"zen-observable": "^0.5.2"
},
"scripts": {
"all": "npm run lint && npm run flow && npm run test && npm run build && npm run size",
Expand All @@ -106,6 +107,7 @@
},
"dependencies": {
"is-in-browser": "^1.1.3",
"is-observable": "^0.2.0",
"warning": "^3.0.0"
},
"files": [
Expand Down
5 changes: 3 additions & 2 deletions src/Jss.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import isInBrowser from 'is-in-browser'
import StyleSheet from './StyleSheet'
import PluginsRegistry from './PluginsRegistry'
import rulesPlugins from './plugins/rules'
import observablesPlugin from './plugins/observables'
import sheets from './sheets'
import StyleRule from './rules/StyleRule'
import createGenerateClassNameDefault from './utils/createGenerateClassName'
Expand Down Expand Up @@ -38,7 +39,7 @@ export default class Jss {

constructor(options?: JssOptions) {
// eslint-disable-next-line prefer-spread
this.use.apply(this, rulesPlugins)
this.use.apply(this, rulesPlugins.concat([observablesPlugin]))
this.setup(options)
}

Expand Down Expand Up @@ -124,7 +125,7 @@ export default class Jss {
* Register plugin. Passed function will be invoked with a rule instance.
*/
use(...plugins: Array<Plugin>): this {
plugins.forEach(plugin => {
plugins.forEach((plugin) => {
// Avoids applying same plugin twice, at least based on ref.
if (this.options.plugins.indexOf(plugin) === -1) {
this.options.plugins.push(plugin)
Expand Down
21 changes: 21 additions & 0 deletions src/plugins/observables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* @flow */
import isObservable from 'is-observable'
import StyleRule from '../rules/StyleRule'
import type {Rule} from '../types'

export default {
onProcessRule(rule: Rule) {
if (!(rule instanceof StyleRule)) return
const {style} = rule
for (const prop in style) {
const value = style[prop]
if (!isObservable(value)) continue
value.subscribe({
next: (nextValue) => {
// $FlowFixMe
rule.prop(prop, nextValue)
}
})
}
}
}
6 changes: 5 additions & 1 deletion src/rules/StyleRule.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* @flow */
import toCss from '../utils/toCss'
import toCssValue from '../utils/toCssValue'
import isDynamicValue from '../utils/isDynamicValue'
import type {ToCssOptions, RuleOptions, Renderer as RendererInterface, JssStyle, BaseRule} from '../types'

export default class StyleRule implements BaseRule {
Expand Down Expand Up @@ -61,7 +62,9 @@ export default class StyleRule implements BaseRule {
* Get or set a style property.
*/
prop(name: string, nextValue?: string): StyleRule|string {
const $name = typeof this.style[name] === 'function' ? `$${name}` : name
// The result of a dynamic value is prefixed with $ and is not inumerable in
// order to be ignored by all plugins or during stringification.
const $name = isDynamicValue(this.style[name]) ? `$${name}` : name

// Its a setter.
if (nextValue != null) {
Expand Down Expand Up @@ -100,6 +103,7 @@ export default class StyleRule implements BaseRule {
for (const prop in this.style) {
const value = this.style[prop]
const type = typeof value
// XXX
if (type === 'function') json[prop] = this.style[`$${prop}`]
else if (type !== 'object') json[prop] = value
else if (Array.isArray(value)) json[prop] = toCssValue(value)
Expand Down
1 change: 0 additions & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export type generateClassName = (rule: Rule, sheet?: StyleSheet) => string
// Find a way to declare all types: Object|string|Array<Object>
export type JssStyle = Object


export interface Renderer {
constructor(sheet?: StyleSheet): Renderer;
setStyle(cssRule: HTMLElement|CSSStyleRule, prop: string, value: string): boolean;
Expand Down
3 changes: 2 additions & 1 deletion src/utils/cloneStyle.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import isObservable from 'is-observable'
import type {JssStyle} from '../types'

const {isArray} = Array
Expand All @@ -16,7 +17,7 @@ export default function cloneStyle(style: JssStyle): JssStyle {
const newStyle = {}
for (const name in style) {
const value = style[name]
if (typeof value === 'object') {
if (typeof value === 'object' && !isObservable(value)) {
newStyle[name] = cloneStyle(value)
continue
}
Expand Down
3 changes: 3 additions & 0 deletions src/utils/isDynamicValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import isObservable from 'is-observable'

export default value => typeof value === 'function' || isObservable(value)
9 changes: 5 additions & 4 deletions src/utils/toCss.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @flow */
import toCssValue from './toCssValue'
import isDynamicValue from './isDynamicValue'
import type {ToCssOptions as Options, JssStyle} from '../types'

/**
Expand Down Expand Up @@ -50,20 +51,20 @@ export default function toCss(selector: string, style: JssStyle, options: Option
}
}

let hasFunctionValue = false
let hasDynamicValue = false

for (const prop in style) {
let value = style[prop]
if (typeof value === 'function') {
if (isDynamicValue(value)) {
value = style[`$${prop}`]
hasFunctionValue = true
hasDynamicValue = true
}
if (value != null && prop !== 'fallbacks') {
result += `\n${indentStr(`${prop}: ${toCssValue(value)};`, indent)}`
}
}

if (!result && !hasFunctionValue) return result
if (!result && !hasDynamicValue) return result

indent--
result = indentStr(`${selector} {${result}\n`, indent) + indentStr('}', indent)
Expand Down
51 changes: 51 additions & 0 deletions tests/functional/observables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import expect from 'expect.js'
import Observable from 'zen-observable'

import {create} from '../../src'
import {
createGenerateClassName,
computeStyle
} from '../utils'

const settings = {createGenerateClassName}

describe('Functional: Observable', () => {
let jss

beforeEach(() => {
jss = create(settings)
})

describe('Observables', () => {
let sheet
let observer

beforeEach(() => {
sheet = jss.createStyleSheet({
a: {
height: new Observable((obs) => {
observer = obs
})
}
}, {link: true}).attach()
})

it('should subscribe the observer', () => {
expect(observer).to.be.an(Object)
})

it('should accept an observable', () => {
expect(computeStyle(sheet.classes.a).height).to.be('0px')
})

it('should update the value 1', () => {
observer.next('10px')
expect(computeStyle(sheet.classes.a).height).to.be('10px')
})

it('should update the value 2', () => {
observer.next('20px')
expect(computeStyle(sheet.classes.a).height).to.be('20px')
})
})
})
1 change: 1 addition & 0 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import './integration/rules'
import './integration/sheet'
import './integration/sheetsRegistry'
import './integration/hooks'
import './functional/observables'
import './functional/rules'
import './functional/sheet'
import './functional/priority'
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/Jss.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ describe('Integration: jss', () => {
const plugin = {}

beforeEach(() => {
jss.plugins.use = (plugin) => {
used = plugin
jss.plugins.use = (receivedPlugin) => {
used = receivedPlugin
}
jss.use(plugin)
})
Expand Down

0 comments on commit f02abf8

Please sign in to comment.