diff --git a/internal/build/src/type-unsafe-stricter.json b/internal/build/src/type-unsafe-stricter.json index dfe78e2166..0b5b4051d5 100644 --- a/internal/build/src/type-unsafe-stricter.json +++ b/internal/build/src/type-unsafe-stricter.json @@ -1,5 +1,4 @@ [ - "packages/components/autocomplete/", "packages/components/cascader-panel/", "packages/components/cascader/", "packages/components/checkbox/", diff --git a/packages/components/alert/src/alert.vue b/packages/components/alert/src/alert.vue index b76284cb26..88876aaa74 100644 --- a/packages/components/alert/src/alert.vue +++ b/packages/components/alert/src/alert.vue @@ -5,12 +5,10 @@ :class="[ns.b(), ns.m(type), ns.is('center', center), ns.is(effect)]" role="alert" > - + +
TypeComponentsMap[props.type] || TypeComponentsMap['info'] ) -const isBigIcon = computed( - () => props.description || { [ns.is('big')]: slots.default } -) + +const iconClass = computed(() => [ + ns.e('icon'), + { [ns.is('big')]: !!props.description || !!slots.default }, +]) + const isBoldTitle = computed( () => props.description || { [ns.is('bold')]: slots.default } ) -// methods const close = (evt: MouseEvent) => { visible.value = false emit('close', evt) diff --git a/packages/components/autocomplete/src/autocomplete.ts b/packages/components/autocomplete/src/autocomplete.ts index 2efe6ff219..bc44fcd6a2 100644 --- a/packages/components/autocomplete/src/autocomplete.ts +++ b/packages/components/autocomplete/src/autocomplete.ts @@ -6,13 +6,18 @@ import { isString, } from '@element-plus/utils' import { useTooltipContentProps } from '@element-plus/components/tooltip' -import { UPDATE_MODEL_EVENT } from '@element-plus/constants' +import { + CHANGE_EVENT, + INPUT_EVENT, + UPDATE_MODEL_EVENT, +} from '@element-plus/constants' + import type { ExtractPropTypes } from 'vue' import type Autocomplete from './autocomplete.vue' import type { Placement } from '@element-plus/components/popper' import type { Awaitable } from '@element-plus/utils' -export type AutocompleteData = { value: string }[] +export type AutocompleteData = Record[] export type AutocompleteFetchSuggestionsCallback = ( data: AutocompleteData ) => void @@ -81,8 +86,8 @@ export type AutocompleteProps = ExtractPropTypes export const autocompleteEmits = { [UPDATE_MODEL_EVENT]: (value: string) => isString(value), - input: (value: string) => isString(value), - change: (value: string) => isString(value), + [INPUT_EVENT]: (value: string) => isString(value), + [CHANGE_EVENT]: (value: string) => isString(value), focus: (evt: FocusEvent) => evt instanceof FocusEvent, blur: (evt: FocusEvent) => evt instanceof FocusEvent, clear: () => true, diff --git a/packages/components/autocomplete/src/autocomplete.vue b/packages/components/autocomplete/src/autocomplete.vue index fcc8b92aee..ddcceab278 100644 --- a/packages/components/autocomplete/src/autocomplete.vue +++ b/packages/components/autocomplete/src/autocomplete.vue @@ -95,19 +95,25 @@ import { nextTick, onMounted, ref, - useAttrs as useCompAttrs, + useAttrs as useRawAttrs, } from 'vue' import { debounce } from 'lodash-unified' import { onClickOutside } from '@vueuse/core' +import { Loading } from '@element-plus/icons-vue' import { useAttrs, useNamespace } from '@element-plus/hooks' import { generateId, isArray, throwError } from '@element-plus/utils' -import { UPDATE_MODEL_EVENT } from '@element-plus/constants' +import { + CHANGE_EVENT, + INPUT_EVENT, + UPDATE_MODEL_EVENT, +} from '@element-plus/constants' import ElInput from '@element-plus/components/input' import ElScrollbar from '@element-plus/components/scrollbar' import ElTooltip from '@element-plus/components/tooltip' import ElIcon from '@element-plus/components/icon' -import { Loading } from '@element-plus/icons-vue' import { autocompleteEmits, autocompleteProps } from './autocomplete' +import type { AutocompleteData } from './autocomplete' + import type { StyleValue } from 'vue' import type { TooltipInstance } from '@element-plus/components/tooltip' import type { InputInstance } from '@element-plus/components/input' @@ -122,73 +128,71 @@ const COMPONENT_NAME = 'ElAutocomplete' const props = defineProps(autocompleteProps) const emit = defineEmits(autocompleteEmits) -const ns = useNamespace('autocomplete') -let isClear = false const attrs = useAttrs() -const compAttrs = useCompAttrs() -const suggestions = ref([]) -const highlightedIndex = ref(-1) -const dropdownWidth = ref('') -const activated = ref(false) -const suggestionDisabled = ref(false) -const loading = ref(false) +const rawAttrs = useRawAttrs() +const ns = useNamespace('autocomplete') + const inputRef = ref() const regionRef = ref() const popperRef = ref() const listboxRef = ref() -const listboxId = computed(() => { - return ns.b(String(generateId())) -}) -const styles = computed(() => compAttrs.style as StyleValue) +let isClear = false +const suggestions = ref([]) +const highlightedIndex = ref(-1) +const dropdownWidth = ref('') +const activated = ref(false) +const suggestionDisabled = ref(false) +const loading = ref(false) + +const listboxId = computed(() => ns.b(String(generateId()))) +const styles = computed(() => rawAttrs.style as StyleValue) + const suggestionVisible = computed(() => { - const isValidData = isArray(suggestions.value) && suggestions.value.length > 0 + const isValidData = suggestions.value.length > 0 return (isValidData || loading.value) && activated.value }) -const suggestionLoading = computed(() => { - return !props.hideLoading && loading.value -}) -const onSuggestionShow = () => { - nextTick(() => { - if (suggestionVisible.value) { - dropdownWidth.value = `${inputRef.value!.$el.offsetWidth}px` - } - }) +const suggestionLoading = computed(() => !props.hideLoading && loading.value) + +const onSuggestionShow = async () => { + await nextTick() + if (suggestionVisible.value) { + dropdownWidth.value = `${inputRef.value!.$el.offsetWidth}px` + } } const getData = async (queryString: string) => { - if (suggestionDisabled.value) { - return - } - loading.value = true - const cb = (suggestionsArg: any[]) => { + if (suggestionDisabled.value) return + + const cb = (suggestionList: AutocompleteData) => { loading.value = false - if (suggestionDisabled.value) { - return - } - if (isArray(suggestionsArg)) { - suggestions.value = suggestionsArg + if (suggestionDisabled.value) return + + if (isArray(suggestionList)) { + suggestions.value = suggestionList highlightedIndex.value = props.highlightFirstItem ? 0 : -1 } else { throwError(COMPONENT_NAME, 'autocomplete suggestions must be an array') } } + + loading.value = true if (isArray(props.fetchSuggestions)) { cb(props.fetchSuggestions) } else { const result = await props.fetchSuggestions(queryString, cb) - if (isArray(result)) { - cb(result) - } + if (isArray(result)) cb(result) } } const debouncedGetData = debounce(getData, props.debounce) -const handleInput = (value: string) => { - const valuePresented = Boolean(value) - emit('input', value) +const handleInput = (value: string) => { + const valuePresented = !!value + + emit(INPUT_EVENT, value) emit(UPDATE_MODEL_EVENT, value) + suggestionDisabled.value = false activated.value ||= isClear && valuePresented @@ -202,9 +206,11 @@ const handleInput = (value: string) => { } debouncedGetData(value) } + const handleChange = (value: string) => { - emit('change', value) + emit(CHANGE_EVENT, value) } + const handleFocus = (evt: FocusEvent) => { activated.value = true emit('focus', evt) @@ -212,16 +218,19 @@ const handleFocus = (evt: FocusEvent) => { debouncedGetData(String(props.modelValue)) } } + const handleBlur = (evt: FocusEvent) => { emit('blur', evt) } + const handleClear = () => { activated.value = false isClear = true emit(UPDATE_MODEL_EVENT, '') emit('clear') } -const handleKeyEnter = () => { + +const handleKeyEnter = async () => { if ( suggestionVisible.value && highlightedIndex.value >= 0 && @@ -230,19 +239,20 @@ const handleKeyEnter = () => { handleSelect(suggestions.value[highlightedIndex.value]) } else if (props.selectWhenUnmatched) { emit('select', { value: props.modelValue }) - nextTick(() => { - suggestions.value = [] - highlightedIndex.value = -1 - }) + await nextTick() + suggestions.value = [] + highlightedIndex.value = -1 } } -const handleKeyEscape = (e) => { + +const handleKeyEscape = (evt: Event) => { if (suggestionVisible.value) { - e.preventDefault() - e.stopPropagation() + evt.preventDefault() + evt.stopPropagation() close() } } + const close = () => { activated.value = false } @@ -251,23 +261,23 @@ const focus = () => { inputRef.value?.focus() } -const handleSelect = (item: any) => { - emit('input', item[props.valueKey]) +const handleSelect = async (item: any) => { + emit(INPUT_EVENT, item[props.valueKey]) emit(UPDATE_MODEL_EVENT, item[props.valueKey]) emit('select', item) - nextTick(() => { - suggestions.value = [] - highlightedIndex.value = -1 - }) + await nextTick() + suggestions.value = [] + highlightedIndex.value = -1 } + const highlight = (index: number) => { - if (!suggestionVisible.value || loading.value) { - return - } + if (!suggestionVisible.value || loading.value) return + if (index < 0) { highlightedIndex.value = -1 return } + if (index >= suggestions.value.length) { index = suggestions.value.length - 1 } diff --git a/packages/components/avatar/src/avatar.ts b/packages/components/avatar/src/avatar.ts index e9d78014b8..3b50045ee9 100644 --- a/packages/components/avatar/src/avatar.ts +++ b/packages/components/avatar/src/avatar.ts @@ -1,4 +1,9 @@ -import { buildProps, definePropType, iconPropType } from '@element-plus/utils' +import { + buildProps, + definePropType, + iconPropType, + isNumber, +} from '@element-plus/utils' import { componentSizes } from '@element-plus/constants' import type { ExtractPropTypes } from 'vue' import type { ObjectFitProperty } from 'csstype' @@ -9,7 +14,7 @@ export const avatarProps = buildProps({ type: [Number, String], values: componentSizes, default: '', - validator: (val: unknown): val is number => typeof val === 'number', + validator: (val: unknown): val is number => isNumber(val), }, shape: { type: String, diff --git a/packages/components/backtop/src/backtop.ts b/packages/components/backtop/src/backtop.ts index f38499e128..c83fe0559b 100644 --- a/packages/components/backtop/src/backtop.ts +++ b/packages/components/backtop/src/backtop.ts @@ -1,4 +1,5 @@ import type { ExtractPropTypes } from 'vue' +import type Backtop from './backtop.vue' export const backtopProps = { visibilityHeight: { @@ -24,3 +25,5 @@ export const backtopEmits = { click: (evt: MouseEvent) => evt instanceof MouseEvent, } export type BacktopEmits = typeof backtopEmits + +export type BacktopInstance = InstanceType diff --git a/packages/components/backtop/src/backtop.vue b/packages/components/backtop/src/backtop.vue index 0c5eae106b..fc1ebd9c6b 100644 --- a/packages/components/backtop/src/backtop.vue +++ b/packages/components/backtop/src/backtop.vue @@ -20,7 +20,6 @@ import { ElIcon } from '@element-plus/components/icon' import { easeInOutCubic, throwError } from '@element-plus/utils' import { CaretTop } from '@element-plus/icons-vue' import { useNamespace } from '@element-plus/hooks' - import { backtopEmits, backtopProps } from './backtop' const COMPONENT_NAME = 'ElBacktop' @@ -43,6 +42,8 @@ const backTopStyle = computed(() => ({ })) const scrollToTop = () => { + // TODO: use https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo, with behavior: 'smooth' + if (!el.value) return const beginTime = Date.now() const beginValue = el.value.scrollTop @@ -68,6 +69,7 @@ const handleClick = (event: MouseEvent) => { const handleScrollThrottled = useThrottleFn(handleScroll, 300) +useEventListener(container, 'scroll', handleScrollThrottled) onMounted(() => { container.value = document el.value = document.documentElement @@ -79,7 +81,5 @@ onMounted(() => { } container.value = el.value } - - useEventListener(container, 'scroll', handleScrollThrottled) }) diff --git a/packages/components/breadcrumb/src/breadcrumb-item.vue b/packages/components/breadcrumb/src/breadcrumb-item.vue index f3e34dbee3..cd7ca55d29 100644 --- a/packages/components/breadcrumb/src/breadcrumb-item.vue +++ b/packages/components/breadcrumb/src/breadcrumb-item.vue @@ -18,14 +18,13 @@