refactor(hooks): rewrite composition as a composable function (#14240)

* refactor(hooks): rewrite composition as a composable function

* fix(components): [select] avoid navigateOptions when composing Chinese

* fix: error

* chore: change afterComposition
This commit is contained in:
qiang 2024-07-28 21:11:20 +08:00 committed by GitHub
parent 9fe6eab4c7
commit 233d38b631
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 102 additions and 94 deletions

View File

@ -209,14 +209,13 @@ import ElTag from '@element-plus/components/tag'
import ElIcon from '@element-plus/components/icon'
import { useFormItem, useFormSize } from '@element-plus/components/form'
import { ClickOutside as vClickoutside } from '@element-plus/directives'
import { useEmptyValues, useLocale, useNamespace } from '@element-plus/hooks'
import {
debugWarn,
focusNode,
getSibling,
isClient,
isKorean,
} from '@element-plus/utils'
useComposition,
useEmptyValues,
useLocale,
useNamespace,
} from '@element-plus/hooks'
import { debugWarn, focusNode, getSibling, isClient } from '@element-plus/utils'
import {
CHANGE_EVENT,
EVENT_CODE,
@ -271,6 +270,12 @@ const nsInput = useNamespace('input')
const { t } = useLocale()
const { form, formItem } = useFormItem()
const { valueOnClear } = useEmptyValues(props)
const { isComposing, handleComposition } = useComposition({
afterComposition(event) {
const text = (event.target as HTMLInputElement)?.value
handleInput(text)
},
})
const tooltipRef: Ref<TooltipInstance | null> = ref(null)
const input: Ref<InputInstance | null> = ref(null)
@ -286,7 +291,6 @@ const searchInputValue = ref('')
const presentTags: Ref<Tag[]> = ref([])
const allPresentTags: Ref<Tag[]> = ref([])
const suggestions: Ref<CascaderNode[]> = ref([])
const isOnComposition = ref(false)
const cascaderStyle = computed<StyleValue>(() => {
return attrs.style as StyleValue
@ -297,9 +301,7 @@ const inputPlaceholder = computed(
() => props.placeholder || t('el.cascader.placeholder')
)
const currentPlaceholder = computed(() =>
searchInputValue.value ||
presentTags.value.length > 0 ||
isOnComposition.value
searchInputValue.value || presentTags.value.length > 0 || isComposing.value
? ''
: inputPlaceholder.value
)
@ -538,19 +540,8 @@ const handleExpandChange = (value: CascaderValue) => {
emit('expandChange', value)
}
const handleComposition = (event: CompositionEvent) => {
const text = (event.target as HTMLInputElement)?.value
if (event.type === 'compositionend') {
isOnComposition.value = false
nextTick(() => handleInput(text))
} else {
const lastCharacter = text[text.length - 1] || ''
isOnComposition.value = !isKorean(lastCharacter)
}
}
const handleKeyDown = (e: KeyboardEvent) => {
if (isOnComposition.value) return
if (isComposing.value) return
switch (e.code) {
case EVENT_CODE.enter:

View File

@ -179,11 +179,11 @@ import {
ValidateComponentsMap,
debugWarn,
isClient,
isKorean,
isObject,
} from '@element-plus/utils'
import {
useAttrs,
useComposition,
useCursor,
useDeprecated,
useFocusController,
@ -256,7 +256,6 @@ const input = shallowRef<HTMLInputElement>()
const textarea = shallowRef<HTMLTextAreaElement>()
const hovering = ref(false)
const isComposing = ref(false)
const passwordVisible = ref(false)
const countStyle = ref<StyleValue>()
const textareaCalcStyle = shallowRef(props.inputStyle)
@ -435,25 +434,12 @@ const handleChange = (event: Event) => {
emit('change', (event.target as TargetElement).value)
}
const handleCompositionStart = (event: CompositionEvent) => {
emit('compositionstart', event)
isComposing.value = true
}
const handleCompositionUpdate = (event: CompositionEvent) => {
emit('compositionupdate', event)
const text = (event.target as HTMLInputElement)?.value
const lastCharacter = text[text.length - 1] || ''
isComposing.value = !isKorean(lastCharacter)
}
const handleCompositionEnd = (event: CompositionEvent) => {
emit('compositionend', event)
if (isComposing.value) {
isComposing.value = false
handleInput(event)
}
}
const {
isComposing,
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
} = useComposition({ emit, afterComposition: handleInput })
const handlePasswordVisible = () => {
passwordVisible.value = !passwordVisible.value

View File

@ -1,33 +0,0 @@
// @ts-nocheck
import { ref } from 'vue'
import { isFunction } from '@vue/shared'
import { isKorean } from '@element-plus/utils'
export function useInput(handleInput: (event: InputEvent) => void) {
const isComposing = ref(false)
const handleCompositionStart = () => {
isComposing.value = true
}
const handleCompositionUpdate = (event) => {
const text = event.target.value
const lastCharacter = text[text.length - 1] || ''
isComposing.value = !isKorean(lastCharacter)
}
const handleCompositionEnd = (event) => {
if (isComposing.value) {
isComposing.value = false
if (isFunction(handleInput)) {
handleInput(event)
}
}
}
return {
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
}
}

View File

@ -17,6 +17,7 @@ import {
} from 'lodash-unified'
import { useResizeObserver } from '@vueuse/core'
import {
useComposition,
useEmptyValues,
useFocusController,
useLocale,
@ -40,7 +41,6 @@ import {
import { ArrowDown } from '@element-plus/icons-vue'
import { useAllowCreate } from './useAllowCreate'
import { useInput } from './useInput'
import { useProps } from './useProps'
import type ElTooltip from '@element-plus/components/tooltip'
@ -94,6 +94,15 @@ const useSelect = (props: ISelectV2Props, emit) => {
const tagMenuRef = ref<HTMLElement>(null)
const collapseItemRef = ref<HTMLElement>(null)
const {
isComposing,
handleCompositionStart,
handleCompositionEnd,
handleCompositionUpdate,
} = useComposition({
afterComposition: (e) => onInput(e),
})
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
inputRef,
{
@ -356,11 +365,6 @@ const useSelect = (props: ISelectV2Props, emit) => {
selectNewOption,
clearAllNewOption,
} = useAllowCreate(props, states)
const {
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
} = useInput((e) => onInput(e))
// methods
const toggleMenu = () => {
@ -385,7 +389,7 @@ const useSelect = (props: ISelectV2Props, emit) => {
const debouncedOnInputChange = lodashDebounce(onInputChange, debounce.value)
const handleQueryChange = (val: string) => {
if (states.previousQuery === val) {
if (states.previousQuery === val || isComposing.value) {
return
}
states.previousQuery = val
@ -619,7 +623,8 @@ const useSelect = (props: ISelectV2Props, emit) => {
!['forward', 'backward'].includes(direction) ||
selectDisabled.value ||
options.length <= 0 ||
optionsAllDisabled.value
optionsAllDisabled.value ||
isComposing.value
) {
return
}

View File

@ -33,6 +33,7 @@ import {
scrollIntoView,
} from '@element-plus/utils'
import {
useComposition,
useEmptyValues,
useFocusController,
useId,
@ -45,7 +46,6 @@ import {
useFormSize,
} from '@element-plus/components/form'
import { useInput } from '../../select-v2/src/useInput'
import type ElTooltip from '@element-plus/components/tooltip'
import type { ISelectProps, SelectOptionProxy } from './token'
@ -91,6 +91,15 @@ export const useSelect = (props: ISelectProps, emit) => {
handleScroll: () => void
} | null>(null)
const {
isComposing,
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
} = useComposition({
afterComposition: (e) => onInput(e),
})
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
inputRef,
{
@ -341,7 +350,7 @@ export const useSelect = (props: ISelectProps, emit) => {
})
const handleQueryChange = (val: string) => {
if (states.previousQuery === val) {
if (states.previousQuery === val || isComposing.value) {
return
}
states.previousQuery = val
@ -631,12 +640,6 @@ export const useSelect = (props: ISelectProps, emit) => {
}
}
const {
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
} = useInput((e) => onInput(e))
const popperRef = computed(() => {
return tooltipRef.value?.popperRef?.contentRef
})
@ -733,7 +736,12 @@ export const useSelect = (props: ISelectProps, emit) => {
expanded.value = true
return
}
if (states.options.size === 0 || filteredOptionsCount.value === 0) return
if (
states.options.size === 0 ||
states.filteredOptionsCount === 0 ||
isComposing.value
)
return
if (!optionsAllDisabled.value) {
if (direction === 'next') {

View File

@ -27,5 +27,6 @@ export * from './use-cursor'
export * from './use-ordered-children'
export * from './use-size'
export * from './use-focus-controller'
export * from './use-composition'
export * from './use-empty-values'
export * from './use-aria'

View File

@ -0,0 +1,50 @@
import { nextTick, ref } from 'vue'
import { isKorean } from '@element-plus/utils'
interface UseCompositionOptions {
afterComposition: (event: CompositionEvent) => void
emit?: ((event: 'compositionstart', evt: CompositionEvent) => void) &
((event: 'compositionupdate', evt: CompositionEvent) => void) &
((event: 'compositionend', evt: CompositionEvent) => void)
}
export function useComposition({
afterComposition,
emit,
}: UseCompositionOptions) {
const isComposing = ref(false)
const handleCompositionStart = (event: CompositionEvent) => {
emit?.('compositionstart', event)
isComposing.value = true
}
const handleCompositionUpdate = (event: CompositionEvent) => {
emit?.('compositionupdate', event)
const text = (event.target as HTMLInputElement)?.value
const lastCharacter = text[text.length - 1] || ''
isComposing.value = !isKorean(lastCharacter)
}
const handleCompositionEnd = (event: CompositionEvent) => {
emit?.('compositionend', event)
if (isComposing.value) {
isComposing.value = false
nextTick(() => afterComposition(event))
}
}
const handleComposition = (event: CompositionEvent) => {
event.type === 'compositionend'
? handleCompositionEnd(event)
: handleCompositionUpdate(event)
}
return {
isComposing,
handleComposition,
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
}
}