@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,383 @@
1
+ import React, { useState, useRef, useEffect, useImperativeHandle, forwardRef } from "react";
2
+ import classNames from "classnames";
3
+ import { IconEyeOpened, IconEyeClosed, IconClose } from "@tendaui/icons";
4
+ import { TdInputProps } from "./type";
5
+ import useLayoutEffect from "../hooks/useLayoutEffect";
6
+ import useControlled from "../hooks/useControlled";
7
+ import { inputDefaultProps } from "./defaultProps";
8
+ import parseTNode from "../utils/parseTNode";
9
+ import useDefaultProps from "../hooks/useDefaultProps";
10
+ import { StyledProps, TNode, TElement } from "../common";
11
+ import { isFunction } from "lodash-es";
12
+ import useConfig from "../hooks/useConfig";
13
+ export interface InputProps extends TdInputProps, StyledProps {
14
+ showInput?: boolean; // 控制透传readonly同时是否展示input 默认保留 因为正常Input需要撑开宽度
15
+ keepWrapperWidth?: boolean; // 控制透传autoWidth之后是否容器宽度也自适应 多选等组件需要用到自适应但也需要保留宽度
16
+ }
17
+
18
+ export interface InputRef extends React.RefObject<unknown> {
19
+ currentElement: HTMLDivElement;
20
+ inputElement: HTMLInputElement;
21
+ focus: () => void;
22
+ blur: () => void;
23
+ select: () => void;
24
+ }
25
+
26
+ type InputContextTrigger = "input" | "clear" | "initial";
27
+
28
+ const renderIcon = (classPrefix: string, type: "prefix" | "suffix", icon: TNode | TElement) => {
29
+ const result = parseTNode(icon);
30
+
31
+ const iconClassName = icon ? `${classPrefix}-input__${type}-icon` : "";
32
+
33
+ return result ? <span className={`${classPrefix}-input__${type} ${iconClassName}`}>{result}</span> : null;
34
+ };
35
+
36
+ const Input = forwardRef<HTMLInputElement, InputProps>((originalProps, ref) => {
37
+ const props = useDefaultProps<InputProps>(originalProps, inputDefaultProps as Partial<InputProps>);
38
+ const { classPrefix } = useConfig();
39
+
40
+ const {
41
+ type,
42
+ autoWidth,
43
+ borderless,
44
+ placeholder = "请输入内容",
45
+ disabled,
46
+ size,
47
+ className,
48
+ inputClass,
49
+ style,
50
+ prefixIcon,
51
+ suffixIcon,
52
+ clearable,
53
+ tips,
54
+ align,
55
+ showClearIconOnEmpty,
56
+ autofocus,
57
+ autocomplete,
58
+ readonly,
59
+ label,
60
+ suffix,
61
+ showInput = true,
62
+ keepWrapperWidth,
63
+ allowInput,
64
+ name,
65
+ format,
66
+ onClick,
67
+ onClear,
68
+ onEnter,
69
+ onKeydown,
70
+ onKeyup,
71
+ onKeypress,
72
+ onFocus,
73
+ onBlur,
74
+ onPaste,
75
+ onMouseenter,
76
+ onMouseleave,
77
+ onWheel,
78
+ onCompositionstart,
79
+ onCompositionend,
80
+ onChange: onChangeFromProps,
81
+ ...restProps
82
+ } = props;
83
+ const composingRef = useRef(false);
84
+ const [value, onChange] = useControlled(props, "value", onChangeFromProps);
85
+ const inputRef: React.RefObject<HTMLInputElement | null> = useRef(null);
86
+ const inputPreRef: React.RefObject<HTMLInputElement | null> = useRef(null);
87
+ const wrapperRef: React.RefObject<HTMLDivElement | null> = useRef(null);
88
+
89
+ const [isHover, toggleIsHover] = useState(false);
90
+ const [isFocused, toggleIsFocused] = useState(false);
91
+ const [renderType, setRenderType] = useState(type);
92
+
93
+ const [composingValue, setComposingValue] = useState<string>("");
94
+ const isInnerInputReadonly = readonly || !allowInput;
95
+ const isValueEnabled = value && !disabled;
96
+ const isShowClearIcon = ((clearable && isValueEnabled) || showClearIconOnEmpty) && isHover;
97
+ let suffixIconNew = suffixIcon;
98
+ if (isShowClearIcon)
99
+ suffixIconNew = (
100
+ <IconClose
101
+ className={`${classPrefix}-input__suffix-clear ${classPrefix}-icon`}
102
+ onMouseDown={handleMouseDown}
103
+ onClick={handleClear}
104
+ />
105
+ );
106
+
107
+ if (type === "password" && typeof suffixIcon === "undefined") {
108
+ if (renderType === "password") {
109
+ suffixIconNew = (
110
+ <IconEyeClosed
111
+ className={`${classPrefix}-input__suffix-clear ${classPrefix}-icon`}
112
+ onClick={togglePasswordVisible}
113
+ />
114
+ );
115
+ } else if (renderType === "text") {
116
+ suffixIconNew = (
117
+ <IconEyeOpened
118
+ className={`${classPrefix}-input__suffix-clear ${classPrefix}-icon`}
119
+ onClick={togglePasswordVisible}
120
+ />
121
+ );
122
+ }
123
+ }
124
+ const prefixIconContent = renderIcon("t", "prefix", parseTNode(prefixIcon));
125
+ const suffixIconContent = renderIcon("t", "suffix", parseTNode(suffixIconNew));
126
+ const labelContent = isFunction(label) ? label() : label;
127
+ const suffixContent = isFunction(suffix) ? suffix() : suffix;
128
+
129
+ const updateInputWidth = () => {
130
+ if (!autoWidth || !inputRef.current) return;
131
+ const { offsetWidth } = inputPreRef.current as HTMLInputElement;
132
+ const { width } = inputPreRef.current?.getBoundingClientRect() as DOMRect;
133
+ // 异步渲染场景下 getBoundingClientRect 宽度为 0,需要使用 offsetWidth
134
+ const calcWidth = width < offsetWidth ? offsetWidth + 1 : width;
135
+ inputRef.current.style.width = `${calcWidth}px`;
136
+ };
137
+
138
+ useLayoutEffect(() => {
139
+ // 推迟到下一帧处理防止异步渲染 input 场景宽度计算为 0
140
+ requestAnimationFrame(() => {
141
+ updateInputWidth();
142
+ });
143
+ // eslint-disable-next-line
144
+ }, [autoWidth, value, placeholder, inputRef, composingValue]);
145
+
146
+ // 当元素默认为 display: none 状态,无法提前准确计算宽度,因此需要监听元素宽度变化。比如:Tabs 场景切换。
147
+ useEffect(() => {
148
+ let resizeObserver: ResizeObserver | null = null;
149
+ // IE 11 以下使用设置 minWidth 兼容;IE 11 以上使用 ResizeObserver
150
+ if (typeof window.ResizeObserver === "undefined" || !inputRef.current) return;
151
+ resizeObserver = new window.ResizeObserver(() => {
152
+ updateInputWidth();
153
+ });
154
+ resizeObserver.observe(inputRef.current);
155
+ return () => {
156
+ // resizeObserver.unobserve?.(inputRef.current);
157
+ resizeObserver.disconnect?.();
158
+ resizeObserver = null;
159
+ };
160
+ // eslint-disable-next-line
161
+ }, [inputRef]);
162
+
163
+ useEffect(() => {
164
+ setRenderType(type);
165
+ }, [type]);
166
+
167
+ const innerValue = composingRef.current ? composingValue : value ?? "";
168
+ const formatDisplayValue = format && !isFocused ? format(innerValue) : innerValue;
169
+
170
+ const renderInput = (
171
+ <input
172
+ ref={inputRef}
173
+ placeholder={placeholder}
174
+ type={renderType}
175
+ className={classNames(`${classPrefix}-input__inner`, {
176
+ [`${classPrefix}-input--soft-hidden`]: !showInput
177
+ })}
178
+ value={formatDisplayValue}
179
+ readOnly={isInnerInputReadonly}
180
+ disabled={disabled}
181
+ autoComplete={autocomplete}
182
+ autoFocus={autofocus}
183
+ onChange={handleChange}
184
+ onKeyDown={handleKeyDown}
185
+ onKeyUp={handleKeyUp}
186
+ onKeyPress={handleKeyPress}
187
+ onCompositionStart={handleCompositionStart}
188
+ onCompositionEnd={handleCompositionEnd}
189
+ onFocus={handleFocus}
190
+ onBlur={handleBlur}
191
+ onPaste={handlePaste}
192
+ name={name}
193
+ />
194
+ );
195
+ const renderInputNode = (
196
+ <div
197
+ className={classNames(inputClass, `${classPrefix}-input`, {
198
+ [`${classPrefix}-is-readonly`]: readonly,
199
+ [`${classPrefix}-is-disabled`]: disabled,
200
+ [`${classPrefix}-is-focused`]: isFocused,
201
+ [`${classPrefix}-size-s`]: size === "small",
202
+ [`${classPrefix}-size-l`]: size === "large",
203
+ [`${classPrefix}-align-${align}`]: align,
204
+ [`${classPrefix}-inpu${classPrefix}--prefix`]: prefixIcon || labelContent,
205
+ [`${classPrefix}-inpu${classPrefix}--suffix`]: suffixIconContent || suffixContent,
206
+ [`${classPrefix}-inpu${classPrefix}--borderless`]: borderless,
207
+ [`${classPrefix}-input--focused`]: isFocused
208
+ })}
209
+ onMouseEnter={handleMouseEnter}
210
+ onMouseLeave={handleMouseLeave}
211
+ onWheel={(e) => onWheel?.({ e })}
212
+ onClick={(e) => {
213
+ inputRef.current?.focus();
214
+ onClick?.({ e });
215
+ }}
216
+ >
217
+ {prefixIconContent}
218
+ {labelContent ? <div className={`${classPrefix}-input__prefix`}>{labelContent}</div> : null}
219
+ {renderInput}
220
+ {autoWidth && (
221
+ <span ref={inputPreRef} className={`${classPrefix}-input__inpu${classPrefix}-pre`}>
222
+ {innerValue || placeholder}
223
+ </span>
224
+ )}
225
+ {suffixContent ? <div className={`${classPrefix}-input__suffix`}>{suffixContent}</div> : null}
226
+ {suffixIconContent}
227
+ </div>
228
+ );
229
+ useLayoutEffect(() => {
230
+ // 推迟到下一帧处理防止异步渲染 input 场景宽度计算为 0
231
+ requestAnimationFrame(() => {
232
+ updateInputWidth();
233
+ });
234
+ // eslint-disable-next-line
235
+ }, [autoWidth, value, placeholder, inputRef, composingValue]);
236
+
237
+ function togglePasswordVisible() {
238
+ if (disabled) return;
239
+ // 保存光标位置
240
+ const inputEl = inputRef.current;
241
+ const cursorPosition = inputRef.current?.selectionStart || 0;
242
+
243
+ const toggleType = renderType === "password" ? "text" : "password";
244
+ setRenderType(toggleType);
245
+
246
+ requestAnimationFrame(() => {
247
+ inputEl?.setSelectionRange(cursorPosition, cursorPosition);
248
+ });
249
+ }
250
+
251
+ function handleChange(
252
+ e: React.ChangeEvent<HTMLInputElement> | React.CompositionEvent<HTMLInputElement>,
253
+ trigger: InputContextTrigger = "input"
254
+ ) {
255
+ let { value: newStr } = e.currentTarget;
256
+ if (composingRef.current) {
257
+ setComposingValue(newStr);
258
+ } else {
259
+ // 完成中文输入时同步一次 composingValue
260
+ setComposingValue(newStr);
261
+ onChange(newStr, { e, trigger });
262
+ }
263
+ }
264
+
265
+ // 添加MouseDown阻止冒泡,防止點擊Clear value會導致彈窗閃爍一下
266
+ // https://github.com/Tencent/tdesign-react/issues/2320
267
+ function handleMouseDown(e: React.MouseEvent<HTMLSpanElement, globalThis.MouseEvent>) {
268
+ e.stopPropagation();
269
+ // 兼容React16
270
+ e.nativeEvent.stopImmediatePropagation();
271
+ }
272
+
273
+ function handleClear(e: React.MouseEvent<HTMLSpanElement>) {
274
+ onChange?.("", { e, trigger: "clear" });
275
+ onClear?.({ e: e as unknown as React.MouseEvent<SVGElement> });
276
+ }
277
+
278
+ function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
279
+ const {
280
+ key,
281
+ currentTarget: { value }
282
+ } = e;
283
+ if (key === "Enter") {
284
+ onEnter?.(value, { e });
285
+ }
286
+ onKeydown?.(value, { e });
287
+ }
288
+
289
+ function handleKeyUp(e: React.KeyboardEvent<HTMLInputElement>) {
290
+ const {
291
+ currentTarget: { value }
292
+ } = e;
293
+ onKeyup?.(value, { e });
294
+ }
295
+
296
+ function handleKeyPress(e: React.KeyboardEvent<HTMLInputElement>) {
297
+ const {
298
+ currentTarget: { value }
299
+ } = e;
300
+ onKeypress?.(value, { e });
301
+ }
302
+
303
+ function handleCompositionStart(e: React.CompositionEvent<HTMLInputElement>) {
304
+ composingRef.current = true;
305
+ const {
306
+ currentTarget: { value }
307
+ } = e;
308
+ onCompositionstart?.(value, { e });
309
+ }
310
+
311
+ function handleCompositionEnd(e: React.CompositionEvent<HTMLInputElement>) {
312
+ const {
313
+ currentTarget: { value }
314
+ } = e;
315
+ if (composingRef.current) {
316
+ composingRef.current = false;
317
+ handleChange(e);
318
+ }
319
+ onCompositionend?.(value, { e });
320
+ }
321
+
322
+ function handleFocus(e: React.FocusEvent<HTMLInputElement>) {
323
+ if (isInnerInputReadonly) return;
324
+ const {
325
+ currentTarget: { value }
326
+ } = e;
327
+ onFocus?.(value, { e });
328
+ toggleIsFocused(true);
329
+ }
330
+
331
+ function handleBlur(e: React.FocusEvent<HTMLInputElement>) {
332
+ if (isInnerInputReadonly) return;
333
+ const {
334
+ currentTarget: { value }
335
+ } = e;
336
+ onBlur?.(value, { e });
337
+ toggleIsFocused(false);
338
+ }
339
+
340
+ function handlePaste(e: React.ClipboardEvent<HTMLInputElement>) {
341
+ const clipData = e.clipboardData;
342
+ const pasteValue = clipData?.getData("text/plain");
343
+ onPaste?.({ e, pasteValue });
344
+ }
345
+
346
+ function handleMouseEnter(e: React.MouseEvent<HTMLDivElement>) {
347
+ if (!readonly) {
348
+ toggleIsHover(true);
349
+ }
350
+ onMouseenter?.({ e });
351
+ }
352
+
353
+ function handleMouseLeave(e: React.MouseEvent<HTMLDivElement>) {
354
+ if (!readonly) {
355
+ toggleIsHover(false);
356
+ }
357
+ onMouseleave?.({ e });
358
+ }
359
+
360
+ useImperativeHandle(ref as InputRef, () => ({
361
+ currentElement: wrapperRef.current,
362
+ inputElement: inputRef.current,
363
+ focus: () => inputRef.current?.focus(),
364
+ blur: () => inputRef.current?.blur(),
365
+ select: () => inputRef.current?.select()
366
+ }));
367
+
368
+ return (
369
+ <div
370
+ ref={wrapperRef}
371
+ style={style}
372
+ className={classNames(`${classPrefix}-input__wrap`, className, {
373
+ [`${classPrefix}-input--auto-width`]: autoWidth && !keepWrapperWidth
374
+ })}
375
+ {...restProps}
376
+ >
377
+ {renderInputNode}
378
+ {tips && <div className={classNames(`${classPrefix}-input__tips`)}>{tips}</div>}
379
+ </div>
380
+ );
381
+ });
382
+ Input.displayName = "Input";
383
+ export default Input;
@@ -0,0 +1,29 @@
1
+ import React, { forwardRef } from "react";
2
+ import classNames from "classnames";
3
+
4
+ export interface InputGroupProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /**
6
+ * 是否拆分
7
+ * @default false
8
+ */
9
+ separate?: boolean;
10
+ }
11
+
12
+ const InputGroup = forwardRef<HTMLDivElement, InputGroupProps>((props, ref) => {
13
+ const { separate, children, className, ...wrapperProps } = props;
14
+ return (
15
+ <div
16
+ ref={ref}
17
+ className={classNames(`t-input-group`, className, {
18
+ [`t-input-group--separate`]: separate
19
+ })}
20
+ {...wrapperProps}
21
+ >
22
+ {children}
23
+ </div>
24
+ );
25
+ });
26
+
27
+ InputGroup.displayName = "InputGroup";
28
+
29
+ export default InputGroup;
@@ -0,0 +1,22 @@
1
+ export const inputDefaultProps = {
2
+ align: "left" as "left" | "center" | "right",
3
+ allowInputOverMax: false,
4
+ autoWidth: false,
5
+ autocomplete: undefined,
6
+ autofocus: false,
7
+ borderless: false,
8
+ // 是否允许输入,默认是 true,表示组件内部的 input 元素允许输入;readonly 为 true 时,此值失效
9
+ allowInput: true,
10
+ // 是否可清空,值为 true 时,输入框后会有一个清空按钮,点击后清空输入框;readonly 为 true 时,此值失效
11
+ clearable: false,
12
+ placeholder: undefined,
13
+ // 是否只读,在只读模式下,输入框不能输入,且没有清除按钮,优先级高于 allowInput、clearable
14
+ readonly: false,
15
+ showClearIconOnEmpty: false,
16
+ showLimitNumber: false,
17
+ size: "medium",
18
+ spellCheck: false,
19
+ status: "default",
20
+ type: "text",
21
+ defaultValue: ""
22
+ };
package/input/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import _Input from "./Input";
2
+ import _InputGroup from "./InputGroup";
3
+ import "./style/index.js";
4
+
5
+ export type { InputProps, InputRef } from "./Input";
6
+
7
+ export * from "./type";
8
+
9
+ export const Input = _Input;
10
+ export const InputGroup = _InputGroup;
11
+ export default Input;
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/input/_index.scss";
package/input/type.ts ADDED
@@ -0,0 +1,219 @@
1
+ import { TNode, TElement, SizeEnum, ClassName } from "../common";
2
+ import { MouseEvent, KeyboardEvent, ClipboardEvent, FocusEvent, WheelEvent, FormEvent, CompositionEvent } from "react";
3
+
4
+ export interface TdInputProps {
5
+ /**
6
+ * 文本内容位置,居左/居中/居右
7
+ * @default left
8
+ */
9
+ align?: "left" | "center" | "right";
10
+ /**
11
+ * 输入框是否可输入内容
12
+ * @default true
13
+ */
14
+ allowInput?: boolean;
15
+ /**
16
+ * 超出 `maxlength` 或 `maxcharacter` 之后是否允许继续输入
17
+ * @default false
18
+ */
19
+ allowInputOverMax?: boolean;
20
+ /**
21
+ * 宽度随内容自适应
22
+ * @default false
23
+ */
24
+ autoWidth?: boolean;
25
+ /**
26
+ * 是否开启自动填充功能,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete)
27
+ */
28
+ autocomplete?: string;
29
+ /**
30
+ * 自动聚焦
31
+ * @default false
32
+ */
33
+ autofocus?: boolean;
34
+ /**
35
+ * 是否开启无边框模式
36
+ * @default false
37
+ */
38
+ borderless?: boolean;
39
+ /**
40
+ * 是否可清空
41
+ * @default false
42
+ */
43
+ clearable?: boolean;
44
+ /**
45
+ * 是否禁用输入框
46
+ */
47
+ disabled?: boolean;
48
+ /**
49
+ * 指定输入框展示值的格式
50
+ */
51
+ format?: InputFormatType;
52
+ /**
53
+ * t-input 同级类名,示例:'name1 name2 name3' 或 `['name1', 'name2']` 或 `[{ 'name1': true }]`
54
+ */
55
+ inputClass?: ClassName;
56
+ /**
57
+ * 左侧文本
58
+ */
59
+ label?: TNode;
60
+ /**
61
+ * 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度。`maxcharacter` 和 `maxlength` 二选一使用
62
+ */
63
+ maxcharacter?: number;
64
+ /**
65
+ * 用户最多可以输入的文本长度,一个中文等于一个计数长度。值为空,则表示不限制输入长度。`maxcharacter` 和 `maxlength` 二选一使用
66
+ */
67
+ maxlength?: number;
68
+ /**
69
+ * 名称
70
+ * @default ''
71
+ */
72
+ name?: string;
73
+ /**
74
+ * 占位符
75
+ */
76
+ placeholder?: string;
77
+ /**
78
+ * 组件前置图标
79
+ */
80
+ prefixIcon?: TElement;
81
+ /**
82
+ * 只读状态
83
+ * @default false
84
+ */
85
+ readonly?: boolean;
86
+ /**
87
+ * 输入框内容为空时,悬浮状态是否显示清空按钮,默认不显示
88
+ * @default false
89
+ */
90
+ showClearIconOnEmpty?: boolean;
91
+ /**
92
+ * 是否在输入框右侧显示字数统计
93
+ * @default false
94
+ */
95
+ showLimitNumber?: boolean;
96
+ /**
97
+ * 输入框尺寸
98
+ * @default medium
99
+ */
100
+ size?: SizeEnum;
101
+ /**
102
+ * 是否开启拼写检查,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/spellcheck)
103
+ * @default false
104
+ */
105
+ spellCheck?: boolean;
106
+ /**
107
+ * 输入框状态
108
+ * @default default
109
+ */
110
+ status?: "default" | "success" | "warning" | "error";
111
+ /**
112
+ * 后置图标前的后置内容
113
+ */
114
+ suffix?: TNode;
115
+ /**
116
+ * 组件后置图标
117
+ */
118
+ suffixIcon?: TElement;
119
+ /**
120
+ * 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式
121
+ */
122
+ tips?: TNode;
123
+ /**
124
+ * 输入框类型
125
+ * @default text
126
+ */
127
+ type?: "text" | "number" | "url" | "tel" | "password" | "search" | "submit" | "hidden";
128
+ /**
129
+ * 输入框的值
130
+ * @default ''
131
+ */
132
+ value?: InputValue;
133
+ /**
134
+ * 输入框的值,非受控属性
135
+ * @default ''
136
+ */
137
+ defaultValue?: InputValue;
138
+ /**
139
+ * 失去焦点时触发
140
+ */
141
+ onBlur?: (value: InputValue, context: { e: FocusEvent<HTMLInputElement> }) => void;
142
+ /**
143
+ * 输入框值发生变化时触发。参数 `trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制
144
+ */
145
+ onChange?: (
146
+ value: InputValue,
147
+ context?: {
148
+ e?: FormEvent<HTMLInputElement> | MouseEvent<Element> | CompositionEvent<HTMLDivElement>;
149
+ trigger: "input" | "initial" | "clear";
150
+ }
151
+ ) => void;
152
+ /**
153
+ * 清空按钮点击时触发
154
+ */
155
+ onClear?: (context: { e: MouseEvent<SVGElement> }) => void;
156
+ /**
157
+ * 点击组件时触发
158
+ */
159
+ onClick?: (context: { e: MouseEvent<HTMLDivElement> }) => void;
160
+ /**
161
+ * 中文输入结束时触发
162
+ */
163
+ onCompositionend?: (value: InputValue, context: { e: CompositionEvent<HTMLDivElement> }) => void;
164
+ /**
165
+ * 中文输入开始时触发
166
+ */
167
+ onCompositionstart?: (value: InputValue, context: { e: CompositionEvent<HTMLDivElement> }) => void;
168
+ /**
169
+ * 回车键按下时触发
170
+ */
171
+ onEnter?: (value: InputValue, context: { e: KeyboardEvent<HTMLInputElement> }) => void;
172
+ /**
173
+ * 获得焦点时触发
174
+ */
175
+ onFocus?: (value: InputValue, context: { e: FocusEvent<HTMLInputElement> }) => void;
176
+ /**
177
+ * 键盘按下时触发
178
+ */
179
+ onKeydown?: (value: InputValue, context: { e: KeyboardEvent<HTMLDivElement> }) => void;
180
+ /**
181
+ * 按下字符键时触发(keydown -> keypress -> keyup)
182
+ */
183
+ onKeypress?: (value: InputValue, context: { e: KeyboardEvent<HTMLDivElement> }) => void;
184
+ /**
185
+ * 释放键盘时触发
186
+ */
187
+ onKeyup?: (value: InputValue, context: { e: KeyboardEvent<HTMLDivElement> }) => void;
188
+ /**
189
+ * 进入输入框时触发
190
+ */
191
+ onMouseenter?: (context: { e: MouseEvent<HTMLDivElement> }) => void;
192
+ /**
193
+ * 离开输入框时触发
194
+ */
195
+ onMouseleave?: (context: { e: MouseEvent<HTMLDivElement> }) => void;
196
+ /**
197
+ * 粘贴事件,`pasteValue` 表示粘贴板的内容
198
+ */
199
+ onPaste?: (context: { e: ClipboardEvent<HTMLDivElement>; pasteValue: string }) => void;
200
+ /**
201
+ * 字数超出限制时触发
202
+ */
203
+ onValidate?: (context: { error?: "exceed-maximum" | "below-minimum" }) => void;
204
+ /**
205
+ * 输入框中滚动鼠标时触发
206
+ */
207
+ onWheel?: (context: { e: WheelEvent<HTMLDivElement> }) => void;
208
+ }
209
+
210
+ export interface TdInputGroupProps {
211
+ /**
212
+ * 多个输入框之间是否需要间隔
213
+ */
214
+ separate?: boolean;
215
+ }
216
+
217
+ export type InputFormatType = (value: InputValue) => string;
218
+
219
+ export type InputValue = string;