react-shadow
Advanced tools
Comparing version 1.0.5 to 1.1.0
@@ -83,3 +83,3 @@ import React, { Component, PropTypes } from 'react'; | ||
return ( | ||
<ShadowDOM cssDocuments="css/country.css"> | ||
<ShadowDOM include={['css/country.css', 'css/default.css']}> | ||
@@ -86,0 +86,0 @@ <div className="weather"> |
{ | ||
"name": "react-shadow", | ||
"version": "1.0.5", | ||
"version": "1.1.0", | ||
"description": "Utilise Shadow DOM in React with all the benefits of style encapsulation.", | ||
@@ -5,0 +5,0 @@ "main": "dist/react-shadow.js", |
@@ -1,2 +0,2 @@ | ||
![Screenshot](media/screenshot.png) | ||
<img src="media/logo.png" width="300" alt="ReactShadow" /> | ||
@@ -11,2 +11,4 @@ ![Travis](http://img.shields.io/travis/Wildhoney/ReactShadow.svg?style=flat) | ||
![Screenshot](media/screenshot.png) | ||
--- | ||
@@ -22,7 +24,9 @@ | ||
export default props => { | ||
return ( | ||
<ShadowDOM cssDocuments={['css/core/calendar.css', props.theme]}> | ||
<ShadowDOM include={['css/core/calendar.css', props.theme]}> | ||
<h1>Calendar for {props.date}</h1> | ||
</ShadowDOM> | ||
); | ||
} | ||
@@ -39,2 +43,24 @@ ``` | ||
Where components share CSS documents, only one instance of the CSS document will be fetched due to `memoize` of the [`fetchStylesheet`](https://github.com/Wildhoney/ReactShadow/blob/react-15.0/src/react-shadow.js#L22) function. | ||
Where components share documents, only one instance will be fetched due to `memoize` of the [`fetchInclude`](https://github.com/Wildhoney/ReactShadow/blob/master/src/react-shadow.js#L23) function. | ||
### Inlining Styles | ||
Instead of defining external CSS documents to fetch, you could choose to add all of the component's styles to the component itself by simply embedding a `style` node in your component. Naturally all styles added this way will be encapsulated within the shadow boundary. | ||
```javascript | ||
export default props => { | ||
const styles = `:host { background-color: ${props.theme} }`; | ||
return ( | ||
<ShadowDOM> | ||
<div> | ||
<h1>Calendar for {props.date}</h1> | ||
<style type="text/css">{styles}</style> | ||
</div> | ||
</ShadowDOM> | ||
); | ||
} | ||
``` | ||
It's worth noting that if you combine this approach with the loading of external CSS documents, you will have 2 `style` (*or more*) nodes in your component. |
@@ -6,2 +6,3 @@ import { get as fetch } from 'axios'; | ||
import memoize from 'ramda/src/memoize'; | ||
import groupBy from 'ramda/src/groupBy'; | ||
@@ -19,10 +20,10 @@ /** | ||
/** | ||
* @method fetchStylesheet | ||
* @method fetchInclude | ||
* @param {String} document | ||
* @return {Promise} | ||
*/ | ||
const fetchStylesheet = memoize(document => { | ||
const fetchInclude = memoize(document => { | ||
return new Promise(resolve => { | ||
fetch(document).then(response => response.data).then(response => resolve(response)).catch(() => resolve('')); | ||
fetch(document).then(response => response.data).then(resolve).catch(() => resolve('')); | ||
}); | ||
@@ -33,2 +34,19 @@ | ||
/** | ||
* @constant includeMap | ||
* @type {Object} | ||
*/ | ||
const includeMap = [ | ||
{ | ||
extensions: ['js'], tag: 'script', attrs: { | ||
type: 'text/javascript' | ||
} | ||
}, | ||
{ | ||
extensions: ['css'], tag: 'style', attrs: { | ||
type: 'text/css' | ||
} | ||
} | ||
]; | ||
/** | ||
* @class ShadowDOM | ||
@@ -45,3 +63,3 @@ * @extends Component | ||
children: PropTypes.node.isRequired, | ||
cssDocuments: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), | ||
include: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), | ||
nodeName: PropTypes.string, | ||
@@ -56,3 +74,3 @@ boundaryMode: PropTypes.oneOf(['open', 'closed']) | ||
static defaultProps = { | ||
cssDocuments: [], | ||
include: [], | ||
nodeName: 'span', | ||
@@ -92,3 +110,3 @@ boundaryMode: 'open' | ||
const root = node.attachShadow ? node.attachShadow({ mode: this.props.boundaryMode }) : node.createShadowRoot(); | ||
const cssDocuments = this.props.cssDocuments; | ||
const include = Array.isArray(this.props.include) ? this.props.include : [this.props.include]; | ||
const container = this.getContainer(); | ||
@@ -99,5 +117,5 @@ | ||
render(container, root); | ||
!cssDocuments.length && this.setState({ root }); | ||
!include.length && this.setState({ root }); | ||
if (cssDocuments.length) { | ||
if (include.length) { | ||
@@ -107,3 +125,3 @@ // Otherwise we'll fetch and attach the passed in stylesheets which need to be | ||
this.setState({ resolving: true, root }); | ||
this.attachStylesheets(this.props.cssDocuments); | ||
this.attachIncludes(include); | ||
@@ -128,32 +146,35 @@ } | ||
/** | ||
* @method attachStylesheets | ||
* @param cssDocuments {Array|String} | ||
* @method attachIncludes | ||
* @param include {Array|String} | ||
* @return {void} | ||
*/ | ||
attachStylesheets(cssDocuments) { | ||
attachIncludes(include) { | ||
const styleElement = document.createElement('style'); | ||
styleElement.setAttribute('type', 'text/css'); | ||
const documents = Array.isArray(cssDocuments) ? cssDocuments : [cssDocuments]; | ||
// Group all of the includes by their extension. | ||
const groupedFiles = groupBy(file => file.extension)(include.map(path => ({ path, extension: path.split('.').pop() }))); | ||
/** | ||
* @method insertStyleElement | ||
* @param {Array} cssDocuments | ||
* @return {void} | ||
*/ | ||
const insertStyleElement = cssDocuments => { | ||
const includeFiles = Object.keys(groupedFiles).map(extension => { | ||
styleElement.innerHTML = cssDocuments.reduce((accumulator, document) => { | ||
return `${accumulator} ${document}`; | ||
}); | ||
const nodeData = includeMap.find(model => model.extensions.includes(extension)); | ||
const files = groupedFiles[extension].map(model => model.path); | ||
this.state.root.appendChild(styleElement); | ||
if (!nodeData) { | ||
raise(`Files with extension of "${extension}" are unsupported`); | ||
} | ||
}; | ||
const containerElement = document.createElement(nodeData.tag); | ||
Promise.all(documents.map(fetchStylesheet)).then(cssDocuments => { | ||
insertStyleElement(cssDocuments); | ||
this.setState({ resolving: false }); | ||
// Apply all of the attributes defined in the `includeMap` to the node. | ||
Object.keys(nodeData.attrs).map(key => containerElement.setAttribute(key, nodeData.attrs[key])); | ||
// Load each file individually and then concatenate them. | ||
return Promise.all(files.map(fetchInclude)).then(fileData => { | ||
containerElement.innerHTML = fileData.reduce((acc, fileDatum) => `${acc} ${fileDatum}`).trim(); | ||
containerElement.innerHTML.length && this.state.root.appendChild(containerElement); | ||
}); | ||
}); | ||
Promise.all(includeFiles).then(() => this.setState({ resolving: false })); | ||
} | ||
@@ -160,0 +181,0 @@ |
@@ -98,3 +98,3 @@ import test from 'ava'; | ||
test('Should be able to include CSS documents', t => { | ||
test('Should be able to include external documents', t => { | ||
@@ -104,2 +104,3 @@ return new Promise(resolve => { | ||
const firstStylesheet = '* { border: 1px solid red }'; | ||
const firstJavaScript = 'console.log("Da comrade!")'; | ||
const secondStylesheet = '* { background-color: orange }'; | ||
@@ -109,4 +110,6 @@ | ||
t.context.mockAdapter.onGet('/second.css').reply(200, secondStylesheet); | ||
t.context.mockAdapter.onGet('/first.js').reply(200, firstJavaScript); | ||
t.context.mockAdapter.onGet('/second.js').reply(404); | ||
const Clock = t.context.create({ cssDocuments: ['/first.css', '/second.css'] }); | ||
const Clock = t.context.create({ include: ['/first.css', '/first.js', '/second.css', '/second.js'] }); | ||
const wrapper = mount(<Clock />); | ||
@@ -120,6 +123,15 @@ | ||
const style = { text: () => host.node.querySelector('style').innerHTML }; | ||
const styles = Array.from(host.node.querySelectorAll('style')); | ||
const style = { text: () => styles[0].innerHTML }; | ||
t.is(styles.length, 1); | ||
t.is(style.text(), `${firstStylesheet} ${secondStylesheet}`); | ||
const scripts = Array.from(host.node.querySelectorAll('script')); | ||
const script = { text: () => scripts[0].innerHTML }; | ||
t.is(scripts.length, 1); | ||
t.is(script.text(), `${firstJavaScript}`); | ||
t.true(host.hasClass('simple-clock')); | ||
t.true(host.hasClass('resolved')); | ||
resolve(); | ||
@@ -133,3 +145,3 @@ | ||
test('Should be able to raise exceptions from sanity checks;', t => { | ||
test('Should be able to raise necessary exceptions for happier devs;', t => { | ||
@@ -148,2 +160,7 @@ // Prevent React from throwing errors it would otherwise throw with the below assertions. | ||
t.throws(() => { | ||
t.context.mockAdapter.onGet('/document.png').reply(200, 'data'); | ||
mount(<ShadowDOM include="/document.png"><div><h1>Picture</h1></div></ShadowDOM>); | ||
}, 'ReactShadow: Files with extension of "png" are unsupported.'); | ||
}); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
737808
29
3453
64
4