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

View File

@ -179,11 +179,11 @@ import {
ValidateComponentsMap, ValidateComponentsMap,
debugWarn, debugWarn,
isClient, isClient,
isKorean,
isObject, isObject,
} from '@element-plus/utils' } from '@element-plus/utils'
import { import {
useAttrs, useAttrs,
useComposition,
useCursor, useCursor,
useDeprecated, useDeprecated,
useFocusController, useFocusController,
@ -256,7 +256,6 @@ const input = shallowRef<HTMLInputElement>()
const textarea = shallowRef<HTMLTextAreaElement>() const textarea = shallowRef<HTMLTextAreaElement>()
const hovering = ref(false) const hovering = ref(false)
const isComposing = ref(false)
const passwordVisible = ref(false) const passwordVisible = ref(false)
const countStyle = ref<StyleValue>() const countStyle = ref<StyleValue>()
const textareaCalcStyle = shallowRef(props.inputStyle) const textareaCalcStyle = shallowRef(props.inputStyle)
@ -435,25 +434,12 @@ const handleChange = (event: Event) => {
emit('change', (event.target as TargetElement).value) emit('change', (event.target as TargetElement).value)
} }
const handleCompositionStart = (event: CompositionEvent) => { const {
emit('compositionstart', event) isComposing,
isComposing.value = true handleCompositionStart,
} handleCompositionUpdate,
handleCompositionEnd,
const handleCompositionUpdate = (event: CompositionEvent) => { } = useComposition({ emit, afterComposition: handleInput })
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 handlePasswordVisible = () => { const handlePasswordVisible = () => {
passwordVisible.value = !passwordVisible.value 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' } from 'lodash-unified'
import { useResizeObserver } from '@vueuse/core' import { useResizeObserver } from '@vueuse/core'
import { import {
useComposition,
useEmptyValues, useEmptyValues,
useFocusController, useFocusController,
useLocale, useLocale,
@ -40,7 +41,6 @@ import {
import { ArrowDown } from '@element-plus/icons-vue' import { ArrowDown } from '@element-plus/icons-vue'
import { useAllowCreate } from './useAllowCreate' import { useAllowCreate } from './useAllowCreate'
import { useInput } from './useInput'
import { useProps } from './useProps' import { useProps } from './useProps'
import type ElTooltip from '@element-plus/components/tooltip' import type ElTooltip from '@element-plus/components/tooltip'
@ -94,6 +94,15 @@ const useSelect = (props: ISelectV2Props, emit) => {
const tagMenuRef = ref<HTMLElement>(null) const tagMenuRef = ref<HTMLElement>(null)
const collapseItemRef = ref<HTMLElement>(null) const collapseItemRef = ref<HTMLElement>(null)
const {
isComposing,
handleCompositionStart,
handleCompositionEnd,
handleCompositionUpdate,
} = useComposition({
afterComposition: (e) => onInput(e),
})
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController( const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
inputRef, inputRef,
{ {
@ -356,11 +365,6 @@ const useSelect = (props: ISelectV2Props, emit) => {
selectNewOption, selectNewOption,
clearAllNewOption, clearAllNewOption,
} = useAllowCreate(props, states) } = useAllowCreate(props, states)
const {
handleCompositionStart,
handleCompositionUpdate,
handleCompositionEnd,
} = useInput((e) => onInput(e))
// methods // methods
const toggleMenu = () => { const toggleMenu = () => {
@ -385,7 +389,7 @@ const useSelect = (props: ISelectV2Props, emit) => {
const debouncedOnInputChange = lodashDebounce(onInputChange, debounce.value) const debouncedOnInputChange = lodashDebounce(onInputChange, debounce.value)
const handleQueryChange = (val: string) => { const handleQueryChange = (val: string) => {
if (states.previousQuery === val) { if (states.previousQuery === val || isComposing.value) {
return return
} }
states.previousQuery = val states.previousQuery = val
@ -619,7 +623,8 @@ const useSelect = (props: ISelectV2Props, emit) => {
!['forward', 'backward'].includes(direction) || !['forward', 'backward'].includes(direction) ||
selectDisabled.value || selectDisabled.value ||
options.length <= 0 || options.length <= 0 ||
optionsAllDisabled.value optionsAllDisabled.value ||
isComposing.value
) { ) {
return return
} }

View File

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

View File

@ -27,5 +27,6 @@ export * from './use-cursor'
export * from './use-ordered-children' export * from './use-ordered-children'
export * from './use-size' export * from './use-size'
export * from './use-focus-controller' export * from './use-focus-controller'
export * from './use-composition'
export * from './use-empty-values' export * from './use-empty-values'
export * from './use-aria' 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,
}
}