1 | import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
2 | import { useIsomorphicLayoutEffect } from 'ahooks';
|
3 | import runes from 'runes2';
|
4 | import { withNativeProps } from '../../utils/native-props';
|
5 | import { usePropsValue } from '../../utils/use-props-value';
|
6 | import { mergeProps } from '../../utils/with-default-props';
|
7 | import { devError } from '../../utils/dev-log';
|
8 | import useInputHandleKeyDown from '../../components/input/useInputHandleKeyDown';
|
9 | const classPrefix = 'adm-text-area';
|
10 | const defaultProps = {
|
11 | rows: 2,
|
12 | showCount: false,
|
13 | autoSize: false,
|
14 | defaultValue: ''
|
15 | };
|
16 | export const TextArea = forwardRef((p, ref) => {
|
17 | const props = mergeProps(defaultProps, p);
|
18 | const {
|
19 | autoSize,
|
20 | showCount,
|
21 | maxLength
|
22 | } = props;
|
23 | const [value, setValue] = usePropsValue(Object.assign(Object.assign({}, props), {
|
24 | value: props.value === null ? '' : props.value
|
25 | }));
|
26 | if (props.value === null) {
|
27 | devError('TextArea', '`value` prop on `TextArea` should not be `null`. Consider using an empty string to clear the component.');
|
28 | }
|
29 | const nativeTextAreaRef = useRef(null);
|
30 |
|
31 | const heightRef = useRef('auto');
|
32 |
|
33 | const hiddenTextAreaRef = useRef(null);
|
34 | const handleKeydown = useInputHandleKeyDown({
|
35 | onEnterPress: props.onEnterPress,
|
36 | onKeyDown: props.onKeyDown,
|
37 | nativeInputRef: nativeTextAreaRef,
|
38 | enterKeyHint: props.enterKeyHint
|
39 | });
|
40 | useImperativeHandle(ref, () => ({
|
41 | clear: () => {
|
42 | setValue('');
|
43 | },
|
44 | focus: () => {
|
45 | var _a;
|
46 | (_a = nativeTextAreaRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
47 | },
|
48 | blur: () => {
|
49 | var _a;
|
50 | (_a = nativeTextAreaRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
51 | },
|
52 | get nativeElement() {
|
53 | return nativeTextAreaRef.current;
|
54 | }
|
55 | }));
|
56 | useIsomorphicLayoutEffect(() => {
|
57 | if (!autoSize) return;
|
58 | const textArea = nativeTextAreaRef.current;
|
59 | const hiddenTextArea = hiddenTextAreaRef.current;
|
60 | if (!textArea) return;
|
61 | textArea.style.height = heightRef.current;
|
62 | if (!hiddenTextArea) return;
|
63 | let height = hiddenTextArea.scrollHeight;
|
64 | if (typeof autoSize === 'object') {
|
65 | const computedStyle = window.getComputedStyle(textArea);
|
66 | const lineHeight = parseFloat(computedStyle.lineHeight);
|
67 | if (autoSize.minRows) {
|
68 | height = Math.max(height, autoSize.minRows * lineHeight);
|
69 | }
|
70 | if (autoSize.maxRows) {
|
71 | height = Math.min(height, autoSize.maxRows * lineHeight);
|
72 | }
|
73 | }
|
74 | heightRef.current = `${height}px`;
|
75 | textArea.style.height = `${height}px`;
|
76 | }, [value, autoSize]);
|
77 | const compositingRef = useRef(false);
|
78 | let count;
|
79 | const valueLength = runes(value).length;
|
80 | if (typeof showCount === 'function') {
|
81 | count = showCount(valueLength, maxLength);
|
82 | } else if (showCount) {
|
83 | count = React.createElement("div", {
|
84 | className: `${classPrefix}-count`
|
85 | }, maxLength === undefined ? valueLength : valueLength + '/' + maxLength);
|
86 | }
|
87 | let rows = props.rows;
|
88 | if (typeof autoSize === 'object') {
|
89 | if (autoSize.maxRows && rows > autoSize.maxRows) {
|
90 | rows = autoSize.maxRows;
|
91 | }
|
92 | if (autoSize.minRows && rows < autoSize.minRows) {
|
93 | rows = autoSize.minRows;
|
94 | }
|
95 | }
|
96 | return withNativeProps(props, React.createElement("div", {
|
97 | className: classPrefix
|
98 | }, React.createElement("textarea", {
|
99 | ref: nativeTextAreaRef,
|
100 | className: `${classPrefix}-element`,
|
101 | rows: rows,
|
102 | value: value,
|
103 | placeholder: props.placeholder,
|
104 | onChange: e => {
|
105 | let v = e.target.value;
|
106 | if (maxLength && !compositingRef.current) {
|
107 | v = runes(v).slice(0, maxLength).join('');
|
108 | }
|
109 | setValue(v);
|
110 | },
|
111 | id: props.id,
|
112 | onCompositionStart: e => {
|
113 | var _a;
|
114 | compositingRef.current = true;
|
115 | (_a = props.onCompositionStart) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
116 | },
|
117 | onCompositionEnd: e => {
|
118 | var _a;
|
119 | compositingRef.current = false;
|
120 | if (maxLength) {
|
121 | const v = e.target.value;
|
122 | setValue(runes(v).slice(0, maxLength).join(''));
|
123 | }
|
124 | (_a = props.onCompositionEnd) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
125 | },
|
126 | autoComplete: props.autoComplete,
|
127 | autoFocus: props.autoFocus,
|
128 | disabled: props.disabled,
|
129 | readOnly: props.readOnly,
|
130 | name: props.name,
|
131 | onFocus: props.onFocus,
|
132 | onBlur: props.onBlur,
|
133 | onClick: props.onClick,
|
134 | onKeyDown: handleKeydown
|
135 | }), count, autoSize && React.createElement("textarea", {
|
136 | ref: hiddenTextAreaRef,
|
137 | className: `${classPrefix}-element ${classPrefix}-element-hidden`,
|
138 | value: value,
|
139 | rows: rows,
|
140 | "aria-hidden": true,
|
141 | readOnly: true
|
142 | })));
|
143 | });
|
144 | TextArea.defaultProps = defaultProps; |
\ | No newline at end of file |