Skip to content

Commit 4f4bd1f

Browse files
refactor(input): input (DevCloudFE#1682)
Co-authored-by: GreatZP <greatzp@greatzp.cn>
1 parent f1188ce commit 4f4bd1f

File tree

9 files changed

+112
-12
lines changed

9 files changed

+112
-12
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { App } from 'vue';
2+
import AutoFocus from './src/auto-focus-directive';
3+
4+
export { AutoFocus };
5+
6+
export default {
7+
title: 'AutoFocus 自动聚焦',
8+
category: '公共',
9+
install(app: App): void {
10+
app.directive('dAutoFocus', AutoFocus);
11+
},
12+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
mounted: (el: HTMLElement, binding: Record<string, any>) => {
3+
if (binding.value) {
4+
el.focus();
5+
}
6+
},
7+
};

packages/devui-vue/devui/input/__tests__/input.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils';
22
import { ref, nextTick, reactive } from 'vue';
33
import DInput from '../src/input';
44
import { Form, FormItem } from '../../form';
5-
import { useNamespace } from '../../shared/hooks/use-namespace';
5+
import { useNamespace } from '@devui/shared/utils';
66

77
const ns = useNamespace('input');
88
const dotNs = useNamespace('input', true);

packages/devui-vue/devui/input/src/composables/use-input-event.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { inject } from 'vue';
1+
import { inject, ref } from 'vue';
22
import type { Ref, SetupContext } from 'vue';
33
import { FORM_ITEM_TOKEN, FormItemContext } from '../../../form/src/components/form-item/form-item-types';
4-
import { InputProps, UseInputEvent } from '../input-types';
4+
import { InputProps } from '../input-types';
55

6-
export function useInputEvent(isFocus: Ref<boolean>, props: InputProps, ctx: SetupContext, focus: () => void): UseInputEvent {
6+
export function useInputEvent(isFocus: Ref<boolean>, props: InputProps, ctx: SetupContext, focus: () => void) {
77
const formItemContext = inject(FORM_ITEM_TOKEN, undefined) as FormItemContext;
8+
const isComposition = ref(false);
89
const onFocus = (e: FocusEvent) => {
910
isFocus.value = true;
1011
ctx.emit('focus', e);
@@ -20,6 +21,9 @@ export function useInputEvent(isFocus: Ref<boolean>, props: InputProps, ctx: Set
2021

2122
const onInput = (e: Event) => {
2223
ctx.emit('input', (e.target as HTMLInputElement).value);
24+
if (isComposition.value) {
25+
return;
26+
}
2327
ctx.emit('update:modelValue', (e.target as HTMLInputElement).value);
2428
};
2529

@@ -37,5 +41,22 @@ export function useInputEvent(isFocus: Ref<boolean>, props: InputProps, ctx: Set
3741
focus();
3842
};
3943

40-
return { onFocus, onBlur, onInput, onChange, onKeydown, onClear };
44+
const onCompositionStart = () => {
45+
isComposition.value = true;
46+
};
47+
48+
const onCompositionUpdate = (e: CompositionEvent) => {
49+
const text = (e.target as HTMLInputElement)?.value;
50+
const lastCharacter = text[text.length - 1] || '';
51+
isComposition.value = !/([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi.test(lastCharacter);
52+
};
53+
54+
const onCompositionEnd = (e: CompositionEvent) => {
55+
if (isComposition.value) {
56+
isComposition.value = false;
57+
onInput(e);
58+
}
59+
};
60+
61+
return { onFocus, onBlur, onInput, onChange, onKeydown, onClear, onCompositionStart, onCompositionUpdate, onCompositionEnd };
4162
}

packages/devui-vue/devui/input/src/composables/use-input-render.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { computed, inject, toRefs, ref } from 'vue';
22
import type { SetupContext } from 'vue';
3-
import { FORM_TOKEN, FormContext, FORM_ITEM_TOKEN, FormItemContext } from '../../../form';
3+
import { FORM_TOKEN, FormContext, FORM_ITEM_TOKEN, FormItemContext, STYLE_TOKEN } from '../../../form';
44
import { InputProps, UseInputRender } from '../input-types';
5-
import { useNamespace } from '../../../shared/hooks/use-namespace';
5+
import { useNamespace } from '@devui/shared/utils';
66

77
export function useInputRender(props: InputProps, ctx: SetupContext): UseInputRender {
88
const formContext = inject(FORM_TOKEN, undefined) as FormContext;
@@ -16,6 +16,8 @@ export function useInputRender(props: InputProps, ctx: SetupContext): UseInputRe
1616
const inputDisabled = computed(() => disabled.value || formContext?.disabled);
1717
const inputSize = computed(() => size?.value || formContext?.size || '');
1818

19+
const styleType = inject(STYLE_TOKEN, undefined);
20+
1921
const { style, class: customClass, ...otherAttrs } = ctx.attrs;
2022
const customStyle = { style };
2123

@@ -34,6 +36,7 @@ export function useInputRender(props: InputProps, ctx: SetupContext): UseInputRe
3436
[slotNs.b()]: slots.prepend || slots.append,
3537
[ns.m('append')]: slots.append,
3638
[ns.m('prepend')]: slots.prepend,
39+
[ns.m('gray-style')]: styleType === 'gray',
3740
},
3841
customClass,
3942
]);

packages/devui-vue/devui/input/src/input-types.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ export const inputProps = {
4242
type: String,
4343
default: '',
4444
},
45+
title: {
46+
type: String,
47+
default: '',
48+
},
49+
autofocus: {
50+
type: Boolean,
51+
default: false,
52+
},
4553
} as const;
4654

4755
export type InputProps = ExtractPropTypes<typeof inputProps>;

packages/devui-vue/devui/input/src/input.scss

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
background: none;
7373
outline: none;
7474
box-sizing: border-box;
75+
76+
&::placeholder {
77+
color: $devui-placeholder;
78+
}
7579
}
7680

7781
&--prepend {
@@ -158,4 +162,33 @@
158162
&__password--icon {
159163
cursor: pointer;
160164
}
165+
166+
&--gray-style:not(.#{$devui-prefix}-input--disabled) {
167+
.#{$devui-prefix}-input__wrapper:not(.#{$devui-prefix}-input--error) {
168+
background: $devui-gray-5;
169+
border-color: $devui-gray-5;
170+
171+
&:hover {
172+
background: $devui-gray-10;
173+
border-color: $devui-gray-10;
174+
}
175+
}
176+
}
177+
}
178+
179+
body[ui-theme='galaxy-theme'] {
180+
.#{$devui-prefix}-input__inner:-webkit-autofill,
181+
.#{$devui-prefix}-input__inner:-webkit-autofill:hover,
182+
.#{$devui-prefix}-input__inner:-webkit-autofill:focus,
183+
.#{$devui-prefix}-input__inner:-webkit-autofill:active {
184+
-webkit-box-shadow: 0 0 0 1000px transparent inset !important;
185+
box-shadow: 0 0 0 1000px transparent inset !important;
186+
caret-color: white;
187+
}
188+
189+
.#{$devui-prefix}-input__inner:-internal-autofill-previewed,
190+
.#{$devui-prefix}-input__inner:-internal-autofill-selected {
191+
-webkit-text-fill-color: $devui-text;
192+
transition: background-color 99999s ease-out 0.5s;
193+
}
161194
}

packages/devui-vue/devui/input/src/input.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { defineComponent, watch, inject, toRefs, shallowRef, ref, computed, getCurrentInstance } from 'vue';
22
import type { SetupContext } from 'vue';
33
import Icon from '../../icon/src/icon';
4+
import { AutoFocus } from '../../auto-focus';
45
import { inputProps, InputProps } from './input-types';
56
import { FORM_ITEM_TOKEN, FormItemContext } from '../../form/src/components/form-item/form-item-types';
6-
import { useNamespace } from '../../shared/hooks/use-namespace';
7+
import { useNamespace } from '@devui/shared/utils';
78
import { useInputRender } from './composables/use-input-render';
89
import { useInputEvent } from './composables/use-input-event';
910
import { useInputFunction } from './composables/use-input-function';
@@ -12,6 +13,9 @@ import { createI18nTranslate } from '../../locale/create';
1213

1314
export default defineComponent({
1415
name: 'DInput',
16+
directives: {
17+
dAutoFocus: AutoFocus,
18+
},
1519
inheritAttrs: false,
1620
props: inputProps,
1721
emits: ['update:modelValue', 'focus', 'blur', 'input', 'change', 'keydown', 'clear'],
@@ -20,15 +24,16 @@ export default defineComponent({
2024
const t = createI18nTranslate('DInput', app);
2125

2226
const formItemContext = inject(FORM_ITEM_TOKEN, undefined) as FormItemContext;
23-
const { modelValue } = toRefs(props);
27+
const { modelValue, placeholder, title, autofocus } = toRefs(props);
2428
const ns = useNamespace('input');
2529
const slotNs = useNamespace('input-slot');
2630
const { inputDisabled, inputSize, isFocus, wrapClasses, inputClasses, customStyle, otherAttrs } = useInputRender(props, ctx);
2731

2832
const input = shallowRef<HTMLInputElement>();
2933
const { select, focus, blur } = useInputFunction(input);
3034

31-
const { onFocus, onBlur, onInput, onChange, onKeydown, onClear } = useInputEvent(isFocus, props, ctx, focus);
35+
const { onFocus, onBlur, onInput, onChange, onKeydown, onClear, onCompositionStart, onCompositionUpdate, onCompositionEnd } =
36+
useInputEvent(isFocus, props, ctx, focus);
3237

3338
const passwordVisible = ref(false);
3439
const clickPasswordIcon = () => {
@@ -67,17 +72,22 @@ export default defineComponent({
6772
)}
6873
<input
6974
ref={input}
75+
v-dAutoFocus={autofocus.value}
7076
value={modelValue.value}
7177
disabled={inputDisabled.value}
7278
class={ns.e('inner')}
73-
placeholder={props.placeholder || t('placeholder')}
79+
placeholder={placeholder.value ?? t('placeholder')}
7480
{...otherAttrs}
81+
title={title.value}
7582
type={props.showPassword ? (passwordVisible.value ? 'text' : 'password') : 'text'}
7683
onInput={onInput}
7784
onFocus={onFocus}
7885
onBlur={onBlur}
7986
onChange={onChange}
8087
onKeydown={onKeydown}
88+
onCompositionstart={onCompositionStart}
89+
onCompositionupdate={onCompositionUpdate}
90+
onCompositionend={onCompositionEnd}
8191
/>
8292
{suffixVisible && (
8393
<span class={slotNs.e('suffix')}>
@@ -92,7 +102,7 @@ export default defineComponent({
92102
/>
93103
)}
94104
{showClearable.value && (
95-
<Icon size={inputSize.value} class={ns.em('clear', 'icon')} name="close" onClick={onClear} />
105+
<Icon size={inputSize.value} class={ns.em('clear', 'icon')} name="error-o" color="#adb0b8" onClick={onClear} />
96106
)}
97107
</span>
98108
)}

packages/devui-vue/docs/components/input/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,19 @@ export default defineComponent({
320320
| 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo |
321321
| :------------- | :---------------------- | :----- | :----------------------------------------------- | :-------------------------------- |
322322
| v-model | `string` | '' | 绑定值 | [基本用法](#基本用法) |
323+
| placeholder | `string` | '' | 输入框占位文本 | [基本用法](#基本用法) |
323324
| disabled | `boolean` | false | 可选,文本框是否被禁用 | [基本用法](#基本用法) |
324325
| error | `boolean` | false | 可选,文本框是否出现输入错误 | [基本用法](#基本用法) |
325326
| size | [InputSize](#inputsize) | 'md' | 可选,文本框尺寸,有三种选择`'lg'`,`'md'`,`'sm'` | [尺寸](#尺寸) |
326327
| validate-event | `boolean` | true | 可选,输入时是否触发表单的校验 | |
327328
| prefix | `string` | - | 可选,自定义前缀图标 | [带图标的输入框](#带图标的输入框) |
328329
| suffix | `string` | - | 可选,自定义后缀图标 | [带图标的输入框](#带图标的输入框) |
330+
| show-password | `boolean` | false | 可选,是否显示为密码框 | |
329331
| clearable | `boolean` | false | 可选,是否可清空 | [一键清空](#一键清空) |
332+
| name | `string` | '' | 等价于原生 input name 属性 | |
333+
| readonly | `boolean` | false | 原生 readonly 属性,是否只读 | |
334+
| autocomplete | `string` | 'off' | 原生 autocomplete 属性 | |
335+
| autofocus | `boolean` | false | 自动获取焦点 | |
330336

331337
### Input 插槽
332338

0 commit comments

Comments
 (0)