Skip to content

Commit 4a8a76e

Browse files
authored
Merge pull request #13 from metalabdesign/next
Merge next into master.
2 parents 4824fa5 + b99e0e4 commit 4a8a76e

8 files changed

Lines changed: 102 additions & 291 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
},
1616
"license": "CC0-1.0",
1717
"dependencies": {
18-
"core-js": "^2.4.1",
1918
"cuid": "^1.3.8",
2019
"fbjs": "^0.8.4",
2120
"hoist-non-react-statics": "^1.2.0"

src/action.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
import cuid from 'cuid';
22

33
export const ADD_COMPONENT = '@@relocation/ADD_COMPONENT';
4+
export const SET_COMPONENT = '@@relocation/SET_COMPONENT';
5+
export const UPDATE_COMPONENT = '@@relocation/UPDATE_COMPONENT';
46
export const REMOVE_COMPONENT = '@@relocation/REMOVE_COMPONENT';
5-
export const SET_PREVIOUS_PATH = '@@relocation/SET_PREVIOUS_PATH';
67
export const SET_ROUTE_COMPONENTS = '@@relocation/SET_ROUTE_COMPONENTS';
78

8-
export const addComponent = ({type, props, id = cuid()}) => ({
9+
export const addComponent = (type, props) => ({
910
type: ADD_COMPONENT,
11+
payload: {id: cuid(), type, props},
12+
});
13+
14+
export const setComponent = (type, id = type, props) => ({
15+
type: SET_COMPONENT,
1016
payload: {id, type, props},
1117
});
1218

13-
export const removeComponent = (id) => ({type: REMOVE_COMPONENT, payload: id});
19+
export const updateComponent = (id, props) => ({
20+
type: UPDATE_COMPONENT,
21+
payload: {id, props},
22+
});
1423

15-
export const setPreviousPath = (path) => ({
16-
type: SET_PREVIOUS_PATH,
17-
payload: path,
24+
export const removeComponent = (id) => ({
25+
type: REMOVE_COMPONENT,
26+
payload: {id},
1827
});
1928

2029
export const setRouteComponents = (components) => ({

src/connect.js

Lines changed: 65 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import {Component, createElement, PropTypes} from 'react';
22
import hoistStatics from 'hoist-non-react-statics';
33
import {connect} from 'react-redux';
44

5-
import {getMergedComponents, getPreviousPath} from './selector';
6-
import {removeComponent} from './action';
5+
import {getComponents} from './selector';
6+
import {removeComponent, updateComponent} from './action';
77
import {componentsShape, renderMapShape, getDisplayName} from './util';
88

99
/**
@@ -12,18 +12,18 @@ import {componentsShape, renderMapShape, getDisplayName} from './util';
1212
*
1313
* @param {Object|Function} rawRenderMap An object with component type/render
1414
* function key value pairs or a function returning such an object.
15-
* @param {Object|Function} rawConfig An object or a function returing such an
15+
* @param {Object} defaultProps An object or a function returing such an
1616
* object.
1717
* @returns {Function} Higher-order component wrapper.
1818
*/
19-
export default (rawRenderMap, rawConfig = {}) => (WrappedComponent) => {
19+
export default ({scope, ...defaultProps} = {}) => (WrappedComponent) => {
2020
class Connect extends Component {
2121
static propTypes = {
22-
dispatch: PropTypes.func.isRequired,
23-
___relocation___: PropTypes.shape({
22+
___relocationDispatch___: PropTypes.shape({
23+
removeComponent: PropTypes.func.isRequired,
24+
}),
25+
___relocationState___: PropTypes.shape({
2426
components: componentsShape.isRequired,
25-
previousPath: PropTypes.string.isRequired,
26-
config: PropTypes.object.isRequired,
2727
renderMap: renderMapShape.isRequired,
2828
}).isRequired,
2929
};
@@ -32,107 +32,50 @@ export default (rawRenderMap, rawConfig = {}) => (WrappedComponent) => {
3232
router: PropTypes.object,
3333
}
3434

35-
navigateToPath(path, {useHistoryBack = true} = {}) {
36-
// Check for the react-router context.
37-
if (!this.context.router) {
38-
return;
39-
}
40-
41-
const {previousPath} = this.props.___relocation___;
42-
43-
// The `useHistoryBack` option will trigger the use of the `goBack` method
44-
// instead of `push` in an effort to keep the if the requested path is
45-
// equal to the previous path.
46-
//
47-
// This is useful in scenraios where component that is displayed in
48-
// response to a route change is considered dismissed or completed on
49-
// removal.
50-
if (useHistoryBack && path === previousPath) {
51-
this.context.router.goBack();
52-
} else {
53-
this.context.router.push(path);
54-
}
55-
}
56-
57-
removeComponent(id/* , options */) {
58-
return this.props.dispatch(removeComponent(id));
59-
}
60-
6135
render() {
62-
const {components, renderMap} = this.props.___relocation___;
63-
64-
const assignRender = (component) => ({
65-
...component,
66-
render: renderMap[component.type],
67-
});
36+
const {components, renderMap} = this.props.___relocationState___;
37+
const {
38+
removeComponent,
39+
updateComponent,
40+
} = this.props.___relocationDispatch___;
6841

6942
const inRenderMap = (component) =>
7043
typeof renderMap[component.type] === 'function';
7144

72-
const assignRemoveHandler = (component) => {
73-
let removeHandler = null;
74-
75-
if (typeof component.remove === 'function') {
76-
// The component object remove property is already a function.
77-
// We don't want to override this behavior.
78-
removeHandler = component.remove;
79-
} else if (component.remove === undefined || component.remove) {
80-
// The component object does not have a `remove` property, or it has
81-
// a truthy value that is not a function. Either case indicates that
82-
// it should use the default remove handler.
83-
removeHandler = (options) =>
84-
this.removeComponent(component.id, options);
85-
}
86-
87-
let pathRemoveHandler = null;
45+
const assign = (component) => {
46+
const result = {
47+
...component,
48+
render: renderMap[component.type],
49+
update: (props) => updateComponent(component.id, props),
50+
remove: () => removeComponent(component.id),
51+
};
8852

89-
if (typeof component.removePath === 'string') {
90-
// Create a function that will change the history state when removing
91-
// the component.
92-
pathRemoveHandler = (options) =>
93-
this.navigateToPath(component.removePath, options);
53+
if (scope) {
54+
result.scope = scope;
9455
}
9556

96-
if (pathRemoveHandler && removeHandler) {
97-
// A remove handler function and a
98-
return {
99-
...component,
100-
remove: (options) => {
101-
pathRemoveHandler(options);
102-
return removeHandler(options);
103-
},
104-
};
105-
}
57+
return result;
58+
};
10659

107-
if (pathRemoveHandler && !removeHandler) {
108-
return {
109-
...component,
110-
remove: pathRemoveHandler,
111-
};
112-
}
60+
const currentComponents = components
61+
// Remove components not included in the render function map.
62+
.filter(inRenderMap)
63+
// Assign render update and remove functions and scope if it is defined.
64+
.map(assign);
11365

114-
if (!pathRemoveHandler && removeHandler !== component.remove) {
115-
return {
116-
...component,
117-
remove: removeHandler,
118-
};
119-
}
120-
121-
// `!pathRemoveHandler && removeHandler === component.remove` is true.
122-
// This means `remove` was set and `removePath` was not set on the
123-
// component object. No modification is necessary.
124-
return component;
125-
};
66+
/* eslint-disable no-unused-vars */
67+
const {
68+
___relocationState___,
69+
___relocationDispatch___,
70+
...childProps,
71+
} = this.props;
72+
/* eslint-enable no-unused-vars */
12673

12774
const mergedProps = {
128-
...this.props,
129-
components: components
130-
// Assign render functions.
131-
.map(assignRender)
132-
// Remove components not included in the render function map.
133-
.filter(inRenderMap)
134-
// Assign remove handler functions.
135-
.map(assignRemoveHandler),
75+
...childProps,
76+
...scope
77+
? {[scope]: {components: currentComponents}}
78+
: {components: currentComponents},
13679
};
13780

13881
return <WrappedComponent {...mergedProps}/>;
@@ -142,28 +85,38 @@ export default (rawRenderMap, rawConfig = {}) => (WrappedComponent) => {
14285
Connect.displayName = `Relocation(${getDisplayName(WrappedComponent)})`;
14386

14487
const mapState = (state, props) => {
145-
const config = typeof rawConfig === 'function' ?
146-
rawConfig(props) : rawConfig;
147-
148-
const renderMap = typeof rawRenderMap === 'function' ?
149-
rawRenderMap(props) : rawRenderMap;
88+
const mergedProps = {
89+
...defaultProps,
90+
...scope ? props[scope] : props,
91+
};
15092

151-
const {getRelocationState} = config;
93+
const {components, getRelocationState} = mergedProps;
15294

153-
const selectorProps = getRelocationState ?
154-
{getRelocationState, ...props} : props;
95+
const selectorProps = getRelocationState
96+
? {getRelocationState, ...props}
97+
: props;
15598

15699
return {
157-
// Put everything in a ___relocation___ namespace to avoid possible
100+
// Put everything in a ___relocationState___ namespace to avoid possible
158101
// conflict with existing props.
159-
___relocation___: {
160-
components: getMergedComponents(state, selectorProps),
161-
previousPath: getPreviousPath(state, selectorProps),
162-
config,
163-
renderMap,
102+
___relocationState___: {
103+
components: getComponents(state, selectorProps),
104+
renderMap: components,
164105
},
165106
};
166107
};
167108

168-
return connect(mapState)(hoistStatics(Connect, WrappedComponent));
109+
const mapDispatch = (dispatch) => ({
110+
// Put everything in a ___relocationDispatch___ namespace to avoid
111+
// possible conflict with existing props.
112+
___relocationDispatch___: {
113+
removeComponent: (id) => dispatch(removeComponent(id)),
114+
updateComponent: (id, props) => dispatch(updateComponent(id, props)),
115+
},
116+
});
117+
118+
return connect(
119+
mapState,
120+
mapDispatch,
121+
)(hoistStatics(Connect, WrappedComponent));
169122
};

src/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ export {ADD_COMPONENT, REMOVE_COMPONENT} from './action';
22
export {addComponent, removeComponent} from './action';
33
export {default as relocation} from './connect';
44
export {default as reducer} from './reducer';
5-
export {default as createRelocationRouter} from './router';
65
export {default} from './connect';

src/reducer.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import {combineReducers} from 'redux';
22
import {
3-
REMOVE_COMPONENT,
43
ADD_COMPONENT,
5-
SET_PREVIOUS_PATH,
6-
SET_ROUTE_COMPONENTS,
4+
SET_COMPONENT,
5+
UPDATE_COMPONENT,
6+
REMOVE_COMPONENT,
77
} from './action';
88

9-
const createReducer = (type, initial) =>
10-
(state = initial, action) => action.type === type ? action.payload : state;
11-
129
export default combineReducers({
1310
components: (state = [], action) => ({
14-
[ADD_COMPONENT]: (state, {payload}) => [...state, payload],
11+
[ADD_COMPONENT]: (state, {payload}) =>
12+
[...state, payload],
13+
14+
[SET_COMPONENT]: (state, {payload}) =>
15+
[...state.filter((item) => item.id !== payload.id), payload],
16+
17+
[UPDATE_COMPONENT]: (state, {payload}) =>
18+
state.map((item) => item.id === payload.id
19+
? {...item, props: {...item.props, ...payload.props}}
20+
: item
21+
),
22+
1523
[REMOVE_COMPONENT]: (state, {payload}) =>
16-
state.filter((item) => item.id !== payload),
24+
state.filter((item) => item.id !== payload.id),
25+
1726
}[action.type] || (() => state))(state, action),
18-
routeComponents: createReducer(SET_ROUTE_COMPONENTS, []),
19-
previousPath: createReducer(SET_PREVIOUS_PATH, ''),
2027
});

0 commit comments

Comments
 (0)