/
useKeyRecords.ts
130 lines (106 loc) · 3.29 KB
/
useKeyRecords.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
import * as React from 'react';
import { useRef, useCallback } from 'react';
import warning from 'rc-util/lib/warning';
import { nextSlice } from '../utils/timeUtil';
import { MenuKey } from '@/interface';
const PATH_SPLIT = '__RC_UTIL_PATH_SPLIT__';
const getPathStr = (keyPath: string[]) => keyPath.join(PATH_SPLIT);
const getPathKeys = (keyPathStr: string) => keyPathStr.split(PATH_SPLIT);
export const OVERFLOW_KEY = 'rc-menu-more';
export default function useKeyRecords() {
const [, internalForceUpdate] = React.useState({});
const key2pathRef = useRef(new Map<MenuKey, string>());
const path2keyRef = useRef(new Map<MenuKey, string>());
const [overflowKeys, setOverflowKeys] = React.useState([]);
const updateRef = useRef(0);
const destroyRef = useRef(false);
const forceUpdate = () => {
if (!destroyRef.current) {
internalForceUpdate({});
}
};
const registerPath = useCallback((key: string, keyPath: string[]) => {
// Warning for invalidate or duplicated `key`
if (process.env.NODE_ENV !== 'production') {
warning(
!key2pathRef.current.has(key),
`Duplicated key '${key}' used in Menu by path [${keyPath.join(' > ')}]`,
);
}
// Fill map
const connectedPath = getPathStr(keyPath);
path2keyRef.current.set(connectedPath, key);
key2pathRef.current.set(key, connectedPath);
updateRef.current += 1;
const id = updateRef.current;
nextSlice(() => {
if (id === updateRef.current) {
forceUpdate();
}
});
}, []);
const unregisterPath = useCallback((key: string, keyPath: string[]) => {
const connectedPath = getPathStr(keyPath);
path2keyRef.current.delete(connectedPath);
key2pathRef.current.delete(key);
}, []);
const refreshOverflowKeys = useCallback((keys: string[]) => {
setOverflowKeys(keys);
}, []);
const getKeyPath = useCallback(
(eventKey: string, includeOverflow?: boolean) => {
const fullPath = key2pathRef.current.get(eventKey) || '';
const keys = getPathKeys(fullPath);
if (includeOverflow && overflowKeys.includes(keys[0])) {
keys.unshift(OVERFLOW_KEY);
}
return keys;
},
[overflowKeys],
);
const isSubPathKey = useCallback(
(pathKeys: string[], eventKey: string) =>
pathKeys.some(pathKey => {
const pathKeyList = getKeyPath(pathKey, true);
return pathKeyList.includes(eventKey);
}),
[getKeyPath],
);
const getKeys = () => {
const keys = [...key2pathRef.current.keys()];
if (overflowKeys.length) {
keys.push(OVERFLOW_KEY);
}
return keys;
};
/**
* Find current key related child path keys
*/
const getSubPathKeys = useCallback((key: MenuKey): Set<MenuKey> => {
const connectedPath = `${key2pathRef.current.get(key)}${PATH_SPLIT}`;
const pathKeys = new Set<string>();
[...path2keyRef.current.keys()].forEach(pathKey => {
if (`${pathKey}`.startsWith(connectedPath)) {
pathKeys.add(path2keyRef.current.get(pathKey));
}
});
return pathKeys;
}, []);
React.useEffect(
() => () => {
destroyRef.current = true;
},
[],
);
return {
// Register
registerPath,
unregisterPath,
refreshOverflowKeys,
// Util
isSubPathKey,
getKeyPath,
getKeys,
getSubPathKeys,
};
}