Skip to content

Commit

Permalink
Add source code
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Coedo authored and davidbarral committed Apr 7, 2017
1 parent ac17e9d commit 9b56c5a
Show file tree
Hide file tree
Showing 12 changed files with 3,812 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
node_modules
lib
npm-debug.log
yarn-error.log
4 changes: 4 additions & 0 deletions .npmignore
@@ -0,0 +1,4 @@
src
test
.git
.gitignore
57 changes: 57 additions & 0 deletions package.json
@@ -0,0 +1,57 @@
{
"name": "react-conduit",
"version": "0.1.0",
"description": "Place components anywhere in your dom tree",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/trabe/react-conduit.git"
},
"author": "Roman Coedo <romancoedo@gmail.com>",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-core": "^6.24.0",
"babel-preset-es2015": "^6.24.0",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-3": "^6.22.0",
"chai": "^3.5.0",
"enzyme": "^2.8.0",
"enzyme-to-json": "^1.5.0",
"jest": "^19.0.2",
"prettier": "^0.22.0",
"react": "^15.4.2",
"react-addons-create-fragment": "^15.4.2",
"react-addons-test-utils": "^15.4.2",
"react-dom": "^15.4.2",
"sinon": "^2.1.0"
},
"scripts": {
"clean": "rm -rf ./lib",
"precompile": "npm run clean && npm run test",
"compile": "babel -d lib/ src/",
"compile:watch": "npm run compile -- --watch",
"test": "jest",
"test:watch": "npm run test -- --watch",
"prepublish": "npm run compile"
},
"peerDependencies": {
"react": ">= 0.14.x",
"react-addons-create-fragment": ">= 0.14.x"
},
"babel": {
"presets": [
"es2015",
"stage-3",
"react"
]
},
"jest": {
"snapshotSerializers": [
"<rootDir>/node_modules/enzyme-to-json/serializer"
]
},
"dependencies": {
"uuid": "^3.0.1"
}
}
25 changes: 25 additions & 0 deletions src/conduit-provider.js
@@ -0,0 +1,25 @@
import React, { Component, Children, PropTypes } from "react";
import { createRegistry } from "./registry";

class ConduitProvider extends Component {
constructor(props) {
super(props);
const { registry } = this.props;

this.registry = registry ? registry : createRegistry();
}

getChildContext() {
return { registry: this.registry };
}

render() {
return Children.only(this.props.children);
}
}

ConduitProvider.childContextTypes = {
registry: PropTypes.object.isRequired,
};

export default ConduitProvider;
4 changes: 4 additions & 0 deletions src/index.js
@@ -0,0 +1,4 @@
export { default as ConduitProvider } from "./conduit-provider";
export { default as Inlet } from "./inlet";
export { default as Outlet } from "./outlet";
export { createRegistry } from "./registry";
41 changes: 41 additions & 0 deletions src/inlet.js
@@ -0,0 +1,41 @@
import React, { Component, Children, PropTypes } from "react";
import uuidV4 from "uuid/v4";
import { updateChildren, removeInlet, addInlet } from "./registry";

export class Inlet extends Component {
constructor(props) {
super(props);
this.id = uuidV4();
}

componentWillReceiveProps(nextProps) {
if (this.props.label !== nextProps.label) {
removeInlet(this.context.registry, this.props.label, this.id);
addInlet(this.context.registry, nextProps.label, this.id);
}
updateChildren(this.context.registry, this.id, nextProps.children);
}

componentWillMount() {
addInlet(this.context.registry, this.props.label, this.id);
updateChildren(this.context.registry, this.id, this.props.children);
}

componentWillUnmount() {
removeInlet(this.context.registry, this.props.label, this.id);
}

render() {
return null;
}
}

Inlet.contextTypes = {
registry: PropTypes.object.isRequired,
};

Inlet.PropTypes = {
label: PropTypes.string.isRequired,
};

export default Inlet;
49 changes: 49 additions & 0 deletions src/outlet.js
@@ -0,0 +1,49 @@
import React, { Component, PropTypes } from "react";
import { watchOutlet, mergeInletChildren } from "./registry";

class Outlet extends Component {
constructor(props, context) {
super(props, context);

this.state = { children: this.getChildren() };

this.getChildren = this.getChildren.bind(this);
this.updateChildren = this.updateChildren.bind(this);
}

componentWillMount() {
watchOutlet(this.context.registry, this.props.label, this.updateChildren);
}

componentWillReceiveProps(nextProps) {
if (this.props.label !== nextProps.label) {
this.updateChildren();
}
}

getChildren() {
return mergeInletChildren(this.context.registry, this.props.label);
}

updateChildren() {
this.setState({ children: this.getChildren() });
}

render() {
return (
<div>
{this.state.children}
</div>
);
}
}

Outlet.contextTypes = {
registry: PropTypes.object.isRequired,
};

Outlet.PropTypes = {
label: PropTypes.string.isRequired,
};

export default Outlet;
59 changes: 59 additions & 0 deletions src/registry.js
@@ -0,0 +1,59 @@
import createFragment from "react-addons-create-fragment";
import uuidV4 from "uuid";

export const createRegistry = () => ({
// outletId -> { id: outletId, inlets: [inletIds], watchers: { uuid: fn }}
outlets: {},
// inletId -> children
children: {},
});

const initializeOutlet = (registry, { id, inlets = new Set(), watchers = {} }) => {
registry.outlets[id] = { id, inlets, watchers };
};

const ensureOutletInitialized = fn =>
(registry, outletId, ...args) => {
if (!registry.outlets[outletId]) {
initializeOutlet(registry, { id: outletId });
}
return fn(registry, outletId, ...args);
};

export const watchOutlet = ensureOutletInitialized((registry, outletId, fn) => {
const watcherId = uuidV4();
registry.outlets[outletId].watchers[watcherId] = fn;

return () => {
delete registry.outlets[outletId].watchers[watcherId];
};
});

export const mergeInletChildren = ({ outlets, children }, outletId) =>
createFragment(
Array.from((outlets[outletId] && outlets[outletId].inlets) || [])
.map(inletId => [inletId, children[inletId]])
.reduce((acc, [id, children]) => ({ ...acc, [id]: children }), {}),
);

export const addInlet = ensureOutletInitialized((registry, outletId, inletId) => {
registry.outlets[outletId].inlets.add(inletId);
notifyWatchers(registry, outletId);
});

export const removeInlet = ensureOutletInitialized((registry, outletId, inletId) => {
registry.outlets[outletId].inlets.delete(inletId);
notifyWatchers(registry, outletId);
});

export const updateChildren = (registry, inletId, children) => {
registry.children[inletId] = children;

Object.values(registry.outlets)
.filter(outlet => outlet.inlets.has(inletId))
.forEach(outlet => notifyWatchers(registry, outlet.id));
};

export const notifyWatchers = ensureOutletInitialized(({ outlets }, outletId) => {
Object.values(outlets[outletId].watchers).forEach(watcher => watcher());
});
70 changes: 70 additions & 0 deletions test/__snapshots__/snapshots.test.js.snap
@@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`conduit snapshots Inlet Inlet should not render its children 1`] = `null`;

exports[`conduit snapshots Inlet and Outlet Inlet children are sent to their corresponding outlets 1`] = `
<div>
<div
id="outlet1"
>
<div>
<div>
first inlet
</div>
<div>
second inlet
</div>
</div>
</div>
<div
id="outlet2"
>
<div>
<div>
third inlet
</div>
</div>
</div>
</div>
`;

exports[`conduit snapshots Inlet and Outlet should merge the associated Inlet's children to the corresponding outlet 1`] = `
<div>
<div>
<div>
first inlet
</div>
<div>
second inlet
</div>
<div>
third inlet
</div>
</div>
</div>
`;

exports[`conduit snapshots Inlet and Outlet should output Inlet's children to the corresponding outlet 1`] = `
<div>
<div>
this should be rendered in the outlet
</div>
</div>
`;

exports[`conduit snapshots Inlet and Outlet two outlets with the same label will duplicate the children rendering 1`] = `
<div>
<div>
<div>
inlet
</div>
</div>
<div>
<div>
inlet
</div>
</div>
</div>
`;

exports[`conduit snapshots Outlet Outlet should not render its children 1`] = `<div />`;

0 comments on commit 9b56c5a

Please sign in to comment.