react-shadow
Advanced tools
Comparing version 1.0.0-beta to 1.0.0
import React, { Component, PropTypes } from 'react'; | ||
import { render } from 'react-dom'; | ||
import ready from 'document-ready-promise'; | ||
import nlp from 'nlp_compromise'; | ||
import capitalise from 'capitalize'; | ||
import memoize from 'ramda/src/memoize'; | ||
import ShadowDOM from '../../src/react-shadow'; | ||
/** | ||
* @class ReactShadow | ||
* @constant API_KEY | ||
* @type {String} | ||
*/ | ||
const API_KEY = '07b72c930f0d226f7c6866cc753a678c'; | ||
/** | ||
* @method fetchWeather | ||
* @param {String} url | ||
* @return {Promise} | ||
*/ | ||
const fetchWeather = memoize(url => fetch(url).then(response => response.json())); | ||
/** | ||
* @class Weather | ||
* @author Adam Timberlake | ||
* @link https://github.com/Wildhoney/ReactShadow | ||
*/ | ||
export class Counter extends Component { | ||
export class Weather extends Component { | ||
/** | ||
* @constant propTypes | ||
* @constant defaultProps | ||
* @type {Object} | ||
*/ | ||
static propTypes = { | ||
name: PropTypes.string.isRequired | ||
}; | ||
static defaultProps = { countries: ['Amsterdam', 'Cairo', 'London', 'Moscow', 'Paris', 'Rio de Janeiro', 'Rome', 'Sydney'] }; | ||
@@ -27,30 +41,31 @@ /** | ||
super(); | ||
this.state = { refreshed: 0, value: '' }; | ||
const countries = Weather.defaultProps.countries; | ||
} | ||
/** | ||
* @method componentDidMount | ||
* @method componentWillMount | ||
* @return {void} | ||
*/ | ||
componentDidMount() { | ||
this.startInterval(); | ||
componentWillMount() { | ||
const countries = this.props.countries; | ||
const country = countries[Math.floor(Math.random() * countries.length)]; | ||
this.state = { country, weather: null }; | ||
this.weatherFor(country); | ||
} | ||
/** | ||
* @method startInterval | ||
* @method weatherFor | ||
* @param {String} country | ||
* @return {void} | ||
*/ | ||
startInterval() { | ||
const interval = setInterval(() => this.setState({ refreshed: this.state.refreshed + 1 }), 1000); | ||
this.setState({ interval: interval }); | ||
} | ||
weatherFor(country) { | ||
/** | ||
* @method resetCounter | ||
* @return {void} | ||
*/ | ||
resetCounter() { | ||
clearInterval(this.state.interval); | ||
this.setState({ refreshed: 0, value: this.state.value }); | ||
this.startInterval(); | ||
this.setState({ country, weather: null }); | ||
const url = `http://api.openweathermap.org/data/2.5/weather?q=${country}&appid=${API_KEY}&units=metric`; | ||
fetchWeather(url).then(weather => this.setState({ weather })); | ||
} | ||
@@ -64,9 +79,44 @@ | ||
const { weather, country } = this.state; | ||
const description = () => capitalise.words(nlp.noun(weather.weather[0].description).pluralize()); | ||
const title = weather ? `${description()} in ${country}` : `Weather in ${country}`; | ||
const label = weather ? `${weather.main.temp}${String.fromCharCode(8451)}` : String.fromCharCode(8212); | ||
return ( | ||
<ShadowDOM cssDocuments={['css/core.css', `css/component/${this.props.name}.css`]}> | ||
<section onClick={this.resetCounter.bind(this)} title="Reset Counter"> | ||
<h1 className="title"> | ||
{this.state.refreshed} | ||
</h1> | ||
</section> | ||
<ShadowDOM cssDocuments="css/country.css"> | ||
<div> | ||
<img src={`/images/${country.replace(/ /ig, '-').toLowerCase()}.png`} alt={country} /> | ||
<h1>{title}</h1> | ||
<h2 className={weather ? 'loading' : ''}> | ||
{label} | ||
</h2> | ||
<ul> | ||
<li className="title">Weather for:</li> | ||
{this.props.countries.map(name => { | ||
return ( | ||
<li key={name}> | ||
<a | ||
onClick={() => this.weatherFor(name)} | ||
className={name === country ? 'active' : ''} | ||
> | ||
{name} | ||
</a> | ||
</li> | ||
); | ||
})} | ||
</ul> | ||
</div> | ||
</ShadowDOM> | ||
@@ -79,9 +129,2 @@ ); | ||
ready().then(() => { | ||
['first', 'second', 'third'].forEach(name => { | ||
const node = document.querySelector(`*[data-react-shadow="${name}"]`) || document.body; | ||
render(<Counter name={name} />, node); | ||
}); | ||
}); | ||
ready().then(() => render(<Weather />, document.querySelector('section.container'))); |
{ | ||
"name": "react-shadow", | ||
"version": "1.0.0-beta", | ||
"version": "1.0.0", | ||
"description": "Use Shadow DOM with React.js and CSS imports; write your component styles in CSS!", | ||
@@ -37,2 +37,3 @@ "main": "dist/react-shadow.js", | ||
"babel-preset-stage-0": "^6.5.0", | ||
"capitalize": "^1.0.0", | ||
"document-ready-promise": "^3.0.1", | ||
@@ -45,2 +46,3 @@ "eslint-config-xo-react": "^0.9.0", | ||
"jsdom": "^9.5.0", | ||
"nlp_compromise": "^6.5.0", | ||
"ramda": "^0.22.1", | ||
@@ -87,5 +89,13 @@ "react": "^15.3.1", | ||
"react/jsx-no-bind": "off", | ||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] | ||
"react/jsx-filename-extension": [ | ||
1, | ||
{ | ||
"extensions": [ | ||
".js", | ||
".jsx" | ||
] | ||
} | ||
] | ||
} | ||
} | ||
} |
@@ -1,74 +0,35 @@ | ||
# ReactShadow | ||
![Screenshot](media/screenshot.png) | ||
![Travis](http://img.shields.io/travis/Wildhoney/ReactShadow.svg?style=flat) | ||
| ||
![License MIT](http://img.shields.io/badge/license-mit-orange.svg?style=flat) | ||
| ||
![Experimental](http://img.shields.io/badge/experimental-%E2%9C%93-blue.svg?style=flat) | ||
| ||
![License MIT](http://img.shields.io/badge/license-mit-orange.svg?style=flat) | ||
* **npm**: `npm i react-shadow --save` | ||
* **Heroku**: [http://react-shadow.herokuapp.com/](http://react-shadow.herokuapp.com/) | ||
![Screenshot](http://i.imgur.com/1txgnOL.png) | ||
--- | ||
With `ReactShadow` you can apply a [Shadow DOM](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/) root inside of your component. Under normal React.js conditions, your styles are written inline for style encapsulation – with `ReactShadow` your styles can now be moved into their rightful place – within CSS documents! | ||
## Getting Started | ||
**Note:** Take a [look at the Maple.js framework](https://github.com/Wildhoney/Maple.js), which is a React.js framework with Shadow DOM, HTML Imports, and Custom Elements. It has a lot of functionality that I was unable to integrate into a simple React.js mixin. | ||
By using `ReactShadow` you have all the benefits of [Shadow DOM](https://www.w3.org/TR/shadow-dom/) in React. | ||
# Getting Started | ||
`ReactShadow` is implemented as a mixin that you can import into your component: | ||
```javascript | ||
var ReadmeApp = $react.createClass({ | ||
mixins: [ReactShadow] | ||
}); | ||
``` | ||
import ShadowDOM from 'react-shadow'; | ||
From there `ReactShadow` will take over – creating a shadow root inside of your component, and importing any CSS documents defined in your `cssDocuments` property – which can be either an `array` or a `function`: | ||
```javascript | ||
var ReadmeApp = $react.createClass({ | ||
mixins: [ReactShadow], | ||
cssDocuments: ['../css/Default.css'] | ||
}); | ||
export default props => { | ||
<ShadowDOM cssDocuments={['css/core/calendar.css', props.theme]}> | ||
<h1>Calendar</h1> | ||
</ShadowDOM> | ||
} | ||
``` | ||
If you're applying CSS documents at runtime then it may well be useful to have the `cssDocuments` property as a `function`: | ||
In the above example the `h1` element will become the host element with a shadow boundary — and the two defined CSS documents will be fetched and appended. | ||
```javascript | ||
var ReadmeApp = $react.createClass({ | ||
mixins: [ReactShadow], | ||
cssDocuments: function cssDocuments() { | ||
return ['../css/Component.css', '../css/' + this.props.cssDocument]; | ||
} | ||
}); | ||
``` | ||
### Avoiding FOIC | ||
You can inline css with `cssSource` property. | ||
As the CSS documents are being fetched over the network the host element will have a `className` of `resolving` for you to avoid the dreaded [FOIC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content). Once **all** of the documents have been attached the `className` will change to `resolved`. | ||
```javascript | ||
var ReadmeApp = $react.createClass({ | ||
mixins: [ReactShadow], | ||
cssSource: "body { color: black; }" | ||
}); | ||
``` | ||
### Cached Documents | ||
When `cssDocuments` and `cssSource` are both defined, style defined in `cssSource` is appended after `cssDocuments`. | ||
# Event Retargeting | ||
As Shadow DOM has the concept of [Event Retargeting](http://www.w3.org/TR/shadow-dom/#event-retargeting) for encapsulation purposes, event delegation will not function correctly because all events will appear to be coming from the Shadow DOM – therefore `ReactShadow` uses the React ID for each element to dispatch the event from the original element, therefore maintaining React's event delegation implementation. | ||
Events are therefore written in exactly the same way: | ||
```javascript | ||
var ReadmeApp = $react.createClass({ | ||
render: function render() { | ||
return <a onClick={this.reset} title="Reset Counter"> | ||
Reset, Comrade! | ||
</a> | ||
} | ||
}); | ||
``` | ||
Where components share CSS documents, only one instance of the CSS document will be fetched due to [`memoize` of the `fetchStylesheets`](https://github.com/Wildhoney/ReactShadow/blob/react-15.0/src/react-shadow.js#L22) function. |
import { get as fetch } from 'axios'; | ||
import React, { Component, PropTypes, DOM } from 'react'; | ||
import React, { Component, PropTypes, DOM, Children } from 'react'; | ||
import { render, findDOMNode } from 'react-dom'; | ||
import dissoc from 'ramda/src/dissoc'; | ||
import memoize from 'ramda/src/memoize'; | ||
/** | ||
* @method raise | ||
* @param {String} message | ||
* @throws {Error} | ||
* @return {void} | ||
*/ | ||
const raise = message => { | ||
throw new Error(`ReactShadow: ${message}.`); | ||
}; | ||
/** | ||
* @method fetchStylesheet | ||
* @param {String} document | ||
* @return {Promise} | ||
*/ | ||
const fetchStylesheet = memoize(document => fetch(document).then(response => response.data)); | ||
/** | ||
* @class ShadowDOM | ||
@@ -95,9 +113,2 @@ * @extends Component | ||
/** | ||
* @method fetchStylesheet | ||
* @param {String} document | ||
* @return {Promise} | ||
*/ | ||
const fetchStylesheet = document => fetch(document).then(response => response.data); | ||
/** | ||
* @method insertStyleElement | ||
@@ -125,2 +136,20 @@ * @param {Array} cssDocuments | ||
/** | ||
* @method performSanityCheck | ||
* @return {void} | ||
*/ | ||
performSanityCheck() { | ||
// Ensure that the passed child isn't an array of children. | ||
Array.isArray(this.props.children) && raise('You must pass a single child rather than multiple children'); | ||
if (typeof this.props.children.type !== 'string') { | ||
// Ensure that the passed child has a valid node name. | ||
raise('Passed child must be a concrete HTML element rather than another React component'); | ||
} | ||
} | ||
/** | ||
* @method render | ||
@@ -131,5 +160,9 @@ * @return {XML} | ||
// Process the necessary sanity checks. | ||
this.performSanityCheck(); | ||
// Take all of the props from the passed in component, minus the `children` props | ||
// as that's handled by `componentDidMount`. | ||
const props = dissoc('children', this.props.children.props); | ||
const child = Children.only(this.props.children); | ||
const props = dissoc('children', child.props); | ||
const className = this.state.resolving ? 'resolving' : 'resolved'; | ||
@@ -140,6 +173,6 @@ | ||
const classNames = `${props.className ? props.className : ''} ${className}`.trim(); | ||
const isSupportedElement = this.props.children.type in DOM; | ||
const isSupportedElement = child.type in DOM; | ||
const propName = isSupportedElement ? 'className' : 'class'; | ||
return <this.props.children.type {...{ ...dissoc('className', props), [propName]: classNames }} />; | ||
return <child.type {...{ ...dissoc('className', props), [propName]: classNames }} />; | ||
@@ -146,0 +179,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
687817
25
1045
0
21
36