|
@@ -15,18 +15,19 @@
|
|
<slot name="prepend" />
|
|
<slot name="prepend" />
|
|
</div>
|
|
</div>
|
|
|
|
|
|
- <div :class="wrapperKls">
|
|
|
|
|
|
+ <div ref="wrapperRef" :class="wrapperKls">
|
|
<!-- prefix slot -->
|
|
<!-- prefix slot -->
|
|
<span v-if="$slots.prefix || prefixIcon" :class="nsInput.e('prefix')">
|
|
<span v-if="$slots.prefix || prefixIcon" :class="nsInput.e('prefix')">
|
|
- <span :class="nsInput.e('prefix-inner')" @click="focus">
|
|
|
|
|
|
+ <span :class="nsInput.e('prefix-inner')">
|
|
<slot name="prefix" />
|
|
<slot name="prefix" />
|
|
<kk-icon v-if="prefixIcon" :class="nsInput.e('icon')">
|
|
<kk-icon v-if="prefixIcon" :class="nsInput.e('icon')">
|
|
<component :is="prefixIcon" />
|
|
<component :is="prefixIcon" />
|
|
</kk-icon>
|
|
</kk-icon>
|
|
</span>
|
|
</span>
|
|
</span>
|
|
</span>
|
|
- <!-- :id="inputId" -->
|
|
|
|
|
|
+
|
|
<input
|
|
<input
|
|
|
|
+ :id="inputId"
|
|
ref="input"
|
|
ref="input"
|
|
:class="nsInput.e('inner')"
|
|
:class="nsInput.e('inner')"
|
|
v-bind="attrs"
|
|
v-bind="attrs"
|
|
@@ -53,7 +54,7 @@
|
|
|
|
|
|
<!-- suffix slot -->
|
|
<!-- suffix slot -->
|
|
<span v-if="suffixVisible" :class="nsInput.e('suffix')">
|
|
<span v-if="suffixVisible" :class="nsInput.e('suffix')">
|
|
- <span :class="nsInput.e('suffix-inner')" @click="focus">
|
|
|
|
|
|
+ <span :class="nsInput.e('suffix-inner')">
|
|
<template
|
|
<template
|
|
v-if="!showClear || !showPwdVisible || !isWordLimitVisible"
|
|
v-if="!showClear || !showPwdVisible || !isWordLimitVisible"
|
|
>
|
|
>
|
|
@@ -104,8 +105,8 @@
|
|
|
|
|
|
<!-- textarea -->
|
|
<!-- textarea -->
|
|
<template v-else>
|
|
<template v-else>
|
|
- <!-- :id="inputId" -->
|
|
|
|
<textarea
|
|
<textarea
|
|
|
|
+ :id="inputId"
|
|
ref="textarea"
|
|
ref="textarea"
|
|
:class="nsTextarea.e('inner')"
|
|
:class="nsTextarea.e('inner')"
|
|
v-bind="attrs"
|
|
v-bind="attrs"
|
|
@@ -149,7 +150,7 @@ import {
|
|
useSlots,
|
|
useSlots,
|
|
watch,
|
|
watch,
|
|
} from 'vue'
|
|
} from 'vue'
|
|
-import { isClient, useResizeObserver } from '@vueuse/core'
|
|
|
|
|
|
+import { useResizeObserver } from '@vueuse/core'
|
|
import { isNil } from 'lodash-unified'
|
|
import { isNil } from 'lodash-unified'
|
|
import { KkIcon } from '@kankan-components/components/basic/icon'
|
|
import { KkIcon } from '@kankan-components/components/basic/icon'
|
|
import {
|
|
import {
|
|
@@ -157,22 +158,25 @@ import {
|
|
Hide as IconHide,
|
|
Hide as IconHide,
|
|
View as IconView,
|
|
View as IconView,
|
|
} from '@kankan-components/icons-vue'
|
|
} from '@kankan-components/icons-vue'
|
|
-
|
|
|
|
|
|
+import {
|
|
|
|
+ useFormDisabled,
|
|
|
|
+ useFormItem,
|
|
|
|
+ useFormItemInputId,
|
|
|
|
+ useFormSize,
|
|
|
|
+} from '@kankan-components/components/basic/form'
|
|
import {
|
|
import {
|
|
NOOP,
|
|
NOOP,
|
|
- // ValidateComponentsMap,
|
|
|
|
|
|
+ ValidateComponentsMap,
|
|
debugWarn,
|
|
debugWarn,
|
|
|
|
+ isClient,
|
|
isKorean,
|
|
isKorean,
|
|
isObject,
|
|
isObject,
|
|
} from '@kankan-components/utils'
|
|
} from '@kankan-components/utils'
|
|
import {
|
|
import {
|
|
useAttrs,
|
|
useAttrs,
|
|
useCursor,
|
|
useCursor,
|
|
- useDisabled,
|
|
|
|
- // useFormItem,
|
|
|
|
- // useFormItemInputId,
|
|
|
|
|
|
+ useFocusController,
|
|
useNamespace,
|
|
useNamespace,
|
|
- useSize,
|
|
|
|
} from '@kankan-components/hooks'
|
|
} from '@kankan-components/hooks'
|
|
import { UPDATE_MODEL_EVENT } from '@kankan-components/constants'
|
|
import { UPDATE_MODEL_EVENT } from '@kankan-components/constants'
|
|
import { calcTextareaHeight } from './utils'
|
|
import { calcTextareaHeight } from './utils'
|
|
@@ -182,7 +186,7 @@ import type { StyleValue } from 'vue'
|
|
type TargetElement = HTMLInputElement | HTMLTextAreaElement
|
|
type TargetElement = HTMLInputElement | HTMLTextAreaElement
|
|
|
|
|
|
defineOptions({
|
|
defineOptions({
|
|
- name: 'KkInput',
|
|
|
|
|
|
+ name: 'ElInput',
|
|
inheritAttrs: false,
|
|
inheritAttrs: false,
|
|
})
|
|
})
|
|
const props = defineProps(inputProps)
|
|
const props = defineProps(inputProps)
|
|
@@ -221,7 +225,7 @@ const containerKls = computed(() => [
|
|
|
|
|
|
const wrapperKls = computed(() => [
|
|
const wrapperKls = computed(() => [
|
|
nsInput.e('wrapper'),
|
|
nsInput.e('wrapper'),
|
|
- nsInput.is('focus', focused.value),
|
|
|
|
|
|
+ nsInput.is('focus', isFocused.value),
|
|
])
|
|
])
|
|
|
|
|
|
const attrs = useAttrs({
|
|
const attrs = useAttrs({
|
|
@@ -229,19 +233,18 @@ const attrs = useAttrs({
|
|
return Object.keys(containerAttrs.value)
|
|
return Object.keys(containerAttrs.value)
|
|
}),
|
|
}),
|
|
})
|
|
})
|
|
-// const { form, formItem } = useFormItem()
|
|
|
|
-// const { inputId } = useFormItemInputId(props, {
|
|
|
|
-// formItemContext: formItem,
|
|
|
|
-// })
|
|
|
|
-const inputSize = useSize()
|
|
|
|
-const inputDisabled = useDisabled()
|
|
|
|
|
|
+const { form, formItem } = useFormItem()
|
|
|
|
+const { inputId } = useFormItemInputId(props, {
|
|
|
|
+ formItemContext: formItem,
|
|
|
|
+})
|
|
|
|
+const inputSize = useFormSize()
|
|
|
|
+const inputDisabled = useFormDisabled()
|
|
const nsInput = useNamespace('input')
|
|
const nsInput = useNamespace('input')
|
|
const nsTextarea = useNamespace('textarea')
|
|
const nsTextarea = useNamespace('textarea')
|
|
|
|
|
|
const input = shallowRef<HTMLInputElement>()
|
|
const input = shallowRef<HTMLInputElement>()
|
|
const textarea = shallowRef<HTMLTextAreaElement>()
|
|
const textarea = shallowRef<HTMLTextAreaElement>()
|
|
|
|
|
|
-const focused = ref(false)
|
|
|
|
const hovering = ref(false)
|
|
const hovering = ref(false)
|
|
const isComposing = ref(false)
|
|
const isComposing = ref(false)
|
|
const passwordVisible = ref(false)
|
|
const passwordVisible = ref(false)
|
|
@@ -250,14 +253,22 @@ const textareaCalcStyle = shallowRef(props.inputStyle)
|
|
|
|
|
|
const _ref = computed(() => input.value || textarea.value)
|
|
const _ref = computed(() => input.value || textarea.value)
|
|
|
|
|
|
-// const needStatusIcon = computed(() => form?.statusIcon ?? false)
|
|
|
|
-// const validateState = computed(() => formItem?.validateState || '')
|
|
|
|
-// const validateIcon = computed(
|
|
|
|
-// () => validateState.value && ValidateComponentsMap[validateState.value]
|
|
|
|
-// )
|
|
|
|
-const needStatusIcon = computed(() => false)
|
|
|
|
-const validateState = computed(() => '')
|
|
|
|
-const validateIcon = computed(() => validateState.value)
|
|
|
|
|
|
+const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
|
|
|
|
+ _ref,
|
|
|
|
+ {
|
|
|
|
+ afterBlur() {
|
|
|
|
+ if (props.validateEvent) {
|
|
|
|
+ formItem?.validate?.('blur').catch((err) => debugWarn(err))
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+const needStatusIcon = computed(() => form?.statusIcon ?? false)
|
|
|
|
+const validateState = computed(() => formItem?.validateState || '')
|
|
|
|
+const validateIcon = computed(
|
|
|
|
+ () => validateState.value && ValidateComponentsMap[validateState.value]
|
|
|
|
+)
|
|
const passwordIcon = computed(() =>
|
|
const passwordIcon = computed(() =>
|
|
passwordVisible.value ? IconView : IconHide
|
|
passwordVisible.value ? IconView : IconHide
|
|
)
|
|
)
|
|
@@ -279,7 +290,7 @@ const showClear = computed(
|
|
!inputDisabled.value &&
|
|
!inputDisabled.value &&
|
|
!props.readonly &&
|
|
!props.readonly &&
|
|
!!nativeInputValue.value &&
|
|
!!nativeInputValue.value &&
|
|
- (focused.value || hovering.value)
|
|
|
|
|
|
+ (isFocused.value || hovering.value)
|
|
)
|
|
)
|
|
const showPwdVisible = computed(
|
|
const showPwdVisible = computed(
|
|
() =>
|
|
() =>
|
|
@@ -287,7 +298,7 @@ const showPwdVisible = computed(
|
|
!inputDisabled.value &&
|
|
!inputDisabled.value &&
|
|
!props.readonly &&
|
|
!props.readonly &&
|
|
!!nativeInputValue.value &&
|
|
!!nativeInputValue.value &&
|
|
- (!!nativeInputValue.value || focused.value)
|
|
|
|
|
|
+ (!!nativeInputValue.value || isFocused.value)
|
|
)
|
|
)
|
|
const isWordLimitVisible = computed(
|
|
const isWordLimitVisible = computed(
|
|
() =>
|
|
() =>
|
|
@@ -298,7 +309,7 @@ const isWordLimitVisible = computed(
|
|
!props.readonly &&
|
|
!props.readonly &&
|
|
!props.showPassword
|
|
!props.showPassword
|
|
)
|
|
)
|
|
-const textLength = computed(() => Array.from(nativeInputValue.value).length)
|
|
|
|
|
|
+const textLength = computed(() => nativeInputValue.value.length)
|
|
const inputExceed = computed(
|
|
const inputExceed = computed(
|
|
() =>
|
|
() =>
|
|
// show exceed style if length of initial value greater then maxlength
|
|
// show exceed style if length of initial value greater then maxlength
|
|
@@ -318,6 +329,7 @@ const suffixVisible = computed(
|
|
const [recordCursor, setCursor] = useCursor(input)
|
|
const [recordCursor, setCursor] = useCursor(input)
|
|
|
|
|
|
useResizeObserver(textarea, (entries) => {
|
|
useResizeObserver(textarea, (entries) => {
|
|
|
|
+ onceInitSizeTextarea()
|
|
if (!isWordLimitVisible.value || props.resize !== 'both') return
|
|
if (!isWordLimitVisible.value || props.resize !== 'both') return
|
|
const entry = entries[0]
|
|
const entry = entries[0]
|
|
const { width } = entry.contentRect
|
|
const { width } = entry.contentRect
|
|
@@ -330,25 +342,55 @@ useResizeObserver(textarea, (entries) => {
|
|
const resizeTextarea = () => {
|
|
const resizeTextarea = () => {
|
|
const { type, autosize } = props
|
|
const { type, autosize } = props
|
|
|
|
|
|
- if (!isClient || type !== 'textarea') return
|
|
|
|
|
|
+ if (!isClient || type !== 'textarea' || !textarea.value) return
|
|
|
|
|
|
if (autosize) {
|
|
if (autosize) {
|
|
const minRows = isObject(autosize) ? autosize.minRows : undefined
|
|
const minRows = isObject(autosize) ? autosize.minRows : undefined
|
|
const maxRows = isObject(autosize) ? autosize.maxRows : undefined
|
|
const maxRows = isObject(autosize) ? autosize.maxRows : undefined
|
|
|
|
+ const textareaStyle = calcTextareaHeight(textarea.value, minRows, maxRows)
|
|
|
|
+
|
|
|
|
+ // If the scrollbar is displayed, the height of the textarea needs more space than the calculated height.
|
|
|
|
+ // If set textarea height in this case, the scrollbar will not hide.
|
|
|
|
+ // So we need to hide scrollbar first, and reset it in next tick.
|
|
|
|
+ // see https://github.com/element-plus/element-plus/issues/8825
|
|
textareaCalcStyle.value = {
|
|
textareaCalcStyle.value = {
|
|
- ...calcTextareaHeight(textarea.value!, minRows, maxRows),
|
|
|
|
|
|
+ overflowY: 'hidden',
|
|
|
|
+ ...textareaStyle,
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ // NOTE: Force repaint to make sure the style set above is applied.
|
|
|
|
+ textarea.value!.offsetHeight
|
|
|
|
+ textareaCalcStyle.value = textareaStyle
|
|
|
|
+ })
|
|
} else {
|
|
} else {
|
|
textareaCalcStyle.value = {
|
|
textareaCalcStyle.value = {
|
|
- minHeight: calcTextareaHeight(textarea.value!).minHeight,
|
|
|
|
|
|
+ minHeight: calcTextareaHeight(textarea.value).minHeight,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const createOnceInitResize = (resizeTextarea: () => void) => {
|
|
|
|
+ let isInit = false
|
|
|
|
+ return () => {
|
|
|
|
+ if (isInit || !props.autosize) return
|
|
|
|
+ const isElHidden = textarea.value?.offsetParent === null
|
|
|
|
+ if (!isElHidden) {
|
|
|
|
+ resizeTextarea()
|
|
|
|
+ isInit = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+// fix: https://github.com/element-plus/element-plus/issues/12074
|
|
|
|
+const onceInitSizeTextarea = createOnceInitResize(resizeTextarea)
|
|
|
|
|
|
const setNativeInputValue = () => {
|
|
const setNativeInputValue = () => {
|
|
const input = _ref.value
|
|
const input = _ref.value
|
|
- if (!input || input.value === nativeInputValue.value) return
|
|
|
|
- input.value = nativeInputValue.value
|
|
|
|
|
|
+ const formatterValue = props.formatter
|
|
|
|
+ ? props.formatter(nativeInputValue.value)
|
|
|
|
+ : nativeInputValue.value
|
|
|
|
+ if (!input || input.value === formatterValue) return
|
|
|
|
+ input.value = formatterValue
|
|
}
|
|
}
|
|
|
|
|
|
const handleInput = async (event: Event) => {
|
|
const handleInput = async (event: Event) => {
|
|
@@ -358,7 +400,6 @@ const handleInput = async (event: Event) => {
|
|
|
|
|
|
if (props.formatter) {
|
|
if (props.formatter) {
|
|
value = props.parser ? props.parser(value) : value
|
|
value = props.parser ? props.parser(value) : value
|
|
- value = props.formatter(value)
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// should not emit input during composition
|
|
// should not emit input during composition
|
|
@@ -419,19 +460,6 @@ const focus = async () => {
|
|
|
|
|
|
const blur = () => _ref.value?.blur()
|
|
const blur = () => _ref.value?.blur()
|
|
|
|
|
|
-const handleFocus = (event: FocusEvent) => {
|
|
|
|
- focused.value = true
|
|
|
|
- emit('focus', event)
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-const handleBlur = (event: FocusEvent) => {
|
|
|
|
- focused.value = false
|
|
|
|
- emit('blur', event)
|
|
|
|
- // if (props.validateEvent) {
|
|
|
|
- // formItem?.validate?.('blur').catch((err) => debugWarn(err))
|
|
|
|
- // }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
const handleMouseLeave = (evt: MouseEvent) => {
|
|
const handleMouseLeave = (evt: MouseEvent) => {
|
|
hovering.value = false
|
|
hovering.value = false
|
|
emit('mouseleave', evt)
|
|
emit('mouseleave', evt)
|
|
@@ -461,9 +489,9 @@ watch(
|
|
() => props.modelValue,
|
|
() => props.modelValue,
|
|
() => {
|
|
() => {
|
|
nextTick(() => resizeTextarea())
|
|
nextTick(() => resizeTextarea())
|
|
- // if (props.validateEvent) {
|
|
|
|
- // formItem?.validate?.('change').catch((err) => debugWarn(err))
|
|
|
|
- // }
|
|
|
|
|
|
+ if (props.validateEvent) {
|
|
|
|
+ formItem?.validate?.('change').catch((err) => debugWarn(err))
|
|
|
|
+ }
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
@@ -487,7 +515,7 @@ watch(
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
if (!props.formatter && props.parser) {
|
|
if (!props.formatter && props.parser) {
|
|
debugWarn(
|
|
debugWarn(
|
|
- 'KkInput',
|
|
|
|
|
|
+ 'ElInput',
|
|
'If you set the parser, you also need to set the formatter.'
|
|
'If you set the parser, you also need to set the formatter.'
|
|
)
|
|
)
|
|
}
|
|
}
|