Skip to content

Commit 1c13f2e

Browse files
feat: ✨ Textarea 组件新增clear-triger属性
Closes: #462
1 parent 8ba9a27 commit 1c13f2e

File tree

8 files changed

+144
-64
lines changed

8 files changed

+144
-64
lines changed

docs/component/textarea.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ const value = ref<string>('')
4040
<wd-textarea v-model="value" :maxlength="120" clearable show-word-limit />
4141
```
4242

43+
44+
## 有值且聚焦时展示清空按钮
45+
设置 `clear-trigger` 属性,可以控制是否聚焦时才展示清空按钮。
46+
47+
```html
48+
<wd-textarea clear-trigger="focus" v-model="value14" :maxlength="120" clearable show-word-limit />
49+
```
50+
51+
## 点击清除按钮时不自动聚焦
52+
53+
设置`focus-when-clear` 属性,可以控制点击清除按钮时是否自动聚焦。
54+
55+
```html
56+
<wd-textarea v-model="value" :focus-when-clear="false" :maxlength="120" clearable show-word-limit />
57+
```
58+
59+
4360
## 高度自适应
4461

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

142161
### FormItemRule 数据结构
143162

src/pages/textarea/Index.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
<demo-block title="清空按钮 和 字数限制" transparent>
1313
<wd-textarea v-model="value2" :maxlength="120" clearable show-word-limit />
1414
</demo-block>
15+
<demo-block title="有值且聚焦时展示清空按钮" transparent>
16+
<wd-textarea clear-trigger="focus" v-model="value14" :maxlength="120" clearable show-word-limit />
17+
</demo-block>
18+
<demo-block title="点击清除按钮时不自动聚焦" transparent>
19+
<wd-textarea v-model="value15" :focus-when-clear="false" :maxlength="120" clearable show-word-limit />
20+
</demo-block>
1521
<demo-block title="大尺寸" transparent>
1622
<wd-textarea v-model="value7" size="large" :maxlength="120" clearable show-word-limit></wd-textarea>
1723
</demo-block>
@@ -51,9 +57,10 @@ const value8 = ref<string>('只读只读只度')
5157
const value9 = ref<string>('')
5258
const value10 = ref<string>('')
5359
const value11 = ref<string>('禁用禁用禁用')
54-
5560
const value12 = ref<string>('只读只读只度')
5661
const value13 = ref<string>('禁用禁用禁用')
62+
const value14 = ref<string>('')
63+
const value15 = ref<string>('')
5764
</script>
5865
<style lang="scss" scoped>
5966
.wot-theme-dark {

src/uni_modules/wot-design-uni/components/common/util.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,26 @@ export const requestAnimationFrame = (cb = () => {}) => {
438438
})
439439
}
440440

441+
/**
442+
* 设置多少个requestAnimationFrame 之后执行回调函数
443+
* @param {number} n - 执行次数
444+
* @param {function} cb - 回调函数
445+
*/
446+
export const requestAnimationFrameTimer = (n: number, cb = () => {}) => {
447+
let count = 0
448+
const recursiveFunc = () => {
449+
requestAnimationFrame(() => {
450+
count++
451+
if (count === n) {
452+
cb()
453+
} else {
454+
recursiveFunc()
455+
}
456+
})
457+
}
458+
recursiveFunc()
459+
}
460+
441461
/**
442462
* 深拷贝函数,用于将对象进行完整复制。
443463
* @param obj 要深拷贝的对象

src/uni_modules/wot-design-uni/components/wd-input/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ export const inputProps = {
160160
rules: makeArrayProp<FormItemRule>(),
161161
/**
162162
* 显示清除图标的时机,always 表示输入框不为空时展示,focus 表示输入框聚焦且不为空时展示
163+
* 类型: "focus" | "always"
164+
* 默认值: "always"
165+
* 最低版本: $LOWEST_VERSION$
163166
*/
164167
clearTrigger: makeStringProp<InputClearTrigger>('always'),
165168
/**

src/uni_modules/wot-design-uni/components/wd-input/wd-input.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
:type="type"
2828
:password="showPassword && !isPwdVisible"
2929
v-model="inputValue"
30-
:placeholder="placeholder || translate('placeholder')"
30+
:placeholder="placeholderValue"
3131
:disabled="disabled"
3232
:maxlength="maxlength"
3333
:focus="focused"
@@ -85,7 +85,7 @@ export default {
8585

8686
<script lang="ts" setup>
8787
import { computed, onBeforeMount, ref, watch } from 'vue'
88-
import { isDef, objToStyle, requestAnimationFrame } from '../common/util'
88+
import { isDef, objToStyle, requestAnimationFrameTimer } from '../common/util'
8989
import { useCell } from '../composables/useCell'
9090
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
9191
import { useParent } from '../composables/useParent'
@@ -133,6 +133,10 @@ watch(
133133
134134
const { parent: form } = useParent(FORM_KEY)
135135
136+
const placeholderValue = computed(() => {
137+
return isDef(props.placeholder) ? props.placeholder : translate('placeholder')
138+
})
139+
136140
/**
137141
* 展示清空按钮
138142
*/
@@ -226,12 +230,12 @@ function togglePwdVisible() {
226230
}
227231
function clear() {
228232
clearing.value = true
229-
focusing.value = false
230233
inputValue.value = ''
231234
if (props.focusWhenClear) {
232235
focused.value = false
233236
}
234-
requestAnimationFrame(() => {
237+
focusing.value = false
238+
requestAnimationFrameTimer(1, () => {
235239
if (props.focusWhenClear) {
236240
focused.value = true
237241
focusing.value = true
@@ -248,7 +252,7 @@ function handleBlur() {
248252
clearing.value = false
249253
return
250254
}
251-
requestAnimationFrame(() => {
255+
requestAnimationFrameTimer(3, () => {
252256
focusing.value = false
253257
emit('blur', {
254258
value: inputValue.value

src/uni_modules/wot-design-uni/components/wd-textarea/index.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@
143143
padding: 0;
144144
font-size: 0;
145145
background: $-textarea-bg;
146-
padding-right: 24px;
147146
box-sizing: border-box;
148147

149148
@include when(show-limit) {

src/uni_modules/wot-design-uni/components/wd-textarea/types.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
import type { ExtractPropTypes } from 'vue'
22
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeNumericProp, makeStringProp } from '../common/props'
33
import type { FormItemRule } from '../wd-form/types'
4+
import type { InputClearTrigger } from '../wd-input/types'
45

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

78
export const textareaProps = {
89
...baseProps,
10+
/**
11+
* * 自定义文本域容器class名称。
12+
* 类型:string
13+
*/
14+
customTextareaContainerClass: makeStringProp(''),
15+
16+
/**
17+
* * 自定义文本域class名称。
18+
* 类型:string
19+
*/
20+
customTextareaClass: makeStringProp(''),
21+
22+
/**
23+
* * 自定义标签class名称。
24+
* 类型:string
25+
*/
26+
customLabelClass: makeStringProp(''),
927
// 原生属性
1028
/**
1129
* * 绑定值。
@@ -254,24 +272,20 @@ export const textareaProps = {
254272
* 默认值:[]
255273
*/
256274
rules: makeArrayProp<FormItemRule>(),
257-
258-
/**
259-
* * 自定义文本域容器class名称。
260-
* 类型:string
261-
*/
262-
customTextareaContainerClass: makeStringProp(''),
263-
264275
/**
265-
* * 自定义文本域class名称。
266-
* 类型:string
276+
* 显示清除图标的时机,always 表示输入框不为空时展示,focus 表示输入框聚焦且不为空时展示
277+
* 类型: "focus" | "always"
278+
* 默认值: "always"
279+
* 最低版本: $LOWEST_VERSION$
267280
*/
268-
customTextareaClass: makeStringProp(''),
269-
281+
clearTrigger: makeStringProp<InputClearTrigger>('always'),
270282
/**
271-
* * 自定义标签class名称。
272-
* 类型:string
283+
* 是否在点击清除按钮时聚焦输入框
284+
* 类型: boolean
285+
* 默认值: true
286+
* 最低版本: $LOWEST_VERSION$
273287
*/
274-
customLabelClass: makeStringProp('')
288+
focusWhenClear: makeBooleanProp(true)
275289
}
276290

277291
export type TextareaProps = ExtractPropTypes<typeof textareaProps>

src/uni_modules/wot-design-uni/components/wd-textarea/wd-textarea.vue

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
:placeholder="placeholderValue"
2121
:disabled="disabled"
2222
:maxlength="maxlength"
23-
:focus="isFocus"
23+
:focus="focused"
2424
:auto-focus="autoFocus"
2525
:placeholder-style="placeholderStyle"
2626
:placeholder-class="inputPlaceholderClass"
@@ -72,7 +72,7 @@ export default {
7272

7373
<script lang="ts" setup>
7474
import { computed, onBeforeMount, ref, watch } from 'vue'
75-
import { objToStyle, requestAnimationFrame, isDef } from '../common/util'
75+
import { objToStyle, requestAnimationFrameTimer, isDef } from '../common/util'
7676
import { useCell } from '../composables/useCell'
7777
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
7878
import { useParent } from '../composables/useParent'
@@ -100,37 +100,50 @@ const placeholderValue = computed(() => {
100100
return isDef(props.placeholder) ? props.placeholder : translate('placeholder')
101101
})
102102
103-
const showClear = ref<boolean>(false)
104-
const showWordCount = ref<boolean>(false)
105103
const clearing = ref<boolean>(false)
106-
const isFocus = ref<boolean>(false) // 是否聚焦
104+
const focused = ref<boolean>(false) // 控制聚焦
105+
const focusing = ref<boolean>(false) // 当前是否激活状态
107106
const inputValue = ref<string | number>('') // 输入框的值
108107
const cell = useCell()
109108
110109
watch(
111110
() => props.focus,
112111
(newValue) => {
113-
isFocus.value = newValue
112+
focused.value = newValue
114113
},
115114
{ immediate: true, deep: true }
116115
)
117116
118117
watch(
119118
() => props.modelValue,
120119
(newValue) => {
121-
const { disabled, readonly, clearable } = props
122-
if (newValue === null || newValue === undefined) {
123-
newValue = ''
124-
console.warn('[wot-design] warning(wd-textarea): value can not be null or undefined.')
125-
}
126-
inputValue.value = newValue
127-
showClear.value = Boolean(clearable && !disabled && !readonly && newValue)
120+
inputValue.value = isDef(newValue) ? String(newValue) : ''
128121
},
129122
{ immediate: true, deep: true }
130123
)
131124
132125
const { parent: form } = useParent(FORM_KEY)
133126
127+
/**
128+
* 展示清空按钮
129+
*/
130+
const showClear = computed(() => {
131+
const { disabled, readonly, clearable, clearTrigger } = props
132+
if (clearable && !readonly && !disabled && inputValue.value && (clearTrigger === 'always' || (props.clearTrigger === 'focus' && focusing.value))) {
133+
return true
134+
} else {
135+
return false
136+
}
137+
})
138+
139+
/**
140+
* 展示字数统计
141+
*/
142+
const showWordCount = computed(() => {
143+
const { disabled, readonly, maxlength, showWordLimit } = props
144+
return Boolean(!disabled && !readonly && isDef(maxlength) && maxlength > -1 && showWordLimit)
145+
})
146+
134147
// 表单校验错误信息
135148
const errorMessage = computed(() => {
136149
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
@@ -194,57 +207,58 @@ onBeforeMount(() => {
194207
195208
// 状态初始化
196209
function initState() {
197-
const { disabled, readonly, clearable, maxlength, showWordLimit } = props
198-
showClear.value = Boolean(!disabled && !readonly && clearable && inputValue.value)
199-
showWordCount.value = Boolean(!disabled && !readonly && maxlength && showWordLimit)
200-
inputValue.value = formatValue(inputValue.value as string)
210+
inputValue.value = formatValue(inputValue.value)
201211
emit('update:modelValue', inputValue.value)
202212
}
203213
204-
function formatValue(value: string) {
214+
function formatValue(value: string | number) {
205215
const { maxlength, showWordLimit } = props
206-
if (showWordLimit && maxlength !== -1 && value.length > maxlength) {
216+
if (showWordLimit && maxlength !== -1 && String(value).length > maxlength) {
207217
return value.toString().substring(0, maxlength)
208218
}
209219
return value
210220
}
211221
212222
function clear() {
223+
clearing.value = true
224+
focusing.value = false
213225
inputValue.value = ''
214-
requestAnimationFrame()
215-
.then(() => requestAnimationFrame())
216-
.then(() => requestAnimationFrame())
217-
.then(() => {
218-
emit('change', {
219-
value: ''
220-
})
221-
emit('update:modelValue', inputValue.value)
222-
emit('clear')
223-
224-
requestAnimationFrame().then(() => {
225-
isFocus.value = true
226-
})
226+
if (props.focusWhenClear) {
227+
focused.value = false
228+
}
229+
requestAnimationFrameTimer(1, () => {
230+
if (props.focusWhenClear) {
231+
focused.value = true
232+
focusing.value = true
233+
}
234+
emit('change', {
235+
value: ''
227236
})
237+
emit('update:modelValue', inputValue.value)
238+
emit('clear')
239+
})
228240
}
229241
// 失去焦点时会先后触发change、blur,未输入内容但失焦不触发 change 只触发 blur
230242
function handleBlur({ detail }: any) {
231-
isFocus.value = false
232-
emit('change', {
233-
value: inputValue.value
234-
})
235-
emit('update:modelValue', inputValue.value)
236-
emit('blur', {
237-
value: inputValue.value,
238-
// textarea 有 cursor
239-
cursor: detail.cursor ? detail.cursor : null
243+
if (clearing.value) {
244+
clearing.value = false
245+
return
246+
}
247+
requestAnimationFrameTimer(3, () => {
248+
focusing.value = false
249+
emit('blur', {
250+
value: inputValue.value,
251+
// textarea 有 cursor
252+
cursor: detail.cursor ? detail.cursor : null
253+
})
240254
})
241255
}
242256
function handleFocus({ detail }: any) {
243257
if (clearing.value) {
244258
clearing.value = false
245259
return
246260
}
247-
isFocus.value = true
261+
focusing.value = true
248262
emit('focus', detail)
249263
}
250264
// input事件需要传入

0 commit comments

Comments
 (0)