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.
Files changed (75) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +147 -22
  3. package/build/Alert/Wrapper.js +12 -14
  4. package/build/Alert/style.js +44 -25
  5. package/build/AutoGrid/index.js +21 -15
  6. package/build/CarouselNotice/index.d.ts +19 -11
  7. package/build/CarouselNotice/index.js +80 -74
  8. package/build/CarouselNotice/style.js +14 -4
  9. package/build/CitySelect/index.js +81 -71
  10. package/build/CitySelect/style.js +22 -56
  11. package/build/Clickable/index.js +7 -0
  12. package/build/Container/index.d.ts +12 -4
  13. package/build/Container/index.js +94 -89
  14. package/build/Countdowner/index.js +4 -2
  15. package/build/DatePicker/Column.d.ts +9 -0
  16. package/build/DatePicker/Column.js +330 -0
  17. package/build/DatePicker/index.d.ts +32 -0
  18. package/build/DatePicker/index.js +230 -0
  19. package/build/DatePicker/style.d.ts +6 -0
  20. package/build/DatePicker/style.js +130 -0
  21. package/build/Dialog/Wrapper.d.ts +0 -1
  22. package/build/Dialog/Wrapper.js +22 -12
  23. package/build/Dialog/index.d.ts +7 -1
  24. package/build/Dialog/index.js +57 -32
  25. package/build/Dialog/style.js +6 -2
  26. package/build/Effect/useInterval.js +6 -3
  27. package/build/Fixed/index.js +13 -22
  28. package/build/Flex/FlexItem.d.ts +11 -0
  29. package/build/Flex/FlexItem.js +26 -0
  30. package/build/Flex/index.d.ts +2 -10
  31. package/build/Flex/index.js +12 -22
  32. package/build/Indicator/index.d.ts +9 -6
  33. package/build/Indicator/index.js +34 -37
  34. package/build/Indicator/style.d.ts +4 -3
  35. package/build/Indicator/style.js +8 -13
  36. package/build/Loading/Wrapper.js +2 -1
  37. package/build/Loading/style.js +9 -12
  38. package/build/Overlay/index.js +6 -1
  39. package/build/RegionPicker/data.d.ts +6 -0
  40. package/build/RegionPicker/data.js +14486 -0
  41. package/build/RegionPicker/index.d.ts +33 -0
  42. package/build/RegionPicker/index.js +205 -0
  43. package/build/RegionPicker/style.d.ts +4 -0
  44. package/build/RegionPicker/style.js +187 -0
  45. package/build/SafeArea/index.js +14 -17
  46. package/build/ScrollView/index.d.ts +23 -11
  47. package/build/ScrollView/index.js +132 -118
  48. package/build/ScrollView/style.d.ts +1 -1
  49. package/build/ScrollView/style.js +33 -22
  50. package/build/Toast/Toast.d.ts +0 -1
  51. package/build/Toast/Toast.js +6 -4
  52. package/build/Toast/style.d.ts +3 -10
  53. package/build/Toast/style.js +41 -45
  54. package/build/index.d.ts +3 -0
  55. package/build/index.js +7 -1
  56. package/build/utils/color.d.ts +5 -0
  57. package/build/utils/color.js +18 -0
  58. package/build/utils/dom.js +4 -3
  59. package/build/utils/theme.d.ts +2 -0
  60. package/build/utils/theme.js +7 -0
  61. package/package.json +1 -1
  62. package/test/src/date-picker/index.jsx +119 -0
  63. package/test/src/index/index.jsx +2 -0
  64. package/test/src/index.jsx +1 -0
  65. package/test/src/loading/index.jsx +2 -2
  66. package/test/src/region-picker/index.jsx +120 -0
  67. package/test/src/scrollview/BasicSection.jsx +56 -0
  68. package/test/src/scrollview/CustomLoadingSection.jsx +53 -0
  69. package/test/src/scrollview/HeightModeSection.jsx +42 -0
  70. package/test/src/scrollview/ImperativeSection.jsx +56 -0
  71. package/test/src/scrollview/NotScrollableSection.jsx +32 -0
  72. package/test/src/scrollview/PerfSection.jsx +34 -0
  73. package/test/src/scrollview/index.css +92 -8
  74. package/test/src/scrollview/index.jsx +13 -45
  75. package/test/src/toast/index.jsx +1 -0
@@ -13,87 +13,93 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.CarouselNotice = CarouselNotice;
15
15
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
16
- const react_1 = require("@emotion/react");
17
- const react_2 = require("react");
16
+ const react_1 = require("react");
17
+ const react_2 = require("@emotion/react");
18
18
  const useInterval_1 = require("../Effect/useInterval");
19
19
  const style_1 = require("./style");
20
+ const justifyMap = {
21
+ start: style_1.style.justifyStart,
22
+ center: style_1.style.justifyCenter,
23
+ end: style_1.style.justifyEnd,
24
+ };
20
25
  /**
21
26
  * 滚动循环轮播公告
22
- * @param props
27
+ *
28
+ * 实现要点:
29
+ * - 渲染当前条 + 下一条共两项,wrapper 高 200%
30
+ * - 用 keyframes 把 wrapper 从 0 推到 -50%,配合 fill-mode: forwards
31
+ * 保证动画结束后定格在 -50%,与 React 提交解耦,杜绝"回弹闪烁"
32
+ * - animationend 触发后同步切换 current(新 current 的第一项 = 旧 current 的第二项)
33
+ * 且 wrapper 因 key 变化重挂载,回到 0 即开始下一轮
23
34
  */
24
35
  function CarouselNotice(props) {
25
- const { width, height, justify = 'center', interval = 3000, duration = 200, list = [], containerStyle, wrapperStyle, itemStyle } = props, attrs = __rest(props, ["width", "height", "justify", "interval", "duration", "list", "containerStyle", "wrapperStyle", "itemStyle"]);
26
- const [current, setCurrent] = (0, react_2.useState)(0);
27
- const [animation, setAnimation] = (0, react_2.useState)(false);
28
- /**
29
- * 一旦列表发生更新时,触发的逻辑
30
- */
31
- (0, react_2.useEffect)(() => {
36
+ var _a;
37
+ const { width, height, justify = "center", interval = 3000, duration = 400, list, containerStyle, wrapperStyle, itemStyle } = props, attrs = __rest(props, ["width", "height", "justify", "interval", "duration", "list", "containerStyle", "wrapperStyle", "itemStyle"]);
38
+ const safeList = Array.isArray(list) ? list : [];
39
+ const len = safeList.length;
40
+ const [current, setCurrent] = (0, react_1.useState)(0);
41
+ const [animating, setAnimating] = (0, react_1.useState)(false);
42
+ // list 变化:归零 + 取消动画态;用引用比较保险
43
+ const lastListRef = (0, react_1.useRef)(safeList);
44
+ if (lastListRef.current !== safeList) {
45
+ lastListRef.current = safeList;
46
+ }
47
+ (0, react_1.useEffect)(() => {
32
48
  setCurrent(0);
33
- setAnimation(false);
34
- }, [list]);
35
- /**
36
- * 每隔多少秒更新一次动画
37
- */
38
- (0, useInterval_1.useInterval)(() => {
39
- setAnimation(true);
40
- }, list.length > 1 ? interval : null);
41
- /**
42
- * 当前显示的两条数据,只用两条数据来轮播
43
- */
44
- const showContent = () => {
45
- const justifyStyle = {};
46
- if (justify === 'center') {
47
- justifyStyle.justifyContent = 'center';
48
- }
49
- else if (justify === 'start') {
50
- justifyStyle.justifyContent = 'flex-start';
51
- }
52
- else if (justify === 'end') {
53
- justifyStyle.justifyContent = 'flex-end';
54
- }
55
- else {
56
- justifyStyle.justifyContent = 'center';
49
+ setAnimating(false);
50
+ }, [safeList]);
51
+ // current 越界保护(list 缩短时)
52
+ (0, react_1.useEffect)(() => {
53
+ if (len > 0 && current >= len) {
54
+ setCurrent(0);
55
+ setAnimating(false);
57
56
  }
58
- const itemCss = [style_1.style.item, justifyStyle];
59
- if (list.length === 1) {
60
- return ((0, jsx_runtime_1.jsx)("div", { css: [itemCss, itemStyle], children: list[0] }, 0));
61
- }
62
- const showList = [];
63
- if (current === list.length - 1) {
64
- showList.push((0, jsx_runtime_1.jsx)("div", { css: [itemCss, itemStyle], children: list[list.length - 1] }, current));
65
- showList.push((0, jsx_runtime_1.jsx)("div", { css: [itemCss, itemStyle], children: list[0] }, 0));
66
- }
67
- else {
68
- showList.push((0, jsx_runtime_1.jsx)("div", { css: [itemCss, itemStyle], children: list[current] }, current));
69
- showList.push((0, jsx_runtime_1.jsx)("div", { css: [itemCss, itemStyle], children: list[current + 1] }, current + 1));
70
- }
71
- return showList;
72
- };
73
- /**
74
- * 获取动画
75
- */
76
- const getAnimation = () => {
77
- if (!animation || list.length <= 1) {
78
- return null;
79
- }
80
- return (0, react_1.css)({
81
- animationName: style_1.Bubble,
82
- animationTimingFunction: 'linear',
83
- animationDuration: `${duration}ms`,
84
- });
85
- };
86
- /**
87
- * 一轮动画结束时触发下一轮
88
- */
89
- const animationEnd = () => {
90
- let newIndex = current + 1;
91
- if (current >= list.length - 1) {
92
- newIndex = 0;
57
+ }, [len, current]);
58
+ // 定时触发动画;只有列表 >1 条且未在动画中才启动 interval
59
+ (0, useInterval_1.useInterval)(() => {
60
+ // 若上一轮动画还没结束(极端时序),跳过本次
61
+ if (!animating)
62
+ setAnimating(true);
63
+ }, len > 1 ? interval : null);
64
+ // 容器尺寸样式(仅在 width/height 变化时重建)
65
+ const sizeStyle = (0, react_1.useMemo)(() => ({ width, height }), [width, height]);
66
+ // 对齐样式(静态映射,无新对象产生)
67
+ const justifyStyle = (_a = justifyMap[justify]) !== null && _a !== void 0 ? _a : style_1.style.justifyCenter;
68
+ // 动画 css(仅依赖 duration,缓存复用)
69
+ const animationCss = (0, react_1.useMemo)(() => (0, react_2.css)({
70
+ animationName: `${style_1.Bubble}`,
71
+ animationTimingFunction: "ease-in-out",
72
+ animationDuration: `${duration}ms`,
73
+ animationFillMode: "forwards",
74
+ }), [duration]);
75
+ // 动画结束:切到下一条 + 重置动画态
76
+ const handleAnimationEnd = (0, react_1.useCallback)((e) => {
77
+ // 防止子元素动画事件冒泡误触
78
+ if (e.currentTarget !== e.target)
79
+ return;
80
+ setCurrent((prev) => (prev + 1) % len);
81
+ setAnimating(false);
82
+ }, [len]);
83
+ // 当前显示的两条
84
+ const items = (0, react_1.useMemo)(() => {
85
+ if (len === 0)
86
+ return [];
87
+ if (len === 1) {
88
+ return [
89
+ (0, jsx_runtime_1.jsx)("div", { css: [style_1.style.item, justifyStyle, itemStyle], children: safeList[0] }, "only"),
90
+ ];
93
91
  }
94
- setCurrent(newIndex);
95
- setAnimation(false);
96
- };
97
- return (Array.isArray(list) &&
98
- list.length > 0 && ((0, jsx_runtime_1.jsx)("div", Object.assign({}, attrs, { css: [style_1.style.box, { width, height }, containerStyle], children: (0, jsx_runtime_1.jsx)("div", { onAnimationEnd: animationEnd, css: [style_1.style.wrapper, getAnimation(), wrapperStyle], children: showContent() }) }))));
92
+ const nextIndex = (current + 1) % len;
93
+ return [
94
+ (0, jsx_runtime_1.jsx)("div", { css: [style_1.style.item, justifyStyle, itemStyle], children: safeList[current] }, `a-${current}`),
95
+ (0, jsx_runtime_1.jsx)("div", { css: [style_1.style.item, justifyStyle, itemStyle], children: safeList[nextIndex] }, `b-${nextIndex}`),
96
+ ];
97
+ }, [safeList, current, len, justifyStyle, itemStyle]);
98
+ if (len === 0)
99
+ return null;
100
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({}, attrs, { css: [style_1.style.box, sizeStyle, containerStyle], children: (0, jsx_runtime_1.jsx)("div", { onAnimationEnd: handleAnimationEnd, css: [
101
+ style_1.style.wrapper,
102
+ animating && len > 1 ? animationCss : null,
103
+ wrapperStyle,
104
+ ], children: items }, animating ? `anim-${current}` : `idle-${current}`) })));
99
105
  }
@@ -2,20 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.style = exports.Bubble = void 0;
4
4
  const react_1 = require("@emotion/react");
5
+ // translate 走百分比;fill-mode: forwards 保证动画结束后定格在 -50%,
6
+ // 与 React 的 setState 提交顺序解耦,杜绝"动画结束帧"回弹到 0 造成的闪烁
5
7
  exports.Bubble = (0, react_1.keyframes) `
6
8
  from {
7
- transform: translateY(0);
9
+ transform: translate3d(0, 0, 0);
8
10
  }
9
11
  to {
10
- transform: translateY(-50%);
12
+ transform: translate3d(0, -50%, 0);
11
13
  }
12
14
  `;
13
15
  exports.style = {
14
16
  box: {
15
17
  position: "relative",
16
18
  overflow: "hidden",
17
- transition: "all 200ms",
18
- height: '.8rem',
19
+ height: ".8rem",
19
20
  },
20
21
  wrapper: {
21
22
  position: "absolute",
@@ -23,6 +24,10 @@ exports.style = {
23
24
  top: 0,
24
25
  width: "100%",
25
26
  height: "200%",
27
+ // 提示分层,避免动画期间反复合成层切换
28
+ willChange: "transform",
29
+ // 默认静止态,由 animation 驱动
30
+ transform: "translate3d(0, 0, 0)",
26
31
  },
27
32
  item: {
28
33
  width: "100%",
@@ -30,5 +35,10 @@ exports.style = {
30
35
  display: "flex",
31
36
  alignItems: "center",
32
37
  fontSize: "initial",
38
+ // 避免子元素文本溢出影响 box 高度计算
39
+ overflow: "hidden",
33
40
  },
41
+ justifyStart: { justifyContent: "flex-start" },
42
+ justifyCenter: { justifyContent: "center" },
43
+ justifyEnd: { justifyContent: "flex-end" },
34
44
  };
@@ -4,7 +4,7 @@ exports.CitySelect = CitySelect;
4
4
  exports.showCitySelect = showCitySelect;
5
5
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
6
6
  const react_1 = require("react");
7
- const dom_1 = require("../utils/dom");
7
+ const Dialog_1 = require("../Dialog");
8
8
  const data_1 = require("./data");
9
9
  const search_1 = require("./search");
10
10
  const style_1 = require("./style");
@@ -33,7 +33,7 @@ function findCity(key) {
33
33
  }
34
34
  return null;
35
35
  }
36
- // 滑入/滑出动画在 style.inner transition 中定义
36
+ // 弹出与滑入/滑出动画由 showDialog (pullLeft) 接管
37
37
  function CitySelect(props) {
38
38
  const { onClose, onSelect, onLetterChange, getLocation, primary = style_1.DEFAULT_PRIMARY, } = props;
39
39
  const style = (0, react_1.useMemo)(() => (0, style_1.createStyle)(primary), [primary]);
@@ -79,8 +79,6 @@ function CitySelect(props) {
79
79
  const [touching, setTouching] = (0, react_1.useState)(false);
80
80
  const [keyword, setKeyword] = (0, react_1.useState)("");
81
81
  const [composing, setComposing] = (0, react_1.useState)(false);
82
- // 动画状态:entering 刚挂载还未滑入;active 已滑入到位;exiting 正在滑出
83
- const [phase, setPhase] = (0, react_1.useState)("entering");
84
82
  // IME 合成期间不触发搜索,避免中文输入时的拼音中间态干扰结果
85
83
  const searchResult = (0, react_1.useMemo)(() => {
86
84
  if (composing)
@@ -93,37 +91,7 @@ function CitySelect(props) {
93
91
  lettersRef.current = letters;
94
92
  const onLetterChangeRef = (0, react_1.useRef)(onLetterChange);
95
93
  onLetterChangeRef.current = onLetterChange;
96
- // 进入动画:下一帧切换到 active,触发 CSS transition
97
- (0, react_1.useEffect)(() => {
98
- const id = requestAnimationFrame(() => {
99
- // 再套一层,确保初始 transform 已被浏览器应用
100
- requestAnimationFrame(() => setPhase("active"));
101
- });
102
- return () => cancelAnimationFrame(id);
103
- }, []);
104
- // 触发退出动画,真正的 onClose 在 transitionend 中触发
105
- const triggerClose = () => {
106
- if (phase === "exiting")
107
- return;
108
- // 进入动画未完成前退出:进入态和退出态 transform 相同,
109
- // 切到 exiting 不会产生 transform 变化,transitionend 不会触发;
110
- // 此时面板尚未滑入,直接调用 onClose 即可
111
- if (phase === "entering") {
112
- onClose === null || onClose === void 0 ? void 0 : onClose();
113
- return;
114
- }
115
- setPhase("exiting");
116
- };
117
- // 监听 inner 的 transform 过渡结束,退出阶段调用 onClose
118
- const handleTransitionEnd = (e) => {
119
- if (e.target !== e.currentTarget)
120
- return;
121
- if (e.propertyName !== "transform")
122
- return;
123
- if (phase === "exiting")
124
- onClose === null || onClose === void 0 ? void 0 : onClose();
125
- };
126
- // 选中城市:回调后触发退出动画
94
+ // 选中城市:回调后请求关闭(动画与卸载交由 Dialog 处理)
127
95
  const handleSelect = (city) => {
128
96
  var _a;
129
97
  const province = data_1.provinceData[city.pcode];
@@ -135,7 +103,7 @@ function CitySelect(props) {
135
103
  code: city.pcode,
136
104
  },
137
105
  });
138
- triggerClose();
106
+ onClose === null || onClose === void 0 ? void 0 : onClose();
139
107
  };
140
108
  const measureSections = () => {
141
109
  const listEl = listRef.current;
@@ -201,7 +169,7 @@ function CitySelect(props) {
201
169
  if (!el)
202
170
  return;
203
171
  let lastLetter = null;
204
- const updateByTouch = (clientY) => {
172
+ const updateByY = (clientY) => {
205
173
  var _a;
206
174
  const list = lettersRef.current;
207
175
  if (list.length === 0)
@@ -221,28 +189,63 @@ function CitySelect(props) {
221
189
  (_a = onLetterChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onLetterChangeRef, letter);
222
190
  }
223
191
  };
224
- const handleTouchStart = (e) => {
192
+ // 优先使用触摸事件;不支持触摸才回退到鼠标事件,避免两套并存冲突
193
+ const isTouch = typeof window !== "undefined" &&
194
+ ("ontouchstart" in window || navigator.maxTouchPoints > 0);
195
+ if (isTouch) {
196
+ const handleTouchStart = (e) => {
197
+ setTouching(true);
198
+ updateByY(e.touches[0].clientY);
199
+ };
200
+ const handleTouchMove = (e) => {
201
+ // 阻止滚动,需要非 passive 监听器
202
+ e.preventDefault();
203
+ updateByY(e.touches[0].clientY);
204
+ };
205
+ const handleTouchEnd = () => {
206
+ setTouching(false);
207
+ lastLetter = null;
208
+ };
209
+ el.addEventListener("touchstart", handleTouchStart, { passive: false });
210
+ el.addEventListener("touchmove", handleTouchMove, { passive: false });
211
+ el.addEventListener("touchend", handleTouchEnd);
212
+ el.addEventListener("touchcancel", handleTouchEnd);
213
+ return () => {
214
+ el.removeEventListener("touchstart", handleTouchStart);
215
+ el.removeEventListener("touchmove", handleTouchMove);
216
+ el.removeEventListener("touchend", handleTouchEnd);
217
+ el.removeEventListener("touchcancel", handleTouchEnd);
218
+ };
219
+ }
220
+ // 鼠标端:按下后拖动,move/up 绑在 document 以支持拖出 sidebar
221
+ let dragging = false;
222
+ const handleMouseDown = (e) => {
223
+ if (e.button !== 0)
224
+ return;
225
+ e.preventDefault();
226
+ dragging = true;
225
227
  setTouching(true);
226
- updateByTouch(e.touches[0].clientY);
228
+ updateByY(e.clientY);
227
229
  };
228
- const handleTouchMove = (e) => {
229
- // 阻止滚动,需要非 passive 监听器
230
- e.preventDefault();
231
- updateByTouch(e.touches[0].clientY);
230
+ const handleMouseMove = (e) => {
231
+ if (!dragging)
232
+ return;
233
+ updateByY(e.clientY);
232
234
  };
233
- const handleTouchEnd = () => {
235
+ const handleMouseUp = () => {
236
+ if (!dragging)
237
+ return;
238
+ dragging = false;
234
239
  setTouching(false);
235
240
  lastLetter = null;
236
241
  };
237
- el.addEventListener("touchstart", handleTouchStart, { passive: false });
238
- el.addEventListener("touchmove", handleTouchMove, { passive: false });
239
- el.addEventListener("touchend", handleTouchEnd);
240
- el.addEventListener("touchcancel", handleTouchEnd);
242
+ el.addEventListener("mousedown", handleMouseDown);
243
+ document.addEventListener("mousemove", handleMouseMove);
244
+ document.addEventListener("mouseup", handleMouseUp);
241
245
  return () => {
242
- el.removeEventListener("touchstart", handleTouchStart);
243
- el.removeEventListener("touchmove", handleTouchMove);
244
- el.removeEventListener("touchend", handleTouchEnd);
245
- el.removeEventListener("touchcancel", handleTouchEnd);
246
+ el.removeEventListener("mousedown", handleMouseDown);
247
+ document.removeEventListener("mousemove", handleMouseMove);
248
+ document.removeEventListener("mouseup", handleMouseUp);
246
249
  };
247
250
  }, [isSearching]);
248
251
  // 手动滚动城市列表时,根据当前最靠上的区块激活对应字母(rAF 节流)
@@ -276,25 +279,32 @@ function CitySelect(props) {
276
279
  cancelAnimationFrame(rafId);
277
280
  };
278
281
  }, [isSearching]);
279
- return ((0, jsx_runtime_1.jsx)("div", { css: [style.container], children: (0, jsx_runtime_1.jsxs)("div", { css: [
280
- style.inner,
281
- phase === "entering" && style.innerEnter,
282
- phase === "active" && style.innerActive,
283
- phase === "exiting" && style.innerExit,
284
- ], onTransitionEnd: handleTransitionEnd, children: [(0, jsx_runtime_1.jsxs)(Col_1.ColStart, { css: style.original, alignItems: "stretch", children: [(0, jsx_runtime_1.jsx)("div", { css: style.top, children: (0, jsx_runtime_1.jsxs)(Row_1.RowStart, { children: [(0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 1024 1024", css: style.icon, children: (0, jsx_runtime_1.jsx)("path", { d: "M896 870.4l-128-128c55.467-68.267 89.6-149.333 89.6-238.933 0-98.134-38.4-192-110.933-264.534-149.334-149.333-384-149.333-533.334-4.266-145.066 145.066-145.066 384 0 529.066 72.534 72.534 166.4 110.934 264.534 110.934 89.6 0 174.933-29.867 238.933-89.6l128 128c4.267 4.266 12.8 8.533 21.333 8.533s17.067-4.267 21.334-8.533c17.066-8.534 17.066-29.867 8.533-42.667zM260.267 721.067c-119.467-123.734-119.467-320 0-439.467 59.733-59.733 140.8-89.6 217.6-89.6 81.066 0 157.866 29.867 217.6 89.6 59.733 59.733 89.6 136.533 89.6 217.6 0 81.067-34.134 162.133-89.6 217.6-55.467 59.733-132.267 93.867-217.6 93.867-81.067 0-157.867-34.134-217.6-89.6z" }) }), (0, jsx_runtime_1.jsx)("input", { css: style.input, placeholder: "\u8BF7\u8F93\u5165\u5B57\u6BCD\u6216\u6C49\u5B57\u641C\u7D22\u57CE\u5E02", value: keyword, onChange: (e) => setKeyword(e.target.value), onCompositionStart: () => setComposing(true), onCompositionEnd: (e) => {
285
- setComposing(false);
286
- setKeyword(e.currentTarget.value);
287
- } }), (0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: style.exit, onClick: triggerClose, children: "\u9000\u51FA" })] }) }), !isSearching && locatedCity && ((0, jsx_runtime_1.jsxs)(Clickable_1.Clickable, { css: style.locate, onClick: () => handleSelect(locatedCity), children: [(0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 1024 1024", css: style.locateIcon, children: [(0, jsx_runtime_1.jsx)("path", { d: "M512 128C352 128 224 256 224 416c0 115.2 70.4 204.8 160 320 32 44.8 70.4 89.6 102.4 147.2C492.8 889.6 499.2 896 512 896l0 0c12.8 0 19.2-6.4 25.6-12.8 32-51.2 70.4-96 102.4-140.8 89.6-121.6 160-211.2 160-326.4C800 256 672 128 512 128zM588.8 704c-25.6 32-51.2 64-76.8 102.4-25.6-38.4-57.6-76.8-76.8-108.8-83.2-108.8-147.2-192-147.2-281.6C288 294.4 390.4 192 512 192s224 102.4 224 224C736 505.6 672 595.2 588.8 704z" }), (0, jsx_runtime_1.jsx)("path", { d: "M512 416m-96 0a1.5 1.5 0 1 0 192 0 1.5 1.5 0 1 0-192 0Z" })] }), (0, jsx_runtime_1.jsx)("span", { css: style.locateLabel, children: "\u5F53\u524D\u5B9A\u4F4D" }), (0, jsx_runtime_1.jsx)("span", { css: style.locateName, children: locatedCity.name })] })), isSearching ? (searchResult.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { css: style.searchList, children: searchResult.map((item) => ((0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: style.item, onClick: () => handleSelect(item), children: item.name }, item.code))) })) : ((0, jsx_runtime_1.jsx)("div", { css: style.empty, children: "\u6CA1\u6709\u5339\u914D\u7684\u57CE\u5E02" }))) : ((0, jsx_runtime_1.jsx)("div", { ref: listRef, css: style.list, children: letters.map((k) => {
288
- return (
289
- // 每个字母对应一个区块,包含该字母开头的城市列表
290
- (0, jsx_runtime_1.jsxs)("div", { ref: (el) => {
291
- sectionRefs.current[k] = el;
292
- }, children: [(0, jsx_runtime_1.jsx)("div", { css: style.title, children: k.toUpperCase() }), data_1.cityData[k].map((item) => ((0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: style.item, onClick: () => handleSelect(item), children: item.name }, item.code)))] }, k));
293
- }) }))] }), !isSearching && ((0, jsx_runtime_1.jsx)("div", { ref: sidebarRef, css: style.sidebar, children: letters.map((k) => ((0, jsx_runtime_1.jsx)("div", { css: [style.letter, activeLetter === k && style.letterActive], children: k.toUpperCase() }, k))) })), touching && activeLetter && !isSearching && ((0, jsx_runtime_1.jsx)("div", { css: style.bigLetter, children: activeLetter.toUpperCase() }))] }) }));
282
+ return ((0, jsx_runtime_1.jsxs)("div", { css: style.inner, children: [(0, jsx_runtime_1.jsxs)(Col_1.ColStart, { css: style.original, alignItems: "stretch", children: [(0, jsx_runtime_1.jsx)("div", { css: style.top, children: (0, jsx_runtime_1.jsxs)(Row_1.RowStart, { children: [(0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 1024 1024", css: style.icon, children: (0, jsx_runtime_1.jsx)("path", { d: "M896 870.4l-128-128c55.467-68.267 89.6-149.333 89.6-238.933 0-98.134-38.4-192-110.933-264.534-149.334-149.333-384-149.333-533.334-4.266-145.066 145.066-145.066 384 0 529.066 72.534 72.534 166.4 110.934 264.534 110.934 89.6 0 174.933-29.867 238.933-89.6l128 128c4.267 4.266 12.8 8.533 21.333 8.533s17.067-4.267 21.334-8.533c17.066-8.534 17.066-29.867 8.533-42.667zM260.267 721.067c-119.467-123.734-119.467-320 0-439.467 59.733-59.733 140.8-89.6 217.6-89.6 81.066 0 157.866 29.867 217.6 89.6 59.733 59.733 89.6 136.533 89.6 217.6 0 81.067-34.134 162.133-89.6 217.6-55.467 59.733-132.267 93.867-217.6 93.867-81.067 0-157.867-34.134-217.6-89.6z" }) }), (0, jsx_runtime_1.jsx)("input", { css: style.input, placeholder: "\u8BF7\u8F93\u5165\u5B57\u6BCD\u6216\u6C49\u5B57\u641C\u7D22\u57CE\u5E02", value: keyword, onChange: (e) => setKeyword(e.target.value), onCompositionStart: () => setComposing(true), onCompositionEnd: (e) => {
283
+ setComposing(false);
284
+ setKeyword(e.currentTarget.value);
285
+ } }), (0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: style.exit, onClick: () => onClose === null || onClose === void 0 ? void 0 : onClose(), children: "\u9000\u51FA" })] }) }), !isSearching && locatedCity && ((0, jsx_runtime_1.jsxs)(Clickable_1.Clickable, { css: style.locate, onClick: () => handleSelect(locatedCity), children: [(0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 1024 1024", css: style.locateIcon, children: [(0, jsx_runtime_1.jsx)("path", { d: "M512 128C352 128 224 256 224 416c0 115.2 70.4 204.8 160 320 32 44.8 70.4 89.6 102.4 147.2C492.8 889.6 499.2 896 512 896l0 0c12.8 0 19.2-6.4 25.6-12.8 32-51.2 70.4-96 102.4-140.8 89.6-121.6 160-211.2 160-326.4C800 256 672 128 512 128zM588.8 704c-25.6 32-51.2 64-76.8 102.4-25.6-38.4-57.6-76.8-76.8-108.8-83.2-108.8-147.2-192-147.2-281.6C288 294.4 390.4 192 512 192s224 102.4 224 224C736 505.6 672 595.2 588.8 704z" }), (0, jsx_runtime_1.jsx)("path", { d: "M512 416m-96 0a1.5 1.5 0 1 0 192 0 1.5 1.5 0 1 0-192 0Z" })] }), (0, jsx_runtime_1.jsx)("span", { css: style.locateLabel, children: "\u5F53\u524D\u5B9A\u4F4D" }), (0, jsx_runtime_1.jsx)("span", { css: style.locateName, children: locatedCity.name })] })), isSearching ? (searchResult.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { css: style.searchList, children: searchResult.map((item) => ((0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: style.item, onClick: () => handleSelect(item), children: item.name }, item.code))) })) : ((0, jsx_runtime_1.jsx)("div", { css: style.empty, children: "\u6CA1\u6709\u5339\u914D\u7684\u57CE\u5E02" }))) : ((0, jsx_runtime_1.jsx)("div", { ref: listRef, css: style.list, children: letters.map((k) => {
286
+ return (
287
+ // 每个字母对应一个区块,包含该字母开头的城市列表
288
+ (0, jsx_runtime_1.jsxs)("div", { ref: (el) => {
289
+ sectionRefs.current[k] = el;
290
+ }, children: [(0, jsx_runtime_1.jsx)("div", { css: style.title, children: k.toUpperCase() }), data_1.cityData[k].map((item) => ((0, jsx_runtime_1.jsx)(Clickable_1.Clickable, { css: style.item, onClick: () => handleSelect(item), children: item.name }, item.code)))] }, k));
291
+ }) }))] }), !isSearching && ((0, jsx_runtime_1.jsx)("div", { ref: sidebarRef, css: style.sidebar, children: letters.map((k) => ((0, jsx_runtime_1.jsx)("div", { css: [style.letter, activeLetter === k && style.letterActive], children: k.toUpperCase() }, k))) })), touching && activeLetter && !isSearching && ((0, jsx_runtime_1.jsx)("div", { css: style.bigLetter, children: activeLetter.toUpperCase() }))] }));
294
292
  }
295
293
  function showCitySelect(options = {}) {
296
- const container = (0, dom_1.createPortalDOM)();
297
- container.mount((0, jsx_runtime_1.jsx)(CitySelect, Object.assign({}, options, { onClose: () => {
298
- container.unmount();
299
- } })));
294
+ let closing = false;
295
+ let close;
296
+ const requestClose = () => {
297
+ if (closing)
298
+ return;
299
+ closing = true;
300
+ close === null || close === void 0 ? void 0 : close();
301
+ };
302
+ close = (0, Dialog_1.showDialog)({
303
+ type: "pullLeft",
304
+ // 全屏不透明面板,无需遮罩
305
+ showMask: false,
306
+ // 给 Dialog 的 pullLeft 容器拉满宽度(默认仅 right:0/height:100%)
307
+ boxStyle: { left: 0, width: "100%" },
308
+ content: (0, jsx_runtime_1.jsx)(CitySelect, Object.assign({}, options, { onClose: requestClose })),
309
+ });
300
310
  }
@@ -3,63 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_PRIMARY = void 0;
4
4
  exports.createStyle = createStyle;
5
5
  const react_1 = require("@emotion/react");
6
- // 设计变量(除 primary 外集中管理)
7
- const textPrimary = "#1f2328";
8
- const textSecondary = "#6b7280";
9
- const textTertiary = "#9ca3af";
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 / placeholder
10
12
  const bgPage = "#ffffff";
11
- const bgSubtle = "#f5f6f8";
12
- const border = "#e5e7eb";
13
- const fontStack = '-apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif';
14
- // 将 #rrggbb 颜色按比例变暗,用于派生 primaryActive
15
- function darken(hex, amount) {
16
- const m = hex.replace("#", "");
17
- if (m.length !== 6)
18
- return hex;
19
- const r = parseInt(m.slice(0, 2), 16);
20
- const g = parseInt(m.slice(2, 4), 16);
21
- const b = parseInt(m.slice(4, 6), 16);
22
- const f = (v) => Math.max(0, Math.min(255, Math.round(v * (1 - amount))));
23
- const toHex = (v) => f(v).toString(16).padStart(2, "0");
24
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
25
- }
13
+ const bgSubtle = "rgba(120,120,128,.12)"; // iOS quaternary fill
14
+ const bgGrouped = "#f2f2f7"; // iOS systemGroupedBackground
26
15
  // 根据 primary 色值生成一整套样式;primaryActive 由 primary 派生
27
16
  function createStyle(primary) {
28
- const primaryActive = darken(primary, 0.15);
17
+ const primaryActive = (0, color_1.darken)(primary, 0.15);
29
18
  return {
30
- container: (0, react_1.css)({
31
- position: "fixed",
32
- top: 0,
33
- left: 0,
19
+ // 内容容器:动画/全屏由 Dialog (pullLeft) 提供,这里只保留视觉与排版。
20
+ // 内部 sidebar / bigLetter 使用 absolute 时,以此为定位上下文。
21
+ inner: (0, react_1.css)({
22
+ position: "relative",
34
23
  width: "100%",
35
24
  height: "100%",
36
- zIndex: 9999,
37
- overflow: "hidden",
25
+ backgroundColor: bgPage,
38
26
  userSelect: "none",
39
27
  color: textPrimary,
40
- fontFamily: fontStack,
28
+ fontFamily: theme_1.fontStack,
41
29
  WebkitFontSmoothing: "antialiased",
42
30
  MozOsxFontSmoothing: "grayscale",
43
31
  }),
44
- inner: (0, react_1.css)({
45
- position: "absolute",
46
- top: 0,
47
- left: 0,
48
- width: "100%",
49
- height: "100%",
50
- backgroundColor: bgPage,
51
- transition: "transform .3s cubic-bezier(.22,.61,.36,1)",
52
- willChange: "transform",
53
- }),
54
- innerEnter: (0, react_1.css)({
55
- transform: "translateX(100%)",
56
- }),
57
- innerActive: (0, react_1.css)({
58
- transform: "translateX(0)",
59
- }),
60
- innerExit: (0, react_1.css)({
61
- transform: "translateX(100%)",
62
- }),
63
32
  sidebar: (0, react_1.css)({
64
33
  position: "absolute",
65
34
  top: "50%",
@@ -97,7 +66,7 @@ function createStyle(primary) {
97
66
  top: "50%",
98
67
  left: "50%",
99
68
  transform: "translate(-50%, -50%)",
100
- backgroundColor: "rgba(17,24,39,0.6)",
69
+ backgroundColor: "rgba(0,0,0,0.55)",
101
70
  color: "#fff",
102
71
  fontSize: ".6rem",
103
72
  width: "1.4rem",
@@ -112,8 +81,7 @@ function createStyle(primary) {
112
81
  backgroundColor: bgPage,
113
82
  }),
114
83
  top: (0, react_1.css)({
115
- padding: ".24rem .3rem",
116
- borderBottom: `1px solid ${border}`,
84
+ padding: ".3rem",
117
85
  "& > div": {
118
86
  height: ".72rem",
119
87
  backgroundColor: bgSubtle,
@@ -169,8 +137,7 @@ function createStyle(primary) {
169
137
  locate: (0, react_1.css)({
170
138
  display: "flex",
171
139
  alignItems: "center",
172
- padding: ".2rem .23rem",
173
- borderBottom: `1px solid ${border}`,
140
+ padding: "0 .3rem .3rem",
174
141
  backgroundColor: bgPage,
175
142
  transition: "background-color .12s",
176
143
  "&:active": {
@@ -195,13 +162,13 @@ function createStyle(primary) {
195
162
  color: primary,
196
163
  }),
197
164
  title: (0, react_1.css)({
198
- padding: ".08rem .3rem",
165
+ padding: ".12rem .3rem .06rem",
199
166
  fontSize: ".22rem",
200
- fontWeight: 600,
201
- color: textSecondary,
167
+ fontWeight: 500,
168
+ color: textTertiary,
202
169
  letterSpacing: ".02rem",
203
170
  textTransform: "uppercase",
204
- backgroundColor: bgSubtle,
171
+ backgroundColor: bgGrouped,
205
172
  position: "sticky",
206
173
  top: 0,
207
174
  zIndex: 1,
@@ -210,7 +177,6 @@ function createStyle(primary) {
210
177
  padding: ".24rem .3rem",
211
178
  fontSize: ".3rem",
212
179
  color: textPrimary,
213
- borderBottom: `1px solid ${border}`,
214
180
  backgroundColor: bgPage,
215
181
  transition: "background-color .12s",
216
182
  "&:active": {
@@ -135,6 +135,13 @@ function Clickable(props) {
135
135
  };
136
136
  }
137
137
  }, [disable, touchable, onMouseUp]);
138
+ // 切换到 disable 时,立即清掉激活态,避免视觉上停留在按下样式
139
+ (0, react_1.useEffect)(() => {
140
+ if (disable) {
141
+ touchRef.current = false;
142
+ deactivate();
143
+ }
144
+ }, [disable, deactivate]);
138
145
  // 根据激活状态计算最终的 className 和 style
139
146
  const finalClassName = isActive && typeof activeClassName === 'string'
140
147
  ? (typeof className === 'string' ? `${className} ${activeClassName}` : activeClassName)
@@ -1,12 +1,20 @@
1
1
  import { Interpolation, Theme } from "@emotion/react";
2
- import React from "react";
2
+ import { ReactNode } from "react";
3
3
  export interface ContainerProps {
4
4
  globalStyle?: Interpolation<Theme>;
5
- children?: React.ReactNode;
5
+ children?: ReactNode;
6
6
  designWidth?: number;
7
+ maxWidth?: number;
7
8
  }
8
9
  /**
9
- * 自适应容器
10
- * @param props
10
+ * 自适应容器:所有使用本库的工程都需在根节点放置该组件,
11
+ * 否则各组件中的 rem 单位将无法自动跟随设备宽度缩放。
12
+ *
13
+ * 实现要点:
14
+ * - <Global> 通过 useInsertionEffect 早于 useLayoutEffect 注入样式,
15
+ * 所以首次 layout 阶段所有 rem 已使用正确 fontSize,无需阻塞 children
16
+ * - 浏览器字体缩放(用户系统调大字号)首挂载同步检测一次,scaleFactor 修正
17
+ * - resize 走 rAF 节流,桌面拖拽 / 模拟器切设备不会反复 setState
18
+ * - SSR 安全:所有 window 访问加 isBrowser 守卫
11
19
  */
12
20
  export declare function Container(props: ContainerProps): import("@emotion/react/jsx-runtime").JSX.Element;