/
utils.ts
executable file
·175 lines (154 loc) · 5.55 KB
/
utils.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import clone from 'lodash/clone';
import toPath from 'lodash/toPath';
import * as React from 'react';
// Assertions
/** @private is the value an empty array? */
export const isEmptyArray = (value?: any) =>
Array.isArray(value) && value.length === 0;
/** @private is the given object a Function? */
export const isFunction = (obj: any): obj is Function =>
typeof obj === 'function';
/** @private is the given object an Object? */
export const isObject = (obj: any): obj is Object =>
obj !== null && typeof obj === 'object';
/** @private is the given object an integer? */
export const isInteger = (obj: any): boolean =>
String(Math.floor(Number(obj))) === obj;
/** @private is the given object a string? */
export const isString = (obj: any): obj is string =>
Object.prototype.toString.call(obj) === '[object String]';
/** @private is the given object a NaN? */
// eslint-disable-next-line no-self-compare
export const isNaN = (obj: any): boolean => obj !== obj;
/** @private Does a React component have exactly 0 children? */
export const isEmptyChildren = (children: any): boolean =>
React.Children.count(children) === 0;
/** @private is the given object/value a promise? */
export const isPromise = (value: any): value is PromiseLike<any> =>
isObject(value) && isFunction(value.then);
/** @private is the given object/value a type of synthetic event? */
export const isInputEvent = (value: any): value is React.SyntheticEvent<any> =>
value && isObject(value) && isObject(value.target);
/**
* Same as document.activeElement but wraps in a try-catch block. In IE it is
* not safe to call document.activeElement if there is nothing focused.
*
* The activeElement will be null only if the document or document body is not
* yet defined.
*
* @param {?Document} doc Defaults to current document.
* @return {Element | null}
* @see https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/dom/getActiveElement.js
*/
export function getActiveElement(doc?: Document): Element | null {
doc = doc || (typeof document !== 'undefined' ? document : undefined);
if (typeof doc === 'undefined') {
return null;
}
try {
return doc.activeElement || doc.body;
} catch (e) {
return doc.body;
}
}
/**
* Deeply get a value from an object via its path.
*/
export function getIn(
obj: any,
key: string | string[],
def?: any,
p: number = 0
) {
const path = toPath(key);
while (obj && p < path.length) {
obj = obj[path[p++]];
}
return obj === undefined ? def : obj;
}
/**
* Deeply set a value from in object via it's path. If the value at `path`
* has changed, return a shallow copy of obj with `value` set at `path`.
* If `value` has not changed, return the original `obj`.
*
* Existing objects / arrays along `path` are also shallow copied. Sibling
* objects along path retain the same internal js reference. Since new
* objects / arrays are only created along `path`, we can test if anything
* changed in a nested structure by comparing the object's reference in
* the old and new object, similar to how russian doll cache invalidation
* works.
*
* In earlier versions of this function, which used cloneDeep, there were
* issues whereby settings a nested value would mutate the parent
* instead of creating a new object. `clone` avoids that bug making a
* shallow copy of the objects along the update path
* so no object is mutated in place.
*
* Before changing this function, please read through the following
* discussions.
*
* @see https://github.com/developit/linkstate
* @see https://github.com/jaredpalmer/formik/pull/123
*/
export function setIn(obj: any, path: string, value: any): any {
let res: any = clone(obj); // this keeps inheritance when obj is a class
let resVal: any = res;
let i = 0;
let pathArray = toPath(path);
for (; i < pathArray.length - 1; i++) {
const currentPath: string = pathArray[i];
let currentObj: any = getIn(obj, pathArray.slice(0, i + 1));
if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {
resVal = resVal[currentPath] = clone(currentObj);
} else {
const nextPath: string = pathArray[i + 1];
resVal = resVal[currentPath] =
isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};
}
}
// Return original object if new value is the same as current
if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {
return obj;
}
if (value === undefined) {
delete resVal[pathArray[i]];
} else {
resVal[pathArray[i]] = value;
}
// If the path array has a single element, the loop did not run.
// Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.
if (i === 0 && value === undefined) {
delete res[pathArray[i]];
}
return res;
}
/**
* Recursively a set the same value for all keys and arrays nested object, cloning
* @param object
* @param value
* @param visited
* @param response
*/
export function setNestedObjectValues<T>(
object: any,
value: any,
visited: any = new WeakMap(),
response: any = {}
): T {
for (let k of Object.keys(object)) {
const val = object[k];
if (isObject(val)) {
if (!visited.get(val)) {
visited.set(val, true);
// In order to keep array values consistent for both dot path and
// bracket syntax, we need to check if this is an array so that
// this will output { friends: [true] } and not { friends: { "0": true } }
response[k] = Array.isArray(val) ? [] : {};
setNestedObjectValues(val, value, visited, response[k]);
}
} else {
response[k] = value;
}
}
return response;
}