8000 fix: textarea cursor error, close #7121 · vueComponent/ant-design-vue@73f0a29 · GitHub
[go: up one dir, main page]

Skip to content 8000

Commit 73f0a29

Browse files
committed
fix: textarea cursor error, close #7121
1 parent f93dd91 commit 73f0a29

File tree

2 files changed

+117
-85
lines changed

2 files changed

+117
-85
lines changed

components/input/ResizableTextArea.tsx

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
import type { VNode } from 'vue';
1+
import type { VNode, CSSProperties } from 'vue';
22
import {
3-
onMounted,
3+
computed,
4+
watchEffect,
45
getCurrentInstance,
56
watch,
67
onBeforeUnmount,
78
ref,
8-
nextTick,
99
defineComponent,
1010
withDirectives,
1111
} from 'vue';
1212
import ResizeObserver from '../vc-resize-observer';
1313
import classNames from '../_util/classNames';
14-
import calculateNodeHeight from './calculateNodeHeight';
1514
import raf from '../_util/raf';
1615
import warning from '../_util/warning';
1716
import antInput from '../_util/antInputDirective';
1817
import omit from '../_util/omit';
1918
import { textAreaProps } from './inputProps';
19+
import calculateAutoSizeStyle from './calculateNodeHeight';
2020

21-
const RESIZE_STATUS_NONE = 0;
22-
const RESIZE_STATUS_RESIZING = 1;
23-
const RESIZE_STATUS_RESIZED = 2;
21+
const RESIZE_START = 0;
22+
const RESIZE_MEASURING = 1;
23+
const RESIZE_STABLE = 2;
2424

2525
const ResizableTextArea = defineComponent({
2626
compatConfig: { MODE: 3 },
@@ -32,7 +32,7 @@ const ResizableTextArea = defineComponent({
3232
let resizeFrameId: any;
3333
const textAreaRef = ref();
3434
const textareaStyles = ref({});
35-
const resizeStatus = ref(RESIZE_STATUS_NONE);
35+
const resizeStatus = ref(RESIZE_STABLE);
3636
onBeforeUnmount(() => {
3737
raf.cancel(nextFrameActionId);
3838
raf.cancel(resizeFrameId);
@@ -44,57 +44,100 @@ const ResizableTextArea = defineComponent({
4444
if (document.activeElement === textAreaRef.value) {
4545
const currentStart = textAreaRef.value.selectionStart;
4646
const currentEnd = textAreaRef.value.selectionEnd;
47+
const scrollTop = textAreaRef.value.scrollTop;
4748
textAreaRef.value.setSelectionRange(currentStart, currentEnd);
49+
textAreaRef.value.scrollTop = scrollTop;
4850
}
4951
} catch (e) {
5052
// Fix error in Chrome:
5153
// Failed to read the 'selectionStart' property from 'HTMLInputElement'
5254
// http://stackoverflow.com/q/21177489/3040605
5355
}
5456
};
55-
56-
const resizeTextarea = () => {
57+
const minRows = ref<number>();
58+
const maxRows = ref<number>();
59+
watchEffect(() => {
5760
const autoSize = props.autoSize || props.autosize;
58-
if (!autoSize || !textAreaRef.value) {
59-
return;
61+
if (autoSize) {
62+
minRows.value = autoSize.minRows;
63+
maxRows.value = autoSize.maxRows;
64+
} else {
65+
minRows.value = undefined;
66+
maxRows.value = undefined;
6067
}
61-
const { minRows, maxRows } = autoSize;
62-
textareaStyles.value = calculateNodeHeight(textAreaRef.value, false, minRows, maxRows);
63-
resizeStatus.value = RESIZE_STATUS_RESIZING;
64-
raf.cancel(resizeFrameId);
65-
resizeFrameId = raf(() => {
66-
resizeStatus.value = RESIZE_STATUS_RESIZED;
67-
resizeFrameId = raf(() => {
68-
resizeStatus.value = RESIZE_STATUS_NONE;
69-
fixFirefoxAutoScroll();
70-
});
71-
});
68+
});
69+
const needAutoSize = computed(() => !!(props.autoSize || props.autosize));
70+
const startResize = () => {
71+
resizeStatus.value = RESIZE_START;
7272
};
73-
74-
const resizeOnNextFrame = () => {
75-
raf.cancel(nextFrameActionId);
76-
nextFrameActionId = raf(resizeTextarea);
73+
watch(
74+
[() => props.value, minRows, maxRows, needAutoSize],
75+
() => {
76+
if (needAutoSize.value) {
77+
startResize();
78+
}
79+
},
80+
{ immediate: true, flush: 'post' },
81+
);
82+
const autoSizeStyle = ref<CSSProperties>();
83+
watch(
84+
[resizeStatus, textAreaRef],
85+
() => {
86+
if (!textAreaRef.value) return;
87+
if (resizeStatus.value === RESIZE_START) {
88+
resizeStatus.value = RESIZE_MEASURING;
89+
} else if (resizeStatus.value === RESIZE_MEASURING) {
90+
const textareaStyles = calculateAutoSizeStyle(
91+
textAreaRef.value,
92+
false,
93+
minRows.value,
94+
maxRows.value,
95+
);
96+
resizeStatus.value = RESIZE_STABLE;
97+
autoSizeStyle.value = textareaStyles;
98+
} else {
99+
fixFirefoxAutoScroll();
100+
}
101+
},
102+
{ immediate: true, flush: 'post' },
103+
);
104+
const instance = getCurrentInstance();
105+
const resizeRafRef = ref();
106+
const cleanRaf = () => {
107+
raf.cancel(resizeRafRef.value);
77108
};
109+
const onInternalResize = (size: { width: number; height: number }) => {
110+
if (resizeStatus.value === RESIZE_STABLE) {
111+
emit('resize', size);
78112

79-
const handleResize = (size: { width: number; height: number }) => {
80-
if (resizeStatus.value !== RESIZE_STATUS_NONE) {
81-
return;
82-
}
83-
emit('resize', size);
84-
85-
const autoSize = props.autoSize || props.autosize;
86-
if (autoSize) {
87-
resizeOnNextFrame();
113+
if (needAutoSize.value) {
114+
cleanRaf();
115+
resizeRafRef.value = raf(() => {
116+
startResize();
117+
});
118+
}
88119
}
89120
};
121+
onBeforeUnmount(() => {
122+
cleanRaf();
123+
});
124+
const resizeTextarea = () => {
125+
startResize();
126+
};
127+
128+
expose({
129+
resizeTextarea,
130+
textArea: textAreaRef,
131+
instance,
132+
});
90133
warning(
91134
props.autosize === undefined,
92135
'Input.TextArea',
93136
'autosize is deprecated, please use autoSize instead.',
94137
);
95138

96139
const renderTextArea = () => {
97-
const { prefixCls, autoSize, autosize, disabled } = props;
140+
const { prefixCls, disabled } = props;
98141
const otherProps = omit(props, [
99142
'prefixCls',
100143
'onPressEnter',
@@ -110,54 +153,35 @@ const ResizableTextArea = defineComponent({
110153
const cls = classNames(prefixCls, attrs.class, {
111154
[`${prefixCls}-disabled`]: disabled,
112155
});
113-
const style = [
114-
attrs.style,
115-
textareaStyles.value,
116-
resizeStatus.value === RESIZE_STATUS_RESIZING
117-
? { overflowX: 'hidden', overflowY: 'hidden' }
118-
: null,
119-
];
156+
const mergedAutoSizeStyle = needAutoSize.value ? autoSizeStyle.value : null;
157+
const style = [attrs.style, textareaStyles.value, mergedAutoSizeStyle];
120158
const textareaProps: any = {
121159
...otherProps,
122160
...attrs,
123161
style,
124162
class: cls,
125163
};
164+
if (resizeStatus.value === RESIZE_START || resizeStatus.value === RESIZE_MEASURING) {
165+
style.push({
166+
overflowX: 'hidden',
167+
overflowY: 'hidden',
168+
});
169+
}
126170
if (!textareaProps.autofocus) {
127171
delete textareaProps.autofocus;
128172
}
129173
if (textareaProps.rows === 0) {
130174
delete textareaProps.rows;
131175
}
132176
return (
133-
<ResizeObserver onResize={handleResize} disabled={!(autoSize || autosize)}>
177+
<ResizeObserver onResize={onInternalResize} disabled={!needAutoSize.value}>
134178
{withDirectives((<textarea {...textareaProps} ref={textAreaRef} />) as VNode, [
135179
[antInput],
136180
])}
137181
</ResizeObserver>
138182
);
139183
};
140184

141-
watch(
142-
() => props.value,
143-
() => {
144-
nextTick(() => {
145-
resizeTextarea();
146-
});
147-
},
148-
);
149-
onMounted(() => {
150-
nextTick(() => {
151-
resizeTextarea();
152-
});
153-
});
154-
const instance = getCurrentInstance();
155-
expose({
156-
resizeTextarea,
157-
textArea: textAreaRef,
158-
instance,
159-
});
160-
161185
return () => {
162186
return renderTextArea();
163187
};

components/input/calculateNodeHeight.tsx

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
// Thanks to https://github.com/andreypopp/react-textarea-autosize/
2-
31
import type { CSSProperties } from 'vue';
4-
52
/**
63
* calculateNodeHeight(uiTextNode, useCache = false)
74
*/
85

96
const HIDDEN_TEXTAREA_STYLE = `
10-
min-height:0 !important;
11-
max-height:none !important;
12-
height:0 !important;
13-
visibility:hidden !important;
14-
overflow:hidden !important;
15-
position:absolute !important;
16-
z-index:-1000 !important;
17-
top:0 !important;
18-
right:0 !important
7+
min-height:0 !important;
8+
max-height:none !important;
9+
height:0 !important;
10+
visibility:hidden !important;
11+
overflow:hidden !important;
12+
position:absolute !important;
13+
z-index:-1000 !important;
14+
top:0 !important;
15+
right:0 !important;
16+
pointer-events: none !important;
1917
`;
2018

2119
const SIZING_STYLE = [
@@ -36,6 +34,7 @@ const SIZING_STYLE = [
3634
'border-width',
3735
'box-sizing',
3836
'word-break',
37+
'white-space',
3938
];
4039

4140
export interface NodeType {
@@ -45,7 +44,7 @@ export interface NodeType {
4544
boxSizing: string;
4645
}
4746

48-
const computedStyleCache: { [key: string]: NodeType } = {};
47+
const computedStyleCache: Record<string, NodeType> = {};
4948
let hiddenTextarea: HTMLTextAreaElement;
5049

5150
export function calculateNodeStyling(node: HTMLElement, useCache = false) {
@@ -88,7 +87,7 @@ export function calculateNodeStyling(node: HTMLElement, useCache = false) {
8887
return nodeInfo;
8988
}
9089

91-
export default function calculateNodeHeight(
90+
export default function calculateAutoSizeStyle(
9291
uiTextNode: HTMLTextAreaElement,
9392
useCache = false,
9493
minRows: number | null = null,
@@ -122,11 +121,12 @@ export default function calculateNodeHeight(
122121
hiddenTextarea.setAttribute('style', `${sizingStyle};${HIDDEN_TEXTAREA_STYLE}`);
123122
hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || '';
124123

125-
let minHeight = Number.MIN_SAFE_INTEGER;
126-
let maxHeight = Number.MAX_SAFE_INTEGER;
127-
let height = hiddenTextarea.scrollHeight;
124+
let minHeight: number | undefined = undefined;
125+
let maxHeight: number | undefined = undefined;
128126
let overflowY: any;
129127

128+
let height = hiddenTextarea.scrollHeight;
129+
130130
if (boxSizing === 'border-box') {
131131
// border-box: add border, since height = content + padding + border
132132
height += borderSize;
@@ -155,11 +155,19 @@ export default function calculateNodeHeight(
155155
height = Math.min(maxHeight, height);
156156
}
157157
}
158-
return {
158+
159+
const style: CSSProperties = {
159160
height: `${height}px`,
160-
minHeight: `${minHeight}px`,
161-
maxHeight: `${maxHeight}px`,
162161
overflowY,
163162
resize: 'none',
164163
};
164+
165+
if (minHeight) {
166+
style.minHeight = `${minHeight}px`;
167+
}
168+
if (maxHeight) {
169+
style.maxHeight = `${maxHeight}px`;
170+
}
171+
172+
return style;
165173
}

0 commit comments

Comments
 (0)
0