clxx 2.1.8 → 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 (74) 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/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
@@ -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";
@@ -1,4 +1,3 @@
1
- /** @jsx jsx */
2
1
  import { Theme, Interpolation } from "@emotion/react";
3
2
  import { DialogType, AnimationStatus } from "./style";
4
3
  export interface WrapperProps {
@@ -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
- let boxCss = [
12
- style_1.style.boxCss,
13
- ["pullUp", "pullDown", "pullLeft", "pullRight"].includes(type) ? style_1.style[type] : {}
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: { overflow: "hidden" }, 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: [boxCss, boxStyle, animation], onAnimationEnd: (event) => {
30
- if (status === "hide" && event.animationName === keyframes.name) {
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 })] }));
@@ -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>;
@@ -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 onBlankClick = config.onBlankClick;
41
- const onHide = config.onHide;
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
- const closeDialog = () => __awaiter(this, void 0, void 0, function* () {
49
- props.status = 'hide';
50
- props.onHide = () => {
51
- unmount();
52
- onHide === null || onHide === void 0 ? void 0 : onHide();
53
- };
54
- yield mount((0, jsx_runtime_1.jsx)(Wrapper_1.Wrapper, Object.assign({}, props, { children: children })));
55
- });
56
- // 空白处可点击关闭
57
- if (blankClosable) {
58
- props.onBlankClick = (event) => __awaiter(this, void 0, void 0, function* () {
59
- yield closeDialog();
60
- onBlankClick === null || onBlankClick === void 0 ? void 0 : onBlankClick(event);
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
- const amountShow = mount((0, jsx_runtime_1.jsx)(Wrapper_1.Wrapper, Object.assign({}, props, { children: children })));
65
- return () => __awaiter(this, void 0, void 0, function* () {
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
  }
@@ -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.6)",
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
- if (delay !== null) {
10
- const interval = setInterval(() => savedCallback.current(), delay || 0);
11
- return () => clearInterval(interval);
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
  }
@@ -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
- const styles = {
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;
@@ -0,0 +1,26 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.FlexItem = FlexItem;
15
+ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
16
+ function FlexItem(props) {
17
+ const { children, alignSelf, order, flex, flexGrow, flexShrink, flexBasis, style } = props, extra = __rest(props, ["children", "alignSelf", "order", "flex", "flexGrow", "flexShrink", "flexBasis", "style"]);
18
+ // 布局属性走原生 inline style:避免 emotion 在每次 render 哈希对象字面量
19
+ const inlineStyle = Object.assign({ alignSelf,
20
+ order,
21
+ flex,
22
+ flexGrow,
23
+ flexShrink,
24
+ flexBasis }, style);
25
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ style: inlineStyle }, extra, { children: children })));
26
+ }
@@ -8,14 +8,6 @@ export interface FlexProps extends React.HTMLProps<HTMLDivElement> {
8
8
  flexWrap?: CSS.Property.FlexWrap;
9
9
  flexDirection?: CSS.Property.FlexDirection;
10
10
  }
11
- export interface FlexItemProps extends React.HTMLProps<HTMLDivElement> {
12
- children?: React.ReactNode;
13
- alignSelf?: CSS.Property.AlignSelf;
14
- order?: CSS.Property.Order;
15
- flex?: CSS.Property.BoxFlex;
16
- flexGrow?: CSS.Property.FlexGrow;
17
- flexShrink?: CSS.Property.FlexShrink;
18
- flexBasis?: CSS.Property.FlexBasis;
19
- }
20
11
  export declare function Flex(props: FlexProps): import("@emotion/react/jsx-runtime").JSX.Element;
21
- export declare function FlexItem(props: FlexItemProps): import("@emotion/react/jsx-runtime").JSX.Element;
12
+ export type { FlexItemProps } from './FlexItem';
13
+ export { FlexItem } from './FlexItem';
@@ -11,29 +11,19 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.FlexItem = void 0;
14
15
  exports.Flex = Flex;
15
- exports.FlexItem = FlexItem;
16
16
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
17
17
  function Flex(props) {
18
- const { children, alignItems = 'center', alignContent, justifyContent, flexFlow, flexWrap, flexDirection } = props, extra = __rest(props, ["children", "alignItems", "alignContent", "justifyContent", "flexFlow", "flexWrap", "flexDirection"]);
19
- return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
20
- display: 'flex',
21
- alignItems,
22
- alignContent,
23
- justifyContent,
24
- flexFlow,
25
- flexWrap,
26
- flexDirection,
27
- } }, extra, { children: children })));
28
- }
29
- function FlexItem(props) {
30
- const { children, alignSelf, order, flex, flexGrow, flexShrink, flexBasis } = props, extra = __rest(props, ["children", "alignSelf", "order", "flex", "flexGrow", "flexShrink", "flexBasis"]);
31
- return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
32
- alignSelf,
33
- order,
34
- flex,
35
- flexGrow,
36
- flexShrink,
37
- flexBasis,
38
- } }, extra, { children: children })));
18
+ const { children, alignItems = 'center', alignContent, justifyContent, flexFlow, flexWrap, flexDirection, style } = props, extra = __rest(props, ["children", "alignItems", "alignContent", "justifyContent", "flexFlow", "flexWrap", "flexDirection", "style"]);
19
+ // 布局属性走原生 inline style:避免 emotion 在每次 render 哈希对象字面量
20
+ const inlineStyle = Object.assign({ display: 'flex', alignItems,
21
+ alignContent,
22
+ justifyContent,
23
+ flexFlow,
24
+ flexWrap,
25
+ flexDirection }, style);
26
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ style: inlineStyle }, extra, { children: children })));
39
27
  }
28
+ var FlexItem_1 = require("./FlexItem");
29
+ Object.defineProperty(exports, "FlexItem", { enumerable: true, get: function () { return FlexItem_1.FlexItem; } });
@@ -1,7 +1,7 @@
1
- import { Interpolation, Theme } from '@emotion/react';
2
- import React from 'react';
3
- import * as CSS from 'csstype';
4
- export interface IndicatorProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
1
+ import { Interpolation, Theme } from "@emotion/react";
2
+ import { HTMLAttributes } from "react";
3
+ import * as CSS from "csstype";
4
+ export interface IndicatorProps extends HTMLAttributes<HTMLDivElement> {
5
5
  size?: CSS.Property.Width | number;
6
6
  rounded?: boolean;
7
7
  barWidth?: number;
@@ -12,7 +12,10 @@ export interface IndicatorProps extends React.DetailedHTMLProps<React.HTMLAttrib
12
12
  containerStyle?: Interpolation<Theme>;
13
13
  }
14
14
  /**
15
- * SVG转圈指示器,一般用作loading效果
16
- * @param props
15
+ * iOS 风菊花转圈指示器(仿 UIActivityIndicatorView 节奏)。
16
+ * 性能要点:
17
+ * - 用 opacity 动画替代 fill 动画,触发 GPU 合成而非 SVG paint
18
+ * - keyframes 全局单例,颜色/时长变化不会污染样式表
19
+ * - animation-delay 走 inline style,emotion 不再为每条 bar 生成独立类名
17
20
  */
18
21
  export declare function Indicator(props: IndicatorProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -10,56 +10,53 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  }
11
11
  return t;
12
12
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
13
  Object.defineProperty(exports, "__esModule", { value: true });
17
14
  exports.Indicator = Indicator;
18
15
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
19
16
  const react_1 = require("@emotion/react");
20
- const react_2 = __importDefault(require("react"));
17
+ const react_2 = require("react");
21
18
  const cssUtil_1 = require("../utils/cssUtil");
22
19
  const style_1 = require("./style");
23
20
  /**
24
- * SVG转圈指示器,一般用作loading效果
25
- * @param props
21
+ * iOS 风菊花转圈指示器(仿 UIActivityIndicatorView 节奏)。
22
+ * 性能要点:
23
+ * - 用 opacity 动画替代 fill 动画,触发 GPU 合成而非 SVG paint
24
+ * - keyframes 全局单例,颜色/时长变化不会污染样式表
25
+ * - animation-delay 走 inline style,emotion 不再为每条 bar 生成独立类名
26
26
  */
27
27
  function Indicator(props) {
28
- const { size, rounded = true, barWidth = 7, barHeight = 28, barColor = '#fff', barCount = 12, duration = 600, containerStyle } = props, attributes = __rest(props, ["size", "rounded", "barWidth", "barHeight", "barColor", "barCount", "duration", "containerStyle"]);
28
+ const { size, rounded = true, barWidth = 8, barHeight = 26, barColor = "#ffffff", barCount = 12, duration = 1000, containerStyle } = props, attributes = __rest(props, ["size", "rounded", "barWidth", "barHeight", "barColor", "barCount", "duration", "containerStyle"]);
29
29
  const radius = rounded ? barWidth / 2 : 0;
30
- // 使用 useMemo 缓存 barList,避免每次渲染都重新生成
31
- const barList = react_2.default.useMemo(() => {
32
- const bars = [];
33
- for (let i = 0; i < barCount; i++) {
34
- bars.push((0, jsx_runtime_1.jsx)("rect", { x: (100 - barWidth) / 2, y: "0", rx: radius, ry: radius, width: barWidth, height: barHeight, transform: `rotate(${(360 / barCount) * i}, 50, 50)`, css: {
35
- animationDelay: `${-(duration * (barCount - i)) / barCount}ms`,
36
- } }, i));
37
- }
38
- return bars;
39
- }, [barCount, barWidth, barHeight, radius, duration]);
40
- // 使用 useMemo 缓存样式,避免每次都重新计算
41
- const style = react_2.default.useMemo(() => [
42
- {
43
- fontSize: 0,
44
- },
45
- typeof size !== 'undefined' ? {
46
- width: (0, cssUtil_1.normalizeUnit)(size),
47
- height: (0, cssUtil_1.normalizeUnit)(size),
48
- } : {
49
- width: '.6rem',
50
- height: '.6rem',
51
- }
30
+ const containerCss = (0, react_2.useMemo)(() => [
31
+ { fontSize: 0, display: "inline-block", lineHeight: 0 },
32
+ size !== undefined
33
+ ? { width: (0, cssUtil_1.normalizeUnit)(size), height: (0, cssUtil_1.normalizeUnit)(size) }
34
+ : { width: ".4rem", height: ".4rem" },
52
35
  ], [size]);
53
- const svgStyle = react_2.default.useMemo(() => (0, react_1.css)({
54
- width: '100%',
55
- height: '100%',
36
+ const svgCss = (0, react_2.useMemo)(() => (0, react_1.css)({
37
+ width: "100%",
38
+ height: "100%",
39
+ display: "block",
56
40
  rect: {
57
- fill: 'transparent',
58
- animationName: (0, style_1.getBarChangeKeyFrames)(barColor),
41
+ fill: barColor,
42
+ animationName: `${style_1.barFadeKeyframes}`,
59
43
  animationDuration: `${duration}ms`,
60
- animationTimingFunction: 'linear',
61
- animationIterationCount: 'infinite',
44
+ animationTimingFunction: "linear",
45
+ animationIterationCount: "infinite",
46
+ willChange: "opacity",
62
47
  },
63
48
  }), [barColor, duration]);
64
- return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: [style, containerStyle] }, attributes, { children: (0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 100 100", css: svgStyle, children: barList }) })));
49
+ const bars = (0, react_2.useMemo)(() => {
50
+ const list = [];
51
+ const x = (100 - barWidth) / 2;
52
+ for (let i = 0; i < barCount; i++) {
53
+ list.push((0, jsx_runtime_1.jsx)("rect", { x: x, y: 0, rx: radius, ry: radius, width: barWidth, height: barHeight, transform: `rotate(${(360 / barCount) * i} 50 50)`,
54
+ // 负 delay:组件挂载即处于稳态动画中,不会先停后转
55
+ style: {
56
+ animationDelay: `${-(duration * (barCount - i)) / barCount}ms`,
57
+ } }, i));
58
+ }
59
+ return list;
60
+ }, [barCount, barWidth, barHeight, radius, duration]);
61
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: [containerCss, containerStyle] }, attributes, { children: (0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 100 100", css: svgCss, "aria-hidden": "true", children: bars }) })));
65
62
  }
@@ -1,8 +1,9 @@
1
1
  /**
2
- * 获取转圈每一条bar的过渡动画
3
- * @param color
2
+ * iOS 菊花单条 bar 的不透明度衰减动画
3
+ * 1 -> 0.18 线性衰减;keyframes 与颜色解耦,全局共享单例,
4
+ * 走 opacity 触发 GPU 合成而非 SVG paint,性能更高。
4
5
  */
5
- export declare function getBarChangeKeyFrames(color: string): {
6
+ export declare const barFadeKeyframes: {
6
7
  name: string;
7
8
  styles: string;
8
9
  anim: 1;