mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-02-17 13:20:52 +08:00
refactor(input): pattern -> allow-input
This commit is contained in:
parent
599d524b35
commit
014ddea7de
@ -15,6 +15,7 @@
|
||||
### Feats
|
||||
|
||||
- 🌟 `n-pagination` adds dropdown menu for fast jump button.
|
||||
- `n-input` adds `allow-input` prop.
|
||||
- `n-tree-select` adds `arrow` slot, closes [#3084](https://github.com/TuSimple/naive-ui/issues/3084).
|
||||
- `n-cascader` will show corresponding submenu after checkbox is clicked, closes [#3079](https://github.com/TuSimple/naive-ui/issues/3079).
|
||||
- `n-upload` will disable dragger when maximum number of files was reached.
|
||||
@ -22,7 +23,6 @@
|
||||
- `n-popselect` adds `node-props` prop.
|
||||
- `n-popselect` adds `virtual-scroll` prop.
|
||||
- `n-data-table` adds `scrollTo` method, closes [#2570](https://github.com/TuSimple/naive-ui/issues/2570).
|
||||
- `n-input` adds `pattern` props.
|
||||
|
||||
## 2.30.3
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
- `n-popselect` 新增 `node-props` 属性
|
||||
- `n-popselect` 新增 `virtual-scroll` 属性
|
||||
- `n-data-table` 新增 `scrollTo` 方法,关闭 [#2570](https://github.com/TuSimple/naive-ui/issues/2570)
|
||||
- `n-input` 新增 `pattern` 属性
|
||||
- `n-input` 新增 `allow-input` 属性
|
||||
|
||||
## 2.30.3
|
||||
|
||||
|
@ -31,6 +31,7 @@ pattern.vue
|
||||
|
||||
| Name | Type | Default | Description | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| allow-input | `(value: string) => boolean` | `undefined` | Check the incoming value, if it returns `false`, input will not be accepted. | NEXT_VERSION |
|
||||
| autofocus | `boolean` | `false` | Whether to autofocus. | |
|
||||
| autosize | `boolean \| { minRows?: number, maxRows?: number }` | `false` | Sizing property for when the input is of type `textarea`. e.g. `{ minRows: 1, maxRows: 3 }`. | |
|
||||
| clearable | `boolean` | `false` | Whether the input is clearable. | |
|
||||
@ -51,7 +52,6 @@ pattern.vue
|
||||
| show-password-on | `'click' \| 'mousedown'` | `undefined` | The event to show the password. | |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Input size. | |
|
||||
| status | `'success' \| 'warning' \| 'error'` | `undefined` | Validaiton status. | 2.25.0 |
|
||||
| pattern | `(value: string) => boolean` | `undefined` | Check the incoming value, if it returns `false`, input will not be accepted. | NEXT_VERSION |
|
||||
| type | `'text' \| 'password' \| 'textarea'` | `'text'` | Input type. | |
|
||||
| value | `string \| [string, string] \| null` | `undefined` | Manually set the input value. When `pair` is `true`, this is an array. | |
|
||||
| on-blur | `() => void` | `undefined` | Callback triggered when the input is blurred. | |
|
||||
|
@ -1,20 +1,20 @@
|
||||
<markdown>
|
||||
# Input intercept
|
||||
# Limit input format
|
||||
|
||||
Control the entry format of the input.
|
||||
Use `allow-input` to limit input value to desired format. You can use it to achieve trim effect.
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-input
|
||||
type="text"
|
||||
:pattern="validateOnlyNumber"
|
||||
placeholder="Only enter the number."
|
||||
:allow-input="onlyAllowNumber"
|
||||
placeholder="Only allow number"
|
||||
/>
|
||||
<n-input
|
||||
type="textarea"
|
||||
:pattern="validateEmpty"
|
||||
placeholder="Can't enter space."
|
||||
:allow-input="noSideSpace"
|
||||
placeholder="No leading or trailing space"
|
||||
/>
|
||||
</n-space>
|
||||
</template>
|
||||
@ -25,8 +25,8 @@ import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
validateOnlyNumber: (value: string) => !value || /^\d+$/.test(value),
|
||||
validateEmpty: (value: string) => !/ /g.test(value)
|
||||
onlyAllowNumber: (value: string) => !value || /^\d+$/.test(value),
|
||||
noSideSpace: (value: string) => !/ /g.test(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -32,6 +32,7 @@ rtl-debug.vue
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| allow-input | `(value: string) => false` | `undefined` | 校验当前的输入是否合法,如果返回 `false` 输入框便不会响应此次的输入 | NEXT_VERSION |
|
||||
| autofocus | `boolean` | `false` | 是否自动获取焦点 | |
|
||||
| autosize | `boolean \| { minRows?: number, maxRows?: number }` | `false` | 自适应内容高度,只对 `type="textarea"` 有效,可传入对象,如 `{ minRows: 1, maxRows: 3 }` | |
|
||||
| clearable | `boolean` | `false` | 是否可清空 | |
|
||||
@ -52,7 +53,6 @@ rtl-debug.vue
|
||||
| show-password-on | `'click' \| 'mousedown'` | `undefined` | 显示密码的时机 | |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 输入框尺寸 | |
|
||||
| status | `'success' \| 'warning' \| 'error'` | `undefined` | 验证状态 | 2.25.0 |
|
||||
| pattern | `(value: string) => false` | `undefined` | 校验当前的输入是否合法,如果返回 `false` 输入框便不会响应此次的输入 | NEXT_VERSION |
|
||||
| type | `'text' \| 'password' \| 'textarea'` | `'text'` | 输入框类型 | |
|
||||
| value | `string \| [string, string] \| null` | `undefined` | 文本输入的值。如果 `pair` 是 `true`,`value` 是一个数组 | |
|
||||
| on-blur | `() => void` | `undefined` | 输入框失去焦点时触发 | |
|
||||
|
@ -1,20 +1,20 @@
|
||||
<markdown>
|
||||
# 输入校验
|
||||
|
||||
限制输入框的输入格式。
|
||||
使用 `allow-input` 限制输入框的输入格式,你可以使用它来达到 `trim` 的效果。
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-input
|
||||
type="text"
|
||||
:pattern="validateOnlyNumber"
|
||||
:allow-input="onlyAllowNumber"
|
||||
placeholder="只能输入数字"
|
||||
/>
|
||||
<n-input
|
||||
type="textarea"
|
||||
:pattern="validateEmpty"
|
||||
placeholder="不能输入空格"
|
||||
:allow-input="noSideSpace"
|
||||
placeholder="没有前后空格"
|
||||
/>
|
||||
</n-space>
|
||||
</template>
|
||||
@ -25,8 +25,9 @@ import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
validateOnlyNumber: (value: string) => !value || /^\d+$/.test(value),
|
||||
validateEmpty: (value: string) => !/ /g.test(value)
|
||||
onlyAllowNumber: (value: string) => !value || /^\d+$/.test(value),
|
||||
noSideSpace: (value: string) =>
|
||||
!value.startsWith(' ') && !value.endsWith(' ')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -24,6 +24,7 @@ import { VResizeObserver } from 'vueuc'
|
||||
import { off, on } from 'evtd'
|
||||
import type { FormValidationStatus } from '../../form/src/interface'
|
||||
import { EyeIcon, EyeOffIcon } from '../../_internal/icons'
|
||||
import useRtl from '../../_mixins/use-rtl'
|
||||
import {
|
||||
NBaseClear,
|
||||
NBaseIcon,
|
||||
@ -57,10 +58,9 @@ import type {
|
||||
InputWrappedRef
|
||||
} from './interface'
|
||||
import { inputInjectionKey } from './interface'
|
||||
import { isEmptyValue, useCursor } from './utils'
|
||||
import { isEmptyInputValue, useCursor } from './utils'
|
||||
import WordCount from './WordCount'
|
||||
import style from './styles/input.cssr'
|
||||
import useRtl from '../../_mixins/use-rtl'
|
||||
|
||||
const inputProps = {
|
||||
...(useTheme.props as ThemeProps<InputTheme>),
|
||||
@ -120,7 +120,7 @@ const inputProps = {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
pattern: Function as PropType<(value: string) => boolean>,
|
||||
allowInput: Function as PropType<(value: string) => boolean>,
|
||||
onMousedown: Function as PropType<(e: MouseEvent) => void>,
|
||||
onKeydown: Function as PropType<(e: KeyboardEvent) => void>,
|
||||
onKeyup: Function as PropType<(e: KeyboardEvent) => void>,
|
||||
@ -200,7 +200,7 @@ export default defineComponent({
|
||||
const currentFocusedInputRef = ref<
|
||||
HTMLInputElement | HTMLTextAreaElement | null
|
||||
>(null)
|
||||
const focusedInputCorsurControl = useCursor(currentFocusedInputRef)
|
||||
const focusedInputCursorControl = useCursor(currentFocusedInputRef)
|
||||
const textareaScrollbarInstRef = ref<ScrollbarInst | null>(null)
|
||||
// local
|
||||
const { localeRef } = useLocale('Input')
|
||||
@ -242,8 +242,8 @@ export default defineComponent({
|
||||
const { value: mergedPlaceholder } = mergedPlaceholderRef
|
||||
return (
|
||||
!isComposing &&
|
||||
(isEmptyValue(mergedValue) ||
|
||||
(Array.isArray(mergedValue) && isEmptyValue(mergedValue[0]))) &&
|
||||
(isEmptyInputValue(mergedValue) ||
|
||||
(Array.isArray(mergedValue) && isEmptyInputValue(mergedValue[0]))) &&
|
||||
mergedPlaceholder[0]
|
||||
)
|
||||
})
|
||||
@ -254,8 +254,8 @@ export default defineComponent({
|
||||
return (
|
||||
!isComposing &&
|
||||
mergedPlaceholder[1] &&
|
||||
(isEmptyValue(mergedValue) ||
|
||||
(Array.isArray(mergedValue) && isEmptyValue(mergedValue[1])))
|
||||
(isEmptyInputValue(mergedValue) ||
|
||||
(Array.isArray(mergedValue) && isEmptyInputValue(mergedValue[1])))
|
||||
)
|
||||
})
|
||||
// focus
|
||||
@ -437,7 +437,6 @@ export default defineComponent({
|
||||
} else {
|
||||
handleInput(e, 0)
|
||||
}
|
||||
focusedInputCorsurControl.recordCursor()
|
||||
}
|
||||
function handleInput (
|
||||
e: InputEvent | CompositionEvent | Event,
|
||||
@ -446,7 +445,6 @@ export default defineComponent({
|
||||
): void {
|
||||
const targetValue = (e.target as HTMLInputElement).value
|
||||
syncMirror(targetValue)
|
||||
focusedInputCorsurControl.recordCursor()
|
||||
if (props.type === 'textarea') {
|
||||
const { value: textareaScrollbarInst } = textareaScrollbarInstRef
|
||||
if (textareaScrollbarInst) {
|
||||
@ -455,8 +453,9 @@ export default defineComponent({
|
||||
}
|
||||
syncSource = targetValue
|
||||
if (isComposingRef.value) return
|
||||
const isValidInputValue = matches(targetValue)
|
||||
if (isValidInputValue) {
|
||||
focusedInputCursorControl.recordCursor()
|
||||
const isIncomingValueValid = allowInput(targetValue)
|
||||
if (isIncomingValueValid) {
|
||||
if (!props.pair) {
|
||||
event === 'input' ? doUpdateValue(targetValue) : doChange(targetValue)
|
||||
} else {
|
||||
@ -464,7 +463,7 @@ export default defineComponent({
|
||||
if (!Array.isArray(value)) {
|
||||
value = ['', '']
|
||||
} else {
|
||||
value = [...value]
|
||||
value = [value[0], value[1]]
|
||||
}
|
||||
value[index] = targetValue
|
||||
event === 'input' ? doUpdateValue(value) : doChange(value)
|
||||
@ -473,14 +472,14 @@ export default defineComponent({
|
||||
// force update to sync input's view with value
|
||||
// if not set, after input, input value won't sync with dom input value
|
||||
vm.$forceUpdate()
|
||||
if (!isValidInputValue) {
|
||||
void nextTick(focusedInputCorsurControl.restoreCursor)
|
||||
if (!isIncomingValueValid) {
|
||||
void nextTick(focusedInputCursorControl.restoreCursor)
|
||||
}
|
||||
}
|
||||
function matches (value: string): boolean {
|
||||
const { pattern } = props
|
||||
if (typeof pattern === 'function') {
|
||||
return pattern(value)
|
||||
function allowInput (value: string): boolean {
|
||||
const { allowInput } = props
|
||||
if (typeof allowInput === 'function') {
|
||||
return allowInput(value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -502,7 +501,7 @@ export default defineComponent({
|
||||
dealWithEvent(e, 'blur')
|
||||
currentFocusedInputRef.value = null
|
||||
}
|
||||
function handleInputFocus (e: FocusEvent, index?: number): void {
|
||||
function handleInputFocus (e: FocusEvent, index: number): void {
|
||||
doUpdateValueFocus(e)
|
||||
focusedRef.value = true
|
||||
activatedRef.value = true
|
||||
@ -512,7 +511,7 @@ export default defineComponent({
|
||||
currentFocusedInputRef.value = inputElRef.value
|
||||
} else if (index === 1) {
|
||||
currentFocusedInputRef.value = inputEl2Ref.value
|
||||
} else {
|
||||
} else if (index === 2) {
|
||||
currentFocusedInputRef.value = textareaElRef.value
|
||||
}
|
||||
}
|
||||
@ -1059,7 +1058,7 @@ export default defineComponent({
|
||||
scrollContainerWidthStyle
|
||||
]}
|
||||
onBlur={this.handleInputBlur}
|
||||
onFocus={this.handleInputFocus}
|
||||
onFocus={(e) => this.handleInputFocus(e, 2)}
|
||||
onInput={this.handleInput}
|
||||
onChange={this.handleChange}
|
||||
onScroll={this.handleTextAreaScroll}
|
||||
|
@ -10,18 +10,17 @@ export function len (s: string): number {
|
||||
return count
|
||||
}
|
||||
|
||||
export function isEmptyValue (value: any): boolean {
|
||||
return ['', undefined, null].includes(value)
|
||||
export function isEmptyInputValue (value: unknown): boolean {
|
||||
return value === '' || value == null
|
||||
}
|
||||
|
||||
export interface UseCursorControl {
|
||||
recordCursor: () => void
|
||||
restoreCursor: () => void
|
||||
clearRecord: () => void
|
||||
}
|
||||
|
||||
export function useCursor (
|
||||
inputRef: Ref<HTMLInputElement | HTMLTextAreaElement | null>
|
||||
inputElRef: Ref<HTMLInputElement | HTMLTextAreaElement | null>
|
||||
): UseCursorControl {
|
||||
const selectionRef = ref<{
|
||||
start: number
|
||||
@ -31,35 +30,32 @@ export function useCursor (
|
||||
} | null>(null)
|
||||
|
||||
function recordCursor (): void {
|
||||
const { value: input } = inputRef
|
||||
const { value: input } = inputElRef
|
||||
if (!input || !input.focus) {
|
||||
clearRecord()
|
||||
reset()
|
||||
return
|
||||
}
|
||||
const { selectionStart, selectionEnd, value } = input
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (selectionStart == void 0 || selectionEnd == void 0) {
|
||||
clearRecord()
|
||||
if (selectionStart == null || selectionEnd == null) {
|
||||
reset()
|
||||
return
|
||||
}
|
||||
selectionRef.value = {
|
||||
start: selectionStart,
|
||||
end: selectionEnd,
|
||||
beforeText: value.substring(0, selectionStart),
|
||||
afterText: value.substring(selectionEnd)
|
||||
beforeText: value.slice(0, selectionStart),
|
||||
afterText: value.slice(selectionEnd)
|
||||
}
|
||||
}
|
||||
|
||||
function restoreCursor (): void {
|
||||
const { value: selection } = selectionRef
|
||||
const { value: input } = inputRef
|
||||
if (!selection || !input || !input.focus) {
|
||||
const { value: inputEl } = inputElRef
|
||||
if (!selection || !inputEl) {
|
||||
return
|
||||
}
|
||||
|
||||
const { value } = input
|
||||
const { value } = inputEl
|
||||
const { start, beforeText, afterText } = selection
|
||||
|
||||
let startPos = value.length
|
||||
if (value.endsWith(afterText)) {
|
||||
startPos = value.length - afterText.length
|
||||
@ -72,18 +68,16 @@ export function useCursor (
|
||||
startPos = newIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
input.setSelectionRange?.(startPos, startPos)
|
||||
inputEl.setSelectionRange?.(startPos, startPos)
|
||||
}
|
||||
|
||||
function clearRecord (): void {
|
||||
function reset (): void {
|
||||
selectionRef.value = null
|
||||
}
|
||||
watch(inputRef, clearRecord)
|
||||
watch(inputElRef, reset)
|
||||
|
||||
return {
|
||||
recordCursor,
|
||||
restoreCursor,
|
||||
clearRecord
|
||||
restoreCursor
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user