@codeleap/utils 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +30 -0
- package/package.json.bak +30 -0
- package/src/array.ts +51 -0
- package/src/cloneDeep.ts +43 -0
- package/src/colors.ts +47 -0
- package/src/faker.ts +34 -0
- package/src/file.ts +23 -0
- package/src/index.ts +9 -0
- package/src/misc.ts +134 -0
- package/src/object.ts +162 -0
- package/src/react.tsx +107 -0
- package/src/string.ts +49 -0
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codeleap/utils",
|
|
3
|
+
"version": "4.3.0",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
|
|
8
|
+
"type": "git",
|
|
9
|
+
"directory": "packages/utils"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@codeleap/config": "4.3.0",
|
|
13
|
+
"@codeleap/types": "4.3.0",
|
|
14
|
+
"ts-node-dev": "1.1.8"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "echo 'No build needed'"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@codeleap/types": "4.3.0",
|
|
21
|
+
"axios": "^1.7.9",
|
|
22
|
+
"typescript": "5.0.4",
|
|
23
|
+
"react": "18.1.0",
|
|
24
|
+
"@tanstack/react-query": "5.60.6"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"tinycolor2": "^1.4.2",
|
|
28
|
+
"deep-equal": "^2.0.5"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json.bak
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codeleap/utils",
|
|
3
|
+
"version": "4.3.0",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
|
|
8
|
+
"type": "git",
|
|
9
|
+
"directory": "packages/utils"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@codeleap/config": "workspace:*",
|
|
13
|
+
"@codeleap/types": "workspace:*",
|
|
14
|
+
"ts-node-dev": "1.1.8"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "echo 'No build needed'"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@codeleap/types": "workspace:*",
|
|
21
|
+
"axios": "^1.7.9",
|
|
22
|
+
"typescript": "5.0.4",
|
|
23
|
+
"react": "18.1.0",
|
|
24
|
+
"@tanstack/react-query": "5.60.6"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"tinycolor2": "^1.4.2",
|
|
28
|
+
"deep-equal": "^2.0.5"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/array.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FunctionType } from '@codeleap/types'
|
|
2
|
+
|
|
3
|
+
type GetterFunction<T> = FunctionType<[T, number], string | number> | keyof T
|
|
4
|
+
|
|
5
|
+
export function objectFromArray<T, Getter extends GetterFunction<T>>(
|
|
6
|
+
arr: T[],
|
|
7
|
+
keyAccessor?: Getter,
|
|
8
|
+
): Record<string, T> {
|
|
9
|
+
let getObjectKey = (_, idx) => idx
|
|
10
|
+
|
|
11
|
+
if (keyAccessor) {
|
|
12
|
+
switch (typeof keyAccessor) {
|
|
13
|
+
case 'string':
|
|
14
|
+
getObjectKey = (value) => value[keyAccessor]
|
|
15
|
+
break
|
|
16
|
+
case 'function':
|
|
17
|
+
getObjectKey = keyAccessor
|
|
18
|
+
break
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const indexedMap = arr.map((value, idx) => [getObjectKey(value, idx), value])
|
|
23
|
+
|
|
24
|
+
return Object.fromEntries(indexedMap)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function uniqueArrayByProperty<T, G extends GetterFunction<T>>(
|
|
28
|
+
array: T[],
|
|
29
|
+
getProperty: G,
|
|
30
|
+
) {
|
|
31
|
+
return Object.values(objectFromArray(array, getProperty))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function flatten<T extends unknown>(arr: T[]) {
|
|
35
|
+
let newArr = [] as T[]
|
|
36
|
+
|
|
37
|
+
arr.forEach((item) => {
|
|
38
|
+
if (Array.isArray(item)) {
|
|
39
|
+
newArr = [...newArr, ...flatten(item)]
|
|
40
|
+
} else {
|
|
41
|
+
newArr.push(item)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return newArr
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function range(start: number, end: number) {
|
|
49
|
+
const length = end - start + 1
|
|
50
|
+
return Array.from({ length }, (_, index) => index + start)
|
|
51
|
+
}
|
package/src/cloneDeep.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function cloneDeep(value) {
|
|
2
|
+
if (value === null || typeof value !== 'object') {
|
|
3
|
+
return value
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.map(cloneDeep)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (value instanceof Date) {
|
|
11
|
+
return new Date(value.getTime())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (value instanceof Map) {
|
|
15
|
+
const clonedMap = new Map()
|
|
16
|
+
value.forEach((v, k) => {
|
|
17
|
+
clonedMap.set(k, cloneDeep(v))
|
|
18
|
+
})
|
|
19
|
+
return clonedMap
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (value instanceof Set) {
|
|
23
|
+
const clonedSet = new Set()
|
|
24
|
+
value.forEach((v) => {
|
|
25
|
+
clonedSet.add(cloneDeep(v))
|
|
26
|
+
})
|
|
27
|
+
return clonedSet
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (value instanceof RegExp) {
|
|
31
|
+
return new RegExp(value.source, value.flags)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const clonedObj = {}
|
|
35
|
+
|
|
36
|
+
for (const key in value) {
|
|
37
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
38
|
+
clonedObj[key] = cloneDeep(value[key])
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return clonedObj
|
|
43
|
+
}
|
package/src/colors.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import tinycolor from 'tinycolor2'
|
|
2
|
+
import { TypeGuards } from '@codeleap/types'
|
|
3
|
+
|
|
4
|
+
export function hexToRgb(hex) {
|
|
5
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
6
|
+
return result
|
|
7
|
+
? {
|
|
8
|
+
r: parseInt(result[1], 16),
|
|
9
|
+
g: parseInt(result[2], 16),
|
|
10
|
+
b: parseInt(result[3], 16),
|
|
11
|
+
}
|
|
12
|
+
: null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const shadeColorCache = {}
|
|
16
|
+
export function shadeColor(color: string, percent = 0, opacity = null) {
|
|
17
|
+
const _color = color.trim()
|
|
18
|
+
const serialParams = [_color, percent.toString()]
|
|
19
|
+
if (TypeGuards.isNumber(opacity)) {
|
|
20
|
+
serialParams.push(opacity.toString())
|
|
21
|
+
}
|
|
22
|
+
const cacheKey = serialParams.join('/')
|
|
23
|
+
|
|
24
|
+
if (!!shadeColorCache[cacheKey]) {
|
|
25
|
+
return shadeColorCache[cacheKey]
|
|
26
|
+
}
|
|
27
|
+
const cl = tinycolor(_color)
|
|
28
|
+
if (percent !== 0) {
|
|
29
|
+
|
|
30
|
+
const shouldDarken = percent < 0
|
|
31
|
+
|
|
32
|
+
if (shouldDarken) {
|
|
33
|
+
cl.darken(-percent)
|
|
34
|
+
} else {
|
|
35
|
+
cl.lighten(percent)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (TypeGuards.isNumber(opacity)) {
|
|
39
|
+
cl.setAlpha(opacity)
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rgbObj = cl.toRgb()
|
|
44
|
+
const rgbStr = `rgba(${rgbObj.r},${rgbObj.g},${rgbObj.b},${rgbObj.a})`
|
|
45
|
+
shadeColorCache[cacheKey] = rgbStr
|
|
46
|
+
return rgbStr
|
|
47
|
+
}
|
package/src/faker.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const names = [
|
|
2
|
+
'James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Joseph', 'Thomas',
|
|
3
|
+
'Charles', 'Christopher', 'Daniel', 'Matthew', 'Anthony', 'Donald', 'Mark', 'Paul', 'Steven',
|
|
4
|
+
'Andrew', 'Kenneth', 'Joshua', 'Kevin', 'Brian', 'George', 'Edward', 'Ronald', 'Timothy',
|
|
5
|
+
'Jason', 'Jeffrey', 'Ryan', 'Gary', 'Jacob'
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
const surnames = [
|
|
9
|
+
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis',
|
|
10
|
+
'Lopez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore',
|
|
11
|
+
'Jackson', 'Martin', 'Lee', 'Perez', 'Thompson', 'White', 'Harris', 'Sanchez', 'Clark',
|
|
12
|
+
'Ramirez', 'Lewis', 'Robinson', 'Walker', 'Young', 'Allen', 'King', 'Wright', 'Scott', 'Torres',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const animals = [
|
|
16
|
+
'lion', 'tiger', 'zebra', 'panda', 'koala', 'bear',
|
|
17
|
+
'wolf', 'fox', 'rabbit', 'bat', 'spider', 'frog', 'shark'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
function getRandom(list: Array<string>) {
|
|
21
|
+
return list[Math.floor(Math.random() * list.length)]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function number(min: number = 0, max: number = 100) {
|
|
25
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const faker = {
|
|
29
|
+
lastName: () => getRandom(surnames),
|
|
30
|
+
firstName: () => getRandom(names),
|
|
31
|
+
animal: () => getRandom(animals),
|
|
32
|
+
number,
|
|
33
|
+
name: () => `${getRandom(names)} ${getRandom(surnames)}`
|
|
34
|
+
}
|
package/src/file.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const separators = /[\\\/]+/
|
|
2
|
+
|
|
3
|
+
export function parseFilePathData(path: string) {
|
|
4
|
+
const parts = path.split(separators)
|
|
5
|
+
|
|
6
|
+
const lastPart = parts[parts.length - 1]
|
|
7
|
+
|
|
8
|
+
let fileName = lastPart
|
|
9
|
+
let ext = ''
|
|
10
|
+
|
|
11
|
+
if (lastPart.includes('.')) {
|
|
12
|
+
const dotIdx = fileName.lastIndexOf('.')
|
|
13
|
+
fileName = fileName.substring(0, dotIdx)
|
|
14
|
+
|
|
15
|
+
ext = lastPart.substring(dotIdx + 1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
path: parts.slice(0, -1).join('/'),
|
|
20
|
+
extension: ext,
|
|
21
|
+
name: fileName,
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
package/src/misc.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { capitalize } from './string'
|
|
2
|
+
import { StylesOf } from '@codeleap/types'
|
|
3
|
+
|
|
4
|
+
export function imagePathToFileObject(imagePath: string | null) {
|
|
5
|
+
const parts = imagePath ? imagePath.split('.') : ''
|
|
6
|
+
|
|
7
|
+
const ext = imagePath ? parts[parts.length - 1].toLowerCase() : ''
|
|
8
|
+
|
|
9
|
+
const fileValue = imagePath
|
|
10
|
+
? {
|
|
11
|
+
uri: imagePath,
|
|
12
|
+
name: 'image_' + imagePath,
|
|
13
|
+
type: `image/${ext}`,
|
|
14
|
+
}
|
|
15
|
+
: null
|
|
16
|
+
|
|
17
|
+
return fileValue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const letterToColorMap = {
|
|
21
|
+
a: '#7CB9E8',
|
|
22
|
+
b: '#3a9e77',
|
|
23
|
+
c: '#A3C1AD',
|
|
24
|
+
d: '#E1BD27',
|
|
25
|
+
e: '#badc58',
|
|
26
|
+
f: '#db5970',
|
|
27
|
+
g: '#9b8ef1',
|
|
28
|
+
h: '#ffe169',
|
|
29
|
+
i: '#3ea9d1',
|
|
30
|
+
j: '#8aa341',
|
|
31
|
+
k: '#baf2f5',
|
|
32
|
+
l: '#ffa02d',
|
|
33
|
+
m: '#d46830',
|
|
34
|
+
n: '#62ecaa',
|
|
35
|
+
o: '#ffbe50',
|
|
36
|
+
p: '#0078D7',
|
|
37
|
+
q: '#8764B8',
|
|
38
|
+
r: '#52dd64',
|
|
39
|
+
s: '#7edce9',
|
|
40
|
+
t: '#dadd5d',
|
|
41
|
+
u: '#e9b55d',
|
|
42
|
+
v: '#99d669',
|
|
43
|
+
w: '#a3c83a',
|
|
44
|
+
x: '#f28d67',
|
|
45
|
+
y: '#ea82ec',
|
|
46
|
+
z: '#ff8295',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function matchInitialToColor(anyString?: string) {
|
|
50
|
+
if (!anyString) return '#999999'
|
|
51
|
+
return letterToColorMap[anyString.toLowerCase().charAt(0)] || '#999999'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function waitFor(ms) {
|
|
55
|
+
return new Promise<void>((resolve) => {
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
resolve()
|
|
58
|
+
}, ms)
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type ParseSourceUrlArg = {
|
|
63
|
+
source?: string
|
|
64
|
+
src?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parseSourceUrl(args: string, Settings?: any): string
|
|
68
|
+
export function parseSourceUrl(
|
|
69
|
+
args: ParseSourceUrlArg,
|
|
70
|
+
Settings?: any
|
|
71
|
+
): string
|
|
72
|
+
export function parseSourceUrl(
|
|
73
|
+
args: ParseSourceUrlArg | string,
|
|
74
|
+
Settings?: any,
|
|
75
|
+
): string {
|
|
76
|
+
if (!args) return null
|
|
77
|
+
|
|
78
|
+
let res = ''
|
|
79
|
+
let address = ''
|
|
80
|
+
if (typeof args === 'string') {
|
|
81
|
+
address = args
|
|
82
|
+
} else {
|
|
83
|
+
address = args.source || args.src || ''
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (address && address.startsWith('/media/')) {
|
|
87
|
+
const tmp = address.substr(1, address.length)
|
|
88
|
+
res = `${Settings.BaseURL}${tmp}`
|
|
89
|
+
} else if (address) {
|
|
90
|
+
res = address
|
|
91
|
+
} else {
|
|
92
|
+
res = `https://picsum.photos/600?random=${Math.random() * 100}`
|
|
93
|
+
}
|
|
94
|
+
return res
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getNestedStylesByKey<T extends StylesOf<any>>(match:string, variantStyles: T) {
|
|
98
|
+
const styles = {}
|
|
99
|
+
|
|
100
|
+
for (const [key, value] of Object.entries(variantStyles)) {
|
|
101
|
+
|
|
102
|
+
if (key.startsWith(match)) {
|
|
103
|
+
const partName = capitalize(key.replace(match, ''), true)
|
|
104
|
+
styles[partName] = value
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return styles
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function hasFastRefreshed(Settings: any) {
|
|
112
|
+
if (Settings?.Environment?.InitTime) {
|
|
113
|
+
const timeFromStartup = (new Date()).getTime() - Settings.Environment.InitTime.getTime()
|
|
114
|
+
// It usually takes less than a seconds (~300ms) from app launch to running this, so if's been more than that we've probably fast refreshed
|
|
115
|
+
const fastRefreshed = Settings.Environment.IsDev && timeFromStartup > 1000
|
|
116
|
+
return fastRefreshed
|
|
117
|
+
} else {
|
|
118
|
+
console.log('hasFreshRefreshed() => Missing datetime from settings, please include to make this work')
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const throttleTimerId = []
|
|
124
|
+
|
|
125
|
+
export function throttle(func, ref, delay) {
|
|
126
|
+
if (throttleTimerId[ref]) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
throttleTimerId[ref] = setTimeout(function () {
|
|
131
|
+
func()
|
|
132
|
+
throttleTimerId[ref] = undefined
|
|
133
|
+
}, delay)
|
|
134
|
+
}
|
package/src/object.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { FunctionType, AppSettings } from '@codeleap/types'
|
|
2
|
+
|
|
3
|
+
export function deepMerge(base = {}, changes = {}): any {
|
|
4
|
+
const obj = {
|
|
5
|
+
...base,
|
|
6
|
+
}
|
|
7
|
+
let changeEntries = []
|
|
8
|
+
try {
|
|
9
|
+
changeEntries = Object.entries(changes)
|
|
10
|
+
} catch (e) {
|
|
11
|
+
return changes
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (const [key, value] of changeEntries) {
|
|
15
|
+
obj[key] =
|
|
16
|
+
typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)
|
|
17
|
+
? deepMerge(obj[key], changes[key])
|
|
18
|
+
: value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return obj
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function mapObject<T>(
|
|
25
|
+
obj: T,
|
|
26
|
+
callback: FunctionType<[[keyof T, T[keyof T]]], any>,
|
|
27
|
+
) {
|
|
28
|
+
return Object.entries(obj).map((args) => callback(args as [keyof T, T[keyof T]]),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const deepSet = ([path, value]) => {
|
|
33
|
+
const parts = path.split('.')
|
|
34
|
+
const newObj = Array.isArray(value) ? [] : {}
|
|
35
|
+
|
|
36
|
+
if (parts.length === 1) {
|
|
37
|
+
newObj[parts[0]] = value
|
|
38
|
+
} else {
|
|
39
|
+
newObj[parts[0]] = deepSet([parts.slice(1).join('.'), value])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return newObj
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const deepGet = (path, obj) => {
|
|
46
|
+
const parts = path.split('.')
|
|
47
|
+
let newObj = { ...obj }
|
|
48
|
+
|
|
49
|
+
for (const prop of parts) {
|
|
50
|
+
newObj = newObj[prop]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return newObj
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function objectPaths(obj) {
|
|
57
|
+
let paths = []
|
|
58
|
+
|
|
59
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
60
|
+
if (!Array.isArray(value) && typeof value === 'object') {
|
|
61
|
+
paths = [...paths, ...objectPaths(value).map((k) => `${key}.${k}`)]
|
|
62
|
+
} else {
|
|
63
|
+
paths.push(key)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return paths
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isValuePrimitive(a:any) {
|
|
71
|
+
return ['string', 'number', 'boolean'].includes(typeof a)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function optionalObject(condition: boolean, ifTrue: any, ifFalse: any) {
|
|
75
|
+
return condition ? ifTrue : ifFalse
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type TraverseRecArgs = {path:string[]; value: any; depth: number; key: string; type: string; primitive: boolean}
|
|
79
|
+
|
|
80
|
+
type TraverseCallback = (args?: TraverseRecArgs) => {stop?: boolean } | void
|
|
81
|
+
|
|
82
|
+
export function traverse(obj = {}, callback:TraverseCallback, args?: TraverseRecArgs) {
|
|
83
|
+
const isPrimitive = isValuePrimitive(obj)
|
|
84
|
+
|
|
85
|
+
const info = {
|
|
86
|
+
path: [],
|
|
87
|
+
depth: 0,
|
|
88
|
+
key: '',
|
|
89
|
+
type: typeof obj,
|
|
90
|
+
value: obj,
|
|
91
|
+
primitive: isPrimitive,
|
|
92
|
+
...args,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isPrimitive) {
|
|
96
|
+
callback({
|
|
97
|
+
...info,
|
|
98
|
+
depth: info.depth,
|
|
99
|
+
|
|
100
|
+
})
|
|
101
|
+
} else {
|
|
102
|
+
for (const [key, value] of Object.entries(obj || {})) {
|
|
103
|
+
const isPrimitive = isValuePrimitive(value)
|
|
104
|
+
|
|
105
|
+
if (!isPrimitive) {
|
|
106
|
+
|
|
107
|
+
callback({
|
|
108
|
+
...info,
|
|
109
|
+
key,
|
|
110
|
+
value,
|
|
111
|
+
type: typeof value,
|
|
112
|
+
primitive: isPrimitive,
|
|
113
|
+
path: [...info.path, key],
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
traverse(value, callback, {
|
|
118
|
+
...info,
|
|
119
|
+
key,
|
|
120
|
+
value,
|
|
121
|
+
type: typeof value,
|
|
122
|
+
primitive: isValuePrimitive(value),
|
|
123
|
+
path: [...info.path, key],
|
|
124
|
+
depth: info.depth + 1,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function createSettings<T extends AppSettings>(a:T): T {
|
|
131
|
+
return a
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function extractKey(obj:any) {
|
|
135
|
+
if (obj.id) {
|
|
136
|
+
return obj.id
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function objectPickBy<K extends string = string, P = any>(obj: Record<K, P>, predicate: (valueKey: P, key: K) => boolean) {
|
|
141
|
+
const result = {} as Record<K, P>
|
|
142
|
+
|
|
143
|
+
for (const key in obj) {
|
|
144
|
+
if (obj?.hasOwnProperty?.(key) && predicate?.(obj?.[key], key)) {
|
|
145
|
+
result[key] = obj?.[key]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function transformObject<K extends string = string, T extends string = string>(obj: Record<K, T>, predicate: (value: T, key: K) => [K, T]): Record<string, any> {
|
|
153
|
+
const result = {}
|
|
154
|
+
|
|
155
|
+
for (const key in obj) {
|
|
156
|
+
const [newKey, newValue] = predicate?.(obj?.[key], key)
|
|
157
|
+
|
|
158
|
+
result[newKey as string] = newValue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
}
|
package/src/react.tsx
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import equals from 'deep-equal'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { TypeGuards } from '@codeleap/types'
|
|
4
|
+
|
|
5
|
+
export const deepEqual = equals
|
|
6
|
+
|
|
7
|
+
type ArePropsEqualOptions<MergedObject> = {
|
|
8
|
+
check: (keyof MergedObject)[]
|
|
9
|
+
excludeKeys?: string[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function arePropsEqual<A, B>(
|
|
13
|
+
previous: A,
|
|
14
|
+
next: B,
|
|
15
|
+
options: ArePropsEqualOptions<A & B>,
|
|
16
|
+
) {
|
|
17
|
+
const { check, excludeKeys = [] } = options
|
|
18
|
+
|
|
19
|
+
for (const c of check) {
|
|
20
|
+
const nextItem = next[c as string]
|
|
21
|
+
const prevItem = previous[c as string]
|
|
22
|
+
|
|
23
|
+
for (const key of excludeKeys) {
|
|
24
|
+
if (nextItem?.[key]) delete nextItem[key]
|
|
25
|
+
if (prevItem?.[key]) delete prevItem[key]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const propsAreEqual = equals(nextItem, prevItem)
|
|
29
|
+
|
|
30
|
+
if (!propsAreEqual) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const flattenChildren = (children, flat = []) => {
|
|
39
|
+
flat = [...flat, ...React.Children.toArray(children)]
|
|
40
|
+
|
|
41
|
+
if (children.props && children.props.children) {
|
|
42
|
+
return flattenChildren(children.props.children, flat)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return flat
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const simplifyChildren = children => {
|
|
49
|
+
const flat = flattenChildren(children)
|
|
50
|
+
|
|
51
|
+
return flat.map(
|
|
52
|
+
({
|
|
53
|
+
key,
|
|
54
|
+
ref,
|
|
55
|
+
type,
|
|
56
|
+
props: {
|
|
57
|
+
children,
|
|
58
|
+
...props
|
|
59
|
+
},
|
|
60
|
+
}) => ({
|
|
61
|
+
key, ref, type, props,
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getRenderedComponent<P = any>(
|
|
67
|
+
ComponentOrProps: React.ComponentType<P> | P | React.ReactNode | null | undefined,
|
|
68
|
+
DefaultComponent: React.ComponentType<P>,
|
|
69
|
+
props?: P,
|
|
70
|
+
): React.ReactNode {
|
|
71
|
+
if (TypeGuards.isNil(ComponentOrProps) || Object.keys(ComponentOrProps).length === 0) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (TypeGuards.isFunction(ComponentOrProps)) {
|
|
76
|
+
return <ComponentOrProps {...props} />
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (React.isValidElement(ComponentOrProps)) {
|
|
80
|
+
return ComponentOrProps
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const _props = ComponentOrProps as P
|
|
84
|
+
|
|
85
|
+
return <DefaultComponent {...props} {..._props} />
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function memoize<P extends object>(ComponentToMemoize: React.FunctionComponent<P>): React.NamedExoticComponent<P> {
|
|
89
|
+
return React.memo(ComponentToMemoize, () => true)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function memoChecker<P>(prop: keyof P, prevProps: P, nextProps: P): boolean {
|
|
93
|
+
const nextItem = nextProps[prop]
|
|
94
|
+
const prevItem = prevProps[prop]
|
|
95
|
+
|
|
96
|
+
return equals(nextItem, prevItem)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function memoBy<P extends object>(
|
|
100
|
+
ComponentToMemoize: React.FunctionComponent<P>,
|
|
101
|
+
check: keyof P | Array<keyof P>
|
|
102
|
+
): React.NamedExoticComponent<P> {
|
|
103
|
+
return React.memo(ComponentToMemoize, (prevProps, nextProps) => {
|
|
104
|
+
const checks = Array.isArray(check) ? check : [check]
|
|
105
|
+
return checks.every((key) => memoChecker(key, prevProps, nextProps))
|
|
106
|
+
})
|
|
107
|
+
}
|
package/src/string.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function singleLine(text: string) {
|
|
2
|
+
return text?.replace(/\n/g, ' ')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function stringiparse(string) {
|
|
6
|
+
return JSON.parse(JSON.stringify(string))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function capitalize(str: string, reverse = false) {
|
|
10
|
+
if (!str.length) return str
|
|
11
|
+
const firstChar = reverse ? str[0].toLowerCase() : str[0].toUpperCase()
|
|
12
|
+
return firstChar + str.substring(1)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isUppercase(char: string) {
|
|
16
|
+
return /[A-Z]|[\u0080-\u024F]/.test(char) && char.toUpperCase() === char
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isLowercase(char: string) {
|
|
20
|
+
return !isUppercase(char)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function humanizeCamelCase(str: string) {
|
|
24
|
+
const characters = []
|
|
25
|
+
let previousCharacter = ''
|
|
26
|
+
str.split('').forEach((char, idx) => {
|
|
27
|
+
if (idx === 0) {
|
|
28
|
+
characters.push(char.toUpperCase())
|
|
29
|
+
} else {
|
|
30
|
+
if (isUppercase(char) && isLowercase(previousCharacter)) {
|
|
31
|
+
characters.push(` ${char}`)
|
|
32
|
+
} else {
|
|
33
|
+
characters.push(char)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
previousCharacter = char
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return characters.join('')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ellipsis(str: string, maxLen: number) {
|
|
44
|
+
if (str.length - 3 > maxLen) {
|
|
45
|
+
return str.slice(0, maxLen - 3) + '...'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return str
|
|
49
|
+
}
|