Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pro:form): add ProForm component #1028

Merged
merged 2 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cdk/forms/src/messages/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const enUSMessages = {
return `Please enter a number no greater than ${err.max}`
},
range: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `Please enter a number between ${err.min - 1}-${err.max + 1}`
return `Please enter a number between ${err.min}-${err.max}`
},
minLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { minLength, isArray } = err
Expand Down
6 changes: 3 additions & 3 deletions packages/cdk/forms/src/messages/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export const zhCNMessages = {
return `请输入正确的邮箱格式${example ? ', 例: ' + example : ''}`
},
min: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `请输入一个不小于 ${err.min} 的数字`
return `请输入不小于 ${err.min} 的数字`
},
max: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `请输入一个不大于 ${err.max} 的数字`
return `请输入不大于 ${err.max} 的数字`
},
range: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
return `请输入一个 ${err.min - 1}-${err.max + 1} 之间的数字`
return `请输入 ${err.min}-${err.max} 之间的数字`
},
minLength: (err: Omit<ValidateError, 'message'>, __: AbstractControl): string => {
const { minLength, isArray } = err
Expand Down
7 changes: 6 additions & 1 deletion packages/cdk/forms/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
AsyncValidatorFn,
ValidateError,
ValidateErrors,
ValidateMessage,
ValidateMessageFn,
ValidateMessages,
ValidatorFn,
Expand All @@ -35,6 +36,10 @@ export class Validators {
Validators.messages = { ...Validators.messages, ...messages }
}

static getMessage(key: string): ValidateMessage | undefined {
return Validators.messages[key]
}

static getError(
key: string,
control: AbstractControl,
Expand Down Expand Up @@ -207,7 +212,7 @@ export class Validators {

/** checks whether string or array is empty */
function isEmpty(val: any): boolean {
return isNil(val) || (val.length === 0 && (isString(val) || isArray(val)))
return isNil(val) || ((isString(val) || isArray(val)) && val.length === 0)
}

/** checks whether variable has length props */
Expand Down
4 changes: 2 additions & 2 deletions packages/components/checkbox/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export const checkboxProps = {
size: { type: String as PropType<FormSize>, default: undefined },

// events
'onUpdate:checked': { type: [Function, Array] as PropType<MaybeArray<<K = CheckValue>(checked: K) => void>> },
'onUpdate:checked': { type: [Function, Array] as PropType<MaybeArray<(checked: any) => void>> },
onChange: {
type: [Function, Array] as PropType<MaybeArray<<K = CheckValue>(newChecked: K, oldChecked: K) => void>>,
type: [Function, Array] as PropType<MaybeArray<(newChecked: any, oldChecked: any) => void>>,
},
onBlur: { type: [Function, Array] as PropType<MaybeArray<(evt: FocusEvent) => void>> },
onFocus: { type: [Function, Array] as PropType<MaybeArray<(evt: FocusEvent) => void>> },
Expand Down
2 changes: 1 addition & 1 deletion packages/components/config/__tests__/globalConfig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ParentComponent = {
components: { ChildComponent },
template: `<ChildComponent />`,
setup() {
const [, changeConfig] = useGlobalConfig('form', true)
const [, changeConfig] = useGlobalConfig('form', {})
return { changeConfig }
},
}
Expand Down
6 changes: 2 additions & 4 deletions packages/components/form/demo/CustomizedValidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ order: 7

## zh

我们提供了 `status`, `hasFeedback` 和 `message` 等属性,你可以不通过 `control` 来设置验证状态和提示信息。
我们提供了 `status`, 和 `message` 等属性,你可以不通过 `control` 来设置验证状态和提示信息。

- `status`: 校验状态, 共3种: `valid`, `validating`, `invalid`。
- `hasFeedback`: 添加反馈图标。
- `message`: 设置提示信息。

## en

We provide properties like `status`, `hasFeedback` and `message` to customize validate status and message, without using `control`.
We provide properties like `status` and `message` to customize validate status and message, without using `control`.

- `status`: validate status of form components which could be `valid`, `validating` and `invalid`.
- `hasFeedback`: display feed icon of input control
- `message`: display validate message.
11 changes: 6 additions & 5 deletions packages/components/form/demo/CustomizedValidation.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<template>
<IxForm class="demo-form" hasFeedback labelCol="6">
<IxFormItem label="Valid" status="valid" :message="messageMap">
<IxForm class="demo-form" labelCol="6">
<IxFormItem label="Valid" status="valid" :message="messageMap.valid">
<IxInput v-model:value="formValue.valid"></IxInput>
</IxFormItem>
<IxFormItem label="Validating" status="validating" :message="messageMap">
<IxFormItem label="Validating" status="validating" :message="messageMap.validating">
<IxInput v-model:value="formValue.validating"></IxInput>
</IxFormItem>
<IxFormItem label="Invalid" status="invalid" :message="messageMap">
<IxFormItem label="Invalid" status="invalid" :message="messageMap.invalid">
<IxInput v-model:value="formValue.invalid"></IxInput>
</IxFormItem>
<IxFormItem label="Dynamic" :status="status" :message="messageMap">
<IxFormItem label="Dynamic" :status="status" :message="getMessage">
<IxInput v-model:value="formValue.dynamic"></IxInput>
</IxFormItem>
<IxFormItem :controlCol="{ offset: 6 }">
Expand Down Expand Up @@ -39,6 +39,7 @@ const messageMap = {
validating: 'This is validating field!',
invalid: 'This is invalid field!',
}
const getMessage = () => messageMap[status.value]

const changeStatus = () => {
const currStatus = status.value
Expand Down
2 changes: 1 addition & 1 deletion packages/components/form/demo/DynamicItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { useFormArray, useFormControl, useFormGroup } from '@idux/cdk/forms'

interface FormValue {
array: string[]
[key: string]: any
[key: string]: unknown
}

const formArray = useFormArray([['']])
Expand Down
2 changes: 1 addition & 1 deletion packages/components/form/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ order: 0
| `labelTooltip` | 配置表单文本的提示信息 | `string \| #labelTooltip` | - | - | 通常用于对表单本文的解释说名 |
| `labelTooltipIcon` | 配置表单文本的提示信息icon | `string` | - | -| - |
| `required` | 必填样式设置 | `boolean` | `false` | - | 仅控制样式 |
| `message` | 手动指定表单项的校验提示 | `string \| (control?: AbstractControl) => string \| FormValidateMessage` | - | - | 传入 `string` 时,为 `invalid` 状态的提示 |
| `message` | 手动指定表单项的校验提示 | `string \| (control?: AbstractControl) => string` | - | - | 传入 `string` 时,为 `invalid` 状态的提示 |
| `status` | 手动指定表单项的校验状态 | `valid \| invalid \| validating` | - | - | - |

### IxFormWrapper
Expand Down
1 change: 0 additions & 1 deletion packages/components/form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export type {
FormColType,
FormLabelAlign,
FormLayout,
FormValidateMessage,
FormSize,
} from './src/types'

Expand Down
10 changes: 5 additions & 5 deletions packages/components/form/src/FormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ function renderControl(
extra: extraSlot,
extraMessage: extraMessageSlot,
description: descriptionSlot,
message: messageSlot,
} = slots
if (__DEV__ && (extra || extraSlot)) {
Logger.warn('components/form', '`extra` was deprecated, please use `description` instead.')
Expand All @@ -127,20 +128,19 @@ function renderControl(
<IxIcon name={statusIcon.value} />
</span>
)
const messageNode = message.value && <div class={`${prefixCls}-message`}>{message.value}</div>
const tooltipNode = renderTooltip(controlTooltipSlot, controlTooltip, controlTooltipIcon.value)
const messageNode = messageSlot ? messageSlot(message.value) : message.value
const _descriptionSlot = extraSlot || extraMessageSlot || descriptionSlot
const descriptionNode = _descriptionSlot ? _descriptionSlot() : extra || extraMessage || description
const descriptionWrapper = descriptionNode && <div class={`${prefixCls}-description`}>{descriptionNode}</div>
const tooltipNode = renderTooltip(controlTooltipSlot, controlTooltip, controlTooltipIcon.value)
return (
<IxCol class={`${prefixCls}-control`} {...controlColConfig.value}>
<div class={`${prefixCls}-control-input`}>
<div class={`${prefixCls}-control-input-content`}>{slots.default && slots.default()}</div>
{statusNode}
{tooltipNode && <span class={`${prefixCls}-control-tooltip`}>{tooltipNode}</span>}
</div>
{messageNode}
{descriptionWrapper}
{message.value && <div class={`${prefixCls}-message`}>{messageNode}</div>}
{descriptionNode && <div class={`${prefixCls}-description`}>{descriptionNode}</div>}
</IxCol>
)
}
Expand Down
3 changes: 3 additions & 0 deletions packages/components/form/src/composables/useFormItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ function useMessage(
status: ComputedRef<ValidateStatus | undefined>,
) {
const locale = useGlobalConfig('locale')
/**
* @deprecated
*/
const messages = computed(() => {
const message = props.message
return isString(message) || isFunction(message) ? { invalid: message } : message || {}
Expand Down
15 changes: 7 additions & 8 deletions packages/components/form/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms'
import type { ExtractInnerPropTypes, ExtractPublicPropTypes } from '@idux/cdk/utils'
import type { ColProps } from '@idux/components/grid'
import type { ColProps, RowProps } from '@idux/components/grid'
import type { DefineComponent, HTMLAttributes, PropType } from 'vue'

const colProp = [Number, String, Object] as PropType<number | string | ColProps>
Expand Down Expand Up @@ -49,7 +49,6 @@ export type FormInstance = InstanceType<DefineComponent<FormProps>>
export type FormColType = number | string | ColProps
export type FormLabelAlign = 'start' | 'end'
export type FormLayout = 'horizontal' | 'vertical' | `inline`
export type FormValidateMessage = Partial<Record<ValidateStatus, string | ((control: AbstractControl) => string)>>
export type FormSize = 'sm' | 'md' | 'lg'

export const formItemProps = {
Expand Down Expand Up @@ -77,19 +76,17 @@ export const formItemProps = {
* @deprecated Use `description` instead.
*/
extraMessage: String,
label: String,
label: [String, Number] as PropType<string | number>,
labelAlign: String as PropType<FormLabelAlign>,
labelCol: colProp,
labelFor: String,
labelFor: [String, Number] as PropType<string | number>,
labelTooltip: String,
labelTooltipIcon: String,
required: {
type: Boolean,
default: false,
},
message: [String, Function, Object] as PropType<
string | ((control: AbstractControl) => string) | FormValidateMessage
>,
message: [String, Function, Object] as PropType<string | ((control: AbstractControl) => string)>,
status: String as PropType<ValidateStatus>,
/**
* @deprecated
Expand All @@ -105,7 +102,9 @@ export type FormItemPublicProps = Omit<
ExtractPublicPropTypes<typeof formItemProps>,
'extra' | 'extraMessage' | 'hasFeedback' | 'statusIcon'
>
export type FormItemComponent = DefineComponent<Omit<HTMLAttributes, keyof FormItemPublicProps> & FormItemPublicProps>
export type FormItemComponent = DefineComponent<
Omit<HTMLAttributes, keyof FormItemPublicProps> & FormItemPublicProps & RowProps
>
export type FormItemInstance = InstanceType<DefineComponent<FormItemProps>>

export const formWrapperProps = {
Expand Down
1 change: 1 addition & 0 deletions packages/components/modal/src/ModalWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ function watchVisibleChange(
wrapperRef: Ref<HTMLDivElement | undefined>,
sentinelStartRef: Ref<HTMLDivElement | undefined>,
mask: ComputedRef<boolean>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
draggableResult: Ref<any>,
) {
let lastOutSideActiveElement: HTMLElement | null = null
Expand Down
1 change: 0 additions & 1 deletion packages/components/table/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export type TableColumn<T = any, V = any> =
| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `type` | 列类型 | `'expandable'` | - | - | 必填 |

| `disabled` | 设置是否允许行展开 | `(record:T) => boolean` | - | - | - |
| `icon` | 展开按钮图标 | `string` | `'right'` | ✅ | - |
| `indent` | 展示树形数据时,每层缩进的宽度 | `number` | `12` | - | - |
Expand Down
17 changes: 16 additions & 1 deletion packages/pro/config/src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,29 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { IxInput } from '@idux/components/input'
import { type ProFormJsonSchema } from '@idux/pro/form'
import { zhCN } from '@idux/pro/locales'

import { type ProGlobalConfig } from './types'

export const defaultConfig: ProGlobalConfig = {
common: { prefixCls: 'ix-pro' },
locale: zhCN,

form: {
ajvOptions: {
allErrors: true,
loopEnum: 50,
code: { esm: true },
},
autoId: true,
autoLabelFor: true,
formatComponents: {
default: { component: IxInput },
},
ignoreKeywords: ['type', 'enum'],
schemaFormatter: (fields, schema) => ({ fields: fields || {}, schema: schema || ({} as ProFormJsonSchema) }),
},
table: {
columnIndexable: {
align: 'center',
Expand Down
43 changes: 42 additions & 1 deletion packages/pro/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

/* eslint-disable @typescript-eslint/no-explicit-any */

import type { PortalTargetType } from '@idux/cdk/portal'
import type { ProFormSchemaFormatter } from '@idux/pro/form'
import type { ProLocale } from '@idux/pro/locales'
import type { ProTableColumnIndexable } from '@idux/pro/table'
import type { VNode } from 'vue'
import type { Options as AjvOptions } from 'ajv'
import type { Component, VNode } from 'vue'

export interface ProGlobalConfig {
common: ProCommonConfig
locale: ProLocale

form: ProFormConfig
table: ProTableConfig
tree: ProTreeConfig
search: ProSearchConfig
Expand All @@ -25,6 +30,42 @@ export interface ProCommonConfig {
prefixCls: string
}

interface ProFormConfigFormatComponent {
component?: string | Component
componentProps?: any
}

export interface ProFormConfig {
/**
* [ajv](https://ajv.js.org/options.html) 参数
*/
ajvOptions: AjvOptions
autoId: boolean
autoLabelFor: boolean
/**
* 根据 format 字段渲染的组件
*
* @example { 'date': { component: IxDatePicker, componentProps: { format: 'dd/MM/yyyy' }}}
*/
formatComponents: {
default?: ProFormConfigFormatComponent
data?: ProFormConfigFormatComponent
time?: ProFormConfigFormatComponent
'date-time'?: ProFormConfigFormatComponent
uri?: ProFormConfigFormatComponent
email?: ProFormConfigFormatComponent
ipv4?: ProFormConfigFormatComponent
ipv6?: ProFormConfigFormatComponent
[key: string]: ProFormConfigFormatComponent | undefined
}
/**
* 忽略某些数据类型 (type) 的校验, 默认:['type', 'enum']
*/
ignoreKeywords: string[]

schemaFormatter: ProFormSchemaFormatter
}

export interface ProTableConfig {
columnIndexable: Omit<ProTableColumnIndexable, 'type'>
}
Expand Down
4 changes: 3 additions & 1 deletion packages/pro/default.less
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@import './form/style/themes/default.less';
@import './layout/style/themes/default.less';
@import './search/style/themes/default.less';
@import './table/style/themes/default.less';
@import './transfer/style/themes/default.less';
@import './tree/style/themes/default.less';
@import './search/style/themes/default.less';

25 changes: 25 additions & 0 deletions packages/pro/form/__tests__/proForm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MountingOptions, mount } from '@vue/test-utils'

import { renderWork } from '@tests'

import ProForm from '../src/ProForm'
import { ProFormProps } from '../src/types'

describe.skip('ProForm', () => {
const ProFormMount = (options?: MountingOptions<Partial<ProFormProps>>) =>
mount(ProForm, { ...(options as MountingOptions<ProFormProps>) })

renderWork<ProFormProps>(ProForm, {
props: {},
})

test('xxx work', async () => {
const wrapper = ProFormMount({ props: { xxx: 'Xxx' } })

expect(wrapper.classes()).toContain('ix-Xxx')

await wrapper.setProps({ xxx: 'Yyy' })

expect(wrapper.classes()).toContain('ix-Yyy')
})
})
Loading