This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
<script setup lang="ts">
import type { TextareaConfirmType, TextareaOnBlurEvent, TextareaOnConfirmEvent, TextareaOnFocusEvent, TextareaOnInputEvent } from '@uni-helper/uni-app-types'
import type { CSSProperties } from 'vue'
import { computed, defineComponent, nextTick, toRef } from 'vue'
import { BLUR_EVENT, CHANGE_EVENT, CONFIRM_EVENT, FOCUS_EVENT, INPUT_EVENT, PREFIX, UPDATE_MODEL_EVENT } from '../_constants'
import { getMainClass, isH5, isMpAlipay, pxCheck } from '../_utils'
import { useTranslate } from '../../locale'
import { useFormDisabled } from '../form/form'
import type { InputTarget } from '../input/type'
import { textareaEmits, textareaProps } from './textarea'
const props = defineProps(textareaProps)
const emit = defineEmits(textareaEmits)
const formDisabled = useFormDisabled(toRef(props, 'disabled'))
function stringModelValue() {
if (props.modelValue == null)
return ''
return String(props.modelValue)
}
const innerValue = computed<string>(() => {
return stringModelValue()
})
const classes = computed(() => {
return getMainClass(props, componentName, {
[`${componentName}--disabled`]: formDisabled.value,
})
})
const textareaClasses = computed(() => {
return [props.textareaClass, {
'nut-textarea__ali': isMpAlipay,
}]
})
const textareaStyles = computed(() => {
const style: CSSProperties = {
textAlign: props.textAlign,
}
if (typeof props.autosize === 'object') {
const { minHeight, maxHeight } = props.autosize
if (minHeight != null)
style.minHeight = pxCheck(minHeight)
if (maxHeight != null)
style.maxHeight = pxCheck(maxHeight)
}
return [props.textareaStyle, style]
})
const innerMaxLength = computed(() => {
if (props.maxLength == null)
return -1
return Number(props.maxLength)
})
function updateValue(value: string, evt: any) {
if (innerMaxLength.value > 0 && value.length > innerMaxLength.value)
value = value.slice(0, innerMaxLength.value)
emit(UPDATE_MODEL_EVENT, value, evt)
emit(CHANGE_EVENT, value, evt)
}
function _onInput(evt: TextareaOnInputEvent) {
updateValue(evt.detail.value, evt)
nextTick(() => {
emit(INPUT_EVENT, innerValue.value, evt)
})
}
function handleInput(evt: any) {
if (isH5) {
const target = evt.target as InputTarget
if (!target.composing)
_onInput(evt)
}
else {
_onInput(evt)
}
}
function handleFocus(evt: TextareaOnFocusEvent) {
if (formDisabled.value || props.readonly)
return
emit(FOCUS_EVENT, evt)
}
function handleBlur(evt: TextareaOnBlurEvent) {
if (formDisabled.value || props.readonly)
return
updateValue(evt.detail.value, evt)
emit(BLUR_EVENT, evt)
}
function handleConfirm(evt: TextareaOnConfirmEvent) {
emit(CONFIRM_EVENT, evt)
}
function startComposing(evt: any) {
if (isH5) {
const target = evt.target as InputTarget
target.composing = true
}
}
function endComposing(evt: any) {
if (isH5) {
const target = evt.target as InputTarget
if (target.composing) {
target.composing = false
target.dispatchEvent(new Event('input'))
}
}
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-textarea`
const { translate } = useTranslate(componentName)
export default defineComponent({
name: componentName,
inheritAttrs: false,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="classes" :style="props.customStyle">
<textarea
v-if="props.readonly"
class="nut-textarea__textarea nut-textarea__textarea__readonly"
:class="textareaClasses"
:style="textareaStyles"
:value="innerValue"
:rows="props.rows"
:disabled="true"
:show-count="false"
:placeholder="props.placeholder || translate('placeholder')"
:placeholder-style="props.placeholderStyle"
:placeholder-class="props.placeholderClass"
:auto-height="!!props.autosize"
:disable-default-padding="props.disableDefaultPadding"
/>
<textarea
v-else
class="nut-textarea__textarea"
:class="textareaClasses"
:style="textareaStyles"
:value="innerValue"
:rows="props.rows"
:disabled="formDisabled || props.readonly"
:show-count="false"
:maxlength="innerMaxLength"
:placeholder="props.placeholder || translate('placeholder')"
:placeholder-style="props.placeholderStyle"
:placeholder-class="props.placeholderClass"
:auto-focus="props.autofocus"
:auto-height="!!props.autosize"
:cursor-spacing="props.cursorSpacing"
:cursor="props.cursor"
:show-confirm-bar="props.showConfirmBar"
:selection-start="props.selectionStart"
:selection-end="props.selectionEnd"
:adjust-position="props.adjustPosition"
:hold-keyboard="props.holdKeyboard"
:disable-default-padding="props.disableDefaultPadding"
:confirm-type="props.confirmType as TextareaConfirmType"
:confirm-hold="props.confirmHold"
:adjust-keyboard-to="props.adjustKeyboardTo"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="endComposing"
@compositionstart="startComposing"
@compositionend="endComposing"
@confirm="handleConfirm"
/>
<view v-if="props.limitShow && innerMaxLength > 0" class="nut-textarea__limit">
{{ innerValue.length }}/{{ innerMaxLength }}
</view>
</view>
</template>
<style lang="scss">
@import './index';
</style>