@tendaui/components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/alert/Alert.tsx +147 -0
  4. package/alert/defaultProps.ts +3 -0
  5. package/alert/index.ts +9 -0
  6. package/alert/style/css.js +1 -0
  7. package/alert/style/index.js +1 -0
  8. package/alert/type.ts +44 -0
  9. package/badge/Badge.tsx +85 -0
  10. package/badge/defaultProps.ts +10 -0
  11. package/badge/index.ts +9 -0
  12. package/badge/style/css.js +1 -0
  13. package/badge/style/index.js +1 -0
  14. package/badge/type.ts +51 -0
  15. package/button/Button.tsx +95 -0
  16. package/button/defaultProps.ts +13 -0
  17. package/button/index.ts +7 -0
  18. package/button/style/css.js +1 -0
  19. package/button/style/index.js +1 -0
  20. package/button/type.ts +82 -0
  21. package/checkbox/Checkbox.tsx +19 -0
  22. package/checkbox/CheckboxGroup.tsx +207 -0
  23. package/checkbox/defaultProps.ts +14 -0
  24. package/checkbox/index.ts +10 -0
  25. package/checkbox/style/css.js +1 -0
  26. package/checkbox/style/index.js +1 -0
  27. package/checkbox/type.ts +117 -0
  28. package/common/Check.tsx +131 -0
  29. package/common/FakeArrow.tsx +36 -0
  30. package/common/PluginContainer.tsx +21 -0
  31. package/common/Portal.tsx +67 -0
  32. package/common.ts +76 -0
  33. package/config-provider/ConfigContext.tsx +21 -0
  34. package/config-provider/ConfigProvider.tsx +53 -0
  35. package/config-provider/index.ts +9 -0
  36. package/config-provider/type.ts +1062 -0
  37. package/dialog/Dialog.tsx +254 -0
  38. package/dialog/DialogCard.tsx +152 -0
  39. package/dialog/defaultProps.ts +25 -0
  40. package/dialog/hooks/useDialogDrag.ts +50 -0
  41. package/dialog/hooks/useDialogEsc.ts +31 -0
  42. package/dialog/hooks/useDialogPosition.ts +36 -0
  43. package/dialog/hooks/useLockStyle.ts +54 -0
  44. package/dialog/index.ts +13 -0
  45. package/dialog/plugin.tsx +78 -0
  46. package/dialog/style/css.js +1 -0
  47. package/dialog/style/index.js +1 -0
  48. package/dialog/type.ts +241 -0
  49. package/dialog/utils.ts +4 -0
  50. package/form/Form.tsx +136 -0
  51. package/form/FormContext.tsx +64 -0
  52. package/form/FormItem.tsx +554 -0
  53. package/form/FormList.tsx +303 -0
  54. package/form/const.ts +6 -0
  55. package/form/defaultProps.ts +26 -0
  56. package/form/formModel.ts +117 -0
  57. package/form/hooks/interface.ts +20 -0
  58. package/form/hooks/useForm.ts +122 -0
  59. package/form/hooks/useFormItemInitialData.ts +95 -0
  60. package/form/hooks/useFormItemStyle.tsx +122 -0
  61. package/form/hooks/useInstance.tsx +275 -0
  62. package/form/hooks/useWatch.ts +42 -0
  63. package/form/index.ts +11 -0
  64. package/form/style/css.js +1 -0
  65. package/form/style/index.js +1 -0
  66. package/form/type.ts +519 -0
  67. package/form/utils/index.ts +69 -0
  68. package/hooks/useAttach.ts +24 -0
  69. package/hooks/useCommonClassName.ts +45 -0
  70. package/hooks/useConfig.ts +3 -0
  71. package/hooks/useControlled.ts +39 -0
  72. package/hooks/useDefaultProps.ts +16 -0
  73. package/hooks/useDomCallback.ts +13 -0
  74. package/hooks/useDomRefCallback.ts +12 -0
  75. package/hooks/useDragSorter.tsx +151 -0
  76. package/hooks/useEventCallback.ts +47 -0
  77. package/hooks/useGlobalConfig.ts +14 -0
  78. package/hooks/useGlobalIcon.ts +14 -0
  79. package/hooks/useLastest.ts +13 -0
  80. package/hooks/useLayoutEffect.ts +7 -0
  81. package/hooks/useMouseEvent.ts +142 -0
  82. package/hooks/useMutationObserver.ts +56 -0
  83. package/hooks/usePopper.ts +189 -0
  84. package/hooks/useRipple.ts +0 -0
  85. package/hooks/useSetState.ts +25 -0
  86. package/hooks/useVirtualScroll.ts +246 -0
  87. package/hooks/useWindowSize.ts +31 -0
  88. package/index.ts +70 -0
  89. package/input/Input.tsx +383 -0
  90. package/input/InputGroup.tsx +29 -0
  91. package/input/defaultProps.ts +22 -0
  92. package/input/index.ts +11 -0
  93. package/input/style/css.js +1 -0
  94. package/input/style/index.js +1 -0
  95. package/input/type.ts +219 -0
  96. package/loading/Gradient.tsx +36 -0
  97. package/loading/Loading.tsx +169 -0
  98. package/loading/circleAdapter.ts +44 -0
  99. package/loading/defaultProps.ts +12 -0
  100. package/loading/index.ts +13 -0
  101. package/loading/style/css.js +1 -0
  102. package/loading/style/index.js +1 -0
  103. package/loading/type.ts +71 -0
  104. package/loading/utils/setStyle.ts +13 -0
  105. package/myform/index.ts +0 -0
  106. package/notification/Notify.ts +24 -0
  107. package/notification/NotifyContainer.tsx +90 -0
  108. package/notification/NotifyContext.tsx +173 -0
  109. package/notification/NotifyItem.tsx +121 -0
  110. package/notification/index.ts +3 -0
  111. package/notification/style/css.js +1 -0
  112. package/notification/style/index.js +1 -0
  113. package/notification/type.ts +23 -0
  114. package/package.json +52 -0
  115. package/popup/Popup.tsx +264 -0
  116. package/popup/defaultProps.ts +13 -0
  117. package/popup/hooks/useTrigger.ts +276 -0
  118. package/popup/index.ts +6 -0
  119. package/popup/style/css.js +1 -0
  120. package/popup/style/index.js +1 -0
  121. package/popup/type.ts +130 -0
  122. package/portal/Portal.tsx +63 -0
  123. package/portal/index.ts +1 -0
  124. package/select/Option.tsx +162 -0
  125. package/select/OptionGroup.tsx +30 -0
  126. package/select/PopupContent.tsx +271 -0
  127. package/select/Select.tsx +586 -0
  128. package/select/defaultProps.ts +27 -0
  129. package/select/hooks/useOptions.ts +120 -0
  130. package/select/hooks/usePanelVirtualScroll.ts +111 -0
  131. package/select/index.ts +9 -0
  132. package/select/style/css.js +1 -0
  133. package/select/style/index.js +2 -0
  134. package/select/type.ts +382 -0
  135. package/select/utils/helper.ts +256 -0
  136. package/select-input/SelectInput.tsx +98 -0
  137. package/select-input/defaultProps.ts +15 -0
  138. package/select-input/hook/useMultiple.tsx +100 -0
  139. package/select-input/hook/useOverlayInnerStyle.ts +84 -0
  140. package/select-input/hook/useSingle.tsx +112 -0
  141. package/select-input/index.ts +6 -0
  142. package/select-input/interface.ts +18 -0
  143. package/select-input/style/css.js +1 -0
  144. package/select-input/style/index.js +1 -0
  145. package/select-input/type.ts +280 -0
  146. package/space/defaultProps.ts +0 -0
  147. package/space/index.ts +0 -0
  148. package/space/type.ts +0 -0
  149. package/style/index.js +2 -0
  150. package/styles/_global.scss +39 -0
  151. package/styles/_vars.scss +386 -0
  152. package/styles/components/alert/_index.scss +175 -0
  153. package/styles/components/alert/_vars.scss +39 -0
  154. package/styles/components/badge/_index.scss +70 -0
  155. package/styles/components/badge/_vars.scss +25 -0
  156. package/styles/components/button/_index.scss +511 -0
  157. package/styles/components/button/_mixins.scss +39 -0
  158. package/styles/components/button/_vars.scss +122 -0
  159. package/styles/components/checkbox/_index.scss +158 -0
  160. package/styles/components/checkbox/_mixin.scss +0 -0
  161. package/styles/components/checkbox/_var.scss +60 -0
  162. package/styles/components/dialog/_animate.scss +135 -0
  163. package/styles/components/dialog/_index.scss +311 -0
  164. package/styles/components/dialog/_mixins.scss +0 -0
  165. package/styles/components/dialog/_vars.scss +59 -0
  166. package/styles/components/form/_index.scss +174 -0
  167. package/styles/components/form/_mixins.scss +76 -0
  168. package/styles/components/form/_vars.scss +100 -0
  169. package/styles/components/input/_index.scss +349 -0
  170. package/styles/components/input/_map.scss +0 -0
  171. package/styles/components/input/_mixins.scss +116 -0
  172. package/styles/components/input/_vars.scss +134 -0
  173. package/styles/components/loading/_index.scss +112 -0
  174. package/styles/components/loading/_vars.scss +39 -0
  175. package/styles/components/notification/_index.scss +160 -0
  176. package/styles/components/notification/_mixins.scss +12 -0
  177. package/styles/components/notification/_vars.scss +59 -0
  178. package/styles/components/popup/_index.scss +82 -0
  179. package/styles/components/popup/_mixin.scss +149 -0
  180. package/styles/components/popup/_var.scss +31 -0
  181. package/styles/components/select/_index.scss +290 -0
  182. package/styles/components/select/_var.scss +65 -0
  183. package/styles/components/select-input/_index.scss +5 -0
  184. package/styles/components/select-input/_var.scss +3 -0
  185. package/styles/components/switch/_index.scss +279 -0
  186. package/styles/components/switch/_mixins.scss +0 -0
  187. package/styles/components/switch/_vars.scss +61 -0
  188. package/styles/components/tag/_index.scss +316 -0
  189. package/styles/components/tag/_var.scss +85 -0
  190. package/styles/components/tag-input/_index.scss +163 -0
  191. package/styles/components/tag-input/_vars.scss +16 -0
  192. package/styles/globals.css +250 -0
  193. package/styles/mixins/_focus.scss +7 -0
  194. package/styles/mixins/_layout.scss +32 -0
  195. package/styles/mixins/_reset.scss +10 -0
  196. package/styles/mixins/_scrollbar.scss +31 -0
  197. package/styles/mixins/_text.scss +48 -0
  198. package/styles/rillple.css +16 -0
  199. package/styles/scrollbar.css +42 -0
  200. package/styles/themes/_dark.scss +191 -0
  201. package/styles/themes/_font.scss +79 -0
  202. package/styles/themes/_index.scss +5 -0
  203. package/styles/themes/_light.scss +190 -0
  204. package/styles/themes/_radius.scss +9 -0
  205. package/styles/themes/_size.scss +68 -0
  206. package/styles/themes.css +66 -0
  207. package/styles/utilities/_animation.scss +57 -0
  208. package/styles/utilities/_tips.scss +9 -0
  209. package/switch/Switch.tsx +120 -0
  210. package/switch/defaultProps.ts +3 -0
  211. package/switch/index.ts +7 -0
  212. package/switch/style/css.js +1 -0
  213. package/switch/style/index.js +1 -0
  214. package/switch/type.ts +46 -0
  215. package/tag/Tag.tsx +149 -0
  216. package/tag/defaultProps.ts +19 -0
  217. package/tag/index.ts +8 -0
  218. package/tag/style/css.js +1 -0
  219. package/tag/style/index.js +1 -0
  220. package/tag/type.ts +170 -0
  221. package/tag-input/TagInput.tsx +215 -0
  222. package/tag-input/defaultProps.ts +15 -0
  223. package/tag-input/hooks/useHover.ts +28 -0
  224. package/tag-input/hooks/useTagList.tsx +131 -0
  225. package/tag-input/hooks/useTagScroll.ts +105 -0
  226. package/tag-input/index.ts +9 -0
  227. package/tag-input/style/css.js +1 -0
  228. package/tag-input/style/index.js +1 -0
  229. package/tag-input/type.ts +224 -0
  230. package/tag-input/useTagList.tsx +131 -0
  231. package/utils/composeRefs.ts +14 -0
  232. package/utils/dom.ts +29 -0
  233. package/utils/forwardRefWithStatics.ts +12 -0
  234. package/utils/getScrollbarWidth.ts +11 -0
  235. package/utils/helper.ts +161 -0
  236. package/utils/isFragment.ts +22 -0
  237. package/utils/listener.ts +37 -0
  238. package/utils/noop.ts +3 -0
  239. package/utils/parentTNode.ts +38 -0
  240. package/utils/parseTNode.ts +38 -0
  241. package/utils/react-render.ts +108 -0
  242. package/utils/ref.ts +6 -0
  243. package/utils/refs.ts +81 -0
  244. package/utils/style.ts +60 -0
  245. package/utils/transition.ts +28 -0
@@ -0,0 +1,189 @@
1
+ // https://github.com/floating-ui/react-popper/blob/master/src/usePopper.js
2
+ // React popper was archived by the owner on Dec 6, 2024.
3
+ // to maintain this hook in the repo since upgrading to support React 19.0
4
+ import { useMemo, useRef, useState } from "react";
5
+ import { flushSync } from "react-dom";
6
+ import {
7
+ createPopper as defaultCreatePopper,
8
+ type Options as PopperOptions,
9
+ type VirtualElement,
10
+ type State as PopperState,
11
+ type Instance as PopperInstance
12
+ } from "@popperjs/core";
13
+
14
+ import isEqual from "react-fast-compare";
15
+ import useIsomorphicLayoutEffect from "./useLayoutEffect";
16
+ import type { Styles } from "../common";
17
+
18
+ type Options = Partial<
19
+ PopperOptions & {
20
+ createPopper: typeof defaultCreatePopper;
21
+ }
22
+ >;
23
+
24
+ type Attributes = {
25
+ [key: string]: { [key: string]: string };
26
+ };
27
+
28
+ type State = {
29
+ styles: Record<string, Styles>;
30
+ attributes: Attributes;
31
+ };
32
+
33
+ import type { ModifierPhases } from "@popperjs/core";
34
+
35
+ interface Modifier {
36
+ name: string;
37
+ enabled?: boolean;
38
+ phase?: ModifierPhases;
39
+ fn?: (data: { state: PopperState }) => void;
40
+ requires?: string[];
41
+ }
42
+
43
+ const EMPTY_MODIFIERS: Modifier[] = [];
44
+
45
+ type UsePopperResult = {
46
+ state?: PopperState;
47
+ styles: Record<string, Styles>;
48
+ attributes: Attributes;
49
+ update: PopperInstance["update"] | null;
50
+ forceUpdate: PopperInstance["forceUpdate"] | null;
51
+ };
52
+
53
+ const fromEntries = <T>(entries: Array<[string, T]>): Record<string, T> =>
54
+ entries.reduce(
55
+ (acc, [key, value]) => {
56
+ acc[key] = value;
57
+ return acc;
58
+ },
59
+ {} as Record<string, T>
60
+ );
61
+
62
+ const usePopper = (
63
+ referenceElement?: Element | VirtualElement,
64
+ popperElement?: HTMLElement,
65
+ options: Options = {}
66
+ ): UsePopperResult => {
67
+ const prevOptions = useRef<PopperOptions>(null);
68
+
69
+ const optionsWithDefaults = {
70
+ onFirstUpdate: options.onFirstUpdate,
71
+ placement: options.placement || "bottom",
72
+ strategy: options.strategy || "absolute",
73
+ modifiers: options.modifiers || EMPTY_MODIFIERS
74
+ };
75
+
76
+ const [state, setState] = useState<State>({
77
+ styles: {
78
+ popper: {
79
+ position: optionsWithDefaults.strategy,
80
+ left: "0",
81
+ top: "0"
82
+ },
83
+ arrow: {
84
+ position: "absolute"
85
+ }
86
+ },
87
+ attributes: {}
88
+ });
89
+
90
+ const updateStateModifier = useMemo(
91
+ () => ({
92
+ name: "updateState",
93
+ enabled: true,
94
+ phase: "write" as ModifierPhases,
95
+ fn: ({ state }: { state: PopperState }) => {
96
+ const elements = Object.keys(state.elements);
97
+
98
+ flushSync(() => {
99
+ setState({
100
+ styles: fromEntries(elements.map((element) => [element, state.styles[element] || {}])) as Record<
101
+ string,
102
+ Styles
103
+ >,
104
+ attributes: fromEntries(elements.map((element) => [element, state.attributes[element]])) as Attributes
105
+ });
106
+ });
107
+ },
108
+ requires: ["computeStyles"]
109
+ }),
110
+ []
111
+ );
112
+
113
+ const popperOptions = useMemo(() => {
114
+ const newOptions = {
115
+ onFirstUpdate: optionsWithDefaults.onFirstUpdate,
116
+ placement: optionsWithDefaults.placement,
117
+ strategy: optionsWithDefaults.strategy,
118
+ modifiers: [
119
+ ...optionsWithDefaults.modifiers,
120
+ updateStateModifier,
121
+ {
122
+ name: "applyStyles",
123
+ enabled: false,
124
+ phase: "write" as ModifierPhases
125
+ }
126
+ ]
127
+ };
128
+
129
+ // 使用 setTimeout 避免在渲染期间访问 ref
130
+ setTimeout(() => {
131
+ if (!isEqual(prevOptions.current, newOptions)) {
132
+ prevOptions.current = newOptions;
133
+ }
134
+ }, 0);
135
+
136
+ return newOptions;
137
+ }, [
138
+ optionsWithDefaults.onFirstUpdate,
139
+ optionsWithDefaults.placement,
140
+ optionsWithDefaults.strategy,
141
+ optionsWithDefaults.modifiers,
142
+ updateStateModifier
143
+ ]);
144
+
145
+ const popperInstanceRef = useRef<PopperInstance | null>(null);
146
+
147
+ useIsomorphicLayoutEffect(() => {
148
+ if (popperInstanceRef.current) {
149
+ popperInstanceRef.current.setOptions(popperOptions);
150
+ }
151
+ }, [popperOptions]);
152
+
153
+ useIsomorphicLayoutEffect(() => {
154
+ if (referenceElement == null || popperElement == null) {
155
+ return;
156
+ }
157
+
158
+ const createPopper = options.createPopper || defaultCreatePopper;
159
+ const popperInstance = createPopper(referenceElement, popperElement, popperOptions);
160
+
161
+ popperInstanceRef.current = popperInstance;
162
+
163
+ return () => {
164
+ popperInstance.destroy();
165
+ popperInstanceRef.current = null;
166
+ };
167
+ }, [referenceElement, popperElement, options.createPopper]);
168
+
169
+ const getPopperInstance = () => popperInstanceRef.current;
170
+
171
+ return {
172
+ get state() {
173
+ const instance = getPopperInstance();
174
+ return instance ? instance.state : undefined;
175
+ },
176
+ styles: state.styles,
177
+ attributes: state.attributes,
178
+ get update() {
179
+ const instance = getPopperInstance();
180
+ return instance ? instance.update : null;
181
+ },
182
+ get forceUpdate() {
183
+ const instance = getPopperInstance();
184
+ return instance ? instance.forceUpdate : null;
185
+ }
186
+ };
187
+ };
188
+
189
+ export default usePopper;
File without changes
@@ -0,0 +1,25 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ const isFunction = (arg: unknown) => typeof arg === "function";
4
+
5
+ /**
6
+ * 管理 object 类型 state 的 Hooks,用法与 class 组件的 this.setState 基本一致。
7
+ * @param initialState
8
+ * @returns [state, setMergeState]
9
+ */
10
+ const useSetState = <T extends object>(
11
+ initialState: T = {} as T
12
+ ): [T, (patch: Partial<T> | ((prevState: T) => Partial<T>)) => void] => {
13
+ const [state, setState] = useState<T>(initialState);
14
+
15
+ const setMergeState = useCallback((patch: ((prevState: T) => Partial<T>) | Partial<T>) => {
16
+ setState((prevState) => ({
17
+ ...prevState,
18
+ ...(isFunction(patch) ? patch(prevState) : patch)
19
+ }));
20
+ }, []);
21
+
22
+ return [state, setMergeState];
23
+ };
24
+
25
+ export default useSetState;
@@ -0,0 +1,246 @@
1
+ /**
2
+ * 通用虚拟滚动,可支持 Select/List/Table/TreeSelect/Cascader 等组件
3
+ */
4
+ import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react";
5
+ import { isEqual } from "lodash-es";
6
+ import type { ScrollToElementParams, TScroll } from "../common";
7
+
8
+ export type UseVirtualScrollParams = {
9
+ /** 列数据 */
10
+ data: { [key: string]: unknown }[];
11
+ scroll: TScroll & {
12
+ fixedRows?: Array<number>;
13
+ };
14
+ };
15
+
16
+ const requestAnimationFrame =
17
+ (typeof window === "undefined" ? false : window.requestAnimationFrame) || ((cb) => setTimeout(cb, 16.6));
18
+
19
+ const useVirtualScroll = (container: MutableRefObject<HTMLElement>, params: UseVirtualScrollParams) => {
20
+ const { data, scroll } = params;
21
+ const dataRef = useRef(data);
22
+
23
+ /** 注意测试:数据长度为空;数据长度小于表格高度等情况。即期望只有数据量达到一定程度才允许开启虚拟滚动 */
24
+ const [visibleData, setVisibleData] = useState<unknown[]>([]);
25
+ // 滚动过程中表格顶部占位距离
26
+ const [translateY, setTranslateY] = useState(() => (data?.length || 0) * (scroll?.rowHeight || 50));
27
+ // 滚动高度,用于显示滚动条
28
+ const [scrollHeight, setScrollHeight] = useState(0);
29
+ const trScrollTopHeightList = useRef<number[]>([]);
30
+ // 已经通过节点渲染计算出来的各自行高
31
+ const [trHeightList, setTrHeightList] = useState<number[]>([]);
32
+ const containerHeight = useRef(0);
33
+ const [startAndEndIndex, setStartAndEndIndex] = useState<[number, number]>(() => [0, (scroll?.bufferSize || 10) * 3]);
34
+
35
+ // 设置初始值
36
+ const tScroll = useMemo(() => {
37
+ if (!scroll) return {};
38
+ return {
39
+ bufferSize: scroll.bufferSize || 10,
40
+ isFixedRowHeight: scroll.isFixedRowHeight ?? false,
41
+ rowHeight: scroll.rowHeight || 47,
42
+ threshold: scroll.threshold || 100,
43
+ type: scroll.type,
44
+ fixedRows: scroll.fixedRows ?? [0, 0]
45
+ };
46
+ }, [scroll]);
47
+
48
+ // 当前场景是否满足开启虚拟滚动的条件
49
+ const isVirtualScroll = useMemo(() => tScroll.type === "virtual" && tScroll.threshold < data.length, [tScroll, data]);
50
+ const getTrScrollTopHeightList = (trHeightList: number[]) => {
51
+ const list: number[] = [];
52
+ // 大数据场景不建议使用 forEach 一类函数迭代
53
+ // 当前行滚动高度 = 上一行滚动高度 + 当前行高度
54
+ for (let i = 0, len = data.length; i < len; i++) {
55
+ list[i] = (list[i - 1] || 0) + (trHeightList[i] || tScroll.rowHeight);
56
+ }
57
+ return list;
58
+ };
59
+
60
+ const updateVisibleData = (trScrollTopHeightList: number[], scrollTop: number) => {
61
+ let currentIndex = -1;
62
+ // 获取当前滚动到哪一个元素(大数据场景不建议使用 forEach 一类函数迭代)
63
+ for (let i = 0, len = trScrollTopHeightList.length; i < len; i++) {
64
+ if (trScrollTopHeightList[i] >= scrollTop) {
65
+ currentIndex = i;
66
+ break;
67
+ }
68
+ }
69
+ let lastIndex = trScrollTopHeightList.length;
70
+ const containerCurrentHeight = containerHeight.current || container.current.getBoundingClientRect().height;
71
+ const scrollBottom = scrollTop + containerCurrentHeight;
72
+ // 获取当前视窗的最后一个元素(大数据场景不建议使用 forEach 一类函数迭代)
73
+ for (let i = currentIndex, len = trScrollTopHeightList.length; i < len; i++) {
74
+ if (trScrollTopHeightList[i] >= scrollBottom) {
75
+ lastIndex = i;
76
+ break;
77
+ }
78
+ }
79
+ if (currentIndex < 0) return;
80
+ const startIndex = Math.max(currentIndex - tScroll.bufferSize, 0);
81
+ const endIndex = Math.min(lastIndex + tScroll.bufferSize, trScrollTopHeightList.length);
82
+
83
+ // 计算固定行情况
84
+ const { fixedRows } = tScroll;
85
+ const [fixedStart, fixedEnd] = fixedRows;
86
+ let fixedStartData = fixedStart ? data.slice(0, fixedStart) : [];
87
+ if (fixedStart && startIndex < fixedStart) {
88
+ fixedStartData = fixedStartData.slice(0, startIndex);
89
+ }
90
+ let fixedEndData = fixedEnd ? data.slice(data.length - fixedEnd) : [];
91
+ const bottomStartIndex = endIndex - data.length + 1 + (fixedEnd ?? 0);
92
+ if (fixedEnd && bottomStartIndex > 0) {
93
+ fixedEndData = fixedEndData.slice(bottomStartIndex);
94
+ }
95
+
96
+ if (startAndEndIndex.join() !== [startIndex, endIndex].join() && startIndex >= 0) {
97
+ const tmpVisibleData = fixedStartData.concat(data.slice(startIndex, endIndex)).concat(fixedEndData);
98
+ setVisibleData(tmpVisibleData);
99
+ const lastScrollTop = trScrollTopHeightList[startIndex - 1];
100
+ const top = lastScrollTop > 0 ? lastScrollTop : 0;
101
+ const stickyHeight = trScrollTopHeightList[Math.min(startIndex, fixedStart) - 1] || 0;
102
+ setTranslateY(top - stickyHeight);
103
+ setStartAndEndIndex([startIndex, endIndex]);
104
+ }
105
+ };
106
+
107
+ // 仅非固定高度场景需要
108
+ const handleRowMounted = (rowData: { ref: HTMLElement; data: { __VIRTUAL_SCROLL_INDEX: number } }) => {
109
+ if (!isVirtualScroll || !rowData || tScroll.isFixedRowHeight || !container?.current) return;
110
+ const trHeight = rowData.ref.offsetHeight;
111
+
112
+ const rowIndex = rowData.data.__VIRTUAL_SCROLL_INDEX;
113
+ const newTrHeightList = [...trHeightList];
114
+ if (newTrHeightList[rowIndex] !== trHeight) {
115
+ newTrHeightList[rowIndex] = trHeight;
116
+ setTrHeightList(newTrHeightList);
117
+
118
+ const scrollTopHeightList = getTrScrollTopHeightList(newTrHeightList);
119
+ trScrollTopHeightList.current = scrollTopHeightList;
120
+
121
+ const lastIndex = scrollTopHeightList.length - 1;
122
+ setScrollHeight(scrollTopHeightList[lastIndex] - containerHeight.current);
123
+ updateVisibleData(scrollTopHeightList, container.current.scrollTop);
124
+ }
125
+ };
126
+
127
+ const handleScroll = () => {
128
+ if (!isVirtualScroll) return;
129
+ updateVisibleData(trScrollTopHeightList.current, container.current.scrollTop);
130
+ };
131
+
132
+ const addIndexToData = (data: { [key: string]: unknown }[]) => {
133
+ data.forEach((item, index) => {
134
+ Reflect.set(item, "__VIRTUAL_SCROLL_INDEX", index);
135
+ });
136
+ };
137
+
138
+ const updateScrollTop = ({ index, top = 0, behavior }: ScrollToElementParams) => {
139
+ const scrollTop = trScrollTopHeightList.current[index] - top;
140
+ container.current?.scrollTo({
141
+ top: scrollTop,
142
+ behavior: behavior || "auto"
143
+ });
144
+ };
145
+
146
+ /**
147
+ * 滚动到指定元素(对外暴露的方法,谨慎修改)
148
+ */
149
+ const scrollToElement = (p: ScrollToElementParams) => {
150
+ updateScrollTop(p);
151
+ if (!tScroll.isFixedRowHeight) {
152
+ requestAnimationFrame(() => {
153
+ const duration = p.time ?? 60;
154
+ const timer = setTimeout(() => {
155
+ updateScrollTop(p);
156
+ clearTimeout(timer);
157
+ }, duration);
158
+ });
159
+ }
160
+ };
161
+
162
+ // 固定高度场景,可直接通过数据长度计算出最大滚动高度
163
+ useEffect(
164
+ () => {
165
+ if (!isVirtualScroll) {
166
+ trScrollTopHeightList.current = getTrScrollTopHeightList(trHeightList);
167
+ return;
168
+ }
169
+
170
+ // 给数据添加下标
171
+ addIndexToData(data);
172
+
173
+ const scrollTopHeightList = trScrollTopHeightList.current;
174
+ const dataChanged = !isEqual(dataRef.current, data);
175
+
176
+ if (scrollTopHeightList?.length === data?.length && !dataChanged) {
177
+ // 正常滚动时更新可见数据
178
+ const lastIndex = scrollTopHeightList.length - 1;
179
+ setScrollHeight(scrollTopHeightList[lastIndex]);
180
+
181
+ updateVisibleData(scrollTopHeightList, container.current.scrollTop);
182
+ } else {
183
+ /**
184
+ /* 进入这个分支的场景可能有:
185
+ * - 初始化
186
+ * - 从非虚拟滚动切换到虚拟滚动
187
+ * - 外部数据动态更新(长度变化、内容结构变化等)
188
+ */
189
+ dataRef.current = data;
190
+ setScrollHeight(data.length * tScroll.rowHeight);
191
+
192
+ // 如果之前存在滚动,基于原先数据计算位置
193
+ const currentScrollTop = container.current?.scrollTop || 0;
194
+ let currentIndex = Math.floor(currentScrollTop / tScroll.rowHeight);
195
+ const prevScrollTopHeightList = trScrollTopHeightList.current;
196
+ for (let i = 0; i < prevScrollTopHeightList?.length; i++) {
197
+ if (prevScrollTopHeightList[i] >= currentScrollTop) {
198
+ currentIndex = i;
199
+ break;
200
+ }
201
+ }
202
+
203
+ const startIndex = Math.max(currentIndex - tScroll.bufferSize, 0);
204
+ const visibleCount = Math.min(tScroll.bufferSize * 3, data.length);
205
+ const endIndex = Math.min(startIndex + visibleCount, data.length);
206
+ const tmpData = data.slice(startIndex, endIndex);
207
+
208
+ let translateY = startIndex * tScroll.rowHeight;
209
+
210
+ if (prevScrollTopHeightList?.length > 0 && startIndex > 0) {
211
+ const prevHeight = prevScrollTopHeightList[Math.min(startIndex - 1, prevScrollTopHeightList.length - 1)] || 0;
212
+ translateY = Math.max(0, prevHeight);
213
+ }
214
+
215
+ setVisibleData(tmpData);
216
+ setTranslateY(translateY);
217
+ }
218
+
219
+ const timer = setTimeout(() => {
220
+ if (container.current) {
221
+ const tmpContainerHeight = container.current.getBoundingClientRect().height;
222
+ containerHeight.current = tmpContainerHeight;
223
+ const scrollTopHeightList = getTrScrollTopHeightList(trHeightList);
224
+ trScrollTopHeightList.current = scrollTopHeightList;
225
+ clearTimeout(timer);
226
+ }
227
+ }, 1);
228
+ },
229
+ // eslint-disable-next-line
230
+ [container, data, tScroll, isVirtualScroll, startAndEndIndex, trHeightList]
231
+ );
232
+
233
+ return {
234
+ visibleData,
235
+ translateY,
236
+ scrollHeight,
237
+ isVirtualScroll,
238
+ handleScroll,
239
+ handleRowMounted,
240
+ scrollToElement
241
+ };
242
+ };
243
+
244
+ export type VirtualScrollConfig = ReturnType<typeof useVirtualScroll>;
245
+
246
+ export default useVirtualScroll;
@@ -0,0 +1,31 @@
1
+ import { useEffect, useState } from "react";
2
+ import { debounce } from "lodash-es";
3
+ import { getWindowSize } from "../utils/dom";
4
+
5
+ export interface WindowSize {
6
+ width: number;
7
+ height: number;
8
+ }
9
+
10
+ function useWindowSize(): WindowSize {
11
+ const [size, setSize] = useState(getWindowSize);
12
+
13
+ useEffect(() => {
14
+ function handleResize() {
15
+ setSize(getWindowSize());
16
+ }
17
+
18
+ const debounceResize = debounce(handleResize, 400);
19
+
20
+ window.addEventListener("resize", debounceResize);
21
+
22
+ return () => {
23
+ window.removeEventListener("resize", debounceResize);
24
+ debounceResize.cancel();
25
+ };
26
+ }, []);
27
+
28
+ return size;
29
+ }
30
+
31
+ export default useWindowSize;
package/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ // export * from './grid';
2
+ export * from "./loading";
3
+ export * from "./popup";
4
+ export * from "./button";
5
+ export * from "./input";
6
+ // export * from './input-adornment';
7
+ export * from "./alert";
8
+ export * from "./badge";
9
+ // export * from './radio';
10
+ export * from "./checkbox";
11
+ // export * from './input-number';
12
+ export * from "./config-provider";
13
+ // export * from './steps';
14
+ // export * from './sticky-tool';
15
+ // export * from './message';
16
+ // export * from './table';
17
+ export * from "./tag";
18
+ export * from "./tag-input";
19
+ export * from "./select";
20
+ export * from "./select-input";
21
+ // export * from './list';
22
+ // export * from './tabs';
23
+ export * from "./notification";
24
+ // export * from './pagination';
25
+ // export * from './menu';
26
+ export * from "./dialog";
27
+ // export * from './tree';
28
+ // export * from './tree-select';
29
+ // export * from './divider';
30
+ export * from "./switch";
31
+ // export * from './anchor';
32
+ // export * from './calendar';
33
+ export * from "./form";
34
+ // export * from './tooltip';
35
+ // export * from './drawer';
36
+ // export * from './progress';
37
+ // export * from './popconfirm';
38
+ // export * from './textarea';
39
+ // export * from './breadcrumb';
40
+ // export * from './affix';
41
+ // export * from './dropdown';
42
+ // export * from './slider';
43
+ // export * from './auto-complete';
44
+ // export * from './cascader';
45
+ // export * from './time-picker';
46
+ // export * from './date-picker';
47
+ // export * from './upload';
48
+ // export * from './swiper';
49
+ // export * from './comment';
50
+ // export * from './transfer';
51
+ // export * from './avatar';
52
+ // export * from './skeleton';
53
+ // export * from './color-picker';
54
+ // export * from './card';
55
+ // export * from './collapse';
56
+ // export * from './range-input';
57
+ // export * from './watermark';
58
+ // export * from './image-viewer';
59
+ // export * from './space';
60
+ // export * from './timeline';
61
+ // export * from './image';
62
+ // export * from './rate';
63
+ // export * from './link';
64
+ // export * from './guide';
65
+ // export * from './back-top';
66
+ // export * from './statistic';
67
+ // export * from './descriptions';
68
+ // export * from './empty';
69
+ // export * from './typography';
70
+ // export * from './qrcode';