Skip to content

Commit

Permalink
feat(cdk: forms): getValud support skip disabled control (#897)
Browse files Browse the repository at this point in the history
* feat(comp: input,textarea): add trim prop

BREAKING CHANGE: `ValidatorOptions.trim` was deprecated, please use `trim` of `IxInput` or
`IxTextarea` instead.
  • Loading branch information
danranVm committed May 10, 2022
1 parent 645301c commit 9ba3eca
Show file tree
Hide file tree
Showing 20 changed files with 147 additions and 122 deletions.
5 changes: 5 additions & 0 deletions packages/cdk/forms/__tests__/formArray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ describe('formArray.ts', () => {
array.setValue([{ control: 'test1' }, { control: 'test2' }])

expect(array.getValue()).toEqual([{ ...basicValue, control: 'test1' }])

array.get(0)!.disable()

expect(array.getValue()).toEqual([{ ...basicValue, control: 'test1' }])
expect(array.getValue({ skipDisabled: true })).toEqual([])
})

test('markAsBlurred and markAsUnblurred work', async () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/cdk/forms/__tests__/formGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ describe('formGroup.ts', () => {
group.setValue({ control: 'test1' })

expect(group.getValue()).toEqual({ ...basicValue, control: 'test1' })

group.get('control')!.disable()

expect(group.getValue()).toEqual({ ...basicValue, control: 'test1' })
const { control, ...rest } = basicValue
expect(group.getValue({ skipDisabled: true })).toEqual(rest)
})

test('markAsBlurred and markAsUnblurred work', async () => {
Expand Down
1 change: 0 additions & 1 deletion packages/cdk/forms/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export function useFormControl<T>(
| `disabled` | 默认禁用当前控件 | `boolean` | - | - |
| `name` | 控件的名称 | `string` | - | 通常用于自定义提示信息 |
| `trigger` | 验证器触发的时机 | `'change' \| 'blur' \| 'submit'` | `change` | - |
| `trim` | 是否去除首尾空字符串 | `boolean` | - | 默认不去除 |
| `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | - |
| `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | - |

Expand Down
9 changes: 6 additions & 3 deletions packages/cdk/forms/docs/Overview.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { AbstractControl, useValueAccessor, useValueControl } from '@idux/cdk/fo
defineProps<{
value?: string
control?: string | AbstractControl
control?: string | number | AbstractControl
disabled?: boolean
}>()
Expand Down Expand Up @@ -341,14 +341,17 @@ export declare abstract class AbstractControl<T = any> {
* Sets a new value for the control.
*
* @param value The new value.
* @param options Configuration options that emits events when the value changes.
* @param options
* * `dirty`: Marks it dirty, default is false.
*/
abstract setValue(value: T | Partial<T>, options?: { dirty?: boolean }): void;
/**
* The aggregate value of the control.
*
* @param options
* * `skipDisabled`: Ignore value of disabled control, default is false.
*/
abstract getValue(): T;
abstract getValue(options?: { skipDisabled?: boolean }): T;
/**
* Resets the control, marking it `unblurred` `pristine`, and setting the value to initialization value.
*/
Expand Down
27 changes: 20 additions & 7 deletions packages/cdk/forms/src/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export abstract class AbstractControl<T = any> {
}

/**
* @deprecated
*
* Whether to remove the first and tail space
* Possible value: true | false
* Default value: false
Expand Down Expand Up @@ -188,15 +190,18 @@ export abstract class AbstractControl<T = any> {
* Sets a new value for the control.
*
* @param value The new value.
* @param options Configuration options that emits events when the value changes.
* @param options
* * `dirty`: Marks it dirty, default is false.
*/
abstract setValue(value: T | Partial<T>, options?: { dirty?: boolean }): void

/**
* The aggregate value of the control.
*
* @param options
* * `skipDisabled`: Ignore value of disabled control, default is false.
*/
abstract getValue(): T
abstract getValue(options?: { skipDisabled?: boolean }): T

protected abstract _forEachControls(cb: (v: AbstractControl, k: keyof T) => void): void

Expand All @@ -219,7 +224,7 @@ export abstract class AbstractControl<T = any> {
* Running validations manually, rather than automatically.
*/
async validate(): Promise<ValidateErrors | undefined> {
if (this._controls.value) {
if (!this._disabled.value && this._controls.value) {
const validates: Promise<ValidateErrors | undefined>[] = []
this._forEachControls(control => validates.push(control.validate()))
if (validates.length > 0) {
Expand All @@ -246,11 +251,12 @@ export abstract class AbstractControl<T = any> {
*/
enable(): void {
this._disabled.value = false
this._validate()

if (this._controls.value) {
this._forEachControls(control => control.enable())
}

this._validate()
}

/**
Expand Down Expand Up @@ -596,9 +602,13 @@ export class FormGroup<T extends Record<string, any> = Record<string, any>> exte
})
}

getValue(): T {
getValue(options: { skipDisabled?: boolean } = {}): T {
const { skipDisabled } = options
const value = {} as T
this._forEachControls((control, key) => {
if (skipDisabled && control.disabled.value) {
return
}
value[key] = control.getValue()
})
return value
Expand Down Expand Up @@ -753,8 +763,11 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
})
}

getValue(): T {
return this._controls.value.map(control => control.getValue()) as T
getValue(options: { skipDisabled?: boolean } = {}): T {
const { skipDisabled } = options
return this._controls.value
.filter(control => !skipDisabled || !control.disabled.value)
.map(control => control.getValue()) as T
}

protected _calculateInitValue(): T {
Expand Down
5 changes: 4 additions & 1 deletion packages/cdk/forms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ export interface ValidatorOptions {
disabled?: boolean
name?: string
trigger?: TriggerType
trim?: boolean
validators?: ValidatorFn | ValidatorFn[]
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[]
/**
* @deprecated
*/
trim?: boolean
}

export type ValidateStatus = 'valid' | 'invalid' | 'validating'
15 changes: 8 additions & 7 deletions packages/components/cascader/demo/Searchable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@
<script setup lang="ts">
import { ref } from 'vue'
import { CascaderData } from '@idux/components/cascader'
import { type CascaderData } from '@idux/components/cascader'
import { type RadioData } from '@idux/components/radio'
const searchable = ref(true)
const searchableData = [
{ key: true, label: 'true' },
{ key: 'overlay', label: 'overlay' },
{ key: false, label: 'false' },
] as RadioData[]
const dataSource: CascaderData[] = [
{
key: 'components',
Expand Down Expand Up @@ -124,10 +131,4 @@ const dataSource: CascaderData[] = [
],
},
]
const searchableData = [
{ label: 'true', value: true },
{ label: 'overlay', value: 'overlay' },
{ label: 'false', value: false },
]
</script>
27 changes: 11 additions & 16 deletions packages/components/input/__tests__/input.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MountingOptions, mount } from '@vue/test-utils'
import { nextTick, ref } from 'vue'
import { nextTick } from 'vue'

import { renderWork } from '@tests'

Expand All @@ -12,42 +12,37 @@ describe('Input', () => {
renderWork(IxInput)

test('v-model work', async () => {
const valueRef = ref('init value')
const wrapper = mount({
components: { IxInput },
template: `<IxInput v-model:value="valueRef" />`,
setup() {
return { valueRef }
},
})
const onUpdateValue = vi.fn()
const wrapper = InputMount({ props: { value: 'init value', 'onUpdate:value': onUpdateValue } })
const input = wrapper.find('input')

expect(input.element.value).toBe('init value')

await input.setValue('input setValue')

expect(valueRef.value).toBe('input setValue')
expect(onUpdateValue).toBeCalledTimes(1)
expect(onUpdateValue).toBeCalledWith('input setValue')

valueRef.value = 'valueRef change'
await nextTick()
await wrapper.setProps({ value: 'wrapper setProps' })

expect(input.element.value).toBe('valueRef change')
expect(input.element.value).toBe('wrapper setProps')

input.element.value = '使用拼音'
await input.trigger('compositionstart')

expect(wrapper.emitted()).toHaveProperty('compositionstart')
expect(valueRef.value).toBe('valueRef change')
expect(onUpdateValue).toBeCalledTimes(1)

await input.trigger('input')

expect(wrapper.emitted()).toHaveProperty('input')
expect(valueRef.value).toBe('valueRef change')
expect(onUpdateValue).toBeCalledTimes(1)

await input.trigger('compositionend')

expect(wrapper.emitted()).toHaveProperty('compositionend')
expect(valueRef.value).toBe('使用拼音')
expect(onUpdateValue).toBeCalledTimes(2)
expect(onUpdateValue).toBeCalledWith('使用拼音')
})

test('controlled value work', async () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/components/input/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ subtitle: 输入框
| `readonly` | 是否只读状态 | `boolean` | `false` | - | - |
| `size` | 设置大小 | `'sm' \| 'md' \| 'lg'` | `'md'` || - |
| `suffix` | 设置后缀图标 | `string \| #suffix` | - | - | - |
| `trim` | 失去焦点后自动去除前后空格 | `boolean` | `false` | - | - |
| `onChange` | 值发生改变后的回调 | `(value: string, oldValue: string) => void` | - | - | - |
| `onClear` | 清除图标被点击后的回调 | `(evt: MouseEvent) => void` | - | - | - |

<!--- insert less variable begin --->
Expand Down
45 changes: 23 additions & 22 deletions packages/components/input/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,46 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { ExtractInnerPropTypes, ExtractPublicPropTypes } from '@idux/cdk/utils'
import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils'
import type { FormSize } from '@idux/components/form'
import type { DefineComponent, InputHTMLAttributes } from 'vue'
import type { DefineComponent, InputHTMLAttributes, PropType } from 'vue'

import { controlPropDef } from '@idux/cdk/forms'
import { IxPropTypes } from '@idux/cdk/utils'

export type TextareaResize = 'none' | 'both' | 'horizontal' | 'vertical'
export type TextareaAutoRows = { minRows: number; maxRows: number }

export const commonProps = {
value: IxPropTypes.string,
control: controlPropDef,
clearable: IxPropTypes.bool,
clearIcon: IxPropTypes.string,
disabled: IxPropTypes.bool.def(false),
readonly: IxPropTypes.bool.def(false),
size: IxPropTypes.oneOf<FormSize>(['sm', 'md', 'lg']),
value: { type: String, default: undefined },

clearable: { type: Boolean, default: undefined },
clearIcon: { type: String, default: undefined },
disabled: { type: Boolean, default: false },
readonly: { type: Boolean, default: false },
size: { type: String as PropType<FormSize>, default: undefined },
trim: { type: Boolean, default: false },

// events
'onUpdate:value': IxPropTypes.emit<(value: string) => void>(),
onChange: IxPropTypes.emit<(value: string, oldValue: string) => void>(),
onClear: IxPropTypes.emit<(evt: Event) => void>(),
onCompositionStart: IxPropTypes.emit<(evt: CompositionEvent) => void>(),
onCompositionEnd: IxPropTypes.emit<(evt: CompositionEvent) => void>(),
onInput: IxPropTypes.emit<(evt: Event) => void>(),
onFocus: IxPropTypes.emit<(evt: FocusEvent) => void>(),
onBlur: IxPropTypes.emit<(evt: FocusEvent) => void>(),
'onUpdate:value': [Function, Array] as PropType<MaybeArray<(value: string) => void>>,
onChange: [Function, Array] as PropType<MaybeArray<(value: string, oldValue: string) => void>>,
onClear: [Function, Array] as PropType<MaybeArray<(evt: Event) => void>>,
onCompositionStart: [Function, Array] as PropType<MaybeArray<(evt: CompositionEvent) => void>>,
onCompositionEnd: [Function, Array] as PropType<MaybeArray<(evt: CompositionEvent) => void>>,
onInput: [Function, Array] as PropType<MaybeArray<(evt: Event) => void>>,
onFocus: [Function, Array] as PropType<MaybeArray<(evt: FocusEvent) => void>>,
onBlur: [Function, Array] as PropType<MaybeArray<(evt: FocusEvent) => void>>,
}

export type CommonProps = ExtractInnerPropTypes<typeof commonProps>

export const inputProps = {
...commonProps,
addonAfter: IxPropTypes.string,
addonBefore: IxPropTypes.string,
borderless: IxPropTypes.bool,
prefix: IxPropTypes.string,
suffix: IxPropTypes.string,
addonAfter: { type: String, default: undefined },
addonBefore: { type: String, default: undefined },
borderless: { type: Boolean, default: undefined },
prefix: { type: String, default: undefined },
suffix: { type: String, default: undefined },
}

export type InputProps = ExtractInnerPropTypes<typeof inputProps>
Expand Down
24 changes: 16 additions & 8 deletions packages/components/input/src/useInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,28 @@ export function useInput(
isFocused.value = false
callEmit(props.onBlur, evt)
accessor.markAsBlurred()

if (props.trim) {
setValue((evt.target as HTMLInputElement).value.trim())
}
}

const { elementRef, focus, blur } = useFormFocusMonitor<HTMLInputElement | HTMLTextAreaElement>({
handleFocus,
handleBlur,
})

const setValue = (value: string) => {
const oldValue = toRaw(accessor.valueRef.value)
if (value !== oldValue) {
accessor.setValue(value)
callEmit(props.onChange, value, oldValue)

//controlled value , see: https://github.com/IDuxFE/idux/issues/495
nextTick(() => syncValue())
}
}

const syncValue = () => {
const element = elementRef.value
const value = accessor.valueRef.value ?? ''
Expand All @@ -77,15 +92,8 @@ export function useInput(
if (isComposing.value) {
return
}
const { value } = evt.target as HTMLInputElement
const oldValue = toRaw(accessor.valueRef.value)
if (value !== oldValue) {
accessor.setValue(value)
callEmit(props.onChange, value, oldValue)

//controlled value , see: https://github.com/IDuxFE/idux/issues/495
nextTick(() => syncValue())
}
setValue((evt.target as HTMLInputElement).value)
}

const handleCompositionStart = (evt: CompositionEvent) => {
Expand Down
3 changes: 1 addition & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@
"vue-types": "^3.0.0"
},
"devDependencies": {
"axios": "^0.25.0",
"vue": "^3.2.0",
"vue-router": "^4.0.12"
"vue-router": "^4.0.0"
},
"peerDependencies": {
"vue": "^3.2.0"
Expand Down

0 comments on commit 9ba3eca

Please sign in to comment.