fix(input-number): many fixes (#259)

* fix(input-number): many fixes

- Fix `n-input-number` lacks `on-update-value` prop.
- Fix `n-input-number`'s value can't be null.
- Fix `n-input-number`'s button doesn't work after value is cleared, closes [#251](https://github.com/TuSimple/naive-ui/issues/251).
- `n-input-number` will focus directly, closes [#244](https://github.com/TuSimple/naive-ui/issues/244).

* docs(input-number): fixes

* docs
This commit is contained in:
07akioni 2021-06-23 13:35:14 +08:00 committed by GitHub
parent 6edf9db812
commit 4ebecee456
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 155 additions and 61 deletions

View File

@ -14,7 +14,14 @@
### Fixes
- `n-select` can't input in filterable mode in single mode in iOS Safari, closes [#230](https://github.com/TuSimple/naive-ui/issues/230)
- Fix `n-select` can't input in filterable mode in single mode in iOS Safari, closes [#230](https://github.com/TuSimple/naive-ui/issues/230)
- Fix `n-input-number` lacks `on-update-value` prop.
- Fix `n-input-number`'s value can't be null.
- Fix `n-input-number`'s button doesn't work after value is cleared, closes [#251](https://github.com/TuSimple/naive-ui/issues/251).
## Refactors
- `n-input-number` will focus directly, closes [#244](https://github.com/TuSimple/naive-ui/issues/244).
## 2.13.0 (2021-06-21)

View File

@ -14,7 +14,14 @@
### Fixes
- `n-select` 在可过滤单选模式下在 iOS Safari 无法输入,关闭 [#230](https://github.com/TuSimple/naive-ui/issues/230)
- 修复 `n-select` 在可过滤单选模式下在 iOS Safari 无法输入,关闭 [#230](https://github.com/TuSimple/naive-ui/issues/230)
- 修复 `n-input-number` 缺少 `on-update-value` 属性
- 修复 `n-input-number` 值无法为 `null`
- 修复 `n-input-number` 的按钮在值清空后无法使用,关闭 [#251](https://github.com/TuSimple/naive-ui/issues/251)
## Refactors
- `n-input-number` 会直接聚焦,关闭 [#244](https://github.com/TuSimple/naive-ui/issues/244)
## 2.13.0 (2021-06-21)

View File

@ -29,7 +29,7 @@ show-button
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | The size of input box. |
| step | `number` | `1` | The number to which the current value is increased or decreased. It can be an integer or decimal. |
| validator | `(value) => boolean` | `undefined` | Setup custom validation. |
| value | `number` | `undefined` | Value in controlled mode. |
| value | `number \| null` | `undefined` | Value in controlled mode. |
| on-blur | `(event: FocusEvent) => void` | `undefined` | Callback when blur. |
| on-focus | `(event: FocusEvent) => void` | `undefined` | Callback when focused. |
| on-update:value | `(value: number) => void` | `undefined` | Callback when the component's value changes. |
| on-update:value | `(value: number \| null) => void` | `undefined` | Callback when the component's value changes. |

View File

@ -0,0 +1,33 @@
# Debug
```html
<n-input-number
v-model:value="value1"
placeholder="最小值"
:min="-3"
:max="5"
@update:value="handleUpdateValue1"
/>
{{ JSON.stringify(value1) }}
<n-input-number v-model:value="value2" @update:value="handleUpdateValue2" />
{{ JSON.stringify(value2) }}
```
```js
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
return {
handleUpdateValue1 (v) {
console.log(v)
},
handleUpdateValue2 (v) {
console.log(v)
},
value1: ref(null),
value2: ref(null)
}
}
})
```

View File

@ -13,6 +13,7 @@ size
step
validator
show-button
debug
```
## Props
@ -32,4 +33,4 @@ show-button
| value | `number \| null` | `undefined` | 受控模式下的值 |
| on-blur | `(event: FocusEvent) => void` | `undefined` | 移除焦点的回调 |
| on-focus | `(event: FocusEvent) => void` | `undefined` | 获取焦点的回调 |
| on-update:value | `(value: number) => void` | `undefined` | 组件值发生变化的回调 |
| on-update:value | `(value: number \| null) => void` | `undefined` | 组件值发生变化的回调 |

View File

@ -11,6 +11,7 @@ import type { ThemeProps } from '../../_mixins'
import { warn, call, MaybeArray, ExtractPublicPropTypes } from '../../_utils'
import { inputNumberLight, InputNumberTheme } from '../styles'
import { parse, validator, format, parseNumber } from './utils'
import type { OnUpdateValue } from './interface'
const inputNumberProps = {
...(useTheme.props as ThemeProps<InputNumberTheme>),
@ -19,27 +20,15 @@ const inputNumberProps = {
type: Number as PropType<number | null>,
default: null
},
value: {
type: Number,
default: undefined
},
value: Number,
step: {
type: [Number, String],
default: 1
},
min: {
type: [Number, String],
default: undefined
},
max: {
type: [Number, String],
default: undefined
},
min: [Number, String],
max: [Number, String],
size: String as PropType<'small' | 'medium' | 'large'>,
disabled: {
type: Boolean,
default: false
},
disabled: Boolean,
validator: Function as PropType<(value: number) => boolean>,
bordered: {
type: Boolean as PropType<boolean | undefined>,
@ -49,17 +38,13 @@ const inputNumberProps = {
type: Boolean,
default: true
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: number) => void>
>,
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onFocus: [Function, Array] as PropType<MaybeArray<(e: FocusEvent) => void>>,
onBlur: [Function, Array] as PropType<MaybeArray<(e: FocusEvent) => void>>,
// deprecated
onChange: {
type: [Function, Array] as PropType<
MaybeArray<(value: number) => void> | undefined
>,
type: [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>,
validator: () => {
if (__DEV__) {
warn(
@ -124,39 +109,47 @@ export default defineComponent({
if (parsedNumber !== null) return parsedNumber
else return null
})
const doUpdateValue = (value: number): void => {
const doUpdateValue = (value: number | null): void => {
const { value: mergedValue } = mergedValueRef
if (value === mergedValue) return
const { 'onUpdate:value': onUpdateValue, onChange } = props
const {
'onUpdate:value': _onUpdateValue,
onUpdateValue,
onChange
} = props
const { nTriggerFormInput, nTriggerFormChange } = formItem
if (onChange) call(onChange, value)
if (onUpdateValue) call(onUpdateValue, value)
if (_onUpdateValue) call(_onUpdateValue, value)
uncontrolledValueRef.value = value
nTriggerFormInput()
nTriggerFormChange()
}
const deriveValueFromDisplayedValue = (
offset = 0,
postUpdateIfValid = true
doUpdateIfValid = true
): null | number | false => {
const { value: displayedValue } = displayedValueRef
const parsedValue = parse(displayedValue)
if (parsedValue === null) return null
if (parsedValue === null) {
if (doUpdateIfValid) doUpdateValue(null)
return null
}
if (validator(parsedValue)) {
let nextValue = parsedValue + offset
if (validator(nextValue)) {
const { value: mergedMax } = mergedMaxRef
const { value: mergedMin } = mergedMinRef
if (mergedMax !== null && nextValue > mergedMax) {
if (!postUpdateIfValid) return false
if (!doUpdateIfValid) return false
nextValue = mergedMax
}
if (mergedMin !== null && nextValue < mergedMin) {
if (!postUpdateIfValid) return false
if (!doUpdateIfValid) return false
nextValue = mergedMin
}
if (props.validator && !props.validator(nextValue)) return false
if (postUpdateIfValid) doUpdateValue(nextValue)
if (doUpdateIfValid) doUpdateValue(nextValue)
return nextValue
}
}
@ -307,6 +300,7 @@ export default defineComponent({
}
function handleUpdateDisplayedValue (value: string): void {
displayedValueRef.value = value
deriveValueFromDisplayedValue()
}
watch(mergedValueRef, () => {
deriveDisplayedValueFromValue()
@ -360,7 +354,6 @@ export default defineComponent({
bordered={this.mergedBordered}
value={this.displayedValue}
onUpdateValue={this.handleUpdateDisplayedValue}
passively-activated
theme={this.mergedTheme.peers.Input}
themeOverrides={this.mergedTheme.peerOverrides.Input}
builtinThemeOverrides={this.inputThemeOverrides}

View File

@ -0,0 +1 @@
export type OnUpdateValue = (value: number | null) => void

View File

@ -1,17 +0,0 @@
import { mount } from '@vue/test-utils'
import { NInputNumber } from '../index'
import { NButton } from '../../button'
describe('n-input-number', () => {
it('should work with import on demand', () => {
mount(NInputNumber)
})
it('should work with `show-button` prop', async () => {
const wrapper = mount(NInputNumber)
expect(wrapper.findComponent(NButton).exists()).toBe(true)
await wrapper.setProps({ showButton: false })
expect(wrapper.findComponent(NButton).exists()).toBe(false)
})
})

View File

@ -0,0 +1,58 @@
import { mount } from '@vue/test-utils'
import { NInputNumber } from '../index'
import { NButton } from '../../button'
describe('n-input-number', () => {
it('should work with import on demand', () => {
mount(NInputNumber)
})
it('should work with `show-button` prop', async () => {
const wrapper = mount(NInputNumber)
expect(wrapper.findComponent(NButton).exists()).toBe(true)
await wrapper.setProps({ showButton: false })
expect(wrapper.findComponent(NButton).exists()).toBe(false)
})
it('should work with default value', async () => {
const wrapper = mount(NInputNumber, {
props: {
defaultValue: 1
}
})
expect(wrapper.find('input').element.value).toEqual('1')
})
it('should not trigger update if value is same', async () => {
const onUpdateValue = jest.fn()
const wrapper = mount(NInputNumber, {
attachTo: document.body,
props: {
defaultValue: 1,
onUpdateValue
}
})
wrapper.find('input').element.value = ''
await wrapper.find('input').trigger('input')
expect(onUpdateValue).toHaveBeenCalledWith(null)
wrapper.unmount()
})
it('trigger focus & blur event', () => {
const onFocus = jest.fn()
const onBlur = jest.fn()
const wrapper = mount(NInputNumber, {
attachTo: document.body,
props: {
onFocus,
onBlur
}
})
wrapper.find('input').element.focus()
expect(onFocus).toHaveBeenCalledTimes(1)
wrapper.find('input').element.blur()
expect(onBlur).toHaveBeenCalledTimes(1)
wrapper.unmount()
})
})

View File

@ -1,8 +0,0 @@
import { mount } from '@vue/test-utils'
import { NInput } from '../index'
describe('n-input', () => {
it('should work with import on demand', () => {
mount(NInput)
})
})

View File

@ -0,0 +1,19 @@
import { mount } from '@vue/test-utils'
import { NInput } from '../index'
describe('n-input', () => {
it('should work with import on demand', () => {
mount(NInput)
})
it('should call input callbacks', async () => {
const onUpdateValue = jest.fn()
const wrapper = mount(NInput, {
props: {
onUpdateValue
}
})
wrapper.find('input').element.value = 'cool'
await wrapper.find('input').trigger('input')
expect(onUpdateValue).toHaveBeenCalledWith('cool')
})
})