init
This commit is contained in:
62
uni_modules/nutui-uni/components/textarea/index.scss
Normal file
62
uni_modules/nutui-uni/components/textarea/index.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
.nut-theme-dark {
|
||||
.nut-textarea {
|
||||
background: $dark-background;
|
||||
|
||||
&__textarea {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-textarea {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 10px 25px;
|
||||
font-size: $textarea-font;
|
||||
background: $white;
|
||||
|
||||
&--disabled {
|
||||
.nut-textarea__textarea,
|
||||
|
||||
// TODO PR原版类名错误
|
||||
.nut-textarea__limit {
|
||||
color: $textarea-disabled-color !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&__limit {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
bottom: 12px;
|
||||
font-size: $textarea-font;
|
||||
color: $textarea-limit-color;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: $textarea-font;
|
||||
line-height: 20px;
|
||||
color: $textarea-text-color;
|
||||
text-align: left;
|
||||
resize: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
.taro-textarea {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__ali {
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
2
uni_modules/nutui-uni/components/textarea/index.ts
Normal file
2
uni_modules/nutui-uni/components/textarea/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './textarea'
|
||||
export * from './type'
|
||||
133
uni_modules/nutui-uni/components/textarea/textarea.ts
Normal file
133
uni_modules/nutui-uni/components/textarea/textarea.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { TextareaOnBlurEvent, TextareaOnConfirmEvent, TextareaOnFocusEvent, TextareaOnInputEvent } from '@uni-helper/uni-app-types'
|
||||
import type { ExtractPropTypes, PropType, StyleValue } from 'vue'
|
||||
import { BLUR_EVENT, CHANGE_EVENT, CONFIRM_EVENT, FOCUS_EVENT, INPUT_EVENT, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import type { ClassType } from '../_utils'
|
||||
import { commonProps, isString, makeNumberProp, makeStringProp, nullableBooleanProp, truthProp } from '../_utils'
|
||||
import type { InputAlignType } from '../input'
|
||||
import type { TextareaAdjustKeyboardTo, TextareaAutosizeObject, TextareaConfirmType } from './type'
|
||||
|
||||
export const textareaProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 输入值,支持双向绑定
|
||||
*/
|
||||
modelValue: String,
|
||||
/**
|
||||
* @description 文本位置,可选值left,center,right
|
||||
*/
|
||||
textAlign: String as PropType<InputAlignType>,
|
||||
/**
|
||||
* @description textarea是否展示输入字符。须配合max-length使用
|
||||
*/
|
||||
limitShow: Boolean,
|
||||
/**
|
||||
* @description 限制最长输入字符
|
||||
*/
|
||||
maxLength: [String, Number],
|
||||
/**
|
||||
* @description textarea的高度,优先级高于autosize属性 仅支持 H5
|
||||
*/
|
||||
rows: [String, Number],
|
||||
/**
|
||||
* @description 文本域自定义类名
|
||||
*/
|
||||
textareaClass: {
|
||||
type: [String, Object, Array] as PropType<ClassType>,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description 文本域自定义样式
|
||||
*/
|
||||
textareaStyle: {
|
||||
type: [String, Object, Array] as PropType<StyleValue>,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description 设置占位提示文字
|
||||
*/
|
||||
placeholder: String,
|
||||
/**
|
||||
* @description 指定 placeholder 的样式
|
||||
*/
|
||||
placeholderStyle: makeStringProp(''),
|
||||
/**
|
||||
* @description 指定 placeholder 的样式类
|
||||
*/
|
||||
placeholderClass: makeStringProp('textarea-placeholder'),
|
||||
/**
|
||||
* @description 只读属性
|
||||
*/
|
||||
readonly: Boolean,
|
||||
/**
|
||||
* @description 禁用属性
|
||||
*/
|
||||
disabled: nullableBooleanProp,
|
||||
/**
|
||||
* @description 是否自适应内容高度,也可传入对象
|
||||
*/
|
||||
autosize: {
|
||||
type: [Boolean, Object] as PropType<boolean | TextareaAutosizeObject>,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* @description 自动获取焦点
|
||||
*/
|
||||
autofocus: Boolean,
|
||||
/**
|
||||
* @description 指定光标与键盘的距离。取textarea距离底部的距离和cursor-spacing指定的距离的最小值作为光标与键盘的距离
|
||||
*/
|
||||
cursorSpacing: makeNumberProp(0),
|
||||
/**
|
||||
* @description 指定focus时的光标位置
|
||||
*/
|
||||
cursor: makeNumberProp(-1),
|
||||
/**
|
||||
* @description 是否显示键盘上方带有”完成“按钮那一栏
|
||||
*/
|
||||
showConfirmBar: truthProp,
|
||||
/**
|
||||
* @description 光标起始位置,自动聚集时有效,需与selection-end搭配使用
|
||||
*/
|
||||
selectionStart: makeNumberProp(-1),
|
||||
/**
|
||||
* @description 光标结束位置,自动聚集时有效,需与selection-start搭配使用
|
||||
*/
|
||||
selectionEnd: makeNumberProp(-1),
|
||||
/**
|
||||
* @description 键盘弹起时,是否自动上推页面
|
||||
*/
|
||||
adjustPosition: truthProp,
|
||||
/**
|
||||
* @description focus时,点击页面的时候不收起键盘
|
||||
*/
|
||||
holdKeyboard: Boolean,
|
||||
/**
|
||||
* @description 是否去掉 iOS 下的默认内边距
|
||||
*/
|
||||
disableDefaultPadding: Boolean,
|
||||
/**
|
||||
* @description 设置键盘右下角按钮的文字,可选值 `send` `search` `next` `go` `done` `return`
|
||||
*/
|
||||
confirmType: makeStringProp<TextareaConfirmType>('return'),
|
||||
/**
|
||||
* @description 点击键盘右下角按钮时是否保持键盘不收起
|
||||
*/
|
||||
confirmHold: Boolean,
|
||||
/**
|
||||
* @description 键盘对齐位置,可选值 `cursor` `bottom`
|
||||
*/
|
||||
adjustKeyboardTo: makeStringProp<TextareaAdjustKeyboardTo>('cursor'),
|
||||
}
|
||||
|
||||
export type TextareaProps = ExtractPropTypes<typeof textareaProps>
|
||||
|
||||
export const textareaEmits = {
|
||||
[BLUR_EVENT]: (evt: TextareaOnBlurEvent) => evt instanceof Object,
|
||||
[FOCUS_EVENT]: (evt: TextareaOnFocusEvent) => evt instanceof Object,
|
||||
[CHANGE_EVENT]: (val1?: string, val2?: string | Event) => isString(val1) && (isString(val2) || (val2 instanceof Object)),
|
||||
[UPDATE_MODEL_EVENT]: (val1?: string, val2?: string | Event) => isString(val1) && (isString(val2) || (val2 instanceof Object)),
|
||||
[CONFIRM_EVENT]: (evt: TextareaOnConfirmEvent) => evt instanceof Object,
|
||||
[INPUT_EVENT]: (val: string, evt: TextareaOnInputEvent) => isString(val) && evt instanceof Object,
|
||||
}
|
||||
|
||||
export type TextareaEmits = typeof textareaEmits
|
||||
208
uni_modules/nutui-uni/components/textarea/textarea.vue
Normal file
208
uni_modules/nutui-uni/components/textarea/textarea.vue
Normal 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>
|
||||
16
uni_modules/nutui-uni/components/textarea/type.ts
Normal file
16
uni_modules/nutui-uni/components/textarea/type.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface TextareaAutosizeObject {
|
||||
/**
|
||||
* 最大高度
|
||||
*/
|
||||
maxHeight?: number | string
|
||||
/**
|
||||
* 最小高度
|
||||
*/
|
||||
minHeight?: number | string
|
||||
}
|
||||
|
||||
export const textareaConfirmType = ['send', 'search', 'next', 'go', 'done', 'return'] as const
|
||||
export type TextareaConfirmType = (typeof textareaConfirmType)[number]
|
||||
|
||||
export const textareaAdjustKeyboardTo = ['cursor', 'bottom']
|
||||
export type TextareaAdjustKeyboardTo = (typeof textareaAdjustKeyboardTo)[number]
|
||||
Reference in New Issue
Block a user