Skip to content

Commit

Permalink
feat: ✨ Textarea 组件新增clear-triger属性
Browse files Browse the repository at this point in the history
Closes: #462
  • Loading branch information
Moonofweisheng committed Aug 3, 2024
1 parent 8ba9a27 commit 1c13f2e
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 64 deletions.
21 changes: 20 additions & 1 deletion docs/component/textarea.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ const value = ref<string>('')
<wd-textarea v-model="value" :maxlength="120" clearable show-word-limit />
```


## 有值且聚焦时展示清空按钮
设置 `clear-trigger` 属性,可以控制是否聚焦时才展示清空按钮。

```html
<wd-textarea clear-trigger="focus" v-model="value14" :maxlength="120" clearable show-word-limit />
```

## 点击清除按钮时不自动聚焦

设置`focus-when-clear` 属性,可以控制点击清除按钮时是否自动聚焦。

```html
<wd-textarea v-model="value" :focus-when-clear="false" :maxlength="120" clearable show-word-limit />
```


## 高度自适应

通过设置 `auto-height` 属性,实现高度自适应。
Expand Down Expand Up @@ -137,7 +154,9 @@ const value = ref<string>('')
| no-border | 非 cell 类型下是否隐藏下划线 | boolean | - | false | - | - |
| required | cell 类型下必填样式 | boolean | - | false | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
| clearTrigger | 显示清除图标的时机,always 表示输入框不为空时展示,focus 表示输入框聚焦且不为空时展示 | `InputClearTrigger` | `focus` / `always` | `always` | $LOWEST_VERSION$ |
| focusWhenClear | 是否在点击清除按钮时聚焦输入框 | boolean | - | true | $LOWEST_VERSION$ |

### FormItemRule 数据结构

Expand Down
9 changes: 8 additions & 1 deletion src/pages/textarea/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
<demo-block title="清空按钮 和 字数限制" transparent>
<wd-textarea v-model="value2" :maxlength="120" clearable show-word-limit />
</demo-block>
<demo-block title="有值且聚焦时展示清空按钮" transparent>
<wd-textarea clear-trigger="focus" v-model="value14" :maxlength="120" clearable show-word-limit />
</demo-block>
<demo-block title="点击清除按钮时不自动聚焦" transparent>
<wd-textarea v-model="value15" :focus-when-clear="false" :maxlength="120" clearable show-word-limit />
</demo-block>
<demo-block title="大尺寸" transparent>
<wd-textarea v-model="value7" size="large" :maxlength="120" clearable show-word-limit></wd-textarea>
</demo-block>
Expand Down Expand Up @@ -51,9 +57,10 @@ const value8 = ref<string>('只读只读只度')
const value9 = ref<string>('')
const value10 = ref<string>('')
const value11 = ref<string>('禁用禁用禁用')
const value12 = ref<string>('只读只读只度')
const value13 = ref<string>('禁用禁用禁用')
const value14 = ref<string>('')
const value15 = ref<string>('')
</script>
<style lang="scss" scoped>
.wot-theme-dark {
Expand Down
20 changes: 20 additions & 0 deletions src/uni_modules/wot-design-uni/components/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,26 @@ export const requestAnimationFrame = (cb = () => {}) => {
})
}

/**
* 设置多少个requestAnimationFrame 之后执行回调函数
* @param {number} n - 执行次数
* @param {function} cb - 回调函数
*/
export const requestAnimationFrameTimer = (n: number, cb = () => {}) => {
let count = 0
const recursiveFunc = () => {
requestAnimationFrame(() => {
count++
if (count === n) {
cb()
} else {
recursiveFunc()
}
})
}
recursiveFunc()
}

/**
* 深拷贝函数,用于将对象进行完整复制。
* @param obj 要深拷贝的对象
Expand Down
3 changes: 3 additions & 0 deletions src/uni_modules/wot-design-uni/components/wd-input/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ export const inputProps = {
rules: makeArrayProp<FormItemRule>(),
/**
* 显示清除图标的时机,always 表示输入框不为空时展示,focus 表示输入框聚焦且不为空时展示
* 类型: "focus" | "always"
* 默认值: "always"
* 最低版本: $LOWEST_VERSION$
*/
clearTrigger: makeStringProp<InputClearTrigger>('always'),
/**
Expand Down
14 changes: 9 additions & 5 deletions src/uni_modules/wot-design-uni/components/wd-input/wd-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
:type="type"
:password="showPassword && !isPwdVisible"
v-model="inputValue"
:placeholder="placeholder || translate('placeholder')"
:placeholder="placeholderValue"
:disabled="disabled"
:maxlength="maxlength"
:focus="focused"
Expand Down Expand Up @@ -85,7 +85,7 @@ export default {

<script lang="ts" setup>
import { computed, onBeforeMount, ref, watch } from 'vue'
import { isDef, objToStyle, requestAnimationFrame } from '../common/util'
import { isDef, objToStyle, requestAnimationFrameTimer } from '../common/util'
import { useCell } from '../composables/useCell'
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
Expand Down Expand Up @@ -133,6 +133,10 @@ watch(
const { parent: form } = useParent(FORM_KEY)
const placeholderValue = computed(() => {
return isDef(props.placeholder) ? props.placeholder : translate('placeholder')
})
/**
* 展示清空按钮
*/
Expand Down Expand Up @@ -226,12 +230,12 @@ function togglePwdVisible() {
}
function clear() {
clearing.value = true
focusing.value = false
inputValue.value = ''
if (props.focusWhenClear) {
focused.value = false
}
requestAnimationFrame(() => {
focusing.value = false
requestAnimationFrameTimer(1, () => {
if (props.focusWhenClear) {
focused.value = true
focusing.value = true
Expand All @@ -248,7 +252,7 @@ function handleBlur() {
clearing.value = false
return
}
requestAnimationFrame(() => {
requestAnimationFrameTimer(3, () => {
focusing.value = false
emit('blur', {
value: inputValue.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
padding: 0;
font-size: 0;
background: $-textarea-bg;
padding-right: 24px;
box-sizing: border-box;

@include when(show-limit) {
Expand Down
42 changes: 28 additions & 14 deletions src/uni_modules/wot-design-uni/components/wd-textarea/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import type { ExtractPropTypes } from 'vue'
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeNumericProp, makeStringProp } from '../common/props'
import type { FormItemRule } from '../wd-form/types'
import type { InputClearTrigger } from '../wd-input/types'

export type ConfirmType = 'send' | 'search' | 'next' | 'go' | 'done'

export const textareaProps = {
...baseProps,
/**
* * 自定义文本域容器class名称。
* 类型:string
*/
customTextareaContainerClass: makeStringProp(''),

/**
* * 自定义文本域class名称。
* 类型:string
*/
customTextareaClass: makeStringProp(''),

/**
* * 自定义标签class名称。
* 类型:string
*/
customLabelClass: makeStringProp(''),
// 原生属性
/**
* * 绑定值。
Expand Down Expand Up @@ -254,24 +272,20 @@ export const textareaProps = {
* 默认值:[]
*/
rules: makeArrayProp<FormItemRule>(),

/**
* * 自定义文本域容器class名称。
* 类型:string
*/
customTextareaContainerClass: makeStringProp(''),

/**
* * 自定义文本域class名称。
* 类型:string
* 显示清除图标的时机,always 表示输入框不为空时展示,focus 表示输入框聚焦且不为空时展示
* 类型: "focus" | "always"
* 默认值: "always"
* 最低版本: $LOWEST_VERSION$
*/
customTextareaClass: makeStringProp(''),

clearTrigger: makeStringProp<InputClearTrigger>('always'),
/**
* * 自定义标签class名称。
* 类型:string
* 是否在点击清除按钮时聚焦输入框
* 类型: boolean
* 默认值: true
* 最低版本: $LOWEST_VERSION$
*/
customLabelClass: makeStringProp('')
focusWhenClear: makeBooleanProp(true)
}

export type TextareaProps = ExtractPropTypes<typeof textareaProps>
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
:placeholder="placeholderValue"
:disabled="disabled"
:maxlength="maxlength"
:focus="isFocus"
:focus="focused"
:auto-focus="autoFocus"
:placeholder-style="placeholderStyle"
:placeholder-class="inputPlaceholderClass"
Expand Down Expand Up @@ -72,7 +72,7 @@ export default {

<script lang="ts" setup>
import { computed, onBeforeMount, ref, watch } from 'vue'
import { objToStyle, requestAnimationFrame, isDef } from '../common/util'
import { objToStyle, requestAnimationFrameTimer, isDef } from '../common/util'
import { useCell } from '../composables/useCell'
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
Expand Down Expand Up @@ -100,37 +100,50 @@ const placeholderValue = computed(() => {
return isDef(props.placeholder) ? props.placeholder : translate('placeholder')
})
const showClear = ref<boolean>(false)
const showWordCount = ref<boolean>(false)
const clearing = ref<boolean>(false)
const isFocus = ref<boolean>(false) // 是否聚焦
const focused = ref<boolean>(false) // 控制聚焦
const focusing = ref<boolean>(false) // 当前是否激活状态
const inputValue = ref<string | number>('') // 输入框的值
const cell = useCell()
watch(
() => props.focus,
(newValue) => {
isFocus.value = newValue
focused.value = newValue
},
{ immediate: true, deep: true }
)
watch(
() => props.modelValue,
(newValue) => {
const { disabled, readonly, clearable } = props
if (newValue === null || newValue === undefined) {
newValue = ''
console.warn('[wot-design] warning(wd-textarea): value can not be null or undefined.')
}
inputValue.value = newValue
showClear.value = Boolean(clearable && !disabled && !readonly && newValue)
inputValue.value = isDef(newValue) ? String(newValue) : ''
},
{ immediate: true, deep: true }
)
const { parent: form } = useParent(FORM_KEY)
/**
* 展示清空按钮
*/
const showClear = computed(() => {
const { disabled, readonly, clearable, clearTrigger } = props
if (clearable && !readonly && !disabled && inputValue.value && (clearTrigger === 'always' || (props.clearTrigger === 'focus' && focusing.value))) {
return true
} else {
return false
}
})
/**
* 展示字数统计
*/
const showWordCount = computed(() => {
const { disabled, readonly, maxlength, showWordLimit } = props
return Boolean(!disabled && !readonly && isDef(maxlength) && maxlength > -1 && showWordLimit)
})
// 表单校验错误信息
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
Expand Down Expand Up @@ -194,57 +207,58 @@ onBeforeMount(() => {
// 状态初始化
function initState() {
const { disabled, readonly, clearable, maxlength, showWordLimit } = props
showClear.value = Boolean(!disabled && !readonly && clearable && inputValue.value)
showWordCount.value = Boolean(!disabled && !readonly && maxlength && showWordLimit)
inputValue.value = formatValue(inputValue.value as string)
inputValue.value = formatValue(inputValue.value)
emit('update:modelValue', inputValue.value)
}
function formatValue(value: string) {
function formatValue(value: string | number) {
const { maxlength, showWordLimit } = props
if (showWordLimit && maxlength !== -1 && value.length > maxlength) {
if (showWordLimit && maxlength !== -1 && String(value).length > maxlength) {
return value.toString().substring(0, maxlength)
}
return value
}
function clear() {
clearing.value = true
focusing.value = false
inputValue.value = ''
requestAnimationFrame()
.then(() => requestAnimationFrame())
.then(() => requestAnimationFrame())
.then(() => {
emit('change', {
value: ''
})
emit('update:modelValue', inputValue.value)
emit('clear')
requestAnimationFrame().then(() => {
isFocus.value = true
})
if (props.focusWhenClear) {
focused.value = false
}
requestAnimationFrameTimer(1, () => {
if (props.focusWhenClear) {
focused.value = true
focusing.value = true
}
emit('change', {
value: ''
})
emit('update:modelValue', inputValue.value)
emit('clear')
})
}
// 失去焦点时会先后触发change、blur,未输入内容但失焦不触发 change 只触发 blur
function handleBlur({ detail }: any) {
isFocus.value = false
emit('change', {
value: inputValue.value
})
emit('update:modelValue', inputValue.value)
emit('blur', {
value: inputValue.value,
// textarea 有 cursor
cursor: detail.cursor ? detail.cursor : null
if (clearing.value) {
clearing.value = false
return
}
requestAnimationFrameTimer(3, () => {
focusing.value = false
emit('blur', {
value: inputValue.value,
// textarea 有 cursor
cursor: detail.cursor ? detail.cursor : null
})
})
}
function handleFocus({ detail }: any) {
if (clearing.value) {
clearing.value = false
return
}
isFocus.value = true
focusing.value = true
emit('focus', detail)
}
// input事件需要传入
Expand Down

0 comments on commit 1c13f2e

Please sign in to comment.