clxx 2.1.8 → 3.0.1

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 (76) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +147 -22
  3. package/build/Alert/Wrapper.js +4 -3
  4. package/build/Alert/style.js +11 -7
  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 +30 -55
  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 -7
  53. package/build/Toast/style.js +33 -41
  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/createApp.d.ts +16 -2
  59. package/build/utils/createApp.js +142 -109
  60. package/build/utils/dom.js +4 -3
  61. package/build/utils/theme.d.ts +2 -0
  62. package/build/utils/theme.js +7 -0
  63. package/package.json +1 -1
  64. package/test/src/date-picker/index.jsx +119 -0
  65. package/test/src/index/index.jsx +2 -0
  66. package/test/src/index.jsx +1 -0
  67. package/test/src/loading/index.jsx +2 -2
  68. package/test/src/region-picker/index.jsx +120 -0
  69. package/test/src/scrollview/BasicSection.jsx +56 -0
  70. package/test/src/scrollview/CustomLoadingSection.jsx +53 -0
  71. package/test/src/scrollview/HeightModeSection.jsx +42 -0
  72. package/test/src/scrollview/ImperativeSection.jsx +56 -0
  73. package/test/src/scrollview/NotScrollableSection.jsx +32 -0
  74. package/test/src/scrollview/PerfSection.jsx +34 -0
  75. package/test/src/scrollview/index.css +92 -8
  76. package/test/src/scrollview/index.jsx +13 -45
@@ -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;
@@ -311,25 +279,32 @@ function CitySelect(props) {
311
279
  cancelAnimationFrame(rafId);
312
280
  };
313
281
  }, [isSearching]);
314
- return ((0, jsx_runtime_1.jsx)("div", { css: [style.container], children: (0, jsx_runtime_1.jsxs)("div", { css: [
315
- style.inner,
316
- phase === "entering" && style.innerEnter,
317
- phase === "active" && style.innerActive,
318
- phase === "exiting" && style.innerExit,
319
- ], 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) => {
320
- setComposing(false);
321
- setKeyword(e.currentTarget.value);
322
- } }), (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) => {
323
- return (
324
- // 每个字母对应一个区块,包含该字母开头的城市列表
325
- (0, jsx_runtime_1.jsxs)("div", { ref: (el) => {
326
- sectionRefs.current[k] = el;
327
- }, 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));
328
- }) }))] }), !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() }))] }));
329
292
  }
330
293
  function showCitySelect(options = {}) {
331
- const container = (0, dom_1.createPortalDOM)();
332
- container.mount((0, jsx_runtime_1.jsx)(CitySelect, Object.assign({}, options, { onClose: () => {
333
- container.unmount();
334
- } })));
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
+ });
335
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;
@@ -1,114 +1,119 @@
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
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.Container = Container;
37
4
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
38
5
  const react_1 = require("@emotion/react");
39
- const react_2 = __importStar(require("react"));
6
+ const react_2 = require("react");
40
7
  const useWindowResize_1 = require("../Effect/useWindowResize");
41
8
  const useViewport_1 = require("../Effect/useViewport");
9
+ const isBrowser = typeof window !== "undefined";
10
+ // SSR 时 useLayoutEffect 会 warn,统一降级
11
+ const useIsomorphicLayoutEffect = isBrowser ? react_2.useLayoutEffect : react_2.useEffect;
12
+ // 取当前视口宽度,可选 maxWidth 兑底(0 / Infinity 表示不限制)
13
+ function getViewportWidth(maxWidth) {
14
+ if (!isBrowser)
15
+ return 0;
16
+ const w = window.innerWidth || document.documentElement.clientWidth || 0;
17
+ if (!maxWidth || !isFinite(maxWidth))
18
+ return w;
19
+ return Math.min(w, maxWidth);
20
+ }
42
21
  /**
43
- * 自适应容器
44
- * @param props
22
+ * 自适应容器:所有使用本库的工程都需在根节点放置该组件,
23
+ * 否则各组件中的 rem 单位将无法自动跟随设备宽度缩放。
24
+ *
25
+ * 实现要点:
26
+ * - <Global> 通过 useInsertionEffect 早于 useLayoutEffect 注入样式,
27
+ * 所以首次 layout 阶段所有 rem 已使用正确 fontSize,无需阻塞 children
28
+ * - 浏览器字体缩放(用户系统调大字号)首挂载同步检测一次,scaleFactor 修正
29
+ * - resize 走 rAF 节流,桌面拖拽 / 模拟器切设备不会反复 setState
30
+ * - SSR 安全:所有 window 访问加 isBrowser 守卫
45
31
  */
46
32
  function Container(props) {
47
- const { designWidth = 750, globalStyle, children } = props;
48
- // 计算理论根字体大小(未经浏览器缩放修正)
49
- const calculateFontSize = (0, react_2.useCallback)((width) => {
50
- return (width * 100) / designWidth;
51
- }, [designWidth]);
52
- // 理论基准字体大小(跟随窗口尺寸变化)
53
- const [rawFontSize, setRawFontSize] = (0, react_2.useState)(() => calculateFontSize(window.innerWidth));
54
- // 浏览器字体缩放因子(>1 表示用户放大了系统字体,<1 表示缩小)
55
- // 独立存储,使得 resize 后缩放修正依然生效
33
+ const { designWidth = 750, maxWidth = 750, globalStyle, children } = props;
34
+ // 当前视口宽度
35
+ const [viewportWidth, setViewportWidth] = (0, react_2.useState)(() => getViewportWidth(maxWidth));
36
+ // 浏览器字体缩放因子(>1 用户放大系统字体),首挂载探测一次
56
37
  const [scaleFactor, setScaleFactor] = (0, react_2.useState)(1);
57
- // 是否已完成字体缩放检测
58
- const [isInitialized, setIsInitialized] = (0, react_2.useState)(false);
59
- // 修正后的字体大小:统一对所有字体计算应用缩放修正
60
- const correctedFontSize = (0, react_2.useMemo)(() => scaleFactor === 1
61
- ? rawFontSize
62
- : Math.round((rawFontSize / scaleFactor) * 10) / 10, [rawFontSize, scaleFactor]);
63
- // 字体缩放检测
64
- // Emotion <Global> 通过 useInsertionEffect 注入样式,早于 useLayoutEffect
65
- // 因此 useLayoutEffect 内 getComputedStyle 可正确读取已注入的字体大小
66
- // 检测和修正均在浏览器绘制前同步完成,避免闪烁
67
- (0, react_2.useLayoutEffect)(() => {
68
- // 缩放因子在页面生命周期内不变,只需检测一次
69
- if (isInitialized)
38
+ // 理论 fontSize(未修正)
39
+ const rawFontSize = (0, react_2.useMemo)(() => (viewportWidth * 100) / designWidth, [viewportWidth, designWidth]);
40
+ // 最终 fontSize:缩放修正 + 1 位小数,避免浮点抖动
41
+ const fontSize = (0, react_2.useMemo)(() => Math.round((scaleFactor === 1 ? rawFontSize : rawFontSize / scaleFactor) * 10) / 10, [rawFontSize, scaleFactor]);
42
+ // 浏览器字体缩放检测:仅首挂载执行一次,避免 setState 触发循环
43
+ const detectedRef = (0, react_2.useRef)(false);
44
+ useIsomorphicLayoutEffect(() => {
45
+ if (detectedRef.current || !isBrowser)
70
46
  return;
71
- const computedSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
72
- // 如果计算出的字体大小与期望不符(说明被浏览器字体设置影响了)
73
- // 容差 1px,避免浮点精度导致误判
74
- if (computedSize > 0 && Math.abs(computedSize - rawFontSize) > 1) {
75
- // 记录缩放因子,后续所有字体计算(包括 resize)都会自动应用
76
- setScaleFactor(computedSize / rawFontSize);
47
+ detectedRef.current = true;
48
+ const computed = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
49
+ // 容差 1px 屏蔽浮点误差
50
+ if (computed > 0 && Math.abs(computed - rawFontSize) > 1) {
51
+ setScaleFactor(computed / rawFontSize);
77
52
  }
78
- setIsInitialized(true);
79
- }, [rawFontSize, isInitialized]);
80
- // 窗口大小变化时更新理论字体大小
81
- // correctedFontSize 通过 useMemo 自动应用 scaleFactor 修正
53
+ // eslint-disable-next-line react-hooks/exhaustive-deps
54
+ }, []);
55
+ // maxWidth 变更时重算 viewportWidth
56
+ (0, react_2.useEffect)(() => {
57
+ setViewportWidth((prev) => {
58
+ const next = getViewportWidth(maxWidth);
59
+ return prev === next ? prev : next;
60
+ });
61
+ }, [maxWidth]);
62
+ // resize:rAF 节流;桌面拖动窗口、DevTools 切换设备模拟时也只每帧一次
63
+ const rafRef = (0, react_2.useRef)(null);
82
64
  (0, useWindowResize_1.useWindowResize)(() => {
83
- setRawFontSize(calculateFontSize(window.innerWidth));
65
+ if (!isBrowser || rafRef.current !== null)
66
+ return;
67
+ rafRef.current = requestAnimationFrame(() => {
68
+ rafRef.current = null;
69
+ const next = getViewportWidth(maxWidth);
70
+ setViewportWidth((prev) => (prev === next ? prev : next));
71
+ });
84
72
  });
85
- // 设置 viewport meta
73
+ // 组件卸载清理 pending rAF
74
+ (0, react_2.useEffect)(() => {
75
+ return () => {
76
+ if (rafRef.current !== null) {
77
+ cancelAnimationFrame(rafRef.current);
78
+ rafRef.current = null;
79
+ }
80
+ };
81
+ }, []);
82
+ // viewport meta
86
83
  (0, useViewport_1.useViewport)();
87
84
  // 激活 iOS 上的 :active 伪类
88
85
  (0, react_2.useEffect)(() => {
86
+ if (!isBrowser)
87
+ return;
89
88
  const noop = () => { };
90
89
  document.body.addEventListener("touchstart", noop, { passive: true });
91
90
  return () => {
92
91
  document.body.removeEventListener("touchstart", noop);
93
92
  };
94
93
  }, []);
95
- return ((0, jsx_runtime_1.jsxs)(react_2.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Global, { styles: [
96
- {
97
- "*": {
98
- boxSizing: "border-box",
99
- },
100
- html: {
101
- WebkitTapHighlightColor: "transparent",
102
- WebkitOverflowScrolling: "touch",
103
- WebkitTextSizeAdjust: "100%",
104
- fontSize: `${correctedFontSize}px`,
105
- touchAction: "manipulation",
106
- },
107
- body: {
108
- fontSize: "16px",
109
- margin: "0 auto",
110
- },
111
- },
112
- globalStyle,
113
- ] }), isInitialized ? children : null] }));
94
+ // 全局样式:fontSize 写入 html,rem 自动跟随
95
+ // body 以 maxWidth 居中,保证 PC 端上页面不超过设计宽度,两侧留白
96
+ // CSS 变量 --clxx-max-width 供 Overlay/Fixed 等使用 fixed 定位的组件读取
97
+ // 从而把弹窗 / 遮罩限制在视口内(fixed 默认参考浏览器窗口,无法继承 body 宽度)
98
+ const hasMaxWidth = !!maxWidth && isFinite(maxWidth);
99
+ const globalStyles = (0, react_2.useMemo)(() => [
100
+ {
101
+ ":root": {
102
+ "--clxx-max-width": hasMaxWidth ? `${maxWidth}px` : "100%",
103
+ },
104
+ "*": {
105
+ boxSizing: "border-box",
106
+ },
107
+ html: {
108
+ WebkitTapHighlightColor: "transparent",
109
+ WebkitOverflowScrolling: "touch",
110
+ WebkitTextSizeAdjust: "100%",
111
+ fontSize: `${fontSize}px`,
112
+ touchAction: "manipulation",
113
+ },
114
+ body: Object.assign({ fontSize: "16px", margin: "0 auto" }, (hasMaxWidth ? { maxWidth: `${maxWidth}px` } : null)),
115
+ },
116
+ globalStyle,
117
+ ], [fontSize, maxWidth, hasMaxWidth, globalStyle]);
118
+ return ((0, jsx_runtime_1.jsxs)(react_2.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Global, { styles: globalStyles }), children] }));
114
119
  }
@@ -50,12 +50,14 @@ const react_1 = __importStar(require("react"));
50
50
  const Countdown_1 = require("../utils/Countdown");
51
51
  const Row_1 = require("../Flex/Row");
52
52
  function Countdowner(props) {
53
- let { remain = 0, separator = ':', format = 'his', onUpdate, onEnd, separatorStyle, containerStyle, numberStyle, renderNumber, renderSeparator } = props, extra = __rest(props, ["remain", "separator", "format", "onUpdate", "onEnd", "separatorStyle", "containerStyle", "numberStyle", "renderNumber", "renderSeparator"]);
53
+ const { remain = 0, separator = ':', format = 'his', onUpdate, onEnd, separatorStyle, containerStyle, numberStyle, renderNumber, renderSeparator } = props, extra = __rest(props, ["remain", "separator", "format", "onUpdate", "onEnd", "separatorStyle", "containerStyle", "numberStyle", "renderNumber", "renderSeparator"]);
54
54
  const [value, setValue] = (0, react_1.useState)(null);
55
55
  // 使用 ref 保存最新的回调,避免频繁重建倒计时实例
56
56
  const callbacksRef = react_1.default.useRef({ onUpdate, onEnd });
57
57
  callbacksRef.current = { onUpdate, onEnd };
58
- let content = [];
58
+ // content 每秒因 value 变化必然重建;这里没有 emotion 序列化负担:
59
+ // numberStyle / separatorStyle 仅做引用透传,emotion 对相同引用有 cache
60
+ const content = [];
59
61
  if (value && typeof value === 'object') {
60
62
  for (let i = 0; i < format.length; i++) {
61
63
  // 渲染数字进组件
@@ -0,0 +1,9 @@
1
+ import type { DatePickerStyle } from "./style";
2
+ export interface ColumnProps {
3
+ items: number[];
4
+ value: number;
5
+ onChange: (v: number) => void;
6
+ format?: (n: number) => string;
7
+ style: DatePickerStyle;
8
+ }
9
+ export declare function Column(props: ColumnProps): import("@emotion/react/jsx-runtime").JSX.Element;