@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.
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/alert/Alert.tsx +147 -0
- package/alert/defaultProps.ts +3 -0
- package/alert/index.ts +9 -0
- package/alert/style/css.js +1 -0
- package/alert/style/index.js +1 -0
- package/alert/type.ts +44 -0
- package/badge/Badge.tsx +85 -0
- package/badge/defaultProps.ts +10 -0
- package/badge/index.ts +9 -0
- package/badge/style/css.js +1 -0
- package/badge/style/index.js +1 -0
- package/badge/type.ts +51 -0
- package/button/Button.tsx +95 -0
- package/button/defaultProps.ts +13 -0
- package/button/index.ts +7 -0
- package/button/style/css.js +1 -0
- package/button/style/index.js +1 -0
- package/button/type.ts +82 -0
- package/checkbox/Checkbox.tsx +19 -0
- package/checkbox/CheckboxGroup.tsx +207 -0
- package/checkbox/defaultProps.ts +14 -0
- package/checkbox/index.ts +10 -0
- package/checkbox/style/css.js +1 -0
- package/checkbox/style/index.js +1 -0
- package/checkbox/type.ts +117 -0
- package/common/Check.tsx +131 -0
- package/common/FakeArrow.tsx +36 -0
- package/common/PluginContainer.tsx +21 -0
- package/common/Portal.tsx +67 -0
- package/common.ts +76 -0
- package/config-provider/ConfigContext.tsx +21 -0
- package/config-provider/ConfigProvider.tsx +53 -0
- package/config-provider/index.ts +9 -0
- package/config-provider/type.ts +1062 -0
- package/dialog/Dialog.tsx +254 -0
- package/dialog/DialogCard.tsx +152 -0
- package/dialog/defaultProps.ts +25 -0
- package/dialog/hooks/useDialogDrag.ts +50 -0
- package/dialog/hooks/useDialogEsc.ts +31 -0
- package/dialog/hooks/useDialogPosition.ts +36 -0
- package/dialog/hooks/useLockStyle.ts +54 -0
- package/dialog/index.ts +13 -0
- package/dialog/plugin.tsx +78 -0
- package/dialog/style/css.js +1 -0
- package/dialog/style/index.js +1 -0
- package/dialog/type.ts +241 -0
- package/dialog/utils.ts +4 -0
- package/form/Form.tsx +136 -0
- package/form/FormContext.tsx +64 -0
- package/form/FormItem.tsx +554 -0
- package/form/FormList.tsx +303 -0
- package/form/const.ts +6 -0
- package/form/defaultProps.ts +26 -0
- package/form/formModel.ts +117 -0
- package/form/hooks/interface.ts +20 -0
- package/form/hooks/useForm.ts +122 -0
- package/form/hooks/useFormItemInitialData.ts +95 -0
- package/form/hooks/useFormItemStyle.tsx +122 -0
- package/form/hooks/useInstance.tsx +275 -0
- package/form/hooks/useWatch.ts +42 -0
- package/form/index.ts +11 -0
- package/form/style/css.js +1 -0
- package/form/style/index.js +1 -0
- package/form/type.ts +519 -0
- package/form/utils/index.ts +69 -0
- package/hooks/useAttach.ts +24 -0
- package/hooks/useCommonClassName.ts +45 -0
- package/hooks/useConfig.ts +3 -0
- package/hooks/useControlled.ts +39 -0
- package/hooks/useDefaultProps.ts +16 -0
- package/hooks/useDomCallback.ts +13 -0
- package/hooks/useDomRefCallback.ts +12 -0
- package/hooks/useDragSorter.tsx +151 -0
- package/hooks/useEventCallback.ts +47 -0
- package/hooks/useGlobalConfig.ts +14 -0
- package/hooks/useGlobalIcon.ts +14 -0
- package/hooks/useLastest.ts +13 -0
- package/hooks/useLayoutEffect.ts +7 -0
- package/hooks/useMouseEvent.ts +142 -0
- package/hooks/useMutationObserver.ts +56 -0
- package/hooks/usePopper.ts +189 -0
- package/hooks/useRipple.ts +0 -0
- package/hooks/useSetState.ts +25 -0
- package/hooks/useVirtualScroll.ts +246 -0
- package/hooks/useWindowSize.ts +31 -0
- package/index.ts +70 -0
- package/input/Input.tsx +383 -0
- package/input/InputGroup.tsx +29 -0
- package/input/defaultProps.ts +22 -0
- package/input/index.ts +11 -0
- package/input/style/css.js +1 -0
- package/input/style/index.js +1 -0
- package/input/type.ts +219 -0
- package/loading/Gradient.tsx +36 -0
- package/loading/Loading.tsx +169 -0
- package/loading/circleAdapter.ts +44 -0
- package/loading/defaultProps.ts +12 -0
- package/loading/index.ts +13 -0
- package/loading/style/css.js +1 -0
- package/loading/style/index.js +1 -0
- package/loading/type.ts +71 -0
- package/loading/utils/setStyle.ts +13 -0
- package/myform/index.ts +0 -0
- package/notification/Notify.ts +24 -0
- package/notification/NotifyContainer.tsx +90 -0
- package/notification/NotifyContext.tsx +173 -0
- package/notification/NotifyItem.tsx +121 -0
- package/notification/index.ts +3 -0
- package/notification/style/css.js +1 -0
- package/notification/style/index.js +1 -0
- package/notification/type.ts +23 -0
- package/package.json +52 -0
- package/popup/Popup.tsx +264 -0
- package/popup/defaultProps.ts +13 -0
- package/popup/hooks/useTrigger.ts +276 -0
- package/popup/index.ts +6 -0
- package/popup/style/css.js +1 -0
- package/popup/style/index.js +1 -0
- package/popup/type.ts +130 -0
- package/portal/Portal.tsx +63 -0
- package/portal/index.ts +1 -0
- package/select/Option.tsx +162 -0
- package/select/OptionGroup.tsx +30 -0
- package/select/PopupContent.tsx +271 -0
- package/select/Select.tsx +586 -0
- package/select/defaultProps.ts +27 -0
- package/select/hooks/useOptions.ts +120 -0
- package/select/hooks/usePanelVirtualScroll.ts +111 -0
- package/select/index.ts +9 -0
- package/select/style/css.js +1 -0
- package/select/style/index.js +2 -0
- package/select/type.ts +382 -0
- package/select/utils/helper.ts +256 -0
- package/select-input/SelectInput.tsx +98 -0
- package/select-input/defaultProps.ts +15 -0
- package/select-input/hook/useMultiple.tsx +100 -0
- package/select-input/hook/useOverlayInnerStyle.ts +84 -0
- package/select-input/hook/useSingle.tsx +112 -0
- package/select-input/index.ts +6 -0
- package/select-input/interface.ts +18 -0
- package/select-input/style/css.js +1 -0
- package/select-input/style/index.js +1 -0
- package/select-input/type.ts +280 -0
- package/space/defaultProps.ts +0 -0
- package/space/index.ts +0 -0
- package/space/type.ts +0 -0
- package/style/index.js +2 -0
- package/styles/_global.scss +39 -0
- package/styles/_vars.scss +386 -0
- package/styles/components/alert/_index.scss +175 -0
- package/styles/components/alert/_vars.scss +39 -0
- package/styles/components/badge/_index.scss +70 -0
- package/styles/components/badge/_vars.scss +25 -0
- package/styles/components/button/_index.scss +511 -0
- package/styles/components/button/_mixins.scss +39 -0
- package/styles/components/button/_vars.scss +122 -0
- package/styles/components/checkbox/_index.scss +158 -0
- package/styles/components/checkbox/_mixin.scss +0 -0
- package/styles/components/checkbox/_var.scss +60 -0
- package/styles/components/dialog/_animate.scss +135 -0
- package/styles/components/dialog/_index.scss +311 -0
- package/styles/components/dialog/_mixins.scss +0 -0
- package/styles/components/dialog/_vars.scss +59 -0
- package/styles/components/form/_index.scss +174 -0
- package/styles/components/form/_mixins.scss +76 -0
- package/styles/components/form/_vars.scss +100 -0
- package/styles/components/input/_index.scss +349 -0
- package/styles/components/input/_map.scss +0 -0
- package/styles/components/input/_mixins.scss +116 -0
- package/styles/components/input/_vars.scss +134 -0
- package/styles/components/loading/_index.scss +112 -0
- package/styles/components/loading/_vars.scss +39 -0
- package/styles/components/notification/_index.scss +160 -0
- package/styles/components/notification/_mixins.scss +12 -0
- package/styles/components/notification/_vars.scss +59 -0
- package/styles/components/popup/_index.scss +82 -0
- package/styles/components/popup/_mixin.scss +149 -0
- package/styles/components/popup/_var.scss +31 -0
- package/styles/components/select/_index.scss +290 -0
- package/styles/components/select/_var.scss +65 -0
- package/styles/components/select-input/_index.scss +5 -0
- package/styles/components/select-input/_var.scss +3 -0
- package/styles/components/switch/_index.scss +279 -0
- package/styles/components/switch/_mixins.scss +0 -0
- package/styles/components/switch/_vars.scss +61 -0
- package/styles/components/tag/_index.scss +316 -0
- package/styles/components/tag/_var.scss +85 -0
- package/styles/components/tag-input/_index.scss +163 -0
- package/styles/components/tag-input/_vars.scss +16 -0
- package/styles/globals.css +250 -0
- package/styles/mixins/_focus.scss +7 -0
- package/styles/mixins/_layout.scss +32 -0
- package/styles/mixins/_reset.scss +10 -0
- package/styles/mixins/_scrollbar.scss +31 -0
- package/styles/mixins/_text.scss +48 -0
- package/styles/rillple.css +16 -0
- package/styles/scrollbar.css +42 -0
- package/styles/themes/_dark.scss +191 -0
- package/styles/themes/_font.scss +79 -0
- package/styles/themes/_index.scss +5 -0
- package/styles/themes/_light.scss +190 -0
- package/styles/themes/_radius.scss +9 -0
- package/styles/themes/_size.scss +68 -0
- package/styles/themes.css +66 -0
- package/styles/utilities/_animation.scss +57 -0
- package/styles/utilities/_tips.scss +9 -0
- package/switch/Switch.tsx +120 -0
- package/switch/defaultProps.ts +3 -0
- package/switch/index.ts +7 -0
- package/switch/style/css.js +1 -0
- package/switch/style/index.js +1 -0
- package/switch/type.ts +46 -0
- package/tag/Tag.tsx +149 -0
- package/tag/defaultProps.ts +19 -0
- package/tag/index.ts +8 -0
- package/tag/style/css.js +1 -0
- package/tag/style/index.js +1 -0
- package/tag/type.ts +170 -0
- package/tag-input/TagInput.tsx +215 -0
- package/tag-input/defaultProps.ts +15 -0
- package/tag-input/hooks/useHover.ts +28 -0
- package/tag-input/hooks/useTagList.tsx +131 -0
- package/tag-input/hooks/useTagScroll.ts +105 -0
- package/tag-input/index.ts +9 -0
- package/tag-input/style/css.js +1 -0
- package/tag-input/style/index.js +1 -0
- package/tag-input/type.ts +224 -0
- package/tag-input/useTagList.tsx +131 -0
- package/utils/composeRefs.ts +14 -0
- package/utils/dom.ts +29 -0
- package/utils/forwardRefWithStatics.ts +12 -0
- package/utils/getScrollbarWidth.ts +11 -0
- package/utils/helper.ts +161 -0
- package/utils/isFragment.ts +22 -0
- package/utils/listener.ts +37 -0
- package/utils/noop.ts +3 -0
- package/utils/parentTNode.ts +38 -0
- package/utils/parseTNode.ts +38 -0
- package/utils/react-render.ts +108 -0
- package/utils/ref.ts +6 -0
- package/utils/refs.ts +81 -0
- package/utils/style.ts +60 -0
- package/utils/transition.ts +28 -0
package/utils/helper.ts
ADDED
|
@@ -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,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
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
|
+
};
|