clxx 3.0.0 → 3.0.2
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/README.md +6 -3
- package/build/CitySelect/data.d.ts +1 -1
- package/build/CitySelect/data.js +2277 -1674
- package/build/CitySelect/index.d.ts +2 -1
- package/build/CitySelect/index.js +35 -9
- package/build/CitySelect/style.js +15 -12
- package/build/DatePicker/style.js +17 -14
- package/build/RegionPicker/data.js +974 -992
- package/build/RegionPicker/index.d.ts +3 -2
- package/build/RegionPicker/index.js +29 -10
- package/build/RegionPicker/style.js +25 -21
- package/build/utils/createApp.d.ts +16 -2
- package/build/utils/createApp.js +142 -109
- package/package.json +1 -1
- package/test/src/city-select/index.jsx +28 -15
- package/test/src/region-picker/index.jsx +29 -21
|
@@ -8,7 +8,7 @@ export interface RegionNode {
|
|
|
8
8
|
export interface RegionSelection {
|
|
9
9
|
province: RegionNode;
|
|
10
10
|
city: RegionNode;
|
|
11
|
-
district: RegionNode;
|
|
11
|
+
district: RegionNode | null;
|
|
12
12
|
}
|
|
13
13
|
export interface RegionLabels {
|
|
14
14
|
province?: string;
|
|
@@ -25,9 +25,10 @@ export interface RegionPickerProps {
|
|
|
25
25
|
maskClosable?: boolean;
|
|
26
26
|
primary?: string;
|
|
27
27
|
rounded?: boolean;
|
|
28
|
+
taiwanHKMacau?: boolean;
|
|
28
29
|
onClose?: () => void;
|
|
29
30
|
onCancel?: () => void;
|
|
30
31
|
onConfirm?: (selection: RegionSelection) => void;
|
|
31
32
|
}
|
|
32
33
|
export declare function RegionPicker(props: RegionPickerProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
33
|
-
export declare function showRegionPicker(options?: Pick<RegionPickerProps, "value" | "data" | "title" | "cancelText" | "confirmText" | "labels" | "maskClosable" | "primary" | "rounded" | "onCancel" | "onConfirm">): void;
|
|
34
|
+
export declare function showRegionPicker(options?: Pick<RegionPickerProps, "value" | "data" | "title" | "cancelText" | "confirmText" | "labels" | "maskClosable" | "primary" | "rounded" | "taiwanHKMacau" | "onCancel" | "onConfirm">): void;
|
|
@@ -19,6 +19,12 @@ const Dialog_1 = require("../Dialog");
|
|
|
19
19
|
const Clickable_1 = require("../Clickable");
|
|
20
20
|
const style_1 = require("./style");
|
|
21
21
|
const data_1 = require("./data");
|
|
22
|
+
// 港澳台对应的顶层省级 value(台湾、香港、澳门)
|
|
23
|
+
const HK_MO_TW_VALUES = new Set([
|
|
24
|
+
"710000",
|
|
25
|
+
"810000",
|
|
26
|
+
"820000",
|
|
27
|
+
]);
|
|
22
28
|
const DEFAULT_LABELS = {
|
|
23
29
|
province: "省",
|
|
24
30
|
city: "市",
|
|
@@ -26,8 +32,12 @@ const DEFAULT_LABELS = {
|
|
|
26
32
|
};
|
|
27
33
|
const TAB_ORDER = ["province", "city", "district"];
|
|
28
34
|
function RegionPicker(props) {
|
|
29
|
-
const { value, data = data_1.treeRegionData, title = "请选择地区", cancelText = "取消", confirmText = "确定", labels, primary = style_1.DEFAULT_PRIMARY, rounded = true, onClose, onCancel, onConfirm, } = props;
|
|
35
|
+
const { value, data = data_1.treeRegionData, title = "请选择地区", cancelText = "取消", confirmText = "确定", labels, primary = style_1.DEFAULT_PRIMARY, rounded = true, taiwanHKMacau = false, onClose, onCancel, onConfirm, } = props;
|
|
30
36
|
const style = (0, react_1.useMemo)(() => (0, style_1.createStyle)(primary, rounded), [primary, rounded]);
|
|
37
|
+
// 过滤后的顶层数据:仅在 data / taiwanHKMacau 变化时重建
|
|
38
|
+
const effectiveData = (0, react_1.useMemo)(() => taiwanHKMacau
|
|
39
|
+
? data
|
|
40
|
+
: data.filter((x) => !HK_MO_TW_VALUES.has(x.value)), [data, taiwanHKMacau]);
|
|
31
41
|
const lab = (0, react_1.useMemo)(() => (Object.assign(Object.assign({}, DEFAULT_LABELS), labels)), [labels]);
|
|
32
42
|
// === 选中状态 ===
|
|
33
43
|
// 当前每级选中节点;未选为 null
|
|
@@ -36,23 +46,26 @@ function RegionPicker(props) {
|
|
|
36
46
|
const [districtNode, setDistrictNode] = (0, react_1.useState)(null);
|
|
37
47
|
// 当前激活 tab
|
|
38
48
|
const [activeTab, setActiveTab] = (0, react_1.useState)("province");
|
|
39
|
-
//
|
|
49
|
+
// 初始化 / data 恢复选中里允许 city 无 children(部分地区没有区级)
|
|
40
50
|
(0, react_1.useEffect)(() => {
|
|
41
51
|
var _a, _b, _c, _d, _e;
|
|
42
52
|
if (!value)
|
|
43
53
|
return;
|
|
44
54
|
const [pv, cv, dv] = value;
|
|
45
|
-
const p = (_a =
|
|
55
|
+
const p = (_a = effectiveData.find((x) => x.value === pv)) !== null && _a !== void 0 ? _a : null;
|
|
46
56
|
const c = (_c = (_b = p === null || p === void 0 ? void 0 : p.children) === null || _b === void 0 ? void 0 : _b.find((x) => x.value === cv)) !== null && _c !== void 0 ? _c : null;
|
|
47
57
|
const d = (_e = (_d = c === null || c === void 0 ? void 0 : c.children) === null || _d === void 0 ? void 0 : _d.find((x) => x.value === dv)) !== null && _e !== void 0 ? _e : null;
|
|
48
58
|
setProvinceNode(p);
|
|
49
59
|
setCityNode(c);
|
|
50
60
|
setDistrictNode(d);
|
|
51
|
-
// 定位 activeTab
|
|
61
|
+
// 定位 activeTab 到「最深的未选层」;
|
|
62
|
+
// 如果选中的市无 children,则停在 city
|
|
52
63
|
if (!p)
|
|
53
64
|
setActiveTab("province");
|
|
54
65
|
else if (!c)
|
|
55
66
|
setActiveTab("city");
|
|
67
|
+
else if (!c.children || c.children.length === 0)
|
|
68
|
+
setActiveTab("city");
|
|
56
69
|
else
|
|
57
70
|
setActiveTab("district");
|
|
58
71
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -61,11 +74,11 @@ function RegionPicker(props) {
|
|
|
61
74
|
const currentList = (0, react_1.useMemo)(() => {
|
|
62
75
|
var _a, _b;
|
|
63
76
|
if (activeTab === "province")
|
|
64
|
-
return
|
|
77
|
+
return effectiveData;
|
|
65
78
|
if (activeTab === "city")
|
|
66
79
|
return (_a = provinceNode === null || provinceNode === void 0 ? void 0 : provinceNode.children) !== null && _a !== void 0 ? _a : [];
|
|
67
80
|
return (_b = cityNode === null || cityNode === void 0 ? void 0 : cityNode.children) !== null && _b !== void 0 ? _b : [];
|
|
68
|
-
}, [activeTab,
|
|
81
|
+
}, [activeTab, effectiveData, provinceNode, cityNode]);
|
|
69
82
|
const selectedValueOfTab = (tab) => {
|
|
70
83
|
var _a, _b, _c;
|
|
71
84
|
if (tab === "province")
|
|
@@ -74,13 +87,13 @@ function RegionPicker(props) {
|
|
|
74
87
|
return (_b = cityNode === null || cityNode === void 0 ? void 0 : cityNode.value) !== null && _b !== void 0 ? _b : null;
|
|
75
88
|
return (_c = districtNode === null || districtNode === void 0 ? void 0 : districtNode.value) !== null && _c !== void 0 ? _c : null;
|
|
76
89
|
};
|
|
77
|
-
// tab
|
|
90
|
+
// tab 是否可点击:上级已选;district 额外要求 city 有子级
|
|
78
91
|
const tabEnabled = (tab) => {
|
|
79
92
|
if (tab === "province")
|
|
80
93
|
return true;
|
|
81
94
|
if (tab === "city")
|
|
82
95
|
return !!provinceNode;
|
|
83
|
-
return !!cityNode;
|
|
96
|
+
return !!(cityNode && cityNode.children && cityNode.children.length > 0);
|
|
84
97
|
};
|
|
85
98
|
// 点击列表项
|
|
86
99
|
const handlePick = (item) => {
|
|
@@ -146,14 +159,20 @@ function RegionPicker(props) {
|
|
|
146
159
|
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
|
|
147
160
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
148
161
|
};
|
|
149
|
-
const canConfirm = !!(provinceNode &&
|
|
162
|
+
const canConfirm = !!(provinceNode &&
|
|
163
|
+
cityNode &&
|
|
164
|
+
(!cityNode.children || cityNode.children.length === 0 || districtNode));
|
|
150
165
|
const handleConfirm = () => {
|
|
151
166
|
if (!canConfirm)
|
|
152
167
|
return;
|
|
168
|
+
if (!provinceNode || !cityNode)
|
|
169
|
+
return;
|
|
153
170
|
onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm({
|
|
154
171
|
province: { value: provinceNode.value, label: provinceNode.label },
|
|
155
172
|
city: { value: cityNode.value, label: cityNode.label },
|
|
156
|
-
district:
|
|
173
|
+
district: districtNode
|
|
174
|
+
? { value: districtNode.value, label: districtNode.label }
|
|
175
|
+
: null,
|
|
157
176
|
});
|
|
158
177
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
159
178
|
};
|
|
@@ -5,16 +5,17 @@ exports.createStyle = createStyle;
|
|
|
5
5
|
const react_1 = require("@emotion/react");
|
|
6
6
|
const color_1 = require("../utils/color");
|
|
7
7
|
const theme_1 = require("../utils/theme");
|
|
8
|
-
//
|
|
9
|
-
const textPrimary = "#
|
|
10
|
-
const textSecondary = "#
|
|
11
|
-
const textTertiary = "#
|
|
8
|
+
// 与 CitySelect 一致的设计变量(带线条风格)
|
|
9
|
+
const textPrimary = "#1f2328";
|
|
10
|
+
const textSecondary = "#6b7280";
|
|
11
|
+
const textTertiary = "#9ca3af";
|
|
12
12
|
const bgPage = "#ffffff";
|
|
13
|
-
const bgSubtle = "
|
|
13
|
+
const bgSubtle = "#f5f6f8";
|
|
14
|
+
const border = "#e5e7eb";
|
|
14
15
|
exports.DEFAULT_PRIMARY = "#2f7dff";
|
|
15
16
|
function createStyle(primary, rounded = true) {
|
|
16
17
|
const primaryActive = (0, color_1.darken)(primary, 0.15);
|
|
17
|
-
const sheetRadius = rounded ? ".
|
|
18
|
+
const sheetRadius = rounded ? ".24rem" : "0";
|
|
18
19
|
return {
|
|
19
20
|
// 内容容器:动画/遮罩/全屏由 Dialog 提供,这里只保留视觉与排版
|
|
20
21
|
sheet: (0, react_1.css)({
|
|
@@ -32,7 +33,7 @@ function createStyle(primary, rounded = true) {
|
|
|
32
33
|
WebkitFontSmoothing: "antialiased",
|
|
33
34
|
MozOsxFontSmoothing: "grayscale",
|
|
34
35
|
}),
|
|
35
|
-
//
|
|
36
|
+
// 标题栏:底部 hairline 与列表区分;按钮中等字重
|
|
36
37
|
header: (0, react_1.css)({
|
|
37
38
|
flexShrink: 0,
|
|
38
39
|
height: ".92rem",
|
|
@@ -40,11 +41,12 @@ function createStyle(primary, rounded = true) {
|
|
|
40
41
|
alignItems: "center",
|
|
41
42
|
justifyContent: "space-between",
|
|
42
43
|
padding: "0 .16rem",
|
|
44
|
+
borderBottom: `1px solid ${border}`,
|
|
43
45
|
}),
|
|
44
46
|
title: (0, react_1.css)({
|
|
45
47
|
flex: 1,
|
|
46
48
|
textAlign: "center",
|
|
47
|
-
fontSize: ".
|
|
49
|
+
fontSize: ".3rem",
|
|
48
50
|
fontWeight: 600,
|
|
49
51
|
color: textPrimary,
|
|
50
52
|
letterSpacing: ".01rem",
|
|
@@ -52,7 +54,7 @@ function createStyle(primary, rounded = true) {
|
|
|
52
54
|
btn: (0, react_1.css)({
|
|
53
55
|
minWidth: "1.1rem",
|
|
54
56
|
padding: "0 .08rem",
|
|
55
|
-
fontSize: ".
|
|
57
|
+
fontSize: ".28rem",
|
|
56
58
|
fontWeight: 400,
|
|
57
59
|
lineHeight: ".92rem",
|
|
58
60
|
cursor: "pointer",
|
|
@@ -65,7 +67,7 @@ function createStyle(primary, rounded = true) {
|
|
|
65
67
|
}),
|
|
66
68
|
btnConfirm: (0, react_1.css)({
|
|
67
69
|
textAlign: "right",
|
|
68
|
-
fontWeight:
|
|
70
|
+
fontWeight: 500,
|
|
69
71
|
color: primary,
|
|
70
72
|
"&:active": { color: primaryActive, opacity: 0.65 },
|
|
71
73
|
}),
|
|
@@ -73,17 +75,18 @@ function createStyle(primary, rounded = true) {
|
|
|
73
75
|
color: textTertiary,
|
|
74
76
|
cursor: "not-allowed",
|
|
75
77
|
pointerEvents: "none",
|
|
76
|
-
fontWeight:
|
|
78
|
+
fontWeight: 500,
|
|
77
79
|
"&:active": { opacity: 1, color: textTertiary },
|
|
78
80
|
}),
|
|
79
|
-
// tabs
|
|
81
|
+
// tabs 行:浅灰底色(与 CitySelect 字母分组标题色一致),激活态细下划线
|
|
80
82
|
tabs: (0, react_1.css)({
|
|
81
83
|
flexShrink: 0,
|
|
82
84
|
display: "flex",
|
|
83
85
|
alignItems: "stretch",
|
|
84
86
|
height: ".8rem",
|
|
85
87
|
padding: "0 .16rem",
|
|
86
|
-
|
|
88
|
+
backgroundColor: bgSubtle,
|
|
89
|
+
borderBottom: `1px solid ${border}`,
|
|
87
90
|
}),
|
|
88
91
|
tab: (0, react_1.css)({
|
|
89
92
|
flex: 1,
|
|
@@ -92,7 +95,7 @@ function createStyle(primary, rounded = true) {
|
|
|
92
95
|
alignItems: "center",
|
|
93
96
|
justifyContent: "center",
|
|
94
97
|
padding: "0 .08rem",
|
|
95
|
-
fontSize: ".
|
|
98
|
+
fontSize: ".26rem",
|
|
96
99
|
color: textSecondary,
|
|
97
100
|
position: "relative",
|
|
98
101
|
cursor: "pointer",
|
|
@@ -107,7 +110,7 @@ function createStyle(primary, rounded = true) {
|
|
|
107
110
|
}),
|
|
108
111
|
tabActive: (0, react_1.css)({
|
|
109
112
|
color: primary,
|
|
110
|
-
fontWeight:
|
|
113
|
+
fontWeight: 600,
|
|
111
114
|
"&::after": {
|
|
112
115
|
content: '""',
|
|
113
116
|
position: "absolute",
|
|
@@ -120,7 +123,7 @@ function createStyle(primary, rounded = true) {
|
|
|
120
123
|
borderRadius: ".02rem",
|
|
121
124
|
},
|
|
122
125
|
}),
|
|
123
|
-
//
|
|
126
|
+
// 选项列表:固定高度避免跳变
|
|
124
127
|
list: (0, react_1.css)({
|
|
125
128
|
flexShrink: 0,
|
|
126
129
|
height: "5.28rem",
|
|
@@ -129,18 +132,19 @@ function createStyle(primary, rounded = true) {
|
|
|
129
132
|
WebkitOverflowScrolling: "touch",
|
|
130
133
|
overscrollBehavior: "contain",
|
|
131
134
|
backgroundColor: bgPage,
|
|
132
|
-
paddingBottom: ".12rem",
|
|
133
135
|
}),
|
|
134
|
-
//
|
|
136
|
+
// 列表项:底部 hairline 分隔(CitySelect 风),按下浅灰底
|
|
135
137
|
listItem: (0, react_1.css)({
|
|
136
138
|
position: "relative",
|
|
137
139
|
height: ".88rem",
|
|
138
140
|
display: "flex",
|
|
139
141
|
alignItems: "center",
|
|
140
|
-
padding: "0 .
|
|
142
|
+
padding: "0 .3rem",
|
|
141
143
|
fontSize: ".3rem",
|
|
142
144
|
color: textPrimary,
|
|
143
|
-
|
|
145
|
+
backgroundColor: bgPage,
|
|
146
|
+
borderBottom: `1px solid ${border}`,
|
|
147
|
+
transition: "background-color .12s",
|
|
144
148
|
"&:active": {
|
|
145
149
|
backgroundColor: bgSubtle,
|
|
146
150
|
},
|
|
@@ -157,7 +161,7 @@ function createStyle(primary, rounded = true) {
|
|
|
157
161
|
color: primary,
|
|
158
162
|
fontWeight: 500,
|
|
159
163
|
}),
|
|
160
|
-
//
|
|
164
|
+
// 对勾
|
|
161
165
|
checkIcon: (0, react_1.css)({
|
|
162
166
|
width: ".32rem",
|
|
163
167
|
height: ".32rem",
|
|
@@ -4,19 +4,33 @@ import { ContainerProps } from "../Container";
|
|
|
4
4
|
export type RouterMode = "browser" | "hash" | "memory";
|
|
5
5
|
export type AwaitValue<T> = T | Promise<T>;
|
|
6
6
|
export interface CreateAppOption extends Omit<ContainerProps, "children"> {
|
|
7
|
+
/** 页面加载前触发,可 async;早于 render */
|
|
7
8
|
onBefore?: (pathname: string) => AwaitValue<void>;
|
|
9
|
+
/** 页面成功渲染后触发,可 async;404 / 错误场景不触发 */
|
|
8
10
|
onAfter?: (pathname: string) => AwaitValue<void>;
|
|
11
|
+
/** 返回加载中占位节点,在 render 执行期间展示 */
|
|
9
12
|
loading?: (pathname: string) => AwaitValue<React.ReactNode>;
|
|
13
|
+
/** 根据路径返回页面节点;返回 null/undefined 触发 notFound */
|
|
10
14
|
render?: (pathname: string) => AwaitValue<React.ReactNode>;
|
|
15
|
+
/** render 返回 null/undefined(404)时调用 */
|
|
11
16
|
notFound?: (pathname: string) => AwaitValue<React.ReactNode>;
|
|
17
|
+
/** render 抛出错误时调用;未提供时降级到 notFound */
|
|
18
|
+
onError?: (pathname: string, error: unknown) => AwaitValue<React.ReactNode>;
|
|
19
|
+
/** 路由模式,默认 browser */
|
|
12
20
|
mode?: RouterMode;
|
|
21
|
+
/** 路径为 / 或空时使用的默认路径,默认 /index */
|
|
13
22
|
default?: string;
|
|
23
|
+
/** 路由切换时自动滚动到顶部,移动端推荐开启,默认 true */
|
|
24
|
+
scrollToTop?: boolean;
|
|
25
|
+
/** 开启 React.StrictMode,默认 false */
|
|
26
|
+
strict?: boolean;
|
|
27
|
+
/** 挂载目标(CSS 选择器或 HTMLElement) */
|
|
14
28
|
target: string | HTMLElement;
|
|
15
29
|
}
|
|
16
30
|
export declare let history: null | History;
|
|
17
31
|
export declare function getHistory(mode?: RouterMode): History;
|
|
18
32
|
/**
|
|
19
|
-
* 创建带路由的
|
|
33
|
+
* 创建带路由的 App,全局单例,通常只调用一次。
|
|
20
34
|
* @param option CreateAppOption
|
|
21
35
|
*/
|
|
22
|
-
export declare function createApp(option: CreateAppOption):
|
|
36
|
+
export declare function createApp(option: CreateAppOption): void;
|
package/build/utils/createApp.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,140 +41,140 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
42
|
});
|
|
10
43
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
45
|
exports.history = void 0;
|
|
16
46
|
exports.getHistory = getHistory;
|
|
17
47
|
exports.createApp = createApp;
|
|
18
48
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
19
|
-
const react_1 = require("react");
|
|
49
|
+
const react_1 = __importStar(require("react"));
|
|
20
50
|
const client_1 = require("react-dom/client");
|
|
21
51
|
const history_1 = require("history");
|
|
22
52
|
const Container_1 = require("../Container");
|
|
23
|
-
const pick_1 = __importDefault(require("lodash/pick"));
|
|
24
53
|
// 存储历史记录对象
|
|
25
54
|
exports.history = null;
|
|
26
55
|
// 获取历史记录对象
|
|
27
56
|
function getHistory(mode = "browser") {
|
|
28
57
|
if (exports.history === null) {
|
|
29
|
-
const
|
|
58
|
+
const factories = {
|
|
30
59
|
browser: history_1.createBrowserHistory,
|
|
31
60
|
hash: history_1.createHashHistory,
|
|
32
61
|
memory: history_1.createMemoryHistory,
|
|
33
62
|
};
|
|
34
|
-
exports.history =
|
|
63
|
+
exports.history = factories[mode]();
|
|
35
64
|
}
|
|
36
65
|
return exports.history;
|
|
37
66
|
}
|
|
67
|
+
const VALID_MODES = new Set(["browser", "hash", "memory"]);
|
|
68
|
+
// 模块级常量,移除路径首尾斜杠;g 标志确保首尾各自替换一次
|
|
69
|
+
const PATH_TRIM_RE = /^\/*|\/*$/g;
|
|
38
70
|
/**
|
|
39
|
-
* 创建带路由的
|
|
71
|
+
* 创建带路由的 App,全局单例,通常只调用一次。
|
|
40
72
|
* @param option CreateAppOption
|
|
41
73
|
*/
|
|
42
74
|
function createApp(option) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
var _a;
|
|
76
|
+
// 不修改入参,用局部变量保存规范化后的值
|
|
77
|
+
const mode = option.mode && VALID_MODES.has(option.mode) ? option.mode : "browser";
|
|
78
|
+
const defaultPath = ((_a = option.default) !== null && _a !== void 0 ? _a : "/index").replace(PATH_TRIM_RE, "");
|
|
79
|
+
const scrollToTop = option.scrollToTop !== false;
|
|
80
|
+
// 确保 history 在组件渲染前已就绪
|
|
81
|
+
exports.history = getHistory(mode);
|
|
82
|
+
// 提取 ContainerProps(含 maxWidth,原 pick 遗漏此项)
|
|
83
|
+
const { designWidth, maxWidth, globalStyle } = option;
|
|
84
|
+
const containerProps = { designWidth, maxWidth, globalStyle };
|
|
85
|
+
const { onBefore, onAfter, loading, render, notFound, onError } = option;
|
|
86
|
+
// 规范化路径:移除首尾斜杠,空路径回退到 defaultPath
|
|
87
|
+
const normalizePath = (path) => {
|
|
88
|
+
const normalized = path.replace(PATH_TRIM_RE, "");
|
|
89
|
+
return normalized || defaultPath;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 全局 App 组件,仅在 createApp 内实例化一次
|
|
93
|
+
*/
|
|
94
|
+
const App = () => {
|
|
95
|
+
const [page, setPage] = (0, react_1.useState)(null);
|
|
96
|
+
// 导航版本号:每次发起新导航自增;过时的异步回调检测到不匹配后静默丢弃,
|
|
97
|
+
// 防止慢请求在更新的导航完成后覆盖页面(竞态条件)
|
|
98
|
+
const navIdRef = (0, react_1.useRef)(0);
|
|
99
|
+
const loadPage = (0, react_1.useCallback)((pathname) => __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
const navId = ++navIdRef.current;
|
|
101
|
+
const path = normalizePath(pathname);
|
|
102
|
+
// 1. 展示加载占位
|
|
103
|
+
if (typeof loading === "function") {
|
|
104
|
+
const loadingNode = yield loading(path);
|
|
105
|
+
if (navId !== navIdRef.current)
|
|
106
|
+
return;
|
|
107
|
+
setPage(loadingNode);
|
|
108
|
+
}
|
|
109
|
+
// 2. 前置钩子
|
|
110
|
+
yield (onBefore === null || onBefore === void 0 ? void 0 : onBefore(path));
|
|
111
|
+
if (navId !== navIdRef.current)
|
|
112
|
+
return;
|
|
113
|
+
// 3. 渲染页面
|
|
114
|
+
if (typeof render === "function") {
|
|
115
|
+
let content;
|
|
116
|
+
let loadError;
|
|
117
|
+
let hasError = false;
|
|
118
|
+
try {
|
|
119
|
+
content = yield render(path);
|
|
81
120
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
// 默认 404 页面
|
|
95
|
-
setPage((0, jsx_runtime_1.jsxs)("div", { children: ["Not Found: ", normalizedPath] }));
|
|
96
|
-
}
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
setPage(pageContent);
|
|
100
|
-
}
|
|
101
|
-
catch (_a) {
|
|
102
|
-
// 动态 import 失败等场景
|
|
103
|
-
if (typeof notFound === "function") {
|
|
104
|
-
setPage(yield notFound(normalizedPath));
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
setPage((0, jsx_runtime_1.jsxs)("div", { children: ["Not Found: ", normalizedPath] }));
|
|
108
|
-
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
loadError = err;
|
|
123
|
+
hasError = true;
|
|
124
|
+
}
|
|
125
|
+
if (navId !== navIdRef.current)
|
|
126
|
+
return;
|
|
127
|
+
if (hasError) {
|
|
128
|
+
// render 抛出异常:优先 onError,降级 notFound,再降级内置提示
|
|
129
|
+
const errNode = typeof onError === "function" ? (yield onError(path, loadError)) : typeof notFound === "function" ? (yield notFound(path)) : ((0, jsx_runtime_1.jsxs)("div", { children: ["Error: ", path] }));
|
|
130
|
+
if (navId !== navIdRef.current)
|
|
109
131
|
return;
|
|
110
|
-
|
|
132
|
+
setPage(errNode);
|
|
133
|
+
return;
|
|
111
134
|
}
|
|
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
|
-
|
|
135
|
+
if (content == null) {
|
|
136
|
+
// render 返回 null/undefined:404
|
|
137
|
+
const notFoundNode = typeof notFound === "function" ? (yield notFound(path)) : ((0, jsx_runtime_1.jsxs)("div", { children: ["Not Found: ", path] }));
|
|
138
|
+
if (navId !== navIdRef.current)
|
|
139
|
+
return;
|
|
140
|
+
setPage(notFoundNode);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// 成功:切换前滚回顶部,再替换页面内容
|
|
144
|
+
if (scrollToTop)
|
|
145
|
+
window.scrollTo(0, 0);
|
|
146
|
+
setPage(content);
|
|
147
|
+
}
|
|
148
|
+
if (navId !== navIdRef.current)
|
|
149
|
+
return;
|
|
150
|
+
// 4. 后置钩子(仅成功渲染后调用)
|
|
151
|
+
yield (onAfter === null || onAfter === void 0 ? void 0 : onAfter(path));
|
|
152
|
+
}),
|
|
153
|
+
// 所有依赖均来自 createApp 闭包,生命周期内不变
|
|
154
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
155
|
+
[]);
|
|
156
|
+
(0, react_1.useEffect)(() => {
|
|
157
|
+
// 初始渲染当前路径
|
|
158
|
+
loadPage(exports.history.location.pathname);
|
|
159
|
+
// 监听后续路由变化,返回 unlisten 作为 cleanup
|
|
160
|
+
return exports.history.listen(({ location }) => loadPage(location.pathname));
|
|
161
|
+
}, [loadPage]);
|
|
162
|
+
return (0, jsx_runtime_1.jsx)(Container_1.Container, Object.assign({}, containerProps, { children: page }));
|
|
163
|
+
};
|
|
164
|
+
// 解析挂载目标
|
|
165
|
+
let mount;
|
|
166
|
+
if (typeof option.target === "string") {
|
|
167
|
+
mount = document.querySelector(option.target);
|
|
168
|
+
}
|
|
169
|
+
else if (option.target instanceof HTMLElement) {
|
|
170
|
+
mount = option.target;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
mount = null;
|
|
174
|
+
}
|
|
175
|
+
if (!mount) {
|
|
176
|
+
throw new Error(`createApp: mount target not found — "${typeof option.target === "string" ? option.target : "[invalid element]"}"`);
|
|
177
|
+
}
|
|
178
|
+
const root = (0, client_1.createRoot)(mount);
|
|
179
|
+
root.render(option.strict ? ((0, jsx_runtime_1.jsx)(react_1.default.StrictMode, { children: (0, jsx_runtime_1.jsx)(App, {}) })) : ((0, jsx_runtime_1.jsx)(App, {})));
|
|
147
180
|
}
|