@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,162 @@
1
+ "use strict";
2
+
3
+ import { useMemoizedFn } from 'ahooks';
4
+ import { useMemo } from 'react';
5
+ /**
6
+ * 判断选项是否为分组
7
+ *
8
+ * @template ValueType - 选项值的类型
9
+ */
10
+ export const isSelectOptionGroup = item => {
11
+ return 'options' in item;
12
+ };
13
+ export function isSelectFlattenedGroupOption(v) {
14
+ return !!v.isGroup;
15
+ }
16
+ function isOptionType(v) {
17
+ return !v.isGroup;
18
+ }
19
+
20
+ /**
21
+ * Select 选项处理 Hook 参数
22
+ *
23
+ * @template ValueType - 选项值的类型
24
+ * @template OptionType - 选项类型
25
+ * @template M - 选择模式
26
+ * @template RealValueType - 实际值类型
27
+ */
28
+
29
+ /**
30
+ * Select 选项处理 Hook 返回值
31
+ *
32
+ * @template ValueType - 选项值的类型
33
+ * @template OptionType - 选项类型
34
+ */
35
+
36
+ /**
37
+ * Select 选项处理 Hook
38
+ *
39
+ * 封装 Select 组件中与选项相关的逻辑,包括:
40
+ * - 扁平化选项列表
41
+ * - 过滤选项
42
+ * - 获取选中的选项
43
+ * - 判断选项是否被选中
44
+ *
45
+ * @example
46
+ * const {
47
+ * allOptions,
48
+ * filteredOptions,
49
+ * selectedOptions,
50
+ * isOptionSelected
51
+ * } = useSelectOptions({
52
+ * options,
53
+ * value,
54
+ * mode,
55
+ * searchValue,
56
+ * filterOption: true,
57
+ * optionFilterProp: 'label'
58
+ * });
59
+ */
60
+ export const useSelectOptions = params => {
61
+ const {
62
+ options,
63
+ value,
64
+ mode,
65
+ searchValue,
66
+ filterOption,
67
+ filterSort,
68
+ optionFilterProp
69
+ } = params;
70
+
71
+ // 扁平化选项列表
72
+ const flattenedOptions = useMemo(() => {
73
+ const result = [];
74
+ options.forEach(item => {
75
+ if (isSelectOptionGroup(item)) {
76
+ result.push({
77
+ label: item.label,
78
+ key: item.key,
79
+ isGroup: true,
80
+ groupKey: item.key
81
+ });
82
+ item.options.forEach(opt => {
83
+ result.push({
84
+ ...opt,
85
+ groupKey: item.key
86
+ });
87
+ });
88
+ } else {
89
+ result.push(item);
90
+ }
91
+ });
92
+ return result;
93
+ }, [options]);
94
+
95
+ // 获取所有可选项(不包含分组标题)
96
+ const allOptions = useMemo(() => {
97
+ return flattenedOptions.filter(item => !item.isGroup);
98
+ }, [flattenedOptions]);
99
+
100
+ // 过滤选项
101
+ const filteredOptions = useMemo(() => {
102
+ if (!searchValue || !filterOption) {
103
+ return flattenedOptions;
104
+ }
105
+ const filterFn = typeof filterOption === 'function' ? filterOption : (input, option) => {
106
+ const props = Array.isArray(optionFilterProp) ? optionFilterProp : [optionFilterProp || 'label'];
107
+ return props.some(prop => {
108
+ const value = option[prop];
109
+ if (typeof value === 'string') {
110
+ return value.toLowerCase().includes(input.toLowerCase());
111
+ }
112
+ return false;
113
+ });
114
+ };
115
+ const filtered = flattenedOptions.filter(item => {
116
+ if (isSelectFlattenedGroupOption(item)) return true;
117
+ return filterFn(searchValue, item);
118
+ });
119
+
120
+ // 排序
121
+ if (filterSort) {
122
+ const sortableItems = filtered.filter(item => isOptionType(item));
123
+ sortableItems.sort((a, b) => filterSort(a, b, {
124
+ searchValue
125
+ }));
126
+ return sortableItems;
127
+ }
128
+ return filtered;
129
+ }, [flattenedOptions, searchValue, filterOption, filterSort, optionFilterProp]);
130
+
131
+ // 获取选中的选项
132
+ const selectedOptions = useMemo(() => {
133
+ if (!value) return [];
134
+ if (mode) {
135
+ const values = Array.isArray(value) ? value : [];
136
+ return values.map(v => {
137
+ return allOptions.find(opt => opt.value === v);
138
+ }).filter(Boolean);
139
+ } else {
140
+ const option = allOptions.find(opt => opt.value === value);
141
+ return option ? [option] : [];
142
+ }
143
+ }, [value, allOptions, mode]);
144
+
145
+ // 判断选项是否被选中
146
+ const isOptionSelected = useMemoizedFn(option => {
147
+ if (!value) return false;
148
+ if (mode) {
149
+ const values = Array.isArray(value) ? value : [];
150
+ return values.some(v => v === option.value);
151
+ } else {
152
+ return value === option.value;
153
+ }
154
+ });
155
+ return {
156
+ flattenedOptions,
157
+ allOptions,
158
+ filteredOptions,
159
+ selectedOptions,
160
+ isOptionSelected
161
+ };
162
+ };
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+
3
+ import { useControllableValue, useMemoizedFn } from 'ahooks';
4
+ import { useRef, useState } from 'react';
5
+
6
+ /**
7
+ * 选择器 Hook 参数
8
+ */
9
+
10
+ /**
11
+ * 选择器 Hook 返回值
12
+ */
13
+
14
+ /**
15
+ * 选择器 Hook
16
+ *
17
+ * 封装 Select 组件中与选择器相关的逻辑,包括:
18
+ * - 选择器 ref 管理
19
+ * - 打开/关闭状态管理
20
+ * - 选择器位置获取
21
+ * - 焦点状态管理
22
+ * - 选择器点击处理
23
+ *
24
+ * @example
25
+ * const {
26
+ * selectorRef,
27
+ * open,
28
+ * selectorLayout,
29
+ * isFocused,
30
+ * handleSelectorPress,
31
+ * handleSelectorLayout
32
+ * } = useSelector({
33
+ * defaultOpen: false,
34
+ * disabled: false,
35
+ * onOpenChange
36
+ * });
37
+ */
38
+ export const useSelector = params => {
39
+ const selectorRef = useRef(null);
40
+ const [open, setOpen] = useControllableValue(params, {
41
+ valuePropName: 'open',
42
+ defaultValuePropName: 'defaultOpen',
43
+ trigger: 'onOpenChange'
44
+ });
45
+ const [selectorLayout, setSelectorLayout] = useState(null);
46
+ const [isFocused, setIsFocused] = useState(false);
47
+
48
+ // 更新选择器位置
49
+ const updateSelectorLayout = useMemoizedFn(() => {
50
+ if (!selectorRef.current?.measure) return;
51
+ selectorRef.current.measure((_x, _y, width, height, pageX, pageY) => {
52
+ setSelectorLayout({
53
+ x: pageX,
54
+ y: pageY,
55
+ width,
56
+ height
57
+ });
58
+ });
59
+ });
60
+
61
+ // 处理选择器点击
62
+ const handleSelectorPress = useMemoizedFn(() => {
63
+ if (params.disabled) return;
64
+ if (!open) {
65
+ // 打开下拉框时,先获取位置再打开
66
+ updateSelectorLayout();
67
+ setOpen(true);
68
+ setIsFocused(true);
69
+ } else {
70
+ // 关闭下拉框
71
+ setOpen(false);
72
+ setIsFocused(false);
73
+ }
74
+ });
75
+
76
+ // 处理选择器布局变化
77
+ const handleSelectorLayout = useMemoizedFn(() => {
78
+ // 当选择器布局变化时,如果下拉框是打开的,更新位置
79
+ if (open) {
80
+ updateSelectorLayout();
81
+ }
82
+ });
83
+
84
+ // 关闭下拉框
85
+ const closePopup = useMemoizedFn(() => {
86
+ setOpen(false);
87
+ setIsFocused(false);
88
+ });
89
+ return {
90
+ selectorRef,
91
+ open,
92
+ selectorLayout,
93
+ isFocused,
94
+ setOpen,
95
+ updateSelectorLayout,
96
+ handleSelectorPress,
97
+ handleSelectorLayout,
98
+ closePopup
99
+ };
100
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export * from "./select.js";
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+
3
+ import { CloseOutlined } from '@seakoi/svg-icons';
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { Platform, Pressable, View } from 'react-native';
6
+ import { Input, Overflow, Text } from "./../index.js";
7
+ import { useTheme } from "../../../native-provider/index.js";
8
+ import { useSelectMultipleContentStyles } from "./style/index.js";
9
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ const IS_NATIVE = Platform.OS !== 'web';
11
+
12
+ /**
13
+ * Select 多选内容组件的 Props
14
+ */
15
+
16
+ const INPUT_INIT_WIDTH = 1;
17
+ /**
18
+ * Select 多选内容组件
19
+ *
20
+ * 负责渲染多选模式下的标签列表,包括标签的显示、关闭按钮等功能。
21
+ *
22
+ * @template ValueType - 选项值的类型
23
+ */
24
+ export const SelectMultipleContent = ({
25
+ selectedOptions,
26
+ disabled = false,
27
+ maxTagCount,
28
+ maxTagTextLength,
29
+ maxTagPlaceholder,
30
+ tagRender,
31
+ labelRender,
32
+ placeholderStyle,
33
+ inputStyle,
34
+ tagItemStyle,
35
+ tagContentStyle,
36
+ tagRemoveStyle,
37
+ tagContainerStyle,
38
+ onTagClose,
39
+ showSearchInput = false,
40
+ searchValue = '',
41
+ onSearchChange,
42
+ placeholder = '',
43
+ mode,
44
+ onCreateOption,
45
+ open
46
+ }) => {
47
+ const theme = useTheme();
48
+ const styles = useSelectMultipleContentStyles();
49
+ const [inputWidth, setInputWidth] = useState(INPUT_INIT_WIDTH);
50
+ useEffect(() => {
51
+ if (open) {
52
+ return;
53
+ }
54
+ // eslint-disable-next-line react-hooks/set-state-in-effect
55
+ setInputWidth(INPUT_INIT_WIDTH);
56
+ }, [open]);
57
+
58
+ /**
59
+ * 渲染标签项的回调函数
60
+ *
61
+ * @param option - 选项数据
62
+ * @returns 渲染的标签组件
63
+ */
64
+ const renderItem = useCallback(option => {
65
+ // 提取条件变量
66
+ const isOptionDisabled = disabled || !!option.disabled;
67
+ const showCloseButton = !disabled && !option.disabled;
68
+
69
+ // 提取标签内容渲染逻辑
70
+ let tagContent;
71
+ if (tagRender) {
72
+ tagContent = tagRender(option, {
73
+ disabled: isOptionDisabled,
74
+ onClose: () => onTagClose(option),
75
+ closable: showCloseButton
76
+ });
77
+ } else {
78
+ let labelContent;
79
+ if (labelRender) {
80
+ labelContent = labelRender(option, {
81
+ index: 0
82
+ });
83
+ } else if (typeof option.label === 'string' && maxTagTextLength && option.label.length > maxTagTextLength) {
84
+ labelContent = option.label.slice(0, maxTagTextLength) + '...';
85
+ } else {
86
+ labelContent = option.label;
87
+ }
88
+ tagContent = /*#__PURE__*/_jsx(_Fragment, {
89
+ children: labelContent
90
+ });
91
+ }
92
+ return /*#__PURE__*/_jsxs(View, {
93
+ style: [styles.tagItem, tagItemStyle],
94
+ children: [/*#__PURE__*/_jsx(Text, {
95
+ color: theme.palette.brand7,
96
+ size: 12,
97
+ style: [tagContentStyle],
98
+ children: tagContent
99
+ }), showCloseButton && /*#__PURE__*/_jsx(Pressable, {
100
+ style: [styles.tagRemoveButton, tagRemoveStyle],
101
+ onPress: () => onTagClose(option),
102
+ children: /*#__PURE__*/_jsx(CloseOutlined, {
103
+ size: 10,
104
+ color: theme.palette.fontGray3
105
+ })
106
+ })]
107
+ });
108
+ }, [styles.tagItem, styles.tagRemoveButton, tagItemStyle, tagContentStyle, tagRemoveStyle, theme.palette.brand7, theme.palette.fontGray3, tagRender, disabled, onTagClose, labelRender, maxTagTextLength]);
109
+
110
+ /**
111
+ * 渲染剩余标签的回调函数
112
+ *
113
+ * @param omittedItems - 被省略的标签项数组
114
+ * @returns 渲染的剩余标签组件
115
+ */
116
+ const renderRest = useCallback(omittedItems => /*#__PURE__*/_jsx(View, {
117
+ style: [styles.tagItem, tagItemStyle],
118
+ children: /*#__PURE__*/_jsx(Text, {
119
+ color: theme.palette.fontGray2,
120
+ size: 12,
121
+ style: tagContentStyle,
122
+ children: typeof maxTagPlaceholder === 'function' ? maxTagPlaceholder(omittedItems) : maxTagPlaceholder || `+${omittedItems.length}`
123
+ })
124
+ }), [styles.tagItem, theme.palette.fontGray2, tagItemStyle, tagContentStyle, maxTagPlaceholder]);
125
+
126
+ /**
127
+ * 后缀内容的记忆化组件
128
+ */
129
+ const suffix = useMemo(() => {
130
+ if (showSearchInput) {
131
+ return /*#__PURE__*/_jsx(Input, {
132
+ variant: "borderless",
133
+ style: [styles.input, inputStyle, {
134
+ width: selectedOptions.length === 0 ? '100%' : inputWidth
135
+ }],
136
+ inputStyle: styles.inputInner,
137
+ value: searchValue,
138
+ onChangeText: onSearchChange,
139
+ placeholder: selectedOptions.length === 0 ? placeholder : '',
140
+ placeholderTextColor: theme.palette.fontGray3,
141
+ onSubmitEditing: () => {
142
+ if (mode === 'tags' && searchValue.trim() && onCreateOption) {
143
+ onCreateOption(searchValue);
144
+ }
145
+ },
146
+ onContentSizeChange: IS_NATIVE ? e => {
147
+ setInputWidth(e.nativeEvent.contentSize.width);
148
+ } : undefined,
149
+ onChange: IS_NATIVE ? undefined : e => {
150
+ setInputWidth(e.nativeEvent.target.scrollWidth);
151
+ },
152
+ autoFocus: true
153
+ });
154
+ }
155
+ if (selectedOptions.length === 0) {
156
+ return /*#__PURE__*/_jsx(Text, {
157
+ size: 14,
158
+ color: theme.palette.fontGray3,
159
+ style: placeholderStyle,
160
+ children: placeholder
161
+ });
162
+ }
163
+ return null;
164
+ }, [showSearchInput, styles.input, styles.inputInner, inputStyle, placeholderStyle, inputWidth, searchValue, onSearchChange, selectedOptions.length, placeholder, theme.palette.fontGray3, mode, onCreateOption, IS_NATIVE]);
165
+ return /*#__PURE__*/_jsx(Overflow, {
166
+ data: selectedOptions,
167
+ itemKey: "value",
168
+ maxCount: maxTagCount,
169
+ renderItem: renderItem,
170
+ renderRest: renderRest,
171
+ itemsWidthTolerance: 2,
172
+ suffix: suffix,
173
+ style: tagContainerStyle,
174
+ suffixStyle: styles.overflowSuffix
175
+ });
176
+ };
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+
3
+ import { CheckOutlined } from '@seakoi/svg-icons';
4
+ import { useMemoizedFn } from 'ahooks';
5
+ import React, { useMemo } from 'react';
6
+ import { ActivityIndicator, Dimensions, FlatList, Pressable, View } from 'react-native';
7
+ import { Portal, Text } from "./../index.js";
8
+ import { isSelectFlattenedGroupOption } from "./hooks/use-select-options.js";
9
+ import { useTheme } from "../../../native-provider/index.js";
10
+ import { useSelectPopupStyles } from "./style/index.js";
11
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ /** 下拉菜单默认最大高度 */
13
+ export const DEFAULT_POPUP_MAX_HEIGHT = 256;
14
+
15
+ /**
16
+ * SelectPopup 组件的属性
17
+ *
18
+ * @template ValueType - 选项值的类型
19
+ */
20
+
21
+ export const SelectPopup = ({
22
+ open,
23
+ selectorLayout,
24
+ matchWidth,
25
+ placement: forcedPlacement,
26
+ popupMaxHeight = DEFAULT_POPUP_MAX_HEIGHT,
27
+ loading,
28
+ filteredOptions,
29
+ notFoundContent,
30
+ searchValue,
31
+ mode,
32
+ popupRootStyle,
33
+ popupListStyle,
34
+ popupListItemStyle,
35
+ popupGroupLabelStyle,
36
+ selectedIcon,
37
+ optionRender,
38
+ popupRender,
39
+ isOptionSelected,
40
+ onClose,
41
+ onOptionPress,
42
+ onCreateOption,
43
+ onPopupScroll
44
+ }) => {
45
+ const theme = useTheme();
46
+ const styles = useSelectPopupStyles();
47
+
48
+ /**
49
+ * 计算下拉框位置信息
50
+ *
51
+ * @returns 包含位置信息和计算状态的对象
52
+ */
53
+ const positionInfo = useMemo(() => {
54
+ // 如果未打开或没有选择器布局信息,返回未计算状态
55
+ if (!open || !selectorLayout) {
56
+ return {
57
+ placement: 'bottom',
58
+ isPositionCalculated: false
59
+ };
60
+ }
61
+ const screenHeight = Dimensions.get('window').height;
62
+ const spaceBelow = screenHeight - (selectorLayout.y + selectorLayout.height);
63
+ const spaceAbove = selectorLayout.y;
64
+ const spacing = theme.spacing.xxs;
65
+
66
+ // 1. 如果指定了 placement,强制使用指定的位置
67
+ if (forcedPlacement) {
68
+ return {
69
+ placement: forcedPlacement,
70
+ isPositionCalculated: true
71
+ };
72
+ }
73
+
74
+ // 2. 没有指定 placement,尝试在下方
75
+ if (spaceBelow >= popupMaxHeight + spacing) {
76
+ return {
77
+ placement: 'bottom',
78
+ isPositionCalculated: true
79
+ };
80
+ }
81
+
82
+ // 3. 下方空间不够,尝试上方
83
+ if (spaceAbove >= popupMaxHeight + spacing) {
84
+ return {
85
+ placement: 'top',
86
+ isPositionCalculated: true
87
+ };
88
+ }
89
+
90
+ // 4. 上方也不够,直接显示在下方
91
+ return {
92
+ placement: 'bottom',
93
+ isPositionCalculated: true
94
+ };
95
+ }, [open, selectorLayout, theme.spacing.xxs, forcedPlacement, popupMaxHeight]);
96
+ const {
97
+ placement,
98
+ isPositionCalculated
99
+ } = positionInfo;
100
+
101
+ /**
102
+ * 下拉框容器样式
103
+ */
104
+ const popupStyle = useMemo(() => {
105
+ const style = {
106
+ left: selectorLayout.x,
107
+ width: matchWidth === true ? selectorLayout.width : typeof matchWidth === 'number' ? matchWidth : undefined,
108
+ maxHeight: popupMaxHeight
109
+ };
110
+ if (placement === 'bottom') {
111
+ style.top = selectorLayout.y + selectorLayout.height + theme.spacing.xxs;
112
+ } else {
113
+ style.bottom = Dimensions.get('window').height - selectorLayout.y + theme.spacing.xxs;
114
+ }
115
+ return style;
116
+ }, [selectorLayout, matchWidth, placement, theme.spacing.xxs, popupMaxHeight]);
117
+
118
+ /**
119
+ * 列表样式
120
+ */
121
+ const listStyle = useMemo(() => ({
122
+ maxHeight: popupMaxHeight
123
+ }), [popupMaxHeight]);
124
+
125
+ // 渲染下拉选项
126
+ const renderOption = ({
127
+ item,
128
+ index
129
+ }) => {
130
+ if (isSelectFlattenedGroupOption(item)) {
131
+ return /*#__PURE__*/_jsx(Text, {
132
+ color: theme.palette.fontGray3,
133
+ size: 12,
134
+ font: "semiBold",
135
+ style: [styles.popupGroupLabel, popupGroupLabelStyle],
136
+ children: item.label
137
+ });
138
+ }
139
+ const option = item;
140
+ const isSelected = isOptionSelected(option);
141
+ return /*#__PURE__*/_jsx(Pressable, {
142
+ style: [styles.popupListItem, isSelected && styles.popupListItemSelected, option.disabled && styles.popupListItemDisabled, popupListItemStyle, option.style],
143
+ onPress: () => onOptionPress(option),
144
+ disabled: option.disabled,
145
+ children: optionRender ? optionRender(option, {
146
+ index,
147
+ selected: isSelected
148
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
149
+ children: [/*#__PURE__*/_jsx(Text, {
150
+ color: theme.palette.fontGray1,
151
+ size: 14,
152
+ style: styles.popupListItemText,
153
+ children: option.label
154
+ }), isSelected && (selectedIcon || /*#__PURE__*/_jsx(CheckOutlined, {
155
+ color: theme.palette.brand7,
156
+ size: 18
157
+ }))]
158
+ })
159
+ });
160
+ };
161
+
162
+ // 渲染下拉框内容
163
+ const renderPopupContent = () => {
164
+ if (loading) {
165
+ return /*#__PURE__*/_jsx(View, {
166
+ style: styles.loading,
167
+ children: /*#__PURE__*/_jsx(ActivityIndicator, {
168
+ color: theme.palette.brand7
169
+ })
170
+ });
171
+ }
172
+ if (filteredOptions.length === 0) {
173
+ if (mode === 'tags' && searchValue.trim()) {
174
+ return /*#__PURE__*/_jsx(Pressable, {
175
+ style: styles.popupCreateOption,
176
+ onPress: onCreateOption,
177
+ children: /*#__PURE__*/_jsxs(Text, {
178
+ color: theme.palette.brand7,
179
+ size: 14,
180
+ children: ["+ \u521B\u5EFA \"", searchValue.trim(), "\""]
181
+ })
182
+ });
183
+ }
184
+ return /*#__PURE__*/_jsx(View, {
185
+ style: styles.empty,
186
+ children: /*#__PURE__*/_jsx(Text, {
187
+ color: theme.palette.fontGray3,
188
+ size: 14,
189
+ children: notFoundContent
190
+ })
191
+ });
192
+ }
193
+ return /*#__PURE__*/_jsx(FlatList, {
194
+ data: filteredOptions,
195
+ renderItem: renderOption,
196
+ keyExtractor: (item, index) => isSelectFlattenedGroupOption(item) ? `group-${item.key}` : `option-${item.value}-${index}`,
197
+ style: [styles.popupList, listStyle, popupListStyle],
198
+ onScroll: onPopupScroll,
199
+ showsVerticalScrollIndicator: true,
200
+ removeClippedSubviews: true,
201
+ maxToRenderPerBatch: 10,
202
+ windowSize: 5,
203
+ initialNumToRender: 10
204
+ });
205
+ };
206
+
207
+ // 使用 useMemoizedFn 包装回调函数
208
+ const handleBackdropPress = useMemoizedFn(e => {
209
+ if (e.target === e.currentTarget) {
210
+ onClose();
211
+ return;
212
+ }
213
+ e.stopPropagation();
214
+ });
215
+ if (!open || !isPositionCalculated) return null;
216
+ const popupContent = /*#__PURE__*/_jsx(View, {
217
+ style: [styles.popupRoot, popupStyle, popupRootStyle],
218
+ children: renderPopupContent()
219
+ });
220
+ return /*#__PURE__*/_jsx(Portal, {
221
+ children: /*#__PURE__*/_jsx(Pressable, {
222
+ style: styles.backdrop,
223
+ onPress: handleBackdropPress,
224
+ children: popupRender ? popupRender(popupContent) : popupContent
225
+ })
226
+ });
227
+ };