Compare commits

...

26 Commits

Author SHA1 Message Date
Nicolas Gallagher
71cfd23624 0.0.25 2016-04-29 12:49:13 -07:00
Nicolas Gallagher
77b8e4a1fc [fix] pin inline-style-prefix-all
Version 1.1.0 contains a breaking change
2016-04-29 12:48:40 -07:00
Nicolas Gallagher
9543a79c3f 0.0.24 2016-04-20 11:38:38 -07:00
Nicolas Gallagher
e3eea6e132 [fix] TouchableHighlight
The fix in 97c0a31ce6 was incomplete due
to state key not being renamed.
2016-04-20 11:37:15 -07:00
Nicolas Gallagher
4d3418a968 0.0.23 2016-04-19 17:11:26 -07:00
Nicolas Gallagher
ea9bc734f1 [fix] TouchableWithoutFeedback
Fix #127
2016-04-19 17:10:50 -07:00
Nicolas Gallagher
e03af435ac 0.0.22 2016-04-18 16:55:57 -07:00
Nicolas Gallagher
97c0a31ce6 [fix] TouchableHighlight default underlayColor 2016-04-18 16:46:09 -07:00
Nicolas Gallagher
25d11ded46 [fix] NetInfo event handlers 2016-04-18 16:38:09 -07:00
Nicolas Gallagher
6a73d77030 Fix build 2016-03-24 17:01:38 -07:00
Nicolas Gallagher
0b63ba4e89 0.0.21 2016-03-24 11:50:01 -07:00
Nicolas Gallagher
51109d0768 [fix] update inline-style-prefix-all
inline-style-prefix-all@1.0.4 doesn't depend on `Set` anymore
2016-03-24 11:49:22 -07:00
Nicolas Gallagher
ac04ecd69e Update Dimensions when window resizes 2016-03-24 11:44:02 -07:00
Nicolas Gallagher
1a670ba6a7 Fix UMD bundle
Include React and ReactDOM in the UMD bundle. The library occupies the
`React` global.

Fix #105
2016-03-22 18:39:24 -07:00
Nicolas Gallagher
7a16d5711c 0.0.20 2016-03-20 12:19:40 -07:00
Nicolas Gallagher
9dde70fff5 Update documentation 2016-03-20 12:19:29 -07:00
Nicolas Gallagher
203980ab66 [fix] fbjs version compatible with React Native
React Native 0.21 currently uses fbjs@0.6.x, and React Native 0.22 will
use fbjs@0.7.x.

Fix #103
2016-03-20 12:11:31 -07:00
Nicolas Gallagher
924dc36d4a [fix] refactor StyleSheet
**Problem**

StyleSheet's implementation was overly complex. It required
`flattenStyle` to use `expandStyle`, and couldn't support mapping React
Native style props to CSS properties without also exposing those CSS
properties in the API.

**Response**

- `flattenStyle` is concerned only with flattening style objects.

- `StyleSheetRegistry` is responsible for registering styles, mapping
  the React Native style prop to DOM props, and generating the CSS for
  the backing style element.

- `StyleSheetRegistry` uses a simpler approach to caching styles and
  generating style sheet strings. It also drops the unobfuscated class
  names from development mode, as the React Dev Tools can provide a
  better debugging experience (pending a fix to allow props/styles to be
  changed from the dev tools).

- `StyleSheet` will fall back to inline styles if it doesn't think a
  style sheet has been rendered into the document. The relationship is
  currently only implicit. This should be revisited.

- `StyleSheet` exports `renderToString` as part of the documented API.

- Fix processing of `transformMatrix` and add tests for
  `processTransform`.

- Fix `input[type=search]` rendering in Safari by using `display:none`
  on its pseudo-elements.

- Add support for `textDecorationLine` and `textAlignVertical`.

- Note the `View` hack to conditionally apply the `flex-shrink:0` reset
  from css-layout. This is required because React Native's approach to
  resolving `style` is to give precendence to long-hand styles
  (e.g., `flexShrink`) over short-hand styles (e.g., `flex`). This means
  the `View` reset overrides any `flex:1` declaration. To get around
  this, `flexShrink` is only set in `View` if `flex` is not set.
2016-03-20 12:09:04 -07:00
Nicolas Gallagher
9b2421cdfa [fix] Server-side rendering
`AppRegistry.prerenderApplication` now returns a style element for use
in app shells.

Guard use of `window` in APIs and Event plugin.

Fix #107
Fix #108
2016-03-20 11:43:13 -07:00
Nicolas Gallagher
36ea662402 0.0.19 2016-03-16 10:19:17 -07:00
Nicolas Gallagher
69962ae815 [fix] StyleSheet: add hairlineWidth
Fix #99
2016-03-16 10:03:37 -07:00
Nicolas Gallagher
62d1a0f83d Add Game2048 example 2016-03-16 00:55:04 -07:00
Nicolas Gallagher
910286303a Add TicTacToe example 2016-03-16 00:48:41 -07:00
Nicolas Gallagher
706fa887e6 [fix] remove invariant error from Portal 2016-03-16 00:48:41 -07:00
Nicolas Gallagher
c589d79035 Reorganize karma and webpack configs 2016-03-16 00:48:37 -07:00
Nicolas Gallagher
83e4c68461 [fix] TouchableHighlight inactive state
TouchableHighlight didn't preserve its original 'backgroundColor' after
pressOut. This was caused by the inactive background style (transparent)
being applied as an inline style, and so not merging with the original
prop style. The patch sets inactive 'backgroundColor' to 'null' so as to
remove the inline style and render the backgroundColor from props.

Fix #98
2016-03-16 00:36:06 -07:00
39 changed files with 1213 additions and 437 deletions

View File

@@ -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')
}

View File

@@ -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>)

View File

@@ -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

View File

@@ -60,7 +60,8 @@ This function is called on press.
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textAlignVertical`
+ `textDecorationLine`
+ `textShadow`
+ `textTransform`
+ `whiteSpace`

View File

@@ -70,7 +70,6 @@ module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
'Platform.OS': 'web',
'process.env.NODE_ENV': JSON.stringify('production')
})
}

View File

@@ -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} />)
```

View File

@@ -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
View 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
View 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;

View 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;

View File

@@ -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>

View File

@@ -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')
})

View File

@@ -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({

View File

@@ -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,

View File

@@ -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"

View File

@@ -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)
})
})

View File

@@ -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 }
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 ]

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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)
})
})
})

View 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)
})
})

View File

@@ -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;}`
)
})
})

View 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)' }
)
})
})

View File

@@ -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
}

View File

@@ -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 = {}

View File

@@ -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
}

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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 = []

View File

@@ -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' ])
}

View File

@@ -56,7 +56,7 @@ const styles = StyleSheet.create({
font: 'inherit',
margin: 0,
padding: 0,
textDecoration: 'none',
textDecorationLine: 'none',
wordWrap: 'break-word'
},
singleLineStyle: {

View File

@@ -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: {

View File

@@ -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,

View File

@@ -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
}
})

View File

@@ -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') }),