@pawover/kit 0.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/enums/index.js +20 -0
- package/dist/hooks/react/index.js +5 -0
- package/dist/hooks/react/useCreation.js +11 -0
- package/dist/hooks/react/useLatest.js +6 -0
- package/dist/hooks/react/useMount.js +29 -0
- package/dist/hooks/react/useResponsive.js +59 -0
- package/dist/hooks/react/useUnmount.js +17 -0
- package/dist/index.js +2 -0
- package/dist/types/enums/index.d.ts +20 -0
- package/dist/types/hooks/react/index.d.ts +5 -0
- package/dist/types/hooks/react/useCreation.d.ts +2 -0
- package/dist/types/hooks/react/useLatest.d.ts +1 -0
- package/dist/types/hooks/react/useMount.d.ts +11 -0
- package/dist/types/hooks/react/useResponsive.d.ts +16 -0
- package/dist/types/hooks/react/useUnmount.d.ts +6 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/utils/array.d.ts +76 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/object.d.ts +53 -0
- package/dist/types/utils/string.d.ts +13 -0
- package/dist/types/utils/to.d.ts +5 -0
- package/dist/types/utils/tree/index.d.ts +6 -0
- package/dist/types/utils/tree/rowsToTree.d.ts +10 -0
- package/dist/types/utils/tree/treeFilter.d.ts +6 -0
- package/dist/types/utils/tree/treeFind.d.ts +8 -0
- package/dist/types/utils/tree/treeForEach.d.ts +5 -0
- package/dist/types/utils/tree/treeMap.d.ts +6 -0
- package/dist/types/utils/tree/treeToRows.d.ts +9 -0
- package/dist/types/utils/tree/types.d.ts +24 -0
- package/dist/types/utils/typeof.d.ts +29 -0
- package/dist/utils/array.js +196 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/object.js +138 -0
- package/dist/utils/string.js +70 -0
- package/dist/utils/to.js +16 -0
- package/dist/utils/tree/index.js +6 -0
- package/dist/utils/tree/rowsToTree.js +35 -0
- package/dist/utils/tree/treeFilter.js +92 -0
- package/dist/utils/tree/treeFind.js +82 -0
- package/dist/utils/tree/treeForEach.js +60 -0
- package/dist/utils/tree/treeMap.js +79 -0
- package/dist/utils/tree/treeToRows.js +13 -0
- package/dist/utils/tree/types.js +10 -0
- package/dist/utils/typeof.js +114 -0
- package/package.json +90 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { isArray, isFunction } from "./typeof";
|
|
2
|
+
export function toArray(candidate) {
|
|
3
|
+
if (candidate === undefined || candidate === false) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
return isArray(candidate) ? candidate : [candidate];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 获取数组第一项
|
|
10
|
+
*
|
|
11
|
+
* @param initialList 初始数组
|
|
12
|
+
* @param saveValue 安全值
|
|
13
|
+
*/
|
|
14
|
+
export function arrayFirst(initialList, saveValue) {
|
|
15
|
+
if (!isArray(initialList) || initialList.length === 0) {
|
|
16
|
+
return saveValue;
|
|
17
|
+
}
|
|
18
|
+
return initialList[0];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 获取数组最后一项
|
|
22
|
+
*
|
|
23
|
+
* @param initialList 初始数组
|
|
24
|
+
* @param saveValue 安全值
|
|
25
|
+
*/
|
|
26
|
+
export function arrayLast(initialList, saveValue) {
|
|
27
|
+
if (!isArray(initialList) || initialList.length === 0) {
|
|
28
|
+
return saveValue;
|
|
29
|
+
}
|
|
30
|
+
return initialList[initialList.length - 1];
|
|
31
|
+
}
|
|
32
|
+
export function arrayDiff(initialList, diffList, match) {
|
|
33
|
+
if (!isArray(initialList) && !isArray(diffList)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
if (!isArray(initialList) || !initialList.length) {
|
|
37
|
+
return [...diffList];
|
|
38
|
+
}
|
|
39
|
+
if (!isArray(diffList) || !diffList.length) {
|
|
40
|
+
return [...initialList];
|
|
41
|
+
}
|
|
42
|
+
if (!isFunction(match)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const keys = diffList.reduce((prev, curr) => {
|
|
46
|
+
prev[match(curr)] = true;
|
|
47
|
+
return prev;
|
|
48
|
+
}, {});
|
|
49
|
+
return initialList.filter((a) => !keys[match(a)]);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 数组竞争
|
|
53
|
+
* - 返回在匹配函数的比较条件中获胜的最终项目,适用于更复杂的最小值/最大值计算
|
|
54
|
+
*
|
|
55
|
+
* @param initialList 数组
|
|
56
|
+
* @param match 匹配函数
|
|
57
|
+
*/
|
|
58
|
+
export function arrayCompete(initialList, match) {
|
|
59
|
+
if (!isArray(initialList) || initialList.length === 0 || !isFunction(match)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return initialList.reduce(match);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 统计数组的项目出现次数
|
|
66
|
+
* - 通过给定的标识符匹配函数,返回一个对象,其中键是回调函数返回的 key 值,每个值是一个整数,表示该 key 出现的次数
|
|
67
|
+
*
|
|
68
|
+
* @param initialList 初始数组
|
|
69
|
+
* @param match 标识符匹配函数
|
|
70
|
+
*/
|
|
71
|
+
export function arrayCounting(initialList, match) {
|
|
72
|
+
if (!isArray(initialList) || !isFunction(match)) {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
return initialList.reduce((prev, curr) => {
|
|
76
|
+
const id = match(curr).toString();
|
|
77
|
+
prev[id] = (prev[id] ?? 0) + 1;
|
|
78
|
+
return prev;
|
|
79
|
+
}, {});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 数组项替换
|
|
83
|
+
* - 在给定的数组中,替换符合匹配函数结果的项目。只替换第一个匹配项。始终返回原始数组的副本。
|
|
84
|
+
*
|
|
85
|
+
* @param initialList 初始数组
|
|
86
|
+
* @param newItem 替换项
|
|
87
|
+
* @param match 匹配函数
|
|
88
|
+
*/
|
|
89
|
+
export function arrayReplace(initialList, newItem, match) {
|
|
90
|
+
if (!initialList) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
if (newItem === undefined || !isFunction(match)) {
|
|
94
|
+
return [...initialList];
|
|
95
|
+
}
|
|
96
|
+
for (let i = 0; i < initialList.length; i++) {
|
|
97
|
+
const item = initialList[i];
|
|
98
|
+
if (item !== undefined && match(item, i)) {
|
|
99
|
+
return [...initialList.slice(0, i), newItem, ...initialList.slice(i + 1, initialList.length)];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...initialList];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 数组选择
|
|
106
|
+
* - 一次性应用 `filter` 和 `map` 操作
|
|
107
|
+
*
|
|
108
|
+
* @param initialList 初始数组
|
|
109
|
+
* @param filter filter 函数
|
|
110
|
+
* @param mapper map 函数
|
|
111
|
+
*/
|
|
112
|
+
export function arrayPick(initialList, filter, mapper) {
|
|
113
|
+
if (!isArray(initialList)) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
if (!isFunction(filter)) {
|
|
117
|
+
return initialList;
|
|
118
|
+
}
|
|
119
|
+
const hasMapper = isFunction(mapper);
|
|
120
|
+
return initialList.reduce((prev, curr, index) => {
|
|
121
|
+
if (!filter(curr, index)) {
|
|
122
|
+
return prev;
|
|
123
|
+
}
|
|
124
|
+
if (hasMapper) {
|
|
125
|
+
prev.push(mapper(curr, index));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
prev.push(curr);
|
|
129
|
+
}
|
|
130
|
+
return prev;
|
|
131
|
+
}, []);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 数组切分
|
|
135
|
+
* - 将数组以指定的长度切分后,组合在高维数组中
|
|
136
|
+
*
|
|
137
|
+
* @param initialList 初始数组
|
|
138
|
+
* @param size 分割尺寸,默认 `10`
|
|
139
|
+
*/
|
|
140
|
+
export function arraySplit(initialList, size = 10) {
|
|
141
|
+
if (!isArray(initialList)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const count = Math.ceil(initialList.length / size);
|
|
145
|
+
return Array.from({ length: count })
|
|
146
|
+
.fill(null)
|
|
147
|
+
.map((_c, i) => {
|
|
148
|
+
return initialList.slice(i * size, i * size + size);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 数组迭代
|
|
153
|
+
* - 运行一个函数 n 次以生成一个值,迭代函数将作为还原器运行无数次,然后返回累积值
|
|
154
|
+
*
|
|
155
|
+
* @param count 迭代次数
|
|
156
|
+
* @param iterate 迭代函数
|
|
157
|
+
* @param initialValue 初始值
|
|
158
|
+
*/
|
|
159
|
+
export function arrayIterate(count, iterate, initialValue) {
|
|
160
|
+
let result = initialValue;
|
|
161
|
+
if (isFunction(iterate)) {
|
|
162
|
+
for (let i = 1; i <= count; i++) {
|
|
163
|
+
result = iterate(result, i);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 数组合并
|
|
170
|
+
* - 通过给定的标识符匹配函数,用第二个数组中的匹配项替换第一个数组中匹配项的所有内容
|
|
171
|
+
*
|
|
172
|
+
* @param initialList 初始数组
|
|
173
|
+
* @param mergeList 待合并数组
|
|
174
|
+
* @param match 标识符匹配函数
|
|
175
|
+
*/
|
|
176
|
+
export function arrayMerge(initialList, mergeList, match) {
|
|
177
|
+
if (!isArray(initialList)) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
if (!isArray(mergeList)) {
|
|
181
|
+
return initialList;
|
|
182
|
+
}
|
|
183
|
+
if (!isFunction(match)) {
|
|
184
|
+
return initialList;
|
|
185
|
+
}
|
|
186
|
+
return initialList.reduce((prev, curr) => {
|
|
187
|
+
const matched = mergeList.find((list) => match(curr) === match(list));
|
|
188
|
+
if (matched) {
|
|
189
|
+
prev.push(matched);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
prev.push(curr);
|
|
193
|
+
}
|
|
194
|
+
return prev;
|
|
195
|
+
}, []);
|
|
196
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { isArray, isObject } from "./typeof";
|
|
2
|
+
/**
|
|
3
|
+
* 返回对象的可枚举属性和方法的名称
|
|
4
|
+
* - `Object.keys` 始终返回 `string[]` 类型,此函数可以返回具体类型
|
|
5
|
+
*
|
|
6
|
+
* @param obj 对象
|
|
7
|
+
* @returns 对象所有可枚举的属性的键名
|
|
8
|
+
*/
|
|
9
|
+
export function objectKeys(obj) {
|
|
10
|
+
return Object.keys(obj);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 返回对象的可枚举属性的值数组
|
|
14
|
+
*
|
|
15
|
+
* @param obj 对象
|
|
16
|
+
*/
|
|
17
|
+
export function objectValues(obj) {
|
|
18
|
+
return Object.values(obj);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 返回对象的可枚举属性的键/值数组
|
|
22
|
+
*
|
|
23
|
+
* @param obj 对象
|
|
24
|
+
*/
|
|
25
|
+
export function objectEntries(obj) {
|
|
26
|
+
return Object.entries(obj);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 对象反转
|
|
30
|
+
* - 返回交换了对象的可枚举属性的值/键对象
|
|
31
|
+
*
|
|
32
|
+
* @param obj 对象
|
|
33
|
+
*/
|
|
34
|
+
export function objectSwitch(obj) {
|
|
35
|
+
const result = {};
|
|
36
|
+
if (!isObject(obj)) {
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
for (const [k, v] of objectEntries(obj)) {
|
|
40
|
+
result[v] = k;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 对象合并
|
|
46
|
+
* - 将两个对象递归合并为一个新对象,从右到左依次使用数值
|
|
47
|
+
* - 递归只适用于子对象属性
|
|
48
|
+
*/
|
|
49
|
+
export function objectAssign(obj, overrideObj) {
|
|
50
|
+
const result = {};
|
|
51
|
+
if (!isObject(obj) && !isObject(overrideObj)) {
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
if (!isObject(obj) || !isObject(overrideObj)) {
|
|
55
|
+
return obj ?? overrideObj ?? result;
|
|
56
|
+
}
|
|
57
|
+
return Object.entries({ ...obj, ...overrideObj }).reduce((prev, [key, value]) => {
|
|
58
|
+
return {
|
|
59
|
+
...prev,
|
|
60
|
+
[key]: (() => {
|
|
61
|
+
if (isObject(obj[key])) {
|
|
62
|
+
return objectAssign(obj[key], value);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
})(),
|
|
66
|
+
};
|
|
67
|
+
}, result);
|
|
68
|
+
}
|
|
69
|
+
export function objectPick(obj, keys) {
|
|
70
|
+
const result = {};
|
|
71
|
+
if (!isObject(obj)) {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
if (!isArray(keys)) {
|
|
75
|
+
return obj;
|
|
76
|
+
}
|
|
77
|
+
return keys.reduce((prev, curr) => {
|
|
78
|
+
if (curr in obj) {
|
|
79
|
+
prev[curr] = obj[curr];
|
|
80
|
+
}
|
|
81
|
+
return prev;
|
|
82
|
+
}, result);
|
|
83
|
+
}
|
|
84
|
+
function enumType(enumeration) {
|
|
85
|
+
if (!isObject(enumeration)) {
|
|
86
|
+
throw Error(`function enumKeys expected parameter is a enum, but got ${typeof enumeration}`);
|
|
87
|
+
}
|
|
88
|
+
if (!objectKeys(enumeration).length) {
|
|
89
|
+
throw Error("Enum requires at least one member");
|
|
90
|
+
}
|
|
91
|
+
return enumeration;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 获取枚举所有属性的键
|
|
95
|
+
*
|
|
96
|
+
* @param enumeration 枚举
|
|
97
|
+
*/
|
|
98
|
+
export function enumKeys(enumeration) {
|
|
99
|
+
const e = enumType(enumeration);
|
|
100
|
+
const keys = objectKeys(e);
|
|
101
|
+
const values = objectValues(e);
|
|
102
|
+
const isTwoWayEnum = keys.every((k) => values.some((v) => `${v}` === k));
|
|
103
|
+
if (isTwoWayEnum) {
|
|
104
|
+
return keys.splice(keys.length / 2, keys.length / 2);
|
|
105
|
+
}
|
|
106
|
+
return keys;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 获取枚举所有属性的值
|
|
110
|
+
*
|
|
111
|
+
* @param enumeration 枚举
|
|
112
|
+
*/
|
|
113
|
+
export function enumValues(enumeration) {
|
|
114
|
+
const e = enumType(enumeration);
|
|
115
|
+
const keys = objectKeys(e);
|
|
116
|
+
const values = objectValues(e);
|
|
117
|
+
const isTwoWayEnum = keys.every((k) => values.some((v) => `${v}` === k));
|
|
118
|
+
if (isTwoWayEnum) {
|
|
119
|
+
return values.splice(keys.length / 2, keys.length / 2);
|
|
120
|
+
}
|
|
121
|
+
return values;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 返回枚举的属性的键/值数组
|
|
125
|
+
*
|
|
126
|
+
* @param enumeration 枚举
|
|
127
|
+
*/
|
|
128
|
+
export function enumEntries(enumeration) {
|
|
129
|
+
const e = enumType(enumeration);
|
|
130
|
+
const keys = objectKeys(e);
|
|
131
|
+
const values = objectValues(e);
|
|
132
|
+
const entries = objectEntries(e);
|
|
133
|
+
const isTwoWayEnum = keys.every((k) => values.some((v) => `${v}` === k));
|
|
134
|
+
if (isTwoWayEnum) {
|
|
135
|
+
return entries.splice(keys.length / 2, keys.length / 2);
|
|
136
|
+
}
|
|
137
|
+
return entries;
|
|
138
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isString } from "./typeof";
|
|
2
|
+
/**
|
|
3
|
+
* 转义特殊字符
|
|
4
|
+
*
|
|
5
|
+
* @link https://github.com/sindresorhus/escape-string-regexp
|
|
6
|
+
* @param value 字符串
|
|
7
|
+
*/
|
|
8
|
+
export function escapeStringRegexp(value) {
|
|
9
|
+
return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 首字母大小写
|
|
13
|
+
*/
|
|
14
|
+
export function stringInitialCase(value, type) {
|
|
15
|
+
if (!isString(value) || value.length === 0) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
const m1 = /\S+/g;
|
|
19
|
+
const m2 = /[^a-zA-Z\u00C0-\u017F]/;
|
|
20
|
+
return value.replace(m1, (word) => {
|
|
21
|
+
// 单词含非字母字符(如.,'-等)→ 原样保留
|
|
22
|
+
if (m2.test(word)) {
|
|
23
|
+
return word;
|
|
24
|
+
}
|
|
25
|
+
// 纯字母且全大写 → 保留
|
|
26
|
+
if (word === word.toLocaleUpperCase()) {
|
|
27
|
+
return word;
|
|
28
|
+
}
|
|
29
|
+
// 纯字母且非全大写 → 首字母小写,其余保留
|
|
30
|
+
if (type === "lower" && word[0]) {
|
|
31
|
+
return word[0].toLocaleLowerCase() + word.slice(1);
|
|
32
|
+
}
|
|
33
|
+
// 纯字母且非全大写 → 首字母大写写,其余保留
|
|
34
|
+
if (type === "upper" && word[0]) {
|
|
35
|
+
return word[0].toLocaleUpperCase() + word.slice(1);
|
|
36
|
+
}
|
|
37
|
+
return word;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function stringToJson(data, safeValue) {
|
|
41
|
+
if (isString(data) && data) {
|
|
42
|
+
try {
|
|
43
|
+
const value = JSON.parse(data);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return safeValue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return safeValue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function stringToValues(data, valueType = "number") {
|
|
55
|
+
if (isString(data) && data) {
|
|
56
|
+
try {
|
|
57
|
+
const values = data.split(",");
|
|
58
|
+
if (valueType === "number") {
|
|
59
|
+
return values.map((d) => Number(d));
|
|
60
|
+
}
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/utils/to.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param promise
|
|
3
|
+
* @param errorExt - 可以传递给err对象的其他信息
|
|
4
|
+
*/
|
|
5
|
+
export function to(promise, errorExt) {
|
|
6
|
+
return promise
|
|
7
|
+
.then((data) => [null, data])
|
|
8
|
+
.catch((err) => {
|
|
9
|
+
if (errorExt) {
|
|
10
|
+
const parsedError = { ...err, ...errorExt };
|
|
11
|
+
return [parsedError, undefined];
|
|
12
|
+
}
|
|
13
|
+
const defaultError = err ? err : new Error("defaultError");
|
|
14
|
+
return [defaultError, undefined];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isNull, isUndefined } from "../typeof";
|
|
2
|
+
/**
|
|
3
|
+
* 行结构 转 树结构
|
|
4
|
+
*/
|
|
5
|
+
export function rowsToTree(rows, options) {
|
|
6
|
+
const { parentIdKey = "parentId", rowKey = "id", childrenKey = "children" } = options || {};
|
|
7
|
+
const result = [];
|
|
8
|
+
const map = new Map();
|
|
9
|
+
for (const row of rows) {
|
|
10
|
+
const id = row[rowKey];
|
|
11
|
+
if (!map.get(id)) {
|
|
12
|
+
map.set(id, row);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
for (const row of rows) {
|
|
16
|
+
const parentId = row[parentIdKey];
|
|
17
|
+
const parent = map.get(parentId);
|
|
18
|
+
if (!parent || !parentId) {
|
|
19
|
+
result.push(row);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const siblings = parent[childrenKey];
|
|
23
|
+
if (isNull(siblings) || isUndefined(siblings)) {
|
|
24
|
+
parent[childrenKey] = [row];
|
|
25
|
+
}
|
|
26
|
+
else if (Array.isArray(siblings)) {
|
|
27
|
+
siblings.push(row);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const message = `The key "${childrenKey.toString()}" in parent item is not an array.`;
|
|
31
|
+
throw new Error(message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { arrayLast } from "../array";
|
|
2
|
+
import { isArray } from "../typeof";
|
|
3
|
+
import { getFinalChildrenKey } from "./types";
|
|
4
|
+
// 前置遍历
|
|
5
|
+
function preImpl(row, callback, options) {
|
|
6
|
+
const result = callback(row, options);
|
|
7
|
+
if (!result) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
11
|
+
const children = row[finalChildrenKey];
|
|
12
|
+
let newChildren;
|
|
13
|
+
if (isArray(children)) {
|
|
14
|
+
const nextLevelOptions = { ...options, parents: [...options.parents, row], depth: options.depth + 1 };
|
|
15
|
+
newChildren = children.map((c) => preImpl(c, callback, nextLevelOptions)).filter((c) => !!c);
|
|
16
|
+
}
|
|
17
|
+
return { ...row, [finalChildrenKey]: newChildren };
|
|
18
|
+
}
|
|
19
|
+
// 子节点优先遍历
|
|
20
|
+
function postImpl(row, callback, options) {
|
|
21
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
22
|
+
const children = row[finalChildrenKey];
|
|
23
|
+
let newChildren;
|
|
24
|
+
if (isArray(children)) {
|
|
25
|
+
const nextLevelOptions = { ...options, parents: [...options.parents, row], depth: options.depth + 1 };
|
|
26
|
+
newChildren = children.map((c) => preImpl(c, callback, nextLevelOptions)).filter((c) => !!c);
|
|
27
|
+
}
|
|
28
|
+
const result = callback(row, options);
|
|
29
|
+
if (!result) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return { ...row, [finalChildrenKey]: newChildren };
|
|
33
|
+
}
|
|
34
|
+
// 广度优先遍历
|
|
35
|
+
function breadthImpl(row, callback, options) {
|
|
36
|
+
const queue = [{ queueRow: row, queueOptions: options }];
|
|
37
|
+
const resultCache = new WeakMap();
|
|
38
|
+
const newNodeCache = new WeakMap();
|
|
39
|
+
const childrenKeyCache = new WeakMap();
|
|
40
|
+
let result;
|
|
41
|
+
const runQueue = () => {
|
|
42
|
+
if (queue.length === 0) {
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
const { queueRow, queueOptions } = queue.shift();
|
|
46
|
+
const finalChildrenKey = getFinalChildrenKey(queueRow, queueOptions, queueOptions);
|
|
47
|
+
const children = queueRow[finalChildrenKey];
|
|
48
|
+
if (isArray(children)) {
|
|
49
|
+
const nextLevelOptions = { ...queueOptions, parents: [...queueOptions.parents, queueRow], depth: queueOptions.depth + 1 };
|
|
50
|
+
const subQueueItems = children.map((queueRow) => ({ queueRow, queueOptions: nextLevelOptions }));
|
|
51
|
+
queue.push(...subQueueItems);
|
|
52
|
+
}
|
|
53
|
+
const parent = arrayLast(queueOptions.parents);
|
|
54
|
+
const isTopNode = queueOptions.depth === 0;
|
|
55
|
+
const parentResult = parent && resultCache.get(parent);
|
|
56
|
+
if (!isTopNode && !parentResult) {
|
|
57
|
+
return runQueue();
|
|
58
|
+
}
|
|
59
|
+
const callbackResult = callback(queueRow, queueOptions);
|
|
60
|
+
if (isTopNode && !callbackResult) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const newNode = { ...queueRow, [finalChildrenKey]: undefined };
|
|
64
|
+
if (isTopNode) {
|
|
65
|
+
result = newNode;
|
|
66
|
+
}
|
|
67
|
+
resultCache.set(queueRow, callbackResult);
|
|
68
|
+
newNodeCache.set(queueRow, newNode);
|
|
69
|
+
childrenKeyCache.set(queueRow, finalChildrenKey);
|
|
70
|
+
if (callbackResult && parent) {
|
|
71
|
+
const parentNewNode = newNodeCache.get(parent);
|
|
72
|
+
const parentChildrenKey = childrenKeyCache.get(parent);
|
|
73
|
+
if (parentNewNode && parentChildrenKey) {
|
|
74
|
+
if (!parentNewNode[parentChildrenKey]) {
|
|
75
|
+
parentNewNode[parentChildrenKey] = [];
|
|
76
|
+
}
|
|
77
|
+
parentNewNode[parentChildrenKey].push(newNode);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return runQueue();
|
|
81
|
+
};
|
|
82
|
+
return runQueue();
|
|
83
|
+
}
|
|
84
|
+
const strategies = { pre: preImpl, post: postImpl, breadth: breadthImpl };
|
|
85
|
+
export function treeFilter(tree, callback, options = {}) {
|
|
86
|
+
const { childrenKey = "children", strategy = "pre", getChildrenKey } = options;
|
|
87
|
+
const traversalMethod = strategies[strategy];
|
|
88
|
+
const innerOptions = { childrenKey: childrenKey, depth: 0, parents: [], getChildrenKey };
|
|
89
|
+
return isArray(tree)
|
|
90
|
+
? tree.map((row) => traversalMethod(row, callback, innerOptions)).filter((t) => !!t)
|
|
91
|
+
: traversalMethod(tree, callback, innerOptions) || [];
|
|
92
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isArray } from "../typeof";
|
|
2
|
+
import { getFinalChildrenKey } from "./types";
|
|
3
|
+
const strategies = { pre: preImpl, post: postImpl, breadth: breadthImpl };
|
|
4
|
+
// 前置深度优先遍历
|
|
5
|
+
function preImpl(row, callback, options) {
|
|
6
|
+
const callbackResult = callback(row, options);
|
|
7
|
+
if (callbackResult) {
|
|
8
|
+
return row;
|
|
9
|
+
}
|
|
10
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
11
|
+
const children = row[finalChildrenKey];
|
|
12
|
+
if (isArray(children)) {
|
|
13
|
+
for (const child of children) {
|
|
14
|
+
const nextLevelOptions = { ...options, parents: [...options.parents, row], depth: options.depth + 1 };
|
|
15
|
+
const result = preImpl(child, callback, nextLevelOptions);
|
|
16
|
+
if (result) {
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
// 后置深度优先遍历
|
|
24
|
+
function postImpl(row, callback, options) {
|
|
25
|
+
const finalChildrenKey = getFinalChildrenKey(row, options, options);
|
|
26
|
+
const children = row[finalChildrenKey];
|
|
27
|
+
if (isArray(children)) {
|
|
28
|
+
for (const child of children) {
|
|
29
|
+
const nextLevelOptions = { ...options, parents: [...options.parents, row], depth: options.depth + 1 };
|
|
30
|
+
const result = postImpl(child, callback, nextLevelOptions);
|
|
31
|
+
if (result) {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const callbackResult = callback(row, options);
|
|
37
|
+
if (callbackResult) {
|
|
38
|
+
return row;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
// 广度优先遍历
|
|
43
|
+
function breadthImpl(row, callback, options) {
|
|
44
|
+
const queue = [{ queueRow: row, queueOptions: options }];
|
|
45
|
+
const runQueue = () => {
|
|
46
|
+
if (queue.length === 0) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const { queueRow, queueOptions } = queue.shift();
|
|
50
|
+
const finalChildrenKey = getFinalChildrenKey(queueRow, queueOptions, queueOptions);
|
|
51
|
+
const children = queueRow[finalChildrenKey];
|
|
52
|
+
if (isArray(children)) {
|
|
53
|
+
const nextLevelOptions = { ...queueOptions, parents: [...queueOptions.parents, queueRow], depth: queueOptions.depth + 1 };
|
|
54
|
+
const subQueueItems = children.map((queueRow) => ({ queueRow, queueOptions: nextLevelOptions }));
|
|
55
|
+
queue.push(...subQueueItems);
|
|
56
|
+
}
|
|
57
|
+
const callbackResult = callback(queueRow, queueOptions);
|
|
58
|
+
if (callbackResult) {
|
|
59
|
+
return queueRow;
|
|
60
|
+
}
|
|
61
|
+
return runQueue();
|
|
62
|
+
};
|
|
63
|
+
return runQueue();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 查找树节点,找到第一个返回非空值的节点
|
|
67
|
+
*/
|
|
68
|
+
export function treeFind(tree, callback, options = {}) {
|
|
69
|
+
const { childrenKey = "children", strategy = "pre", getChildrenKey } = options;
|
|
70
|
+
const traversalMethod = strategies[strategy];
|
|
71
|
+
const innerOptions = { childrenKey: childrenKey, depth: 0, parents: [], getChildrenKey };
|
|
72
|
+
if (isArray(tree)) {
|
|
73
|
+
for (const row of tree) {
|
|
74
|
+
const result = traversalMethod(row, callback, innerOptions);
|
|
75
|
+
if (result) {
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return traversalMethod(tree, callback, innerOptions);
|
|
82
|
+
}
|