-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathSlot.ts
135 lines (110 loc) · 3.94 KB
/
Slot.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { managerShape } from '../utils/PropTypes';
import * as React from 'react';
import Fill from './Fill';
import Manager, { Component } from '../Manager';
import { Requireable } from 'prop-types';
export interface Props {
/**
* The name of the component. Use a symbol if you want to be 100% sue the Slot
* will only be filled by a component you create
*/
name: string | Symbol;
/**
* Props to be applied to the child Element of every fill which has the same name.
*
* If the value is a function, it must have the following signature:
* (target: Fill, fills: Fill[]) => void;
*
* This allows you to access props on the fill which invoked the function
* by using target.props.something()
*/
fillChildProps?: { [key: string]: any };
}
export interface State {
components: Component[];
}
export interface Context {
manager: Manager;
}
export default class Slot extends React.Component<Props, State> {
static contextTypes = {
manager: managerShape
};
context: Context;
constructor(props: Props) {
super(props);
this.state = { components: [] };
this.handleComponentChange = this.handleComponentChange.bind(this);
}
componentWillMount() {
this.context.manager.onComponentsChange(this.props.name, this.handleComponentChange);
}
handleComponentChange(components: Component[]) {
this.setState({ components });
}
get fills(): Fill[] {
return this.state.components.map(c => c.fill);
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.name !== this.props.name) {
this.context.manager.removeOnComponentsChange(this.props.name, this.handleComponentChange);
const name = nextProps.name;
this.context.manager.onComponentsChange(name, this.handleComponentChange);
}
}
componentWillUnmount() {
const name = this.props.name;
this.context.manager.removeOnComponentsChange(name, this.handleComponentChange);
}
render() {
const aggElements: React.ReactNode[] = [];
this.state.components.forEach((component, index) => {
const { fill, children } = component;
const { fillChildProps } = this.props;
if (fillChildProps) {
const transform = (acc: {}, key: string) => {
const value = fillChildProps[key];
if (typeof value === 'function') {
acc[key] = () => value(fill, this.fills);
} else {
acc[key] = value;
}
return acc;
};
const fillChildProps2 = Object.keys(this.props.fillChildProps).reduce(transform, {});
children.forEach((child, index2) => {
if (typeof child === 'number' || typeof child === 'string') {
throw new Error('Only element children will work here');
}
aggElements.push(
React.cloneElement(child, { key: index.toString() + index2.toString(), ...fillChildProps2 })
);
});
} else {
children.forEach((child, index2) => {
if (typeof child === 'number' || typeof child === 'string') {
throw new Error('Only element children will work here');
}
aggElements.push(
React.cloneElement(child, { key: index.toString() + index2.toString() })
);
});
}
});
if (typeof this.props.children === 'function') {
const element = this.props.children(aggElements);
if (React.isValidElement(element) || element === null) {
return element;
} else {
const untypedThis: any = this;
const parentConstructor = untypedThis._reactInternalInstance._currentElement._owner._instance.constructor;
const displayName = parentConstructor.displayName || parentConstructor.name;
const message = `Slot rendered with function must return a valid React ` +
`Element. Check the ${displayName} render function.`;
throw new Error(message);
}
} else {
return React.createElement('div', {}, aggElements);
}
}
}