@seakoi/native-ui 1.2.0 → 1.3.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 (178) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/commonjs/components/base/index.js +33 -11
  3. package/dist/commonjs/components/base/input/base-input.js +4 -2
  4. package/dist/commonjs/components/base/overflow/all-mode-overflow.js +49 -0
  5. package/dist/commonjs/components/base/overflow/fixed-count-overflow.js +70 -0
  6. package/dist/commonjs/components/base/overflow/index.js +16 -0
  7. package/dist/commonjs/components/base/overflow/overflow.js +72 -0
  8. package/dist/commonjs/components/base/overflow/responsive-overflow.js +280 -0
  9. package/dist/commonjs/components/base/overflow/style/index.js +39 -0
  10. package/dist/commonjs/components/base/overflow/types.js +5 -0
  11. package/dist/commonjs/components/base/picker/picker-content.js +16 -12
  12. package/dist/commonjs/components/base/picker/picker-field.js +9 -7
  13. package/dist/commonjs/components/base/picker/picker.js +6 -1
  14. package/dist/commonjs/components/base/picker/style/index.js +7 -1
  15. package/dist/commonjs/components/base/portal/portal-host.js +5 -3
  16. package/dist/commonjs/components/base/select/hooks/use-select-actions.js +155 -0
  17. package/dist/commonjs/components/base/select/hooks/use-select-options.js +169 -0
  18. package/dist/commonjs/components/base/select/hooks/use-selector.js +104 -0
  19. package/dist/commonjs/components/base/select/index.js +16 -0
  20. package/dist/commonjs/components/base/select/select-multiple-content.js +182 -0
  21. package/dist/commonjs/components/base/select/select-popup.js +233 -0
  22. package/dist/commonjs/components/base/select/select-single-content.js +100 -0
  23. package/dist/commonjs/components/base/select/select-suffix.js +67 -0
  24. package/dist/commonjs/components/base/select/select.js +285 -0
  25. package/dist/commonjs/components/base/select/style/index.js +40 -0
  26. package/dist/commonjs/components/base/select/style/select-multiple-content-styles.js +46 -0
  27. package/dist/commonjs/components/base/select/style/select-popup-styles.js +67 -0
  28. package/dist/commonjs/components/base/select/style/select-single-content-styles.js +28 -0
  29. package/dist/commonjs/components/base/select/style/select-styles.js +46 -0
  30. package/dist/commonjs/components/base/select/style/select-suffix-styles.js +21 -0
  31. package/dist/commonjs/components/base/select/types.js +5 -0
  32. package/dist/commonjs/components/base/tabs/style/index.js +37 -0
  33. package/dist/commonjs/components/base/tabs/tabs.js +90 -45
  34. package/dist/commonjs/native-provider/native-provider.js +5 -5
  35. package/dist/commonjs/shared/utils/index.js +11 -0
  36. package/dist/commonjs/shared/utils/object.js +39 -0
  37. package/dist/module/components/base/index.js +2 -0
  38. package/dist/module/components/base/input/base-input.js +4 -2
  39. package/dist/module/components/base/overflow/all-mode-overflow.js +43 -0
  40. package/dist/module/components/base/overflow/fixed-count-overflow.js +64 -0
  41. package/dist/module/components/base/overflow/index.js +3 -0
  42. package/dist/module/components/base/overflow/overflow.js +66 -0
  43. package/dist/module/components/base/overflow/responsive-overflow.js +274 -0
  44. package/dist/module/components/base/overflow/style/index.js +36 -0
  45. package/dist/module/components/base/overflow/types.js +3 -0
  46. package/dist/module/components/base/picker/picker-content.js +16 -12
  47. package/dist/module/components/base/picker/picker-field.js +9 -7
  48. package/dist/module/components/base/picker/picker.js +6 -1
  49. package/dist/module/components/base/picker/style/index.js +6 -0
  50. package/dist/module/components/base/portal/portal-host.js +4 -3
  51. package/dist/module/components/base/select/hooks/use-select-actions.js +151 -0
  52. package/dist/module/components/base/select/hooks/use-select-options.js +162 -0
  53. package/dist/module/components/base/select/hooks/use-selector.js +100 -0
  54. package/dist/module/components/base/select/index.js +3 -0
  55. package/dist/module/components/base/select/select-multiple-content.js +176 -0
  56. package/dist/module/components/base/select/select-popup.js +227 -0
  57. package/dist/module/components/base/select/select-single-content.js +94 -0
  58. package/dist/module/components/base/select/select-suffix.js +61 -0
  59. package/dist/module/components/base/select/select.js +279 -0
  60. package/dist/module/components/base/select/style/index.js +7 -0
  61. package/dist/module/components/base/select/style/select-multiple-content-styles.js +43 -0
  62. package/dist/module/components/base/select/style/select-popup-styles.js +64 -0
  63. package/dist/module/components/base/select/style/select-single-content-styles.js +25 -0
  64. package/dist/module/components/base/select/style/select-styles.js +43 -0
  65. package/dist/module/components/base/select/style/select-suffix-styles.js +18 -0
  66. package/dist/module/components/base/select/types.js +3 -0
  67. package/dist/module/components/base/tabs/style/index.js +33 -0
  68. package/dist/module/components/base/tabs/tabs.js +92 -47
  69. package/dist/module/native-provider/native-provider.js +5 -5
  70. package/dist/module/shared/utils/index.js +2 -1
  71. package/dist/module/shared/utils/object.js +35 -0
  72. package/dist/typescript/components/base/index.d.ts +2 -0
  73. package/dist/typescript/components/base/index.d.ts.map +1 -1
  74. package/dist/typescript/components/base/input/base-input.d.ts.map +1 -1
  75. package/dist/typescript/components/base/overflow/all-mode-overflow.d.ts +11 -0
  76. package/dist/typescript/components/base/overflow/all-mode-overflow.d.ts.map +1 -0
  77. package/dist/typescript/components/base/overflow/fixed-count-overflow.d.ts +11 -0
  78. package/dist/typescript/components/base/overflow/fixed-count-overflow.d.ts.map +1 -0
  79. package/dist/typescript/components/base/overflow/index.d.ts +3 -0
  80. package/dist/typescript/components/base/overflow/index.d.ts.map +1 -0
  81. package/dist/typescript/components/base/overflow/overflow.d.ts +13 -0
  82. package/dist/typescript/components/base/overflow/overflow.d.ts.map +1 -0
  83. package/dist/typescript/components/base/overflow/responsive-overflow.d.ts +12 -0
  84. package/dist/typescript/components/base/overflow/responsive-overflow.d.ts.map +1 -0
  85. package/dist/typescript/components/base/overflow/style/index.d.ts +31 -0
  86. package/dist/typescript/components/base/overflow/style/index.d.ts.map +1 -0
  87. package/dist/typescript/components/base/overflow/types.d.ts +77 -0
  88. package/dist/typescript/components/base/overflow/types.d.ts.map +1 -0
  89. package/dist/typescript/components/base/picker/picker-content.d.ts +12 -0
  90. package/dist/typescript/components/base/picker/picker-content.d.ts.map +1 -1
  91. package/dist/typescript/components/base/picker/picker-field.d.ts.map +1 -1
  92. package/dist/typescript/components/base/picker/picker.d.ts +9 -0
  93. package/dist/typescript/components/base/picker/picker.d.ts.map +1 -1
  94. package/dist/typescript/components/base/picker/style/index.d.ts +6 -0
  95. package/dist/typescript/components/base/picker/style/index.d.ts.map +1 -1
  96. package/dist/typescript/components/base/portal/portal-host.d.ts +1 -0
  97. package/dist/typescript/components/base/portal/portal-host.d.ts.map +1 -1
  98. package/dist/typescript/components/base/portal/types.d.ts +2 -0
  99. package/dist/typescript/components/base/portal/types.d.ts.map +1 -1
  100. package/dist/typescript/components/base/select/hooks/use-select-actions.d.ts +144 -0
  101. package/dist/typescript/components/base/select/hooks/use-select-actions.d.ts.map +1 -0
  102. package/dist/typescript/components/base/select/hooks/use-select-options.d.ts +91 -0
  103. package/dist/typescript/components/base/select/hooks/use-select-options.d.ts.map +1 -0
  104. package/dist/typescript/components/base/select/hooks/use-selector.d.ts +90 -0
  105. package/dist/typescript/components/base/select/hooks/use-selector.d.ts.map +1 -0
  106. package/dist/typescript/components/base/select/index.d.ts +3 -0
  107. package/dist/typescript/components/base/select/index.d.ts.map +1 -0
  108. package/dist/typescript/components/base/select/select-multiple-content.d.ts +51 -0
  109. package/dist/typescript/components/base/select/select-multiple-content.d.ts.map +1 -0
  110. package/dist/typescript/components/base/select/select-popup.d.ts +107 -0
  111. package/dist/typescript/components/base/select/select-popup.d.ts.map +1 -0
  112. package/dist/typescript/components/base/select/select-single-content.d.ts +48 -0
  113. package/dist/typescript/components/base/select/select-single-content.d.ts.map +1 -0
  114. package/dist/typescript/components/base/select/select-suffix.d.ts +43 -0
  115. package/dist/typescript/components/base/select/select-suffix.d.ts.map +1 -0
  116. package/dist/typescript/components/base/select/select.d.ts +40 -0
  117. package/dist/typescript/components/base/select/select.d.ts.map +1 -0
  118. package/dist/typescript/components/base/select/style/index.d.ts +6 -0
  119. package/dist/typescript/components/base/select/style/index.d.ts.map +1 -0
  120. package/dist/typescript/components/base/select/style/select-multiple-content-styles.d.ts +40 -0
  121. package/dist/typescript/components/base/select/style/select-multiple-content-styles.d.ts.map +1 -0
  122. package/dist/typescript/components/base/select/style/select-popup-styles.d.ts +61 -0
  123. package/dist/typescript/components/base/select/style/select-popup-styles.d.ts.map +1 -0
  124. package/dist/typescript/components/base/select/style/select-single-content-styles.d.ts +22 -0
  125. package/dist/typescript/components/base/select/style/select-single-content-styles.d.ts.map +1 -0
  126. package/dist/typescript/components/base/select/style/select-styles.d.ts +40 -0
  127. package/dist/typescript/components/base/select/style/select-styles.d.ts.map +1 -0
  128. package/dist/typescript/components/base/select/style/select-suffix-styles.d.ts +15 -0
  129. package/dist/typescript/components/base/select/style/select-suffix-styles.d.ts.map +1 -0
  130. package/dist/typescript/components/base/select/types.d.ts +206 -0
  131. package/dist/typescript/components/base/select/types.d.ts.map +1 -0
  132. package/dist/typescript/components/base/tabs/style/index.d.ts +29 -0
  133. package/dist/typescript/components/base/tabs/style/index.d.ts.map +1 -0
  134. package/dist/typescript/components/base/tabs/tabs.d.ts +26 -5
  135. package/dist/typescript/components/base/tabs/tabs.d.ts.map +1 -1
  136. package/dist/typescript/native-provider/native-provider.d.ts +2 -0
  137. package/dist/typescript/native-provider/native-provider.d.ts.map +1 -1
  138. package/dist/typescript/shared/utils/index.d.ts +1 -0
  139. package/dist/typescript/shared/utils/index.d.ts.map +1 -1
  140. package/dist/typescript/shared/utils/object.d.ts +21 -0
  141. package/dist/typescript/shared/utils/object.d.ts.map +1 -0
  142. package/package.json +1 -1
  143. package/src/components/base/index.ts +2 -0
  144. package/src/components/base/input/base-input.tsx +4 -2
  145. package/src/components/base/overflow/all-mode-overflow.tsx +49 -0
  146. package/src/components/base/overflow/fixed-count-overflow.tsx +71 -0
  147. package/src/components/base/overflow/index.ts +2 -0
  148. package/src/components/base/overflow/overflow.tsx +60 -0
  149. package/src/components/base/overflow/responsive-overflow.tsx +349 -0
  150. package/src/components/base/overflow/style/index.ts +32 -0
  151. package/src/components/base/overflow/types.ts +75 -0
  152. package/src/components/base/picker/picker-content.tsx +24 -9
  153. package/src/components/base/picker/picker-field.tsx +19 -13
  154. package/src/components/base/picker/picker.tsx +10 -1
  155. package/src/components/base/picker/style/index.ts +4 -0
  156. package/src/components/base/portal/portal-host.tsx +13 -3
  157. package/src/components/base/portal/types.ts +2 -0
  158. package/src/components/base/select/hooks/use-select-actions.ts +263 -0
  159. package/src/components/base/select/hooks/use-select-options.ts +250 -0
  160. package/src/components/base/select/hooks/use-selector.ts +155 -0
  161. package/src/components/base/select/index.ts +2 -0
  162. package/src/components/base/select/select-multiple-content.tsx +292 -0
  163. package/src/components/base/select/select-popup.tsx +384 -0
  164. package/src/components/base/select/select-single-content.tsx +127 -0
  165. package/src/components/base/select/select-suffix.tsx +100 -0
  166. package/src/components/base/select/select.tsx +302 -0
  167. package/src/components/base/select/style/index.ts +5 -0
  168. package/src/components/base/select/style/select-multiple-content-styles.ts +41 -0
  169. package/src/components/base/select/style/select-popup-styles.ts +62 -0
  170. package/src/components/base/select/style/select-single-content-styles.ts +23 -0
  171. package/src/components/base/select/style/select-styles.ts +41 -0
  172. package/src/components/base/select/style/select-suffix-styles.ts +16 -0
  173. package/src/components/base/select/types.ts +261 -0
  174. package/src/components/base/tabs/style/index.ts +32 -0
  175. package/src/components/base/tabs/tabs.tsx +146 -55
  176. package/src/native-provider/native-provider.tsx +4 -4
  177. package/src/shared/utils/index.ts +1 -0
  178. package/src/shared/utils/object.ts +37 -0
@@ -0,0 +1,274 @@
1
+ "use strict";
2
+
3
+ import { useMemoizedFn } from 'ahooks';
4
+ import React, { useEffect, useRef, useState } from 'react';
5
+ import { Text, View } from 'react-native';
6
+
7
+ /**
8
+ * 测量数据 - 存储所有测量的宽度信息
9
+ */
10
+
11
+ /**
12
+ * 计算结果 - 存储基于测量数据计算出的显示状态
13
+ */
14
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
15
+ /**
16
+ * 响应式 Overflow 组件
17
+ * 只有在 props 变化时才重新测量,onLayout 不直接触发测量
18
+ */
19
+ export const ResponsiveOverflow = ({
20
+ data,
21
+ renderItem,
22
+ renderRest,
23
+ prefix,
24
+ suffix,
25
+ style,
26
+ prefixStyle,
27
+ itemStyle,
28
+ suffixStyle,
29
+ restStyle,
30
+ onVisibleCountChange,
31
+ itemsWidthTolerance = 0,
32
+ styles,
33
+ getKey
34
+ }) => {
35
+ // 测量数据
36
+ const measurementDataRef = useRef({
37
+ container: 0,
38
+ items: new Map(),
39
+ prefix: 0,
40
+ suffix: 0,
41
+ rest: 0
42
+ });
43
+
44
+ // 计算结果
45
+ const calculationResultRef = useRef({
46
+ visibleCount: 0,
47
+ showRest: false
48
+ });
49
+
50
+ // 上一次的结果(用于测量期间的显示优化)
51
+ const previousResultRef = useRef({
52
+ visibleCount: 0,
53
+ showRest: false
54
+ });
55
+
56
+ // 测量完成标记
57
+ const measurementFlags = useRef({
58
+ container: false,
59
+ prefix: false,
60
+ suffix: false,
61
+ rest: false,
62
+ items: new Set()
63
+ });
64
+
65
+ // 状态管理
66
+ const [, forceUpdate] = useState({});
67
+ const [remeasureKey, setRemeasureKey] = useState(0);
68
+ const isComplete = useRef(false);
69
+ const triggerRerender = useMemoizedFn(() => forceUpdate({}));
70
+ /**
71
+ * 重置所有测量状态
72
+ */
73
+ const resetMeasurement = useMemoizedFn(() => {
74
+ if (!isComplete.current) {
75
+ return;
76
+ }
77
+ // 保存当前结果作为上一次的结果
78
+ previousResultRef.current = {
79
+ ...calculationResultRef.current
80
+ };
81
+ measurementDataRef.current = {
82
+ container: 0,
83
+ items: new Map(),
84
+ prefix: 0,
85
+ suffix: 0,
86
+ rest: 0
87
+ };
88
+ calculationResultRef.current = {
89
+ visibleCount: 0,
90
+ showRest: false
91
+ };
92
+ isComplete.current = false;
93
+ measurementFlags.current = {
94
+ container: false,
95
+ prefix: false,
96
+ suffix: false,
97
+ rest: false,
98
+ items: new Set()
99
+ };
100
+ setRemeasureKey(prev => prev + 1);
101
+ });
102
+
103
+ /**
104
+ * 检查是否所有必要的测量都已完成
105
+ */
106
+ const isAllMeasurementsComplete = () => {
107
+ const flags = measurementFlags.current;
108
+ if (!flags.container || !flags.rest) return false;
109
+ if (prefix && !flags.prefix) return false;
110
+ if (suffix && !flags.suffix) return false;
111
+
112
+ // 检查所有元素是否都已测量
113
+ for (let i = 0; i < data.length; i++) {
114
+ const key = getKey(data[i], i);
115
+ if (!flags.items.has(key)) return false;
116
+ }
117
+ return true;
118
+ };
119
+
120
+ /**
121
+ * 计算最终显示状态
122
+ */
123
+ const calculateDisplayState = measurementData => {
124
+ const {
125
+ container,
126
+ items,
127
+ prefix,
128
+ suffix,
129
+ rest
130
+ } = measurementData;
131
+ if (!container || data.length === 0) {
132
+ return {
133
+ visibleCount: 0,
134
+ showRest: false
135
+ };
136
+ }
137
+ const availableWidth = container - prefix - suffix;
138
+ let currentWidth = 0;
139
+ let visibleCount = 0;
140
+
141
+ // 计算所有元素的总宽度
142
+ let totalItemsWidth = 0;
143
+ for (let i = 0; i < data.length; i++) {
144
+ const key = getKey(data[i], i);
145
+ const itemWidth = items.get(key) || 0;
146
+ totalItemsWidth += itemWidth;
147
+ }
148
+
149
+ // 如果所有元素都能放下(考虑容差)
150
+ if (totalItemsWidth <= availableWidth + itemsWidthTolerance) {
151
+ return {
152
+ visibleCount: data.length,
153
+ showRest: false
154
+ };
155
+ }
156
+
157
+ // 需要截断,从前往后累积宽度
158
+ for (let i = 0; i < data.length; i++) {
159
+ const key = getKey(data[i], i);
160
+ const itemWidth = items.get(key) || 0;
161
+
162
+ // 检查加上当前元素和rest节点是否会超出
163
+ if (currentWidth + itemWidth + rest > availableWidth) {
164
+ // 如果是第一个元素就超出,至少显示一个
165
+ visibleCount = i === 0 ? 1 : i;
166
+ break;
167
+ }
168
+ currentWidth += itemWidth;
169
+ visibleCount = i + 1;
170
+ }
171
+ return {
172
+ visibleCount,
173
+ showRest: visibleCount < data.length
174
+ };
175
+ };
176
+
177
+ /**
178
+ * 完成测量并更新结果
179
+ */
180
+ const completeMeasurement = useMemoizedFn(() => {
181
+ if (!isAllMeasurementsComplete()) return;
182
+ const result = calculateDisplayState(measurementDataRef.current);
183
+ calculationResultRef.current = result;
184
+ isComplete.current = true;
185
+ previousResultRef.current = {
186
+ ...result
187
+ };
188
+ onVisibleCountChange?.(result.visibleCount);
189
+ triggerRerender();
190
+ });
191
+
192
+ // 影响布局的 props 变化时标记需要重新计算
193
+ useEffect(() => {
194
+ resetMeasurement();
195
+ }, [data, renderItem, renderRest, prefix, suffix, style, prefixStyle, itemStyle, suffixStyle, restStyle, onVisibleCountChange, itemsWidthTolerance, styles, getKey]);
196
+
197
+ /**
198
+ * 布局测量处理函数
199
+ */
200
+ const createLayoutHandler = useMemoizedFn((elementType, itemKey) => event => {
201
+ // 测量完成后不再更新数据
202
+ if (isComplete.current) {
203
+ return;
204
+ }
205
+ const {
206
+ width
207
+ } = event.nativeEvent.layout;
208
+
209
+ // 更新测量数据和标记
210
+ if (elementType === 'items') {
211
+ measurementFlags.current.items.add(itemKey);
212
+ measurementDataRef.current.items.set(itemKey, width);
213
+ } else {
214
+ measurementFlags.current[elementType] = true;
215
+ measurementDataRef.current[elementType] = width;
216
+ }
217
+
218
+ // 检查是否可以完成测量
219
+ completeMeasurement();
220
+ });
221
+
222
+ /**
223
+ * 渲染 Rest 内容
224
+ */
225
+ const renderRestContent = () => {
226
+ const omittedData = data.slice(calculationResultRef.current.visibleCount);
227
+ if (typeof renderRest === 'function') {
228
+ return renderRest(omittedData);
229
+ }
230
+ if (renderRest) {
231
+ return renderRest;
232
+ }
233
+ return /*#__PURE__*/_jsxs(Text, {
234
+ style: [styles.rest, restStyle],
235
+ children: ["+", omittedData.length, "..."]
236
+ });
237
+ };
238
+ return /*#__PURE__*/_jsxs(View, {
239
+ onLayout: createLayoutHandler('container'),
240
+ style: [styles.container, style],
241
+ children: [prefix && /*#__PURE__*/_jsx(View, {
242
+ onLayout: createLayoutHandler('prefix'),
243
+ style: [styles.item, prefixStyle],
244
+ children: prefix
245
+ }), data.map((item, index) => {
246
+ const key = getKey(item, index);
247
+
248
+ // 测量完成后,不渲染超出可见范围的元素
249
+ if (isComplete.current && index >= calculationResultRef.current.visibleCount) {
250
+ return null;
251
+ }
252
+
253
+ // 测量期间,如果有上一次的结果且当前元素超出上一次的可见范围,需要隐藏
254
+ const shouldHideInMeasurement = !isComplete.current && previousResultRef.current.visibleCount > 0 && index >= previousResultRef.current.visibleCount;
255
+ return /*#__PURE__*/_jsx(View, {
256
+ onLayout: createLayoutHandler('items', key),
257
+ style: [styles.item, itemStyle, shouldHideInMeasurement && styles.hidden],
258
+ children: renderItem(item, {
259
+ index
260
+ })
261
+ }, key);
262
+ }), !isComplete.current || calculationResultRef.current.showRest ? /*#__PURE__*/_jsx(View, {
263
+ onLayout: createLayoutHandler('rest'),
264
+ style: [styles.item, restStyle,
265
+ // 测量期间隐藏,测量完成后根据 showRest 决定是否隐藏
266
+ !isComplete.current && !previousResultRef.current.showRest && styles.hidden],
267
+ children: renderRestContent()
268
+ }) : null, suffix && /*#__PURE__*/_jsx(View, {
269
+ onLayout: createLayoutHandler('suffix'),
270
+ style: [styles.item, suffixStyle],
271
+ children: suffix
272
+ })]
273
+ }, remeasureKey);
274
+ };
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ import { createThemedStyles } from "../../../../native-provider/index.js";
4
+
5
+ /**
6
+ * 创建样式
7
+ */
8
+ export const useOverflowStyles = createThemedStyles((theme, {
9
+ maxCount
10
+ }) => ({
11
+ container: {
12
+ flexDirection: 'row',
13
+ flexWrap: maxCount === 'responsive' ? 'nowrap' : 'wrap',
14
+ overflow: 'hidden',
15
+ width: '100%'
16
+ },
17
+ item: {
18
+ flexShrink: 0,
19
+ paddingHorizontal: theme.spacing.xxs / 2,
20
+ paddingVertical: theme.spacing.xxs / 4
21
+ },
22
+ rest: {
23
+ backgroundColor: theme.palette.gray1,
24
+ borderRadius: theme.roundness.sm,
25
+ paddingHorizontal: theme.spacing.xs,
26
+ height: '100%',
27
+ display: 'flex',
28
+ alignItems: 'center',
29
+ color: theme.palette.fontGray3
30
+ },
31
+ hidden: {
32
+ opacity: 0,
33
+ pointerEvents: 'none',
34
+ position: 'absolute'
35
+ }
36
+ }));
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export {};
@@ -3,8 +3,15 @@
3
3
  import React, { useContext, useEffect } from 'react';
4
4
  import { Flex, Modal, ModalHeader, Portal } from "./../index.js";
5
5
  import { PickerContext } from "./picker-context.js";
6
+ import { usePickerContentStyles } from "./style/index.js";
7
+
8
+ /**
9
+ * Picker 内容组件的属性接口
10
+ * 继承自 Modal 组件,排除 visible 和 children 属性
11
+ */
6
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
13
  export const PickerContent = ({
14
+ style,
8
15
  children,
9
16
  headerTitle,
10
17
  headerStyle,
@@ -12,20 +19,20 @@ export const PickerContent = ({
12
19
  ...modalProps
13
20
  }) => {
14
21
  const context = useContext(PickerContext);
22
+ const styles = usePickerContentStyles();
15
23
  let childNode = null;
24
+ const injectToChildrenProps = {
25
+ value: context?.value,
26
+ onChange: context?.onChange
27
+ };
16
28
  if (/*#__PURE__*/React.isValidElement(children)) {
17
- childNode = /*#__PURE__*/React.cloneElement(children, {
18
- value: context?.value,
19
- onChange: context?.onChange
20
- });
29
+ childNode = /*#__PURE__*/React.cloneElement(children, injectToChildrenProps);
21
30
  } else if (typeof children === 'function') {
22
- childNode = children({
23
- value: context?.value,
24
- onChange: context?.onChange
25
- });
31
+ childNode = children(injectToChildrenProps);
26
32
  }
27
33
  useEffect(() => {
28
34
  context?.setDisabled?.(!!childNode?.props?.disabled);
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
36
  }, [childNode?.props?.disabled, context?.setDisabled]);
30
37
  return /*#__PURE__*/_jsx(Portal, {
31
38
  children: /*#__PURE__*/_jsx(Modal, {
@@ -38,10 +45,7 @@ export const PickerContent = ({
38
45
  vertical: true,
39
46
  align: "stretch",
40
47
  gap: 16,
41
- style: {
42
- flex: 1,
43
- padding: 16
44
- },
48
+ style: [styles.container, style],
45
49
  children: [/*#__PURE__*/_jsx(ModalHeader, {
46
50
  title: headerTitle,
47
51
  style: headerStyle,
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import { useLatest } from 'ahooks';
4
+ import { isNil } from 'lodash-es';
4
5
  import React, { useContext, useMemo } from 'react';
5
6
  import { TouchableOpacity } from 'react-native';
6
7
  import { Text } from "./../index.js";
@@ -11,19 +12,20 @@ import { usePickerFieldStyles } from "./style/index.js";
11
12
  import { jsx as _jsx } from "react/jsx-runtime";
12
13
  export function PickerField(props) {
13
14
  const theme = useTheme();
15
+ const styles = usePickerFieldStyles();
14
16
  const context = useContext(PickerContext);
15
17
  const renderValueRef = useLatest(props.renderValue);
16
- const styles = usePickerFieldStyles();
17
-
18
- // const hasValue = !!context?.value || typeof context?.value === 'number';
19
-
20
- const displayValue = useMemo(() => renderValueRef.current?.(context?.value), [context?.value, renderValueRef]);
21
- const textColor = displayValue ? theme.palette.fontGray1 : theme.palette.fontGray3;
18
+ const value = context?.value;
19
+ const hasControllableValue = !isNil(value) && (!Array.isArray(value) || value.length > 0);
20
+ const displayValue = useMemo(() => {
21
+ return typeof renderValueRef.current !== 'function' ? value : renderValueRef.current?.(value);
22
+ }, [value, renderValueRef]);
23
+ const textColor = hasControllableValue ? theme.palette.fontGray1 : theme.palette.fontGray3;
22
24
  return /*#__PURE__*/_jsx(TouchableOpacity, {
23
25
  style: [styles.container, context?.disabled && styles.disabled],
24
26
  onPress: context?.toggleVisible,
25
27
  disabled: !!context?.disabled,
26
- children: safetyRenderChildren(displayValue ?? props.placeholder, displayText => /*#__PURE__*/_jsx(Text, {
28
+ children: safetyRenderChildren(hasControllableValue ? displayValue : props.placeholder, displayText => /*#__PURE__*/_jsx(Text, {
27
29
  size: 14,
28
30
  color: textColor,
29
31
  children: displayText
@@ -3,6 +3,11 @@
3
3
  import { useControllableValue, useMemoizedFn, useToggle } from 'ahooks';
4
4
  import { useMemo, useState } from 'react';
5
5
  import { PickerContext } from "./picker-context.js";
6
+
7
+ /**
8
+ * Picker 组件的基础属性接口
9
+ * @template T 选择器值的类型
10
+ */
6
11
  import { jsx as _jsx } from "react/jsx-runtime";
7
12
  export const Picker = props => {
8
13
  const [visible, {
@@ -23,7 +28,7 @@ export const Picker = props => {
23
28
  setDisabled,
24
29
  toggleVisible,
25
30
  onChange: _onChange
26
- }), [value, visible, disabled, toggleVisible, setDisabled, setValue]);
31
+ }), [value, visible, disabled, toggleVisible, _onChange]);
27
32
  return /*#__PURE__*/_jsx(PickerContext.Provider, {
28
33
  value: contextValue,
29
34
  children: props.children
@@ -12,4 +12,10 @@ export const usePickerFieldStyles = createThemedStyles(theme => ({
12
12
  disabled: {
13
13
  backgroundColor: theme.palette.blueGray2
14
14
  }
15
+ }));
16
+ export const usePickerContentStyles = createThemedStyles(() => ({
17
+ container: {
18
+ flex: 1,
19
+ padding: 16
20
+ }
15
21
  }));
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { useCallback, useEffect, useMemo, useRef } from 'react';
3
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { DeviceEventEmitter, View } from 'react-native';
5
5
  import { NATIVE_UI_PORTAL_ADD_EVENT, NATIVE_UI_PORTAL_REMOVE_EVENT } from "../../../shared/constants/index.js";
6
6
  import { PortalContext, registerHost, unregisterHost } from "./portal-context.js";
@@ -9,7 +9,8 @@ import { usePortalHostStyles } from "./style/index.js";
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  export const PortalHost = ({
11
11
  children,
12
- name
12
+ name,
13
+ style
13
14
  }) => {
14
15
  const portalHostStyles = usePortalHostStyles();
15
16
  const managerRef = useRef(null);
@@ -104,7 +105,7 @@ export const PortalHost = ({
104
105
  return /*#__PURE__*/_jsxs(PortalContext.Provider, {
105
106
  value: contextValue,
106
107
  children: [/*#__PURE__*/_jsx(View, {
107
- style: portalHostStyles.container,
108
+ style: [portalHostStyles.container, style],
108
109
  collapsable: false,
109
110
  children: children
110
111
  }), /*#__PURE__*/_jsx(PortalManager, {
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+
3
+ import { useMemoizedFn } from 'ahooks';
4
+
5
+ /**
6
+ * 选项操作 Hook 参数
7
+ *
8
+ * @template ValueType - 选项值的类型
9
+ */
10
+
11
+ /**
12
+ * 选项操作 Hook 返回值
13
+ */
14
+
15
+ /**
16
+ * 选项操作 Hook
17
+ *
18
+ * 封装 Select 组件中与选项操作相关的逻辑,包括:
19
+ * - 选项点击处理
20
+ * - 标签移除处理
21
+ * - 清除操作处理
22
+ * - 创建新选项处理
23
+ *
24
+ * @template ValueType - 选项值的类型
25
+ *
26
+ * @example
27
+ * const {
28
+ * handleOptionPress,
29
+ * handleTagClose,
30
+ * handleClear,
31
+ * handleCreateOption
32
+ * } = useSelectActions({
33
+ * value,
34
+ * mode,
35
+ * disabled,
36
+ * maxCount,
37
+ * autoClearSearchValue,
38
+ * setValue,
39
+ * clearSearch,
40
+ * closePopup,
41
+ * isOptionSelected,
42
+ * onSelect,
43
+ * onDeselect,
44
+ * onClear
45
+ * });
46
+ */
47
+ export const useSelectActions = params => {
48
+ const {
49
+ value,
50
+ mode,
51
+ disabled,
52
+ maxCount,
53
+ autoClearSearchValue,
54
+ setValue,
55
+ clearSearch,
56
+ closePopup,
57
+ isOptionSelected,
58
+ onActive,
59
+ onSelect,
60
+ onDeselect,
61
+ onClear,
62
+ onCreateOption
63
+ } = params;
64
+
65
+ // 处理选项点击
66
+ const handleOptionPress = useMemoizedFn(option => {
67
+ if (option.disabled) return;
68
+ onActive?.(option.value);
69
+
70
+ // 多选
71
+ if (mode) {
72
+ const values = Array.isArray(value) ? value : [];
73
+ const isSelected = isOptionSelected(option);
74
+ if (isSelected) {
75
+ // 取消选中
76
+ const newValues = values.filter(v => {
77
+ return v !== option.value;
78
+ });
79
+ setValue(newValues);
80
+ onDeselect?.(option.value);
81
+ } else {
82
+ // 选中
83
+ if (maxCount && values.length >= maxCount) {
84
+ return;
85
+ }
86
+ const newValues = [...values, option.value];
87
+ setValue(newValues);
88
+ onSelect?.(option.value, option);
89
+ }
90
+
91
+ // 清空搜索
92
+ if (autoClearSearchValue) {
93
+ clearSearch();
94
+ }
95
+ } else {
96
+ // 单选模式
97
+ setValue(option.value);
98
+ onSelect?.(option.value, option);
99
+ closePopup();
100
+ clearSearch();
101
+ }
102
+ });
103
+
104
+ // 处理标签移除
105
+ const handleTagClose = useMemoizedFn(option => {
106
+ if (disabled) return;
107
+ const values = Array.isArray(value) ? value : [];
108
+ const newValues = values.filter(v => {
109
+ return v !== option.value;
110
+ });
111
+ setValue(newValues);
112
+ onDeselect?.(option.value);
113
+ });
114
+
115
+ // 处理清除
116
+ const handleClear = useMemoizedFn(e => {
117
+ e.stopPropagation();
118
+ setValue(mode ? [] : undefined);
119
+ clearSearch();
120
+ onClear?.();
121
+ });
122
+
123
+ // 处理创建新选项(tags 模式)
124
+ const handleCreateOption = useMemoizedFn(searchValue => {
125
+ if (mode !== 'tags' || !searchValue.trim()) return;
126
+ const newOption = onCreateOption?.(searchValue.trim());
127
+ if (newOption) {
128
+ handleOptionPress(newOption);
129
+ } else {
130
+ // 默认创建逻辑
131
+ const defaultOption = {
132
+ label: searchValue.trim(),
133
+ value: searchValue.trim()
134
+ };
135
+ handleOptionPress(defaultOption);
136
+ }
137
+ clearSearch();
138
+ });
139
+ return {
140
+ handleOptionPress,
141
+ handleTagClose,
142
+ handleClear,
143
+ handleCreateOption
144
+ };
145
+ };
146
+
147
+ /**
148
+ * 搜索配置
149
+ *
150
+ * @template OptionType - 选项类型
151
+ */