feat: V3 form (#4678)

* chore: update husky

* perf: update formItem

* perf: useInjectFormItemContext
v2.3
tangjinzhou 2021-09-22 13:00:36 +08:00 committed by GitHub
parent 989d1d4202
commit ca6ce42819
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 337 additions and 133 deletions

1
.gitignore vendored
View File

@ -63,7 +63,6 @@ _site
yarn.lock
package-lock.json
/coverage
.husky
# 备份文件
/components/test/*

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install pretty-quick --staged

View File

@ -4,7 +4,7 @@ exports[`renders ./components/auto-complete/demo/basic.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><input id="rc_select_TEST_OR_SSR" type="search" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">input here</span>
</div>
<!---->
@ -17,7 +17,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.vue correctly
<div class="ant-select ant-select-lg certain-category-search ant-select-lg ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search" style="width: 100%;">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><span class="ant-select-selection-search-input ant-input-affix-wrapper"><!----><input placeholder="input here" type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"><span class="ant-input-suffix"><!----><span role="img" aria-label="search" class="anticon anticon-search certain-category-icon"><svg focusable="false" class="" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></span></span></span></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><span class="ant-select-selection-search-input ant-input-affix-wrapper"><!----><input placeholder="input here" id="rc_select_TEST_OR_SSR" type="search" autocomplete="off" class="ant-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"><span class="ant-input-suffix"><!----><span role="img" aria-label="search" class="anticon anticon-search certain-category-icon"><svg focusable="false" class="" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></span></span></span></span>
<!----><span class="ant-select-selection-placeholder"><!----></span>
</div>
<!---->
@ -30,7 +30,7 @@ exports[`renders ./components/auto-complete/demo/custom.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><textarea placeholder="input here" class="ant-input ant-select-selection-search-input" style="height: 50px;" id="rc_select_TEST_OR_SSR" autocomplete="off" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></textarea></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><textarea placeholder="input here" id="rc_select_TEST_OR_SSR" class="ant-input ant-select-selection-search-input" style="height: 50px;" autocomplete="off" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></textarea></span>
<!----><span class="ant-select-selection-placeholder"><!----></span>
</div>
<!---->
@ -42,7 +42,7 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.vue correctl
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><input id="rc_select_TEST_OR_SSR" type="search" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">input here</span>
</div>
<!---->
@ -54,7 +54,7 @@ exports[`renders ./components/auto-complete/demo/options.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><input id="rc_select_TEST_OR_SSR" type="search" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">input here</span>
</div>
<!---->
@ -67,7 +67,7 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.vue correctl
<div class="ant-select ant-select-lg global-search ant-select-lg ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search" style="width: 100%;">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><span class="ant-input-search ant-select-selection-search-input ant-input-search-enter-button ant-input-search-large ant-input-group-wrapper ant-input-group-wrapper-lg"><span class="ant-input-wrapper ant-input-group"><!----><input placeholder="input here" type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0" class="ant-input ant-input-lg"><span class="ant-input-group-addon"><button class="ant-btn ant-btn-primary ant-btn-lg ant-input-search-button" type="button"><!----><span role="img" aria-label="search" class="anticon anticon-search"><svg focusable="false" class="" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></span></button></span></span></span></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><span class="ant-input-search ant-select-selection-search-input ant-input-search-enter-button ant-input-search-large ant-input-group-wrapper ant-input-group-wrapper-lg"><span class="ant-input-wrapper ant-input-group"><!----><input placeholder="input here" id="rc_select_TEST_OR_SSR" type="search" autocomplete="off" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0" class="ant-input ant-input-lg"><span class="ant-input-group-addon"><button class="ant-btn ant-btn-primary ant-btn-lg ant-input-search-button" type="button"><!----><span role="img" aria-label="search" class="anticon anticon-search"><svg focusable="false" class="" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></span></button></span></span></span></span>
<!----><span class="ant-select-selection-placeholder"><!----></span>
</div>
<!---->

View File

@ -27,6 +27,7 @@ import { defaultConfigProvider } from '../config-provider';
import type { VueNode } from '../_util/type';
import { tuple, withInstall } from '../_util/type';
import type { RenderEmptyHandler } from '../config-provider/renderEmpty';
import { useInjectFormItemContext } from '../form/FormItemContext';
export interface CascaderOptionType {
value?: string | number;
@ -220,12 +221,14 @@ const Cascader = defineComponent({
inheritAttrs: false,
props: cascaderProps,
setup() {
const formItemContext = useInjectFormItemContext();
return {
configProvider: inject('configProvider', defaultConfigProvider),
localeData: inject('localeData', {} as any),
cachedOptions: [],
popupRef: undefined,
input: undefined,
formItemContext,
};
},
data() {
@ -323,6 +326,7 @@ const Cascader = defineComponent({
inputFocused: false,
});
this.$emit('blur', e);
this.formItemContext.onFieldBlur();
},
handleInputClick(e: MouseEvent & { nativeEvent?: any }) {
@ -354,6 +358,7 @@ const Cascader = defineComponent({
}
this.$emit('update:value', value);
this.$emit('change', value, selectedOptions);
this.formItemContext.onFieldChange();
},
getLabel() {
@ -474,7 +479,12 @@ const Cascader = defineComponent({
...otherProps
} = props as any;
const { onEvents, extraAttrs } = splitAttrs(this.$attrs);
const { class: className, style, ...restAttrs } = extraAttrs;
const {
class: className,
style,
id = this.formItemContext.id.value,
...restAttrs
} = extraAttrs;
const getPrefixCls = this.configProvider.getPrefixCls;
const renderEmpty = this.configProvider.renderEmpty;
const prefixCls = getPrefixCls('cascader', customizePrefixCls);
@ -570,6 +580,7 @@ const Cascader = defineComponent({
const inputProps = {
...restAttrs,
...tempInputProps,
id,
prefixCls: inputPrefixCls,
placeholder: value && value.length > 0 ? undefined : placeholder,
value: inputValue,

View File

@ -8,6 +8,7 @@ import { defaultConfigProvider } from '../config-provider';
import warning from '../_util/warning';
import type { RadioChangeEvent } from '../radio/interface';
import type { EventHandler } from '../_util/EventInterface';
import { useInjectFormItemContext } from '../form/FormItemContext';
function noop() {}
export const checkboxProps = () => {
@ -38,7 +39,9 @@ export default defineComponent({
props: checkboxProps(),
emits: ['change', 'update:checked'],
setup() {
const formItemContext = useInjectFormItemContext();
return {
formItemContext,
configProvider: inject('configProvider', defaultConfigProvider),
checkboxGroupContext: inject('checkboxGroupContext', undefined),
};
@ -96,7 +99,13 @@ export default defineComponent({
const props = getOptionProps(this);
const { checkboxGroupContext: checkboxGroup, $attrs } = this;
const children = getSlot(this);
const { indeterminate, prefixCls: customizePrefixCls, skipGroup, ...restProps } = props;
const {
indeterminate,
prefixCls: customizePrefixCls,
skipGroup,
id = this.formItemContext.id.value,
...restProps
} = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const {
@ -109,12 +118,14 @@ export default defineComponent({
} = $attrs;
const checkboxProps: any = {
...restProps,
id,
prefixCls,
...restAttrs,
};
if (checkboxGroup && !skipGroup) {
checkboxProps.onChange = (...args) => {
this.$emit('change', ...args);
this.formItemContext.onFieldChange();
checkboxGroup.toggleOption({ label: children, value: props.value });
};
checkboxProps.name = checkboxGroup.name;

View File

@ -5,6 +5,7 @@ import Checkbox from './Checkbox';
import hasProp, { getSlot } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import type { VueNode } from '../_util/type';
import { useInjectFormItemContext } from '../form/FormItemContext';
export type CheckboxValueType = string | number | boolean;
export interface CheckboxOptionType {
@ -25,10 +26,13 @@ export default defineComponent({
options: { type: Array as PropType<Array<CheckboxOptionType | string>> },
disabled: PropTypes.looseBool,
onChange: PropTypes.func,
id: PropTypes.string,
},
emits: ['change', 'update:value'],
setup() {
const formItemContext = useInjectFormItemContext();
return {
formItemContext,
configProvider: inject('configProvider', defaultConfigProvider),
};
},
@ -95,11 +99,12 @@ export default defineComponent({
// this.$emit('input', val);
this.$emit('update:value', val);
this.$emit('change', val);
this.formItemContext.onFieldChange();
},
},
render() {
const { $props: props, $data: state } = this;
const { prefixCls: customizePrefixCls, options } = props;
const { prefixCls: customizePrefixCls, options, id = this.formItemContext.id.value } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
let children = getSlot(this);
@ -120,6 +125,10 @@ export default defineComponent({
</Checkbox>
));
}
return <div class={groupPrefixCls}>{children}</div>;
return (
<div class={groupPrefixCls} id={id}>
{children}
</div>
);
},
});

View File

@ -15,6 +15,7 @@ import { commonProps, rangePickerProps } from './props';
import type { PanelMode, RangeValue } from '../../vc-picker/interface';
import type { RangePickerSharedProps } from '../../vc-picker/RangePicker';
import devWarning from '../../vc-util/devWarning';
import { useInjectFormItemContext } from '../../form/FormItemContext';
export default function generateRangePicker<DateType, ExtraProps = {}>(
generateConfig: GenerateConfig<DateType>,
@ -52,6 +53,7 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
'blur',
],
setup(props, { expose, slots, attrs, emit }) {
const formItemContext = useInjectFormItemContext();
devWarning(
!attrs.getCalendarContainer,
'DatePicker',
@ -86,6 +88,7 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
const values = maybeToStrings(dates);
emit('update:value', values);
emit('change', values, dateStrings);
formItemContext.onFieldChange();
};
const onOpenChange = (open: boolean) => {
emit('update:open', open);
@ -96,6 +99,7 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
};
const onBlur = () => {
emit('blur');
formItemContext.onFieldBlur();
};
const onPanelChange = (dates: RangeValue<DateType>, modes: [PanelMode, PanelMode]) => {
const values = maybeToStrings(dates);
@ -154,6 +158,7 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
renderExtraFooter = slots.renderExtraFooter,
separator = slots.separator?.(),
clearIcon = slots.clearIcon?.(),
id = formItemContext.id.value,
...restProps
} = p;
const { format, showTime } = p as any;
@ -187,6 +192,7 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
transitionName={transitionName || `${rootPrefixCls.value}-slide-up`}
{...restProps}
{...additionalOverrideProps}
id={id}
value={value.value}
defaultValue={defaultValue.value}
defaultPickerValue={defaultPickerValue.value}

View File

@ -14,6 +14,7 @@ import classNames from '../../_util/classNames';
import { commonProps, datePickerProps } from './props';
import devWarning from '../../vc-util/devWarning';
import { useInjectFormItemContext } from '../../form/FormItemContext';
export default function generateSinglePicker<DateType, ExtraProps = {}>(
generateConfig: GenerateConfig<DateType>,
@ -51,6 +52,7 @@ export default function generateSinglePicker<DateType, ExtraProps = {}>(
'update:open',
],
setup(props, { slots, expose, attrs, emit }) {
const formItemContext = useInjectFormItemContext();
devWarning(
!(props.monthCellContentRender || slots.monthCellContentRender),
'DatePicker',
@ -91,6 +93,7 @@ export default function generateSinglePicker<DateType, ExtraProps = {}>(
const value = maybeToString(date);
emit('update:value', value);
emit('change', value, dateString);
formItemContext.onFieldChange();
};
const onOpenChange = (open: boolean) => {
emit('update:open', open);
@ -101,6 +104,7 @@ export default function generateSinglePicker<DateType, ExtraProps = {}>(
};
const onBlur = () => {
emit('blur');
formItemContext.onFieldBlur();
};
const onPanelChange = (date: DateType, mode: PanelMode | null) => {
const value = maybeToString(date);
@ -157,6 +161,7 @@ export default function generateSinglePicker<DateType, ExtraProps = {}>(
(props as any).monthCellContentRender ||
slots.monthCellContentRender,
clearIcon = slots.clearIcon?.(),
id = formItemContext.id.value,
...restProps
} = p;
const showTime = p.showTime === '' ? true : p.showTime;
@ -198,6 +203,7 @@ export default function generateSinglePicker<DateType, ExtraProps = {}>(
transitionName={transitionName || `${rootPrefixCls.value}-slide-up`}
{...restProps}
{...additionalOverrideProps}
id={id}
picker={mergedPicker}
value={value.value}
defaultValue={defaultValue.value}

View File

@ -19,6 +19,7 @@ import type { VueNode } from '../../_util/type';
function commonProps<DateType>() {
return {
id: String,
dropdownClassName: String,
dropdownAlign: { type: Object as PropType<AlignType> },
popupStyle: { type: Object as PropType<CSSProperties> },
@ -77,6 +78,7 @@ function commonProps<DateType>() {
}
export interface CommonProps<DateType> {
id?: string;
prefixCls?: string;
dropdownClassName?: string;
dropdownAlign?: AlignType;

View File

@ -4,9 +4,8 @@ import cloneDeep from 'lodash-es/cloneDeep';
import PropTypes from '../_util/vue-types';
import Row from '../grid/Row';
import type { ColProps } from '../grid/Col';
import { isValidElement, flattenChildren, filterEmpty } from '../_util/props-util';
import { filterEmpty } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import { cloneElement } from '../_util/vnode';
import { validateRules as validateRulesUtil } from './utils/validateUtil';
import { getNamePath } from './utils/valueUtil';
import { toArray } from './utils/typeUtil';
@ -19,6 +18,7 @@ import { useInjectForm } from './context';
import FormItemLabel from './FormItemLabel';
import FormItemInput from './FormItemInput';
import type { ValidationRule } from './Form';
import { useProvideFormItemContext } from './FormItemContext';
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', '');
export type ValidateStatus = typeof ValidateStatuses[number];
@ -271,6 +271,13 @@ export default defineComponent({
clearValidate,
resetField,
});
useProvideFormItemContext({
id: fieldId,
onFieldBlur,
onFieldChange,
clearValidate,
});
let registered = false;
watch(
fieldName,
@ -299,12 +306,6 @@ export default defineComponent({
onBeforeUnmount(() => {
formContext.removeField(eventKey);
});
// const onHelpAnimEnd = (_key: string, helpShow: boolean) => {
// this.helpShow = helpShow;
// if (!helpShow) {
// this.$forceUpdate();
// }
// };
const itemClassName = computed(() => ({
[`${prefixCls.value}-item`]: true,
@ -318,36 +319,6 @@ export default defineComponent({
}));
return () => {
const help = props.help ?? (slots.help ? filterEmpty(slots.help()) : null);
const children = flattenChildren(slots.default?.());
let firstChildren = children[0];
if (fieldName.value && props.autoLink && isValidElement(firstChildren)) {
const originalEvents = firstChildren.props || {};
const originalBlur = originalEvents.onBlur;
const originalChange = originalEvents.onChange;
firstChildren = cloneElement(firstChildren, {
...(fieldId.value ? { id: fieldId.value } : undefined),
onBlur: (...args: any[]) => {
if (Array.isArray(originalChange)) {
for (let i = 0, l = originalChange.length; i < l; i++) {
originalBlur[i](...args);
}
} else if (originalBlur) {
originalBlur(...args);
}
onFieldBlur();
},
onChange: (...args: any[]) => {
if (Array.isArray(originalChange)) {
for (let i = 0, l = originalChange.length; i < l; i++) {
originalChange[i](...args);
}
} else if (originalChange) {
originalChange(...args);
}
onFieldChange();
},
});
}
return (
<Row
{...attrs}
@ -357,32 +328,37 @@ export default defineComponent({
attrs.class,
]}
key="row"
>
{/* Label */}
<FormItemLabel
{...props}
htmlFor={fieldId.value}
required={isRequired.value}
requiredMark={formContext.requiredMark.value}
prefixCls={prefixCls.value}
onClick={onLabelClick}
label={props.label ?? slots.label?.()}
/>
{/* Input Group */}
<FormItemInput
{...props}
errors={help !== undefined && help !== null ? toArray(help) : errors.value}
prefixCls={prefixCls.value}
status={validateState.value}
onDomErrorVisibleChange={(v: boolean) => (domErrorVisible.value = v)}
validateStatus={validateState.value}
ref={inputRef}
help={help}
extra={props.extra ?? slots.extra?.()}
>
{[firstChildren, children.slice(1)]}
</FormItemInput>
</Row>
v-slots={{
default: () => (
<>
{/* Label */}
<FormItemLabel
{...props}
htmlFor={fieldId.value}
required={isRequired.value}
requiredMark={formContext.requiredMark.value}
prefixCls={prefixCls.value}
onClick={onLabelClick}
label={props.label ?? slots.label?.()}
/>
{/* Input Group */}
<FormItemInput
{...props}
errors={help !== undefined && help !== null ? toArray(help) : errors.value}
prefixCls={prefixCls.value}
status={validateState.value}
onDomErrorVisibleChange={(v: boolean) => (domErrorVisible.value = v)}
validateStatus={validateState.value}
ref={inputRef}
help={help}
extra={props.extra ?? slots.extra?.()}
v-slots={{ default: slots.default }}
// v-slots={{ default: () => [firstChildren, children.slice(1)] }}
></FormItemInput>
</>
),
}}
></Row>
);
};
},

View File

@ -0,0 +1,31 @@
import type { ComputedRef, InjectionKey } from 'vue';
import { computed, inject, provide } from 'vue';
export type FormItemContext = {
id: ComputedRef<string>;
onFieldBlur: () => void;
onFieldChange: () => void;
clearValidate: () => void;
};
type ContextProps = FormItemContext;
const ContextKey: InjectionKey<ContextProps> = Symbol('ContextProps');
export const useProvideFormItemContext = (props: ContextProps) => {
provide(ContextKey, props);
};
export const useInjectFormItemContext = () => {
const defaultContext: ContextProps = {
id: computed(() => undefined),
onFieldBlur: () => {},
onFieldChange: () => {},
clearValidate: () => {},
};
// We should prevent the passing of context for children
provide(ContextKey, defaultContext);
return inject(ContextKey, defaultContext);
};

View File

@ -86,33 +86,32 @@ const FormItemInput = defineComponent({
// Should provides additional icon if `hasFeedback`
const IconNode = validateStatus && iconMap[validateStatus];
const icon =
hasFeedback && IconNode ? (
<span class={`${baseClassName}-children-icon`}>
<IconNode />
</span>
) : null;
const inputDom = (
<div class={`${baseClassName}-control-input`}>
<div class={`${baseClassName}-control-input-content`}>{slots.default?.()}</div>
{icon}
</div>
);
const errorListDom = (
<ErrorList errors={errors} help={help} onDomErrorVisibleChange={onDomErrorVisibleChange} />
);
// If extra = 0, && will goes wrong
// 0&&error -> 0
const extraDom = extra ? <div class={`${baseClassName}-extra`}>{extra}</div> : null;
return (
<Col {...mergedWrapperCol} class={className}>
{inputDom}
{errorListDom}
{extraDom}
</Col>
<Col
{...mergedWrapperCol}
class={className}
v-slots={{
default: () => (
<>
<div class={`${baseClassName}-control-input`}>
<div class={`${baseClassName}-control-input-content`}>{slots.default?.()}</div>
{hasFeedback && IconNode ? (
<span class={`${baseClassName}-children-icon`}>
<IconNode />
</span>
) : null}
</div>
<ErrorList
errors={errors}
help={help}
onDomErrorVisibleChange={onDomErrorVisibleChange}
/>
{extra ? <div class={`${baseClassName}-extra`}>{extra}</div> : null}
</>
),
}}
></Col>
);
};
},

View File

@ -141,7 +141,7 @@ exports[`renders ./components/form/demo/custom-validation.vue correctly 1`] = `
</label></div>
<div class="ant-col ant-col-14 ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content"><input type="password" autocomplete="off" id="custom-validation_pass" class="ant-input"></div>
<div class="ant-form-item-control-input-content"><input type="password" id="custom-validation_pass" autocomplete="off" class="ant-input"></div>
<!---->
</div>
<!---->
@ -154,7 +154,7 @@ exports[`renders ./components/form/demo/custom-validation.vue correctly 1`] = `
</label></div>
<div class="ant-col ant-col-14 ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content"><input type="password" autocomplete="off" id="custom-validation_checkPass" class="ant-input"></div>
<div class="ant-form-item-control-input-content"><input type="password" id="custom-validation_checkPass" autocomplete="off" class="ant-input"></div>
<!---->
</div>
<!---->

View File

@ -15,6 +15,7 @@
</demo-sort>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Basic from './basic.vue';
import CustomValidation from './custom-validation.vue';
import DynamicFormItem from './dynamic-form-item.vue';
@ -27,11 +28,10 @@ import UseFormNested from './useForm-nested.vue';
import UseFormTrigger from './useForm-trigger.vue';
import UseFormMerge from './useForm-merge.vue';
import LableWidth from './lable-width.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
export default {
export default defineComponent({
US,
CN,
components: {
@ -48,7 +48,7 @@ export default {
UseFormMerge,
LableWidth,
},
};
});
</script>
<style>
.code-box-demo .ant-form:not(.ant-form-inline):not(.ant-form-vertical) {

View File

@ -2,6 +2,7 @@ import type { App, Plugin } from 'vue';
import Form, { formProps } from './Form';
import FormItem, { formItemProps } from './FormItem';
import useForm from './useForm';
import { useInjectFormItemContext } from './FormItemContext';
export type { FormProps } from './Form';
export type { FormItemProps } from './FormItem';
@ -13,10 +14,11 @@ Form.install = function (app: App) {
return app;
};
export { FormItem, formItemProps, formProps, useForm };
export { FormItem, formItemProps, formProps, useForm, useInjectFormItemContext };
export default Form as typeof Form &
Plugin & {
readonly Item: typeof Form.Item;
readonly useForm: typeof useForm;
readonly useInjectFormItemContext: typeof useInjectFormItemContext;
};

View File

@ -9,6 +9,7 @@ import VcInputNumber from '../vc-input-number/src';
import { defaultConfigProvider } from '../config-provider';
import { tuple, withInstall } from '../_util/type';
import type { EventHandler } from '../_util/EventInterface';
import { useInjectFormItemContext } from '../form/FormItemContext';
const inputNumberProps = {
prefixCls: PropTypes.string,
@ -40,7 +41,9 @@ const InputNumber = defineComponent({
name: 'AInputNumber',
inheritAttrs: false,
props: inputNumberProps,
setup(props) {
emits: ['input', 'change', 'blur', 'update:value'],
setup(props, { emit }) {
const formItemContext = useInjectFormItemContext();
const inputNumberRef = ref(null);
const focus = () => {
inputNumberRef.value.focus();
@ -48,6 +51,13 @@ const InputNumber = defineComponent({
const blur = () => {
inputNumberRef.value.blur();
};
const handleChange = (val: number) => {
emit('update:value', val);
emit('change', val);
};
const handleBlur = () => {
emit('blur');
};
onMounted(() => {
nextTick(() => {
if (process.env.NODE_ENV === 'test') {
@ -62,6 +72,9 @@ const InputNumber = defineComponent({
inputNumberRef,
focus,
blur,
formItemContext,
handleBlur,
handleChange,
};
},
@ -70,6 +83,7 @@ const InputNumber = defineComponent({
prefixCls: customizePrefixCls,
size,
class: className,
id = this.formItemContext.id.value,
...others
} = {
...getOptionProps(this),
@ -94,6 +108,9 @@ const InputNumber = defineComponent({
downHandler: downIcon,
...others,
class: inputNumberClass,
onChange: this.handleChange,
onBlur: this.handleBlur,
id,
};
return <VcInputNumber {...vcInputNumberProps} ref="inputNumberRef" />;
},

View File

@ -7,6 +7,7 @@ import inputProps from './inputProps';
import { hasProp, getComponent, getOptionProps } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import ClearableLabeledInput from './ClearableLabeledInput';
import { useInjectFormItemContext } from '../form/FormItemContext';
export function fixControlledValue(value: string | number) {
if (typeof value === 'undefined' || value === null) {
@ -56,11 +57,13 @@ export default defineComponent({
...inputProps,
},
setup() {
const formItemContext = useInjectFormItemContext();
return {
configProvider: inject('configProvider', defaultConfigProvider),
removePasswordTimeout: undefined,
input: null,
clearableInput: null,
formItemContext,
};
},
data() {
@ -100,6 +103,7 @@ export default defineComponent({
handleInputBlur(e: Event) {
this.isFocused = false;
this.onBlur && this.onBlur(e);
this.formItemContext.onFieldBlur();
},
focus() {
@ -138,6 +142,7 @@ export default defineComponent({
this.$emit('update:value', (e.target as HTMLInputElement).value);
this.$emit('change', e);
this.$emit('input', e);
this.formItemContext.onFieldChange();
},
handleReset(e: Event) {
this.setValue('', () => {
@ -173,6 +178,7 @@ export default defineComponent({
const inputProps: any = {
...otherProps,
...$attrs,
id: otherProps.id ?? this.formItemContext.id.value,
onKeydown: handleKeyDown,
class: classNames(getInputClassName(prefixCls, size, disabled), {
[$attrs.class as string]: $attrs.class && !addonBefore && !addonAfter,
@ -240,6 +246,7 @@ export default defineComponent({
prefix,
isFocused,
};
return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
},
});

View File

@ -7,6 +7,7 @@ import { defaultConfigProvider } from '../config-provider';
import { fixControlledValue, resolveOnChange } from './Input';
import classNames from '../_util/classNames';
import PropTypes, { withUndefined } from '../_util/vue-types';
import { useInjectFormItemContext } from '../form/FormItemContext';
const TextAreaProps = {
...inputProps,
@ -24,10 +25,12 @@ export default defineComponent({
...TextAreaProps,
},
setup() {
const formItemContext = useInjectFormItemContext();
return {
configProvider: inject('configProvider', defaultConfigProvider),
resizableTextArea: null,
clearableInput: null,
formItemContext,
};
},
data() {
@ -71,6 +74,7 @@ export default defineComponent({
this.$emit('update:value', (e.target as any).value);
this.$emit('change', e);
this.$emit('input', e);
this.formItemContext.onFieldChange();
},
handleChange(e: Event) {
const { value, composing, isComposing } = e.target as any;
@ -103,6 +107,10 @@ export default defineComponent({
});
resolveOnChange(this.resizableTextArea.textArea, e, this.triggerChange);
},
handleBlur(e: Event) {
this.$emit('blur', e);
this.formItemContext.onFieldBlur();
},
renderTextArea(prefixCls: string) {
const props = getOptionProps(this);
@ -118,7 +126,13 @@ export default defineComponent({
onChange: this.handleChange,
onKeydown: this.handleKeyDown,
};
return <ResizableTextArea {...resizeProps} ref={this.saveTextArea} />;
return (
<ResizableTextArea
{...resizeProps}
id={resizeProps.id ?? this.formItemContext.id.value}
ref={this.saveTextArea}
/>
);
},
},
render() {

View File

@ -66,7 +66,7 @@ exports[`renders ./components/input/demo/group.vue correctly 1`] = `
<div style="width: 200px;" class="ant-select ant-select-show-search ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search">
<!---->
<!---->
<div class="ant-select-selector"><span class="ant-select-selection-search"><input type="search" id="rc_select_TEST_OR_SSR" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<div class="ant-select-selector"><span class="ant-select-selection-search"><input id="rc_select_TEST_OR_SSR" type="search" autocomplete="off" class="ant-input ant-select-selection-search-input" role="combobox" aria-haspopup="listbox" aria-owns="rc_select_TEST_OR_SSR_list" aria-autocomplete="list" aria-controls="rc_select_TEST_OR_SSR_list" aria-activedescendant="rc_select_TEST_OR_SSR_list_0"></span>
<!----><span class="ant-select-selection-placeholder">Email</span>
</div>
<!---->

View File

@ -2,6 +2,7 @@ import type { PropType } from 'vue';
import PropTypes from '../_util/vue-types';
import type { SizeType } from '../config-provider';
export default {
id: PropTypes.string,
prefixCls: PropTypes.string,
inputPrefixCls: PropTypes.string,
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

View File

@ -21,7 +21,7 @@ exports[`renders ./components/mentions/demo/form.vue correctly 1`] = `
<div class="ant-col ant-col-18 ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content">
<div class="ant-mentions"><textarea rows="1"></textarea>
<div class="ant-mentions"><textarea rows="1" id="coders"></textarea>
<!---->
</div>
</div>
@ -38,7 +38,7 @@ exports[`renders ./components/mentions/demo/form.vue correctly 1`] = `
<div class="ant-col ant-col-18 ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content">
<div class="ant-mentions"><textarea rows="3" placeholder="You can use @ to ref user here"></textarea>
<div class="ant-mentions"><textarea rows="3" placeholder="You can use @ to ref user here" id="bio"></textarea>
<!---->
</div>
</div>

View File

@ -7,6 +7,7 @@ import VcMentions from '../vc-mentions';
import { mentionsProps as baseMentionsProps } from '../vc-mentions/src/mentionsProps';
import useConfigInject from '../_util/hooks/useConfigInject';
import { flattenChildren, getOptionProps } from '../_util/props-util';
import { useInjectFormItemContext } from '../form/FormItemContext';
const { Option } = VcMentions;
@ -75,6 +76,7 @@ const mentionsProps = {
},
notFoundContent: PropTypes.any,
defaultValue: String,
id: String,
};
export type MentionsProps = Partial<ExtractPropTypes<typeof mentionsProps>>;
@ -92,6 +94,7 @@ const Mentions = defineComponent({
const focused = ref(false);
const vcMentions = ref(null);
const value = ref(props.value ?? props.defaultValue ?? '');
const formItemContext = useInjectFormItemContext();
watch(
() => props.value,
val => {
@ -106,6 +109,7 @@ const Mentions = defineComponent({
const handleBlur = (e: FocusEvent) => {
focused.value = false;
emit('blur', e);
formItemContext.onFieldBlur();
};
const handleSelect = (...args: [MentionsOptionProps, string]) => {
@ -119,6 +123,7 @@ const Mentions = defineComponent({
}
emit('update:value', val);
emit('change', val);
formItemContext.onFieldChange();
};
const getNotFoundContent = () => {
@ -159,7 +164,13 @@ const Mentions = defineComponent({
});
return () => {
const { disabled, getPopupContainer, rows = 1, ...restProps } = props;
const {
disabled,
getPopupContainer,
rows = 1,
id = formItemContext.id.value,
...restProps
} = props;
const { class: className, ...otherAttrs } = attrs;
const otherProps = omit(restProps, ['defaultValue', 'onUpdate:value', 'prefixCls']);
@ -186,6 +197,7 @@ const Mentions = defineComponent({
onBlur: handleBlur,
ref: vcMentions,
value: value.value,
id,
};
return (
<VcMentions

View File

@ -6,6 +6,7 @@ import { getOptionProps, filterEmpty, hasProp, getSlot } from '../_util/props-ut
import { defaultConfigProvider } from '../config-provider';
import { tuple } from '../_util/type';
import type { RadioChangeEvent } from './interface';
import { useInjectFormItemContext } from '../form/FormItemContext';
export default defineComponent({
name: 'ARadioGroup',
@ -19,10 +20,13 @@ export default defineComponent({
name: PropTypes.string,
buttonStyle: PropTypes.string.def('outline'),
onChange: PropTypes.func,
id: PropTypes.string,
},
emits: ['update:value', 'change'],
setup() {
const formItemContext = useInjectFormItemContext();
return {
formItemContext,
updatingValue: false,
configProvider: inject('configProvider', defaultConfigProvider),
radioGroupContext: null,
@ -65,6 +69,7 @@ export default defineComponent({
this.updatingValue = true;
this.$emit('update:value', value);
this.$emit('change', ev);
this.formItemContext.onFieldChange();
}
nextTick(() => {
this.updatingValue = false;
@ -73,7 +78,12 @@ export default defineComponent({
},
render() {
const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, options, buttonStyle } = props;
const {
prefixCls: customizePrefixCls,
options,
buttonStyle,
id = this.formItemContext.id.value,
} = props;
const { getPrefixCls } = this.configProvider;
const prefixCls = getPrefixCls('radio', customizePrefixCls);
@ -114,6 +124,10 @@ export default defineComponent({
});
}
return <div class={classString}>{children}</div>;
return (
<div class={classString} id={id}>
{children}
</div>
);
},
});

View File

@ -6,6 +6,7 @@ import classNames from '../_util/classNames';
import { getOptionProps } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import type { RadioChangeEvent } from './interface';
import { useInjectFormItemContext } from '../form/FormItemContext';
export const radioProps = {
prefixCls: PropTypes.string,
@ -30,9 +31,11 @@ export default defineComponent({
props: radioProps,
emits: ['update:checked', 'update:value', 'change', 'blur', 'focus'],
setup() {
const formItemContext = useInjectFormItemContext();
return {
configProvider: inject('configProvider', defaultConfigProvider),
radioGroupContext: inject('radioGroupContext', null),
formItemContext,
};
},
methods: {
@ -47,6 +50,7 @@ export default defineComponent({
this.$emit('update:checked', targetChecked);
this.$emit('update:value', targetChecked);
this.$emit('change', event);
this.formItemContext.onFieldChange();
},
onChange2(e: RadioChangeEvent) {
this.$emit('change', e);
@ -59,12 +63,17 @@ export default defineComponent({
render() {
const { $slots, radioGroupContext: radioGroup } = this;
const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, ...restProps } = props;
const {
prefixCls: customizePrefixCls,
id = this.formItemContext.id.value,
...restProps
} = props;
const { getPrefixCls } = this.configProvider;
const prefixCls = getPrefixCls('radio', customizePrefixCls);
const rProps: RadioProps = {
prefixCls,
id,
...restProps,
};

View File

@ -12,6 +12,7 @@ import useConfigInject from '../_util/hooks/useConfigInject';
import Star from './Star';
import { useRef } from '../_util/hooks/useRef';
import { useInjectFormItemContext } from '../form/FormItemContext';
export const rateProps = {
prefixCls: PropTypes.string,
@ -25,6 +26,7 @@ export const rateProps = {
autofocus: PropTypes.looseBool,
tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
direction: PropTypes.string,
id: PropTypes.string,
};
export type RateProps = Partial<ExtractPropTypes<typeof rateProps>>;
@ -44,6 +46,7 @@ const Rate = defineComponent({
emits: ['hoverChange', 'update:value', 'change', 'focus', 'blur', 'keydown'],
setup(props, { slots, attrs, emit, expose }) {
const { prefixCls, direction } = useConfigInject('rate', props);
const formItemContext = useInjectFormItemContext();
const rateRef = ref();
const [setRef, starRefs] = useRef();
const state = reactive({
@ -82,6 +85,7 @@ const Rate = defineComponent({
}
emit('update:value', value);
emit('change', value);
formItemContext.onFieldChange();
};
const onHover = (e: MouseEvent, index: number) => {
@ -115,6 +119,7 @@ const Rate = defineComponent({
const onBlur = () => {
state.focused = false;
emit('blur');
formItemContext.onFieldBlur();
};
const onKeyDown = (event: KeyboardEvent) => {
const { keyCode } = event;
@ -187,7 +192,7 @@ const Rate = defineComponent({
const character = getPropsSlot(slots, props, 'character') || <StarFilled />;
return () => {
const { count, allowHalf, disabled, tabindex } = props;
const { count, allowHalf, disabled, tabindex, id = formItemContext.id.value } = props;
const { class: className, style } = attrs;
const stars = [];
const disabledClass = disabled ? `${prefixCls.value}-disabled` : '';
@ -216,6 +221,7 @@ const Rate = defineComponent({
return (
<ul
{...attrs}
id={id}
class={rateClassName}
style={style}
onMouseleave={disabled ? null : onMouseLeave}

View File

@ -8,6 +8,7 @@ import PropTypes from '../_util/vue-types';
import { tuple } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
import omit from '../_util/omit';
import { useInjectFormItemContext } from '../form/FormItemContext';
type RawValue = string | number;
@ -49,7 +50,7 @@ const Select = defineComponent({
inheritAttrs: false,
props: selectProps(),
SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE',
emits: ['change', 'update:value'],
emits: ['change', 'update:value', 'blur'],
slots: [
'notFoundContent',
'suffixIcon',
@ -61,7 +62,7 @@ const Select = defineComponent({
],
setup(props, { attrs, emit, slots, expose }) {
const selectRef = ref();
const formItemContext = useInjectFormItemContext();
const focus = () => {
if (selectRef.value) {
selectRef.value.focus();
@ -99,6 +100,11 @@ const Select = defineComponent({
const triggerChange = (...args: any[]) => {
emit('update:value', args[0]);
emit('change', ...args);
formItemContext.onFieldChange();
};
const handleBlur = (e: InputEvent) => {
emit('blur', e);
formItemContext.onFieldBlur();
};
expose({
blur,
@ -113,6 +119,7 @@ const Select = defineComponent({
dropdownClassName,
virtual,
dropdownMatchSelectWidth,
id = formItemContext.id.value,
} = props;
const { renderEmpty, getPopupContainer: getContextPopupContainer } = configProvider;
@ -175,6 +182,8 @@ const Select = defineComponent({
getPopupContainer={getPopupContainer || getContextPopupContainer}
dropdownClassName={rcSelectRtlDropDownClassName}
onChange={triggerChange}
onBlur={handleBlur}
id={id}
dropdownRender={selectProps.dropdownRender || slots.dropdownRender}
v-slots={{ option: slots.option }}
>

View File

@ -2,7 +2,7 @@
exports[`renders ./components/slider/demo/basic.vue correctly 1`] = `
<div>
<div tabindex="-1" class="ant-slider">
<div id="test" tabindex="-1" class="ant-slider">
<div class="ant-slider-rail"></div>
<div class="ant-slider-track" style="left: 0%; width: 0%;"></div>
<div class="ant-slider-step"></div>

View File

@ -10,6 +10,7 @@ import type { TooltipPlacement } from '../tooltip/Tooltip';
import useConfigInject from '../_util/hooks/useConfigInject';
import SliderTooltip from './SliderTooltip';
import classNames from '../_util/classNames';
import { useInjectFormItemContext } from '../form/FormItemContext';
export type SliderValue = number | [number, number];
@ -39,6 +40,7 @@ type Value = [number, number] | number;
const defaultTipFormatter = (value: number) => (typeof value === 'number' ? value.toString() : '');
export const sliderProps = () => ({
id: String,
prefixCls: String,
tooltipPrefixCls: String,
range: { type: [Boolean, Object] as PropType<boolean | SliderRange>, default: undefined },
@ -78,11 +80,12 @@ const Slider = defineComponent({
props: {
...sliderProps(),
},
emits: ['update:value', 'change', 'afterChange'],
emits: ['update:value', 'change', 'afterChange', 'blur'],
slots: ['mark'],
setup(props, { attrs, slots, emit, expose }) {
const { prefixCls, rootPrefixCls, direction, getPopupContainer, configProvider } =
useConfigInject('slider', props);
const formItemContext = useInjectFormItemContext();
const sliderRef = ref();
const visibles = ref<Visibles>({});
const toggleTooltipVisible = (index: number, visible: boolean) => {
@ -107,6 +110,10 @@ const Slider = defineComponent({
const handleChange = (val: SliderValue) => {
emit('update:value', val);
emit('change', val);
formItemContext.onFieldChange();
};
const handleBlur = () => {
emit('blur');
};
expose({
focus,
@ -140,7 +147,12 @@ const Slider = defineComponent({
);
};
return () => {
const { tooltipPrefixCls: customizeTooltipPrefixCls, range, ...restProps } = props;
const {
tooltipPrefixCls: customizeTooltipPrefixCls,
range,
id = formItemContext.id.value,
...restProps
} = props;
const tooltipPrefixCls = configProvider.getPrefixCls('tooltip', customizeTooltipPrefixCls);
const cls = classNames(attrs.class, {
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
@ -181,6 +193,7 @@ const Slider = defineComponent({
return (
<VcSlider
{...restProps}
id={id}
step={restProps.step!}
class={cls}
ref={ref}
@ -193,6 +206,7 @@ const Slider = defineComponent({
}
prefixCls={prefixCls.value}
onChange={handleChange}
onBlur={handleBlur}
v-slots={{ mark: slots.mark }}
/>
);

View File

@ -9,10 +9,12 @@ import { tuple, withInstall } from '../_util/type';
import { getPropsSlot } from '../_util/props-util';
import Omit from 'omit.js';
import useConfigInject from '../_util/hooks/useConfigInject';
import { useInjectFormItemContext } from '../form/FormItemContext';
export const SwitchSizes = tuple('small', 'default');
type CheckedType = boolean | string | number;
const switchProps = {
id: PropTypes.string,
prefixCls: PropTypes.string,
size: PropTypes.oneOf(SwitchSizes),
disabled: PropTypes.looseBool,
@ -55,8 +57,9 @@ const Switch = defineComponent({
inheritAttrs: false,
props: switchProps,
slots: ['checkedChildren', 'unCheckedChildren'],
emits: ['update:checked', 'mouseup', 'change', 'click', 'keydown'],
emits: ['update:checked', 'mouseup', 'change', 'click', 'keydown', 'blur'],
setup(props, { attrs, slots, expose, emit }) {
const formItemContext = useInjectFormItemContext();
onBeforeMount(() => {
warning(
!('defaultChecked' in attrs),
@ -104,6 +107,11 @@ const Switch = defineComponent({
}
emit('update:checked', check);
emit('change', check, e);
formItemContext.onFieldChange();
};
const handleBlur = () => {
emit('blur');
};
const handleClick = (e: MouseEvent) => {
@ -147,10 +155,13 @@ const Switch = defineComponent({
'defaultChecked',
'checkedValue',
'unCheckedValue',
'id',
])}
{...attrs}
id={props.id ?? formItemContext.id.value}
onKeydown={handleKeyDown}
onClick={handleClick}
onBlur={handleBlur}
onMouseup={handleMouseUp}
type="button"
role="switch"

View File

@ -11,6 +11,7 @@ import type { GenerateConfig } from '../vc-picker/generate';
import type { PanelMode, RangeValue } from '../vc-picker/interface';
import type { RangePickerSharedProps } from '../vc-picker/RangePicker';
import devWarning from '../vc-util/devWarning';
import { useInjectFormItemContext } from '../form/FormItemContext';
export interface TimePickerLocale {
placeholder?: string;
@ -74,6 +75,7 @@ function createTimePicker<
slot: ['addon', 'renderExtraFooter', 'suffixIcon', 'clearIcon'],
emits: ['change', 'openChange', 'focus', 'blur', 'ok', 'update:value', 'update:open'],
setup(props, { slots, expose, emit, attrs }) {
const formItemContext = useInjectFormItemContext();
devWarning(
!(slots.addon || props.addon),
'TimePicker',
@ -91,6 +93,7 @@ function createTimePicker<
const onChange = (value: DateType | string, dateString: string) => {
emit('update:value', value);
emit('change', value, dateString);
formItemContext.onFieldChange();
};
const onOpenChange = (open: boolean) => {
emit('update:open', open);
@ -101,15 +104,18 @@ function createTimePicker<
};
const onBlur = () => {
emit('blur');
formItemContext.onFieldBlur();
};
const onOk = (value: DateType) => {
emit('ok', value);
};
return () => {
const { id = formItemContext.id.value, ...restProps } = props;
return (
<InternalTimePicker
{...attrs}
{...props}
{...restProps}
id={id}
dropdownClassName={props.popupClassName}
mode={undefined}
ref={pickerRef}
@ -152,6 +158,9 @@ function createTimePicker<
],
setup(props, { slots, expose, emit, attrs }) {
const pickerRef = ref();
const formItemContext = useInjectFormItemContext();
expose({
focus: () => {
pickerRef.value?.focus();
@ -166,6 +175,7 @@ function createTimePicker<
) => {
emit('update:value', values);
emit('change', values, dateStrings);
formItemContext.onFieldChange();
};
const onOpenChange = (open: boolean) => {
emit('update:open', open);
@ -176,6 +186,7 @@ function createTimePicker<
};
const onBlur = () => {
emit('blur');
formItemContext.onFieldBlur();
};
const onPanelChange = (
values: RangeValue<string> | RangeValue<DateType>,
@ -194,10 +205,12 @@ function createTimePicker<
emit('calendarChange', values, dateStrings, info);
};
return () => {
const { id = formItemContext.id.value, ...restProps } = props;
return (
<InternalRangePicker
{...attrs}
{...props}
{...restProps}
id={id}
dropdownClassName={props.popupClassName}
picker="time"
mode={undefined}

View File

@ -13,6 +13,7 @@ import { withInstall } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
import type { TransferListBodyProps } from './ListBody';
import type { PaginationType } from './interface';
import { useInjectFormItemContext } from '../form/FormItemContext';
export type { TransferListProps } from './list';
export type { TransferOperationProps } from './operation';
@ -64,6 +65,7 @@ export interface TransferLocale {
}
export const transferProps = {
id: String,
prefixCls: String,
dataSource: { type: Array as PropType<TransferItem[]>, default: [] },
disabled: { type: Boolean, default: undefined },
@ -118,6 +120,7 @@ const Transfer = defineComponent({
const sourceSelectedKeys = ref([]);
const targetSelectedKeys = ref([]);
const formItemContext = useInjectFormItemContext();
watch(
() => props.selectedKeys,
() => {
@ -164,6 +167,7 @@ const Transfer = defineComponent({
emit('update:targetKeys', newTargetKeys);
handleSelectChange(oppositeDirection, []);
emit('change', newTargetKeys, direction, newMoveKeys);
formItemContext.onFieldChange();
};
const moveToLeft = () => {
@ -312,6 +316,7 @@ const Transfer = defineComponent({
selectAllLabels = [],
oneWay,
pagination,
id = formItemContext.id.value,
} = props;
const { class: className, style } = attrs;
@ -335,7 +340,7 @@ const Transfer = defineComponent({
const rightTitle =
(titles && titles[1]) ?? slots.rightTitle?.() ?? (locale.titles || ['', ''])[1];
return (
<div class={cls} style={style}>
<div class={cls} style={style} id={id}>
<List
key="leftList"
prefixCls={`${prefixCls.value}-list`}

View File

@ -20,6 +20,7 @@ import renderSwitcherIcon from '../tree/utils/iconUtil';
import type { AntTreeNodeProps } from '../tree/Tree';
import { warning } from '../vc-util/warning';
import { flattenChildren } from '../_util/props-util';
import { useInjectFormItemContext } from '../form/FormItemContext';
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
if (transitionName !== undefined) {
@ -92,6 +93,8 @@ const TreeSelect = defineComponent({
'`replaceFields` is deprecated, please use fieldNames instead',
);
});
const formItemContext = useInjectFormItemContext();
const {
configProvider,
prefixCls,
@ -131,6 +134,7 @@ const TreeSelect = defineComponent({
const handleChange = (...args: any[]) => {
emit('update:value', args[0]);
emit('change', ...args);
formItemContext.onFieldChange();
};
const handleTreeExpand = (...args: any[]) => {
emit('update:treeExpandedKeys', args[0]);
@ -140,6 +144,10 @@ const TreeSelect = defineComponent({
emit('update:searchValue', args[0]);
emit('search', ...args);
};
const handleBlur = () => {
emit('blur');
formItemContext.onFieldBlur();
};
return () => {
const {
notFoundContent = slots.notFoundContent?.(),
@ -154,6 +162,7 @@ const TreeSelect = defineComponent({
treeLine,
switcherIcon = slots.switcherIcon?.(),
fieldNames = props.replaceFields,
id = formItemContext.id.value,
} = props;
// ===================== Icons =====================
const { suffixIcon, removeIcon, clearIcon } = getIcons(
@ -202,6 +211,7 @@ const TreeSelect = defineComponent({
virtual={virtual.value}
dropdownMatchSelectWidth={dropdownMatchSelectWidth.value}
{...selectProps}
id={id}
fieldNames={fieldNames}
ref={treeSelectRef}
prefixCls={prefixCls.value}
@ -223,6 +233,7 @@ const TreeSelect = defineComponent({
choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)}
transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)}
onChange={handleChange}
onBlur={handleBlur}
onSearch={handleSearch}
onTreeExpand={handleTreeExpand}
v-slots={{

View File

@ -14,6 +14,7 @@ import { UploadProps } from './interface';
import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from './utils';
import { defineComponent, inject } from 'vue';
import { getDataAndAriaProps } from '../_util/util';
import { useInjectFormItemContext } from '../form/FormItemContext';
export type UploadFileStatus = 'error' | 'success' | 'done' | 'uploading' | 'removed';
export interface UploadFile<T = any> {
@ -54,10 +55,12 @@ export default defineComponent({
supportServerRender: true,
}),
setup() {
const formItemContext = useInjectFormItemContext();
return {
upload: null,
progressTimer: null,
configProvider: inject('configProvider', defaultConfigProvider),
formItemContext,
};
},
// recentUploadStatus: boolean | PromiseLike<any>;
@ -190,6 +193,7 @@ export default defineComponent({
}
this.$emit('update:fileList', info.fileList);
this.$emit('change', info);
this.formItemContext.onFieldChange();
},
onFileDrop(e) {
this.setState({
@ -276,6 +280,7 @@ export default defineComponent({
const vcUploadProps = {
...this.$props,
id: this.$props.id ?? this.formItemContext.id.value,
prefixCls,
beforeUpload: this.reBeforeUpload,
onStart: this.onStart,

View File

@ -158,7 +158,6 @@ export default defineComponent({
typeof nextValue === 'number' &&
nextValue > max
) {
this.__emit('update:value', max);
this.__emit('change', max);
}
if (
@ -167,7 +166,6 @@ export default defineComponent({
typeof nextValue === 'number' &&
nextValue < min
) {
this.__emit('update:value', min);
this.__emit('change', min);
}
}
@ -277,7 +275,6 @@ export default defineComponent({
this.rawInput = this.parser(this.getValueFromEvent(e));
this.setState({ inputValue: this.rawInput });
const num = this.toNumber(this.rawInput); // valid number or invalid string
this.__emit('update:value', num);
this.__emit('change', num);
},
onFocus(...args) {
@ -374,7 +371,6 @@ export default defineComponent({
);
}
if (changed) {
this.__emit('update:value', newValue);
this.__emit('change', newValue);
}
return newValue;

View File

@ -16,6 +16,7 @@ function noop() {}
export default function createSlider(Component) {
// const displayName = `ComponentEnhancer(${Component.displayName})`
const propTypes = {
id: PropTypes.string,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
@ -292,8 +293,9 @@ export default function createSlider(Component) {
railStyle,
dotStyle,
activeDotStyle,
id,
} = this;
const { class: className, style, id } = this.$attrs;
const { class: className, style } = this.$attrs;
const { tracks, handles } = this.renderSlider();
const sliderClassName = classNames(prefixCls, className, {

View File

@ -51,7 +51,8 @@
"routes": "node site/scripts/genrateRoutes.js",
"tsc": "tsc --noEmit",
"site": "yarn routes && ./node_modules/vite/bin/vite.js build site --base=https://next.antdv.com/",
"pub:site": "npm run site && node site/scripts/pushToOSS.js"
"pub:site": "npm run site && node site/scripts/pushToOSS.js",
"prepare": "husky install"
},
"repository": {
"type": "git",
@ -157,7 +158,7 @@
"gulp-strip-code": "^0.1.4",
"gulp-typescript": "^6.0.0-alpha.1",
"html-webpack-plugin": "^5.3.1",
"husky": "^4.0.0",
"husky": "^6.0.0",
"ignore-emit-webpack-plugin": "^2.0.6",
"jest": "^26.0.0",
"jest-environment-jsdom-fifteen": "^1.0.2",

View File

@ -1,5 +1,5 @@
// debugger tsx
import Demo from '../../components/modal/demo/index.vue';
import Demo from '../../components/form/demo/validation.vue';
export default {
render() {