From 997293ce10bb85035a9f5c985f56fcbe27682371 Mon Sep 17 00:00:00 2001 From: danranvm Date: Wed, 28 Sep 2022 14:17:34 +0800 Subject: [PATCH] feat(comp:*): all input components support setting status closed #1099 --- .../components/_private/input/src/Input.tsx | 3 +- .../components/_private/input/src/types.ts | 2 + .../_private/selector/src/Selector.tsx | 9 +- .../components/_private/selector/src/types.ts | 5 +- .../_private/selector/style/index.less | 16 +- .../_private/trigger/src/Trigger.tsx | 24 +-- .../components/_private/trigger/src/types.ts | 5 +- .../_private/trigger/style/index.less | 16 +- packages/components/cascader/docs/Api.zh.md | 1 + packages/components/cascader/src/Cascader.tsx | 7 +- packages/components/cascader/src/types.ts | 3 +- .../components/date-picker/docs/Api.zh.md | 1 + .../components/date-picker/src/DatePicker.tsx | 2 +- .../date-picker/src/DateRangePicker.tsx | 2 +- .../src/composables/usePickerState.ts | 11 +- .../src/composables/useTriggerProps.ts | 11 +- .../date-picker/src/trigger/RangeTrigger.tsx | 4 +- .../date-picker/src/trigger/Trigger.tsx | 4 +- packages/components/date-picker/src/types.ts | 3 +- packages/components/form/index.ts | 1 + packages/components/form/src/FormItem.tsx | 10 +- .../components/form/src/composables/public.ts | 28 +++- .../form/src/composables/useFormItem.ts | 66 +------- packages/components/form/style/status.less | 143 ------------------ packages/components/input/docs/Api.zh.md | 1 + packages/components/input/index.ts | 2 +- packages/components/input/src/Input.tsx | 5 +- packages/components/input/src/types.ts | 9 +- packages/components/input/src/useInput.ts | 27 +++- packages/components/input/style/index.less | 56 +++++++ packages/components/select/docs/Api.zh.md | 1 + packages/components/select/src/Select.tsx | 7 +- packages/components/select/src/types.ts | 3 +- packages/components/textarea/docs/Api.zh.md | 1 + packages/components/textarea/src/Textarea.tsx | 8 +- packages/components/textarea/src/types.ts | 4 +- packages/components/textarea/style/index.less | 28 ++++ .../components/time-picker/docs/Api.zh.md | 10 +- .../components/time-picker/src/TimePicker.tsx | 8 +- .../time-picker/src/TimeRangePicker.tsx | 9 +- .../src/composables/usePickerState.ts | 11 +- .../src/composables/useTriggerProps.ts | 6 +- packages/components/time-picker/src/tokens.ts | 2 - packages/components/time-picker/src/types.ts | 3 +- .../components/tree-select/docs/Api.zh.md | 1 + .../components/tree-select/src/TreeSelect.tsx | 7 +- packages/components/tree-select/src/types.ts | 3 +- packages/pro/textarea/src/types.ts | 4 +- 48 files changed, 287 insertions(+), 306 deletions(-) diff --git a/packages/components/_private/input/src/Input.tsx b/packages/components/_private/input/src/Input.tsx index 8b4593d82..4cd4b2e40 100644 --- a/packages/components/_private/input/src/Input.tsx +++ b/packages/components/_private/input/src/Input.tsx @@ -26,11 +26,12 @@ export default defineComponent({ expose({ getInputElement }) const classes = computed(() => { - const { borderless, clearable, disabled, focused, size, addonAfter, addonBefore, prefix, suffix } = props + const { borderless, clearable, disabled, focused, size, status, addonAfter, addonBefore, prefix, suffix } = props const prefixCls = mergedPrefixCls.value return normalizeClass({ [prefixCls]: true, [`${prefixCls}-${size}`]: true, + [`${prefixCls}-${status}`]: !!status, [`${prefixCls}-borderless`]: borderless, [`${prefixCls}-clearable`]: clearable, [`${prefixCls}-disabled`]: disabled, diff --git a/packages/components/_private/input/src/types.ts b/packages/components/_private/input/src/types.ts index ce6801fc9..c15d153a4 100644 --- a/packages/components/_private/input/src/types.ts +++ b/packages/components/_private/input/src/types.ts @@ -5,6 +5,7 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { ValidateStatus } from '@idux/cdk/forms' import type { ExtractInnerPropTypes, ExtractPublicPropTypes } from '@idux/cdk/utils' import type { FormSize } from '@idux/components/form' import type { DefineComponent, InputHTMLAttributes, PropType } from 'vue' @@ -35,6 +36,7 @@ export const inputProps = { }, prefix: String, size: String as PropType, + status: String as PropType, suffix: String, onClear: Function as PropType<(evt: MouseEvent) => void>, } as const diff --git a/packages/components/_private/selector/src/Selector.tsx b/packages/components/_private/selector/src/Selector.tsx index e814be251..321bd1261 100644 --- a/packages/components/_private/selector/src/Selector.tsx +++ b/packages/components/_private/selector/src/Selector.tsx @@ -65,11 +65,14 @@ export default defineComponent({ const classes = computed(() => { const config = props.config - const { allowInput, className, borderless = config.borderless, multiple } = props + const { allowInput, className, borderless = config.borderless, multiple, status } = props const prefixCls = mergedPrefixCls.value return normalizeClass({ [className]: true, [prefixCls]: true, + [`${prefixCls}-${mergedSize.value}`]: true, + [`${prefixCls}-${status}`]: !!status, + [`${prefixCls}-allow-input`]: allowInput, [`${prefixCls}-borderless`]: borderless, [`${prefixCls}-clearable`]: mergedClearable.value, [`${prefixCls}-disabled`]: props.disabled, @@ -77,10 +80,8 @@ export default defineComponent({ [`${prefixCls}-multiple`]: multiple, [`${prefixCls}-opened`]: props.opened, [`${prefixCls}-readonly`]: props.readonly, - [`${prefixCls}-single`]: !multiple, [`${prefixCls}-searchable`]: mergedSearchable.value, - [`${prefixCls}-allow-input`]: allowInput, - [`${prefixCls}-${mergedSize.value}`]: true, + [`${prefixCls}-single`]: !multiple, }) }) diff --git a/packages/components/_private/selector/src/types.ts b/packages/components/_private/selector/src/types.ts index dd6008a48..b8e5d2405 100644 --- a/packages/components/_private/selector/src/types.ts +++ b/packages/components/_private/selector/src/types.ts @@ -5,11 +5,11 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { ValidateStatus } from '@idux/cdk/forms' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' +import type { FormSize } from '@idux/components/form' import type { DefineComponent, HTMLAttributes, PropType } from 'vue' -import { type FormSize } from '@idux/components/form' - export const selectorProps = { allowInput: { type: Boolean, required: true }, autocomplete: { type: String, required: true }, @@ -32,6 +32,7 @@ export const selectorProps = { readonly: { type: Boolean, required: true }, searchable: { type: [Boolean, String] as PropType, required: true }, size: { type: String as PropType, default: undefined }, + status: String as PropType, suffix: { type: String, default: undefined }, value: { type: Array, required: true }, diff --git a/packages/components/_private/selector/style/index.less b/packages/components/_private/selector/style/index.less index e8f187cf1..5b0330cf1 100644 --- a/packages/components/_private/selector/style/index.less +++ b/packages/components/_private/selector/style/index.less @@ -101,14 +101,26 @@ .borderless(); } - &-searchable &-content - &-allow-input &-content { + &-searchable &-content &-allow-input &-content { cursor: text; .@{selector-prefix}-input-inner { cursor: auto; } } + + // form status + &&-invalid { + &:hover .@{selector-prefix}-content, + .@{selector-prefix}-content { + border-color: @form-item-invalid-color; + } + + &.@{selector-prefix}-active .@{selector-prefix}-content { + border-color: @form-item-invalid-color; + box-shadow: @form-item-invalid-box-shadow; + } + } } .selector-icon() { diff --git a/packages/components/_private/trigger/src/Trigger.tsx b/packages/components/_private/trigger/src/Trigger.tsx index 7581ece86..ed4bc17ec 100644 --- a/packages/components/_private/trigger/src/Trigger.tsx +++ b/packages/components/_private/trigger/src/Trigger.tsx @@ -20,8 +20,6 @@ export default defineComponent({ const common = useGlobalConfig('common') const mergedPrefixCls = computed(() => `${common.prefixCls}-trigger`) - const isDisabled = computed(() => props.disabled) - const focusMonitor = useSharedFocusMonitor() const triggerRef = ref() onMounted(() => { @@ -41,19 +39,21 @@ export default defineComponent({ const classes = computed(() => { const prefixCls = mergedPrefixCls.value + const { className, size, status, borderless, disabled, focused, readonly } = props return normalizeClass({ - [`${props.className}`]: !!props.className, + [`${className}`]: !!className, [prefixCls]: true, - [`${prefixCls}-disabled`]: isDisabled.value, - [`${prefixCls}-borderless`]: props.borderless, - [`${prefixCls}-readonly`]: props.readonly, - [`${prefixCls}-focused`]: props.focused, - [`${prefixCls}-${props.size}`]: props.size, + [`${prefixCls}-${size}`]: true, + [`${prefixCls}-${status}`]: !!status, + [`${prefixCls}-borderless`]: borderless, + [`${prefixCls}-disabled`]: disabled, + [`${prefixCls}-focused`]: focused, + [`${prefixCls}-readonly`]: readonly, }) }) const handleClick = (evt: Event) => { - if (isDisabled.value) { + if (props.disabled) { return } @@ -61,7 +61,7 @@ export default defineComponent({ } const handleKeyDown = (evt: KeyboardEvent) => { - if (isDisabled.value) { + if (props.disabled) { return } @@ -69,7 +69,7 @@ export default defineComponent({ } const handleClear = (evt: MouseEvent) => { - if (isDisabled.value) { + if (props.disabled) { return } @@ -88,7 +88,7 @@ export default defineComponent({ ) } const renderClearIcon = () => { - if (!props.clearable || isDisabled.value || (!props.clearIcon && !slots.clearIcon)) { + if (!props.clearable || props.disabled || (!props.clearIcon && !slots.clearIcon)) { return null } diff --git a/packages/components/_private/trigger/src/types.ts b/packages/components/_private/trigger/src/types.ts index 40d405aca..4fab9d9a2 100644 --- a/packages/components/_private/trigger/src/types.ts +++ b/packages/components/_private/trigger/src/types.ts @@ -5,11 +5,11 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { ValidateStatus } from '@idux/cdk/forms' +import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' import type { FormSize } from '@idux/components/form' import type { DefineComponent, HTMLAttributes, PropType } from 'vue' -import { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' - export const triggerProps = { borderless: Boolean as PropType, clearable: Boolean as PropType, @@ -19,6 +19,7 @@ export const triggerProps = { focused: Boolean as PropType, readonly: Boolean as PropType, size: String as PropType, + status: String as PropType, suffix: String as PropType, onClick: [Array, Function] as PropType void>>, onClear: [Array, Function] as PropType void>>, diff --git a/packages/components/_private/trigger/style/index.less b/packages/components/_private/trigger/style/index.less index 0bfd5a7a3..a5c446bfc 100644 --- a/packages/components/_private/trigger/style/index.less +++ b/packages/components/_private/trigger/style/index.less @@ -107,4 +107,18 @@ font-size: inherit; .placeholder(@trigger-placeholder-color); } -} \ No newline at end of file + + // form status + &&-invalid { + border-color: @form-item-invalid-color; + + &:hover { + border-color: @form-item-invalid-color; + } + + &.@{idux-prefix}-trigger-focused { + border-color: @form-item-invalid-color; + box-shadow: @form-item-invalid-box-shadow; + } + } +} diff --git a/packages/components/cascader/docs/Api.zh.md b/packages/components/cascader/docs/Api.zh.md index bcc9ce2ad..be5973f15 100644 --- a/packages/components/cascader/docs/Api.zh.md +++ b/packages/components/cascader/docs/Api.zh.md @@ -39,6 +39,7 @@ | `searchable` | 是否可搜索 | `boolean \| 'overlay'` | `false` | - | 当为 `true` 时搜索功能集成在选择器上,当为 `overlay` 时,搜索功能集成在悬浮层上 | | `searchFn` | 根据搜索的文本进行筛选 | `boolean \| SelectSearchFn` | `true` | - | 为 `true` 时使用默认的搜索规则, 如果使用远程搜索,应该设置为 `false` | | `size` | 设置选择器大小 | `'sm' \| 'md' \| 'lg'` | `md` | ✅ | - | +| `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | | `strategy` | 设置级联策略 | `'all' \| 'parent' \| 'child'` | `'off'` | - | 具体用法参见 [级联策略](#components-cascader-demo-Strategy) | | `suffix` | 设置后缀图标 | `string \| #suffix` | `down` | ✅ | - | | `virtual` | 是否开启虚拟滚动 | `boolean` | `false` | - | 需要设置 `height` | diff --git a/packages/components/cascader/src/Cascader.tsx b/packages/components/cascader/src/Cascader.tsx index e5450b9af..77cb9488e 100644 --- a/packages/components/cascader/src/Cascader.tsx +++ b/packages/components/cascader/src/Cascader.tsx @@ -12,7 +12,7 @@ import { type VKey, useState } from '@idux/cdk/utils' import { ɵOverlay } from '@idux/components/_private/overlay' import { ɵSelector, type ɵSelectorInstance } from '@idux/components/_private/selector' import { useGlobalConfig } from '@idux/components/config' -import { useFormItemRegister } from '@idux/components/form' +import { useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form' import { ɵUseOverlayState } from '@idux/components/select' import { useGetKey, useOverlayContainer } from '@idux/components/utils' @@ -58,6 +58,8 @@ export default defineComponent({ const { accessor, control } = useAccessorAndControl() useFormItemRegister(control) + const mergedSize = useFormSize(props, config) + const mergedStatus = useFormStatus(props, control) const { mergedData, mergedDataMap } = useDataSource( props, @@ -150,7 +152,8 @@ export default defineComponent({ placeholder={props.placeholder} readonly={props.readonly} searchable={props.searchable} - size={props.size} + size={mergedSize.value} + status={mergedStatus.value} suffix={props.suffix} value={selectedStateContext.selectedKeys.value} onBlur={handleBlur} diff --git a/packages/components/cascader/src/types.ts b/packages/components/cascader/src/types.ts index 28ed4b53b..dac13edfa 100644 --- a/packages/components/cascader/src/types.ts +++ b/packages/components/cascader/src/types.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { AbstractControl } from '@idux/cdk/forms' +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' import type { PortalTargetType } from '@idux/cdk/portal' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' import type { EmptyProps } from '@idux/components/empty' @@ -55,6 +55,7 @@ export const cascaderProps = { searchable: { type: [Boolean, String] as PropType, default: false }, searchFn: { type: [Boolean, Function] as PropType, default: true }, size: { type: String as PropType, default: undefined }, + status: String as PropType, strategy: { type: String as PropType, default: 'all' }, suffix: { type: String, default: undefined }, virtual: { type: Boolean, default: false }, diff --git a/packages/components/date-picker/docs/Api.zh.md b/packages/components/date-picker/docs/Api.zh.md index da94fcada..80b9acd1b 100644 --- a/packages/components/date-picker/docs/Api.zh.md +++ b/packages/components/date-picker/docs/Api.zh.md @@ -24,6 +24,7 @@ | `overlayRender` | 自定义日期面板内容的渲染 | `(children:VNode[]) => VNodeChild` | - | - | - | | `readonly` | 只读模式 | `boolean` | - | - | - | | `size` | 设置选择器大小 | `'sm' \| 'md' \| 'lg'` | `md` | ✅ | - | +| `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | | `suffix` | 设置后缀图标 | `string \| #suffix` | `'calendar'` | ✅ | - | | `type` | 设置选择器类型 | `'date' \| 'week' \| 'month' \| 'quarter' \| 'year' \| 'datetime'` | `'date'` | - | - | | `onClear` | 清除图标被点击后的回调 | `(evt: MouseEvent) => void` | - | - | - | diff --git a/packages/components/date-picker/src/DatePicker.tsx b/packages/components/date-picker/src/DatePicker.tsx index 5842c8a30..67053212b 100644 --- a/packages/components/date-picker/src/DatePicker.tsx +++ b/packages/components/date-picker/src/DatePicker.tsx @@ -40,7 +40,7 @@ export default defineComponent({ const inputEnableStatus = useInputEnableStatus(props, config) const formatContext = useFormat(props, config) - const pickerStateContext = usePickerState(props, dateConfig, formatContext.formatRef) + const pickerStateContext = usePickerState(props, config, dateConfig, formatContext.formatRef) const { accessor, handleChange } = pickerStateContext diff --git a/packages/components/date-picker/src/DateRangePicker.tsx b/packages/components/date-picker/src/DateRangePicker.tsx index 12c1465d8..956fc1b3e 100644 --- a/packages/components/date-picker/src/DateRangePicker.tsx +++ b/packages/components/date-picker/src/DateRangePicker.tsx @@ -40,7 +40,7 @@ export default defineComponent({ const inputEnableStatus = useInputEnableStatus(props, config) const formatContext = useFormat(props, config) - const pickerStateContext = usePickerState(props, dateConfig, formatContext.formatRef) + const pickerStateContext = usePickerState(props, config, dateConfig, formatContext.formatRef) const { accessor, handleChange } = pickerStateContext diff --git a/packages/components/date-picker/src/composables/usePickerState.ts b/packages/components/date-picker/src/composables/usePickerState.ts index c7ef29dc6..0aec8c185 100644 --- a/packages/components/date-picker/src/composables/usePickerState.ts +++ b/packages/components/date-picker/src/composables/usePickerState.ts @@ -9,10 +9,10 @@ import { type ComputedRef, toRaw } from 'vue' import { isArray } from 'lodash-es' -import { type FormAccessor, useAccessorAndControl } from '@idux/cdk/forms' +import { type FormAccessor, ValidateStatus, useAccessorAndControl } from '@idux/cdk/forms' import { callEmit, convertArray, useState } from '@idux/cdk/utils' import { type DateConfig } from '@idux/components/config' -import { useFormItemRegister } from '@idux/components/form' +import { FormSize, useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form' import { type DatePickerProps, type DateRangePickerProps } from '../types' import { convertToDate, sortRangeValue } from '../utils' @@ -23,6 +23,8 @@ type StateValueType = T extend export interface PickerStateContext { accessor: FormAccessor + mergedSize: ComputedRef + mergedStatus: ComputedRef isFocused: ComputedRef handleChange: (value: StateValueType) => void handleClear: (evt: MouseEvent) => void @@ -32,11 +34,14 @@ export interface PickerStateContext( props: T, + config: { size: FormSize }, dateConfig: DateConfig, formatRef: ComputedRef, ): PickerStateContext { const { accessor, control } = useAccessorAndControl() useFormItemRegister(control) + const mergedSize = useFormSize(props, config) + const mergedStatus = useFormStatus(props, control) const [isFocused, setFocused] = useState(false) @@ -75,6 +80,8 @@ export function usePickerState return { accessor, + mergedSize, + mergedStatus, isFocused, handleChange, handleClear, diff --git a/packages/components/date-picker/src/composables/useTriggerProps.ts b/packages/components/date-picker/src/composables/useTriggerProps.ts index c6b4e3afb..6079e95b3 100644 --- a/packages/components/date-picker/src/composables/useTriggerProps.ts +++ b/packages/components/date-picker/src/composables/useTriggerProps.ts @@ -7,18 +7,16 @@ import type { DatePickerContext, DateRangePickerContext } from '../token' import type { ɵTriggerProps } from '@idux/components/_private/trigger' -import type { FormContext } from '@idux/components/form' import { type ComputedRef, computed } from 'vue' -export function useTriggerProps( - context: DatePickerContext | DateRangePickerContext, - formContext: FormContext | null, -): ComputedRef<ɵTriggerProps> { +export function useTriggerProps(context: DatePickerContext | DateRangePickerContext): ComputedRef<ɵTriggerProps> { const { props, config, accessor, + mergedSize, + mergedStatus, isFocused, handleFocus, handleBlur, @@ -46,7 +44,8 @@ export function useTriggerProps( disabled: accessor.disabled, focused: isFocused.value, readonly: props.readonly || inputEnableStatus.value.enableInput === false, - size: props.size ?? formContext?.size.value ?? config.size, + size: mergedSize.value, + status: mergedStatus.value, suffix: props.suffix ?? config.suffix, onClick: handleClick, onClear: handleClear, diff --git a/packages/components/date-picker/src/trigger/RangeTrigger.tsx b/packages/components/date-picker/src/trigger/RangeTrigger.tsx index 0caf99aa9..19f73b5f1 100644 --- a/packages/components/date-picker/src/trigger/RangeTrigger.tsx +++ b/packages/components/date-picker/src/trigger/RangeTrigger.tsx @@ -9,7 +9,6 @@ import { computed, defineComponent, inject } from 'vue' import { callEmit } from '@idux/cdk/utils' import { ɵTrigger } from '@idux/components/_private/trigger' -import { FORM_TOKEN } from '@idux/components/form' import { useTriggerProps } from '../composables/useTriggerProps' import { dateRangePickerToken } from '../token' @@ -29,14 +28,13 @@ export default defineComponent({ inputEnableStatus, renderSeparator, } = context - const formContext = inject(FORM_TOKEN, null) const placeholders = computed(() => [ props.placeholder?.[0] ?? locale.dateRangePicker[`${props.type}Placeholder`][0], props.placeholder?.[1] ?? locale.dateRangePicker[`${props.type}Placeholder`][1], ]) const inputSize = computed(() => Math.max(10, formatRef.value.length) + 2) - const triggerProps = useTriggerProps(context, formContext) + const triggerProps = useTriggerProps(context) const handleFromInput = (evt: Event) => { fromControl.handleInput(evt) diff --git a/packages/components/date-picker/src/trigger/Trigger.tsx b/packages/components/date-picker/src/trigger/Trigger.tsx index 1205b7eef..bfc2c775f 100644 --- a/packages/components/date-picker/src/trigger/Trigger.tsx +++ b/packages/components/date-picker/src/trigger/Trigger.tsx @@ -9,7 +9,6 @@ import { computed, defineComponent, inject } from 'vue' import { callEmit } from '@idux/cdk/utils' import { ɵTrigger } from '@idux/components/_private/trigger' -import { FORM_TOKEN } from '@idux/components/form' import { useTriggerProps } from '../composables/useTriggerProps' import { datePickerToken } from '../token' @@ -28,12 +27,11 @@ export default defineComponent({ inputEnableStatus, inputRef, } = context - const formContext = inject(FORM_TOKEN, null) const placeholder = computed(() => props.placeholder ?? locale.datePicker[`${props.type}Placeholder`]) const inputSize = computed(() => Math.max(10, formatRef.value.length) + 2) - const triggerProps = useTriggerProps(context, formContext) + const triggerProps = useTriggerProps(context) const handleInput = (evt: Event) => { _handleInput(evt) diff --git a/packages/components/date-picker/src/types.ts b/packages/components/date-picker/src/types.ts index cf7fac9d6..670253bf1 100644 --- a/packages/components/date-picker/src/types.ts +++ b/packages/components/date-picker/src/types.ts @@ -5,7 +5,7 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { AbstractControl } from '@idux/cdk/forms' +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' import type { PortalTargetType } from '@idux/cdk/portal' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' import type { ɵFooterButtonProps } from '@idux/components/_private/footer' @@ -77,6 +77,7 @@ const datePickerCommonProps = { default: false, }, size: String as PropType, + status: String as PropType, suffix: String, type: { type: String as PropType, diff --git a/packages/components/form/index.ts b/packages/components/form/index.ts index f21965034..be840034d 100644 --- a/packages/components/form/index.ts +++ b/packages/components/form/index.ts @@ -24,6 +24,7 @@ export { useFormElement, useFormFocusMonitor, useFormSize, + useFormStatus, } from './src/composables/public' export type { diff --git a/packages/components/form/src/FormItem.tsx b/packages/components/form/src/FormItem.tsx index cc1cab99d..c9c87d1a8 100644 --- a/packages/components/form/src/FormItem.tsx +++ b/packages/components/form/src/FormItem.tsx @@ -50,14 +50,13 @@ export default defineComponent({ ) const mergedMessageTooltip = computed(() => props.messageTooltip ?? formProps?.messageTooltip) - const { status, statusIcon, message } = useFormItem(props, formProps) + const { status, message } = useFormItem(props) const classes = computed(() => { const prefixCls = mergedPrefixCls.value const currStatus = status.value return normalizeClass({ [prefixCls]: true, - [`${prefixCls}-with-status-icon`]: !!statusIcon.value, [`${prefixCls}-${currStatus}`]: !!currStatus, }) }) @@ -84,7 +83,6 @@ export default defineComponent({ controlColConfig, controlTooltipIcon, status, - statusIcon, message, mergedMessageTooltip, prefixCls, @@ -131,7 +129,6 @@ function renderControl( controlColConfig: ComputedRef, controlTooltipIcon: ComputedRef, status: ComputedRef, - statusIcon: ComputedRef, message: ComputedRef, mergedMessageTooltip: ComputedRef, prefixCls: string, @@ -155,11 +152,6 @@ function renderControl( const inputNode = (
{slots.default && slots.default()}
- {statusIcon.value && ( - - - - )} {tooltipNode && {tooltipNode}}
) diff --git a/packages/components/form/src/composables/public.ts b/packages/components/form/src/composables/public.ts index 91c78703b..579bb5727 100644 --- a/packages/components/form/src/composables/public.ts +++ b/packages/components/form/src/composables/public.ts @@ -18,7 +18,13 @@ import { } from 'vue' import { useSharedFocusMonitor } from '@idux/cdk/a11y' -import { type AbstractControl, type ValueAccessor, useValueAccessor, useValueControl } from '@idux/cdk/forms' +import { + type AbstractControl, + ValidateStatus, + type ValueAccessor, + useValueAccessor, + useValueControl, +} from '@idux/cdk/forms' import { useKey } from '@idux/components/utils' import { FORM_ITEM_TOKEN, FORM_TOKEN } from '../token' @@ -116,3 +122,23 @@ export function useFormSize(props: { size?: FormSize }, config: { size: FormSize return computed(() => props.size ?? formContext?.size.value ?? config.size) } + +export function useFormStatus( + props: { status?: ValidateStatus }, + control: Ref, +): ComputedRef { + return computed(() => { + if (props.status) { + return props.status + } + const currControl = control.value + if (!currControl || currControl.disabled.value) { + return undefined + } + const { trigger, dirty, blurred, status } = currControl + if ((trigger === 'change' && dirty.value) || (trigger === 'blur' && blurred.value)) { + return status.value + } + return undefined + }) +} diff --git a/packages/components/form/src/composables/useFormItem.ts b/packages/components/form/src/composables/useFormItem.ts index 980598058..3129131ef 100644 --- a/packages/components/form/src/composables/useFormItem.ts +++ b/packages/components/form/src/composables/useFormItem.ts @@ -19,28 +19,24 @@ import { import { isFunction, isObject, isString } from 'lodash-es' import { type AbstractControl, type ValidateStatus, useControl as useSelfControl } from '@idux/cdk/forms' -import { Logger, type VKey } from '@idux/cdk/utils' +import { type VKey } from '@idux/cdk/utils' import { useGlobalConfig } from '@idux/components/config' import { type Locale } from '@idux/components/locales' import { FORM_ITEM_TOKEN } from '../token' -import { type FormItemProps, type FormProps } from '../types' +import { type FormItemProps } from '../types' +import { useFormStatus } from './public' -export function useFormItem( - props: FormItemProps, - formProps: FormProps | undefined, -): { +export function useFormItem(props: FormItemProps): { status: ComputedRef - statusIcon: ComputedRef message: ComputedRef } { const control = useControl() - const status = useStatus(props, control) - const statusIcon = useStatusIcon(props, formProps, status) + const status = useFormStatus(props, control) const message = useMessage(props, control, status) - return { status, statusIcon, message } + return { status, message } } function useControl() { @@ -84,56 +80,6 @@ function useControl() { return control } -function useStatus(props: FormItemProps, control: Ref) { - return computed(() => { - if (props.status) { - return props.status - } - const currControl = control.value - if (!currControl) { - return undefined - } - const { trigger, dirty, blurred, status } = currControl - if ((trigger === 'change' && dirty.value) || (trigger === 'blur' && blurred.value)) { - return status.value - } - return undefined - }) -} - -const defaultIconMap = { - invalid: 'close-circle-filled', - validating: 'loading', - valid: 'check-circle-filled', -} as const - -function useStatusIcon( - props: FormItemProps, - formProps: FormProps | undefined, - status: ComputedRef, -) { - return computed(() => { - const currStatus = status.value - if (!currStatus) { - return undefined - } - - const icon = props.hasFeedback ?? props.statusIcon ?? formProps?.hasFeedback ?? formProps?.statusIcon - if (__DEV__ && (props.hasFeedback || formProps?.hasFeedback)) { - Logger.warn('components/form', '`hasFeedback` was deprecated.') - } - if (__DEV__ && (props.statusIcon || formProps?.statusIcon)) { - Logger.warn('components/form', '`statusIcon` was deprecated.') - } - if (!icon) { - return undefined - } - - const iconMap = isObject(icon) ? { ...defaultIconMap, ...icon } : defaultIconMap - return iconMap[currStatus] - }) -} - function useMessage( props: FormItemProps, control: Ref, diff --git a/packages/components/form/style/status.less b/packages/components/form/style/status.less index 273dac95e..bfcf29233 100644 --- a/packages/components/form/style/status.less +++ b/packages/components/form/style/status.less @@ -1,136 +1,6 @@ .@{form-item-prefix} { - // TODO: Deprecated - &-status-icon { - position: absolute; - top: 50%; - right: 0; - z-index: 1; - width: @form-height-md; - height: 20px; - margin-top: -10px; - font-size: @form-font-size-md; - line-height: 20px; - text-align: center; - visibility: visible; - animation: zoomIn 0.3s @ease-out-back; - pointer-events: none; - } - - // TODO: Deprecated - &-with-status-icon { - // input - .@{idux-prefix}-input { - padding-right: 24px; - - .@{idux-prefix}-input-suffix { - padding-right: 18px; - } - } - } - // === invalid === &-invalid > &-control { - .@{form-item-prefix}-status-icon { - color: @form-item-invalid-color; - } - - // === input === - .@{idux-prefix}-input:not(.@{idux-prefix}-input-disabled) { - border-color: @form-item-invalid-color; - - &:hover { - border-color: @form-item-invalid-color; - } - - .@{idux-prefix}-input-wrapper, - .@{idux-prefix}-input-inner { - border-color: @form-item-invalid-color; - - &:hover { - border-color: @form-item-invalid-color; - } - } - - .@{idux-prefix}-input-suffix, - .@{idux-prefix}-input-prefix { - color: @form-item-invalid-color; - - &:hover { - color: @form-item-invalid-color; - } - } - - &:focus, - &.@{idux-prefix}-input-focused { - border-color: @form-item-invalid-color; - box-shadow: @form-item-invalid-box-shadow; - - .@{idux-prefix}-input-wrapper, - .@{idux-prefix}-input-inner { - box-shadow: none; - } - } - - .@{idux-prefix}-input-addon { - color: @form-item-invalid-color; - border-color: @form-item-invalid-color; - - .@{selector-prefix} { - &:hover &-content, - &-content { - border-color: transparent; - } - - &&-active &-content { - border-color: transparent; - // todo remove - box-shadow: none; - } - } - } - } - - // select - .@{selector-prefix}:not(.@{selector-prefix}-disabled) { - &:hover .@{selector-prefix}-content, - .@{selector-prefix}-content { - border-color: @form-item-invalid-color; - } - - &.@{selector-prefix}-active .@{selector-prefix}-content { - border-color: @form-item-invalid-color; - box-shadow: @form-item-invalid-box-shadow; - } - } - - // textarea - .@{idux-prefix}-textarea:not(.@{idux-prefix}-textarea-disabled) { - &::after { - color: @form-item-invalid-color; - } - - .@{idux-prefix}-textarea-inner { - border-color: @form-item-invalid-color; - - &:hover { - border-color: @form-item-invalid-color; - } - } - - .@{idux-prefix}-textarea-suffix { - color: @form-item-invalid-color; - - &:hover { - color: @form-item-invalid-color; - } - } - - &.@{idux-prefix}-textarea-focused .@{idux-prefix}-textarea-inner { - border-color: @form-item-invalid-color; - box-shadow: @form-item-invalid-box-shadow; - } - } - // time/date-picker .@{idux-prefix}-time-picker, .@{idux-prefix}-time-range-picker, @@ -153,24 +23,11 @@ } // === Validating === - &-validating > &-control { - .@{form-item-prefix}-status-icon { - display: inline-block; - color: @form-item-validating-color; - } - } - &-message-validating { color: @form-item-validating-color; } // === valid === - &-valid > &-control { - .@{form-item-prefix}-status-icon { - color: @form-item-valid-color; - } - } - &-message-valid { color: @form-item-valid-color; } diff --git a/packages/components/input/docs/Api.zh.md b/packages/components/input/docs/Api.zh.md index 86fc4fbd6..a22604c5e 100644 --- a/packages/components/input/docs/Api.zh.md +++ b/packages/components/input/docs/Api.zh.md @@ -19,6 +19,7 @@ | `prefix` | 设置前缀图标 | `string \| #prefix` | - | - | - | | `readonly` | 是否只读状态 | `boolean` | `false` | - | - | | `size` | 设置大小 | `'sm' \| 'md' \| 'lg'` | `'md'` | ✅ | - | +| `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | | `suffix` | 设置后缀图标 | `string \| #suffix` | - | - | - | | `trim` | 失去焦点后自动去除前后空格 | `boolean` | `false` | ✅ | - | | `onChange` | 值发生改变后的回调 | `(value: string, oldValue: string) => void` | - | - | - | diff --git a/packages/components/input/index.ts b/packages/components/input/index.ts index 3f881a782..f7f958d7a 100644 --- a/packages/components/input/index.ts +++ b/packages/components/input/index.ts @@ -15,6 +15,6 @@ export { IxInput } export type { InputInstance, InputComponent, InputPublicProps as InputProps } from './src/types' -export { commonProps as ɵCommonProps } from './src/types' +export { inputCommonProps as ɵInputCommonProps } from './src/types' export { useInput as ɵUseInput } from './src/useInput' export type { InputContext as ɵInputContext } from './src/useInput' diff --git a/packages/components/input/src/Input.tsx b/packages/components/input/src/Input.tsx index 2747be2c3..113813335 100644 --- a/packages/components/input/src/Input.tsx +++ b/packages/components/input/src/Input.tsx @@ -11,7 +11,6 @@ import { defineComponent, onMounted, ref } from 'vue' import { ɵInput } from '@idux/components/_private/input' import { useGlobalConfig } from '@idux/components/config' -import { useFormSize } from '@idux/components/form' import { inputProps } from './types' import { useInput } from './useInput' @@ -21,11 +20,12 @@ export default defineComponent({ props: inputProps, setup(props, { slots, expose }) { const config = useGlobalConfig('input') - const mergedSize = useFormSize(props, config) const { elementRef, accessor, + mergedSize, + mergedStatus, clearable, clearIcon, clearVisible, @@ -66,6 +66,7 @@ export default defineComponent({ focused={isFocused.value} prefix={prefix} size={mergedSize.value} + status={mergedStatus.value} suffix={suffix} onClear={handleClear} readonly={props.readonly} diff --git a/packages/components/input/src/types.ts b/packages/components/input/src/types.ts index 8a5670a2a..829209da7 100644 --- a/packages/components/input/src/types.ts +++ b/packages/components/input/src/types.ts @@ -5,7 +5,7 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { AbstractControl } from '@idux/cdk/forms' +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' import type { FormSize } from '@idux/components/form' import type { DefineComponent, InputHTMLAttributes, PropType } from 'vue' @@ -13,7 +13,7 @@ import type { DefineComponent, InputHTMLAttributes, PropType } from 'vue' export type TextareaResize = 'none' | 'both' | 'horizontal' | 'vertical' export type TextareaAutoRows = { minRows: number; maxRows: number } -export const commonProps = { +export const inputCommonProps = { control: { type: [String, Number, Object] as PropType, default: undefined }, value: { type: String, default: undefined }, @@ -22,6 +22,7 @@ export const commonProps = { disabled: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, size: { type: String as PropType, default: undefined }, + status: String as PropType, trim: { type: Boolean, default: undefined }, // events @@ -35,10 +36,10 @@ export const commonProps = { onBlur: [Function, Array] as PropType void>>, } as const -export type CommonProps = ExtractInnerPropTypes +export type InputCommonProps = ExtractInnerPropTypes export const inputProps = { - ...commonProps, + ...inputCommonProps, addonAfter: { type: String, default: undefined }, addonBefore: { type: String, default: undefined }, borderless: { type: Boolean, default: undefined }, diff --git a/packages/components/input/src/useInput.ts b/packages/components/input/src/useInput.ts index aafeb2583..0bf0769a6 100644 --- a/packages/components/input/src/useInput.ts +++ b/packages/components/input/src/useInput.ts @@ -5,18 +5,25 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { CommonProps } from './types' -import type { ComputedRef, Ref } from 'vue' +import { type ComputedRef, type Ref, computed, nextTick, ref, toRaw, watch } from 'vue' -import { computed, nextTick, ref, toRaw, watch } from 'vue' - -import { FormAccessor, useAccessorAndControl } from '@idux/cdk/forms' +import { type FormAccessor, type ValidateStatus, useAccessorAndControl } from '@idux/cdk/forms' import { callEmit } from '@idux/cdk/utils' -import { useFormFocusMonitor, useFormItemRegister } from '@idux/components/form' +import { + type FormSize, + useFormFocusMonitor, + useFormItemRegister, + useFormSize, + useFormStatus, +} from '@idux/components/form' + +import { type InputCommonProps } from './types' export interface InputContext { elementRef: Ref accessor: FormAccessor + mergedSize: ComputedRef + mergedStatus: ComputedRef clearIcon: ComputedRef clearVisible: ComputedRef clearable: ComputedRef @@ -36,11 +43,13 @@ export interface InputContext } export function useInput( - props: CommonProps, - config: { clearable: boolean; clearIcon: string; trim: boolean }, + props: InputCommonProps, + config: { clearable: boolean; clearIcon: string; size: FormSize; trim: boolean }, ): InputContext { const { accessor, control } = useAccessorAndControl() useFormItemRegister(control) + const mergedSize = useFormSize(props, config) + const mergedStatus = useFormStatus(props, control) const clearable = computed(() => props.clearable ?? config.clearable) const clearIcon = computed(() => props.clearIcon ?? config.clearIcon) @@ -121,6 +130,8 @@ export function useInput void` | - | - | - | diff --git a/packages/components/select/src/Select.tsx b/packages/components/select/src/Select.tsx index 848b38a2a..874ac9065 100644 --- a/packages/components/select/src/Select.tsx +++ b/packages/components/select/src/Select.tsx @@ -16,7 +16,7 @@ import { ɵInput } from '@idux/components/_private/input' import { ɵOverlay } from '@idux/components/_private/overlay' import { ɵSelector, type ɵSelectorInstance } from '@idux/components/_private/selector' import { type SelectConfig, useGlobalConfig } from '@idux/components/config' -import { useFormItemRegister } from '@idux/components/form' +import { useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form' import { useOverlayContainer } from '@idux/components/utils' import { useActiveState } from './composables/useActiveState' @@ -65,6 +65,8 @@ export default defineComponent({ const { accessor, control } = useAccessorAndControl() useFormItemRegister(control) + const mergedSize = useFormSize(props, config) + const mergedStatus = useFormStatus(props, control) const getKey = useGetOptionKey(props, config) const { options, optionKeyMap } = useSelectOptions(props, config, slots, getKey, inputValue) @@ -142,7 +144,8 @@ export default defineComponent({ placeholder={props.placeholder} readonly={props.readonly} searchable={props.searchable} - size={props.size} + size={mergedSize.value} + status={mergedStatus.value} suffix={props.suffix} value={selectedValue.value} onBlur={handleBlur} diff --git a/packages/components/select/src/types.ts b/packages/components/select/src/types.ts index 512595eda..ca72adb86 100644 --- a/packages/components/select/src/types.ts +++ b/packages/components/select/src/types.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { AbstractControl } from '@idux/cdk/forms' +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' import type { PortalTargetType } from '@idux/cdk/portal' import type { VirtualScrollToFn } from '@idux/cdk/scroll' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' @@ -88,6 +88,7 @@ export const selectProps = { searchFilter: { type: [Boolean, Function] as PropType, default: undefined }, searchFn: { type: [Boolean, Function] as PropType, default: true }, size: { type: String as PropType, default: undefined }, + status: String as PropType, suffix: { type: String, default: undefined }, /** * @deprecated please use `overlayContainer` instead' diff --git a/packages/components/textarea/docs/Api.zh.md b/packages/components/textarea/docs/Api.zh.md index e8c43546d..b20a2551a 100644 --- a/packages/components/textarea/docs/Api.zh.md +++ b/packages/components/textarea/docs/Api.zh.md @@ -20,6 +20,7 @@ | `resize` | 缩放方向 | `none \| both \| horizontal \| vertical` | `vertical` | ✅ | 启用 `autoRows` 的时,仅 `none \| horizontal` 有效 | | `showCount` | 是否展示字符数 | `boolean` | `false` | ✅ | - | | `size` | 设置大小 | `'sm' \| 'md' \| 'lg'` | `'md'` | ✅ | - | +| `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | | `trim` | 失去焦点后自动去除前后空格 | `boolean` | `false` | ✅ | - | | `onChange` | 值发生改变后的回调 | `(value: string, oldValue: string) => void` | - | - | - | | `onClear` | 清除图标被点击后的回调 | `(evt: MouseEvent) => void` | - | - | - | diff --git a/packages/components/textarea/src/Textarea.tsx b/packages/components/textarea/src/Textarea.tsx index 040cea907..cd041f735 100644 --- a/packages/components/textarea/src/Textarea.tsx +++ b/packages/components/textarea/src/Textarea.tsx @@ -27,6 +27,8 @@ export default defineComponent({ const { elementRef, accessor, + mergedSize, + mergedStatus, clearable, clearIcon, clearVisible, @@ -46,15 +48,17 @@ export default defineComponent({ expose({ focus, blur }) const classes = computed(() => { - const { showCount = config.showCount, size = config.size } = props + const { showCount = config.showCount } = props + const status = mergedStatus.value const prefixCls = mergedPrefixCls.value const classes = { [prefixCls]: true, + [`${prefixCls}-${mergedSize.value}`]: true, + [`${prefixCls}-${status}`]: !!status, [`${prefixCls}-clearable`]: clearable.value, [`${prefixCls}-disabled`]: accessor.disabled, [`${prefixCls}-focused`]: isFocused.value, [`${prefixCls}-with-count`]: showCount, - [`${prefixCls}-${size}`]: true, } return normalizeClass([classes, attrs.class]) }) diff --git a/packages/components/textarea/src/types.ts b/packages/components/textarea/src/types.ts index a163e7097..e3d52f692 100644 --- a/packages/components/textarea/src/types.ts +++ b/packages/components/textarea/src/types.ts @@ -8,10 +8,10 @@ import type { ExtractInnerPropTypes, ExtractPublicPropTypes } from '@idux/cdk/utils' import type { DefineComponent, PropType, TextareaHTMLAttributes } from 'vue' -import { ɵCommonProps } from '@idux/components/input' +import { ɵInputCommonProps } from '@idux/components/input' export const textareaProps = { - ...ɵCommonProps, + ...ɵInputCommonProps, autoRows: { type: [Boolean, Object] as PropType, default: undefined }, computeCount: { type: Function as PropType<(value: string) => string>, default: undefined }, maxCount: { type: [Number, String] as PropType, default: undefined }, diff --git a/packages/components/textarea/style/index.less b/packages/components/textarea/style/index.less index 1201778a6..850899905 100644 --- a/packages/components/textarea/style/index.less +++ b/packages/components/textarea/style/index.less @@ -94,4 +94,32 @@ opacity: @textarea-count-opacity; content: attr(data-count); } + + // form status + &&-invalid { + &::after { + color: @form-item-invalid-color; + } + + .@{textarea-prefix}-inner { + border-color: @form-item-invalid-color; + + &:hover { + border-color: @form-item-invalid-color; + } + } + + .@{textarea-prefix}-suffix { + color: @form-item-invalid-color; + + &:hover { + color: @form-item-invalid-color; + } + } + + &.@{textarea-prefix}-focused .@{textarea-prefix}-inner { + border-color: @form-item-invalid-color; + box-shadow: @form-item-invalid-box-shadow; + } + } } diff --git a/packages/components/time-picker/docs/Api.zh.md b/packages/components/time-picker/docs/Api.zh.md index f4093cd9a..d21d86910 100644 --- a/packages/components/time-picker/docs/Api.zh.md +++ b/packages/components/time-picker/docs/Api.zh.md @@ -15,11 +15,11 @@ | `readonly` | 只读状态 |`boolean` |`false` | - | - | | `clearable` | 是否展示清除按钮 |`boolean` |`true` | ✅ | - | | `borderless` | 是否为无边框 |`boolean` |`false` | ✅ | - | - | `suffix` | 后缀图标 |`string \| #suffix` | `clock-circle` | ✅ | - | - | `target` | 自定义浮层容器节点 | `string \| HTMLElement \| () => string \| HTMLElement` | - | ✅ | - | | `clearIcon` | 清除按钮图标 |`string \| #clearIcon` | `close-circle` | ✅ | - | | `clearText` | hover到clearIcon上,显示的title |`string` | clear | ✅ | - | | `size` | 尺寸大小 | `lg \| md \| sm` | `md` | ✅ | - | + | `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | + | `suffix` | 后缀图标 |`string \| #suffix` | `clock-circle` | ✅ | - | | `disabledHours` | 禁用部分小时选项 | `(selectedAmPm: 'am' \| 'pm' \| undefined) => number[]` | ``() => []`` | - | - | | `disabledMinutes` | 禁用部分分钟选项 | `(selectedHour: number | undefined, selectedAmPm: 'am' \| 'pm' \| undefined) => number[]` | `() => []` | - | - | | `disabledSeconds` | 禁用部分秒选项 | `(selectedHour: number | undefined, selectedMinute: number \| undefined, selectedAmPm: 'am' \| 'pm' | undefined)=>number[]` | `() => []` | - | - | @@ -29,6 +29,7 @@ | `secondStep` | 秒选项的间隔 | `number` | `1` | - | - | | `defaultOpenValue` | 打开面板时默认高亮的值 | `Date \| string \| number` | - | - | 如果value不为空,则高亮value的值 | | `overlayClassName` | 浮层的类名 |`string` | - | - | - | + | `overlayContainer` | 自定义浮层容器节点 | `string \| HTMLElement \| () => string \| HTMLElement` | - | ✅ | - | | `onChange` | 时间选择回调函数 |`(value: Date \| undefined) => void` | - | - | - | | `onClear` | 清除事件回调函数 |`(evt: MouseEvent) => void` | - | - | - | | `onFocus` | focus事件回调函数 |`(evt: FocusEvent) => void` | - | - | - | @@ -48,10 +49,11 @@ | `separator` | 分隔符 |`string \| VNode \| #separator` | 至 | - | - | | `clearable` | 是否展示清除按钮 |`boolean` |`true` | ✅ | - | | `borderless` | 是否为无边框 |`boolean` |`false` | ✅ | - | - | `suffix` | 后缀图标 |`string \| #suffix` | `clock-circle` | ✅ | - | | `clearIcon` | 清除按钮图标 |`string \| #clearIcon` | `close-circle` | ✅ | - | - | `clearText` | hover到clearIcon上,显示的title |`string` | clear | ✅ | - | + | `clearText` | hover到clearIcon上,显示的title |`string` | `clear` | ✅ | - | | `size` | 尺寸大小 | `lg \| md \| sm` | `md` | ✅ | - | + | `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | + | `suffix` | 后缀图标 |`string \| #suffix` | `clock-circle` | ✅ | - | | `disabledHours` | 禁用部分小时选项 | `(selectedAmPm: string | undefined) => number[]` | ``() => []`` | - | - | | `disabledMinutes` | 禁用部分分钟选项 | `(selectedHour: number | undefined, selectedAmPm: string | undefined) => number[]` | `() => []` | - | - | | `disabledSeconds` | 禁用部分秒选项 | `(selectedHour: number | undefined, selectedMinute: number | undefined, selectedAmPm: string | undefined)=>number[]` | `() => []` | - | - | diff --git a/packages/components/time-picker/src/TimePicker.tsx b/packages/components/time-picker/src/TimePicker.tsx index 02400d5b3..bfe676b3c 100644 --- a/packages/components/time-picker/src/TimePicker.tsx +++ b/packages/components/time-picker/src/TimePicker.tsx @@ -5,11 +5,11 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { computed, defineComponent, inject, nextTick, normalizeClass, provide, toRef, watch } from 'vue' +import { computed, defineComponent, nextTick, normalizeClass, provide, toRef, watch } from 'vue' import { ɵOverlay } from '@idux/components/_private/overlay' import { useDateConfig, useGlobalConfig } from '@idux/components/config' -import { FORM_TOKEN, useFormElement } from '@idux/components/form' +import { useFormElement } from '@idux/components/form' import { usePickerControl } from './composables/useControl' import { useInputEnableStatus } from './composables/useInputEnableStatus' @@ -36,13 +36,12 @@ export default defineComponent({ const { elementRef: inputRef, focus, blur } = useFormElement() const formatRef = computed(() => props.format ?? config.format) - const pickerState = usePickerState(props, dateConfig, formatRef) + const pickerState = usePickerState(props, config, dateConfig, formatRef) const { accessor, handleChange } = pickerState const pickerControl = usePickerControl(dateConfig, formatRef, handleChange, toRef(accessor, 'value')) const { overlayOpened, setOverlayOpened } = useOverlayState(props, pickerControl) const inputEnableStatus = useInputEnableStatus(props, config) - const formContext = inject(FORM_TOKEN, null) const handleKeyDown = useKeyboardEvents(setOverlayOpened) const context = { @@ -54,7 +53,6 @@ export default defineComponent({ config, mergedPrefixCls, formatRef, - formContext, handleKeyDown, inputRef, inputEnableStatus, diff --git a/packages/components/time-picker/src/TimeRangePicker.tsx b/packages/components/time-picker/src/TimeRangePicker.tsx index 0b507bca1..65012f252 100644 --- a/packages/components/time-picker/src/TimeRangePicker.tsx +++ b/packages/components/time-picker/src/TimeRangePicker.tsx @@ -5,11 +5,11 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { computed, defineComponent, inject, nextTick, normalizeClass, provide, toRef, watch } from 'vue' +import { computed, defineComponent, nextTick, normalizeClass, provide, toRef, watch } from 'vue' import { ɵOverlay } from '@idux/components/_private/overlay' import { useDateConfig, useGlobalConfig } from '@idux/components/config' -import { FORM_TOKEN, useFormElement } from '@idux/components/form' +import { useFormElement } from '@idux/components/form' import { useInputEnableStatus } from './composables/useInputEnableStatus' import { useRangeKeyboardEvents } from './composables/useKeyboardEvents' @@ -37,15 +37,13 @@ export default defineComponent({ const formatRef = computed(() => props.format ?? config.format) - const pickerState = usePickerState(props, dateConfig, formatRef) + const pickerState = usePickerState(props, config, dateConfig, formatRef) const { accessor, handleChange } = pickerState const rangePickerControl = useRangePickerControl(dateConfig, formatRef, toRef(accessor, 'value')) const { overlayOpened, setOverlayOpened } = useOverlayState(props, rangePickerControl) const inputEnableStatus = useInputEnableStatus(props, config) - const formContext = inject(FORM_TOKEN, null) - const handleKeyDown = useRangeKeyboardEvents(rangePickerControl, setOverlayOpened, handleChange) const renderSeparator = () => slots.separator?.() ?? props.separator ?? locale.timeRangePicker.separator @@ -59,7 +57,6 @@ export default defineComponent({ config, mergedPrefixCls, formatRef, - formContext, handleKeyDown, inputRef, inputEnableStatus, diff --git a/packages/components/time-picker/src/composables/usePickerState.ts b/packages/components/time-picker/src/composables/usePickerState.ts index 36f085ee5..c2e31fc5f 100644 --- a/packages/components/time-picker/src/composables/usePickerState.ts +++ b/packages/components/time-picker/src/composables/usePickerState.ts @@ -9,11 +9,11 @@ import { type ComputedRef, toRaw } from 'vue' import { isArray } from 'lodash-es' -import { type FormAccessor, useAccessorAndControl } from '@idux/cdk/forms' +import { type FormAccessor, type ValidateStatus, useAccessorAndControl } from '@idux/cdk/forms' import { callEmit, convertArray, useState } from '@idux/cdk/utils' import { ɵCalculateViewHour, ɵNormalizeAmPm } from '@idux/components/_private/time-panel' import { type DateConfig } from '@idux/components/config' -import { useFormItemRegister } from '@idux/components/form' +import { type FormSize, useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form' import { type TimePickerProps, type TimeRangePickerProps } from '../types' import { checkUse12Hours, convertToDate, sortRangeValue } from '../utils' @@ -24,6 +24,8 @@ type StateValueType = T extend export interface PickerStateContext { accessor: FormAccessor + mergedSize: ComputedRef + mergedStatus: ComputedRef isFocused: ComputedRef handleChange: (value: StateValueType) => void handleClear: (evt: MouseEvent) => void @@ -33,11 +35,14 @@ export interface PickerStateContext( props: T, + config: { size: FormSize }, dateConfig: DateConfig, formatRef: ComputedRef, ): PickerStateContext { const { accessor, control } = useAccessorAndControl() useFormItemRegister(control) + const mergedSize = useFormSize(props, config) + const mergedStatus = useFormStatus(props, control) const [isFocused, setFocused] = useState(false) @@ -96,6 +101,8 @@ export function usePickerState return { accessor, + mergedSize, + mergedStatus, isFocused, handleChange, handleClear, diff --git a/packages/components/time-picker/src/composables/useTriggerProps.ts b/packages/components/time-picker/src/composables/useTriggerProps.ts index 77879eb01..366ce4119 100644 --- a/packages/components/time-picker/src/composables/useTriggerProps.ts +++ b/packages/components/time-picker/src/composables/useTriggerProps.ts @@ -16,7 +16,8 @@ export function useTriggerProps(context: TimePickerContext | TimeRangePickerCont props, config, accessor, - formContext, + mergedSize, + mergedStatus, isFocused, handleBlur, handleFocus, @@ -43,7 +44,8 @@ export function useTriggerProps(context: TimePickerContext | TimeRangePickerCont disabled: accessor.disabled, focused: isFocused.value, readonly: props.readonly || !inputEnableStatus.value.enableExternalInput, - size: props.size ?? formContext?.size.value ?? config.size, + size: mergedSize.value, + status: mergedStatus.value, suffix: props.suffix ?? config.suffix, onClick: handleClick, onClear: handleClear, diff --git a/packages/components/time-picker/src/tokens.ts b/packages/components/time-picker/src/tokens.ts index 4b3c00d67..75596fa4b 100644 --- a/packages/components/time-picker/src/tokens.ts +++ b/packages/components/time-picker/src/tokens.ts @@ -12,7 +12,6 @@ import type { PickerStateContext } from './composables/usePickerState' import type { PickerRangeControlContext } from './composables/useRangeControl' import type { TimePickerProps, TimeRangePickerProps } from './types' import type { CommonConfig, DateConfig, TimePickerConfig } from '@idux/components/config' -import type { FormContext } from '@idux/components/form' import type { Locale } from '@idux/components/locales' import type { ComputedRef, InjectionKey, Ref, Slots, VNodeTypes } from 'vue' @@ -28,7 +27,6 @@ interface BasePickerContext inputRef: Ref mergedPrefixCls: ComputedRef formatRef: ComputedRef - formContext: FormContext | null inputEnableStatus: ComputedRef handleKeyDown: (evt: KeyboardEvent) => void } diff --git a/packages/components/time-picker/src/types.ts b/packages/components/time-picker/src/types.ts index 5a10ca50b..218e055c0 100644 --- a/packages/components/time-picker/src/types.ts +++ b/packages/components/time-picker/src/types.ts @@ -5,7 +5,7 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { AbstractControl } from '@idux/cdk/forms' +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' import type { PortalTargetType } from '@idux/cdk/portal' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' import type { ɵFooterButtonProps } from '@idux/components/_private/footer' @@ -56,6 +56,7 @@ const timePickerCommonProps = { default: false, }, size: String as PropType, + status: String as PropType, suffix: String, // events diff --git a/packages/components/tree-select/docs/Api.zh.md b/packages/components/tree-select/docs/Api.zh.md index eb4c22c60..dfd238283 100644 --- a/packages/components/tree-select/docs/Api.zh.md +++ b/packages/components/tree-select/docs/Api.zh.md @@ -43,6 +43,7 @@ | `searchable` | 是否开启搜索功能 | `boolean \| 'overlay'` | - | - | 当为 `true` 时搜索功能集成在选择器上,当为 `overlay` 时,搜索功能集成在悬浮层上 | | `showLine` | 是否显示连接线 | `boolean` | `false` | - | 此属性为`tree`的全局配置,修改其即可生效 | | `size` | 设置选择器大小 | `'sm' \| 'md' \| 'lg'` | `md` | ✅ | - | +| `status` | 手动指定校验状态 | `valid \| invalid \| validating` | - | - | - | | `suffix` | 设置后缀图标 | `string \| #suffix` | `down` | ✅ | - | | `treeDisabled` | 树的禁用节点的函数 | 参考 [Tree](/components/tree/zh#API) | - | - | - | | `virtual` | 是否开启虚拟滚动 | `boolean` | `false` | - | - | diff --git a/packages/components/tree-select/src/TreeSelect.tsx b/packages/components/tree-select/src/TreeSelect.tsx index 8a646a3ec..aa6f2fbc7 100644 --- a/packages/components/tree-select/src/TreeSelect.tsx +++ b/packages/components/tree-select/src/TreeSelect.tsx @@ -13,7 +13,7 @@ import { type VKey, useControlledProp, useState } from '@idux/cdk/utils' import { ɵOverlay } from '@idux/components/_private/overlay' import { ɵSelector, type ɵSelectorInstance } from '@idux/components/_private/selector' import { useGlobalConfig } from '@idux/components/config' -import { useFormItemRegister } from '@idux/components/form' +import { useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form' import { ɵUseOverlayState } from '@idux/components/select' import { type TreeInstance } from '@idux/components/tree' import { useOverlayContainer } from '@idux/components/utils' @@ -53,6 +53,8 @@ export default defineComponent({ const { accessor, control } = useAccessorAndControl() useFormItemRegister(control) + const mergedSize = useFormSize(props, config) + const mergedStatus = useFormStatus(props, control) const { mergedNodeMap } = useMergeNodes(props, mergedChildrenKey, mergedGetKey, mergedLabelKey) const { selectedValue, selectedNodes, changeSelected, handleRemove, handleClear } = useSelectedState( @@ -158,7 +160,8 @@ export default defineComponent({ placeholder={props.placeholder} readonly={props.readonly} searchable={props.searchable} - size={props.size} + size={mergedSize.value} + status={mergedStatus.value} suffix={props.suffix} value={selectedValue.value} onBlur={handleBlur} diff --git a/packages/components/tree-select/src/types.ts b/packages/components/tree-select/src/types.ts index 41edf6a89..3fc1e2206 100644 --- a/packages/components/tree-select/src/types.ts +++ b/packages/components/tree-select/src/types.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { AbstractControl } from '@idux/cdk/forms' +import type { AbstractControl, ValidateStatus } from '@idux/cdk/forms' import type { PortalTargetType } from '@idux/cdk/portal' import type { VirtualScrollToFn } from '@idux/cdk/scroll' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' @@ -79,6 +79,7 @@ export const treeSelectProps = { }, size: { type: String as PropType, default: undefined }, showLine: { type: Boolean, default: undefined }, + status: String as PropType, suffix: { type: String, default: undefined }, /** * @deprecated please use `overlayContainer` instead' diff --git a/packages/pro/textarea/src/types.ts b/packages/pro/textarea/src/types.ts index 3111a24e1..de7a5b70d 100644 --- a/packages/pro/textarea/src/types.ts +++ b/packages/pro/textarea/src/types.ts @@ -14,10 +14,10 @@ export interface TextareaError { message?: string } -import { ɵCommonProps } from '@idux/components/input' +import { ɵInputCommonProps } from '@idux/components/input' export const proTextareaProps = { - ...ɵCommonProps, + ...ɵInputCommonProps, computeCount: { type: Function as PropType<(value: string) => string>, default: undefined }, errors: Array as PropType, maxCount: { type: [Number, String] as PropType, default: undefined },