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 | */
|
24 | import React from 'react';
|
25 | import { makeRequirable } from './makeRequirable';
|
26 | var 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 |
|
318 | var getDisplayName = function getDisplayName(Component) {
|
319 | return typeof Component === 'string' ? Component : Component.displayName || Component.name;
|
320 | };
|
321 |
|
322 | export default Children;
|
323 | export {
|
324 | /**
|
325 | * ---
|
326 | * category: utilities/PropTypes
|
327 | * ---
|
328 | * @module Children
|
329 | */
|
330 | Children }; |
\ | No newline at end of file |