clxx 2.1.5 → 2.1.7

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 -0
  2. package/README.md +827 -420
  3. package/build/Ago/index.js +14 -8
  4. package/build/Alert/Wrapper.js +13 -10
  5. package/build/Alert/index.js +17 -11
  6. package/build/Alert/style.js +44 -58
  7. package/build/AutoGrid/index.js +66 -48
  8. package/build/AutoGrid/style.d.ts +3 -4
  9. package/build/AutoGrid/style.js +13 -22
  10. package/build/CarouselNotice/index.js +22 -19
  11. package/build/CarouselNotice/style.js +12 -14
  12. package/build/CitySelect/data.d.ts +3 -0
  13. package/build/CitySelect/data.js +2355 -0
  14. package/build/CitySelect/index.d.ts +17 -0
  15. package/build/CitySelect/index.js +300 -0
  16. package/build/CitySelect/search.d.ts +2 -0
  17. package/build/CitySelect/search.js +70 -0
  18. package/build/CitySelect/style.d.ts +4 -0
  19. package/build/CitySelect/style.js +237 -0
  20. package/build/CitySelect/type.d.ts +17 -0
  21. package/build/CitySelect/type.js +2 -0
  22. package/build/Clickable/index.d.ts +1 -0
  23. package/build/Clickable/index.js +113 -31
  24. package/build/Container/index.js +62 -47
  25. package/build/Countdowner/index.js +50 -14
  26. package/build/Dialog/Wrapper.js +13 -10
  27. package/build/Dialog/index.js +18 -12
  28. package/build/Dialog/style.js +29 -25
  29. package/build/Effect/useInterval.js +7 -4
  30. package/build/Effect/useTick.js +9 -6
  31. package/build/Effect/useUpdate.js +6 -3
  32. package/build/Effect/useViewport.js +15 -8
  33. package/build/Effect/useWindowResize.js +7 -4
  34. package/build/Fixed/index.d.ts +5 -0
  35. package/build/Fixed/index.js +40 -0
  36. package/build/Flex/Col.js +23 -15
  37. package/build/Flex/Row.js +23 -15
  38. package/build/Flex/index.js +9 -5
  39. package/build/Indicator/index.js +24 -18
  40. package/build/Indicator/style.js +6 -3
  41. package/build/Loading/Wrapper.js +14 -11
  42. package/build/Loading/index.js +17 -10
  43. package/build/Loading/style.js +25 -23
  44. package/build/Overlay/index.js +15 -31
  45. package/build/SafeArea/index.js +8 -5
  46. package/build/ScrollView/index.js +24 -21
  47. package/build/ScrollView/style.js +16 -14
  48. package/build/Toast/Toast.js +53 -17
  49. package/build/Toast/index.js +21 -14
  50. package/build/Toast/style.js +31 -30
  51. package/build/index.d.ts +2 -2
  52. package/build/index.js +102 -36
  53. package/build/utils/Countdown.js +7 -3
  54. package/build/utils/ago.js +10 -4
  55. package/build/utils/calendarTable.js +9 -3
  56. package/build/utils/createApp.d.ts +1 -2
  57. package/build/utils/createApp.js +35 -31
  58. package/build/utils/cssUtil.d.ts +0 -9
  59. package/build/utils/cssUtil.js +10 -43
  60. package/build/utils/defaultScroll.js +4 -1
  61. package/build/utils/dom.js +6 -3
  62. package/build/utils/is.js +6 -2
  63. package/build/utils/jsonp.js +4 -1
  64. package/build/utils/request.js +40 -27
  65. package/build/utils/tick.js +4 -1
  66. package/build/utils/uniqKey.js +4 -1
  67. package/build/utils/wait.js +8 -4
  68. package/package.json +1 -1
  69. package/test/src/city-select/index.jsx +21 -0
  70. package/test/src/dialog/index.module.css +1 -1
  71. package/test/src/index/index.jsx +1 -0
  72. package/test/vite.config.js +6 -2
  73. package/build/context.d.ts +0 -15
  74. package/build/context.js +0 -24
package/README.md CHANGED
@@ -18,27 +18,23 @@
18
18
 
19
19
  ## ✨ 特性
20
20
 
21
- - 🎯 **专为移动端设计** - 完美适配移动端交互和体验
22
- - 📱 **响应式布局** - 基于 rem 的自适应方案,支持多种屏幕尺寸
23
- - 🎨 **CSS-in-JS** - 使用 Emotion 实现样式隔离和动态样式
24
- - 🔧 **TypeScript** - 完整的类型定义,提供更好的开发体验
25
- - **高性能** - 优化的渲染逻辑和事件处理
26
- - 🎪 **函数式调用** - Toast、Dialog、Loading 等支持命令式调用
27
- - 🚀 **现代化** - 支持 React 19,使用最新的 Hooks API
28
- - 📦 **轻量级** - 按需加载,tree-shaking 友好
21
+ - 🎯 **专为移动端设计** 完美适配移动端交互和体验
22
+ - 📱 **响应式布局** 基于 rem 的自适应方案,自动处理浏览器字体缩放
23
+ - 🎨 **CSS-in-JS** 使用 Emotion 实现样式隔离和动态样式
24
+ - 🔧 **TypeScript** 完整的类型定义,提供更好的开发体验
25
+ - 🎪 **函数式调用** Toast、Dialog、Loading、Alert 等支持命令式调用
26
+ - 🚀 **现代化** 基于 React 19,使用最新的 Hooks API
27
+ - 📦 **轻量级** — tree-shaking 友好,按需引入
29
28
 
30
29
  ---
31
30
 
32
31
  ## 📦 安装
33
32
 
34
33
  ```bash
35
- # npm
36
34
  npm install clxx
37
-
38
- # yarn
35
+ # or
39
36
  yarn add clxx
40
-
41
- # pnpm
37
+ # or
42
38
  pnpm add clxx
43
39
  ```
44
40
 
@@ -51,6 +47,13 @@ pnpm add clxx
51
47
  }
52
48
  ```
53
49
 
50
+ ### Dependencies
51
+
52
+ - `@emotion/react` — CSS-in-JS 样式方案
53
+ - `dayjs` — 日期处理
54
+ - `history` — 路由历史管理
55
+ - `lodash` — 工具函数
56
+
54
57
  ---
55
58
 
56
59
  ## 🚀 快速开始
@@ -58,7 +61,6 @@ pnpm add clxx
58
61
  ### 基础使用
59
62
 
60
63
  ```tsx
61
- import React from 'react';
62
64
  import { Container, showToast } from 'clxx';
63
65
 
64
66
  function App() {
@@ -70,8 +72,6 @@ function App() {
70
72
  </Container>
71
73
  );
72
74
  }
73
-
74
- export default App;
75
75
  ```
76
76
 
77
77
  ### 使用路由创建应用
@@ -82,12 +82,14 @@ import { createApp } from 'clxx';
82
82
  createApp({
83
83
  target: '#root',
84
84
  designWidth: 750,
85
- routeMethod: 'hash',
86
- renderPage: async (pathname) => {
87
- // 动态加载页面组件
88
- const Page = await import(`./pages${pathname}`);
85
+ mode: 'hash',
86
+ default: '/index',
87
+ loading: () => <div>加载中...</div>,
88
+ render: async (pathname) => {
89
+ const Page = await import(`./pages/${pathname}`);
89
90
  return <Page.default />;
90
91
  },
92
+ notFound: (pathname) => <div>页面不存在: {pathname}</div>,
91
93
  });
92
94
  ```
93
95
 
@@ -95,674 +97,1079 @@ createApp({
95
97
 
96
98
  ## 📚 组件文档
97
99
 
98
- ### 🎨 布局组件
100
+ ### 🏗️ 布局组件
99
101
 
100
- #### Container - 容器组件
102
+ #### Container 自适应容器
101
103
 
102
- 全局根容器,提供移动端自适应、rem 布局和初始化逻辑。
104
+ 全局根容器,提供移动端 rem 自适应布局、viewport 设置和初始化逻辑。
103
105
 
104
106
  ```tsx
105
107
  import { Container } from 'clxx';
106
108
 
107
- <Container
108
- designWidth={750} // 设计稿宽度,默认 750
109
- resizeDelay={100} // resize 防抖延迟,默认 100ms
110
- globalStyle={css`...`} // 全局样式
109
+ <Container
110
+ designWidth={750} // 设计稿宽度,默认 750
111
+ globalStyle={css`...`} // 全局自定义样式(Emotion Interpolation)
111
112
  >
112
113
  <YourApp />
113
114
  </Container>
114
115
  ```
115
116
 
116
- **特性**:
117
- - ✅ 自动 rem 适配
118
- - 处理浏览器字体缩放
119
- - ✅ 防止初始化闪烁
120
- - 支持自定义全局样式
117
+ **Props:**
118
+
119
+ | 属性 | 类型 | 默认值 | 说明 |
120
+ |------|------|--------|------|
121
+ | `designWidth` | `number` | `750` | 设计稿宽度 |
122
+ | `globalStyle` | `Interpolation<Theme>` | — | 全局附加样式 |
123
+ | `children` | `ReactNode` | — | 子元素 |
124
+
125
+ **核心能力:**
126
+ - 自动计算并设置 `html` 根字体大小,实现 rem 适配
127
+ - 检测浏览器字体缩放并自动修正,避免布局偏差
128
+ - 通过 `useLayoutEffect` 在绘制前完成修正,防止闪烁
129
+ - `useViewport` 自动设置 viewport meta
130
+ - 激活 iOS 上的 `:active` 伪类
131
+ - 全局设置 `box-sizing: border-box`
121
132
 
122
133
  ---
123
134
 
124
- #### Flex 布局
135
+ #### Flex / FlexItem — Flex 布局
125
136
 
126
- 提供快捷的 Flex 布局组件。
137
+ 提供灵活的 Flex 容器和子元素组件,支持所有标准 CSS Flex 属性。
127
138
 
128
139
  ```tsx
129
- import { Row, RowCenter, RowBetween, Col, ColCenter } from 'clxx';
140
+ import { Flex, FlexItem } from 'clxx';
141
+
142
+ <Flex justifyContent="center" alignItems="center">
143
+ <FlexItem flex={1}>自适应宽度</FlexItem>
144
+ <FlexItem flexBasis="100px">固定宽度</FlexItem>
145
+ </Flex>
146
+ ```
147
+
148
+ **Flex Props:**
149
+
150
+ | 属性 | 类型 | 默认值 | 说明 |
151
+ |------|------|--------|------|
152
+ | `alignItems` | `CSS.Property.AlignItems` | `'center'` | 交叉轴对齐 |
153
+ | `justifyContent` | `CSS.Property.JustifyContent` | — | 主轴对齐 |
154
+ | `flexDirection` | `CSS.Property.FlexDirection` | — | 主轴方向 |
155
+ | `flexWrap` | `CSS.Property.FlexWrap` | — | 换行方式 |
156
+ | `flexFlow` | `CSS.Property.FlexFlow` | — | direction + wrap 简写 |
157
+ | `alignContent` | `CSS.Property.AlignContent` | — | 多行内容对齐 |
158
+
159
+ **FlexItem Props:**
160
+
161
+ | 属性 | 类型 | 说明 |
162
+ |------|------|------|
163
+ | `flex` | `CSS.Property.BoxFlex` | flex 简写 |
164
+ | `flexGrow` | `CSS.Property.FlexGrow` | 放大比例 |
165
+ | `flexShrink` | `CSS.Property.FlexShrink` | 缩小比例 |
166
+ | `flexBasis` | `CSS.Property.FlexBasis` | 基准大小 |
167
+ | `alignSelf` | `CSS.Property.AlignSelf` | 自身对齐 |
168
+ | `order` | `CSS.Property.Order` | 排列顺序 |
169
+
170
+ ---
171
+
172
+ #### Row / Col — 快捷 Flex 布局
130
173
 
131
- // 水平布局
132
- <RowCenter>
133
- <div>居中对齐</div>
134
- </RowCenter>
174
+ 基于 `Flex` 封装的水平/垂直快捷布局组件,预设 `flexDirection` 和 `justifyContent`。
135
175
 
176
+ ```tsx
177
+ import { RowCenter, RowBetween, ColCenter } from 'clxx';
178
+
179
+ <RowCenter>居中对齐</RowCenter>
136
180
  <RowBetween>
137
- <div>两端对齐</div>
181
+ <span>左侧</span>
182
+ <span>右侧</span>
138
183
  </RowBetween>
139
-
140
- // 垂直布局
141
- <ColCenter>
142
- <div>垂直居中</div>
143
- </ColCenter>
184
+ <ColCenter>垂直居中</ColCenter>
144
185
  ```
145
186
 
146
- **可用组件**:
147
- - `Row` / `RowStart` - 水平布局(左对齐)
148
- - `RowCenter` - 水平居中
149
- - `RowEnd` - 水平右对齐
150
- - `RowBetween` - 两端对齐
151
- - `RowAround` - 周围分布
152
- - `RowEvenly` - 均匀分布
153
- - `Col` / `ColStart` / `ColCenter` / `ColEnd` / `ColBetween` / `ColAround` / `ColEvenly` - 对应的垂直布局
187
+ **水平布局:**
188
+ | 组件 | justifyContent |
189
+ |------|---------------|
190
+ | `Row` / `RowStart` | `flex-start` |
191
+ | `RowCenter` | `center` |
192
+ | `RowEnd` | `flex-end` |
193
+ | `RowBetween` | `space-between` |
194
+ | `RowAround` | `space-around` |
195
+ | `RowEvenly` | `space-evenly` |
196
+
197
+ **垂直布局:**
198
+ | 组件 | justifyContent |
199
+ |------|---------------|
200
+ | `Col` / `ColStart` | `flex-start` |
201
+ | `ColCenter` | `center` |
202
+ | `ColEnd` | `flex-end` |
203
+ | `ColBetween` | `space-between` |
204
+ | `ColAround` | `space-around` |
205
+ | `ColEvenly` | `space-evenly` |
206
+
207
+ > 所有 Row/Col 组件均支持 `FlexProps` 的全部属性,可覆盖默认预设。
154
208
 
155
209
  ---
156
210
 
157
- #### AutoGrid - 自动网格
211
+ #### AutoGrid 自动网格
158
212
 
159
- 自动计算并排列网格布局。
213
+ 自动将子元素排列为网格布局,支持正方形模式。
160
214
 
161
215
  ```tsx
162
216
  import { AutoGrid } from 'clxx';
163
217
 
164
- <AutoGrid
165
- cols={3} // 列数
166
- gap="10px" // 间距
167
- isSquare={true} // 是否正方形
168
- >
169
- <div>Item 1</div>
170
- <div>Item 2</div>
171
- <div>Item 3</div>
218
+ <AutoGrid cols={3} gap="10px" isSquare>
219
+ <div>1</div>
220
+ <div>2</div>
221
+ <div>3</div>
222
+ <div>4</div>
223
+ <div>5</div>
172
224
  </AutoGrid>
173
225
  ```
174
226
 
227
+ **Props:**
228
+
229
+ | 属性 | 类型 | 默认值 | 说明 |
230
+ |------|------|--------|------|
231
+ | `cols` | `number` | `1` | 列数(最小 1) |
232
+ | `gap` | `CSS.Property.Width` | `0` | 表格间距 |
233
+ | `isSquare` | `boolean` | `false` | 是否强制正方形 |
234
+ | `containerStyle` | `Interpolation<Theme>` | — | 容器样式 |
235
+ | `itemStyle` | `Interpolation<Theme>` | — | 每个子元素容器样式 |
236
+
237
+ **特性:**
238
+ - 自动补齐最后一行空位(隐藏占位符),保证列宽一致
239
+ - 正方形模式通过 `padding-bottom: 100%` 实现
240
+
241
+ ---
242
+
243
+ #### Fixed — 固定定位
244
+
245
+ 简化 `position: fixed` 的使用,快速将元素固定到页面四边。
246
+
247
+ ```tsx
248
+ import { Fixed } from 'clxx';
249
+
250
+ <Fixed position="bottom">底部固定栏</Fixed>
251
+ <Fixed position="top">顶部固定栏</Fixed>
252
+ ```
253
+
254
+ **Props:**
255
+
256
+ | 属性 | 类型 | 默认值 | 说明 |
257
+ |------|------|--------|------|
258
+ | `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | 固定位置 |
259
+
175
260
  ---
176
261
 
177
- #### SafeArea - 安全区域
262
+ #### SafeArea 安全区域
178
263
 
179
- 处理手机刘海屏、底部横条等安全区域。
264
+ 处理 iPhone 刘海屏、底部手势条等安全区域。自动设置 `viewport-fit=cover`。
180
265
 
181
266
  ```tsx
182
267
  import { SafeArea } from 'clxx';
183
268
 
184
- <SafeArea type="top">顶部安全区域</SafeArea>
185
- <SafeArea type="bottom">底部安全区域</SafeArea>
269
+ <SafeArea type="top" /> {/* 顶部安全区域 */}
270
+ <div>页面内容</div>
271
+ <SafeArea type="bottom" /> {/* 底部安全区域 */}
186
272
  ```
187
273
 
274
+ **Props:**
275
+
276
+ | 属性 | 类型 | 默认值 | 说明 |
277
+ |------|------|--------|------|
278
+ | `type` | `'top' \| 'bottom'` | `'bottom'` | 安全区域位置 |
279
+
188
280
  ---
189
281
 
190
282
  ### 🎭 交互组件
191
283
 
192
- #### Clickable - 点击态
284
+ #### Clickable 点击态
193
285
 
194
- 提供点击反馈效果的容器组件。
286
+ 提供触摸/点击反馈效果的容器组件,兼容移动端触摸和 PC 鼠标事件。
195
287
 
196
288
  ```tsx
197
289
  import { Clickable } from 'clxx';
198
290
 
199
- <Clickable
200
- activeStyle={{ opacity: 0.6 }} // 激活样式
201
- bubble={false} // 是否冒泡
202
- onClick={() => console.log('clicked')}
203
- >
204
- 点击我
291
+ <Clickable activeStyle={{ opacity: 0.5 }} onClick={() => console.log('clicked')}>
292
+ 点我
293
+ </Clickable>
294
+
295
+ <Clickable activeClassName="pressed" moveThreshold={20}>
296
+ 自定义激活类名
205
297
  </Clickable>
206
298
  ```
207
299
 
300
+ **Props:**
301
+
302
+ | 属性 | 类型 | 默认值 | 说明 |
303
+ |------|------|--------|------|
304
+ | `activeStyle` | `React.CSSProperties` | `{ opacity: 0.6 }` | 激活时的样式 |
305
+ | `activeClassName` | `string` | — | 激活时追加的类名 |
306
+ | `bubble` | `boolean` | `true` | 是否允许事件冒泡 |
307
+ | `disable` | `boolean` | `false` | 是否禁用点击态 |
308
+ | `moveThreshold` | `number` | `10` | touchmove 取消 active 的位移阈值(px) |
309
+
310
+ **特性:**
311
+ - 自动检测运行环境,触屏设备使用 touch 事件,PC 使用 mouse 事件
312
+ - touchmove 超出阈值自动取消激活态
313
+ - PC 端在 `document` 上监听 `mouseup`,处理鼠标移出元素后释放的场景
314
+ - 未设置 `activeClassName` 和 `activeStyle` 时,默认使用 `opacity: 0.6`
315
+
208
316
  ---
209
317
 
210
- #### Overlay - 遮罩层
318
+ #### Overlay 覆盖层
211
319
 
212
- 全屏或局部遮罩层组件。
320
+ 通用遮罩层组件,可作为弹窗、对话框等的底层容器。
213
321
 
214
322
  ```tsx
215
323
  import { Overlay } from 'clxx';
216
324
 
217
- <Overlay
218
- fullScreen={true} // 是否全屏
219
- centerContent={true} // 内容是否居中
220
- maskColor="rgba(0,0,0,0.6)" // 遮罩颜色
221
- outside={true} // 是否渲染到 body
222
- >
223
- <div>遮罩内容</div>
325
+ <Overlay fullScreen maskColor="rgba(0,0,0,.6)" centerContent>
326
+ <div>居中内容</div>
327
+ </Overlay>
328
+
329
+ <Overlay outside>
330
+ 通过 Portal 挂载到 body
224
331
  </Overlay>
225
332
  ```
226
333
 
334
+ **Props:**
335
+
336
+ | 属性 | 类型 | 默认值 | 说明 |
337
+ |------|------|--------|------|
338
+ | `fullScreen` | `boolean` | `true` | 是否全屏覆盖 |
339
+ | `maskColor` | `string` | `'rgba(0,0,0,.6)'` | 遮罩背景色 |
340
+ | `centerContent` | `boolean` | `true` | 内容是否居中 |
341
+ | `outside` | `boolean` | `false` | 是否通过 Portal 挂载到 body |
342
+
227
343
  ---
228
344
 
229
- #### ScrollView - 滚动容器
345
+ #### ScrollView 滚动视图
230
346
 
231
- 带触底、触顶检测的滚动容器。
347
+ 功能丰富的滚动容器,支持触顶/触底事件、滚动节流和自动 loading 显示。
232
348
 
233
349
  ```tsx
234
350
  import { ScrollView } from 'clxx';
235
351
 
236
352
  <ScrollView
237
353
  height="100vh"
238
- reachBottomThreshold={50}
239
- onReachBottom={(e) => {
240
- console.log('到底了', e);
354
+ reachBottomThreshold={100}
355
+ onReachBottom={(event) => {
356
+ console.log('触底加载', event.scrollTop);
241
357
  }}
242
- onReachTop={(e) => {
243
- console.log('到顶了', e);
358
+ onScroll={(event) => {
359
+ console.log('滚动方向:', event.direction);
244
360
  }}
245
361
  >
246
- <div>滚动内容</div>
362
+ {/* 列表内容 */}
247
363
  </ScrollView>
248
364
  ```
249
365
 
366
+ **Props:**
367
+
368
+ | 属性 | 类型 | 默认值 | 说明 |
369
+ |------|------|--------|------|
370
+ | `height` | `CSS.Property.Height` | `'100%'` | 容器高度 |
371
+ | `reachTopThreshold` | `number` | `50` | 触顶事件阈值(px) |
372
+ | `onReachTop` | `(event: ScrollEvent) => void` | — | 触顶回调 |
373
+ | `reachBottomThreshold` | `number` | `50` | 触底事件阈值(px) |
374
+ | `onReachBottom` | `(event: ScrollEvent) => void` | — | 触底回调 |
375
+ | `onScroll` | `(event: ScrollEvent) => void` | — | 滚动事件回调 |
376
+ | `scrollThrottle` | `number` | `16` | 滚动节流时间(ms),约 60fps |
377
+ | `showLoading` | `boolean` | `true` | 是否显示底部 loading |
378
+ | `loadingContent` | `ReactNode` | — | 自定义 loading 内容 |
379
+ | `containerStyle` | `SerializedStyles` | — | 容器样式 |
380
+ | `wrapperStyle` | `SerializedStyles` | — | 内容包裹样式 |
381
+
382
+ **ScrollEvent:**
383
+
384
+ ```typescript
385
+ interface ScrollEvent {
386
+ containerHeight: number; // 容器可视高度
387
+ contentHeight: number; // 内容总高度
388
+ scrollTop: number; // 当前滚动位置
389
+ maxScroll: number; // 最大可滚动距离
390
+ direction: 'upward' | 'downward'; // 滚动方向
391
+ rawEvent?: React.UIEvent; // 原始事件
392
+ }
393
+ ```
394
+
395
+ **特性:**
396
+ - 使用 `ResizeObserver` 自动检测内容变化和滚动条状态
397
+ - 内置 leading + trailing 节流策略
398
+ - 触顶/触底事件防重复触发
399
+ - 有滚动条时自动显示底部 loading
400
+
250
401
  ---
251
402
 
252
- ### 💬 反馈组件
403
+ #### CarouselNotice — 轮播公告
253
404
 
254
- #### Toast - 轻提示
405
+ 垂直滚动的循环轮播公告组件,常用于消息通知、跑马灯等场景。
406
+
407
+ ```tsx
408
+ import { CarouselNotice } from 'clxx';
409
+
410
+ <CarouselNotice
411
+ list={['公告一:xxx', '公告二:xxx', '公告三:xxx']}
412
+ width="100%"
413
+ height="40px"
414
+ interval={3000}
415
+ duration={200}
416
+ />
417
+ ```
418
+
419
+ **Props:**
420
+
421
+ | 属性 | 类型 | 默认值 | 说明 |
422
+ |------|------|--------|------|
423
+ | `list` | `ReactNode[]` | `[]` | 轮播内容列表 |
424
+ | `width` | `CSS.Property.Width` | — | 容器宽度 |
425
+ | `height` | `CSS.Property.Height` | — | 容器高度 |
426
+ | `justify` | `'start' \| 'center' \| 'end'` | `'center'` | 内容水平对齐方式 |
427
+ | `interval` | `number` | `3000` | 切换间隔时间(ms) |
428
+ | `duration` | `number` | `200` | 每次冒泡动画持续时间(ms) |
429
+ | `containerStyle` | `Interpolation<Theme>` | — | 容器样式 |
430
+ | `wrapperStyle` | `Interpolation<Theme>` | — | 内部容器样式 |
431
+ | `itemStyle` | `Interpolation<Theme>` | — | 条目样式 |
432
+
433
+ ---
434
+
435
+ #### CitySelect / showCitySelect — 城市选择器
436
+
437
+ 移动端全屏城市选择器,支持字母侧边栏触摸导航、粘滞字母标题、列表与字母双向联动、搜索(含中文 IME 保护)、滑入/滑出动画、外部定位回显等。
438
+
439
+ ```tsx
440
+ import { CitySelect, showCitySelect } from 'clxx';
441
+
442
+ // 组件方式
443
+ <CitySelect
444
+ primary="#2f7dff"
445
+ getLocation={async () => '北京'}
446
+ onSelect={(city) => console.log(city)}
447
+ onClose={() => console.log('closed')}
448
+ />
449
+
450
+ // 函数式方式(内部使用 Portal 挂载到 body,选中或点击退出后自动卸载)
451
+ showCitySelect({
452
+ primary: '#2f7dff',
453
+ // 同步返回
454
+ getLocation: () => '110100',
455
+ // 或异步返回
456
+ // getLocation: async () => fetchCurrentCity(),
457
+ onSelect: (city) => {
458
+ console.log(city.name, city.code, city.province);
459
+ },
460
+ });
461
+ ```
462
+
463
+ **Props:**
464
+
465
+ | 属性 | 类型 | 默认值 | 说明 |
466
+ |------|------|--------|------|
467
+ | `onSelect` | `(city: SelectedCity) => void` | — | 选中城市时触发 |
468
+ | `onClose` | `() => void` | — | 退出动画结束时触发 |
469
+ | `onLetterChange` | `(letter: string) => void` | — | 侧边栏当前字母变化回调 |
470
+ | `getLocation` | `() => string \| null \| undefined \| Promise<string \| null \| undefined>` | — | 外部定位能力,可同步或异步,返回城市名或城市 code |
471
+ | `primary` | `string` | `'#2f7dff'` | 主题主色,形如 `#rrggbb`;active 态颜色自动派生 |
472
+
473
+ **SelectedCity:**
474
+
475
+ ```typescript
476
+ interface SelectedCity {
477
+ name: string; // 城市名,如 "北京市"
478
+ code: string; // 城市 code(直辖市为市辖区 code,如 "110100")
479
+ province: {
480
+ name: string; // 省级名称
481
+ code: string; // 省级 code
482
+ };
483
+ }
484
+ ```
485
+
486
+ **特性:**
487
+ - **首次挂载调用一次** `getLocation`:结果能匹配 `cityData`(按 code 精确匹配,按 name 允许省略末尾"市",如"北京"命中"北京市")才展示「当前定位」快捷块;失败、为空、匹配不到均不显示。
488
+ - **字母侧边栏触摸导航**:非 passive 触摸监听,滑动时实时切换字母并滚动列表;松开显示大字母提示。
489
+ - **双向联动**:手动滚动列表时基于缓存的 `offsetTop` 二分查找激活对应字母;并通过 `ResizeObserver` 重新测量。
490
+ - **搜索**:按拼音全拼、拼音首字母、中文名前缀匹配;中文输入法合成期间不触发搜索,避免中间态干扰。
491
+ - **滑入滑出动画**:首次挂载右侧滑入;选中或点击退出时滑出,动画结束后才触发 `onClose`(首帧立即退出时直接回调,避免卡住)。
492
+ - **纯属性化主题**:无 `containerStyle`,只允许通过 `primary` 控制主题色。
493
+
494
+ ---
495
+
496
+ ### 🔔 反馈组件
497
+
498
+ #### showToast / showUniqToast — 轻提示
499
+
500
+ 函数式调用的全局轻提示,支持自定义位置、持续时间和内容。
255
501
 
256
502
  ```tsx
257
503
  import { showToast, showUniqToast } from 'clxx';
258
504
 
259
- // 基础用法
505
+ // 简单用法:传入字符串或 ReactNode
260
506
  showToast('操作成功');
507
+ showToast(<MyCustomContent />);
261
508
 
262
- // 完整配置
509
+ // 高级用法:传入配置对象
263
510
  showToast({
264
- content: '操作成功',
265
- position: 'middle', // top | middle | bottom
266
- duration: 2000,
267
- offsetTop: 50,
511
+ content: '提示内容',
512
+ position: 'top', // 'top' | 'middle' | 'bottom'
513
+ duration: 3000, // 持续时间,默认 2000ms
514
+ radius: 16, // 圆角,默认 16
515
+ offsetTop: 50, // top 位置偏移
516
+ offsetBottom: 50, // bottom 位置偏移
517
+ containerStyle: css`...`,
518
+ contentStyle: css`...`,
268
519
  });
269
520
 
270
- // 全局唯一 Toast(新的会替换旧的)
271
- showUniqToast('只显示一个');
521
+ // 唯一 Toast(新调用会自动关闭上一个)
522
+ showUniqToast('只显示最新的一条');
272
523
  ```
273
524
 
274
525
  ---
275
526
 
276
- #### Dialog - 对话框
527
+ #### showDialog 对话框
528
+
529
+ 函数式调用的模态对话框,支持多种弹出动画,返回关闭函数。
277
530
 
278
531
  ```tsx
279
532
  import { showDialog } from 'clxx';
280
533
 
534
+ // 简单用法
535
+ const close = showDialog(<div>对话框内容</div>);
536
+ // 调用 close() 关闭
537
+
538
+ // 高级用法
281
539
  const close = showDialog({
282
- content: <div>对话框内容</div>,
283
- type: 'center', // center | pullUp | pullDown | pullLeft | pullRight
284
- blankClosable: true, // 点击空白处关闭
540
+ content: <div>自定义内容</div>,
541
+ type: 'center', // 'center' | 'pullUp' | 'pullDown' | 'pullLeft' | 'pullRight'
542
+ blankClosable: true, // 点击空白区域关闭
285
543
  showMask: true, // 显示遮罩
544
+ maskColor: 'rgba(0,0,0,.6)', // 遮罩颜色
545
+ boxStyle: css`...`, // 对话框容器样式
546
+ maskStyle: css`...`, // 遮罩样式
547
+ onHide: () => {}, // 关闭动画结束后回调
548
+ onBlankClick: () => {}, // 空白处点击回调
286
549
  });
287
550
 
288
- // 手动关闭
289
- close();
551
+ // 手动关闭(带关闭动画)
552
+ await close();
290
553
  ```
291
554
 
555
+ **动画类型:**
556
+ | type | 效果 |
557
+ |------|------|
558
+ | `center` | 居中缩放弹出 |
559
+ | `pullUp` | 从底部上滑 |
560
+ | `pullDown` | 从顶部下滑 |
561
+ | `pullLeft` | 从右侧左滑 |
562
+ | `pullRight` | 从左侧右滑 |
563
+
292
564
  ---
293
565
 
294
- #### Alert - 警告框
566
+ #### showAlert 弹窗提示
567
+
568
+ 基于 `showDialog` 封装的标准提示弹窗,支持确认/取消按钮。
295
569
 
296
570
  ```tsx
297
571
  import { showAlert } from 'clxx';
298
572
 
299
- showAlert({
300
- title: '提示',
301
- description: '确定要删除吗?',
573
+ // 简单用法
574
+ showAlert('确认要删除吗?');
575
+
576
+ // 高级用法
577
+ const close = showAlert({
578
+ title: '删除确认',
579
+ description: '删除后不可恢复',
580
+ confirm: '确定',
581
+ confirmColor: '#ff4d4d',
582
+ cancel: '取消',
583
+ cancelColor: '#999',
302
584
  showCancel: true,
303
- onConfirm: () => console.log('确认'),
304
- onCancel: () => console.log('取消'),
585
+ showMask: true,
586
+ onConfirm: () => {
587
+ console.log('点击了确认');
588
+ },
589
+ onCancel: () => {
590
+ console.log('点击了取消');
591
+ },
592
+ // 可定制样式
593
+ titleStyle: css`...`,
594
+ descStyle: css`...`,
595
+ btnStyle: css`...`,
596
+ confirmStyle: css`...`,
597
+ cancelStyle: css`...`,
305
598
  });
306
599
  ```
307
600
 
601
+ **Props:**
602
+
603
+ | 属性 | 类型 | 默认值 | 说明 |
604
+ |------|------|--------|------|
605
+ | `title` | `ReactNode` | `'提示'` | 标题 |
606
+ | `description` | `ReactNode` | — | 描述内容 |
607
+ | `confirm` | `ReactNode` | `'确定'` | 确认按钮文字 |
608
+ | `confirmColor` | `string` | `'#007afe'` | 确认按钮颜色 |
609
+ | `cancel` | `ReactNode` | `'取消'` | 取消按钮文字 |
610
+ | `cancelColor` | `string` | `'#666'` | 取消按钮颜色 |
611
+ | `showCancel` | `boolean` | `false` | 是否显示取消按钮 |
612
+ | `showMask` | `boolean` | `true` | 是否显示遮罩 |
613
+ | `onConfirm` | `() => void` | — | 确认回调 |
614
+ | `onCancel` | `() => void` | — | 取消回调 |
615
+
308
616
  ---
309
617
 
310
- #### Loading - 加载中
618
+ #### showLoading / showLoadingAtLeast — 加载指示器
619
+
620
+ 函数式调用的全局 Loading,返回关闭函数。
311
621
 
312
622
  ```tsx
313
623
  import { showLoading, showLoadingAtLeast } from 'clxx';
314
624
 
315
625
  // 基础用法
316
626
  const close = showLoading('加载中...');
317
- // 关闭
318
- close();
319
-
320
- // 至少显示指定时间(避免闪烁)
321
- const close = showLoadingAtLeast(300, '加载中...');
322
- // 即使立即调用 close(),也会至少显示 300ms
323
- close();
627
+ // 异步操作完成后关闭
628
+ await fetchData();
629
+ await close();
630
+
631
+ // 保证最少显示时间(避免闪烁)
632
+ const close = showLoadingAtLeast(500, '处理中...');
633
+ await fetchData(); // 即使请求很快完成,也会至少显示 500ms
634
+ await close();
635
+
636
+ // 高级用法
637
+ const close = showLoading('加载中...', {
638
+ overlay: { maskColor: 'rgba(0,0,0,.3)' }, // 自定义遮罩
639
+ indicator: { barCount: 14, barColor: '#fff' }, // 自定义指示器
640
+ containerStyle: css`...`,
641
+ });
324
642
  ```
325
643
 
326
644
  ---
327
645
 
328
- ### 🎪 展示组件
646
+ ### 📊 展示组件
647
+
648
+ #### Indicator — 加载指示器
329
649
 
330
- #### Indicator - 加载指示器
650
+ 基于 SVG 实现的旋转加载指示器,高度可定制。
331
651
 
332
652
  ```tsx
333
653
  import { Indicator } from 'clxx';
334
654
 
335
- <Indicator
336
- size={60}
337
- barCount={12}
338
- barColor="#fff"
339
- duration={600}
340
- />
655
+ <Indicator />
656
+ <Indicator size={80} barColor="#007afe" barCount={10} duration={800} />
341
657
  ```
342
658
 
659
+ **Props:**
660
+
661
+ | 属性 | 类型 | 默认值 | 说明 |
662
+ |------|------|--------|------|
663
+ | `size` | `CSS.Property.Width \| number` | `'.6rem'` | 容器尺寸 |
664
+ | `barWidth` | `number` | `7` | bar 宽度(SVG 坐标) |
665
+ | `barHeight` | `number` | `28` | bar 高度(SVG 坐标) |
666
+ | `barColor` | `string` | `'#fff'` | bar 颜色 |
667
+ | `barCount` | `number` | `12` | bar 数量 |
668
+ | `rounded` | `boolean` | `true` | bar 是否圆角 |
669
+ | `duration` | `number` | `600` | 一圈旋转时间(ms) |
670
+ | `containerStyle` | `Interpolation<Theme>` | — | 容器样式 |
671
+
343
672
  ---
344
673
 
345
- #### Ago - 相对时间
674
+ #### Countdowner 倒计时
346
675
 
347
- 显示相对时间(多久以前)。
676
+ 倒计时展示组件,支持自定义格式、样式和渲染方式。
348
677
 
349
678
  ```tsx
350
- import { Ago } from 'clxx';
679
+ import { Countdowner } from 'clxx';
680
+
681
+ // 基础用法(倒计时 60 秒)
682
+ <Countdowner remain={60} />
351
683
 
352
- <Ago date={new Date()} />
353
- // 输出:刚刚
684
+ // 自定义格式和样式
685
+ <Countdowner
686
+ remain={86400}
687
+ format="dhis"
688
+ separator=":"
689
+ numberStyle={css`color: red; font-weight: bold;`}
690
+ separatorStyle={css`color: #999; margin: 0 4px;`}
691
+ onEnd={() => console.log('倒计时结束')}
692
+ />
354
693
 
355
- <Ago
356
- date="2024-01-01"
357
- renderContent={(value) => `${value.num}${value.unit}前`}
694
+ // 完全自定义渲染
695
+ <Countdowner
696
+ remain={3600}
697
+ format="his"
698
+ renderNumber={(value, key) => (
699
+ <span className="number-box">{value < 10 ? `0${value}` : value}</span>
700
+ )}
701
+ renderSeparator={(value, key) => <span>:</span>}
358
702
  />
359
703
  ```
360
704
 
705
+ **Props:**
706
+
707
+ | 属性 | 类型 | 默认值 | 说明 |
708
+ |------|------|--------|------|
709
+ | `remain` | `number \| string` | `0` | 剩余时间(秒) |
710
+ | `format` | `string` | `'his'` | 显示格式:`d`天`h`时`i`分`s`秒 |
711
+ | `separator` | `ReactNode` | `':'` | 数字间分隔符 |
712
+ | `onUpdate` | `(value: CountdownValue) => void` | — | 每秒更新回调 |
713
+ | `onEnd` | `() => void` | — | 倒计时结束回调 |
714
+ | `numberStyle` | `Interpolation<Theme>` | — | 数字样式 |
715
+ | `separatorStyle` | `Interpolation<Theme>` | — | 分隔符样式 |
716
+ | `containerStyle` | `Interpolation<Theme>` | — | 容器样式 |
717
+ | `renderNumber` | `(value, key) => ReactNode` | — | 自定义数字渲染 |
718
+ | `renderSeparator` | `(value, key) => ReactNode` | — | 自定义分隔符渲染 |
719
+
361
720
  ---
362
721
 
363
- #### Countdowner - 倒计时
722
+ #### Ago 相对时间
723
+
724
+ 将日期格式化为相对时间展示(如 "3分钟前"、"2天后")。
364
725
 
365
726
  ```tsx
366
- import { Countdowner } from 'clxx';
727
+ import { Ago } from 'clxx';
367
728
 
368
- <Countdowner
369
- remain={3600} // 剩余秒数
370
- format="his" // 格式:d(天) h(时) i(分) s(秒)
371
- seperator=":" // 分隔符
372
- onEnd={() => console.log('倒计时结束')}
373
- />
729
+ <Ago date="2025-01-01" /> // "x个月前"
730
+ <Ago date={new Date()} /> // "刚刚"
731
+ <Ago date="2028-01-01" /> // "x年后"
732
+ <Ago block /> // 渲染为 <div>
733
+ <Ago date={someDate} renderContent={(result) => (
734
+ <span>{result.num}{result.unit === 'h' ? '小时' : '...'}</span>
735
+ )} />
374
736
  ```
375
737
 
376
- ---
738
+ **Props:**
377
739
 
378
- #### CarouselNotice - 滚动公告
740
+ | 属性 | 类型 | 默认值 | 说明 |
741
+ |------|------|--------|------|
742
+ | `date` | `dayjs.ConfigType` | `dayjs()` | 要格式化的日期 |
743
+ | `block` | `boolean` | `false` | 是否渲染为 `<div>`,默认 `<span>` |
744
+ | `renderContent` | `(result: AgoValue) => ReactNode` | — | 自定义渲染函数 |
379
745
 
380
- ```tsx
381
- import { CarouselNotice } from 'clxx';
746
+ **AgoValue:**
382
747
 
383
- <CarouselNotice
384
- list={['公告1', '公告2', '公告3']}
385
- height="40px"
386
- interval={3000}
387
- duration={200}
388
- />
748
+ ```typescript
749
+ interface AgoValue {
750
+ num: number; // 数字值
751
+ unit: 'y' | 'm' | 'd' | 'h' | 'i' | 's'; // 单位
752
+ format: string; // 格式化中文字符串,如 "3天前"
753
+ }
389
754
  ```
390
755
 
391
756
  ---
392
757
 
393
- ## 🛠️ 工具函数
758
+ ## 🪝 Hooks
394
759
 
395
- ### 网络请求
760
+ #### useWindowResize
396
761
 
397
- ```tsx
398
- import { GET, POST, sendJSON, jsonp } from 'clxx';
762
+ 监听窗口尺寸变化(包括屏幕旋转),回调通过 ref 保持最新引用。
399
763
 
400
- // GET 请求
401
- const data = await GET('/api/users', { page: 1 });
764
+ ```tsx
765
+ import { useWindowResize } from 'clxx';
402
766
 
403
- // POST 请求(FormData)
404
- const result = await POST('/api/login', {
405
- username: 'admin',
406
- password: '123456'
767
+ useWindowResize(() => {
768
+ console.log('窗口大小变化:', window.innerWidth);
407
769
  });
408
-
409
- // 发送 JSON
410
- const result = await sendJSON('/api/data', { data: {...} });
411
-
412
- // JSONP 请求
413
- const data = await jsonp('https://api.example.com/data');
414
770
  ```
415
771
 
416
- **高级配置**:
417
- ```tsx
418
- import { sendRequest, registerHostAlias } from 'clxx';
772
+ ---
419
773
 
420
- // 注册 Host 别名
421
- registerHostAlias({
422
- api: 'https://api.example.com',
423
- cdn: 'https://cdn.example.com'
424
- });
774
+ #### useViewport
425
775
 
426
- // 使用别名
427
- await GET('api@/users'); // 实际请求:https://api.example.com/users
776
+ 自动设置或更新 `<meta name="viewport">` 标签。
428
777
 
429
- // 完整配置
430
- const result = await sendRequest({
431
- url: '/api/data',
432
- method: 'POST',
433
- sendType: 'json',
434
- data: { key: 'value' },
435
- timeout: 5000,
436
- disableUrlCache: true,
778
+ ```tsx
779
+ import { useViewport } from 'clxx';
780
+
781
+ useViewport(); // 使用默认值
782
+ useViewport({
783
+ width: 'device-width',
784
+ initialScale: 1,
785
+ userScalable: 'no',
786
+ viewportFit: 'cover',
437
787
  });
438
788
  ```
439
789
 
440
790
  ---
441
791
 
442
- ### 时间处理
792
+ #### useInterval
793
+
794
+ 安全的 `setInterval` Hook,通过 ref 保持回调最新,`delay` 为 `null` 时暂停。
443
795
 
444
796
  ```tsx
445
- import { ago, calendarTable, Countdown, waitFor, waitUntil } from 'clxx';
797
+ import { useInterval } from 'clxx';
446
798
 
447
- // 相对时间
448
- const result = ago('2024-01-01');
449
- console.log(result.format); // "3个月前"
799
+ useInterval(() => {
800
+ setCount(c => c + 1);
801
+ }, 1000);
450
802
 
451
- // 日历表格
452
- const table = calendarTable('2024-10', false, true);
453
- // 返回 6x7 的日期数组
803
+ // 传入 null 暂停
804
+ useInterval(callback, isRunning ? 1000 : null);
805
+ ```
454
806
 
455
- // 倒计时器
456
- const countdown = new Countdown({
457
- remain: 3600,
458
- format: 'his',
459
- onUpdate: (value) => console.log(value),
460
- onEnd: () => console.log('结束'),
461
- });
462
- countdown.start();
807
+ ---
463
808
 
464
- // 等待指定时间
465
- await waitFor(1000);
809
+ #### useTick
466
810
 
467
- // 等待条件满足
468
- await waitUntil(() => document.querySelector('.loaded'), 5000);
811
+ 基于 `requestAnimationFrame` 的逐帧执行 Hook,适用于动画或高频更新场景。
812
+
813
+ ```tsx
814
+ import { useTick } from 'clxx';
815
+
816
+ useTick(() => {
817
+ // 每帧执行
818
+ updateAnimation();
819
+ });
469
820
  ```
470
821
 
471
822
  ---
472
823
 
473
- ### 工具类
824
+ #### useUpdate
825
+
826
+ 返回一个强制组件重新渲染的函数。
474
827
 
475
828
  ```tsx
476
- import {
477
- tick,
478
- uniqKey,
479
- defaultScroll,
480
- createPortalDOM,
481
- is,
482
- normalizeUnit,
483
- adaptive
484
- } from 'clxx';
485
-
486
- // 逐帧执行
487
- const stop = tick(() => {
488
- console.log('每帧执行');
489
- }, 16); // 可选的间隔时间
490
- stop(); // 停止
829
+ import { useUpdate } from 'clxx';
830
+
831
+ const forceUpdate = useUpdate();
832
+ // 需要时调用
833
+ forceUpdate();
834
+ ```
835
+
836
+ ---
491
837
 
492
- // 生成唯一 Key
493
- const key = uniqKey(); // "1730123456789_1"
838
+ ## 🛠️ 工具函数
494
839
 
495
- // 禁用/启用滚动
496
- defaultScroll.disable();
497
- defaultScroll.enable();
840
+ #### createApp — 创建路由应用
498
841
 
499
- // 创建 Portal DOM
500
- const portal = createPortalDOM();
501
- portal.mount(<YourComponent />);
502
- portal.unmount();
842
+ 创建一个带路由的单页应用,支持 browser / hash / memory 三种路由模式。
503
843
 
504
- // 环境判断
505
- if (is('ios')) { /* iOS 设备 */ }
506
- if (is('android')) { /* Android 设备 */ }
507
- if (is('mobile')) { /* 移动设备 */ }
508
- if (is('touchable')) { /* 支持触摸 */ }
844
+ ```tsx
845
+ import { createApp, history, getHistory } from 'clxx';
846
+
847
+ await createApp({
848
+ target: '#root', // 挂载目标元素
849
+ mode: 'hash', // 路由模式:'browser' | 'hash' | 'memory'
850
+ default: '/index', // 默认路由路径
851
+ designWidth: 750, // 设计稿宽度
852
+ globalStyle: css`...`, // 全局样式
853
+ loading: (pathname) => <Loading />, // 加载占位
854
+ render: async (pathname) => <PageComponent />, // 页面渲染
855
+ notFound: (pathname) => <NotFound />, // 404 页面
856
+ onBefore: async (pathname) => {}, // 页面加载前钩子
857
+ onAfter: async (pathname) => {}, // 页面加载后钩子
858
+ });
509
859
 
510
- // 单位标准化
511
- normalizeUnit(100); // "100px"
512
- normalizeUnit('50rem'); // "50rem"
513
- normalizeUnit(0.5, '%'); // "0.5%"
860
+ // 编程式导航
861
+ history.push('/about');
862
+ history.replace('/login');
514
863
  ```
515
864
 
516
865
  ---
517
866
 
518
- ## 🎯 自定义 Hooks
867
+ #### sendRequest / GET / POST / sendJSON — 网络请求
519
868
 
520
- ```tsx
521
- import {
522
- useInterval,
523
- useTick,
524
- useUpdate,
525
- useWindowResize,
526
- useViewport
527
- } from 'clxx';
528
-
529
- // 定时器 Hook
530
- useInterval(() => {
531
- console.log('每秒执行');
532
- }, 1000);
869
+ 基于 Fetch API 封装的网络请求工具,支持多种数据格式和超时控制。
533
870
 
534
- // 逐帧执行 Hook
535
- useTick(() => {
536
- console.log('每帧执行');
537
- });
871
+ ```typescript
872
+ import { GET, POST, sendJSON, sendRequest, registerHostAlias } from 'clxx';
538
873
 
539
- // 强制更新
540
- const update = useUpdate();
541
- update(); // 触发组件重新渲染
874
+ // 简单 GET 请求
875
+ const result = await GET('/api/list', { page: 1 });
542
876
 
543
- // 窗口大小变化
544
- useWindowResize(() => {
545
- console.log('窗口大小改变');
546
- }, 100); // 防抖 100ms
877
+ // POST 表单
878
+ const result = await POST('/api/submit', { name: 'test' });
547
879
 
548
- // Viewport 设置
549
- useViewport({
550
- width: 'device-width',
551
- initialScale: 1,
552
- userScalable: 'no',
880
+ // 发送 JSON
881
+ const result = await sendJSON('/api/create', { title: 'new' });
882
+
883
+ // 完整配置
884
+ const result = await sendRequest({
885
+ url: '/api/data',
886
+ method: 'POST',
887
+ sendType: 'json', // 'normal' | 'text' | 'form' | 'json' | 'blob' | 'params' | 'buffer'
888
+ data: { key: 'value' },
889
+ timeout: 10000, // 超时时间,默认 30s
890
+ disableUrlCache: true, // 禁用 URL 缓存
891
+ transmitPageParam: true, // 透传当前页面参数
892
+ });
893
+
894
+ // 注册 Host 别名
895
+ registerHostAlias({
896
+ api: 'https://api.example.com',
553
897
  });
898
+ // 使用别名发送请求
899
+ GET('api@/user/info', { id: 1 });
554
900
  ```
555
901
 
902
+ **SendType 类型:**
903
+
904
+ | 类型 | 说明 | data 类型 |
905
+ |------|------|----------|
906
+ | `normal` | GET 请求,数据附加到 URL | `Record<string, any>` / `string` |
907
+ | `text` | 文本请求体 | `string` |
908
+ | `form` | FormData 请求体 | `Record<string, any>` / `FormData` |
909
+ | `json` | JSON 字符串请求体 | `Record<string, any>` |
910
+ | `blob` | Blob 原始二进制 | `Blob` |
911
+ | `params` | URLSearchParams 请求体 | `Record<string, any>` / `URLSearchParams` |
912
+ | `buffer` | ArrayBuffer 请求体 | `ArrayBuffer` / `TypedArray` / `DataView` |
913
+
556
914
  ---
557
915
 
558
- ## 🎨 样式工具
916
+ #### jsonp — JSONP 请求
559
917
 
560
- ```tsx
561
- import { css } from '@emotion/react';
562
- import { adaptive, normalizeUnit, splitValue } from 'clxx';
563
-
564
- // 自适应样式(基于 750 设计稿)
565
- const styles = adaptive({
566
- width: 375, // 自动转换为 vw + 媒体查询
567
- height: 200,
568
- padding: 20,
569
- fontSize: 28,
570
- });
918
+ ```typescript
919
+ import { jsonp } from 'clxx';
571
920
 
572
- // 拆分数值和单位
573
- const { num, unit } = splitValue('100px');
574
- console.log(num, unit); // 100, "px"
921
+ const result = await jsonp('https://api.example.com/data', 'callback');
575
922
  ```
576
923
 
577
924
  ---
578
925
 
579
- ## ⚙️ 配置
926
+ #### Countdown — 倒计时类
580
927
 
581
- ### 全局配置
928
+ 底层倒计时工具类,基于 `requestAnimationFrame` 实现精确计时。
582
929
 
583
- ```tsx
584
- import { setContextValue } from 'clxx';
930
+ ```typescript
931
+ import { Countdown } from 'clxx';
585
932
 
586
- // 设置文档宽度范围
587
- setContextValue({
588
- maxDocWidth: 576, // 最大宽度
589
- minDocWidth: 312, // 最小宽度
933
+ const countdown = new Countdown({
934
+ remain: 120, // 剩余秒数
935
+ format: 'his', // 格式:d天 h时 i分 s秒
936
+ onUpdate: (value) => {
937
+ console.log(value); // { h: 0, i: 2, s: 0 }
938
+ },
939
+ onEnd: () => {
940
+ console.log('倒计时结束');
941
+ },
590
942
  });
943
+
944
+ countdown.start();
945
+ // countdown.stop(); // 暂停,可再次 start 恢复
591
946
  ```
592
947
 
593
948
  ---
594
949
 
595
- ## 📱 最佳实践
950
+ #### tick — 帧循环
596
951
 
597
- ### 1. 项目入口配置
952
+ 基于 `requestAnimationFrame` 的循环执行函数,支持可选间隔。
598
953
 
599
- ```tsx
600
- import React from 'react';
601
- import ReactDOM from 'react-dom/client';
602
- import { Container } from 'clxx';
603
- import App from './App';
954
+ ```typescript
955
+ import { tick } from 'clxx';
956
+
957
+ // 每帧执行
958
+ const stop = tick(() => {
959
+ // 每帧回调
960
+ });
604
961
 
605
- const root = ReactDOM.createRoot(document.getElementById('root'));
962
+ // 指定间隔执行
963
+ const stop = tick(() => {
964
+ // 每 1000ms 执行一次
965
+ }, 1000);
606
966
 
607
- root.render(
608
- <Container
609
- designWidth={750}
610
- resizeDelay={100}
611
- >
612
- <App />
613
- </Container>
614
- );
967
+ // 停止
968
+ stop();
615
969
  ```
616
970
 
617
- ### 2. 使用路由
971
+ ---
618
972
 
619
- ```tsx
620
- import { createApp, setContextValue } from 'clxx';
973
+ #### is — 环境检测
621
974
 
622
- // 配置全局上下文
623
- setContextValue({
624
- maxDocWidth: 576,
625
- minDocWidth: 312,
626
- });
975
+ 常用的运行环境判断工具,结果缓存,避免重复检测。
627
976
 
628
- // 创建应用
629
- createApp({
630
- target: '#root',
631
- designWidth: 750,
632
- routeMethod: 'hash',
633
-
634
- // 加载前
635
- onBeforeRenderPage: async (pathname) => {
636
- console.log('准备加载:', pathname);
637
- },
638
-
639
- // 加载中
640
- onLoadingPage: () => <div>Loading...</div>,
641
-
642
- // 渲染页面
643
- renderPage: async (pathname) => {
644
- const modules = {
645
- '/': () => import('./pages/Home'),
646
- '/about': () => import('./pages/About'),
647
- };
648
-
649
- const loadModule = modules[pathname] || modules['/'];
650
- const module = await loadModule();
651
- return <module.default />;
652
- },
653
-
654
- // 加载后
655
- onAfterRenderPage: (pathname) => {
656
- document.title = pathname;
657
- },
658
- });
977
+ ```typescript
978
+ import { is } from 'clxx';
979
+
980
+ is('ios'); // iOS 平台(含 iPadOS 13+)
981
+ is('android'); // Android 平台
982
+ is('wechat'); // 微信环境
983
+ is('qq'); // QQ / QQ浏览器
984
+ is('alipay'); // 支付宝
985
+ is('weibo'); // 微博
986
+ is('douyin'); // 抖音
987
+ is('xiaohongshu'); // 小红书
988
+ is('toutiao'); // 今日头条
989
+ is('baidu'); // 百度 APP
990
+ is('touchable'); // 可触摸设备
659
991
  ```
660
992
 
661
- ### 3. 性能优化建议
993
+ ---
662
994
 
663
- ```tsx
664
- // 1. 使用防抖优化 resize
665
- <Container resizeDelay={150} />
995
+ #### ago — 相对时间格式化
666
996
 
667
- // 2. 使用 showUniqToast 避免多个 Toast 堆叠
668
- showUniqToast('只显示最新的');
997
+ 将任意日期格式化为中文相对时间描述,支持过去和未来。
669
998
 
670
- // 3. Loading 至少显示一定时间避免闪烁
671
- const close = showLoadingAtLeast(300, '加载中...');
999
+ ```typescript
1000
+ import { ago } from 'clxx';
672
1001
 
673
- // 4. ScrollView 使用合适的阈值
674
- <ScrollView
675
- reachBottomThreshold={100}
676
- onReachBottom={loadMore}
677
- />
1002
+ ago('2025-01-01'); // { num: x, unit: 'm', format: 'x个月前' }
1003
+ ago(new Date()); // { num: 0, unit: 's', format: '刚刚' }
1004
+ ago('2028-06-01'); // { num: x, unit: 'y', format: 'x年后' }
678
1005
  ```
679
1006
 
680
1007
  ---
681
1008
 
682
- ## 🔧 TypeScript 支持
1009
+ #### calendarTable 月历数据
683
1010
 
684
- 所有组件和函数都提供完整的 TypeScript 类型定义:
1011
+ 生成一个月的日历表格数据(6×7 的二维 dayjs 数组),可用于自定义日历组件。
685
1012
 
686
- ```tsx
687
- import type {
688
- ContainerProps,
689
- ToastProps,
690
- DialogProps,
691
- OverlayProps,
692
- // ... 更多类型
693
- } from 'clxx';
1013
+ ```typescript
1014
+ import { calendarTable } from 'clxx';
694
1015
 
695
- // 自定义扩展
696
- import { showToast } from 'clxx';
1016
+ const table = calendarTable('2026-03-01');
1017
+ // table: Dayjs[][] 6 行 7 列
697
1018
 
698
- const myShowToast = (message: string) => {
699
- showToast({
700
- content: message,
701
- duration: 3000,
702
- position: 'middle',
703
- });
704
- };
1019
+ const table = calendarTable('2026-03', true); // 从周日开始
1020
+ const table = calendarTable('2026-03', false, false); // 不保证 6 行
705
1021
  ```
706
1022
 
1023
+ | 参数 | 类型 | 默认值 | 说明 |
1024
+ |------|------|--------|------|
1025
+ | `usefulFormat` | `dayjs.ConfigType` | `dayjs()` | 日期值 |
1026
+ | `startFromSunday` | `boolean` | `false` | 是否周日开始 |
1027
+ | `sizeGuarantee` | `boolean` | `true` | 是否保证 6 行 |
1028
+
707
1029
  ---
708
1030
 
709
- ## 📋 浏览器支持
1031
+ #### waitFor / waitUntil — 等待工具
710
1032
 
711
- - iOS Safari 10+
712
- - Android Chrome 5.0+
713
- - 现代浏览器(Chrome, Firefox, Safari, Edge)
1033
+ ```typescript
1034
+ import { waitFor, waitUntil } from 'clxx';
1035
+
1036
+ // 等待指定时间
1037
+ await waitFor(1000); // 等待 1 秒
1038
+
1039
+ // 等待条件为真
1040
+ const success = await waitUntil(() => document.querySelector('.target') !== null, 5000);
1041
+ // success: true(条件满足)或 false(超时)
1042
+ ```
714
1043
 
715
1044
  ---
716
1045
 
717
- ## 🤝 贡献
1046
+ #### normalizeUnit / splitValue — CSS 值处理
718
1047
 
719
- 欢迎提交 Issue 和 Pull Request!
1048
+ ```typescript
1049
+ import { normalizeUnit, splitValue } from 'clxx';
1050
+
1051
+ normalizeUnit(100); // '100px'
1052
+ normalizeUnit('1.5rem'); // '1.5rem'
1053
+ normalizeUnit('100'); // '100px'
1054
+
1055
+ splitValue(100); // { num: 100, unit: 'px' }
1056
+ splitValue('1.5rem'); // { num: 1.5, unit: 'rem' }
1057
+ ```
720
1058
 
721
1059
  ---
722
1060
 
723
- ## 📄 License
1061
+ #### defaultScroll — 默认滚动控制
724
1062
 
725
- [MIT](./LICENSE)
1063
+ 禁用/启用页面默认滚动行为(针对触摸事件)。
1064
+
1065
+ ```typescript
1066
+ import { defaultScroll } from 'clxx';
1067
+
1068
+ defaultScroll.disable(); // 禁止页面滚动
1069
+ defaultScroll.enable(); // 恢复页面滚动
1070
+ ```
726
1071
 
727
1072
  ---
728
1073
 
729
- ## 🔗 相关链接
1074
+ #### uniqKey — 唯一键生成
1075
+
1076
+ 生成全局唯一的字符串标识。
730
1077
 
731
- - [GitHub](https://github.com/joye61/clxx)
732
- - [NPM](https://www.npmjs.com/package/clxx)
733
- - [Issues](https://github.com/joye61/clxx/issues)
1078
+ ```typescript
1079
+ import { uniqKey } from 'clxx';
1080
+
1081
+ const key = uniqKey(); // 如 "m1abc1"
1082
+ ```
734
1083
 
735
1084
  ---
736
1085
 
737
- ## 📝 更新日志
1086
+ #### createPortalDOM — Portal 容器
1087
+
1088
+ 创建挂载到 body 的 React Portal 容器,用于命令式渲染组件。
1089
+
1090
+ ```typescript
1091
+ import { createPortalDOM } from 'clxx';
738
1092
 
739
- ### v2.1.4 (2025-10-28)
1093
+ const { mount, unmount, element } = createPortalDOM();
1094
+ mount(<MyComponent />); // 渲染组件到 body
1095
+ unmount(); // 卸载并移除 DOM
1096
+ ```
1097
+
1098
+ ---
740
1099
 
741
- #### 🎉 新增
742
- - 新增 `useUpdate`、`useWindowResize`、`useViewport` Hooks 导出
743
- - Container 组件新增 `resizeDelay` 配置项
1100
+ #### setContextValue / getContextValue — 全局上下文
744
1101
 
745
- #### 🐛 Bug 修复
746
- - 修复 Overlay 组件 resize 性能问题
747
- - 修复 Toast 组件定时器清理错误
748
- - 修复 Countdowner 依赖数组缺失
749
- - 修复 SafeArea 类型定义
750
- - 修复字体缩放可能导致的无限循环
1102
+ 简单的全局键值存储,用于跨组件共享数据。
751
1103
 
752
- #### ⚡ 性能优化
753
- - Overlay 组件添加 resize 防抖
754
- - Container 组件优化初始化逻辑
755
- - useWindowResize 使用现代 API 替代废弃的 orientationchange
756
- - 优化网络请求超时逻辑,使用 AbortController
1104
+ ```typescript
1105
+ import { setContextValue, getContextValue } from 'clxx';
757
1106
 
758
- #### 🔨 代码改进
759
- - 移除不必要的 lodash/round 依赖
760
- - 统一 Hooks 导出风格
761
- - 改进类型定义准确性
762
- - 优化事件监听器使用 passive 选项
1107
+ setContextValue({ token: 'xxx', userId: 123 });
1108
+ getContextValue('token'); // 'xxx'
1109
+ getContextValue(); // { token: 'xxx', userId: 123 }
1110
+ ```
763
1111
 
764
1112
  ---
765
1113
 
766
- <p align="center">
767
- Made with ❤️ by <a href="https://github.com/joye61">Joye</a>
768
- </p>
1114
+ ## 📋 完整导出列表
1115
+
1116
+ ### 组件
1117
+ | 导出 | 类型 | 说明 |
1118
+ |------|------|------|
1119
+ | `Container` | 组件 | 自适应根容器 |
1120
+ | `Flex` / `FlexItem` | 组件 | Flex 布局 |
1121
+ | `Row` / `RowStart` ~ `RowEvenly` | 组件 | 水平布局快捷组件 |
1122
+ | `Col` / `ColStart` ~ `ColEvenly` | 组件 | 垂直布局快捷组件 |
1123
+ | `AutoGrid` | 组件 | 自动网格 |
1124
+ | `SafeArea` | 组件 | 安全区域 |
1125
+ | `Clickable` | 组件 | 点击态 |
1126
+ | `Overlay` | 组件 | 覆盖层 |
1127
+ | `ScrollView` | 组件 | 滚动视图 |
1128
+ | `CarouselNotice` | 组件 | 轮播公告 |
1129
+ | `CitySelect` | 组件 | 城市选择器 |
1130
+ | `Indicator` | 组件 | 加载指示器 |
1131
+ | `Countdowner` | 组件 | 倒计时 |
1132
+ | `Ago` | 组件 | 相对时间 |
1133
+
1134
+ ### 函数式调用
1135
+ | 导出 | 说明 |
1136
+ |------|------|
1137
+ | `showToast` / `showUniqToast` | 轻提示 |
1138
+ | `showDialog` | 对话框 |
1139
+ | `showAlert` | 弹窗提示 |
1140
+ | `showLoading` / `showLoadingAtLeast` | 加载指示 |
1141
+ | `showCitySelect` | 城市选择器 |
1142
+
1143
+ ### Hooks
1144
+ | 导出 | 说明 |
1145
+ |------|------|
1146
+ | `useWindowResize` | 窗口变化监听 |
1147
+ | `useViewport` | Viewport meta 管理 |
1148
+ | `useInterval` | 安全 setInterval |
1149
+ | `useTick` | 帧循环 |
1150
+ | `useUpdate` | 强制刷新 |
1151
+
1152
+ ### 工具函数
1153
+ | 导出 | 说明 |
1154
+ |------|------|
1155
+ | `createApp` / `history` / `getHistory` | 路由应用 |
1156
+ | `GET` / `POST` / `sendJSON` / `sendRequest` / `sugarSend` | 网络请求 |
1157
+ | `buildUrlByOption` / `registerHostAlias` | URL 构建与别名 |
1158
+ | `jsonp` | JSONP 请求 |
1159
+ | `Countdown` | 倒计时类 |
1160
+ | `tick` | 帧循环函数 |
1161
+ | `is` | 环境检测 |
1162
+ | `ago` | 相对时间格式化 |
1163
+ | `calendarTable` | 月历数据 |
1164
+ | `waitFor` / `waitUntil` | 等待工具 |
1165
+ | `normalizeUnit` / `splitValue` | CSS 值处理 |
1166
+ | `defaultScroll` | 滚动控制 |
1167
+ | `uniqKey` | 唯一键 |
1168
+ | `createPortalDOM` | Portal 容器 |
1169
+ | `setContextValue` / `getContextValue` | 全局上下文 |
1170
+
1171
+ ---
1172
+
1173
+ ## 📄 License
1174
+
1175
+ [MIT](LICENSE) © [Joye](https://github.com/joye61)