Comparing version 1.0.5 to 1.0.6
{ | ||
"name": "scrollable", | ||
"version": "1.0.5", | ||
"version": "1.0.6", | ||
"description": "Components for layer composition and scrolling with React.js", | ||
@@ -5,0 +5,0 @@ "main": "src/scrollable.js", |
@@ -9,4 +9,4 @@ /* Copyright 2015, Yahoo Inc. | ||
var ScrollItem = require('../scroll-item'); | ||
var StyleHelper = require('../style-helper'); | ||
describe('<ScrollItem>', function() { | ||
@@ -145,2 +145,73 @@ var div; | ||
it("cleanup serverStyles after componentDidMount", function (done) { | ||
var Scroller = MockScroller(); | ||
var wrapper = React.render( | ||
<Scroller> | ||
<ScrollItem name="foo" scrollHandler={function(){ | ||
return {y:10}; | ||
}} serverStyles={function(){ | ||
return { | ||
y: 10, | ||
}; | ||
}}> | ||
foo | ||
</ScrollItem> | ||
</Scroller>, | ||
div | ||
); | ||
var sut = TestUtils.findRenderedDOMComponentWithClass(wrapper, 'scrollable-item'); | ||
var node = sut.getDOMNode(); | ||
var clientStyles = StyleHelper.scrollStyles({y:10}); | ||
setTimeout(function(){ | ||
for(var prop in clientStyles) { | ||
expect(node.style[prop]).toEqual(clientStyles[prop]); | ||
} | ||
expect(node.getAttribute('style')).toEqual(node.style.cssText); | ||
done(); | ||
},1); | ||
}); | ||
it("cleanup serverStyles after componentDidUpdate", function (done) { | ||
var Scroller = MockScroller(); | ||
function positions10() {return {y:10};} | ||
function positions20() {return {y:20};} | ||
var SuposedConsumer = React.createClass({ | ||
getInitialState: function() {return {changePositions:false};}, | ||
render: function() { | ||
var handler = this.state.changePositions ? positions20 : positions10; | ||
return ( | ||
<Scroller ref="wrapper"> | ||
<ScrollItem name="foo" scrollHandler={handler} serverStyles={handler}> | ||
foo | ||
</ScrollItem> | ||
</Scroller> | ||
); | ||
}, | ||
}); | ||
var consumer = React.render( | ||
<SuposedConsumer />, | ||
div | ||
); | ||
var sut = TestUtils.findRenderedComponentWithType(consumer, ScrollItem); | ||
var node = sut.getDOMNode(); | ||
// update state | ||
var clientStyles = StyleHelper.scrollStyles({y:20}); | ||
sut._prevStyles = clientStyles; // this would be done by <Scroller> | ||
consumer.setState({changePositions: true}, function() { | ||
setTimeout(function(){ | ||
for(var prop in clientStyles) { | ||
expect(node.style[prop]).toEqual(clientStyles[prop]); | ||
} | ||
expect(node.getAttribute('style')).toEqual(node.style.cssText); | ||
done(); | ||
done(); | ||
},300); | ||
}); | ||
}); | ||
}); | ||
@@ -147,0 +218,0 @@ |
@@ -222,35 +222,2 @@ /* Copyright 2015, Yahoo Inc. | ||
it("cleanup serverStyles after nextTick", function (done) { | ||
var sut = React.render( | ||
<Scroller> | ||
</Scroller>, | ||
div | ||
); | ||
var clientStyles = StyleHelper.scrollStyles({y:10}); | ||
var wrapper = document.createElement('section'); | ||
var style = 'height:3px;transform:translate3d(0px, 10px, 0px);-webkit-transform:translate3d(0px, 10px, 0px);-moz-transform:translate3d(0px, 10px, 0px);-o-transform:translate3d(0px, 10px, 0px);-ms-transform:translate3d(0px, 10px, 0px);'; | ||
wrapper.innerHTML = '<div style="'+style+'"></div>'; | ||
var node = wrapper.querySelector('div'); | ||
var fooItem = { | ||
props: { | ||
name: 'foo', | ||
scrollHandler: function(x, y) { | ||
return {height:'20px'}; | ||
}, | ||
}, | ||
_node: node, | ||
_prevStyles: clientStyles, | ||
}; | ||
sut._registerItem(fooItem); | ||
sut._resetScroll = function(){ | ||
for(var prop in clientStyles) { | ||
expect(node.style[prop]).toEqual(clientStyles[prop]); | ||
} | ||
expect(node.getAttribute('style')).toEqual(node.style.cssText); | ||
done(); | ||
}; | ||
}); | ||
it("self, items and scroller params passed properly", function () { | ||
@@ -257,0 +224,0 @@ var sut = React.render( |
@@ -41,5 +41,10 @@ /* Copyright 2015, Yahoo Inc. | ||
this._node = this.getDOMNode(); | ||
cleanupStyles(this); | ||
this._pendingOperation && this._pendingOperation(); | ||
}, | ||
componentDidUpdate: function() { | ||
cleanupStyles(this); | ||
}, | ||
componentWillUnmount: function () { | ||
@@ -53,15 +58,17 @@ this._node = null; | ||
render: function () { | ||
var ownProps = {className: "scrollable-item"}; | ||
var ssStyles = this.props.serverStyles; | ||
if (ssStyles) { | ||
var styleObject; | ||
try { | ||
styleObject = ssStyles(this, this._scrollingParent); | ||
} catch(e) {} | ||
if (styleObject) { | ||
// store for cleanup phase | ||
this._prevStyles = StyleHelper.scrollStyles(styleObject, /* serverTick */true); | ||
ownProps.style = StyleHelper.prefixAll(this._prevStyles); | ||
} | ||
} | ||
var ownProps = { | ||
className: "scrollable-item", | ||
style: {}, | ||
}; | ||
try { | ||
/* | ||
this._prevStyles is always client-side, after the first rendering values. It might be initialized | ||
by serverStyles or a race condition could make the first use of _prevStyles to be output of the | ||
setStyleWithPosition on <Scroller>. If we don't have it yet, it is initialized below, otherwise, | ||
we just keep it for consistency. | ||
*/ | ||
var ssStyles = this.props.serverStyles; | ||
this._prevStyles = this._prevStyles || StyleHelper.scrollStyles(ssStyles(this, this._scrollingParent), /* serverTick = */ false); | ||
ownProps.style = StyleHelper.prefixAll(StyleHelper.scrollStyles(ssStyles(this, this._scrollingParent), /* serverTick = */ true)); | ||
} catch(e) {} | ||
return ( | ||
@@ -76,2 +83,38 @@ React.createElement("div", React.__spread(ownProps, this.props), | ||
/* | ||
cleanupStyles | ||
------------- | ||
Used for removing all prefixed versions added by server-side rendering. | ||
There is a lot of edge-cases in browsers with vendor prefixes, so the | ||
only strategy that works consistently is let React render all prefixed styles | ||
at all times, then having this cleanup phase that removes all styles, then adds | ||
back the styles that we should keep. | ||
Given: <div style="-webkit-transform:FOO;transform:FOO;"> | ||
Known cases: | ||
* In Chrome <= 42 the example will render the values for -webkit-transform | ||
even though unprefixed transform is supported. This will cause the runtime | ||
to use transform while the browser is rendering -webkit-transform. | ||
* Both Safari and Chrome will remove all combination of prefixes when | ||
removing a property. So `_node.style.WebkitTransform = null;` will also | ||
remove `_node.style.transform`. | ||
We go though all this trouble to make sure we recover from server-side rendering | ||
without using states. This whole library should not use state in any fashion, for | ||
the simple reason of not interfering with the consumer application. If we used | ||
state in any fashion, then it would require every single consumer to fine-tune | ||
their component for double-step mounting. | ||
*/ | ||
function cleanupStyles(item) { | ||
item._node.removeAttribute('style'); | ||
var props = item._prevStyles; | ||
if (props) { | ||
// cannot re-use applyStyles because should not check _prevStyles | ||
for(var prop in props) { | ||
item._node.style[prop] = props[prop]; | ||
} | ||
} | ||
} | ||
module.exports = ScrollItem; |
@@ -329,6 +329,2 @@ /* Copyright 2015, Yahoo Inc. | ||
if (self.isMounted()) { | ||
var items = self._scrollItems; | ||
for(var itemK in items) { | ||
cleanupStyles(items[itemK]); | ||
} | ||
self._resetScroll(); | ||
@@ -386,34 +382,2 @@ } | ||
/* | ||
cleanupStyles | ||
------------- | ||
Used for removing all prefixed versions added by server-side rendering. | ||
There is a lot of edge-cases in browsers with vendor prefixes, so the | ||
only strategy that works consistently is removing all styles, then adding | ||
back the styles that we should keep. | ||
Given: <div style="-webkit-transform:FOO;transform:FOO;"> | ||
Known cases: | ||
* In Chrome <= 42 the example will render the values for -webkit-transform | ||
even though unprefixed transform is supported. This will cause the runtime | ||
to use transform while the browser is rendering -webkit-transform. | ||
* Both Safari and Chrome will remove all combination of prefixes when | ||
removing a property. So `_node.style.WebkitTransform = null;` will also | ||
remove `_node.style.transform`. | ||
*/ | ||
function cleanupStyles(item) { | ||
if(item._node) { | ||
item._node.removeAttribute('style'); | ||
var props = item._prevStyles; | ||
if (props) { | ||
// cannot re-use applyStyles because should not check _prevStyles | ||
for(var prop in props) { | ||
item._node.style[prop] = props[prop]; | ||
} | ||
} | ||
} | ||
} | ||
function queueStylesOperation(item, styleObject) { | ||
@@ -420,0 +384,0 @@ return applyStyles.bind(null, item, styleObject); |
1938410
3007