Comparing version 0.20.1 to 0.21.0
# Radium Changelog | ||
## 0.21.0 (January 8, 2018) | ||
- Automatically clear browser state of elements when unmounting and remounting (#956). | ||
- `resolveStyles` returns `{ extraRadiumStateKeys, element }` instead of just `element`. | ||
## 0.20.1 (January 8, 2018) | ||
@@ -4,0 +8,0 @@ - Fix `v0.20.0` build. |
@@ -11,2 +11,3 @@ # Frequently Asked Questions | ||
- [Why do React warnings have the wrong component name?](#why-do-react-warnings-have-the-wrong-component-name) | ||
- [Why does the browser state of a child element not reset after unmounting and remounting?](#why-does-the-browser-state-of-a-child-element-not-reset-after-unmounting-and-remounting) | ||
@@ -192,1 +193,5 @@ ## How do I use pseudo-selectors like `:checked`, `:last`, `:before`, or `:after`? | ||
Your transpiler is probably not able to set the `displayName` property of the component correctly, which can happen if you wrap `React.createClass` immediately with `Radium`, e.g. `var Button = Radium(React.createClass({ ... }));`. Instead, wrap your component afterward, ex. `Button = Radium(Button);`, or when exporting, ex. `module.exports = Radium(Button);`, or set `displayName` manually. | ||
## Why does the browser state of a child element not reset after unmounting and remounting? | ||
If you have an element that takes a browser state (e.g. `:active`, `:hover`, `:focus`), you need to give it a unique `key` prop. There is a case where if you only have a single element in your component that takes an interactive style, you do not need to provide a `key`; however, if you remove the element and show it again, it will maintain it's state, which is usually unexpected behavior. To fix this, simply give it a custom `key` prop. |
@@ -151,3 +151,3 @@ # Using Radium | ||
To add styles for these states, add a special key to your style object with the additional rules: | ||
To add styles for these states, add a special key to your style object with the additional rules. Additionally, you will need to add a unique `key` prop to the elements that take these styles: | ||
@@ -184,5 +184,19 @@ ```jsx | ||
}; | ||
class MyComponent extends Component { | ||
... | ||
render() { | ||
return ( | ||
<div key="1" style={styles.base}> | ||
<div key="2" style={styles.block} /> | ||
</div> | ||
); | ||
} | ||
... | ||
} | ||
export default Radium(MyComponent); | ||
``` | ||
Radium will merge styles for any active states when your component is rendered. | ||
Radium will merge styles for any active states when your component is rendered. If you are having trouble with browser states, check out [this section](https://github.com/FormidableLabs/radium/tree/master/docs/faq#why-does-the-browser-state-of-a-child-element-not-reset-after-unmounting-and-remounting) of the FAQ. | ||
@@ -189,0 +203,0 @@ ## Media queries |
@@ -19,12 +19,18 @@ 'use strict'; | ||
var _styleKeeper = require('./style-keeper.js'); | ||
var _styleKeeper = require('./style-keeper'); | ||
var _styleKeeper2 = _interopRequireDefault(_styleKeeper); | ||
var _resolveStyles = require('./resolve-styles.js'); | ||
var _resolveStyles2 = require('./resolve-styles'); | ||
var _resolveStyles2 = _interopRequireDefault(_resolveStyles); | ||
var _resolveStyles3 = _interopRequireDefault(_resolveStyles2); | ||
var _getRadiumStyleState = require('./get-radium-style-state'); | ||
var _getRadiumStyleState2 = _interopRequireDefault(_getRadiumStyleState); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -199,5 +205,30 @@ | ||
return (0, _resolveStyles2.default)(this, renderedElement, currentConfig); | ||
var _resolveStyles = (0, _resolveStyles3.default)(this, renderedElement, currentConfig), | ||
extraStateKeyMap = _resolveStyles.extraStateKeyMap, | ||
element = _resolveStyles.element; | ||
this._extraRadiumStateKeys = Object.keys(extraStateKeyMap); | ||
return element; | ||
}; | ||
/* eslint-disable react/no-did-update-set-state, no-unused-vars */ | ||
RadiumEnhancer.prototype.componentDidUpdate = function componentDidUpdate() { | ||
if (this._extraRadiumStateKeys.length > 0) { | ||
var trimmedRadiumState = this._extraRadiumStateKeys.reduce(function (state, key) { | ||
var extraStateKey = state[key], | ||
remainingState = _objectWithoutProperties(state, [key]); | ||
return remainingState; | ||
}, (0, _getRadiumStyleState2.default)(this)); | ||
this._lastRadiumState = trimmedRadiumState; | ||
this.setState({ _radiumStyleState: trimmedRadiumState }); | ||
} | ||
}; | ||
/* eslint-enable react/no-did-update-set-state, no-unused-vars */ | ||
return RadiumEnhancer; | ||
@@ -204,0 +235,0 @@ }(ComposedComponent), _class._isRadiumEnhanced = true, _temp); |
@@ -6,4 +6,4 @@ 'use strict'; | ||
}); | ||
var getStateKey = function getStateKey(elementKey) { | ||
return elementKey === null || elementKey === undefined ? 'main' : elementKey.toString(); | ||
var getStateKey = function getStateKey(renderedElement) { | ||
return typeof renderedElement.ref === 'string' ? renderedElement.ref : renderedElement.key; | ||
}; | ||
@@ -10,0 +10,0 @@ |
@@ -7,5 +7,5 @@ 'use strict'; | ||
var _getStateKey = require('./get-state-key'); | ||
var _cleanStateKey = require('./clean-state-key'); | ||
var _getStateKey2 = _interopRequireDefault(_getStateKey); | ||
var _cleanStateKey2 = _interopRequireDefault(_cleanStateKey); | ||
@@ -15,3 +15,3 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var getState = function getState(state, elementKey, value) { | ||
var key = (0, _getStateKey2.default)(elementKey); | ||
var key = (0, _cleanStateKey2.default)(elementKey); | ||
@@ -18,0 +18,0 @@ return !!state && !!state._radiumStyleState && !!state._radiumStyleState[key] && state._radiumStyleState[key][value]; |
@@ -27,2 +27,10 @@ 'use strict'; | ||
var _cleanStateKey = require('./clean-state-key'); | ||
var _cleanStateKey2 = _interopRequireDefault(_cleanStateKey); | ||
var _getRadiumStyleState = require('./get-radium-style-state'); | ||
var _getRadiumStyleState2 = _interopRequireDefault(_getRadiumStyleState); | ||
var _hash = require('./hash'); | ||
@@ -66,3 +74,4 @@ | ||
config = _ref.config, | ||
existingKeyMap = _ref.existingKeyMap; | ||
existingKeyMap = _ref.existingKeyMap, | ||
extraStateKeyMap = _ref.extraStateKeyMap; | ||
@@ -84,5 +93,13 @@ if (!children) { | ||
var result = children.apply(this, arguments); | ||
if (_react2.default.isValidElement(result)) { | ||
return resolveStyles(component, result, config, existingKeyMap, true); | ||
var _key = (0, _getStateKey2.default)(result); | ||
delete extraStateKeyMap[_key]; | ||
var _resolveStyles = resolveStyles(component, result, config, existingKeyMap, true, extraStateKeyMap), | ||
_element = _resolveStyles.element; | ||
return _element; | ||
} | ||
return result; | ||
@@ -96,3 +113,9 @@ }; | ||
var onlyChild = _react2.default.Children.only(children); | ||
return resolveStyles(component, onlyChild, config, existingKeyMap, true); | ||
var _key2 = (0, _getStateKey2.default)(onlyChild); | ||
delete extraStateKeyMap[_key2]; | ||
var _resolveStyles2 = resolveStyles(component, onlyChild, config, existingKeyMap, true, extraStateKeyMap), | ||
_element2 = _resolveStyles2.element; | ||
return _element2; | ||
} | ||
@@ -102,3 +125,9 @@ | ||
if (_react2.default.isValidElement(child)) { | ||
return resolveStyles(component, child, config, existingKeyMap, true); | ||
var _key3 = (0, _getStateKey2.default)(child); | ||
delete extraStateKeyMap[_key3]; | ||
var _resolveStyles3 = resolveStyles(component, child, config, existingKeyMap, true, extraStateKeyMap), | ||
_element3 = _resolveStyles3.element; | ||
return _element3; | ||
} | ||
@@ -115,3 +144,4 @@ | ||
existingKeyMap = _ref2.existingKeyMap, | ||
props = _ref2.props; | ||
props = _ref2.props, | ||
extraStateKeyMap = _ref2.extraStateKeyMap; | ||
@@ -128,4 +158,10 @@ var newProps = props; | ||
if (_react2.default.isValidElement(propValue)) { | ||
var _key4 = (0, _getStateKey2.default)(propValue); | ||
delete extraStateKeyMap[_key4]; | ||
newProps = _extends({}, newProps); | ||
newProps[prop] = resolveStyles(component, propValue, config, existingKeyMap, true); | ||
var _resolveStyles4 = resolveStyles(component, propValue, config, existingKeyMap, true, extraStateKeyMap), | ||
_element4 = _resolveStyles4.element; | ||
newProps[prop] = _element4; | ||
} | ||
@@ -145,4 +181,4 @@ }); | ||
// styles. | ||
var originalKey = typeof renderedElement.ref === 'string' ? renderedElement.ref : renderedElement.key; | ||
var key = (0, _getStateKey2.default)(originalKey); | ||
var originalKey = (0, _getStateKey2.default)(renderedElement); | ||
var key = (0, _cleanStateKey2.default)(originalKey); | ||
@@ -181,5 +217,5 @@ var alreadyGotKey = false; | ||
var existing = component._lastRadiumState || component.state && component.state._radiumStyleState || {}; | ||
var existing = (0, _getRadiumStyleState2.default)(component); | ||
var state = { _radiumStyleState: _extends({}, existing) }; | ||
var state = { _radiumStyleState: _extends({}, existing) }; | ||
state._radiumStyleState[key] = _extends({}, state._radiumStyleState[key]); | ||
@@ -305,10 +341,28 @@ state._radiumStyleState[key][stateKey] = value; | ||
// | ||
/* eslint-disable max-params */ | ||
resolveStyles = function resolveStyles(component, // ReactComponent, flow+eslint complaining | ||
renderedElement) { | ||
var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_CONFIG; | ||
var existingKeyMap = arguments[3]; | ||
var existingKeyMap = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
var shouldCheckBeforeResolve = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; | ||
var extraStateKeyMap = arguments[5]; | ||
// The extraStateKeyMap is for determining which keys should be erased from | ||
// the state (i.e. which child components are unmounted and should no longer | ||
// have a style state). | ||
if (!extraStateKeyMap) { | ||
var state = (0, _getRadiumStyleState2.default)(component); | ||
extraStateKeyMap = Object.keys(state).reduce(function (acc, key) { | ||
// 'main' is the auto-generated key when there is only one element with | ||
// interactive styles and if a custom key is not assigned. Because of | ||
// this, it is impossible to know which child is 'main', so we won't | ||
// count this key when generating our extraStateKeyMap. | ||
if (key !== 'main') { | ||
acc[key] = true; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
// ReactElement | ||
existingKeyMap = existingKeyMap || {}; | ||
if (!renderedElement || | ||
@@ -323,3 +377,3 @@ // Bail if we've already processed this element. This ensures that only the | ||
shouldCheckBeforeResolve && !_shouldResolveStyles(renderedElement)) { | ||
return renderedElement; | ||
return { extraStateKeyMap: extraStateKeyMap, element: renderedElement }; | ||
} | ||
@@ -331,3 +385,4 @@ | ||
config: config, | ||
existingKeyMap: existingKeyMap | ||
existingKeyMap: existingKeyMap, | ||
extraStateKeyMap: extraStateKeyMap | ||
}); | ||
@@ -339,2 +394,3 @@ | ||
existingKeyMap: existingKeyMap, | ||
extraStateKeyMap: extraStateKeyMap, | ||
props: renderedElement.props | ||
@@ -355,7 +411,10 @@ }); | ||
if (newChildren === renderedElement.props.children && newProps === renderedElement.props) { | ||
return renderedElement; | ||
return { extraStateKeyMap: extraStateKeyMap, element: renderedElement }; | ||
} | ||
return _cloneElement(renderedElement, newProps !== renderedElement.props ? newProps : {}, newChildren); | ||
var element = _cloneElement(renderedElement, newProps !== renderedElement.props ? newProps : {}, newChildren); | ||
return { extraStateKeyMap: extraStateKeyMap, element: element }; | ||
}; | ||
/* eslint-enable max-params */ | ||
@@ -362,0 +421,0 @@ // Only for use by tests |
@@ -8,2 +8,3 @@ 'use strict'; | ||
exports.getElement = getElement; | ||
exports.getElements = getElements; | ||
exports.expectCSS = expectCSS; | ||
@@ -51,2 +52,8 @@ exports.expectColor = expectColor; | ||
function getElements(output, tagName) { | ||
return _testUtils2.default.scryRenderedDOMComponentsWithTag(output, tagName).map(function (component) { | ||
return _reactDom2.default.findDOMNode(component); | ||
}); | ||
} | ||
function cleanCSS(css) { | ||
@@ -53,0 +60,0 @@ return css.replace(/\s*\n\s*/g, '').replace(/\s*([{};:,])\s*/g, '$1'); |
{ | ||
"name": "radium", | ||
"version": "0.20.1", | ||
"version": "0.21.0", | ||
"description": "A set of tools to manage inline styles on React elements", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -1,2 +0,2 @@ | ||
import camelCasePropsToDashCase from 'camel-case-props-to-dash-case.js'; | ||
import camelCasePropsToDashCase from 'camel-case-props-to-dash-case'; | ||
@@ -3,0 +3,0 @@ describe('camelCasePropsToDashCase', function() { |
@@ -1,4 +0,4 @@ | ||
const resolveStyles = sinon.spy(require('resolve-styles.js')); | ||
const Enhancer = require('inject-loader!enhancer.js')({ | ||
'./resolve-styles.js': resolveStyles | ||
const resolveStyles = sinon.spy(require('resolve-styles')); | ||
const Enhancer = require('inject-loader!enhancer')({ | ||
'./resolve-styles': resolveStyles | ||
}); | ||
@@ -5,0 +5,0 @@ |
@@ -1,5 +0,5 @@ | ||
import getState from 'get-state.js'; | ||
import getState from 'get-state'; | ||
describe('getState', function() { | ||
it('successfully gets the state if passed number zero', function() { | ||
describe('getState', () => { | ||
it('successfully gets the state if passed number zero', () => { | ||
const result = getState( | ||
@@ -6,0 +6,0 @@ {_radiumStyleState: {'0': {':hover': true}}}, |
/* eslint-disable react/prop-types */ | ||
import Radium from 'index.js'; | ||
import MouseUpListener from 'plugins/mouse-up-listener.js'; | ||
import Radium from 'index'; | ||
import MouseUpListener from 'plugins/mouse-up-listener'; | ||
import React, {Component} from 'react'; | ||
@@ -9,3 +9,3 @@ import PropTypes from 'prop-types'; | ||
import TestUtils from 'react-dom/test-utils'; | ||
import {getRenderOutput, getElement} from 'test-helpers'; | ||
import {getRenderOutput, getElement, getElements} from 'test-helpers'; | ||
@@ -272,2 +272,44 @@ describe('Radium blackbox tests', () => { | ||
it('resets state for unmounted components, Issue #524', () => { | ||
class TestComponent extends Component { | ||
state = {showSpan: true}; | ||
render() { | ||
return ( | ||
<div> | ||
<button onClick={() => this.setState({showSpan: true})} /> | ||
{this.state.showSpan && | ||
<span | ||
key="s" | ||
onClick={() => this.setState({showSpan: false})} | ||
style={{ | ||
color: 'blue', | ||
':hover': {color: 'red'} | ||
}} | ||
/>} | ||
</div> | ||
); | ||
} | ||
} | ||
const WrappedTestComponent = Radium(TestComponent); | ||
const output = TestUtils.renderIntoDocument(<WrappedTestComponent />); | ||
let spans = getElements(output, 'span'); | ||
const button = getElement(output, 'button'); | ||
expect(spans[0].style.color).to.equal('blue'); | ||
TestUtils.Simulate.mouseEnter(spans[0]); | ||
expect(spans[0].style.color).to.equal('red'); | ||
TestUtils.Simulate.click(spans[0]); | ||
spans = getElements(output, 'span'); | ||
expect(spans).to.have.length(0); | ||
TestUtils.Simulate.click(button); | ||
spans = getElements(output, 'span'); | ||
expect(spans).to.have | ||
.length(1) | ||
.and.to.have.deep.property('[0].style.color', 'blue'); | ||
}); | ||
it('resolves styles on multiple elements nested far down, Issue #307', () => { | ||
@@ -274,0 +316,0 @@ @Radium class TestComponent extends Component { |
import React from 'react'; | ||
import MouseUpListener from 'plugins/mouse-up-listener.js'; | ||
import MouseUpListener from 'plugins/mouse-up-listener'; | ||
import objectAssign from 'object-assign'; | ||
const resolveStyles = require('inject-loader!resolve-styles.js')({ | ||
exenv: require('__mocks__/exenv.js') | ||
const resolveStyles = require('inject-loader!resolve-styles')({ | ||
exenv: require('__mocks__/exenv') | ||
}); | ||
const genComponent = function() { | ||
const genComponent = function(initialState = {}) { | ||
return { | ||
@@ -13,3 +13,3 @@ setState: sinon.spy(function(newState) { | ||
}), | ||
state: {}, | ||
state: initialState, | ||
_radiumIsMounted: true | ||
@@ -45,3 +45,3 @@ }; | ||
describe('resolveStyles', function() { | ||
describe('resolveStyles', () => { | ||
beforeEach(() => { | ||
@@ -51,4 +51,4 @@ MouseUpListener.subscribe = sinon.spy(); | ||
describe('no-op behavior', function() { | ||
it('handles null rendered element', function() { | ||
describe('no-op behavior', () => { | ||
it('handles null rendered element', () => { | ||
const component = genComponent(); | ||
@@ -59,7 +59,7 @@ | ||
it("doesn't explode", function() { | ||
it("doesn't explode", () => { | ||
const component = genComponent(); | ||
const renderedElement = <div />; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -70,7 +70,7 @@ expect(result).to.equal(renderedElement); | ||
it('passes through normal style objects', function() { | ||
it('passes through normal style objects', () => { | ||
const component = genComponent(); | ||
const renderedElement = <div style={{color: 'blue'}} />; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -80,3 +80,3 @@ expect(result.props.style).to.deep.equal(renderedElement.props.style); | ||
it('passes through normal style objects of children', function() { | ||
it('passes through normal style objects of children', () => { | ||
const component = genComponent(); | ||
@@ -90,3 +90,3 @@ const style = {color: 'blue'}; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
const children = getChildrenArray(result.props.children); | ||
@@ -96,19 +96,19 @@ expect(children[0].props.style).to.deep.equal(style); | ||
it("doesn't wrap string children in spans", function() { | ||
it("doesn't wrap string children in spans", () => { | ||
const component = genComponent(); | ||
const renderedElement = <div>Hello</div>; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.children).to.equal('Hello'); | ||
}); | ||
it("doesn't wrap number children in spans", function() { | ||
it("doesn't wrap number children in spans", () => { | ||
const component = genComponent(); | ||
const renderedElement = <div>{88347}</div>; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.children).to.equal(88347); | ||
}); | ||
it('ignores invalid children', function() { | ||
it('ignores invalid children', () => { | ||
const component = genComponent(); | ||
@@ -123,3 +123,3 @@ | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
const children = getChildrenArray(result.props.children); | ||
@@ -130,3 +130,3 @@ | ||
it('only processes an element once', function() { | ||
it('only processes an element once', () => { | ||
sinon.spy(React, 'cloneElement'); | ||
@@ -139,4 +139,4 @@ | ||
let result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, result); | ||
let result = resolveStyles(component, renderedElement).element; | ||
result = resolveStyles(component, result).element; | ||
@@ -154,4 +154,4 @@ expect(result.props.style).to.deep.equal({ | ||
describe('style array', function() { | ||
it('merges an array of style objects', function() { | ||
describe('style array', () => { | ||
it('merges an array of style objects', () => { | ||
const component = genComponent(); | ||
@@ -162,3 +162,3 @@ const renderedElement = ( | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -171,3 +171,3 @@ expect(result.props.style).to.deep.equal({ | ||
it('skips falsy and non-object entries', function() { | ||
it('skips falsy and non-object entries', () => { | ||
const component = genComponent(); | ||
@@ -188,3 +188,3 @@ const renderedElement = ( | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -197,3 +197,3 @@ expect(result.props.style).to.deep.equal({ | ||
it('overwrites earlier styles with later ones', function() { | ||
it('overwrites earlier styles with later ones', () => { | ||
const component = genComponent(); | ||
@@ -204,3 +204,3 @@ const renderedElement = ( | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -212,3 +212,3 @@ expect(result.props.style).to.deep.equal({ | ||
it('merges nested special styles', function() { | ||
it('merges nested special styles', () => { | ||
const component = genComponent(); | ||
@@ -224,5 +224,5 @@ const renderedElement = ( | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
result.props.onMouseEnter(); | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
@@ -241,3 +241,3 @@ expect(result.props.style).to.deep.equal({ | ||
) { | ||
it('strips special styles if not applied', function() { | ||
it('strips special styles if not applied', () => { | ||
const component = genComponent(); | ||
@@ -248,3 +248,3 @@ const style = {background: 'blue'}; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -254,3 +254,3 @@ expect(result.props.style).to.deep.equal({background: 'blue'}); | ||
it('adds appropriate handlers for ' + pseudo + ' styles', function() { | ||
it('adds appropriate handlers for ' + pseudo + ' styles', () => { | ||
const component = genComponent(); | ||
@@ -261,3 +261,3 @@ const style = {background: 'blue'}; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -270,3 +270,3 @@ expect(typeof result.props[onHandlerName]).to.equal('function'); | ||
it('adds ' + pseudo + ' styles ' + onHandlerName, function() { | ||
it('adds ' + pseudo + ' styles ' + onHandlerName, () => { | ||
const component = genComponent(); | ||
@@ -277,3 +277,3 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('blue'); | ||
@@ -287,7 +287,7 @@ | ||
// resolveStyles mutates | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('red'); | ||
}); | ||
it('throws if multiple elements have the same key', function() { | ||
it('throws if multiple elements have the same key', () => { | ||
const component = genComponent(); | ||
@@ -311,3 +311,3 @@ const style = {background: 'blue'}; | ||
it('throws if multiple elements have no key', function() { | ||
it('throws if multiple elements have no key', () => { | ||
const component = genComponent(); | ||
@@ -329,3 +329,3 @@ const style = {background: 'blue'}; | ||
it('adds ' + pseudo + ' styles to correct element by key', function() { | ||
it('adds ' + pseudo + ' styles to correct element by key', () => { | ||
const component = genComponent(); | ||
@@ -342,3 +342,3 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
let children = getChildrenArray(result.props.children); | ||
@@ -350,3 +350,3 @@ expect(children[0].props.style).to.be.undefined; | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
children = getChildrenArray(result.props.children); | ||
@@ -357,3 +357,3 @@ expect(children[0].props.style).to.be.undefined; | ||
it('adds ' + pseudo + ' styles to correct element by ref', function() { | ||
it('adds ' + pseudo + ' styles to correct element by ref', () => { | ||
const component = genComponent(); | ||
@@ -370,3 +370,3 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
let children = getChildrenArray(result.props.children); | ||
@@ -378,3 +378,3 @@ expect(children[0].props.style).to.be.undefined; | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
children = getChildrenArray(result.props.children); | ||
@@ -386,3 +386,3 @@ expect(children[0].props.style).to.be.undefined; | ||
if (offHandlerName) { | ||
it('removes ' + pseudo + ' styles ' + offHandlerName, function() { | ||
it('removes ' + pseudo + ' styles ' + offHandlerName, () => { | ||
const component = genComponent(); | ||
@@ -393,7 +393,7 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
result.props[onHandlerName](); | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('red'); | ||
@@ -405,7 +405,7 @@ | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('blue'); | ||
}); | ||
it("doesn't mutate state", function() { | ||
it("doesn't mutate state", () => { | ||
const component = genComponent(); | ||
@@ -416,3 +416,3 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
@@ -427,3 +427,3 @@ // Capturing a reference to the existing state is enough, since Radium | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
@@ -437,14 +437,14 @@ previousState = component.state._radiumStyleState; | ||
describe(':hover', function() { | ||
describe(':hover', () => { | ||
createPseduoStyleTests('hover', 'onMouseEnter', 'onMouseLeave'); | ||
}); | ||
describe(':focus', function() { | ||
describe(':focus', () => { | ||
createPseduoStyleTests('focus', 'onFocus', 'onBlur'); | ||
}); | ||
describe(':active', function() { | ||
describe(':active', () => { | ||
createPseduoStyleTests('active', 'onMouseDown'); | ||
it('subscribes to mouse up listener', function() { | ||
it('subscribes to mouse up listener', () => { | ||
const component = genComponent(); | ||
@@ -458,3 +458,3 @@ const renderedElement = <div style={{':active': {background: 'red'}}} />; | ||
it('adds active styles on mouse down', function() { | ||
it('adds active styles on mouse down', () => { | ||
const component = genComponent(); | ||
@@ -467,3 +467,3 @@ const style = { | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('blue'); | ||
@@ -473,7 +473,7 @@ | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('red'); | ||
}); | ||
it('removes active styles on mouse up', function() { | ||
it('removes active styles on mouse up', () => { | ||
const component = genComponent(); | ||
@@ -486,7 +486,7 @@ const style = { | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
result.props.onMouseDown(); | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('red'); | ||
@@ -497,7 +497,7 @@ | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('blue'); | ||
}); | ||
it('ignores mouse up if no active styles', function() { | ||
it('ignores mouse up if no active styles', () => { | ||
const component = genComponent(); | ||
@@ -510,3 +510,3 @@ const style = { | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
@@ -519,7 +519,7 @@ result.props.onMouseDown(); | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('blue'); | ||
}); | ||
it('calls existing onMouseDown handler', function() { | ||
it('calls existing onMouseDown handler', () => { | ||
const component = genComponent(); | ||
@@ -535,3 +535,3 @@ const style = { | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
@@ -542,3 +542,3 @@ result.props.onMouseDown(); | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style.background).to.equal('red'); | ||
@@ -548,4 +548,4 @@ }); | ||
describe('multiple states triggered at once', function() { | ||
describe('applies pseudo styles in the defined order', function() { | ||
describe('multiple states triggered at once', () => { | ||
describe('applies pseudo styles in the defined order', () => { | ||
const component = genComponent(); | ||
@@ -568,3 +568,3 @@ const stylePermutations = permutate([ | ||
onHandlers.join(', '); | ||
it(name, function() { | ||
it(name, () => { | ||
const style = {}; | ||
@@ -576,3 +576,3 @@ pseudoStyles.forEach(pseudo => { | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
@@ -583,3 +583,3 @@ onHandlers.forEach(onHandler => { | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
@@ -600,8 +600,25 @@ expect(result.props.style.background).to.equal( | ||
describe('React.Children.only', function() { | ||
it("doesn't break React.Children.only", function() { | ||
describe('when elements are unmounted', () => { | ||
it('returns an extraStateKeyMap with keys of unmounted elements', () => { | ||
const initialState = { | ||
_radiumStyleState: { | ||
mountedDiv: {}, | ||
unmountedDiv: {} | ||
} | ||
}; | ||
const component = genComponent(initialState); | ||
const renderedElement = <div><div ref="mountedDiv" /></div>; | ||
const result = resolveStyles(component, renderedElement).extraStateKeyMap; | ||
expect(result).to.deep.equal({unmountedDiv: true}); | ||
}); | ||
}); | ||
describe('React.Children.only', () => { | ||
it("doesn't break React.Children.only", () => { | ||
const component = genComponent(); | ||
const renderedElement = <div><span /></div>; | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
@@ -611,3 +628,3 @@ expect(React.Children.only(result.props.children)).to.be.ok; | ||
it("doesn't break when only child isn't ReactElement", function() { | ||
it("doesn't break when only child isn't ReactElement", () => { | ||
const component = genComponent(); | ||
@@ -620,4 +637,4 @@ const renderedElement = <div>Foo</div>; | ||
describe('ReactComponentElement children', function() { | ||
it("doesn't resolve ReactComponentElement children", function() { | ||
describe('ReactComponentElement children', () => { | ||
it("doesn't resolve ReactComponentElement children", () => { | ||
const component = genComponent(); | ||
@@ -632,3 +649,3 @@ class CustomComponent extends React.Component {} | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
const children = getChildrenArray(result.props.children); | ||
@@ -638,3 +655,3 @@ expect(children[0].props.style).to.deep.equal(style); | ||
it('resolves ReactDOMElement children of ReactComponentElements', function() { | ||
it('resolves ReactDOMElement children of ReactComponentElements', () => { | ||
const component = genComponent(); | ||
@@ -651,3 +668,3 @@ class CustomComponent extends React.Component {} | ||
const result = resolveStyles(component, renderedElement); | ||
const result = resolveStyles(component, renderedElement).element; | ||
expect(result.props.style).to.deep.equal({ | ||
@@ -669,4 +686,4 @@ background: 'white', | ||
describe('disabled', function() { | ||
it('discards interaction styles if element is disabled', function() { | ||
describe('disabled', () => { | ||
it('discards interaction styles if element is disabled', () => { | ||
const component = genComponent(); | ||
@@ -683,3 +700,3 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
let children = getChildrenArray(result.props.children); | ||
@@ -691,3 +708,3 @@ expect(children[0].props.style).to.be.undefined; | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
children = getChildrenArray(result.props.children); | ||
@@ -698,3 +715,3 @@ expect(children[0].props.style).to.be.undefined; | ||
it('styles according to :disabled style if element is disabled', function() { | ||
it('styles according to :disabled style if element is disabled', () => { | ||
const component = genComponent(); | ||
@@ -712,3 +729,3 @@ const style = {background: 'blue'}; | ||
let result = resolveStyles(component, renderedElement); | ||
let result = resolveStyles(component, renderedElement).element; | ||
let children = getChildrenArray(result.props.children); | ||
@@ -720,3 +737,3 @@ expect(children[0].props.style).to.be.undefined; | ||
result = resolveStyles(component, renderedElement); | ||
result = resolveStyles(component, renderedElement).element; | ||
children = getChildrenArray(result.props.children); | ||
@@ -729,3 +746,3 @@ expect(children[0].props.style).to.be.undefined; | ||
/* eslint-disable no-console */ | ||
describe('warnings', function() { | ||
describe('warnings', () => { | ||
beforeEach(() => { | ||
@@ -740,3 +757,3 @@ sinon.stub(console, 'warn'); | ||
it('warns when mixing longhand and shorthand properties', function() { | ||
it('warns when mixing longhand and shorthand properties', () => { | ||
const component = genComponent(); | ||
@@ -760,3 +777,3 @@ const renderedElement = ( | ||
it('warns when mixing longhand and shorthand properties in nested styles', function() { | ||
it('warns when mixing longhand and shorthand properties in nested styles', () => { | ||
const component = genComponent(); | ||
@@ -782,3 +799,3 @@ const renderedElement = ( | ||
it('does not warn when mixing border and borderRadius', function() { | ||
it('does not warn when mixing border and borderRadius', () => { | ||
const component = genComponent(); | ||
@@ -799,3 +816,3 @@ const renderedElement = ( | ||
it('does not throw when passed a falsy entry value', function() { | ||
it('does not throw when passed a falsy entry value', () => { | ||
const component = genComponent(); | ||
@@ -802,0 +819,0 @@ const renderedElement = <div style={{height: null}} />; |
@@ -6,4 +6,5 @@ /* @flow */ | ||
import StyleKeeper from './style-keeper.js'; | ||
import resolveStyles from './resolve-styles.js'; | ||
import StyleKeeper from './style-keeper'; | ||
import resolveStyles from './resolve-styles'; | ||
import getRadiumStyleState from './get-radium-style-state'; | ||
@@ -131,2 +132,4 @@ const KEYS_TO_IGNORE_WHEN_COPYING_PROPERTIES = [ | ||
_radiumIsMounted: boolean; | ||
_lastRadiumState: Object; | ||
_extraRadiumStateKeys: any; | ||
@@ -193,4 +196,28 @@ constructor() { | ||
return resolveStyles(this, renderedElement, currentConfig); | ||
const {extraStateKeyMap, element} = resolveStyles( | ||
this, | ||
renderedElement, | ||
currentConfig | ||
); | ||
this._extraRadiumStateKeys = Object.keys(extraStateKeyMap); | ||
return element; | ||
} | ||
/* eslint-disable react/no-did-update-set-state, no-unused-vars */ | ||
componentDidUpdate() { | ||
if (this._extraRadiumStateKeys.length > 0) { | ||
const trimmedRadiumState = this._extraRadiumStateKeys.reduce( | ||
(state, key) => { | ||
const {[key]: extraStateKey, ...remainingState} = state; | ||
return remainingState; | ||
}, | ||
getRadiumStyleState(this) | ||
); | ||
this._lastRadiumState = trimmedRadiumState; | ||
this.setState({_radiumStyleState: trimmedRadiumState}); | ||
} | ||
} | ||
/* eslint-enable react/no-did-update-set-state, no-unused-vars */ | ||
} | ||
@@ -197,0 +224,0 @@ |
/* @flow */ | ||
const getStateKey = function(elementKey: ?string): string { | ||
return elementKey === null || elementKey === undefined | ||
? 'main' | ||
: elementKey.toString(); | ||
const getStateKey = function(renderedElement: any): string { | ||
return typeof renderedElement.ref === 'string' | ||
? renderedElement.ref | ||
: renderedElement.key; | ||
}; | ||
export default getStateKey; |
/* @flow */ | ||
import getStateKey from './get-state-key'; | ||
import cleanStateKey from './clean-state-key'; | ||
@@ -10,3 +10,3 @@ const getState = function( | ||
): any { | ||
const key = getStateKey(elementKey); | ||
const key = cleanStateKey(elementKey); | ||
@@ -13,0 +13,0 @@ return !!state && |
@@ -9,2 +9,4 @@ /* @flow */ | ||
import getStateKey from './get-state-key'; | ||
import cleanStateKey from './clean-state-key'; | ||
import getRadiumStyleState from './get-radium-style-state'; | ||
import hash from './hash'; | ||
@@ -34,2 +36,7 @@ import {isNestedStyle, mergeStyles} from './merge-styles'; | ||
type ResolvedStyles = { | ||
extraStateKeyMap: {[key: string]: boolean}, | ||
element: any | ||
}; | ||
// Declare early for recursive helpers. | ||
@@ -40,5 +47,6 @@ let resolveStyles = ((null: any): ( | ||
config: Config, | ||
existingKeyMap?: {[key: string]: boolean}, | ||
shouldCheckBeforeResolve: true | ||
) => any); | ||
existingKeyMap: {[key: string]: boolean}, | ||
shouldCheckBeforeResolve: boolean, | ||
extraStateKeyMap?: {[key: string]: boolean} | ||
) => ResolvedStyles); | ||
@@ -54,3 +62,4 @@ const _shouldResolveStyles = function(component) { | ||
config, | ||
existingKeyMap | ||
existingKeyMap, | ||
extraStateKeyMap | ||
} | ||
@@ -73,5 +82,17 @@ ) { | ||
const result = children.apply(this, arguments); | ||
if (React.isValidElement(result)) { | ||
return resolveStyles(component, result, config, existingKeyMap, true); | ||
const key = getStateKey(result); | ||
delete extraStateKeyMap[key]; | ||
const {element} = resolveStyles( | ||
component, | ||
result, | ||
config, | ||
existingKeyMap, | ||
true, | ||
extraStateKeyMap | ||
); | ||
return element; | ||
} | ||
return result; | ||
@@ -85,3 +106,13 @@ }; | ||
const onlyChild = React.Children.only(children); | ||
return resolveStyles(component, onlyChild, config, existingKeyMap, true); | ||
const key = getStateKey(onlyChild); | ||
delete extraStateKeyMap[key]; | ||
const {element} = resolveStyles( | ||
component, | ||
onlyChild, | ||
config, | ||
existingKeyMap, | ||
true, | ||
extraStateKeyMap | ||
); | ||
return element; | ||
} | ||
@@ -91,3 +122,13 @@ | ||
if (React.isValidElement(child)) { | ||
return resolveStyles(component, child, config, existingKeyMap, true); | ||
const key = getStateKey(child); | ||
delete extraStateKeyMap[key]; | ||
const {element} = resolveStyles( | ||
component, | ||
child, | ||
config, | ||
existingKeyMap, | ||
true, | ||
extraStateKeyMap | ||
); | ||
return element; | ||
} | ||
@@ -105,3 +146,4 @@ | ||
existingKeyMap, | ||
props | ||
props, | ||
extraStateKeyMap | ||
} | ||
@@ -119,4 +161,6 @@ ) { | ||
if (React.isValidElement(propValue)) { | ||
const key = getStateKey(propValue); | ||
delete extraStateKeyMap[key]; | ||
newProps = {...newProps}; | ||
newProps[prop] = resolveStyles( | ||
const {element} = resolveStyles( | ||
component, | ||
@@ -126,4 +170,6 @@ propValue, | ||
existingKeyMap, | ||
true | ||
true, | ||
extraStateKeyMap | ||
); | ||
newProps[prop] = element; | ||
} | ||
@@ -145,6 +191,4 @@ }); | ||
// styles. | ||
const originalKey = typeof renderedElement.ref === 'string' | ||
? renderedElement.ref | ||
: renderedElement.key; | ||
const key = getStateKey(originalKey); | ||
const originalKey = getStateKey(renderedElement); | ||
const key = cleanStateKey(originalKey); | ||
@@ -195,6 +239,5 @@ let alreadyGotKey = false; | ||
const existing = component._lastRadiumState || | ||
(component.state && component.state._radiumStyleState) || {}; | ||
const existing = getRadiumStyleState(component); | ||
const state = {_radiumStyleState: {...existing}}; | ||
const state = {_radiumStyleState: {...existing}}; | ||
state._radiumStyleState[key] = {...state._radiumStyleState[key]}; | ||
@@ -328,2 +371,3 @@ state._radiumStyleState[key][stateKey] = value; | ||
// | ||
/* eslint-disable max-params */ | ||
resolveStyles = function( | ||
@@ -333,7 +377,27 @@ component: any, // ReactComponent, flow+eslint complaining | ||
config: Config = DEFAULT_CONFIG, | ||
existingKeyMap?: {[key: string]: boolean}, | ||
shouldCheckBeforeResolve: boolean = false | ||
): any { | ||
existingKeyMap: {[key: string]: boolean} = {}, | ||
shouldCheckBeforeResolve: boolean = false, | ||
extraStateKeyMap?: {[key: string]: boolean} | ||
): ResolvedStyles { | ||
// The extraStateKeyMap is for determining which keys should be erased from | ||
// the state (i.e. which child components are unmounted and should no longer | ||
// have a style state). | ||
if (!extraStateKeyMap) { | ||
const state = getRadiumStyleState(component); | ||
extraStateKeyMap = Object.keys(state).reduce( | ||
(acc, key) => { | ||
// 'main' is the auto-generated key when there is only one element with | ||
// interactive styles and if a custom key is not assigned. Because of | ||
// this, it is impossible to know which child is 'main', so we won't | ||
// count this key when generating our extraStateKeyMap. | ||
if (key !== 'main') { | ||
acc[key] = true; | ||
} | ||
return acc; | ||
}, | ||
{} | ||
); | ||
} | ||
// ReactElement | ||
existingKeyMap = existingKeyMap || {}; | ||
if ( | ||
@@ -350,3 +414,3 @@ !renderedElement || | ||
) { | ||
return renderedElement; | ||
return {extraStateKeyMap, element: renderedElement}; | ||
} | ||
@@ -358,3 +422,4 @@ | ||
config, | ||
existingKeyMap | ||
existingKeyMap, | ||
extraStateKeyMap | ||
}); | ||
@@ -366,2 +431,3 @@ | ||
existingKeyMap, | ||
extraStateKeyMap, | ||
props: renderedElement.props | ||
@@ -385,6 +451,6 @@ }); | ||
) { | ||
return renderedElement; | ||
return {extraStateKeyMap, element: renderedElement}; | ||
} | ||
return _cloneElement( | ||
const element = _cloneElement( | ||
renderedElement, | ||
@@ -394,3 +460,6 @@ newProps !== renderedElement.props ? newProps : {}, | ||
); | ||
return {extraStateKeyMap, element}; | ||
}; | ||
/* eslint-enable max-params */ | ||
@@ -397,0 +466,0 @@ // Only for use by tests |
@@ -19,2 +19,9 @@ import Color from 'color'; | ||
export function getElements(output, tagName) { | ||
return TestUtils.scryRenderedDOMComponentsWithTag( | ||
output, | ||
tagName | ||
).map(component => ReactDOM.findDOMNode(component)); | ||
} | ||
function cleanCSS(css) { | ||
@@ -21,0 +28,0 @@ return css.replace(/\s*\n\s*/g, '').replace(/\s*([{};:,])\s*/g, '$1'); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
587698
94
12893