@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,161 @@
1
+ import { camelCase, isNumber } from "lodash-es";
2
+
3
+ export function omit(obj: object, fields: string[]): object {
4
+ const shallowCopy = {
5
+ ...obj
6
+ };
7
+ for (let i = 0; i < fields.length; i++) {
8
+ const key = fields[i];
9
+ delete shallowCopy[key];
10
+ }
11
+ return shallowCopy;
12
+ }
13
+
14
+ export function removeEmptyAttrs<T>(obj: T): Partial<T> {
15
+ const newObj = {};
16
+
17
+ Object.keys(obj).forEach((key) => {
18
+ if (typeof obj[key] !== "undefined" || obj[key] === null) {
19
+ newObj[key] = obj[key];
20
+ }
21
+ });
22
+
23
+ return newObj;
24
+ }
25
+
26
+ export function getTabElementByValue(
27
+ tabs: Array<{ id: string; [key: string]: unknown }> = [],
28
+ value: string
29
+ ): { id: string; [key: string]: unknown } | null {
30
+ const [result] = tabs.filter((item) => {
31
+ const { id } = item;
32
+ return id === value;
33
+ });
34
+ return result || null;
35
+ }
36
+
37
+ export function firstUpperCase(str: string): string {
38
+ return str.toLowerCase().replace(/( |^)[a-z]/g, (char: string) => char.toUpperCase());
39
+ }
40
+
41
+ export type Gradients = { [percent: string]: string };
42
+ export type FromTo = { from: string; to: string };
43
+ export type LinearGradient = { direction?: string } & (Gradients | FromTo);
44
+ export function getBackgroundColor(color: string | string[] | LinearGradient): string {
45
+ if (typeof color === "string") {
46
+ return color;
47
+ }
48
+ if (Array.isArray(color)) {
49
+ if (color[0] && color[0][0] === "#") {
50
+ color.unshift("90deg");
51
+ }
52
+ return `linear-gradient( ${color.join(",")} )`;
53
+ }
54
+ const { from, to, direction = "to right", ...rest } = color;
55
+ let keys = Object.keys(rest);
56
+ if (keys.length) {
57
+ keys = keys.sort((a, b) => parseFloat(a.substr(0, a.length - 1)) - parseFloat(b.substr(0, b.length - 1)));
58
+ const tempArr = keys.map((key: string) => `${rest[key]} ${key}`);
59
+ return `linear-gradient(${direction}, ${tempArr.join(",")})`;
60
+ }
61
+ return `linear-gradient(${direction}, ${from}, ${to})`;
62
+ }
63
+
64
+ // keyboard-event => onKeyboardEvent
65
+ export function getPropsApiByEvent(eventName: string) {
66
+ return camelCase(`on-${eventName}`);
67
+ }
68
+
69
+ /**
70
+ * 兼容样式中支持 number/string 类型的传值 得出最后的结果。
71
+ * @param param number 或 string 类型的可用于样式上的值
72
+ * @returns 可使用的样式值。
73
+ */
74
+ export function pxCompat(param: string | number) {
75
+ return typeof param === "number" ? `${param}px` : param;
76
+ }
77
+
78
+ /**
79
+ * 获取元素相对于容器(祖先)的偏移量
80
+ * @param element 目标元素
81
+ * @param container 容器元素
82
+ * @returns 相对于容器的偏移量
83
+ */
84
+ export function getOffsetTopToContainer(element: HTMLElement, container: HTMLElement) {
85
+ let { offsetTop } = element;
86
+
87
+ let current = element.offsetParent as HTMLElement;
88
+ while (current && current !== container) {
89
+ offsetTop += current.offsetTop;
90
+ current = current.offsetParent as HTMLElement;
91
+ }
92
+ return offsetTop;
93
+ }
94
+
95
+ export function getIEVersion() {
96
+ if (typeof navigator === "undefined" || !navigator) return Number.MAX_SAFE_INTEGER;
97
+
98
+ const { userAgent } = navigator;
99
+ // 判断是否IE<11浏览器
100
+ const isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1;
101
+ // 判断是否IE11浏览器
102
+ const isIE11 = userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;
103
+ if (isIE) {
104
+ const reIE = new RegExp("MSIE (\\d+\\.\\d+);");
105
+ const match = userAgent.match(reIE);
106
+ if (!match) return -1;
107
+ const fIEVersion = parseFloat(match[1]);
108
+ return fIEVersion < 7 ? 6 : fIEVersion;
109
+ }
110
+ if (isIE11) {
111
+ // IE11
112
+ return 11;
113
+ }
114
+ // 不是ie浏览器
115
+ return Number.MAX_SAFE_INTEGER;
116
+ }
117
+
118
+ /**
119
+ * 计算字符串字符的长度并可以截取字符串。
120
+ * @param str 传入字符串
121
+ * @param maxCharacter 规定最大字符串长度
122
+ * @returns 当没有传入maxCharacter时返回字符串字符长度,当传入maxCharacter时返回截取之后的字符串和长度。
123
+ */
124
+ export function getCharacterLength(
125
+ str: string,
126
+ maxCharacter?: number
127
+ ): number | { length: number; characters: string } {
128
+ const hasMaxCharacter = isNumber(maxCharacter);
129
+ if (!str || str.length === 0) {
130
+ if (hasMaxCharacter) {
131
+ return {
132
+ length: 0,
133
+ characters: str
134
+ };
135
+ }
136
+ return 0;
137
+ }
138
+ let len = 0;
139
+ for (let i = 0; i < str.length; i++) {
140
+ let currentStringLength = 0;
141
+ if (str.charCodeAt(i) > 127) {
142
+ currentStringLength = 2;
143
+ } else {
144
+ currentStringLength = 1;
145
+ }
146
+ if (hasMaxCharacter && len + currentStringLength > maxCharacter) {
147
+ return {
148
+ length: len,
149
+ characters: str.slice(0, i)
150
+ };
151
+ }
152
+ len += currentStringLength;
153
+ }
154
+ if (hasMaxCharacter) {
155
+ return {
156
+ length: len,
157
+ characters: str
158
+ };
159
+ }
160
+ return len;
161
+ }
@@ -0,0 +1,22 @@
1
+ // Source from:
2
+ // https://github.com/react-component/util/blob/master/src/React/isFragment.ts
3
+
4
+ const REACT_ELEMENT_TYPE_18 = Symbol.for("react.element");
5
+ const REACT_ELEMENT_TYPE_19 = Symbol.for("react.transitional.element");
6
+ const REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
7
+
8
+ /**
9
+ * Compatible with React 18 or 19 to check if node is a Fragment.
10
+ */
11
+ export default function isFragment(object: React.ReactElement | null | undefined): boolean {
12
+ return (
13
+ // Base object type
14
+ object &&
15
+ typeof object === "object" &&
16
+ // React Element type
17
+ ((object as { $$typeof: symbol }).$$typeof === REACT_ELEMENT_TYPE_18 ||
18
+ (object as { $$typeof: symbol }).$$typeof === REACT_ELEMENT_TYPE_19) &&
19
+ // React Fragment type
20
+ (object as { type: symbol }).type === REACT_FRAGMENT_TYPE
21
+ );
22
+ }
@@ -0,0 +1,37 @@
1
+ import { canUseDocument } from "./dom";
2
+
3
+ type EventHandler = (element: Node, event: string, handler: EventListenerOrEventListenerObject) => void;
4
+
5
+ export const on = ((): EventHandler => {
6
+ if (canUseDocument && document.addEventListener) {
7
+ return (element: Node, event: string, handler: EventListenerOrEventListenerObject): void => {
8
+ if (element && event && handler) {
9
+ element.addEventListener(event, handler, false);
10
+ }
11
+ };
12
+ }
13
+ return (element: Node, event: string, handler: EventListenerOrEventListenerObject): void => {
14
+ if (element && event && handler) {
15
+ (
16
+ element as unknown as { attachEvent: (event: string, handler: EventListenerOrEventListenerObject) => void }
17
+ ).attachEvent(`on${event}`, handler);
18
+ }
19
+ };
20
+ })();
21
+
22
+ export const off = ((): EventHandler => {
23
+ if (canUseDocument && document.removeEventListener) {
24
+ return (element: Node, event: string, handler: EventListenerOrEventListenerObject): void => {
25
+ if (element && event) {
26
+ element.removeEventListener(event, handler, false);
27
+ }
28
+ };
29
+ }
30
+ return (element: Node, event: string, handler: EventListenerOrEventListenerObject): void => {
31
+ if (element && event) {
32
+ (
33
+ element as unknown as { detachEvent: (event: string, handler: EventListenerOrEventListenerObject) => void }
34
+ ).detachEvent(`on${event}`, handler);
35
+ }
36
+ };
37
+ })();
package/utils/noop.ts ADDED
@@ -0,0 +1,3 @@
1
+ const noop = () => {};
2
+
3
+ export default noop;
@@ -0,0 +1,38 @@
1
+ import React, { ReactElement, ReactNode } from "react";
2
+ import { isFunction } from "lodash-es";
3
+ import { TNode } from "../common";
4
+
5
+ // 解析 TNode 数据结构
6
+ export default function parseTNode<T = unknown>(
7
+ renderNode: TNode | TNode<T> | undefined,
8
+ renderParams?: T,
9
+ defaultNode?: ReactNode
10
+ ): ReactNode {
11
+ let node: ReactNode = null;
12
+
13
+ if (typeof renderNode === "function") {
14
+ node = renderNode(renderParams);
15
+ } else if (renderNode === true) {
16
+ node = defaultNode;
17
+ } else if (renderNode !== null) {
18
+ node = renderNode ?? defaultNode;
19
+ }
20
+ return node as ReactNode;
21
+ }
22
+
23
+ /**
24
+ * 解析各种数据类型的 TNode
25
+ * 函数类型:content={(props) => <Icon></Icon>}
26
+ * 组件类型:content={<Button>click me</Button>} 这种方式可以避免函数重复渲染,对应的 props 已经注入
27
+ * 字符类型
28
+ */
29
+ export function parseContentTNode<T>(tnode: TNode<T>, props: T) {
30
+ if (isFunction(tnode)) return tnode(props) as ReactNode;
31
+ if (!tnode || ["string", "number", "boolean"].includes(typeof tnode)) return tnode as ReactNode;
32
+ try {
33
+ return React.cloneElement(tnode as ReactElement, { ...props });
34
+ } catch {
35
+ console.warn("parseContentTNode", `${tnode} is not a valid ReactNode`);
36
+ return null;
37
+ }
38
+ }
@@ -0,0 +1,38 @@
1
+ import React, { ReactElement, ReactNode } from "react";
2
+ import { isFunction } from "lodash-es";
3
+ import { TNode } from "../common";
4
+
5
+ // 解析 TNode 数据结构
6
+ export default function parseTNode<T = Record<string, unknown>>(
7
+ renderNode: TNode<T> | undefined,
8
+ renderParams?: T,
9
+ defaultNode?: ReactNode
10
+ ): ReactNode {
11
+ let node: ReactNode = null;
12
+
13
+ if (typeof renderNode === "function") {
14
+ node = renderNode(renderParams);
15
+ } else if (renderNode === true) {
16
+ node = defaultNode;
17
+ } else if (renderNode !== null) {
18
+ node = renderNode ?? defaultNode;
19
+ }
20
+ return node as ReactNode;
21
+ }
22
+
23
+ /**
24
+ * 解析各种数据类型的 TNode
25
+ * 函数类型:content={(props) => <Icon></Icon>}
26
+ * 组件类型:content={<Button>click me</Button>} 这种方式可以避免函数重复渲染,对应的 props 已经注入
27
+ * 字符类型
28
+ */
29
+ export function parseContentTNode<T>(tnode: TNode<T>, props: T) {
30
+ if (isFunction(tnode)) return tnode(props) as ReactNode;
31
+ if (!tnode || ["string", "number", "boolean"].includes(typeof tnode)) return tnode as ReactNode;
32
+ try {
33
+ return React.cloneElement(tnode as ReactElement, { ...props });
34
+ } catch {
35
+ console.warn("parseContentTNode", `${tnode} is not a valid ReactNode`);
36
+ return null;
37
+ }
38
+ }
@@ -0,0 +1,108 @@
1
+ // Implementation reference from: https://github.com/react-component/util/blob/master/src/React/render.ts
2
+ import type * as React from "react";
3
+ import * as ReactDOM from "react-dom";
4
+ import type { Root } from "react-dom/client";
5
+
6
+ // Let compiler not to search module usage
7
+ const fullClone = {
8
+ ...ReactDOM
9
+ } as typeof ReactDOM & {
10
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
11
+ usingClientEntryPoint?: boolean;
12
+ };
13
+ createRoot?: CreateRoot;
14
+ };
15
+
16
+ type CreateRoot = (container: ContainerType) => Root;
17
+
18
+ const { version, render: reactRender, unmountComponentAtNode } = fullClone;
19
+
20
+ let legacyCreateRoot: CreateRoot;
21
+ try {
22
+ const mainVersion = Number((version || "").split(".")[0]);
23
+ if (mainVersion >= 18 && mainVersion < 19) {
24
+ legacyCreateRoot = fullClone.createRoot;
25
+ }
26
+ if (process.env.NODE_ENV !== "production" && mainVersion >= 19) {
27
+ console.warn(
28
+ "TDesign warning: Please import react-19-adapter in React 19, See link: https://github.com/Tencent/tdesign-react/blob/develop/packages/tdesign-react/site/docs/getting-started.md#如何在-react-19-中使用"
29
+ );
30
+ }
31
+ } catch {
32
+ // Do nothing;
33
+ }
34
+
35
+ function toggleWarning(skip: boolean) {
36
+ const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;
37
+
38
+ if (
39
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
40
+ typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === "object"
41
+ ) {
42
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = skip;
43
+ }
44
+ }
45
+
46
+ const MARK = "__td_react_root__";
47
+
48
+ // ========================== Render ==========================
49
+ type ContainerType = (Element | DocumentFragment) & {
50
+ [MARK]?: Root;
51
+ };
52
+
53
+ function modernRender(node: React.ReactElement, container: ContainerType) {
54
+ toggleWarning(true);
55
+ const root = container[MARK] || legacyCreateRoot(container);
56
+ toggleWarning(false);
57
+
58
+ root.render(node);
59
+
60
+ container[MARK] = root;
61
+ }
62
+
63
+ function legacyRender(node: React.ReactElement, container: ContainerType) {
64
+ reactRender(node, container);
65
+ }
66
+
67
+ export function render(node: React.ReactElement, container: ContainerType) {
68
+ if (legacyCreateRoot) {
69
+ modernRender(node, container);
70
+ return;
71
+ }
72
+
73
+ legacyRender?.(node, container);
74
+ }
75
+
76
+ // ========================= Unmount ==========================
77
+ async function modernUnmount(container: ContainerType) {
78
+ // Delay to unmount to avoid React 18 sync warning
79
+ return Promise.resolve().then(() => {
80
+ container[MARK]?.unmount();
81
+
82
+ delete container[MARK];
83
+ });
84
+ }
85
+
86
+ function legacyUnmount(container: ContainerType) {
87
+ unmountComponentAtNode(container);
88
+ }
89
+
90
+ export async function unmount(container: ContainerType) {
91
+ if (legacyCreateRoot !== undefined) {
92
+ // Delay to unmount to avoid React 18 sync warning
93
+ return modernUnmount(container);
94
+ }
95
+
96
+ legacyUnmount(container);
97
+ }
98
+
99
+ /**
100
+ * @deprecated Set React render function for compatible usage.
101
+ * This is internal usage only compatible with React 19.
102
+ * And will be removed in next major version.
103
+ */
104
+ export function renderAdapter(render?: CreateRoot) {
105
+ if (render) {
106
+ legacyCreateRoot = render;
107
+ }
108
+ }
package/utils/ref.ts ADDED
@@ -0,0 +1,6 @@
1
+ export function getRefDom<T = HTMLElement>(domRef: React.RefObject<T>) {
2
+ if (domRef.current && typeof domRef.current === "object" && "currentElement" in domRef.current) {
3
+ return domRef.current.currentElement;
4
+ }
5
+ return domRef.current;
6
+ }
package/utils/refs.ts ADDED
@@ -0,0 +1,81 @@
1
+ // Source from:
2
+ // https://github.com/react-component/util/blob/master/src/ref.ts
3
+
4
+ import { isValidElement } from "react";
5
+ import { ForwardRef, isMemo } from "react-is";
6
+ import isFragment from "./isFragment";
7
+
8
+ // 判断是否支持 ref 透传
9
+ export const supportRef = (nodeOrComponent: unknown): boolean => {
10
+ if (!nodeOrComponent) {
11
+ return false;
12
+ }
13
+
14
+ // React 19 no need `forwardRef` anymore. So just pass if is a React element.
15
+
16
+ if (
17
+ isReactElement(nodeOrComponent as React.ReactNode) &&
18
+ Object.prototype.propertyIsEnumerable.call((nodeOrComponent as React.ReactElement).props, "ref")
19
+ ) {
20
+ return true;
21
+ }
22
+
23
+ const type = isMemo(nodeOrComponent)
24
+ ? (nodeOrComponent as { type: { type: unknown } }).type.type
25
+ : (nodeOrComponent as { type: unknown }).type;
26
+
27
+ // Function component node
28
+ if (
29
+ typeof type === "function" &&
30
+ !(type as { prototype?: { render?: unknown } }).prototype?.render &&
31
+ (type as { $$typeof?: symbol }).$$typeof !== ForwardRef
32
+ ) {
33
+ return false;
34
+ }
35
+
36
+ // Class component
37
+ if (
38
+ typeof nodeOrComponent === "function" &&
39
+ !(nodeOrComponent as { prototype?: { render?: unknown } }).prototype?.render &&
40
+ (nodeOrComponent as { $$typeof?: symbol }).$$typeof !== ForwardRef
41
+ ) {
42
+ return false;
43
+ }
44
+ return true;
45
+ };
46
+
47
+ // 获取 ref 中的 dom 元素
48
+ export function getRefDom<T = HTMLElement>(domRef: React.RefObject<T>) {
49
+ if (domRef.current && typeof domRef.current === "object" && "currentElement" in domRef.current) {
50
+ return (domRef.current as { currentElement: HTMLElement }).currentElement;
51
+ }
52
+ return domRef.current;
53
+ }
54
+
55
+ interface RefAttributes<T> extends React.Attributes {
56
+ ref: React.Ref<T>;
57
+ }
58
+
59
+ function isReactElement(node: React.ReactNode) {
60
+ return isValidElement(node) && !isFragment(node);
61
+ }
62
+
63
+ export const supportNodeRef = <T = HTMLElement>(node: React.ReactNode): node is React.ReactElement & RefAttributes<T> =>
64
+ isReactElement(node) && supportRef(node);
65
+
66
+ /**
67
+ * In React 19. `ref` is not a property from node.
68
+ * But a property from `props.ref`.
69
+ * To check if `props.ref` exist or fallback to `ref`.
70
+ */
71
+ export const getNodeRef = <T = HTMLElement>(node: React.ReactNode): React.Ref<T> | null => {
72
+ if (node && isReactElement(node)) {
73
+ const ele = node as React.ReactElement & { ref?: React.Ref<T> };
74
+
75
+ // Source from:
76
+ // https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts
77
+
78
+ return Object.prototype.propertyIsEnumerable.call(ele.props, "ref") ? ele.props.ref : ele.ref;
79
+ }
80
+ return null;
81
+ };
package/utils/style.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { canUseDocument } from "./dom";
2
+ export const getCssVarsValue = (name: string, element?: HTMLElement) => {
3
+ if (!canUseDocument) return;
4
+
5
+ const el = element || document.documentElement;
6
+ return getComputedStyle(el).getPropertyValue(name);
7
+ };
8
+
9
+ const trim = (str: string): string => (str || "").replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, "");
10
+
11
+ export function hasClass(el: Element, cls: string) {
12
+ if (!el || !cls) return false;
13
+ if (cls.indexOf(" ") !== -1) throw new Error("className should not contain space.");
14
+ if (el.classList) {
15
+ return el.classList.contains(cls);
16
+ }
17
+ return ` ${el.className} `.indexOf(` ${cls} `) > -1;
18
+ }
19
+
20
+ export const addClass = function (el: Element, cls: string) {
21
+ if (!el) return;
22
+ let curClass = el.className;
23
+ const classes = (cls || "").split(" ");
24
+
25
+ for (let i = 0, j = classes.length; i < j; i++) {
26
+ const clsName = classes[i];
27
+ if (!clsName) continue;
28
+
29
+ if (el.classList) {
30
+ el.classList.add(clsName);
31
+ } else if (!hasClass(el, clsName)) {
32
+ curClass += ` ${clsName}`;
33
+ }
34
+ }
35
+ if (!el.classList) {
36
+ // eslint-disable-next-line
37
+ el.className = curClass;
38
+ }
39
+ };
40
+
41
+ export const removeClass = function (el: Element, cls: string) {
42
+ if (!el || !cls) return;
43
+ const classes = cls.split(" ");
44
+ let curClass = ` ${el.className} `;
45
+
46
+ for (let i = 0, j = classes.length; i < j; i++) {
47
+ const clsName = classes[i];
48
+ if (!clsName) continue;
49
+
50
+ if (el.classList) {
51
+ el.classList.remove(clsName);
52
+ } else if (hasClass(el, clsName)) {
53
+ curClass = curClass.replace(` ${clsName} `, " ");
54
+ }
55
+ }
56
+ if (!el.classList) {
57
+ // eslint-disable-next-line
58
+ el.className = trim(curClass);
59
+ }
60
+ };
@@ -0,0 +1,28 @@
1
+ export interface IAnimationTransitionParams {
2
+ classPrefix: string;
3
+ expandAnimation?: boolean;
4
+ fadeAnimation?: boolean;
5
+ }
6
+
7
+ export const getTransitionParams = ({ classPrefix, expandAnimation, fadeAnimation }: IAnimationTransitionParams) => {
8
+ if (!fadeAnimation) return {};
9
+
10
+ const popupAnimationClassPrefix = expandAnimation
11
+ ? `${classPrefix}-popup--animation-expand`
12
+ : `${classPrefix}-popup--animation`;
13
+
14
+ return {
15
+ // 与公共 className 保持一致
16
+ classNames: {
17
+ appear: `${popupAnimationClassPrefix}-enter ${popupAnimationClassPrefix}-enter-active`,
18
+ appearActive: `${popupAnimationClassPrefix}-enter-active`,
19
+ appearDone: `${popupAnimationClassPrefix}-enter-active ${popupAnimationClassPrefix}-enter-to`,
20
+ enter: `${popupAnimationClassPrefix}-enter ${popupAnimationClassPrefix}-enter-active`,
21
+ enterActive: `${popupAnimationClassPrefix}-enter-active`,
22
+ enterDone: `${popupAnimationClassPrefix}-enter-active ${popupAnimationClassPrefix}-enter-to`,
23
+ exit: `${popupAnimationClassPrefix}-leave ${popupAnimationClassPrefix}-leave-active`,
24
+ exitActive: `${popupAnimationClassPrefix}-leave-active`,
25
+ exitDone: `${popupAnimationClassPrefix}-leave-active ${popupAnimationClassPrefix}-leave-to`
26
+ }
27
+ };
28
+ };