@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,39 @@
1
+ import { upperFirst } from "lodash-es";
2
+ import React from "react";
3
+ type DefaultOptions<T extends string> = `default${Capitalize<T>}`;
4
+ export interface ChangeHander<T, P extends unknown[]> {
5
+ (value: T, ...args: P): void;
6
+ }
7
+ type ToString<T extends string | number | symbol> = T extends string ? T : `${Extract<T, number>}`;
8
+ const useControlled: <P extends unknown[], R extends object, K extends keyof R>(
9
+ props: R,
10
+ valueKey: K,
11
+ onChange: ChangeHander<R[K], P>,
12
+ defaultOptions?: { [key in DefaultOptions<ToString<K>>]: R[K] }
13
+ ) => [R[K], ChangeHander<R[K], P>] = (
14
+ props,
15
+ valueKey, // 默认受控属性为 value
16
+ onChange,
17
+ defaultOptions = {}
18
+ ) => {
19
+ const isControlled = Reflect.has(props, valueKey);
20
+ // 受控属性
21
+ const value = props[valueKey];
22
+
23
+ // 默认值
24
+ const defaultKey = `default${upperFirst(valueKey as string)}`;
25
+ const defaultValue = defaultOptions[defaultKey as keyof typeof defaultOptions] || props[defaultKey];
26
+
27
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
28
+
29
+ if (isControlled) return [value, onChange || (() => {})];
30
+ return [
31
+ internalValue,
32
+ (newValue, ...args) => {
33
+ setInternalValue(newValue);
34
+ onChange?.(newValue, ...args);
35
+ }
36
+ ];
37
+ };
38
+
39
+ export default useControlled;
@@ -0,0 +1,16 @@
1
+ import { useMemo } from "react";
2
+
3
+ // defaultProps 将于 19.0.0 废弃,故需实现 hook 在组件内部兼容
4
+ // https://github.com/facebook/react/pull/16210
5
+ export default function useDefaultProps<T>(originalProps: T, defaultProps: Partial<T>): T {
6
+ return useMemo<T>(() => {
7
+ const props: Partial<T> = Object.assign({}, originalProps);
8
+ Object.keys(defaultProps).forEach((key) => {
9
+ // https://github.com/facebook/react/blob/main/packages/react/src/jsx/ReactJSXElement.js#L733-L740
10
+ if (props[key] === undefined) {
11
+ props[key] = defaultProps[key];
12
+ }
13
+ });
14
+ return props as T;
15
+ }, [originalProps, defaultProps]);
16
+ }
@@ -0,0 +1,13 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ export default function useDomRefCallback<T extends HTMLElement>() {
4
+ const [refCurrent, setRefCurrent] = useState<T | null>(null);
5
+
6
+ const refCallback = useCallback((dom: T | null) => {
7
+ if (dom) {
8
+ setRefCurrent(dom);
9
+ }
10
+ }, []);
11
+
12
+ return [refCurrent, refCallback] as const;
13
+ }
@@ -0,0 +1,12 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ // https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
4
+ export default function useDomRefCallback(): [HTMLElement, React.Dispatch<React.SetStateAction<HTMLElement>>] {
5
+ const [refCurrent, setRefCurrent] = useState<HTMLElement>();
6
+
7
+ useCallback((dom: HTMLElement) => {
8
+ if (dom) setRefCurrent(dom);
9
+ }, []);
10
+
11
+ return [refCurrent, setRefCurrent];
12
+ }
@@ -0,0 +1,151 @@
1
+ import { useCallback, useState } from "react";
2
+ import useEventCallback from "./useEventCallback";
3
+
4
+ interface DragSortProps<T> {
5
+ sortOnDraggable: boolean;
6
+ onDragSort?: (context: DragSortContext<T>) => void;
7
+ onDragOverCheck?: {
8
+ x?: boolean;
9
+ targetClassNameRegExp?: RegExp;
10
+ };
11
+ }
12
+
13
+ type DragFnType<T = unknown> = (e?: React.DragEvent<HTMLTableRowElement>, index?: number, record?: T) => void;
14
+ interface DragSortInnerData<T = unknown> {
15
+ dragging?: boolean;
16
+ draggable?: boolean;
17
+ onDragStart?: DragFnType<T>;
18
+ onDragOver?: DragFnType<T>;
19
+ onDrop?: DragFnType<T>;
20
+ onDragEnd?: DragFnType<T>;
21
+ }
22
+
23
+ export interface DragSortInnerProps<T = unknown> extends DragSortInnerData<T> {
24
+ getDragProps?: (index?: number, record?: T) => DragSortInnerData<T>;
25
+ }
26
+
27
+ export interface DragSortContext<T> {
28
+ currentIndex: number;
29
+ current: T;
30
+ targetIndex: number;
31
+ target: T;
32
+ }
33
+
34
+ function useDragSorter<T>(props: DragSortProps<T>): DragSortInnerProps<T> {
35
+ const { sortOnDraggable, onDragSort, onDragOverCheck } = props;
36
+ const [draggingIndex, setDraggingIndex] = useState(-1);
37
+ const [dragStartData, setDragStartData] = useState(null);
38
+ const [isDropped, setIsDropped] = useState(null);
39
+ const [startInfo, setStartInfo] = useState({
40
+ nodeX: 0,
41
+ nodeWidth: 0,
42
+ mouseX: 0
43
+ });
44
+ const onDragSortEvent = useEventCallback(onDragSort as (context: DragSortContext<T>) => void);
45
+
46
+ const onDragOver = useCallback(
47
+ (e, index, record: T) => {
48
+ e.preventDefault();
49
+ if (draggingIndex === index || draggingIndex === -1) return;
50
+ if (onDragOverCheck?.targetClassNameRegExp && !onDragOverCheck?.targetClassNameRegExp.test(e.target?.className)) {
51
+ return;
52
+ }
53
+
54
+ if (onDragOverCheck?.x) {
55
+ if (!startInfo.nodeWidth) return;
56
+
57
+ const { x, width } = e.target.getBoundingClientRect();
58
+ const targetNodeMiddleX = x + width / 2;
59
+ const clientX = e.clientX || 0;
60
+ const draggingNodeLeft = clientX - (startInfo.mouseX - startInfo.nodeX);
61
+ const draggingNodeRight = draggingNodeLeft + startInfo.nodeWidth;
62
+
63
+ let overlap = false;
64
+ if (draggingNodeLeft > x && draggingNodeLeft < x + width) {
65
+ overlap = draggingNodeLeft < targetNodeMiddleX;
66
+ } else {
67
+ overlap = draggingNodeRight > targetNodeMiddleX;
68
+ }
69
+ if (!overlap) return;
70
+ }
71
+
72
+ onDragSortEvent({
73
+ currentIndex: draggingIndex,
74
+ current: dragStartData,
75
+ target: record,
76
+ targetIndex: index
77
+ });
78
+ setDraggingIndex(index);
79
+ },
80
+ [
81
+ draggingIndex,
82
+ onDragOverCheck?.targetClassNameRegExp,
83
+ onDragOverCheck?.x,
84
+ dragStartData,
85
+ startInfo.nodeWidth,
86
+ startInfo.mouseX,
87
+ startInfo.nodeX,
88
+ onDragSortEvent
89
+ ]
90
+ );
91
+
92
+ if (!sortOnDraggable) {
93
+ return {};
94
+ }
95
+
96
+ function onDragStart(e, index, record: T) {
97
+ setDraggingIndex(index);
98
+ setDragStartData(record);
99
+ if (onDragOverCheck) {
100
+ const { x, width } = e.target.getBoundingClientRect();
101
+ setStartInfo({
102
+ nodeX: x,
103
+ nodeWidth: width,
104
+ mouseX: e.clientX || 0
105
+ });
106
+ }
107
+ }
108
+
109
+ function onDrop() {
110
+ setIsDropped(true);
111
+ }
112
+ function onDragEnd() {
113
+ if (!isDropped) {
114
+ // 取消排序,待扩展 api,输出 dragStartData
115
+ }
116
+ setIsDropped(false);
117
+ setDraggingIndex(-1);
118
+ setDragStartData(null);
119
+ }
120
+ function getDragProps(index, record: T) {
121
+ if (sortOnDraggable) {
122
+ return {
123
+ draggable: true,
124
+ onDragStart: (e) => {
125
+ onDragStart(e, index, record);
126
+ },
127
+ onDragOver: (e) => {
128
+ onDragOver(e, index, record);
129
+ },
130
+ onDrop: () => {
131
+ onDrop();
132
+ },
133
+ onDragEnd: () => {
134
+ onDragEnd();
135
+ }
136
+ };
137
+ }
138
+ return {};
139
+ }
140
+
141
+ return {
142
+ onDragStart,
143
+ onDragOver,
144
+ onDrop,
145
+ onDragEnd,
146
+ getDragProps,
147
+ dragging: draggingIndex !== -1
148
+ };
149
+ }
150
+
151
+ export default useDragSorter;
@@ -0,0 +1,47 @@
1
+ // https://github.com/scottrippey/react-use-event-hook/blob/75ba34af9175dc311afb3fb302d6fea44e4a5203/src/useEvent.ts
2
+ // [RFC](https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md)
3
+ import React from "react";
4
+ import noop from "../utils/noop";
5
+
6
+ type AnyFunction = (...args: unknown[]) => unknown;
7
+
8
+ /**
9
+ * Suppress the warning when using useLayoutEffect with SSR. (https://reactjs.org/link/uselayouteffect-ssr)
10
+ * Make use of useInsertionEffect if available.
11
+ */
12
+ const useInsertionEffect = typeof window !== "undefined" ? React.useInsertionEffect || React.useLayoutEffect : noop;
13
+
14
+ /**
15
+ * Render methods should be pure, especially when concurrency is used,
16
+ * so we will throw this error if the callback is called while rendering.
17
+ */
18
+ function useEventCallbackShouldNotBeInvokedBeforeMount() {
19
+ throw new Error(
20
+ "INVALID_USEEVENTCALLBACK_INVOCATION: the callback from useEventCallback cannot be invoked before the component has mounted."
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Similar to useCallback, with a few subtle differences:
26
+ * - The returned function is a stable reference, and will always be the same between renders
27
+ * - No dependency lists required
28
+ * - Properties or state accessed within the callback will always be "current"
29
+ */
30
+ export default function useEventCallback<TCallback extends AnyFunction>(callback: TCallback): TCallback {
31
+ // Keep track of the latest callback:
32
+ const latestRef = React.useRef<TCallback>(useEventCallbackShouldNotBeInvokedBeforeMount as TCallback);
33
+ useInsertionEffect(() => {
34
+ latestRef.current = callback;
35
+ }, [callback]);
36
+
37
+ // Create a stable callback that always calls the latest callback:
38
+ // using useRef instead of useCallback avoids creating and empty array on every render
39
+ const stableRef = React.useRef<TCallback>(null);
40
+ if (!stableRef.current) {
41
+ stableRef.current = function (...args: Parameters<TCallback>) {
42
+ return latestRef.current(...args);
43
+ } as TCallback;
44
+ }
45
+
46
+ return stableRef.current;
47
+ }
@@ -0,0 +1,14 @@
1
+ import useConfig from "./useConfig";
2
+
3
+ // 从 globalConfig 获取 icon 配置用于覆盖组件内置 icon
4
+ export default function useGlobalIcon(tdIcon: Record<string, unknown>) {
5
+ const { icon: globalIcon } = useConfig();
6
+
7
+ const resultIcon = {};
8
+
9
+ Object.keys(tdIcon).forEach((key) => {
10
+ resultIcon[key] = globalIcon?.[key] || tdIcon[key];
11
+ });
12
+
13
+ return resultIcon;
14
+ }
@@ -0,0 +1,14 @@
1
+ import useConfig from "./useConfig";
2
+ import Icon from "@tendaui/icons";
3
+ // 从 globalConfig 获取 icon 配置用于覆盖组件内置 icon
4
+ export default function useGlobalIcon(tdIcon: Record<string, typeof Icon>) {
5
+ const { icon: globalIcon } = useConfig();
6
+
7
+ const resultIcon: Record<string, typeof Icon> = {};
8
+
9
+ Object.keys(tdIcon).forEach((key) => {
10
+ resultIcon[key] = globalIcon?.[key] || tdIcon[key];
11
+ });
12
+
13
+ return resultIcon;
14
+ }
@@ -0,0 +1,13 @@
1
+ import { useRef, useEffect } from "react";
2
+
3
+ const useLatest = <T>(value: T) => {
4
+ const ref = useRef(value);
5
+
6
+ useEffect(() => {
7
+ ref.current = value;
8
+ });
9
+
10
+ return ref;
11
+ };
12
+
13
+ export default useLatest;
@@ -0,0 +1,7 @@
1
+ import { useLayoutEffect, useEffect } from "react";
2
+ import { canUseDocument } from "../utils/dom";
3
+ // 兼容 SSR 环境
4
+ // 在浏览器环境中使用 useLayoutEffect,否则使用 useEffect
5
+ const useIsomorphicLayoutEffect = canUseDocument ? useLayoutEffect : useEffect;
6
+
7
+ export default useIsomorphicLayoutEffect;
@@ -0,0 +1,142 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ export type MouseEventLike = MouseEvent | React.MouseEvent | TouchEvent | React.TouchEvent;
4
+ export type MouseCallback = MouseEvent | React.MouseEvent | Touch | React.Touch;
5
+
6
+ /**
7
+ * 鼠标相对当前元素的坐标
8
+ */
9
+ export interface MouseCoordinate {
10
+ x: number;
11
+ y: number;
12
+ }
13
+ export interface MouseContext {
14
+ coordinate: MouseCoordinate;
15
+ }
16
+
17
+ type MouseEventOptions = {
18
+ enabled?: boolean;
19
+ enableTouch?: boolean; // 支持触摸事件
20
+ onDown?: (e: MouseCallback, ctx: MouseContext) => void;
21
+ onMove?: (e: MouseCallback, ctx: MouseContext) => void;
22
+ onUp?: (e: MouseCallback, ctx: MouseContext) => void;
23
+ onEnter?: (e: MouseCallback, ctx: MouseContext) => void;
24
+ onLeave?: (e: MouseCallback, ctx: MouseContext) => void;
25
+ };
26
+
27
+ const useMouseEvent = (elementRef: React.RefObject<HTMLElement | null>, options: MouseEventOptions) => {
28
+ const { enabled = true, enableTouch = true } = options;
29
+ const isMovingRef = useRef(false);
30
+
31
+ const normalizeEvent = (e: MouseEventLike) => {
32
+ if (!enableTouch) {
33
+ return e as MouseEvent;
34
+ }
35
+ if ("touches" in e && e.touches.length > 0) {
36
+ return e.touches[0];
37
+ }
38
+ if ("changedTouches" in e && e.changedTouches.length > 0) {
39
+ return e.changedTouches[0];
40
+ }
41
+ if ("clientX" in e && "clientY" in e) {
42
+ return e as MouseEvent;
43
+ }
44
+ return undefined;
45
+ };
46
+
47
+ const getCoordinate = (event: MouseCallback) => {
48
+ const rect = elementRef?.current?.getBoundingClientRect();
49
+ if (!rect) {
50
+ return { x: 0, y: 0 };
51
+ }
52
+ const { clientX, clientY } = event;
53
+ const left = clientX - rect.left;
54
+ const top = clientY - rect.top;
55
+ return {
56
+ x: Math.min(Math.max(0, left), rect.width),
57
+ y: Math.min(Math.max(0, top), rect.height)
58
+ };
59
+ };
60
+
61
+ const emitMouseChange = (e: MouseEventLike, handler?: (e: MouseCallback, ctx: MouseContext) => void) => {
62
+ if (!handler) return;
63
+ const event = normalizeEvent(e);
64
+ if (event !== undefined) {
65
+ const coordinate = getCoordinate(event);
66
+ handler(event, { coordinate });
67
+ }
68
+ };
69
+
70
+ const handleMouseMove = (e: MouseEventLike) => {
71
+ if (!isMovingRef.current) return;
72
+ e.preventDefault();
73
+ emitMouseChange(e, options.onMove);
74
+ };
75
+
76
+ const handleMouseUp = (e: MouseEventLike) => {
77
+ if (!isMovingRef.current) return;
78
+ isMovingRef.current = false;
79
+ emitMouseChange(e, options.onUp);
80
+ document.removeEventListener("mouseup", handleMouseUp);
81
+ document.removeEventListener("mousemove", handleMouseMove);
82
+ document.removeEventListener("touchend", handleMouseUp);
83
+ document.removeEventListener("touchmove", handleMouseMove);
84
+ };
85
+
86
+ const handleMouseDown = (e: MouseEventLike) => {
87
+ isMovingRef.current = true;
88
+ emitMouseChange(e, options.onDown);
89
+ document.addEventListener("mouseup", handleMouseUp);
90
+ document.addEventListener("mousemove", handleMouseMove);
91
+ if (!enableTouch) return;
92
+ document.addEventListener("touchend", handleMouseUp);
93
+ document.addEventListener("touchmove", handleMouseMove, { passive: false });
94
+ };
95
+
96
+ const handleMouseEnter = (e: MouseEventLike) => {
97
+ emitMouseChange(e, options.onEnter);
98
+ };
99
+
100
+ const handleMouseLeave = (e: MouseEventLike) => {
101
+ emitMouseChange(e, options.onLeave);
102
+ };
103
+
104
+ useEffect(() => {
105
+ const el = elementRef.current;
106
+ if (!el || !enabled) return;
107
+
108
+ // 基本上只要开启了鼠标事件,就会用到这三个
109
+ // 有的组件虽然只需要 mousemove 的回调结果,但也需要 mousedown 和 mouseup 来控制状态
110
+ el.addEventListener("mousedown", handleMouseDown);
111
+ el.addEventListener("mousemove", handleMouseMove);
112
+ el.addEventListener("mouseup", handleMouseUp);
113
+ // 下面这两个一般是为了处理 hover 状态,可选性监听
114
+ if (options.onEnter) {
115
+ el.addEventListener("mouseenter", handleMouseEnter);
116
+ }
117
+ if (options.onLeave) {
118
+ el.addEventListener("mouseleave", handleMouseLeave);
119
+ }
120
+
121
+ if (!enableTouch) return;
122
+ el.addEventListener("touchstart", handleMouseDown, { passive: false });
123
+ el.addEventListener("touchend", handleMouseUp);
124
+
125
+ return () => {
126
+ el.removeEventListener("mousedown", handleMouseDown);
127
+ el.removeEventListener("mouseenter", handleMouseDown);
128
+ el.removeEventListener("mouseleave", handleMouseLeave);
129
+ el.removeEventListener("mousemove", handleMouseMove);
130
+ el.removeEventListener("mouseup", handleMouseUp);
131
+ el.removeEventListener("touchstart", handleMouseDown);
132
+ el.removeEventListener("touchend", handleMouseUp);
133
+ };
134
+ // eslint-disable-next-line react-hooks/exhaustive-deps
135
+ }, [elementRef.current, options, enabled]);
136
+
137
+ return {
138
+ isMoving: isMovingRef.current
139
+ };
140
+ };
141
+
142
+ export default useMouseEvent;
@@ -0,0 +1,56 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { debounce, isEqual } from "lodash-es";
3
+ import useLatest from "./useLastest";
4
+
5
+ const DEFAULT_OPTIONS = {
6
+ debounceTime: 0,
7
+ config: {
8
+ attributes: true,
9
+ childList: true,
10
+ characterData: true,
11
+ subtree: true
12
+ } as MutationObserverInit
13
+ };
14
+
15
+ type Options = typeof DEFAULT_OPTIONS;
16
+
17
+ export default function useMutationObservable(
18
+ targetEl: HTMLElement | null,
19
+ cb: MutationCallback,
20
+ options = DEFAULT_OPTIONS
21
+ ) {
22
+ const optionsRef = useRef<Options>(null);
23
+ const signalRef = useRef(0);
24
+ const callbackRef = useLatest(cb);
25
+
26
+ if (!isEqual(options, optionsRef.current)) {
27
+ signalRef.current += 1;
28
+ }
29
+
30
+ optionsRef.current = options;
31
+
32
+ useEffect(() => {
33
+ if (!targetEl || !targetEl?.nodeType) return;
34
+ let observer: MutationObserver | null = null;
35
+ try {
36
+ if (optionsRef.current !== null) {
37
+ const { debounceTime, config } = optionsRef.current;
38
+ const mutationCallback: MutationCallback = (...args) => {
39
+ callbackRef.current(...args);
40
+ };
41
+ observer = new MutationObserver(debounceTime > 0 ? debounce(mutationCallback, debounceTime) : mutationCallback);
42
+ observer.observe(targetEl, config);
43
+ }
44
+ } catch (e) {
45
+ console.error(e);
46
+ }
47
+
48
+ return () => {
49
+ if (observer) {
50
+ observer.disconnect();
51
+ observer = null;
52
+ }
53
+ };
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, [targetEl, signalRef.current]);
56
+ }