@blocklet/ui-react 2.1.25 → 2.1.28
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/lib/Header/index.js +27 -14
- package/lib/utils.js +55 -3
- package/package.json +4 -4
- package/src/Header/index.js +15 -12
- package/src/utils.js +35 -0
package/lib/Header/index.js
CHANGED
|
@@ -35,6 +35,8 @@ require("@iconify/iconify");
|
|
|
35
35
|
|
|
36
36
|
var _types = require("../types");
|
|
37
37
|
|
|
38
|
+
var _utils = require("../utils");
|
|
39
|
+
|
|
38
40
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
39
41
|
|
|
40
42
|
const _excluded = ["meta", "addons", "sessionManagerProps"];
|
|
@@ -51,18 +53,18 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
|
|
|
51
53
|
|
|
52
54
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
53
55
|
|
|
54
|
-
const muiTheme = (0, _Theme.create)();
|
|
56
|
+
const muiTheme = (0, _Theme.create)(); // blocklet meta 中的 navigation 数据 => NavMenu 组件的 items
|
|
55
57
|
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
58
|
+
const parseNavigation = navigation => {
|
|
59
|
+
if (!(navigation !== null && navigation !== void 0 && navigation.length)) {
|
|
60
|
+
return {
|
|
61
|
+
navItems: [],
|
|
62
|
+
activeId: null
|
|
63
|
+
};
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
}; // blocklet meta 中的 navigation 数据 => NavMenu 组件的 items
|
|
66
|
+
let counter = 1;
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
const parseNavigation = navigation => {
|
|
66
68
|
const parseItem = item => {
|
|
67
69
|
var _item$link, _item$link2;
|
|
68
70
|
|
|
@@ -73,9 +75,10 @@ const parseNavigation = navigation => {
|
|
|
73
75
|
|
|
74
76
|
if (item.items) {
|
|
75
77
|
return {
|
|
78
|
+
id: "".concat(counter++),
|
|
76
79
|
label: item.title,
|
|
77
80
|
icon,
|
|
78
|
-
children: item.items
|
|
81
|
+
children: item.items
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -89,18 +92,24 @@ const parseNavigation = navigation => {
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
return {
|
|
95
|
+
id: "".concat(counter++),
|
|
92
96
|
label: /*#__PURE__*/(0, _jsxRuntime.jsx)("a", _objectSpread(_objectSpread({
|
|
93
97
|
href: item.link
|
|
94
98
|
}, props), {}, {
|
|
95
99
|
children: item.title
|
|
96
100
|
})),
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
icon,
|
|
102
|
+
link: item.link
|
|
99
103
|
};
|
|
100
104
|
};
|
|
101
105
|
|
|
102
|
-
const
|
|
103
|
-
|
|
106
|
+
const navItems = (0, _utils.mapRecursive)(navigation, parseItem, 'items');
|
|
107
|
+
const flattened = (0, _utils.flatRecursive)(navItems);
|
|
108
|
+
const matchedIndex = (0, _utils.matchPaths)(flattened.map(item => item.link));
|
|
109
|
+
return {
|
|
110
|
+
navItems,
|
|
111
|
+
activeId: matchedIndex >= 0 ? flattened[matchedIndex].id : null
|
|
112
|
+
};
|
|
104
113
|
};
|
|
105
114
|
/**
|
|
106
115
|
* 专门用于 (composable) blocklet 的 Header 组件, 解析 blocklet meta 中的数据, 通过组合 UX 中的 Header & NavMenu 组件来渲染
|
|
@@ -134,7 +143,10 @@ function Header(_ref) {
|
|
|
134
143
|
enableConnect = true,
|
|
135
144
|
enableLocale = true
|
|
136
145
|
} = blocklet;
|
|
137
|
-
const
|
|
146
|
+
const {
|
|
147
|
+
navItems,
|
|
148
|
+
activeId
|
|
149
|
+
} = parseNavigation(navigation);
|
|
138
150
|
|
|
139
151
|
const renderAddons = () => {
|
|
140
152
|
// 不关心内置的 session manager 和 locale selector, 直接覆盖 UX Header 的 addons
|
|
@@ -187,6 +199,7 @@ function Header(_ref) {
|
|
|
187
199
|
} = _ref2;
|
|
188
200
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavMenu.default, {
|
|
189
201
|
mode: isMobile ? 'inline' : 'horizontal',
|
|
202
|
+
activeId: activeId,
|
|
190
203
|
items: navItems,
|
|
191
204
|
onSelected: closeMenu,
|
|
192
205
|
className: "header-nav",
|
package/lib/utils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.mapRecursive = exports.isUrl = void 0;
|
|
6
|
+
exports.matchPaths = exports.matchPath = exports.mapRecursive = exports.isUrl = exports.flatRecursive = void 0;
|
|
7
7
|
|
|
8
8
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
9
9
|
|
|
@@ -22,13 +22,65 @@ const mapRecursive = function mapRecursive(array, fn) {
|
|
|
22
22
|
|
|
23
23
|
return fn(item);
|
|
24
24
|
});
|
|
25
|
-
}; //
|
|
25
|
+
}; // 展平有层级结构的 array
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
exports.mapRecursive = mapRecursive;
|
|
29
29
|
|
|
30
|
+
const flatRecursive = function flatRecursive(array) {
|
|
31
|
+
let childrenKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'children';
|
|
32
|
+
const result = [];
|
|
33
|
+
mapRecursive(array, item => result.push(item), childrenKey);
|
|
34
|
+
return result;
|
|
35
|
+
}; // "http://", "https://", "//" 开头的 3 种情况
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
exports.flatRecursive = flatRecursive;
|
|
39
|
+
|
|
30
40
|
const isUrl = str => {
|
|
31
41
|
return /^(https?:)?\/\//.test(str);
|
|
32
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* 检测 path 是否匹配当前 location, path 只考虑 "/" 开头的相对路径
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
exports.isUrl = isUrl;
|
|
49
|
+
|
|
50
|
+
const matchPath = path => {
|
|
51
|
+
if (!path || !(path !== null && path !== void 0 && path.startsWith('/'))) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ensureTrailingSlash = str => str.endsWith('/') ? str : "".concat(str, "/");
|
|
56
|
+
|
|
57
|
+
const pathname = ensureTrailingSlash(window.location.pathname);
|
|
58
|
+
const normalizedPath = ensureTrailingSlash(new URL(path, window.location.origin).pathname);
|
|
59
|
+
return pathname.startsWith(normalizedPath);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* 从一组 paths 中, 找到匹配当前 location 的 path, 返回序号
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
exports.matchPath = matchPath;
|
|
67
|
+
|
|
68
|
+
const matchPaths = function matchPaths() {
|
|
69
|
+
let paths = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
70
|
+
const matched = paths.map((item, index) => ({
|
|
71
|
+
path: item,
|
|
72
|
+
index
|
|
73
|
+
})).filter(item => matchPath(item.path));
|
|
74
|
+
|
|
75
|
+
if (!(matched !== null && matched !== void 0 && matched.length)) {
|
|
76
|
+
return -1;
|
|
77
|
+
} // 多个 path 都匹配时, 取一个最具体 (最长的) path
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
const mostSpecific = matched.slice(1).reduce((prev, cur) => {
|
|
81
|
+
return prev.path.length >= cur.path.length ? prev : cur;
|
|
82
|
+
}, matched[0]);
|
|
83
|
+
return mostSpecific.index;
|
|
84
|
+
};
|
|
33
85
|
|
|
34
|
-
exports.
|
|
86
|
+
exports.matchPaths = matchPaths;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/ui-react",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.28",
|
|
4
4
|
"description": "Some useful front-end web components that can be used in Blocklets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"url": "https://github.com/ArcBlock/ux/issues"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@arcblock/did-connect": "^2.1.
|
|
38
|
-
"@arcblock/ux": "^2.1.
|
|
37
|
+
"@arcblock/did-connect": "^2.1.28",
|
|
38
|
+
"@arcblock/ux": "^2.1.28",
|
|
39
39
|
"@iconify/iconify": "^2.2.1",
|
|
40
40
|
"@mui/material": "^5.6.4",
|
|
41
41
|
"core-js": "^3.6.4",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"eslint-plugin-react-hooks": "^4.2.0",
|
|
58
58
|
"jest": "^24.1.0"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "725aea0efca877ff15c93d5eab829fca3fe8da25"
|
|
61
61
|
}
|
package/src/Header/index.js
CHANGED
|
@@ -14,25 +14,24 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
14
14
|
import '@iconify/iconify';
|
|
15
15
|
|
|
16
16
|
import { blockletMetaProps, sessionManagerProps } from '../types';
|
|
17
|
+
import { mapRecursive, flatRecursive, matchPaths } from '../utils';
|
|
17
18
|
|
|
18
19
|
const muiTheme = create();
|
|
19
20
|
|
|
20
|
-
const isLinkActive = (link) => {
|
|
21
|
-
if (window.location.pathname === '/') {
|
|
22
|
-
return link === '/';
|
|
23
|
-
}
|
|
24
|
-
return link && link.indexOf(window.location.pathname) > -1;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
21
|
// blocklet meta 中的 navigation 数据 => NavMenu 组件的 items
|
|
28
22
|
const parseNavigation = (navigation) => {
|
|
23
|
+
if (!navigation?.length) {
|
|
24
|
+
return { navItems: [], activeId: null };
|
|
25
|
+
}
|
|
26
|
+
let counter = 1;
|
|
29
27
|
const parseItem = (item) => {
|
|
30
28
|
const icon = item.icon ? <span className="iconify" data-icon={item.icon} /> : null;
|
|
31
29
|
if (item.items) {
|
|
32
30
|
return {
|
|
31
|
+
id: `${counter++}`,
|
|
33
32
|
label: item.title,
|
|
34
33
|
icon,
|
|
35
|
-
children: item.items
|
|
34
|
+
children: item.items,
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
37
|
let props = {};
|
|
@@ -43,17 +42,20 @@ const parseNavigation = (navigation) => {
|
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
44
|
return {
|
|
45
|
+
id: `${counter++}`,
|
|
46
46
|
label: (
|
|
47
47
|
<a href={item.link} {...props}>
|
|
48
48
|
{item.title}
|
|
49
49
|
</a>
|
|
50
50
|
),
|
|
51
|
-
active: isLinkActive(item.link),
|
|
52
51
|
icon,
|
|
52
|
+
link: item.link,
|
|
53
53
|
};
|
|
54
54
|
};
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const navItems = mapRecursive(navigation, parseItem, 'items');
|
|
56
|
+
const flattened = flatRecursive(navItems);
|
|
57
|
+
const matchedIndex = matchPaths(flattened.map((item) => item.link));
|
|
58
|
+
return { navItems, activeId: matchedIndex >= 0 ? flattened[matchedIndex].id : null };
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
/**
|
|
@@ -69,7 +71,7 @@ function Header({ meta, addons, sessionManagerProps, ...rest }) {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
const { appLogo, appName, theme, navigation = [], enableConnect = true, enableLocale = true } = blocklet;
|
|
72
|
-
const navItems = parseNavigation(navigation);
|
|
74
|
+
const { navItems, activeId } = parseNavigation(navigation);
|
|
73
75
|
|
|
74
76
|
const renderAddons = () => {
|
|
75
77
|
// 不关心内置的 session manager 和 locale selector, 直接覆盖 UX Header 的 addons
|
|
@@ -111,6 +113,7 @@ function Header({ meta, addons, sessionManagerProps, ...rest }) {
|
|
|
111
113
|
: ({ isMobile, closeMenu }) => (
|
|
112
114
|
<NavMenu
|
|
113
115
|
mode={isMobile ? 'inline' : 'horizontal'}
|
|
116
|
+
activeId={activeId}
|
|
114
117
|
items={navItems}
|
|
115
118
|
onSelected={closeMenu}
|
|
116
119
|
className="header-nav"
|
package/src/utils.js
CHANGED
|
@@ -10,7 +10,42 @@ export const mapRecursive = (array, fn, childrenKey = 'children') => {
|
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// 展平有层级结构的 array
|
|
14
|
+
export const flatRecursive = (array, childrenKey = 'children') => {
|
|
15
|
+
const result = [];
|
|
16
|
+
mapRecursive(array, (item) => result.push(item), childrenKey);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
// "http://", "https://", "//" 开头的 3 种情况
|
|
14
21
|
export const isUrl = (str) => {
|
|
15
22
|
return /^(https?:)?\/\//.test(str);
|
|
16
23
|
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 检测 path 是否匹配当前 location, path 只考虑 "/" 开头的相对路径
|
|
27
|
+
*/
|
|
28
|
+
export const matchPath = (path) => {
|
|
29
|
+
if (!path || !path?.startsWith('/')) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const ensureTrailingSlash = (str) => (str.endsWith('/') ? str : `${str}/`);
|
|
33
|
+
const pathname = ensureTrailingSlash(window.location.pathname);
|
|
34
|
+
const normalizedPath = ensureTrailingSlash(new URL(path, window.location.origin).pathname);
|
|
35
|
+
return pathname.startsWith(normalizedPath);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 从一组 paths 中, 找到匹配当前 location 的 path, 返回序号
|
|
40
|
+
*/
|
|
41
|
+
export const matchPaths = (paths = []) => {
|
|
42
|
+
const matched = paths.map((item, index) => ({ path: item, index })).filter((item) => matchPath(item.path));
|
|
43
|
+
if (!matched?.length) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
// 多个 path 都匹配时, 取一个最具体 (最长的) path
|
|
47
|
+
const mostSpecific = matched.slice(1).reduce((prev, cur) => {
|
|
48
|
+
return prev.path.length >= cur.path.length ? prev : cur;
|
|
49
|
+
}, matched[0]);
|
|
50
|
+
return mostSpecific.index;
|
|
51
|
+
};
|