UNPKG

9.46 kBJavaScriptView Raw
1/*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2015 - present Instructure, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24import React from 'react';
25import { makeRequirable } from './makeRequirable';
26var Children = {
27 /**
28 * Validate that the children of a component are one of the specified types.
29 *
30 * ```js
31 * import { Children } from '@instructure/ui-prop-types'
32 *
33 * class Example extends Component {
34 * static propTypes = {
35 * children: Children.oneOf([Foo, Bar, Baz])
36 * }
37 *
38 * render () {
39 * return <div>{this.props.children}</div>
40 * }
41 * }
42 * ```
43 *
44 * This will allow children such as:
45 *
46 * ```jsx
47 * <Example>
48 * <Foo />
49 * </Example>
50 * ```
51 *
52 * OR
53 *
54 * ```jsx
55 * <Example>
56 * <Bar />
57 * <Foo />
58 * </Example>
59 * ```
60 *
61 * But will fail on something like:
62 *
63 * ```jsx
64 * <Example>
65 * <h1>Example</h1>
66 * <Foo />
67 * </Example>
68 * ```
69 * @returns {Error} if validation failed
70 */
71 oneOf: function oneOf(validTypes) {
72 function validator(props, propName, componentName) {
73 var children = React.Children.toArray(props[propName]);
74 var validTypeNames = validTypes.map(function (type) {
75 return type ? getDisplayName(type) : type;
76 });
77
78 for (var i = 0; i < children.length; i++) {
79 var child = children[i];
80
81 if (child && child.type) {
82 var childName = getDisplayName(child.type);
83
84 if (validTypeNames.indexOf(childName) < 0) {
85 return new Error("Expected one of ".concat(validTypeNames.join(', '), " in ").concat(componentName, " but found '").concat(childName, "'"));
86 }
87 } else if (child) {
88 return new Error("Expected one of ".concat(validTypeNames.join(', '), " in ").concat(componentName, " but found an element with unknown type: ").concat(child));
89 }
90 }
91 }
92
93 validator.isRequired = makeRequirable(validator);
94 return validator;
95 },
96
97 /**
98 * Ensures that there is exactly one of each specified child
99 *
100 * ```js
101 * import { Children } from '@instructure/ui-prop-types'
102 *
103 * class Example extends Component {
104 * static propTypes = {
105 * children: Children.oneOfEach([Foo, Bar, Baz])
106 * }
107 *
108 * render () {
109 * return <div>{this.props.children}</div>
110 * }
111 * }
112 * ```
113 *
114 * This will enforce the following:
115 *
116 * ```jsx
117 * <Example>
118 * <Foo />
119 * <Bar />
120 * <Baz />
121 * </Example>
122 * ```
123 * An error will be thrown
124 * - If any of the children are not provided (ex. Foo, Bar, but missing Baz)
125 * - If multiple children of the same type are provided (ex. Foo, Foo, Bar, and Baz)
126 *
127 * @param {Array} validTypes - Array of child types
128 * @returns {Error} if validation failed
129 */
130 oneOfEach: function oneOfEach(validTypes) {
131 return function (props, propName, componentName) {
132 var children = React.Children.toArray(props[propName]);
133 var instanceCount = {};
134 var validTypeNames = validTypes.map(function (type) {
135 var typeName = getDisplayName(type);
136 instanceCount[typeName] = 0;
137 return typeName;
138 });
139
140 for (var i = 0; i < children.length; i++) {
141 var child = children[i];
142
143 if (child && child.type) {
144 var childName = getDisplayName(child.type);
145
146 if (validTypeNames.indexOf(childName) < 0) {
147 return new Error("Expected one of ".concat(validTypeNames.join(', '), " in ").concat(componentName, " but found '").concat(childName, "'"));
148 }
149
150 instanceCount[childName] = (instanceCount[childName] || 0) + 1;
151 } else if (child) {
152 return new Error("Expected one of ".concat(validTypeNames.join(', '), " in ").concat(componentName, " but found an element of unknown type: ").concat(child));
153 }
154 }
155
156 var errors = [];
157 Object.keys(instanceCount).forEach(function (childName) {
158 if (instanceCount[childName] > 1) {
159 errors.push("".concat(instanceCount[childName], " children of type ").concat(childName));
160 }
161
162 if (instanceCount[childName] === 0) {
163 errors.push("0 children of type ".concat(childName));
164 }
165 });
166
167 if (errors.length > 0) {
168 return new Error("Expected exactly one of each ".concat(validTypeNames.join(', '), " in ").concat(componentName, " but found:\n ").concat(errors.join('\n')));
169 }
170 };
171 },
172
173 /**
174 * Validate the type and order of children for a component.
175 *
176 * ```js
177 * import { Children } from '@instructure/ui-prop-types'
178 *
179 * class Example extends Component {
180 * static propTypes = {
181 * children: Children.enforceOrder([Foo, Bar, Baz])
182 * }
183 *
184 * render () {
185 * return <div>{this.props.children}</div>
186 * }
187 * }
188 * ```
189 *
190 * This will enforce the following:
191 *
192 * ```jsx
193 * <Example>
194 * <Foo />
195 * <Bar />
196 * <Baz />
197 * </Example>
198 * ```
199 *
200 * This validator will also allow various permutations of the order.
201 *
202 * ```js
203 * import { Children } from '@instructure/ui-prop-types'
204 *
205 * class Example extends Component {
206 * static propTypes = {
207 * children: Children.enforceOrder(
208 * [Foo, Bar, Baz],
209 * [Foo, Bar],
210 * [Bar, Baz],
211 * )
212 * }
213 *
214 * render () {
215 * return <div>{this.props.children}</div>
216 * }
217 * }
218 * ```
219 *
220 * This will enforce one of the following:
221 *
222 * ```jsx
223 * <Example>
224 * <Foo />
225 * <Bar />
226 * <Baz />
227 * </Example>
228 * ```
229 *
230 * OR
231 *
232 * ```jsx
233 * <Example>
234 * <Foo />
235 * <Bar />
236 * </Example>
237 * ```
238 *
239 * OR
240 *
241 * ```jsx
242 * <Example>
243 * <Bar />
244 * <Baz />
245 * </Example>
246 * ```
247 *
248 * @param {...Array} validTypeGroups One or more Arrays of valid types
249 * @returns {Error} if validation failed
250 */
251 enforceOrder: function enforceOrder() {
252 for (var _len = arguments.length, validTypeGroups = new Array(_len), _key = 0; _key < _len; _key++) {
253 validTypeGroups[_key] = arguments[_key];
254 }
255
256 function validateTypes(childNames, typeNames) {
257 for (var i = 0; i < childNames.length; i++) {
258 if (childNames[i] !== typeNames[i]) {
259 return false;
260 }
261 }
262
263 return true;
264 }
265
266 function formatGroupTypes(componentName, typeGroups) {
267 return typeGroups.map(function (types) {
268 return formatTypes(componentName, types);
269 }).join('\n\n');
270 }
271
272 function formatTypes(componentName, types) {
273 var children = types.map(function (type) {
274 if (type) {
275 return getDisplayName(type);
276 } else {
277 return '??';
278 }
279 }).map(function (name) {
280 return " <".concat(name, " />");
281 }).join('\n');
282 return "<".concat(componentName, ">\n").concat(children, "\n</").concat(componentName, ">");
283 }
284
285 function validator(props, propName, componentName) {
286 var childNames = React.Children.toArray(props[propName]).map(function (child) {
287 if (child && child.type) {
288 return getDisplayName(child.type);
289 } else if (child) {
290 return null;
291 }
292 }); // Validate each group, if any of them are valid we're done
293
294 for (var i = 0; i < validTypeGroups.length; i++) {
295 var validTypeNames = validTypeGroups[i].map(function (type) {
296 if (type) {
297 return getDisplayName(type);
298 } else {
299 return '??';
300 }
301 });
302
303 if (validateTypes(childNames, validTypeNames)) {
304 return;
305 }
306 } // If we make it through the loop then children are not valid
307
308
309 return new Error("Expected children of ".concat(componentName, " in one of the following formats:\n ").concat(formatGroupTypes(componentName, validTypeGroups), "\n\n\n Instead of:\n ").concat(formatTypes(componentName, childNames)));
310 }
311
312 validator.isRequired = makeRequirable(validator);
313 return validator;
314 }
315}; // TODO: Remove when we further break up ui-utils and bringing this in no longer creates
316// a circular dep
317
318var getDisplayName = function getDisplayName(Component) {
319 return typeof Component === 'string' ? Component : Component.displayName || Component.name;
320};
321
322export default Children;
323export {
324/**
325 * ---
326 * category: utilities/PropTypes
327 * ---
328 * @module Children
329 */
330Children };
\No newline at end of file