mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-31 10:11:38 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71cfd23624 | ||
|
|
77b8e4a1fc | ||
|
|
9543a79c3f | ||
|
|
e3eea6e132 | ||
|
|
4d3418a968 | ||
|
|
ea9bc734f1 | ||
|
|
e03af435ac | ||
|
|
97c0a31ce6 | ||
|
|
25d11ded46 | ||
|
|
6a73d77030 | ||
|
|
0b63ba4e89 | ||
|
|
51109d0768 | ||
|
|
ac04ecd69e | ||
|
|
1a670ba6a7 | ||
|
|
7a16d5711c | ||
|
|
9dde70fff5 | ||
|
|
203980ab66 | ||
|
|
924dc36d4a | ||
|
|
9b2421cdfa | ||
|
|
36ea662402 | ||
|
|
69962ae815 | ||
|
|
62d1a0f83d | ||
|
|
910286303a | ||
|
|
706fa887e6 | ||
|
|
c589d79035 | ||
|
|
83e4c68461 |
@@ -1,11 +0,0 @@
|
||||
var path = require('path')
|
||||
|
||||
var ROOT = path.join(__dirname, '..')
|
||||
|
||||
module.exports = {
|
||||
DIST_DIRECTORY: path.join(ROOT, 'dist'),
|
||||
EXAMPLES_DIRECTORY: path.join(ROOT, 'examples'),
|
||||
SRC_DIRECTORY: path.join(ROOT, 'src'),
|
||||
ROOT_DIRECTORY: ROOT,
|
||||
TEST_ENTRY: path.join(ROOT, 'tests.webpack.js')
|
||||
}
|
||||
@@ -16,8 +16,11 @@ into `runApplication`. These should always be used as a pair.
|
||||
(web) static **prerenderApplication**(appKey:string, appParameters: object)
|
||||
|
||||
Renders the given application to an HTML string. Use this for server-side
|
||||
rendering. Return object is of type `{ html: string; style: string; }`, where
|
||||
`html` the prerendered HTML, and `style` is the prerendered style sheet.
|
||||
rendering. Return object is of type `{ html: string; style: string;
|
||||
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
|
||||
prerendered style sheet, and `styleElement` is a React Component. It's
|
||||
recommended that you use `styleElement` to render the style sheet in an app
|
||||
shell.
|
||||
|
||||
static **registerConfig**(config: Array<AppConfig>)
|
||||
|
||||
|
||||
@@ -11,6 +11,18 @@ outside of the render loop and are applied as inline styles. Read more about to
|
||||
|
||||
Each key of the object passed to `create` must define a style object.
|
||||
|
||||
**flatten**: function
|
||||
|
||||
Flattens an array of styles into a single style object.
|
||||
|
||||
**renderToString**: function
|
||||
|
||||
Returns a string of CSS used to style the application.
|
||||
|
||||
## Properties
|
||||
|
||||
**hairlineWidth**: number
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
|
||||
@@ -60,7 +60,8 @@ This function is called on press.
|
||||
+ `letterSpacing`
|
||||
+ `lineHeight`
|
||||
+ `textAlign`
|
||||
+ `textDecoration`
|
||||
+ `textAlignVertical`
|
||||
+ `textDecorationLine`
|
||||
+ `textShadow`
|
||||
+ `textTransform`
|
||||
+ `whiteSpace`
|
||||
|
||||
@@ -70,7 +70,6 @@ module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'Platform.OS': 'web',
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ React.renderToStaticMarkup(<div />)
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
|
||||
```
|
||||
```js
|
||||
// App.js
|
||||
|
||||
import React, { AppRegistry } from 'react-native'
|
||||
@@ -64,12 +64,12 @@ rendering.
|
||||
|
||||
import React from 'react-native'
|
||||
|
||||
const AppShell = (html, style) => (
|
||||
const AppShell = (html, styleElement) => (
|
||||
<html>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta content="initial-scale=1,width=device-width" name="viewport" />
|
||||
{style}
|
||||
{styleElement}
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-app" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
@@ -90,8 +90,8 @@ import AppShell from './AppShell'
|
||||
AppRegistry.registerComponent('App', () => App)
|
||||
|
||||
// prerenders the app
|
||||
const { html, style } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
const { html, style, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
|
||||
// renders the full-page markup
|
||||
const renderedApplicationHTML = React.renderToString(<AppShell html={html} style={style} />)
|
||||
const renderedApplicationHTML = React.renderToStaticMarkup(<AppShell html={html} styleElement={styleElement} />)
|
||||
```
|
||||
|
||||
@@ -174,16 +174,16 @@ const styles = StyleSheet.create({
|
||||
CSS output:
|
||||
|
||||
```css
|
||||
._s1 { color: gray; }
|
||||
._s2 { font-size: 2rem; }
|
||||
._s3 { font-size: 1.25rem; }
|
||||
.__style1 { color: gray; }
|
||||
.__style2 { font-size: 2rem; }
|
||||
.__style3 { font-size: 1.25rem; }
|
||||
```
|
||||
|
||||
Rendered HTML:
|
||||
|
||||
```html
|
||||
<span className="_s1 _s2">Heading</span>
|
||||
<span className="_s1 _s3">Text</span>
|
||||
<span className="__style1 __style2">Heading</span>
|
||||
<span className="__style1 __style3">Text</span>
|
||||
```
|
||||
|
||||
### Reset
|
||||
@@ -200,6 +200,7 @@ html {
|
||||
font-family: sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color:rgba(0,0,0,0)
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -214,12 +215,6 @@ input::-moz-focus-inner {
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
li {
|
||||
list-style:none
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
324
examples/2048/Game2048.js
Normal file
324
examples/2048/Game2048.js
Normal file
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule Game2048
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Animated,
|
||||
AppRegistry,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableBounce,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var GameBoard = require('./GameBoard');
|
||||
|
||||
var BOARD_PADDING = 3;
|
||||
var CELL_MARGIN = 4;
|
||||
var CELL_SIZE = 60;
|
||||
|
||||
class Cell extends React.Component {
|
||||
render() {
|
||||
return <View style={styles.cell} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Board extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.board}>
|
||||
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
|
||||
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
|
||||
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
|
||||
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Tile extends React.Component {
|
||||
state: any;
|
||||
|
||||
static _getPosition(index): number {
|
||||
return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
|
||||
}
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
|
||||
var tile = this.props.tile;
|
||||
|
||||
this.state = {
|
||||
opacity: new Animated.Value(0),
|
||||
top: new Animated.Value(Tile._getPosition(tile.toRow())),
|
||||
left: new Animated.Value(Tile._getPosition(tile.toColumn())),
|
||||
};
|
||||
}
|
||||
|
||||
calculateOffset(): {top: number; left: number; opacity: number} {
|
||||
var tile = this.props.tile;
|
||||
|
||||
var offset = {
|
||||
top: this.state.top,
|
||||
left: this.state.left,
|
||||
opacity: this.state.opacity,
|
||||
};
|
||||
|
||||
if (tile.isNew()) {
|
||||
Animated.timing(this.state.opacity, {
|
||||
duration: 100,
|
||||
toValue: 1,
|
||||
}).start();
|
||||
} else {
|
||||
Animated.parallel([
|
||||
Animated.timing(offset.top, {
|
||||
duration: 100,
|
||||
toValue: Tile._getPosition(tile.toRow()),
|
||||
}),
|
||||
Animated.timing(offset.left, {
|
||||
duration: 100,
|
||||
toValue: Tile._getPosition(tile.toColumn()),
|
||||
}),
|
||||
]).start();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
render() {
|
||||
var tile = this.props.tile;
|
||||
|
||||
var tileStyles = [
|
||||
styles.tile,
|
||||
styles['tile' + tile.value],
|
||||
this.calculateOffset(),
|
||||
];
|
||||
|
||||
var textStyles = [
|
||||
styles.value,
|
||||
tile.value > 4 && styles.whiteText,
|
||||
tile.value > 100 && styles.threeDigits,
|
||||
tile.value > 1000 && styles.fourDigits,
|
||||
];
|
||||
|
||||
return (
|
||||
<Animated.View style={tileStyles}>
|
||||
<Text style={textStyles}>{tile.value}</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GameEndOverlay extends React.Component {
|
||||
render() {
|
||||
var board = this.props.board;
|
||||
|
||||
if (!board.hasWon() && !board.hasLost()) {
|
||||
return <View/>;
|
||||
}
|
||||
|
||||
var message = board.hasWon() ?
|
||||
'Good Job!' : 'Game Over';
|
||||
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<Text style={styles.overlayMessage}>{message}</Text>
|
||||
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||
</TouchableBounce>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Game2048 extends React.Component {
|
||||
startX: number;
|
||||
startY: number;
|
||||
state: any;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
board: new GameBoard(),
|
||||
};
|
||||
this.startX = 0;
|
||||
this.startY = 0;
|
||||
}
|
||||
|
||||
restartGame() {
|
||||
this.setState({board: new GameBoard()});
|
||||
}
|
||||
|
||||
handleTouchStart(event: Object) {
|
||||
if (this.state.board.hasWon()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.startX = event.nativeEvent.pageX;
|
||||
this.startY = event.nativeEvent.pageY;
|
||||
}
|
||||
|
||||
handleTouchEnd(event: Object) {
|
||||
if (this.state.board.hasWon()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var deltaX = event.nativeEvent.pageX - this.startX;
|
||||
var deltaY = event.nativeEvent.pageY - this.startY;
|
||||
|
||||
var direction = -1;
|
||||
if (Math.abs(deltaX) > 3 * Math.abs(deltaY) && Math.abs(deltaX) > 30) {
|
||||
direction = deltaX > 0 ? 2 : 0;
|
||||
} else if (Math.abs(deltaY) > 3 * Math.abs(deltaX) && Math.abs(deltaY) > 30) {
|
||||
direction = deltaY > 0 ? 3 : 1;
|
||||
}
|
||||
|
||||
if (direction !== -1) {
|
||||
this.setState({board: this.state.board.move(direction)});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var tiles = this.state.board.tiles
|
||||
.filter((tile) => tile.value)
|
||||
.map((tile) => <Tile ref={tile.id} key={tile.id} tile={tile} />);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
onTouchStart={(event) => this.handleTouchStart(event)}
|
||||
onTouchEnd={(event) => this.handleTouchEnd(event)}>
|
||||
<Board>
|
||||
{tiles}
|
||||
</Board>
|
||||
<GameEndOverlay board={this.state.board} onRestart={() => this.restartGame()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
board: {
|
||||
padding: BOARD_PADDING,
|
||||
backgroundColor: '#bbaaaa',
|
||||
borderRadius: 5,
|
||||
},
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'rgba(221, 221, 221, 0.5)',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
overlayMessage: {
|
||||
fontSize: 40,
|
||||
marginBottom: 20,
|
||||
},
|
||||
tryAgain: {
|
||||
backgroundColor: '#887761',
|
||||
padding: 20,
|
||||
borderRadius: 5,
|
||||
},
|
||||
tryAgainText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 20,
|
||||
fontWeight: '500',
|
||||
},
|
||||
cell: {
|
||||
width: CELL_SIZE,
|
||||
height: CELL_SIZE,
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#ddccbb',
|
||||
margin: CELL_MARGIN,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tile: {
|
||||
position: 'absolute',
|
||||
width: CELL_SIZE,
|
||||
height: CELL_SIZE,
|
||||
backgroundColor: '#ddccbb',
|
||||
borderRadius: 5,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
value: {
|
||||
fontSize: 24,
|
||||
color: '#776666',
|
||||
fontFamily: 'Verdana',
|
||||
fontWeight: '500',
|
||||
},
|
||||
tile2: {
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
tile4: {
|
||||
backgroundColor: '#eeeecc',
|
||||
},
|
||||
tile8: {
|
||||
backgroundColor: '#ffbb87',
|
||||
},
|
||||
tile16: {
|
||||
backgroundColor: '#ff9966',
|
||||
},
|
||||
tile32: {
|
||||
backgroundColor: '#ff7755',
|
||||
},
|
||||
tile64: {
|
||||
backgroundColor: '#ff5533',
|
||||
},
|
||||
tile128: {
|
||||
backgroundColor: '#eecc77',
|
||||
},
|
||||
tile256: {
|
||||
backgroundColor: '#eecc66',
|
||||
},
|
||||
tile512: {
|
||||
backgroundColor: '#eecc55',
|
||||
},
|
||||
tile1024: {
|
||||
backgroundColor: '#eecc33',
|
||||
},
|
||||
tile2048: {
|
||||
backgroundColor: '#eecc22',
|
||||
},
|
||||
whiteText: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
threeDigits: {
|
||||
fontSize: 20,
|
||||
},
|
||||
fourDigits: {
|
||||
fontSize: 18,
|
||||
},
|
||||
});
|
||||
|
||||
AppRegistry.registerComponent('Game2048', () => Game2048);
|
||||
|
||||
module.exports = Game2048;
|
||||
201
examples/2048/GameBoard.js
Normal file
201
examples/2048/GameBoard.js
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule GameBoard
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js
|
||||
// with no modification except to format it for CommonJS and fix lint/flow errors
|
||||
|
||||
var rotateLeft = function (matrix) {
|
||||
var rows = matrix.length;
|
||||
var columns = matrix[0].length;
|
||||
var res = [];
|
||||
for (var row = 0; row < rows; ++row) {
|
||||
res.push([]);
|
||||
for (var column = 0; column < columns; ++column) {
|
||||
res[row][column] = matrix[column][columns - row - 1];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
var Tile = function (value?: number, row?: number, column?: number) {
|
||||
this.value = value || 0;
|
||||
this.row = row || -1;
|
||||
|
||||
this.column = column || -1;
|
||||
this.oldRow = -1;
|
||||
this.oldColumn = -1;
|
||||
this.markForDeletion = false;
|
||||
this.mergedInto = null;
|
||||
this.id = Tile.id++;
|
||||
};
|
||||
|
||||
Tile.id = 0;
|
||||
|
||||
Tile.prototype.moveTo = function (row, column) {
|
||||
this.oldRow = this.row;
|
||||
this.oldColumn = this.column;
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
};
|
||||
|
||||
Tile.prototype.isNew = function () {
|
||||
return this.oldRow === -1 && !this.mergedInto;
|
||||
};
|
||||
|
||||
Tile.prototype.hasMoved = function () {
|
||||
return (this.fromRow() !== -1 && (this.fromRow() !== this.toRow() || this.fromColumn() !== this.toColumn())) ||
|
||||
this.mergedInto;
|
||||
};
|
||||
|
||||
Tile.prototype.fromRow = function () {
|
||||
return this.mergedInto ? this.row : this.oldRow;
|
||||
};
|
||||
|
||||
Tile.prototype.fromColumn = function () {
|
||||
return this.mergedInto ? this.column : this.oldColumn;
|
||||
};
|
||||
|
||||
Tile.prototype.toRow = function () {
|
||||
return this.mergedInto ? this.mergedInto.row : this.row;
|
||||
};
|
||||
|
||||
Tile.prototype.toColumn = function () {
|
||||
return this.mergedInto ? this.mergedInto.column : this.column;
|
||||
};
|
||||
|
||||
var Board = function () {
|
||||
this.tiles = [];
|
||||
this.cells = [];
|
||||
for (var i = 0; i < Board.size; ++i) {
|
||||
this.cells[i] = [this.addTile(), this.addTile(), this.addTile(), this.addTile()];
|
||||
}
|
||||
this.addRandomTile();
|
||||
this.setPositions();
|
||||
this.won = false;
|
||||
};
|
||||
|
||||
Board.prototype.addTile = function () {
|
||||
var res = new Tile();
|
||||
Tile.apply(res, arguments);
|
||||
this.tiles.push(res);
|
||||
return res;
|
||||
};
|
||||
|
||||
Board.size = 4;
|
||||
|
||||
Board.prototype.moveLeft = function () {
|
||||
var hasChanged = false;
|
||||
for (var row = 0; row < Board.size; ++row) {
|
||||
var currentRow = this.cells[row].filter(function (tile) { return tile.value !== 0; });
|
||||
var resultRow = [];
|
||||
for (var target = 0; target < Board.size; ++target) {
|
||||
var targetTile = currentRow.length ? currentRow.shift() : this.addTile();
|
||||
if (currentRow.length > 0 && currentRow[0].value === targetTile.value) {
|
||||
var tile1 = targetTile;
|
||||
targetTile = this.addTile(targetTile.value);
|
||||
tile1.mergedInto = targetTile;
|
||||
var tile2 = currentRow.shift();
|
||||
tile2.mergedInto = targetTile;
|
||||
targetTile.value += tile2.value;
|
||||
}
|
||||
resultRow[target] = targetTile;
|
||||
this.won = this.won || (targetTile.value === 2048);
|
||||
hasChanged = hasChanged || (targetTile.value !== this.cells[row][target].value);
|
||||
}
|
||||
this.cells[row] = resultRow;
|
||||
}
|
||||
return hasChanged;
|
||||
};
|
||||
|
||||
Board.prototype.setPositions = function () {
|
||||
this.cells.forEach(function (row, rowIndex) {
|
||||
row.forEach(function (tile, columnIndex) {
|
||||
tile.oldRow = tile.row;
|
||||
tile.oldColumn = tile.column;
|
||||
tile.row = rowIndex;
|
||||
tile.column = columnIndex;
|
||||
tile.markForDeletion = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Board.fourProbability = 0.1;
|
||||
|
||||
Board.prototype.addRandomTile = function () {
|
||||
var emptyCells = [];
|
||||
for (var r = 0; r < Board.size; ++r) {
|
||||
for (var c = 0; c < Board.size; ++c) {
|
||||
if (this.cells[r][c].value === 0) {
|
||||
emptyCells.push({r: r, c: c});
|
||||
}
|
||||
}
|
||||
}
|
||||
var index = Math.floor(Math.random() * emptyCells.length);
|
||||
var cell = emptyCells[index];
|
||||
var newValue = Math.random() < Board.fourProbability ? 4 : 2;
|
||||
this.cells[cell.r][cell.c] = this.addTile(newValue);
|
||||
};
|
||||
|
||||
Board.prototype.move = function (direction) {
|
||||
// 0 -> left, 1 -> up, 2 -> right, 3 -> down
|
||||
this.clearOldTiles();
|
||||
for (var i = 0; i < direction; ++i) {
|
||||
this.cells = rotateLeft(this.cells);
|
||||
}
|
||||
var hasChanged = this.moveLeft();
|
||||
for (var i = direction; i < 4; ++i) {
|
||||
this.cells = rotateLeft(this.cells);
|
||||
}
|
||||
if (hasChanged) {
|
||||
this.addRandomTile();
|
||||
}
|
||||
this.setPositions();
|
||||
return this;
|
||||
};
|
||||
|
||||
Board.prototype.clearOldTiles = function () {
|
||||
this.tiles = this.tiles.filter(function (tile) { return tile.markForDeletion === false; });
|
||||
this.tiles.forEach(function (tile) { tile.markForDeletion = true; });
|
||||
};
|
||||
|
||||
Board.prototype.hasWon = function () {
|
||||
return this.won;
|
||||
};
|
||||
|
||||
Board.deltaX = [-1, 0, 1, 0];
|
||||
Board.deltaY = [0, -1, 0, 1];
|
||||
|
||||
Board.prototype.hasLost = function () {
|
||||
var canMove = false;
|
||||
for (var row = 0; row < Board.size; ++row) {
|
||||
for (var column = 0; column < Board.size; ++column) {
|
||||
canMove = canMove || (this.cells[row][column].value === 0);
|
||||
for (var dir = 0; dir < 4; ++dir) {
|
||||
var newRow = row + Board.deltaX[dir];
|
||||
var newColumn = column + Board.deltaY[dir];
|
||||
if (newRow < 0 || newRow >= Board.size || newColumn < 0 || newColumn >= Board.size) {
|
||||
continue;
|
||||
}
|
||||
canMove = canMove || (this.cells[row][column].value === this.cells[newRow][newColumn].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return !canMove;
|
||||
};
|
||||
|
||||
module.exports = Board;
|
||||
321
examples/TicTacToe/TicTacToe.js
Normal file
321
examples/TicTacToe/TicTacToe.js
Normal file
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule TicTacToeApp
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
AppRegistry,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
class Board {
|
||||
grid: Array<Array<number>>;
|
||||
turn: number;
|
||||
|
||||
constructor() {
|
||||
var size = 3;
|
||||
var grid = Array(size);
|
||||
for (var i = 0; i < size; i++) {
|
||||
var row = Array(size);
|
||||
for (var j = 0; j < size; j++) {
|
||||
row[j] = 0;
|
||||
}
|
||||
grid[i] = row;
|
||||
}
|
||||
this.grid = grid;
|
||||
|
||||
this.turn = 1;
|
||||
}
|
||||
|
||||
mark(row: number, col: number, player: number): Board {
|
||||
this.grid[row][col] = player;
|
||||
return this;
|
||||
}
|
||||
|
||||
hasMark(row: number, col: number): boolean {
|
||||
return this.grid[row][col] !== 0;
|
||||
}
|
||||
|
||||
winner(): ?number {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
if (this.grid[i][0] !== 0 && this.grid[i][0] === this.grid[i][1] &&
|
||||
this.grid[i][0] === this.grid[i][2]) {
|
||||
return this.grid[i][0];
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
if (this.grid[0][i] !== 0 && this.grid[0][i] === this.grid[1][i] &&
|
||||
this.grid[0][i] === this.grid[2][i]) {
|
||||
return this.grid[0][i];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.grid[0][0] !== 0 && this.grid[0][0] === this.grid[1][1] &&
|
||||
this.grid[0][0] === this.grid[2][2]) {
|
||||
return this.grid[0][0];
|
||||
}
|
||||
|
||||
if (this.grid[0][2] !== 0 && this.grid[0][2] === this.grid[1][1] &&
|
||||
this.grid[0][2] === this.grid[2][0]) {
|
||||
return this.grid[0][2];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
tie(): boolean {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
for (var j = 0; j < 3; j++) {
|
||||
if (this.grid[i][j] === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.winner() === null;
|
||||
}
|
||||
}
|
||||
|
||||
var Cell = React.createClass({
|
||||
cellStyle() {
|
||||
switch (this.props.player) {
|
||||
case 1:
|
||||
return styles.cellX;
|
||||
case 2:
|
||||
return styles.cellO;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
textStyle() {
|
||||
switch (this.props.player) {
|
||||
case 1:
|
||||
return styles.cellTextX;
|
||||
case 2:
|
||||
return styles.cellTextO;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
textContents() {
|
||||
switch (this.props.player) {
|
||||
case 1:
|
||||
return 'X';
|
||||
case 2:
|
||||
return 'O';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
onPress={this.props.onPress}
|
||||
underlayColor="transparent"
|
||||
activeOpacity={0.5}>
|
||||
<View style={[styles.cell, this.cellStyle()]}>
|
||||
<Text style={[styles.cellText, this.textStyle()]}>
|
||||
{this.textContents()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var GameEndOverlay = React.createClass({
|
||||
render() {
|
||||
var board = this.props.board;
|
||||
|
||||
var tie = board.tie();
|
||||
var winner = board.winner();
|
||||
if (!winner && !tie) {
|
||||
return <View />;
|
||||
}
|
||||
|
||||
var message;
|
||||
if (tie) {
|
||||
message = 'It\'s a tie!';
|
||||
} else {
|
||||
message = (winner === 1 ? 'X' : 'O') + ' wins!';
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<Text style={styles.overlayMessage}>{message}</Text>
|
||||
<TouchableHighlight
|
||||
onPress={this.props.onRestart}
|
||||
underlayColor="transparent"
|
||||
activeOpacity={0.5}>
|
||||
<View style={styles.newGame}>
|
||||
<Text style={styles.newGameText}>New Game</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TicTacToeApp = React.createClass({
|
||||
getInitialState() {
|
||||
return { board: new Board(), player: 1 };
|
||||
},
|
||||
|
||||
restartGame() {
|
||||
this.setState(this.getInitialState());
|
||||
},
|
||||
|
||||
nextPlayer(): number {
|
||||
return this.state.player === 1 ? 2 : 1;
|
||||
},
|
||||
|
||||
handleCellPress(row: number, col: number) {
|
||||
if (this.state.board.hasMark(row, col)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
board: this.state.board.mark(row, col, this.state.player),
|
||||
player: this.nextPlayer(),
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
var rows = this.state.board.grid.map((cells, row) =>
|
||||
<View key={'row' + row} style={styles.row}>
|
||||
{cells.map((player, col) =>
|
||||
<Cell
|
||||
key={'cell' + col}
|
||||
player={player}
|
||||
onPress={this.handleCellPress.bind(this, row, col)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>EXTREME T3</Text>
|
||||
<View style={styles.board}>
|
||||
{rows}
|
||||
</View>
|
||||
<GameEndOverlay
|
||||
board={this.state.board}
|
||||
onRestart={this.restartGame}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'white'
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Chalkduster',
|
||||
fontSize: 39,
|
||||
marginBottom: 20,
|
||||
},
|
||||
board: {
|
||||
padding: 5,
|
||||
backgroundColor: '#47525d',
|
||||
borderRadius: 10,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
|
||||
// CELL
|
||||
|
||||
cell: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#7b8994',
|
||||
margin: 5,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cellX: {
|
||||
backgroundColor: '#72d0eb',
|
||||
},
|
||||
cellO: {
|
||||
backgroundColor: '#7ebd26',
|
||||
},
|
||||
|
||||
// CELL TEXT
|
||||
|
||||
cellText: {
|
||||
borderRadius: 5,
|
||||
fontSize: 50,
|
||||
fontFamily: 'AvenirNext-Bold',
|
||||
},
|
||||
cellTextX: {
|
||||
color: '#19a9e5',
|
||||
},
|
||||
cellTextO: {
|
||||
color: '#b9dc2f',
|
||||
},
|
||||
|
||||
// GAME OVER
|
||||
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'rgba(221, 221, 221, 0.5)',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
overlayMessage: {
|
||||
fontSize: 40,
|
||||
marginBottom: 20,
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
fontFamily: 'AvenirNext-DemiBold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
newGame: {
|
||||
backgroundColor: '#887765',
|
||||
padding: 20,
|
||||
borderRadius: 5,
|
||||
},
|
||||
newGameText: {
|
||||
color: 'white',
|
||||
fontSize: 20,
|
||||
fontFamily: 'AvenirNext-DemiBold',
|
||||
},
|
||||
});
|
||||
|
||||
AppRegistry.registerComponent('TicTacToeApp', () => TicTacToeApp);
|
||||
|
||||
module.exports = TicTacToeApp;
|
||||
@@ -3,4 +3,4 @@
|
||||
<title>React Native for Web</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<div id="react-root"></div>
|
||||
<script src="/examples.js"></script>
|
||||
<script src="/bundle.js"></script>
|
||||
|
||||
@@ -1,23 +1,7 @@
|
||||
import { MediaProvider, matchMedia } from 'react-media-queries'
|
||||
import App from './components/App'
|
||||
import createGetter from 'react-media-queries/lib/createMediaQueryGetter'
|
||||
import createListener from 'react-media-queries/lib/createMediaQueryListener'
|
||||
import React, { AppRegistry } from 'react-native'
|
||||
import Game2048 from './2048/Game2048'
|
||||
import TicTacToeApp from './TicTacToe/TicTacToe'
|
||||
|
||||
const mediaQueries = {
|
||||
small: '(min-width: 300px)',
|
||||
medium: '(min-width: 400px)',
|
||||
large: '(min-width: 500px)'
|
||||
}
|
||||
const ResponsiveApp = matchMedia()(App)
|
||||
const WrappedApp = () => (
|
||||
<MediaProvider getMedia={createGetter(mediaQueries)} listener={createListener(mediaQueries)}>
|
||||
<ResponsiveApp />
|
||||
</MediaProvider>
|
||||
)
|
||||
|
||||
AppRegistry.registerComponent('Example', () => WrappedApp)
|
||||
|
||||
AppRegistry.runApplication('Example', {
|
||||
AppRegistry.runApplication('Game2048', {
|
||||
rootTag: document.getElementById('react-root')
|
||||
})
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const constants = require('./constants')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const EXAMPLES_DIRECTORY = __dirname
|
||||
|
||||
module.exports = {
|
||||
devServer: {
|
||||
contentBase: constants.EXAMPLES_DIRECTORY
|
||||
contentBase: EXAMPLES_DIRECTORY
|
||||
},
|
||||
entry: {
|
||||
example: constants.EXAMPLES_DIRECTORY
|
||||
example: EXAMPLES_DIRECTORY
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
@@ -20,7 +21,7 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: 'examples.js'
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
@@ -1,9 +1,9 @@
|
||||
var constants = require('./constants')
|
||||
var webpack = require('webpack')
|
||||
|
||||
var testEntry = 'tests.webpack.js'
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: constants.ROOT_DIRECTORY,
|
||||
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
|
||||
browserNoActivityTimeout: 60000,
|
||||
client: {
|
||||
@@ -12,7 +12,7 @@ module.exports = function (config) {
|
||||
useIframe: true
|
||||
},
|
||||
files: [
|
||||
constants.TEST_ENTRY
|
||||
testEntry
|
||||
],
|
||||
frameworks: [ 'mocha' ],
|
||||
plugins: [
|
||||
@@ -24,7 +24,7 @@ module.exports = function (config) {
|
||||
'karma-webpack'
|
||||
],
|
||||
preprocessors: {
|
||||
[constants.TEST_ENTRY]: [ 'webpack', 'sourcemap' ]
|
||||
[testEntry]: [ 'webpack', 'sourcemap' ]
|
||||
},
|
||||
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
|
||||
singleRun: true,
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.18",
|
||||
"version": "0.0.25",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
@@ -8,16 +8,16 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
|
||||
"build:umd": "webpack --config config/webpack.config.js --sort-assets-by --progress",
|
||||
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
|
||||
"lint": "eslint config src",
|
||||
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
|
||||
"examples": "webpack-dev-server --config examples/webpack.config.js --inline --hot --colors --quiet",
|
||||
"lint": "eslint src",
|
||||
"prepublish": "npm run build && npm run build:umd",
|
||||
"test": "karma start config/karma.config.js",
|
||||
"test": "karma start karma.config.js",
|
||||
"test:watch": "npm run test -- --no-single-run"
|
||||
},
|
||||
"dependencies": {
|
||||
"fbjs": "^0.7.2",
|
||||
"inline-style-prefix-all": "^1.0.3",
|
||||
"fbjs": "0.6.x || 0.7.x",
|
||||
"inline-style-prefix-all": "1.0.5",
|
||||
"lodash.debounce": "^4.0.3",
|
||||
"react-textarea-autosize": "^3.1.0",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
import { elementId } from '../../StyleSheet'
|
||||
import { prerenderApplication } from '../renderApplication'
|
||||
|
||||
const component = () => <div />
|
||||
|
||||
suite('apis/AppRegistry/renderApplication', () => {
|
||||
test('prerenderApplication', () => {
|
||||
const { html, style, styleElement } = prerenderApplication(component, {})
|
||||
|
||||
assert.ok(html.indexOf('<div ') > -1)
|
||||
assert.ok(typeof style === 'string')
|
||||
assert.equal(styleElement.type, 'style')
|
||||
assert.equal(styleElement.props.id, elementId)
|
||||
assert.equal(styleElement.props.dangerouslySetInnerHTML.__html, style)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,14 +13,16 @@ import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNativeApp from './ReactNativeApp'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
|
||||
const renderStyleSheetToString = () => `<style id="${StyleSheet.elementId}">${StyleSheet._renderToString()}</style>`
|
||||
const renderStyleSheetToString = () => StyleSheet.renderToString()
|
||||
const styleAsElement = (style) => <style dangerouslySetInnerHTML={{ __html: style }} id={StyleSheet.elementId} />
|
||||
const styleAsTagString = (style) => `<style id="${StyleSheet.elementId}">${style}</style>`
|
||||
|
||||
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
|
||||
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
|
||||
|
||||
// insert style sheet if needed
|
||||
const styleElement = document.getElementById(StyleSheet.elementId)
|
||||
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', renderStyleSheetToString()) }
|
||||
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', styleAsTagString(renderStyleSheetToString())) }
|
||||
|
||||
const component = (
|
||||
<ReactNativeApp
|
||||
@@ -41,5 +43,6 @@ export function prerenderApplication(RootComponent: Component, initialProps: Obj
|
||||
)
|
||||
const html = ReactDOMServer.renderToString(component)
|
||||
const style = renderStyleSheetToString()
|
||||
return { html, style }
|
||||
const styleElement = styleAsElement(style)
|
||||
return { html, style, styleElement }
|
||||
}
|
||||
|
||||
@@ -6,28 +6,38 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import debounce from 'lodash.debounce'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const dimensions = {
|
||||
screen: {
|
||||
fontScale: 1,
|
||||
get height() { return window.screen.height },
|
||||
scale: window.devicePixelRatio || 1,
|
||||
get width() { return window.screen.width }
|
||||
},
|
||||
window: {
|
||||
fontScale: 1,
|
||||
get height() { return window.innerHeight },
|
||||
scale: window.devicePixelRatio || 1,
|
||||
get width() { return window.innerWidth }
|
||||
}
|
||||
}
|
||||
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
|
||||
|
||||
const dimensions = {}
|
||||
|
||||
class Dimensions {
|
||||
static get(dimension: string): Object {
|
||||
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
|
||||
return dimensions[dimension]
|
||||
}
|
||||
|
||||
static set(): void {
|
||||
dimensions.window = {
|
||||
fontScale: 1,
|
||||
height: win.innerHeight,
|
||||
scale: win.devicePixelRatio || 1,
|
||||
width: win.innerWidth
|
||||
}
|
||||
|
||||
dimensions.screen = {
|
||||
fontScale: 1,
|
||||
height: win.screen.height,
|
||||
scale: win.devicePixelRatio || 1,
|
||||
width: win.screen.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dimensions.set()
|
||||
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50))
|
||||
|
||||
module.exports = Dimensions
|
||||
|
||||
@@ -6,9 +6,15 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection
|
||||
const connection = ExecutionEnvironment.canUseDOM && (
|
||||
window.navigator.connection ||
|
||||
window.navigator.mozConnection ||
|
||||
window.navigator.webkitConnection
|
||||
)
|
||||
|
||||
const eventTypes = [ 'change' ]
|
||||
|
||||
/**
|
||||
@@ -50,8 +56,8 @@ const NetInfo = {
|
||||
isConnected: {
|
||||
addEventListener(type: string, handler: Function): { remove: () => void } {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
window.addEventListener('online', handler.bind(true), false)
|
||||
window.addEventListener('offline', handler.bind(false), false)
|
||||
window.addEventListener('online', handler.bind(null, true), false)
|
||||
window.addEventListener('offline', handler.bind(null, false), false)
|
||||
|
||||
return {
|
||||
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
|
||||
@@ -60,8 +66,8 @@ const NetInfo = {
|
||||
|
||||
removeEventListener(type: string, handler: Function): void {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
window.removeEventListener('online', handler.bind(true), false)
|
||||
window.removeEventListener('offline', handler.bind(false), false)
|
||||
window.removeEventListener('online', handler.bind(null, true), false)
|
||||
window.removeEventListener('offline', handler.bind(null, false), false)
|
||||
},
|
||||
|
||||
fetch(): Promise {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import EventConstants from 'react/lib/EventConstants'
|
||||
import EventPluginRegistry from 'react/lib/EventPluginRegistry'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import ResponderEventPlugin from 'react/lib/ResponderEventPlugin'
|
||||
import ResponderTouchHistoryStore from 'react/lib/ResponderTouchHistoryStore'
|
||||
import normalizeNativeEvent from './normalizeNativeEvent'
|
||||
@@ -18,7 +19,10 @@ const {
|
||||
topTouchStart
|
||||
} = EventConstants.topLevelTypes
|
||||
|
||||
const supportsTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch
|
||||
const supportsTouch = ExecutionEnvironment.canUseDOM && (
|
||||
'ontouchstart' in window ||
|
||||
window.DocumentTouch && document instanceof window.DocumentTouch
|
||||
)
|
||||
|
||||
const endDependencies = supportsTouch ? [ topTouchCancel, topTouchEnd ] : [ topMouseUp ]
|
||||
const moveDependencies = supportsTouch ? [ topTouchMove ] : [ topMouseMove ]
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import prefixAll from 'inline-style-prefix-all'
|
||||
import hyphenate from './hyphenate'
|
||||
|
||||
class Store {
|
||||
constructor(
|
||||
initialState:Object = {},
|
||||
options:Object = { obfuscateClassNames: false }
|
||||
) {
|
||||
this._counter = 0
|
||||
this._classNames = { ...initialState.classNames }
|
||||
this._declarations = { ...initialState.declarations }
|
||||
this._options = options
|
||||
}
|
||||
|
||||
get(property, value) {
|
||||
const key = this._getDeclarationKey(property, value)
|
||||
return this._classNames[key]
|
||||
}
|
||||
|
||||
set(property, value) {
|
||||
if (value != null) {
|
||||
const values = this._getPropertyValues(property) || []
|
||||
if (values.indexOf(value) === -1) {
|
||||
values.push(value)
|
||||
this._setClassName(property, value)
|
||||
this._setPropertyValues(property, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
const obfuscate = this._options.obfuscateClassNames
|
||||
|
||||
// sort the properties to ensure shorthands are first in the cascade
|
||||
const properties = Object.keys(this._declarations).sort()
|
||||
|
||||
// transform the class name to a valid CSS selector
|
||||
const getCssSelector = (property, value) => {
|
||||
let className = this.get(property, value)
|
||||
if (!obfuscate && className) {
|
||||
className = className.replace(/[(),":?.%\\$#]/g, '\\$&')
|
||||
}
|
||||
return className
|
||||
}
|
||||
|
||||
// transform the declarations into CSS rules with vendor-prefixes
|
||||
const buildCSSRules = (property, values) => {
|
||||
return values.reduce((cssRules, value) => {
|
||||
const declarations = prefixAll({ [property]: value })
|
||||
const cssDeclarations = Object.keys(declarations).reduce((str, prop) => {
|
||||
const value = declarations[prop]
|
||||
str += `${hyphenate(prop)}:${value};`
|
||||
return str
|
||||
}, '')
|
||||
const selector = getCssSelector(property, value)
|
||||
|
||||
cssRules += `\n.${selector}{${cssDeclarations}}`
|
||||
|
||||
return cssRules
|
||||
}, '')
|
||||
}
|
||||
|
||||
const css = properties.reduce((css, property) => {
|
||||
const values = this._declarations[property]
|
||||
css += buildCSSRules(property, values)
|
||||
return css
|
||||
}, '')
|
||||
|
||||
return (`/* ${this._counter} unique declarations */${css}`)
|
||||
}
|
||||
|
||||
_getDeclarationKey(property, value) {
|
||||
return `${property}:${value}`
|
||||
}
|
||||
|
||||
_getPropertyValues(property) {
|
||||
return this._declarations[property]
|
||||
}
|
||||
|
||||
_setPropertyValues(property, values) {
|
||||
this._declarations[property] = values.map(value => value)
|
||||
}
|
||||
|
||||
_setClassName(property, value) {
|
||||
const key = this._getDeclarationKey(property, value)
|
||||
const exists = !!this._classNames[key]
|
||||
if (!exists) {
|
||||
this._counter += 1
|
||||
if (this._options.obfuscateClassNames) {
|
||||
this._classNames[key] = `_s_${this._counter}`
|
||||
} else {
|
||||
const val = `${value}`.replace(/\s/g, '-')
|
||||
this._classNames[key] = `${property}:${val}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Store
|
||||
@@ -7,42 +7,94 @@
|
||||
*/
|
||||
|
||||
import prefixAll from 'inline-style-prefix-all'
|
||||
import hyphenate from './hyphenate'
|
||||
import expandStyle from './expandStyle'
|
||||
import flattenStyle from './flattenStyle'
|
||||
import processTransform from './processTransform'
|
||||
import { predefinedClassNames } from './predefs'
|
||||
|
||||
let stylesCache = {}
|
||||
let uniqueID = 0
|
||||
|
||||
const getCacheKey = (prop, value) => `${prop}:${value}`
|
||||
|
||||
const normalizeStyle = (style) => {
|
||||
return processTransform(expandStyle(flattenStyle(style)))
|
||||
}
|
||||
|
||||
const createCssDeclarations = (style) => {
|
||||
return Object.keys(style).map((prop) => {
|
||||
const property = hyphenate(prop)
|
||||
const value = style[prop]
|
||||
return `${property}:${value};`
|
||||
}).sort().join('')
|
||||
}
|
||||
|
||||
class StyleSheetRegistry {
|
||||
static registerStyle(style: Object, store): number {
|
||||
/* for testing */
|
||||
static _reset() {
|
||||
stylesCache = {}
|
||||
uniqueID = 0
|
||||
}
|
||||
|
||||
static renderToString() {
|
||||
let str = `/* ${uniqueID} unique declarations */`
|
||||
|
||||
return Object.keys(stylesCache).reduce((str, key) => {
|
||||
const id = stylesCache[key].id
|
||||
const style = stylesCache[key].style
|
||||
const declarations = createCssDeclarations(style)
|
||||
const rule = `\n.${id}{${declarations}}`
|
||||
str += rule
|
||||
return str
|
||||
}, str)
|
||||
}
|
||||
|
||||
static registerStyle(style: Object): number {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Object.freeze(style)
|
||||
}
|
||||
|
||||
const normalizedStyle = processTransform(flattenStyle(style))
|
||||
const normalizedStyle = normalizeStyle(style)
|
||||
|
||||
Object.keys(normalizedStyle).forEach((prop) => {
|
||||
// add each declaration to the store
|
||||
store.set(prop, normalizedStyle[prop])
|
||||
const value = normalizedStyle[prop]
|
||||
const cacheKey = getCacheKey(prop, value)
|
||||
const exists = stylesCache[cacheKey] && stylesCache[cacheKey].id
|
||||
if (!exists) {
|
||||
const id = ++uniqueID
|
||||
// add new declaration to the store
|
||||
stylesCache[cacheKey] = {
|
||||
id: `__style${id}`,
|
||||
style: prefixAll({ [prop]: value })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
static getStyleAsNativeProps(style, store) {
|
||||
let _className
|
||||
let _style = {}
|
||||
static getStyleAsNativeProps(styleSheetObject, canUseCSS = false) {
|
||||
const classList = []
|
||||
const normalizedStyle = processTransform(flattenStyle(style))
|
||||
const normalizedStyle = normalizeStyle(styleSheetObject)
|
||||
let style = {}
|
||||
|
||||
for (const prop in normalizedStyle) {
|
||||
let styleClass = store.get(prop, normalizedStyle[prop])
|
||||
const value = normalizedStyle[prop]
|
||||
const cacheKey = getCacheKey(prop, value)
|
||||
let selector = stylesCache[cacheKey] && stylesCache[cacheKey].id || predefinedClassNames[cacheKey]
|
||||
|
||||
if (styleClass) {
|
||||
classList.push(styleClass)
|
||||
if (selector && canUseCSS) {
|
||||
classList.push(selector)
|
||||
} else {
|
||||
_style[prop] = normalizedStyle[prop]
|
||||
style[prop] = normalizedStyle[prop]
|
||||
}
|
||||
}
|
||||
|
||||
_className = classList.join(' ')
|
||||
_style = prefixAll(_style)
|
||||
|
||||
return { className: _className, style: _style }
|
||||
return {
|
||||
className: classList.join(' '),
|
||||
style: prefixAll(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,7 @@ StyleSheetValidation.addValidStylePropTypes({
|
||||
direction: PropTypes.string, /* @private */
|
||||
float: PropTypes.oneOf([ 'left', 'none', 'right' ]),
|
||||
font: PropTypes.string, /* @private */
|
||||
listStyle: PropTypes.string,
|
||||
verticalAlign: PropTypes.string
|
||||
listStyle: PropTypes.string
|
||||
})
|
||||
|
||||
module.exports = StyleSheetValidation
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import Store from '../Store'
|
||||
|
||||
suite('apis/StyleSheet/Store', () => {
|
||||
suite('the constructor', () => {
|
||||
test('initialState', () => {
|
||||
const initialState = { classNames: { 'textAlign:center': '__classname__' } }
|
||||
const store = new Store(initialState)
|
||||
assert.deepEqual(store._classNames['textAlign:center'], '__classname__')
|
||||
})
|
||||
})
|
||||
|
||||
suite('#get', () => {
|
||||
test('returns a declaration-specific className', () => {
|
||||
const initialState = {
|
||||
classNames: {
|
||||
'textAlign:center': '__expected__',
|
||||
'textAlign:left': '__error__'
|
||||
}
|
||||
}
|
||||
const store = new Store(initialState)
|
||||
assert.deepEqual(store.get('textAlign', 'center'), '__expected__')
|
||||
})
|
||||
})
|
||||
|
||||
suite('#set', () => {
|
||||
test('stores declarations', () => {
|
||||
const store = new Store()
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginTop', 0)
|
||||
store.set('marginTop', 1)
|
||||
store.set('marginTop', 2)
|
||||
assert.deepEqual(store._declarations, {
|
||||
textAlign: [ 'center' ],
|
||||
marginTop: [ 0, 1, 2 ]
|
||||
})
|
||||
})
|
||||
|
||||
test('human-readable classNames', () => {
|
||||
const store = new Store()
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginTop', 0)
|
||||
store.set('marginTop', 1)
|
||||
store.set('marginTop', 2)
|
||||
assert.deepEqual(store._classNames, {
|
||||
'textAlign:center': 'textAlign:center',
|
||||
'marginTop:0': 'marginTop:0',
|
||||
'marginTop:1': 'marginTop:1',
|
||||
'marginTop:2': 'marginTop:2'
|
||||
})
|
||||
})
|
||||
|
||||
test('obfuscated classNames', () => {
|
||||
const store = new Store({}, { obfuscateClassNames: true })
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginTop', 0)
|
||||
store.set('marginTop', 1)
|
||||
store.set('marginTop', 2)
|
||||
assert.deepEqual(store._classNames, {
|
||||
'textAlign:center': '_s_1',
|
||||
'marginTop:0': '_s_2',
|
||||
'marginTop:1': '_s_3',
|
||||
'marginTop:2': '_s_4'
|
||||
})
|
||||
})
|
||||
|
||||
test('replaces space characters', () => {
|
||||
const store = new Store()
|
||||
|
||||
store.set('backgroundPosition', 'top left')
|
||||
assert.equal(store.get('backgroundPosition', 'top left'), 'backgroundPosition\:top-left')
|
||||
})
|
||||
})
|
||||
|
||||
suite('#toString', () => {
|
||||
test('human-readable style sheet', () => {
|
||||
const store = new Store()
|
||||
store.set('textAlign', 'center')
|
||||
store.set('backgroundColor', 'rgba(0,0,0,0)')
|
||||
store.set('color', '#fff')
|
||||
store.set('fontFamily', '"Helvetica Neue", Arial, sans-serif')
|
||||
store.set('marginBottom', '0px')
|
||||
store.set('width', '100%')
|
||||
|
||||
const expected = '/* 6 unique declarations */\n' +
|
||||
'.backgroundColor\\:rgba\\(0\\,0\\,0\\,0\\){background-color:rgba(0,0,0,0);}\n' +
|
||||
'.color\\:\\#fff{color:#fff;}\n' +
|
||||
'.fontFamily\\:\\"Helvetica-Neue\\"\\,-Arial\\,-sans-serif{font-family:"Helvetica Neue", Arial, sans-serif;}\n' +
|
||||
'.marginBottom\\:0px{margin-bottom:0px;}\n' +
|
||||
'.textAlign\\:center{text-align:center;}\n' +
|
||||
'.width\\:100\\%{width:100%;}'
|
||||
|
||||
assert.equal(store.toString(), expected)
|
||||
})
|
||||
|
||||
test('obfuscated style sheet', () => {
|
||||
const store = new Store({}, { obfuscateClassNames: true })
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginBottom', '0px')
|
||||
store.set('margin', '1px')
|
||||
store.set('margin', '2px')
|
||||
store.set('margin', '3px')
|
||||
|
||||
const expected = '/* 5 unique declarations */\n' +
|
||||
'._s_3{margin:1px;}\n' +
|
||||
'._s_4{margin:2px;}\n' +
|
||||
'._s_5{margin:3px;}\n' +
|
||||
'._s_2{margin-bottom:0px;}\n' +
|
||||
'._s_1{text-align:center;}'
|
||||
|
||||
assert.equal(store.toString(), expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
55
src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js
Normal file
55
src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import StyleSheetRegistry from '../StyleSheetRegistry'
|
||||
|
||||
suite('apis/StyleSheet/StyleSheetRegistry', () => {
|
||||
setup(() => {
|
||||
StyleSheetRegistry._reset()
|
||||
})
|
||||
|
||||
test('static renderToString', () => {
|
||||
const style1 = { alignItems: 'center', opacity: 1 }
|
||||
const style2 = { alignItems: 'center', opacity: 1 }
|
||||
StyleSheetRegistry.registerStyle(style1)
|
||||
StyleSheetRegistry.registerStyle(style2)
|
||||
|
||||
const actual = StyleSheetRegistry.renderToString()
|
||||
const expected = `/* 2 unique declarations */
|
||||
.__style1{-ms-flex-align:center;-webkit-align-items:center;-webkit-box-align:center;align-items:center;}
|
||||
.__style2{opacity:1;}`
|
||||
|
||||
assert.equal(actual, expected)
|
||||
})
|
||||
|
||||
test('static getStyleAsNativeProps', () => {
|
||||
const style = { borderColorTop: 'white', opacity: 1 }
|
||||
const style1 = { opacity: 1 }
|
||||
StyleSheetRegistry.registerStyle(style1)
|
||||
|
||||
// canUseCSS = false
|
||||
const actual1 = StyleSheetRegistry.getStyleAsNativeProps(style)
|
||||
const expected1 = {
|
||||
className: '',
|
||||
style: { borderColorTop: 'white', opacity: 1 }
|
||||
}
|
||||
assert.deepEqual(actual1, expected1)
|
||||
|
||||
// canUseCSS = true
|
||||
const actual2 = StyleSheetRegistry.getStyleAsNativeProps(style, true)
|
||||
const expected2 = {
|
||||
className: '__style1',
|
||||
style: { borderColorTop: 'white' }
|
||||
}
|
||||
assert.deepEqual(actual2, expected2)
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { resetCSS, predefinedCSS } from '../predefs'
|
||||
import assert from 'assert'
|
||||
import StyleSheet from '..'
|
||||
|
||||
const styles = { root: { borderWidth: 1 } }
|
||||
const styles = { root: { opacity: 1 } }
|
||||
|
||||
suite('apis/StyleSheet', () => {
|
||||
setup(() => {
|
||||
@@ -12,55 +12,40 @@ suite('apis/StyleSheet', () => {
|
||||
})
|
||||
|
||||
suite('create', () => {
|
||||
const div = document.createElement('div')
|
||||
|
||||
setup(() => {
|
||||
document.body.appendChild(div)
|
||||
StyleSheet.create(styles)
|
||||
div.innerHTML = `<style id='${StyleSheet.elementId}'>${StyleSheet._renderToString()}</style>`
|
||||
})
|
||||
|
||||
teardown(() => {
|
||||
document.body.removeChild(div)
|
||||
})
|
||||
|
||||
test('returns styles object', () => {
|
||||
assert.equal(StyleSheet.create(styles), styles)
|
||||
})
|
||||
|
||||
test('updates already-rendered style sheet', () => {
|
||||
StyleSheet.create({ root: { color: 'red' } })
|
||||
// setup
|
||||
const div = document.createElement('div')
|
||||
document.body.appendChild(div)
|
||||
StyleSheet.create(styles)
|
||||
div.innerHTML = `<style id='${StyleSheet.elementId}'>${StyleSheet.renderToString()}</style>`
|
||||
|
||||
// test
|
||||
StyleSheet.create({ root: { color: 'red' } })
|
||||
assert.equal(
|
||||
document.getElementById(StyleSheet.elementId).textContent,
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 5 unique declarations */\n` +
|
||||
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
|
||||
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
|
||||
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
|
||||
`.borderTopWidth\\:1px{border-top-width:1px;}\n` +
|
||||
`.color\\:red{color:red;}`
|
||||
`/* 2 unique declarations */\n` +
|
||||
`.__style1{opacity:1;}\n` +
|
||||
`.__style2{color:red;}`
|
||||
)
|
||||
|
||||
// teardown
|
||||
document.body.removeChild(div)
|
||||
})
|
||||
})
|
||||
|
||||
test('resolve', () => {
|
||||
const props = { style: styles.root }
|
||||
const expected = { className: 'borderTopWidth:1px borderRightWidth:1px borderBottomWidth:1px borderLeftWidth:1px', style: {} }
|
||||
test('renderToString', () => {
|
||||
StyleSheet.create(styles)
|
||||
assert.deepEqual(StyleSheet.resolve(props), expected)
|
||||
})
|
||||
|
||||
test('_renderToString', () => {
|
||||
StyleSheet.create(styles)
|
||||
assert.equal(
|
||||
StyleSheet._renderToString(),
|
||||
StyleSheet.renderToString(),
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 4 unique declarations */\n` +
|
||||
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
|
||||
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
|
||||
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
|
||||
`.borderTopWidth\\:1px{border-top-width:1px;}`
|
||||
`/* 1 unique declarations */\n` +
|
||||
`.__style1{opacity:1;}`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
31
src/apis/StyleSheet/__tests__/processTransform-test.js
Normal file
31
src/apis/StyleSheet/__tests__/processTransform-test.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import processTransform from '../processTransform'
|
||||
|
||||
suite('apis/StyleSheet/processTransform', () => {
|
||||
test('transform', () => {
|
||||
const style = {
|
||||
transform: [
|
||||
{ scaleX: 20 },
|
||||
{ rotate: '20deg' }
|
||||
]
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
processTransform(style),
|
||||
{ transform: 'scaleX(20) rotate(20deg)' }
|
||||
)
|
||||
})
|
||||
|
||||
test('transformMatrix', () => {
|
||||
const style = {
|
||||
transformMatrix: [ 1, 2, 3, 4, 5, 6 ]
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
processTransform(style),
|
||||
{ transform: 'matrix3d(1,2,3,4,5,6)' }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -8,16 +8,19 @@ const styleShortHands = {
|
||||
margin: [ 'marginTop', 'marginRight', 'marginBottom', 'marginLeft' ],
|
||||
marginHorizontal: [ 'marginRight', 'marginLeft' ],
|
||||
marginVertical: [ 'marginTop', 'marginBottom' ],
|
||||
overflow: [ 'overflowX', 'overflowY' ],
|
||||
padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ],
|
||||
paddingHorizontal: [ 'paddingRight', 'paddingLeft' ],
|
||||
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
|
||||
textDecorationLine: [ 'textDecoration' ],
|
||||
writingDirection: [ 'direction' ]
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpha-sort properties, apart from shorthands which appear before the
|
||||
* properties they expand into. This ensures that more specific styles override
|
||||
* the shorthands, whatever the order in which they were originally declared.
|
||||
* Alpha-sort properties, apart from shorthands – they must appear before the
|
||||
* longhand properties that they expand into. This lets more specific styles
|
||||
* override less specific styles, whatever the order in which they were
|
||||
* originally declared.
|
||||
*/
|
||||
const sortProps = (propsArray) => propsArray.sort((a, b) => {
|
||||
const expandedA = styleShortHands[a]
|
||||
@@ -41,14 +44,17 @@ const expandStyle = (style) => {
|
||||
const expandedProps = styleShortHands[key]
|
||||
const value = normalizeValue(key, style[key])
|
||||
|
||||
if (expandedProps) {
|
||||
expandedProps.forEach((prop, i) => {
|
||||
resolvedStyle[expandedProps[i]] = value
|
||||
})
|
||||
} else if (key === 'flex') {
|
||||
// React Native treats `flex:1` like `flex:1 1 auto`
|
||||
if (key === 'flex') {
|
||||
resolvedStyle.flexGrow = value
|
||||
resolvedStyle.flexShrink = 1
|
||||
resolvedStyle.flexBasis = 'auto'
|
||||
} else if (key === 'textAlignVertical') {
|
||||
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value)
|
||||
} else if (expandedProps) {
|
||||
expandedProps.forEach((prop, i) => {
|
||||
resolvedStyle[expandedProps[i]] = value
|
||||
})
|
||||
} else {
|
||||
resolvedStyle[key] = value
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* @flow
|
||||
*/
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import expandStyle from './expandStyle'
|
||||
|
||||
module.exports = function flattenStyle(style): ?Object {
|
||||
if (!style) {
|
||||
@@ -16,9 +15,7 @@ module.exports = function flattenStyle(style): ?Object {
|
||||
invariant(style !== true, 'style may be false but not true')
|
||||
|
||||
if (!Array.isArray(style)) {
|
||||
// we must expand styles during the flattening because expanded styles
|
||||
// override shorthands
|
||||
return expandStyle(style)
|
||||
return style
|
||||
}
|
||||
|
||||
const result = {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
|
||||
import { resetCSS, predefinedCSS } from './predefs'
|
||||
import flattenStyle from './flattenStyle'
|
||||
import Store from './Store'
|
||||
import StyleSheetRegistry from './StyleSheetRegistry'
|
||||
import StyleSheetValidation from './StyleSheetValidation'
|
||||
|
||||
@@ -12,64 +11,61 @@ let lastStyleSheet = ''
|
||||
* Initialize the store with pointer-event styles mapping to our custom pointer
|
||||
* event classes
|
||||
*/
|
||||
const initialState = { classNames: predefinedClassNames }
|
||||
const options = { obfuscateClassNames: !(process.env.NODE_ENV !== 'production') }
|
||||
const createStore = () => new Store(initialState, options)
|
||||
let store = createStore()
|
||||
|
||||
/**
|
||||
* Destroy existing styles
|
||||
*/
|
||||
const _destroy = () => {
|
||||
store = createStore()
|
||||
isRendered = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the styles as a CSS style sheet
|
||||
*/
|
||||
const _renderToString = () => {
|
||||
const css = store.toString()
|
||||
isRendered = true
|
||||
return `${resetCSS}\n${predefinedCSS}\n${css}`
|
||||
StyleSheetRegistry._reset()
|
||||
}
|
||||
|
||||
const create = (styles: Object): Object => {
|
||||
for (const key in styles) {
|
||||
StyleSheetValidation.validateStyle(key, styles)
|
||||
StyleSheetRegistry.registerStyle(styles[key], store)
|
||||
StyleSheetRegistry.registerStyle(styles[key])
|
||||
}
|
||||
|
||||
// update the style sheet in place
|
||||
if (isRendered) {
|
||||
const stylesheet = document.getElementById(ELEMENT_ID)
|
||||
if (stylesheet) {
|
||||
const newStyleSheet = _renderToString()
|
||||
const newStyleSheet = renderToString()
|
||||
if (lastStyleSheet !== newStyleSheet) {
|
||||
stylesheet.textContent = newStyleSheet
|
||||
lastStyleSheet = newStyleSheet
|
||||
}
|
||||
} else if (process.env.NODE_ENV !== 'production') {
|
||||
console.error('ReactNative: cannot find "react-stylesheet" element')
|
||||
console.error(`ReactNative: cannot find "${ELEMENT_ID}" element`)
|
||||
}
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the styles as a CSS style sheet
|
||||
*/
|
||||
const renderToString = () => {
|
||||
const css = StyleSheetRegistry.renderToString()
|
||||
isRendered = true
|
||||
return `${resetCSS}\n${predefinedCSS}\n${css}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts React props and converts inline styles to single purpose classes
|
||||
* where possible.
|
||||
*/
|
||||
const resolve = ({ style = {} }) => {
|
||||
return StyleSheetRegistry.getStyleAsNativeProps(style, store)
|
||||
return StyleSheetRegistry.getStyleAsNativeProps(style, isRendered)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_destroy,
|
||||
_renderToString,
|
||||
create,
|
||||
elementId: ELEMENT_ID,
|
||||
hairlineWidth: 1,
|
||||
flatten: flattenStyle,
|
||||
renderToString,
|
||||
resolve
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
* Reset unwanted styles beyond the control of React inline styles
|
||||
*/
|
||||
export const resetCSS =
|
||||
`/* React Native Web */
|
||||
`/* React Native for Web */
|
||||
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
|
||||
body {margin:0}
|
||||
button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}
|
||||
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}`
|
||||
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}`
|
||||
|
||||
/**
|
||||
* Custom pointer event styles
|
||||
*/
|
||||
export const predefinedCSS =
|
||||
`/* pointer-events */
|
||||
._s_pe-a, ._s_pe-bo, ._s_pe-bn * {pointer-events:auto}
|
||||
._s_pe-n, ._s_pe-bo *, ._s_pe-bn {pointer-events:none}`
|
||||
.__style_pea, .__style_pebo, .__style_pebn * {pointer-events:auto}
|
||||
.__style_pen, .__style_pebo *, .__style_pebn {pointer-events:none}`
|
||||
|
||||
export const predefinedClassNames = {
|
||||
'pointerEvents:auto': '_s_pe-a',
|
||||
'pointerEvents:box-none': '_s_pe-bn',
|
||||
'pointerEvents:box-only': '_s_pe-bo',
|
||||
'pointerEvents:none': '_s_pe-n'
|
||||
'pointerEvents:auto': '__style_pea',
|
||||
'pointerEvents:box-none': '__style_pebn',
|
||||
'pointerEvents:box-only': '__style_pebo',
|
||||
'pointerEvents:none': '__style_pen'
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ const processTransform = (style) => {
|
||||
if (style.transform) {
|
||||
style.transform = style.transform.map(mapTransform).join(' ')
|
||||
} else if (style.transformMatrix) {
|
||||
style.transformMatrix = convertTransformMatrix(style.transformMatrix)
|
||||
style.transform = convertTransformMatrix(style.transformMatrix)
|
||||
delete style.transformMatrix
|
||||
}
|
||||
}
|
||||
return style
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import Platform from '../../apis/Platform'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
@@ -81,10 +80,6 @@ class Portal extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
invariant(
|
||||
_portalRef === this || _portalRef === undefined,
|
||||
'More than one Portal instance detected. Never use <Portal> in your code.'
|
||||
)
|
||||
_portalRef = this
|
||||
if (!this.state.modals) { return null }
|
||||
const modals = []
|
||||
|
||||
@@ -15,14 +15,17 @@ module.exports = {
|
||||
letterSpacing: numberOrString,
|
||||
lineHeight: numberOrString,
|
||||
textAlign: oneOf([ 'center', 'inherit', 'justify', 'justify-all', 'left', 'right' ]),
|
||||
/**
|
||||
* @platform web
|
||||
*/
|
||||
textDecoration: string,
|
||||
textAlignVertical: oneOf([ 'auto', 'bottom', 'center', 'top' ]),
|
||||
textDecorationLine: string,
|
||||
/* @platform web */
|
||||
textOverflow: string,
|
||||
/* @platform web */
|
||||
textShadow: string,
|
||||
/* @platform web */
|
||||
textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]),
|
||||
/* @platform web */
|
||||
whiteSpace: string,
|
||||
/* @platform web */
|
||||
wordWrap: string,
|
||||
writingDirection: string
|
||||
writingDirection: oneOf([ 'auto', 'ltr', 'rtl' ])
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ const styles = StyleSheet.create({
|
||||
font: 'inherit',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
textDecoration: 'none',
|
||||
textDecorationLine: 'none',
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
singleLineStyle: {
|
||||
|
||||
@@ -32,7 +32,7 @@ type Event = Object;
|
||||
|
||||
var DEFAULT_PROPS = {
|
||||
activeOpacity: 0.8,
|
||||
underlayColor: 'black',
|
||||
underlayColor: 'black'
|
||||
};
|
||||
|
||||
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
@@ -105,9 +105,11 @@ var TouchableHighlight = React.createClass({
|
||||
backgroundColor: props.underlayColor,
|
||||
}
|
||||
},
|
||||
underlayStyle: [
|
||||
INACTIVE_UNDERLAY_PROPS.style
|
||||
]
|
||||
underlayProps: {
|
||||
style: {
|
||||
backgroundColor: props.style.backgroundColor || null
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -203,10 +205,7 @@ var TouchableHighlight = React.createClass({
|
||||
this._hideTimeout = null;
|
||||
if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) {
|
||||
this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS);
|
||||
this.refs[UNDERLAY_REF].setNativeProps({
|
||||
...INACTIVE_UNDERLAY_PROPS,
|
||||
style: this.state.underlayStyle,
|
||||
});
|
||||
this.refs[UNDERLAY_REF].setNativeProps(this.state.underlayProps);
|
||||
this.props.onHideUnderlay && this.props.onHideUnderlay();
|
||||
}
|
||||
},
|
||||
@@ -233,19 +232,19 @@ var TouchableHighlight = React.createClass({
|
||||
accessible={true}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole || this.props.accessibilityTraits || 'button'}
|
||||
ref={UNDERLAY_REF}
|
||||
style={[styles.root, this.state.underlayStyle, this.props.style]}
|
||||
onLayout={this.props.onLayout}
|
||||
hitSlop={this.props.hitSlop}
|
||||
onKeyDown={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
|
||||
onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
|
||||
onKeyUp={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressOut) }}
|
||||
onLayout={this.props.onLayout}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
ref={UNDERLAY_REF}
|
||||
style={[styles.root, this.props.style]}
|
||||
tabIndex='0'
|
||||
testID={this.props.testID}>
|
||||
{React.cloneElement(
|
||||
@@ -264,9 +263,6 @@ var UNDERLAY_REF = keyOf({underlayRef: null});
|
||||
var INACTIVE_CHILD_PROPS = {
|
||||
style: StyleSheet.create({x: {opacity: 1.0}}).x,
|
||||
};
|
||||
var INACTIVE_UNDERLAY_PROPS = {
|
||||
style: {backgroundColor: null}
|
||||
};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
root: {
|
||||
|
||||
@@ -145,7 +145,7 @@ var TouchableWithoutFeedback = React.createClass({
|
||||
|
||||
render: function(): ReactElement {
|
||||
// Note(avik): remove dynamic typecast once Flow has been upgraded
|
||||
return (React: any).cloneElement(React.children.only(this.props.children), {
|
||||
return (React: any).cloneElement(React.Children.only(this.props.children), {
|
||||
accessible: this.props.accessible !== false,
|
||||
accessibilityLabel: this.props.accessibilityLabel,
|
||||
accessibilityRole: this.props.accessibilityRole,
|
||||
|
||||
@@ -40,7 +40,8 @@ class View extends Component {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
accessible: true
|
||||
accessible: true,
|
||||
style: {}
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
@@ -55,6 +56,7 @@ class View extends Component {
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
const flattenedStyle = StyleSheet.flatten(style)
|
||||
const pointerEventsStyle = pointerEvents && { pointerEvents }
|
||||
|
||||
return (
|
||||
@@ -73,6 +75,8 @@ class View extends Component {
|
||||
style={[
|
||||
styles.initial,
|
||||
style,
|
||||
// 'View' needs to use 'flexShrink' in its reset when there is no 'flex' style provided
|
||||
flattenedStyle.flex == null && styles.flexReset,
|
||||
pointerEventsStyle
|
||||
]}
|
||||
/>
|
||||
@@ -104,7 +108,6 @@ const styles = StyleSheet.create({
|
||||
display: 'flex',
|
||||
flexBasis: 'auto',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
@@ -113,13 +116,16 @@ const styles = StyleSheet.create({
|
||||
color: 'inherit',
|
||||
font: 'inherit',
|
||||
textAlign: 'inherit',
|
||||
textDecoration: 'none',
|
||||
textDecorationLine: 'none',
|
||||
// list reset
|
||||
listStyle: 'none',
|
||||
// fix flexbox bugs
|
||||
maxWidth: '100%',
|
||||
minHeight: 0,
|
||||
minWidth: 0
|
||||
},
|
||||
flexReset: {
|
||||
flexShrink: 0
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
var constants = require('./constants')
|
||||
var webpack = require('webpack')
|
||||
|
||||
const DIST_DIRECTORY = './dist'
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
main: constants.DIST_DIRECTORY
|
||||
main: DIST_DIRECTORY
|
||||
},
|
||||
externals: [{
|
||||
'react': true,
|
||||
'react-dom': true,
|
||||
'react-dom/server': true
|
||||
}],
|
||||
output: {
|
||||
filename: 'react-native-web.js',
|
||||
library: 'ReactNativeWeb',
|
||||
filename: 'ReactNative.js',
|
||||
library: 'React',
|
||||
libraryTarget: 'umd',
|
||||
path: constants.DIST_DIRECTORY
|
||||
path: DIST_DIRECTORY
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
|
||||
Reference in New Issue
Block a user