From 664d57a9e7c113638b5829c0d7063d9797deaf06 Mon Sep 17 00:00:00 2001 From: GreatZP Date: Sun, 20 Aug 2023 16:46:36 +0800 Subject: [PATCH] refactor(textarea): textarea --- .../devui/textarea/__tests__/textarea.spec.ts | 12 ++++----- .../src/composables/use-textarea-event.ts | 27 ++++++++++++++++--- .../src/composables/use-textarea-render.ts | 7 +++-- .../devui/textarea/src/textarea.scss | 16 +++++++++++ .../devui-vue/devui/textarea/src/textarea.tsx | 11 ++++++-- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/packages/devui-vue/devui/textarea/__tests__/textarea.spec.ts b/packages/devui-vue/devui/textarea/__tests__/textarea.spec.ts index a0d3544405..538cbdec0d 100644 --- a/packages/devui-vue/devui/textarea/__tests__/textarea.spec.ts +++ b/packages/devui-vue/devui/textarea/__tests__/textarea.spec.ts @@ -1,9 +1,9 @@ import { mount } from '@vue/test-utils'; import { ref, nextTick } from 'vue'; import DTextarea from '../src/textarea'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; -const ns = useNamespace('textarea', true); +const ns = useNamespace('textarea'); describe('textarea test', () => { it('d-textarea render work', async () => { @@ -20,7 +20,7 @@ describe('textarea test', () => { }, }); const textarea = wrapper.find('textarea'); - expect(textarea.classes()).toContain('devui-textarea'); + expect(textarea.classes()).toContain(ns.b()); expect(textarea.element.value).toBe('abc'); await textarea.setValue('def'); @@ -100,12 +100,12 @@ describe('textarea test', () => { error: false, }, }); - expect(wrapper.find('textarea').classes()).not.toContain('devui-textarea--error'); + expect(wrapper.find('textarea').classes()).not.toContain(ns.m('error')); await wrapper.setProps({ error: true, }); - expect(wrapper.find('textarea').classes()).toContain('devui-textarea--error'); + expect(wrapper.find('textarea').classes()).toContain(ns.m('error')); }); it('d-textarea autosize work', async () => { @@ -138,7 +138,7 @@ describe('textarea test', () => { template: ` `, - setup () { + setup() { return { value, }; diff --git a/packages/devui-vue/devui/textarea/src/composables/use-textarea-event.ts b/packages/devui-vue/devui/textarea/src/composables/use-textarea-event.ts index c29ce27f64..c65182549d 100644 --- a/packages/devui-vue/devui/textarea/src/composables/use-textarea-event.ts +++ b/packages/devui-vue/devui/textarea/src/composables/use-textarea-event.ts @@ -1,9 +1,10 @@ -import { inject, Ref, SetupContext } from 'vue'; +import { inject, Ref, SetupContext, ref } from 'vue'; import { FormItemContext, FORM_ITEM_TOKEN } from '../../../form'; import { TextareaProps, UseTextareaEvent } from '../textarea-types'; -export function useTextareaEvent(isFocus: Ref, props: TextareaProps, ctx: SetupContext): UseTextareaEvent { +export function useTextareaEvent(isFocus: Ref, props: TextareaProps, ctx: SetupContext) { const formItemContext = inject(FORM_ITEM_TOKEN, undefined) as FormItemContext; + const isComposition = ref(false); const onFocus = (e: FocusEvent) => { isFocus.value = true; ctx.emit('focus', e); @@ -18,6 +19,9 @@ export function useTextareaEvent(isFocus: Ref, props: TextareaProps, ct }; const onInput = (e: Event) => { + if (isComposition.value) { + return; + } ctx.emit('update:modelValue', (e.target as HTMLInputElement).value); ctx.emit('update', (e.target as HTMLInputElement).value); }; @@ -30,5 +34,22 @@ export function useTextareaEvent(isFocus: Ref, props: TextareaProps, ct ctx.emit('keydown', e); }; - return { onFocus, onBlur, onInput, onChange, onKeydown }; + const onCompositionStart = () => { + isComposition.value = true; + }; + + const onCompositionUpdate = (e: CompositionEvent) => { + const text = (e.target as HTMLInputElement)?.value; + const lastCharacter = text[text.length - 1] || ''; + isComposition.value = !/([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi.test(lastCharacter); + }; + + const onCompositionEnd = (e: CompositionEvent) => { + if (isComposition.value) { + isComposition.value = false; + onInput(e); + } + }; + + return { onFocus, onBlur, onInput, onChange, onKeydown, onCompositionStart, onCompositionUpdate, onCompositionEnd }; } diff --git a/packages/devui-vue/devui/textarea/src/composables/use-textarea-render.ts b/packages/devui-vue/devui/textarea/src/composables/use-textarea-render.ts index e017d78efc..19549ec8e2 100644 --- a/packages/devui-vue/devui/textarea/src/composables/use-textarea-render.ts +++ b/packages/devui-vue/devui/textarea/src/composables/use-textarea-render.ts @@ -1,7 +1,7 @@ import { computed, toRefs, ref, inject } from 'vue'; -import { FORM_TOKEN, FormContext, FORM_ITEM_TOKEN, FormItemContext } from '../../../form'; +import { FORM_TOKEN, FormContext, FORM_ITEM_TOKEN, FormItemContext, STYLE_TOKEN } from '../../../form'; import { TextareaProps, UseTextareaRender } from '../textarea-types'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; export function useTextareaRender(props: TextareaProps): UseTextareaRender { const formContext = inject(FORM_TOKEN, undefined) as FormContext; @@ -12,12 +12,15 @@ export function useTextareaRender(props: TextareaProps): UseTextareaRender { const { error, disabled } = toRefs(props); const textareaDisabled = computed(() => disabled.value || formContext?.disabled); + const styleType = inject(STYLE_TOKEN, undefined); + const wrapClasses = computed(() => ({ [ns.b()]: true, [ns.m('focus')]: isFocus.value, [ns.m('disabled')]: textareaDisabled.value, [ns.m('error')]: error.value || isValidateError.value, [ns.m('feedback')]: Boolean(formItemContext?.validateState) && formItemContext?.showFeedback, + [ns.m('gary-style')]: styleType === 'gray', })); return { isFocus, textareaDisabled, wrapClasses }; diff --git a/packages/devui-vue/devui/textarea/src/textarea.scss b/packages/devui-vue/devui/textarea/src/textarea.scss index 214663495b..23d08f4172 100644 --- a/packages/devui-vue/devui/textarea/src/textarea.scss +++ b/packages/devui-vue/devui/textarea/src/textarea.scss @@ -4,6 +4,8 @@ width: 100%; box-sizing: border-box; padding: 4px 8px; + font-size: $devui-font-size; + font-family: inherit; color: var(--devui-text, #252b3a); vertical-align: middle; outline: none; @@ -12,6 +14,10 @@ background: $devui-form-control-bg; transition: border-color 0.3s $devui-animation-ease-in-out-smooth; + &::placeholder { + color: $devui-placeholder; + } + &:not(.#{$devui-prefix}-textarea--error):not(.#{$devui-prefix}-textarea--disabled):not(.#{$devui-prefix}-textarea--focus):hover { border-color: $devui-form-control-line-hover; } @@ -52,4 +58,14 @@ &--feedback { padding-right: 28px; } + + &--gray-style:not(.#{$devui-prefix}-textarea--disabled):not(.#{$devui-prefix}-textarea--error) { + background: $devui-gray-5; + border-color: $devui-gray-5; + + &:hover { + background: $devui-gray-10; + border-color: $devui-gray-10 !important; + } + } } diff --git a/packages/devui-vue/devui/textarea/src/textarea.tsx b/packages/devui-vue/devui/textarea/src/textarea.tsx index 5c7152f00f..2c2eeeef3c 100644 --- a/packages/devui-vue/devui/textarea/src/textarea.tsx +++ b/packages/devui-vue/devui/textarea/src/textarea.tsx @@ -1,7 +1,7 @@ import { defineComponent, inject, nextTick, onMounted, SetupContext, shallowRef, toRefs, watch } from 'vue'; import { FORM_ITEM_TOKEN, FormItemContext } from '../../form'; import { textareaProps, TextareaProps } from './textarea-types'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import { useTextareaRender } from './composables/use-textarea-render'; import { useTextareaEvent } from './composables/use-textarea-event'; import { useTextareaAutosize } from './composables/use-textarea-autosize'; @@ -18,7 +18,11 @@ export default defineComponent({ const textarea = shallowRef(); const ns = useNamespace('textarea'); const { isFocus, textareaDisabled, wrapClasses } = useTextareaRender(props); - const { onFocus, onBlur, onInput, onChange, onKeydown } = useTextareaEvent(isFocus, props, ctx); + const { onFocus, onBlur, onInput, onChange, onKeydown, onCompositionStart, onCompositionUpdate, onCompositionEnd } = useTextareaEvent( + isFocus, + props, + ctx + ); const { textareaStyle, updateTextareaStyle } = useTextareaAutosize(props, textarea); watch( @@ -46,6 +50,9 @@ export default defineComponent({ disabled={textareaDisabled.value} style={textareaStyle.value} class={wrapClasses.value} + onCompositionstart={onCompositionStart} + onCompositionupdate={onCompositionUpdate} + onCompositionend={onCompositionEnd} onInput={onInput} onFocus={onFocus} onBlur={onBlur}