Skip to content

Commit 752686e

Browse files
committed
fix: input composing error, close #7516
1 parent 9b0f0e7 commit 752686e

File tree

9 files changed

+190
-150
lines changed

9 files changed

+190
-150
lines changed

components/_util/BaseInput.tsx

+131-37
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,145 @@
1-
import { defineComponent, shallowRef, withDirectives } from 'vue';
2-
import antInput from './antInputDirective';
1+
import type { PropType } from 'vue';
2+
import { defineComponent, shallowRef, ref, watch } from 'vue';
33
import PropTypes from './vue-types';
4+
5+
export interface BaseInputExpose {
6+
focus: () => void;
7+
blur: () => void;
8+
input: HTMLInputElement | HTMLTextAreaElement | null;
9+
setSelectionRange: (
10+
start: number,
11+
end: number,
12+
direction?: 'forward' | 'backward' | 'none',
13+
) => void;
14+
select: () => void;
15+
getSelectionStart: () => number | null;
16+
getSelectionEnd: () => number | null;
17+
getScrollTop: () => number | null;
18+
setScrollTop: (scrollTop: number) => void;
19+
}
420
const BaseInput = defineComponent({
521
compatConfig: { MODE: 3 },
22+
inheritAttrs: false,
623
props: {
7-
value: PropTypes.string.def(''),
24+
disabled: PropTypes.looseBool,
25+
type: PropTypes.string,
26+
value: PropTypes.any,
27+
lazy: PropTypes.bool.def(true),
28+
tag: {
29+
type: String as PropType<'input' | 'textarea'>,
30+
default: 'input',
31+
},
32+
size: PropTypes.string,
833
},
9-
emits: ['change', 'input'],
10-
setup(_p, { emit }) {
34+
emits: [
35+
'change',
36+
'input',
37+
'blur',
38+
'keydown',
39+
'focus',
40+
'compositionstart',
41+
'compositionend',
42+
'keyup',
43+
],
44+
setup(props, { emit, attrs, expose }) {
1145
const inputRef = shallowRef(null);
46+
const renderValue = ref();
47+
const isComposing = ref(false);
48+
watch(
49+
[() => props.value, isComposing],
50+
() => {
51+
if (isComposing.value) return;
52+
renderValue.value = props.value;
53+
},
54+
{ immediate: true },
55+
);
1256
const handleChange = (e: Event) => {
13-
const { composing } = e.target as any;
14-
if ((e as any).isComposing || composing) {
15-
emit('input', e);
16-
} else {
17-
emit('input', e);
18-
emit('change', e);
57+
emit('change', e);
58+
};
59+
const onCompositionstart = (e: CompositionEvent) => {
60+
isComposing.value = true;
61+
(e.target as any).composing = true;
62+
emit('compositionstart', e);
63+
};
64+
const onCompositionend = (e: CompositionEvent) => {
65+
isComposing.value = false;
66+
(e.target as any).composing = false;
67+
emit('compositionend', e);
68+
const event = document.createEvent('HTMLEvents');
69+
event.initEvent('input', true, true);
70+
e.target.dispatchEvent(event);
71+
};
72+
const handleInput = (e: Event) => {
73+
if (isComposing.value && props.lazy) {
74+
renderValue.value = (e.target as HTMLInputElement).value;
75+
return;
1976
}
77+
emit('input', e);
2078
};
21-
return {
22-
inputRef,
23-
focus: () => {
24-
if (inputRef.value) {
25-
inputRef.value.focus();
26-
}
27-
},
28-
blur: () => {
29-
if (inputRef.value) {
30-
inputRef.value.blur();
31-
}
32-
},
33-
handleChange,
79+
80+
const handleBlur = (e: Event) => {
81+
emit('blur', e);
3482
};
35-
},
36-
render() {
37-
return withDirectives(
38-
(
39-
<input
40-
{...this.$props}
41-
{...this.$attrs}
42-
onInput={this.handleChange}
43-
onChange={this.handleChange}
44-
ref="inputRef"
83+
const handleFocus = (e: Event) => {
84+
emit('focus', e);
85+
};
86+
87+
const focus = () => {
88+
if (inputRef.value) {
89+
inputRef.value.focus();
90+
}
91+
};
92+
const blur = () => {
93+
if (inputRef.value) {
94+
inputRef.value.blur();
95+
}
96+
};
97+
const handleKeyDown = (e: KeyboardEvent) => {
98+
emit('keydown', e);
99+
};
100+
const handleKeyUp = (e: KeyboardEvent) => {
101+
emit('keyup', e);
102+
};
103+
const setSelectionRange = (
104+
start: number,
105+
end: number,
106+
direction?: 'forward' | 'backward' | 'none',
107+
) => {
108+
inputRef.value?.setSelectionRange(start, end, direction);
109+
};
110+
111+
const select = () => {
112+
inputRef.value?.select();
113+
};
114+
expose({
115+
focus,
116+
blur,
117+
input: inputRef,
118+
setSelectionRange,
119+
select,
120+
getSelectionStart: () => inputRef.value?.selectionStart,
121+
getSelectionEnd: () => inputRef.value?.selectionEnd,
122+
getScrollTop: () => inputRef.value?.scrollTop,
123+
});
124+
return () => {
125+
const { tag: Tag, ...restProps } = props;
126+
return (
127+
<Tag
128+
{...restProps}
129+
{...attrs}
130+
onInput={handleInput}
131+
onChange={handleChange}
132+
onBlur={handleBlur}
133+
onFocus={handleFocus}
134+
ref={inputRef}
135+
value={renderValue.value}
136+
onCompositionstart={onCompositionstart}
137+
onCompositionend={onCompositionend}
138+
onKeyup={handleKeyUp}
139+
onKeydown={handleKeyDown}
45140
/>
46-
) as any,
47-
[[antInput]],
48-
);
141+
);
142+
};
49143
},
50144
});
51145

components/_util/antInputDirective.ts

-42
This file was deleted.

components/input/ResizableTextArea.tsx

+12-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { VNode, CSSProperties } from 'vue';
1+
import type { CSSProperties } from 'vue';
22
import {
33
computed,
44
watchEffect,
@@ -7,16 +7,16 @@ import {
77
onBeforeUnmount,
88
ref,
99
defineComponent,
10-
withDirectives,
1110
} from 'vue';
1211
import ResizeObserver from '../vc-resize-observer';
1312
import classNames from '../_util/classNames';
1413
import raf from '../_util/raf';
1514
import warning from '../_util/warning';
16-
import antInput from '../_util/antInputDirective';
1715
import omit from '../_util/omit';
1816
import { textAreaProps } from './inputProps';
1917
import calculateAutoSizeStyle from './calculateNodeHeight';
18+
import type { BaseInputExpose } from '../_util/BaseInput';
19+
import BaseInput from '../_util/BaseInput';
2020

2121
const RESIZE_START = 0;
2222
const RESIZE_MEASURING = 1;
@@ -30,7 +30,7 @@ const ResizableTextArea = defineComponent({
3030
setup(props, { attrs, emit, expose }) {
3131
let nextFrameActionId: any;
3232
let resizeFrameId: any;
33-
const textAreaRef = ref();
33+
const textAreaRef = ref<BaseInputExpose>();
3434
const textareaStyles = ref({});
3535
const resizeStatus = ref(RESIZE_STABLE);
3636
onBeforeUnmount(() => {
@@ -41,12 +41,12 @@ const ResizableTextArea = defineComponent({
4141
// https://github.com/ant-design/ant-design/issues/21870
4242
const fixFirefoxAutoScroll = () => {
4343
try {
44-
if (document.activeElement === textAreaRef.value) {
45-
const currentStart = textAreaRef.value.selectionStart;
46-
const currentEnd = textAreaRef.value.selectionEnd;
47-
const scrollTop = textAreaRef.value.scrollTop;
44+
if (textAreaRef.value && document.activeElement === textAreaRef.value.input) {
45+
const currentStart = textAreaRef.value.getSelectionStart();
46+
const currentEnd = textAreaRef.value.getSelectionEnd();
47+
const scrollTop = textAreaRef.value.getScrollTop();
4848
textAreaRef.value.setSelectionRange(currentStart, currentEnd);
49-
textAreaRef.value.scrollTop = scrollTop;
49+
textAreaRef.value.setScrollTop(scrollTop);
5050
}
5151
} catch (e) {
5252
// Fix error in Chrome:
@@ -88,7 +88,7 @@ const ResizableTextArea = defineComponent({
8888
resizeStatus.value = RESIZE_MEASURING;
8989
} else if (resizeStatus.value === RESIZE_MEASURING) {
9090
const textareaStyles = calculateAutoSizeStyle(
91-
textAreaRef.value,
91+
textAreaRef.value.input as HTMLTextAreaElement,
9292
false,
9393
minRows.value,
9494
maxRows.value,
@@ -127,7 +127,7 @@ const ResizableTextArea = defineComponent({
127127

128128
expose({
129129
resizeTextarea,
130-
textArea: textAreaRef,
130+
textArea: computed(() => textAreaRef.value?.input),
131131
instance,
132132
});
133133
warning(
@@ -146,7 +146,6 @@ const ResizableTextArea = defineComponent({
146146
'defaultValue',
147147
'allowClear',
148148
'type',
149-
'lazy',
150149
'maxlength',
151150
'valueModifiers',
152151
]);
@@ -175,9 +174,7 @@ const ResizableTextArea = defineComponent({
175174
}
176175
return (
177176
<ResizeObserver onResize={onInternalResize} disabled={!needAutoSize.value}>
178-
{withDirectives((<textarea {...textareaProps} ref={textAreaRef} />) as VNode, [
179-
[antInput],
180-
])}
177+
<BaseInput {...textareaProps} ref={textAreaRef} tag="textarea"></BaseInput>
181178
</ResizeObserver>
182179
);
183180
};

components/input/TextArea.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,8 @@ export default defineComponent({
170170
};
171171

172172
const handleChange = (e: Event) => {
173-
const { composing } = e.target as any;
174173
let triggerValue = (e.target as any).value;
175-
compositing.value = !!((e as any).isComposing && composing);
176-
if ((compositing.value && props.lazy) || stateValue.value === triggerValue) return;
174+
if (stateValue.value === triggerValue) return;
177175

178176
if (hasMaxLength.value) {
179177
// 1. 复制粘贴超过maxlength的情况 2.未超过maxlength的情况
@@ -227,6 +225,7 @@ export default defineComponent({
227225
id={resizeProps?.id ?? formItemContext.id.value}
228226
ref={resizableTextArea}
229227
maxlength={props.maxlength}
228+
lazy={props.lazy}
230229
/>
231230
);
232231
};

components/vc-input/Input.tsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// base 0.0.1-alpha.7
2-
import type { ComponentPublicInstance, VNode } from 'vue';
3-
import { onMounted, defineComponent, nextTick, shallowRef, watch, withDirectives } from 'vue';
2+
import type { ComponentPublicInstance } from 'vue';
3+
import { computed, onMounted, defineComponent, nextTick, shallowRef, watch } from 'vue';
44
import classNames from '../_util/classNames';
55
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface';
66
import omit from '../_util/omit';
@@ -14,8 +14,8 @@ import {
1414
resolveOnChange,
1515
triggerFocus,
1616
} from './utils/commonUtils';
17-
import antInputDirective from '../_util/antInputDirective';
1817
import BaseInput from './BaseInput';
18+
import BaseInputCore from '../_util/BaseInput';
1919

2020
export default defineComponent({
2121
name: 'VCInput',
@@ -65,7 +65,7 @@ export default defineComponent({
6565
expose({
6666
focus,
6767
blur,
68-
input: inputRef,
68+
input: computed(() => (inputRef.value as any)?.input),
6969
stateValue,
7070
setSelectionRange,
7171
select,
@@ -91,9 +91,8 @@ export default defineComponent({
9191
});
9292
};
9393
const handleChange = (e: ChangeEvent) => {
94-
const { value, composing } = e.target as any;
95-
// https://github.com/vueComponent/ant-design-vue/issues/2203
96-
if (((e as any).isComposing && composing && props.lazy) || stateValue.value === value) return;
94+
const { value } = e.target as any;
95+
if (stateValue.value === value) return;
9796
const newVal = e.target.value;
9897
resolveOnChange(inputRef.value, e, triggerChange);
9998
setValue(newVal);
@@ -184,15 +183,16 @@ export default defineComponent({
184183
key: 'ant-input',
185184
size: htmlSize,
186185
type,
186+
lazy: props.lazy,
187187
};
188188
if (valueModifiers.lazy) {
189189
delete inputProps.onInput;
190190
}
191191
if (!inputProps.autofocus) {
192192
delete inputProps.autofocus;
193193
}
194-
const inputNode = <input {...omit(inputProps, ['size'])} />;
195-
return withDirectives(inputNode as VNode, [[antInputDirective]]);
194+
const inputNode = <BaseInputCore {...omit(inputProps, ['size'])} />;
195+
return inputNode;
196196
};
197197
const getSuffix = () => {
198198
const { maxlength, suffix = slots.suffix?.(), showCount, prefixCls } = props;

0 commit comments

Comments
 (0)