clxx 2.1.7 → 3.0.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/AGENTS.md +2 -1
- package/README.md +147 -22
- package/build/Alert/Wrapper.js +12 -14
- package/build/Alert/style.js +44 -25
- package/build/AutoGrid/index.js +21 -15
- package/build/CarouselNotice/index.d.ts +19 -11
- package/build/CarouselNotice/index.js +80 -74
- package/build/CarouselNotice/style.js +14 -4
- package/build/CitySelect/index.js +81 -71
- package/build/CitySelect/style.js +22 -56
- package/build/Clickable/index.js +7 -0
- package/build/Container/index.d.ts +12 -4
- package/build/Container/index.js +94 -89
- package/build/Countdowner/index.js +4 -2
- package/build/DatePicker/Column.d.ts +9 -0
- package/build/DatePicker/Column.js +330 -0
- package/build/DatePicker/index.d.ts +32 -0
- package/build/DatePicker/index.js +230 -0
- package/build/DatePicker/style.d.ts +6 -0
- package/build/DatePicker/style.js +130 -0
- package/build/Dialog/Wrapper.d.ts +0 -1
- package/build/Dialog/Wrapper.js +22 -12
- package/build/Dialog/index.d.ts +7 -1
- package/build/Dialog/index.js +57 -32
- package/build/Dialog/style.js +6 -2
- package/build/Effect/useInterval.js +6 -3
- package/build/Fixed/index.js +13 -22
- package/build/Flex/FlexItem.d.ts +11 -0
- package/build/Flex/FlexItem.js +26 -0
- package/build/Flex/index.d.ts +2 -10
- package/build/Flex/index.js +12 -22
- package/build/Indicator/index.d.ts +9 -6
- package/build/Indicator/index.js +34 -37
- package/build/Indicator/style.d.ts +4 -3
- package/build/Indicator/style.js +8 -13
- package/build/Loading/Wrapper.js +2 -1
- package/build/Loading/style.js +9 -12
- package/build/Overlay/index.js +6 -1
- package/build/RegionPicker/data.d.ts +6 -0
- package/build/RegionPicker/data.js +14486 -0
- package/build/RegionPicker/index.d.ts +33 -0
- package/build/RegionPicker/index.js +205 -0
- package/build/RegionPicker/style.d.ts +4 -0
- package/build/RegionPicker/style.js +187 -0
- package/build/SafeArea/index.js +14 -17
- package/build/ScrollView/index.d.ts +23 -11
- package/build/ScrollView/index.js +132 -118
- package/build/ScrollView/style.d.ts +1 -1
- package/build/ScrollView/style.js +33 -22
- package/build/Toast/Toast.d.ts +0 -1
- package/build/Toast/Toast.js +6 -4
- package/build/Toast/style.d.ts +3 -10
- package/build/Toast/style.js +41 -45
- package/build/index.d.ts +3 -0
- package/build/index.js +7 -1
- package/build/utils/color.d.ts +5 -0
- package/build/utils/color.js +18 -0
- package/build/utils/dom.js +4 -3
- package/build/utils/theme.d.ts +2 -0
- package/build/utils/theme.js +7 -0
- package/package.json +1 -1
- package/test/src/date-picker/index.jsx +119 -0
- package/test/src/index/index.jsx +2 -0
- package/test/src/index.jsx +1 -0
- package/test/src/loading/index.jsx +2 -2
- package/test/src/region-picker/index.jsx +120 -0
- package/test/src/scrollview/BasicSection.jsx +56 -0
- package/test/src/scrollview/CustomLoadingSection.jsx +53 -0
- package/test/src/scrollview/HeightModeSection.jsx +42 -0
- package/test/src/scrollview/ImperativeSection.jsx +56 -0
- package/test/src/scrollview/NotScrollableSection.jsx +32 -0
- package/test/src/scrollview/PerfSection.jsx +34 -0
- package/test/src/scrollview/index.css +92 -8
- package/test/src/scrollview/index.jsx +13 -45
- package/test/src/toast/index.jsx +1 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DEFAULT_UNITS = void 0;
|
|
18
|
+
exports.DatePicker = DatePicker;
|
|
19
|
+
exports.showDatePicker = showDatePicker;
|
|
20
|
+
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
21
|
+
const react_1 = require("react");
|
|
22
|
+
const dayjs_1 = __importDefault(require("dayjs"));
|
|
23
|
+
const Dialog_1 = require("../Dialog");
|
|
24
|
+
const Clickable_1 = require("../Clickable");
|
|
25
|
+
const style_1 = require("./style");
|
|
26
|
+
const Column_1 = require("./Column");
|
|
27
|
+
exports.DEFAULT_UNITS = {
|
|
28
|
+
year: "年",
|
|
29
|
+
month: "月",
|
|
30
|
+
day: "日",
|
|
31
|
+
hour: "时",
|
|
32
|
+
minute: "分",
|
|
33
|
+
second: "秒",
|
|
34
|
+
};
|
|
35
|
+
// ----------------- 主组件 -----------------
|
|
36
|
+
function toDayjs(v) {
|
|
37
|
+
if (v === undefined)
|
|
38
|
+
return (0, dayjs_1.default)();
|
|
39
|
+
// dayjs 可以接收 Date/string/number/Dayjs
|
|
40
|
+
const d = (0, dayjs_1.default)(v);
|
|
41
|
+
return d.isValid() ? d : (0, dayjs_1.default)();
|
|
42
|
+
}
|
|
43
|
+
function range(start, end) {
|
|
44
|
+
const arr = [];
|
|
45
|
+
for (let i = start; i <= end; i++)
|
|
46
|
+
arr.push(i);
|
|
47
|
+
return arr;
|
|
48
|
+
}
|
|
49
|
+
const pad2 = (n) => (n < 10 ? "0" + n : "" + n);
|
|
50
|
+
function DatePicker(props) {
|
|
51
|
+
const { value, precision = "day", title = "请选择", cancelText = "取消", confirmText = "确定", primary = style_1.DEFAULT_PRIMARY, rounded = true, showUnit = true, units, minDate, maxDate, onClose, onCancel, onConfirm, } = props;
|
|
52
|
+
const style = (0, react_1.useMemo)(() => (0, style_1.createStyle)(primary, rounded), [primary, rounded]);
|
|
53
|
+
// 合并单位配置:用户部分覆盖 + 默认中文
|
|
54
|
+
const u = (0, react_1.useMemo)(() => (Object.assign(Object.assign({}, exports.DEFAULT_UNITS), units)), [units]);
|
|
55
|
+
// showUnit=false 时不追加单位后缀
|
|
56
|
+
const suffix = (key) => (showUnit ? u[key] : "");
|
|
57
|
+
const minD = (0, react_1.useMemo)(() => (minDate !== undefined ? toDayjs(minDate) : (0, dayjs_1.default)("1900-01-01")), [minDate]);
|
|
58
|
+
const maxD = (0, react_1.useMemo)(() => maxDate !== undefined ? toDayjs(maxDate) : (0, dayjs_1.default)("2100-12-31 23:59:59"), [maxDate]);
|
|
59
|
+
// 初始值限制在 [minD, maxD] 范围内
|
|
60
|
+
const initial = (0, react_1.useMemo)(() => {
|
|
61
|
+
let d = toDayjs(value);
|
|
62
|
+
if (d.isBefore(minD))
|
|
63
|
+
d = minD;
|
|
64
|
+
if (d.isAfter(maxD))
|
|
65
|
+
d = maxD;
|
|
66
|
+
return d;
|
|
67
|
+
}, [value, minD, maxD]);
|
|
68
|
+
const [year, setYear] = (0, react_1.useState)(initial.year());
|
|
69
|
+
const [month, setMonth] = (0, react_1.useState)(initial.month() + 1); // 1-12
|
|
70
|
+
const [day, setDay] = (0, react_1.useState)(initial.date());
|
|
71
|
+
const [hour, setHour] = (0, react_1.useState)(initial.hour());
|
|
72
|
+
const [minute, setMinute] = (0, react_1.useState)(initial.minute());
|
|
73
|
+
const [second, setSecond] = (0, react_1.useState)(initial.second());
|
|
74
|
+
const showHour = precision !== "day";
|
|
75
|
+
const showMinute = precision === "minute" || precision === "second";
|
|
76
|
+
const showSecond = precision === "second";
|
|
77
|
+
// 各列范围计算(考虑 min/max)
|
|
78
|
+
const years = (0, react_1.useMemo)(() => range(minD.year(), maxD.year()), [minD, maxD]);
|
|
79
|
+
const months = (0, react_1.useMemo)(() => {
|
|
80
|
+
let s = 1;
|
|
81
|
+
let e = 12;
|
|
82
|
+
if (year === minD.year())
|
|
83
|
+
s = minD.month() + 1;
|
|
84
|
+
if (year === maxD.year())
|
|
85
|
+
e = maxD.month() + 1;
|
|
86
|
+
if (s > e) {
|
|
87
|
+
// 不应发生;兜底返回单月
|
|
88
|
+
return [s];
|
|
89
|
+
}
|
|
90
|
+
return range(s, e);
|
|
91
|
+
}, [year, minD, maxD]);
|
|
92
|
+
const days = (0, react_1.useMemo)(() => {
|
|
93
|
+
const daysInMonth = (0, dayjs_1.default)(`${year}-${pad2(month)}-01`).daysInMonth();
|
|
94
|
+
let s = 1;
|
|
95
|
+
let e = daysInMonth;
|
|
96
|
+
if (year === minD.year() && month === minD.month() + 1)
|
|
97
|
+
s = minD.date();
|
|
98
|
+
if (year === maxD.year() && month === maxD.month() + 1)
|
|
99
|
+
e = maxD.date();
|
|
100
|
+
if (s > e)
|
|
101
|
+
return [s];
|
|
102
|
+
return range(s, e);
|
|
103
|
+
}, [year, month, minD, maxD]);
|
|
104
|
+
const hours = (0, react_1.useMemo)(() => {
|
|
105
|
+
let s = 0;
|
|
106
|
+
let e = 23;
|
|
107
|
+
if (year === minD.year() &&
|
|
108
|
+
month === minD.month() + 1 &&
|
|
109
|
+
day === minD.date())
|
|
110
|
+
s = minD.hour();
|
|
111
|
+
if (year === maxD.year() &&
|
|
112
|
+
month === maxD.month() + 1 &&
|
|
113
|
+
day === maxD.date())
|
|
114
|
+
e = maxD.hour();
|
|
115
|
+
if (s > e)
|
|
116
|
+
return [s];
|
|
117
|
+
return range(s, e);
|
|
118
|
+
}, [year, month, day, minD, maxD]);
|
|
119
|
+
const minutes = (0, react_1.useMemo)(() => {
|
|
120
|
+
let s = 0;
|
|
121
|
+
let e = 59;
|
|
122
|
+
if (year === minD.year() &&
|
|
123
|
+
month === minD.month() + 1 &&
|
|
124
|
+
day === minD.date() &&
|
|
125
|
+
hour === minD.hour())
|
|
126
|
+
s = minD.minute();
|
|
127
|
+
if (year === maxD.year() &&
|
|
128
|
+
month === maxD.month() + 1 &&
|
|
129
|
+
day === maxD.date() &&
|
|
130
|
+
hour === maxD.hour())
|
|
131
|
+
e = maxD.minute();
|
|
132
|
+
if (s > e)
|
|
133
|
+
return [s];
|
|
134
|
+
return range(s, e);
|
|
135
|
+
}, [year, month, day, hour, minD, maxD]);
|
|
136
|
+
const seconds = (0, react_1.useMemo)(() => {
|
|
137
|
+
let s = 0;
|
|
138
|
+
let e = 59;
|
|
139
|
+
if (year === minD.year() &&
|
|
140
|
+
month === minD.month() + 1 &&
|
|
141
|
+
day === minD.date() &&
|
|
142
|
+
hour === minD.hour() &&
|
|
143
|
+
minute === minD.minute())
|
|
144
|
+
s = minD.second();
|
|
145
|
+
if (year === maxD.year() &&
|
|
146
|
+
month === maxD.month() + 1 &&
|
|
147
|
+
day === maxD.date() &&
|
|
148
|
+
hour === maxD.hour() &&
|
|
149
|
+
minute === maxD.minute())
|
|
150
|
+
e = maxD.second();
|
|
151
|
+
if (s > e)
|
|
152
|
+
return [s];
|
|
153
|
+
return range(s, e);
|
|
154
|
+
}, [year, month, day, hour, minute, minD, maxD]);
|
|
155
|
+
// 联动夹取:超出范围 → 夹到最近边界(小于 min 取首;大于 max 取尾)
|
|
156
|
+
const clampToList = (v, list) => {
|
|
157
|
+
if (list.includes(v))
|
|
158
|
+
return v;
|
|
159
|
+
if (v < list[0])
|
|
160
|
+
return list[0];
|
|
161
|
+
return list[list.length - 1];
|
|
162
|
+
};
|
|
163
|
+
(0, react_1.useEffect)(() => {
|
|
164
|
+
const next = clampToList(month, months);
|
|
165
|
+
if (next !== month)
|
|
166
|
+
setMonth(next);
|
|
167
|
+
}, [months, month]);
|
|
168
|
+
(0, react_1.useEffect)(() => {
|
|
169
|
+
const next = clampToList(day, days);
|
|
170
|
+
if (next !== day)
|
|
171
|
+
setDay(next);
|
|
172
|
+
}, [days, day]);
|
|
173
|
+
(0, react_1.useEffect)(() => {
|
|
174
|
+
if (!showHour)
|
|
175
|
+
return;
|
|
176
|
+
const next = clampToList(hour, hours);
|
|
177
|
+
if (next !== hour)
|
|
178
|
+
setHour(next);
|
|
179
|
+
}, [hours, hour, showHour]);
|
|
180
|
+
(0, react_1.useEffect)(() => {
|
|
181
|
+
if (!showMinute)
|
|
182
|
+
return;
|
|
183
|
+
const next = clampToList(minute, minutes);
|
|
184
|
+
if (next !== minute)
|
|
185
|
+
setMinute(next);
|
|
186
|
+
}, [minutes, minute, showMinute]);
|
|
187
|
+
(0, react_1.useEffect)(() => {
|
|
188
|
+
if (!showSecond)
|
|
189
|
+
return;
|
|
190
|
+
const next = clampToList(second, seconds);
|
|
191
|
+
if (next !== second)
|
|
192
|
+
setSecond(next);
|
|
193
|
+
}, [seconds, second, showSecond]);
|
|
194
|
+
// 关闭:动画与卸载交由 Dialog 处理
|
|
195
|
+
const handleCancel = () => {
|
|
196
|
+
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
|
|
197
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
198
|
+
};
|
|
199
|
+
const handleConfirm = () => {
|
|
200
|
+
const hh = showHour ? hour : 0;
|
|
201
|
+
const mm = showMinute ? minute : 0;
|
|
202
|
+
const ss = showSecond ? second : 0;
|
|
203
|
+
// 用 ISO 字符串构造,避免链式 set 时的月份溢出问题
|
|
204
|
+
const d = (0, dayjs_1.default)(`${year}-${pad2(month)}-${pad2(day)}T${pad2(hh)}:${pad2(mm)}:${pad2(ss)}`);
|
|
205
|
+
onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(d);
|
|
206
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
207
|
+
};
|
|
208
|
+
return ((0, jsx_runtime_1.jsxs)("div", { css: style.sheet, children: [(0, jsx_runtime_1.jsxs)("div", { css: style.header, children: [(0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: [style.btn, style.btnCancel], onClick: handleCancel, children: cancelText }), (0, jsx_runtime_1.jsx)("div", { css: style.title, children: title }), (0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: [style.btn, style.btnConfirm], onClick: handleConfirm, children: confirmText })] }), (0, jsx_runtime_1.jsxs)("div", { css: style.body, children: [(0, jsx_runtime_1.jsx)("div", { css: style.indicator }), (0, jsx_runtime_1.jsx)(Column_1.Column, { items: years, value: year, onChange: setYear, format: (n) => `${n}${suffix("year")}`, style: style }), (0, jsx_runtime_1.jsx)(Column_1.Column, { items: months, value: month, onChange: setMonth, format: (n) => `${pad2(n)}${suffix("month")}`, style: style }), (0, jsx_runtime_1.jsx)(Column_1.Column, { items: days, value: day, onChange: setDay, format: (n) => `${pad2(n)}${suffix("day")}`, style: style }), showHour && ((0, jsx_runtime_1.jsx)(Column_1.Column, { items: hours, value: hour, onChange: setHour, format: (n) => `${pad2(n)}${suffix("hour")}`, style: style })), showMinute && ((0, jsx_runtime_1.jsx)(Column_1.Column, { items: minutes, value: minute, onChange: setMinute, format: (n) => `${pad2(n)}${suffix("minute")}`, style: style })), showSecond && ((0, jsx_runtime_1.jsx)(Column_1.Column, { items: seconds, value: second, onChange: setSecond, format: (n) => `${pad2(n)}${suffix("second")}`, style: style }))] })] }));
|
|
209
|
+
}
|
|
210
|
+
function showDatePicker(options = {}) {
|
|
211
|
+
const { maskClosable = true, onCancel } = options, rest = __rest(options, ["maskClosable", "onCancel"]);
|
|
212
|
+
// 防止动画结束前重复触发 close(双击确认/取消、close 后再点遮罩等场景)
|
|
213
|
+
let closing = false;
|
|
214
|
+
let close;
|
|
215
|
+
const requestClose = () => {
|
|
216
|
+
if (closing)
|
|
217
|
+
return;
|
|
218
|
+
closing = true;
|
|
219
|
+
close === null || close === void 0 ? void 0 : close();
|
|
220
|
+
};
|
|
221
|
+
close = (0, Dialog_1.showDialog)({
|
|
222
|
+
type: "pullUp",
|
|
223
|
+
blankClosable: maskClosable,
|
|
224
|
+
// 点击空白时同步补发 onCancel 通知(关闭动画并行进行)
|
|
225
|
+
onBlankClick: () => {
|
|
226
|
+
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
|
|
227
|
+
},
|
|
228
|
+
content: ((0, jsx_runtime_1.jsx)(DatePicker, Object.assign({}, rest, { onCancel: onCancel, onClose: requestClose }))),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Interpolation, Theme } from "@emotion/react";
|
|
2
|
+
export declare const ITEM_HEIGHT_REM = 0.8;
|
|
3
|
+
export declare const VISIBLE_ROWS = 5;
|
|
4
|
+
export type DatePickerStyle = Record<string, Interpolation<Theme>>;
|
|
5
|
+
export declare function createStyle(primary: string, rounded?: boolean): DatePickerStyle;
|
|
6
|
+
export declare const DEFAULT_PRIMARY = "#2f7dff";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_PRIMARY = exports.VISIBLE_ROWS = exports.ITEM_HEIGHT_REM = void 0;
|
|
4
|
+
exports.createStyle = createStyle;
|
|
5
|
+
const react_1 = require("@emotion/react");
|
|
6
|
+
const color_1 = require("../utils/color");
|
|
7
|
+
const theme_1 = require("../utils/theme");
|
|
8
|
+
// iOS 风格设计变量
|
|
9
|
+
const textPrimary = "#000000";
|
|
10
|
+
const textSecondary = "#3c3c43"; // iOS secondaryLabel
|
|
11
|
+
const textTertiary = "#8e8e93"; // iOS tertiaryLabel
|
|
12
|
+
const bgPage = "#ffffff";
|
|
13
|
+
const bgSubtle = "rgba(120,120,128,.12)"; // iOS quaternary fill
|
|
14
|
+
// 可见行数 = 5,单元高度 = .8rem
|
|
15
|
+
exports.ITEM_HEIGHT_REM = 0.8;
|
|
16
|
+
exports.VISIBLE_ROWS = 5;
|
|
17
|
+
function createStyle(primary, rounded = true) {
|
|
18
|
+
const primaryActive = (0, color_1.darken)(primary, 0.15);
|
|
19
|
+
const sheetRadius = rounded ? ".28rem" : "0";
|
|
20
|
+
const indicatorRadius = rounded ? ".12rem" : "0";
|
|
21
|
+
return {
|
|
22
|
+
// 内容容器:动画/全屏/居中由 Dialog 提供,这里只保留视觉与排版
|
|
23
|
+
sheet: (0, react_1.css)({
|
|
24
|
+
width: "100%",
|
|
25
|
+
backgroundColor: bgPage,
|
|
26
|
+
borderTopLeftRadius: sheetRadius,
|
|
27
|
+
borderTopRightRadius: sheetRadius,
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
userSelect: "none",
|
|
30
|
+
color: textPrimary,
|
|
31
|
+
fontFamily: theme_1.fontStack,
|
|
32
|
+
WebkitFontSmoothing: "antialiased",
|
|
33
|
+
MozOsxFontSmoothing: "grayscale",
|
|
34
|
+
}),
|
|
35
|
+
// iOS 风标题栏:底部 hairline 与下方 body 做轻量区块分隔
|
|
36
|
+
header: (0, react_1.css)({
|
|
37
|
+
height: ".92rem",
|
|
38
|
+
display: "flex",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
justifyContent: "space-between",
|
|
41
|
+
padding: "0 .16rem",
|
|
42
|
+
borderBottom: "1px solid rgba(60,60,67,.18)",
|
|
43
|
+
}),
|
|
44
|
+
title: (0, react_1.css)({
|
|
45
|
+
flex: 1,
|
|
46
|
+
textAlign: "center",
|
|
47
|
+
fontSize: ".32rem",
|
|
48
|
+
fontWeight: 600,
|
|
49
|
+
color: textPrimary,
|
|
50
|
+
letterSpacing: ".01rem",
|
|
51
|
+
}),
|
|
52
|
+
btn: (0, react_1.css)({
|
|
53
|
+
minWidth: "1.1rem",
|
|
54
|
+
padding: "0 .08rem",
|
|
55
|
+
fontSize: ".3rem",
|
|
56
|
+
fontWeight: 400,
|
|
57
|
+
lineHeight: ".92rem",
|
|
58
|
+
cursor: "pointer",
|
|
59
|
+
transition: "opacity .15s, color .15s",
|
|
60
|
+
}),
|
|
61
|
+
btnCancel: (0, react_1.css)({
|
|
62
|
+
textAlign: "left",
|
|
63
|
+
color: textSecondary,
|
|
64
|
+
"&:active": { opacity: 0.55 },
|
|
65
|
+
}),
|
|
66
|
+
btnConfirm: (0, react_1.css)({
|
|
67
|
+
textAlign: "right",
|
|
68
|
+
fontWeight: 600,
|
|
69
|
+
color: primary,
|
|
70
|
+
"&:active": { color: primaryActive, opacity: 0.65 },
|
|
71
|
+
}),
|
|
72
|
+
body: (0, react_1.css)({
|
|
73
|
+
position: "relative",
|
|
74
|
+
display: "flex",
|
|
75
|
+
height: `${exports.ITEM_HEIGHT_REM * exports.VISIBLE_ROWS}rem`,
|
|
76
|
+
padding: "0 .16rem .12rem",
|
|
77
|
+
}),
|
|
78
|
+
// iOS 风选中背景:柔和的 quaternary fill、充足圆角
|
|
79
|
+
indicator: (0, react_1.css)({
|
|
80
|
+
position: "absolute",
|
|
81
|
+
left: ".16rem",
|
|
82
|
+
right: ".16rem",
|
|
83
|
+
top: `${exports.ITEM_HEIGHT_REM * 2}rem`,
|
|
84
|
+
height: `${exports.ITEM_HEIGHT_REM}rem`,
|
|
85
|
+
pointerEvents: "none",
|
|
86
|
+
backgroundColor: bgSubtle,
|
|
87
|
+
borderRadius: indicatorRadius,
|
|
88
|
+
}),
|
|
89
|
+
column: (0, react_1.css)({
|
|
90
|
+
position: "relative",
|
|
91
|
+
flex: 1,
|
|
92
|
+
minWidth: 0,
|
|
93
|
+
height: "100%",
|
|
94
|
+
overflow: "hidden",
|
|
95
|
+
touchAction: "none",
|
|
96
|
+
// 自管理手势,禁用浏览器原生选中/拖拽
|
|
97
|
+
userSelect: "none",
|
|
98
|
+
WebkitUserSelect: "none",
|
|
99
|
+
}),
|
|
100
|
+
// 内层位移容器:transform translateY(-offset) 实现"滚动"
|
|
101
|
+
columnInner: (0, react_1.css)({
|
|
102
|
+
position: "absolute",
|
|
103
|
+
top: 0,
|
|
104
|
+
left: 0,
|
|
105
|
+
right: 0,
|
|
106
|
+
willChange: "transform",
|
|
107
|
+
}),
|
|
108
|
+
item: (0, react_1.css)({
|
|
109
|
+
height: `${exports.ITEM_HEIGHT_REM}rem`,
|
|
110
|
+
lineHeight: `${exports.ITEM_HEIGHT_REM}rem`,
|
|
111
|
+
fontSize: ".32rem",
|
|
112
|
+
fontWeight: 400,
|
|
113
|
+
fontFamily: theme_1.numberFontStack,
|
|
114
|
+
fontVariantNumeric: "tabular-nums",
|
|
115
|
+
letterSpacing: ".01rem",
|
|
116
|
+
textAlign: "center",
|
|
117
|
+
color: textTertiary,
|
|
118
|
+
transition: "color .18s ease",
|
|
119
|
+
}),
|
|
120
|
+
itemActive: (0, react_1.css)({
|
|
121
|
+
color: textPrimary,
|
|
122
|
+
fontWeight: 500,
|
|
123
|
+
}),
|
|
124
|
+
spacer: (0, react_1.css)({
|
|
125
|
+
height: `${exports.ITEM_HEIGHT_REM * 2}rem`,
|
|
126
|
+
pointerEvents: "none",
|
|
127
|
+
}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
exports.DEFAULT_PRIMARY = "#2f7dff";
|
package/build/Dialog/Wrapper.js
CHANGED
|
@@ -2,32 +2,42 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Wrapper = Wrapper;
|
|
4
4
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
5
6
|
const Overlay_1 = require("../Overlay");
|
|
6
7
|
const style_1 = require("./style");
|
|
8
|
+
// 常量:Overlay 顶层容器 css
|
|
9
|
+
const overlayCss = { overflow: "hidden" };
|
|
10
|
+
// 仅这几种类型需要附加位置样式;center 由 Overlay 的 centerContent 居中
|
|
11
|
+
const POSITIONED_TYPES = new Set([
|
|
12
|
+
"pullUp",
|
|
13
|
+
"pullDown",
|
|
14
|
+
"pullLeft",
|
|
15
|
+
"pullRight",
|
|
16
|
+
]);
|
|
7
17
|
function Wrapper(props) {
|
|
8
18
|
const { type = "center", status = "show", children, onHide, showMask = true, maskColor, maskStyle, boxStyle, onBlankClick, } = props;
|
|
9
19
|
const { animation, keyframes } = (0, style_1.getAnimation)(type, status);
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
];
|
|
15
|
-
// 遮罩的样式
|
|
16
|
-
let maskCss = [
|
|
20
|
+
// 缓存:仅当 type 变化时重建
|
|
21
|
+
const positionStyle = (0, react_1.useMemo)(() => (POSITIONED_TYPES.has(type) ? style_1.style[type] : null), [type]);
|
|
22
|
+
// 缓存:仅当 status / 外部 maskStyle / maskColor 变化时重建
|
|
23
|
+
const maskCss = (0, react_1.useMemo)(() => [
|
|
17
24
|
style_1.style.mask,
|
|
18
25
|
status === "show" ? style_1.style.maskShow : style_1.style.maskHide,
|
|
19
26
|
maskStyle,
|
|
20
|
-
maskColor ? { backgroundColor: maskColor } :
|
|
21
|
-
];
|
|
22
|
-
//
|
|
27
|
+
maskColor ? { backgroundColor: maskColor } : null,
|
|
28
|
+
], [status, maskStyle, maskColor]);
|
|
29
|
+
// 空白处点击:仅在事件 target 与 currentTarget 一致时触发,避免冒泡误关闭
|
|
23
30
|
const blankClick = (event) => {
|
|
24
31
|
if (event.target === event.currentTarget) {
|
|
25
32
|
event.stopPropagation();
|
|
26
33
|
onBlankClick === null || onBlankClick === void 0 ? void 0 : onBlankClick(event);
|
|
27
34
|
}
|
|
28
35
|
};
|
|
29
|
-
return ((0, jsx_runtime_1.jsxs)(Overlay_1.Overlay, { css:
|
|
30
|
-
|
|
36
|
+
return ((0, jsx_runtime_1.jsxs)(Overlay_1.Overlay, { css: overlayCss, centerContent: type === "center", maskColor: "transparent", fullScreen: true, onClick: showMask ? undefined : blankClick, children: [showMask && (0, jsx_runtime_1.jsx)("div", { css: maskCss, onClick: blankClick }), (0, jsx_runtime_1.jsx)("div", { css: [style_1.style.boxCss, positionStyle, boxStyle, animation], onAnimationEnd: (event) => {
|
|
37
|
+
// 仅响应隐藏动画结束,且必须是 box 自身的动画(避免子元素同名动画事件冒泡误触发)
|
|
38
|
+
if (status === "hide" &&
|
|
39
|
+
event.target === event.currentTarget &&
|
|
40
|
+
event.animationName === keyframes.name) {
|
|
31
41
|
onHide === null || onHide === void 0 ? void 0 : onHide();
|
|
32
42
|
}
|
|
33
43
|
}, children: children })] }));
|
package/build/Dialog/index.d.ts
CHANGED
|
@@ -8,7 +8,13 @@ export interface ShowDialogOption extends WrapperProps {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* 显示一个对话框,出现和隐藏都带有动画效果
|
|
11
|
+
*
|
|
12
|
+
* 关键不变量:
|
|
13
|
+
* 1. 关闭操作幂等:多次调用返回的 close(),或 close() 与遮罩点击并发,都只触发一次隐藏动画与 unmount。
|
|
14
|
+
* 2. 所有等待同一次关闭的 Promise 都会 resolve(不会因后续调用覆盖 onHide 而悬挂)。
|
|
15
|
+
* 3. 用户的 onBlankClick 在事件循环同步阶段触发(避免 React 合成事件被回收),关闭动画并行进行。
|
|
16
|
+
*
|
|
11
17
|
* @param option
|
|
12
|
-
* @returns
|
|
18
|
+
* @returns 关闭函数;返回的 Promise 在隐藏动画结束、容器卸载完成后 resolve
|
|
13
19
|
*/
|
|
14
20
|
export declare function showDialog(option: React.ReactNode | ShowDialogOption): () => Promise<void>;
|
package/build/Dialog/index.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -21,8 +12,14 @@ const isPlainObject_1 = __importDefault(require("lodash/isPlainObject"));
|
|
|
21
12
|
const omit_1 = __importDefault(require("lodash/omit"));
|
|
22
13
|
/**
|
|
23
14
|
* 显示一个对话框,出现和隐藏都带有动画效果
|
|
15
|
+
*
|
|
16
|
+
* 关键不变量:
|
|
17
|
+
* 1. 关闭操作幂等:多次调用返回的 close(),或 close() 与遮罩点击并发,都只触发一次隐藏动画与 unmount。
|
|
18
|
+
* 2. 所有等待同一次关闭的 Promise 都会 resolve(不会因后续调用覆盖 onHide 而悬挂)。
|
|
19
|
+
* 3. 用户的 onBlankClick 在事件循环同步阶段触发(避免 React 合成事件被回收),关闭动画并行进行。
|
|
20
|
+
*
|
|
24
21
|
* @param option
|
|
25
|
-
* @returns
|
|
22
|
+
* @returns 关闭函数;返回的 Promise 在隐藏动画结束、容器卸载完成后 resolve
|
|
26
23
|
*/
|
|
27
24
|
function showDialog(option) {
|
|
28
25
|
const { mount, unmount } = (0, dom_1.createPortalDOM)();
|
|
@@ -37,34 +34,62 @@ function showDialog(option) {
|
|
|
37
34
|
// 提取需要单独处理的配置项
|
|
38
35
|
const blankClosable = !!config.blankClosable;
|
|
39
36
|
const children = config.content;
|
|
40
|
-
const
|
|
41
|
-
const
|
|
37
|
+
const userOnBlankClick = config.onBlankClick;
|
|
38
|
+
const userOnHide = config.onHide;
|
|
42
39
|
const props = (0, omit_1.default)(config, [
|
|
43
40
|
'blankClosable',
|
|
44
41
|
'content',
|
|
45
42
|
'onHide',
|
|
43
|
+
'onBlankClick',
|
|
46
44
|
]);
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
45
|
+
// 关闭状态机:'idle' -> 'closing' -> 'closed'
|
|
46
|
+
// 通过状态保证 closeDialog 幂等,避免重复 mount 已 unmount 的 root,避免 onHide 覆盖丢失 resolve
|
|
47
|
+
let closeState = 'idle';
|
|
48
|
+
const waiters = [];
|
|
49
|
+
const closeDialog = () => {
|
|
50
|
+
if (closeState === 'closed') {
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
}
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
waiters.push(resolve);
|
|
55
|
+
if (closeState === 'closing') {
|
|
56
|
+
// 已经在关闭流程中,仅排队等待
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
closeState = 'closing';
|
|
60
|
+
props.status = 'hide';
|
|
61
|
+
props.onHide = () => {
|
|
62
|
+
// 防御:onAnimationEnd 仅应触发一次,但若 React 重渲染导致再次冒泡也只处理一次
|
|
63
|
+
if (closeState === 'closed')
|
|
64
|
+
return;
|
|
65
|
+
closeState = 'closed';
|
|
66
|
+
unmount();
|
|
67
|
+
try {
|
|
68
|
+
userOnHide === null || userOnHide === void 0 ? void 0 : userOnHide();
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
// 唤醒全部等待者,即使 user onHide 抛错也不影响 Promise 链
|
|
72
|
+
const list = waiters.splice(0);
|
|
73
|
+
list.forEach((fn) => fn());
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
mount((0, jsx_runtime_1.jsx)(Wrapper_1.Wrapper, Object.assign({}, props, { children: children })));
|
|
61
77
|
});
|
|
78
|
+
};
|
|
79
|
+
// 空白处可点击关闭:同步触发用户回调,并行启动关闭动画
|
|
80
|
+
if (blankClosable) {
|
|
81
|
+
props.onBlankClick = (event) => {
|
|
82
|
+
// 先同步调用用户回调(保留事件对象的有效性)
|
|
83
|
+
userOnBlankClick === null || userOnBlankClick === void 0 ? void 0 : userOnBlankClick(event);
|
|
84
|
+
// 再触发关闭(不 await,避免事件对象失效)
|
|
85
|
+
closeDialog();
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else if (userOnBlankClick) {
|
|
89
|
+
// 不自动关闭但用户仍订阅了 blank click
|
|
90
|
+
props.onBlankClick = userOnBlankClick;
|
|
62
91
|
}
|
|
63
92
|
// 挂载容器对象
|
|
64
|
-
|
|
65
|
-
return
|
|
66
|
-
// 关闭前确保容器已经被挂载
|
|
67
|
-
yield amountShow;
|
|
68
|
-
yield closeDialog();
|
|
69
|
-
});
|
|
93
|
+
mount((0, jsx_runtime_1.jsx)(Wrapper_1.Wrapper, Object.assign({}, props, { children: children })));
|
|
94
|
+
return closeDialog;
|
|
70
95
|
}
|
package/build/Dialog/style.js
CHANGED
|
@@ -127,7 +127,7 @@ function getAnimation(type, status) {
|
|
|
127
127
|
}
|
|
128
128
|
exports.style = {
|
|
129
129
|
maskShow: (0, react_1.css)({
|
|
130
|
-
animation: `${maskShow} 300ms ease`,
|
|
130
|
+
animation: `${maskShow} 300ms ease forwards`,
|
|
131
131
|
}),
|
|
132
132
|
maskHide: (0, react_1.css)({
|
|
133
133
|
animation: `${exports.maskHide} 300ms ease forwards`,
|
|
@@ -139,10 +139,14 @@ exports.style = {
|
|
|
139
139
|
bottom: 0,
|
|
140
140
|
width: "100%",
|
|
141
141
|
height: "100%",
|
|
142
|
-
backgroundColor: "rgba(0, 0, 0, 0.
|
|
142
|
+
backgroundColor: "rgba(0, 0, 0, 0.4)",
|
|
143
|
+
// 提升为合成层,opacity 动画走 GPU,避免触发重绘
|
|
144
|
+
willChange: "opacity",
|
|
143
145
|
}),
|
|
144
146
|
boxCss: (0, react_1.css)({
|
|
145
147
|
zIndex: 2,
|
|
148
|
+
// transform 动画走 GPU 合成
|
|
149
|
+
willChange: "transform, opacity",
|
|
146
150
|
}),
|
|
147
151
|
pullUp: (0, react_1.css)({
|
|
148
152
|
position: "absolute",
|
|
@@ -6,9 +6,12 @@ function useInterval(callback, delay) {
|
|
|
6
6
|
const savedCallback = (0, react_1.useRef)(callback);
|
|
7
7
|
savedCallback.current = callback;
|
|
8
8
|
(0, react_1.useEffect)(() => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
// 仅当 delay 为有限的非负数时才启动定时器;
|
|
10
|
+
// null/undefined/NaN 等都视为暂停
|
|
11
|
+
if (typeof delay !== "number" || !isFinite(delay) || delay < 0) {
|
|
12
|
+
return;
|
|
12
13
|
}
|
|
14
|
+
const interval = setInterval(() => savedCallback.current(), delay);
|
|
15
|
+
return () => clearInterval(interval);
|
|
13
16
|
}, [delay]);
|
|
14
17
|
}
|
package/build/Fixed/index.js
CHANGED
|
@@ -13,28 +13,19 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.Fixed = Fixed;
|
|
15
15
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
16
|
+
// 模块级常量:4 种 position 的样式静态、与 props 无关,只哈希一次
|
|
17
|
+
const baseStyle = {
|
|
18
|
+
position: "fixed",
|
|
19
|
+
// 与 Container 的 maxWidth 联动,PC 端 fixed 元素也水平居中限宽
|
|
20
|
+
maxWidth: "var(--clxx-max-width, 100%)",
|
|
21
|
+
};
|
|
22
|
+
const positionStyles = {
|
|
23
|
+
top: Object.assign(Object.assign({}, baseStyle), { top: 0, width: "100%", left: "50%", transform: "translateX(-50%)" }),
|
|
24
|
+
bottom: Object.assign(Object.assign({}, baseStyle), { bottom: 0, width: "100%", left: "50%", transform: "translateX(-50%)" }),
|
|
25
|
+
left: Object.assign(Object.assign({}, baseStyle), { top: 0, left: "50%", height: "100%", transform: "translateX(calc(var(--clxx-max-width, 100vw) / -2))" }),
|
|
26
|
+
right: Object.assign(Object.assign({}, baseStyle), { top: 0, left: "50%", height: "100%", transform: "translateX(calc(var(--clxx-max-width, 100vw) / 2 - 100%))" }),
|
|
27
|
+
};
|
|
16
28
|
function Fixed(props) {
|
|
17
29
|
const { children, position = "bottom" } = props, extra = __rest(props, ["children", "position"]);
|
|
18
|
-
|
|
19
|
-
position: "fixed",
|
|
20
|
-
};
|
|
21
|
-
if (position === "top") {
|
|
22
|
-
styles.top = 0;
|
|
23
|
-
styles.width = "100%";
|
|
24
|
-
styles.left = 0;
|
|
25
|
-
}
|
|
26
|
-
else if (position === "bottom") {
|
|
27
|
-
styles.bottom = 0;
|
|
28
|
-
styles.width = "100%";
|
|
29
|
-
styles.left = 0;
|
|
30
|
-
}
|
|
31
|
-
else if (position === "left") {
|
|
32
|
-
styles.left = 0;
|
|
33
|
-
styles.height = "100%";
|
|
34
|
-
}
|
|
35
|
-
else if (position === "right") {
|
|
36
|
-
styles.right = 0;
|
|
37
|
-
styles.height = "100%";
|
|
38
|
-
}
|
|
39
|
-
return ((0, jsx_runtime_1.jsx)("div", Object.assign({}, props, { css: styles }, extra, { children: props.children })));
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)("div", Object.assign({}, extra, { css: positionStyles[position], children: children })));
|
|
40
31
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as CSS from 'csstype';
|
|
2
|
+
export interface FlexItemProps extends React.HTMLProps<HTMLDivElement> {
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
alignSelf?: CSS.Property.AlignSelf;
|
|
5
|
+
order?: CSS.Property.Order;
|
|
6
|
+
flex?: CSS.Property.BoxFlex;
|
|
7
|
+
flexGrow?: CSS.Property.FlexGrow;
|
|
8
|
+
flexShrink?: CSS.Property.FlexShrink;
|
|
9
|
+
flexBasis?: CSS.Property.FlexBasis;
|
|
10
|
+
}
|
|
11
|
+
export declare function FlexItem(props: FlexItemProps): import("@emotion/react/jsx-runtime").JSX.Element;
|