Skip to content

Commit

Permalink
fix(Radio, Checkbox): 修复在 Group 组件内无法触发 change 事件的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
1zumii committed Mar 13, 2024
1 parent c335504 commit 55a34a6
Show file tree
Hide file tree
Showing 25 changed files with 254 additions and 183 deletions.
4 changes: 4 additions & 0 deletions components/_util/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
App,
Plugin,
ExtractPropTypes,
InjectionKey,
} from 'vue';

export type Emit = SetupContext['emit'];
Expand All @@ -33,6 +34,9 @@ export type GetContainer = () => HTMLElement;

export type SFCWithInstall<T> = T & Plugin;

export type UnboxInjection<Key extends InjectionKey<unknown>> =
Key extends InjectionKey<infer I> ? I : never;

export type ExtractPublicPropTypes<T> = Omit<
Partial<RemoveReadonly<ExtractPropTypes<T>>>,
Extract<keyof T, `internal${string}`>
Expand Down
34 changes: 27 additions & 7 deletions components/_util/use/useSelect.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
import { inject, ref, computed } from 'vue';
import { inject, ref, computed, type InjectionKey } from 'vue';
import { CHANGE_EVENT } from '../constants';
import { useNormalModel } from './useModel';
import useFormAdaptor from './useFormAdaptor';

import type { VModelEvent, ChangeEvent } from '../interface';

export default function useSelect({
export type ParentGroupInjection<
Value,
ParentGroupProps extends {
disabled?: boolean;
},
> = {
name: string;
props: ParentGroupProps;
isSelect: (value: Value) => void;
onSelect: (value: Value, afterSelect: () => void) => void;
};

export default function useSelect<
Value,
ParentGroupProps extends {
disabled?: boolean;
},
>({
props,
emit,
parent,
}: {
props: any;
emit: {
(e: VModelEvent, value: any): void;
(e: ChangeEvent, value: any): void;
(e: VModelEvent, ...args: any[]): void;
(e: ChangeEvent, ...args: any[]): void;
};
parent: {
groupKey: symbol;
groupKey: InjectionKey<ParentGroupInjection<Value, ParentGroupProps>>;
name: string;
};
}) {
const { validate, isFormDisabled } = useFormAdaptor({
valueType: 'boolean',
});
const group = inject(parent.groupKey, null) as any;
const group = inject(parent.groupKey, null);
const focus = ref(false);
const hover = ref(false);
const isGroup = group !== null;
Expand All @@ -45,7 +62,10 @@ export default function useSelect({
return;
}
if (isGroup) {
group.onSelect(props.value);
group.onSelect(props.value, () => {
const newVal = group.isSelect(props.value);
emit(CHANGE_EVENT, newVal);
});
} else {
const newVal = !currentValue.value;
updateCurrentValue(newVal);
Expand Down
8 changes: 5 additions & 3 deletions components/checkbox-group/checkbox-group.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ import { computed, defineComponent } from 'vue';
import { useTheme } from '../_theme/useTheme';
import getPrefixCls from '../_util/getPrefixCls';
import Checkbox from '../checkbox';
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../_util/constants';
import { useCheckboxGroup } from './useCheckboxGroup';
import { name, checkboxGroupProps } from './const';
import { COMPONENT_NAME } from './const';
import { checkboxGroupProps } from './props';
const prefixCls = getPrefixCls('checkbox-group');
export default defineComponent({
name,
name: COMPONENT_NAME,
components: {
Checkbox,
},
props: checkboxGroupProps,
emits: ['update:modelValue', 'change'],
emits: [UPDATE_MODEL_EVENT, CHANGE_EVENT],
setup(props, { emit }) {
useTheme();
const { isFormDisabled } = useCheckboxGroup(props, emit);
Expand Down
39 changes: 8 additions & 31 deletions components/checkbox-group/const.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
import type { ComponentObjectPropsOptions, PropType } from 'vue';
import type { Option } from '../_util/interface';
import type { ExtractPublicPropTypes } from '../_util/interface';
import { type InjectionKey } from 'vue';
import { type ParentGroupInjection } from '../_util/use/useSelect';
import { type CheckboxValue } from '../checkbox/props';
import { type CheckboxGroupInnerProps } from './props';

export const checkboxGroupKey = Symbol('FCheckboxGroup');
export const name = 'FCheckboxGroup';
export const COMPONENT_NAME = 'FCheckboxGroup';

export type OptionValue = Option['value'];

export const checkboxGroupProps = {
modelValue: {
type: Array as PropType<OptionValue[]>,
default: () => [] as OptionValue[],
},
vertical: Boolean,
disabled: Boolean,
options: {
type: Array as PropType<Option[] | null>,
default: () => [] as Option[],
},
valueField: {
type: String,
default: 'value',
},
labelField: {
type: String,
default: 'label',
},
} as const satisfies ComponentObjectPropsOptions;

export type CheckboxGroupProps = ExtractPublicPropTypes<
typeof checkboxGroupProps
>;
export const checkboxGroupKey: InjectionKey<
ParentGroupInjection<CheckboxValue, CheckboxGroupInnerProps>
> = Symbol('FCheckboxGroup');
4 changes: 2 additions & 2 deletions components/checkbox-group/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import CheckboxGroup from './checkbox-group.vue';

import type { SFCWithInstall } from '../_util/interface';

export { checkboxGroupProps } from './const';
export type { CheckboxGroupProps } from './const';
export { checkboxGroupProps } from './props';
export type { CheckboxGroupProps } from './props';

type CheckboxGroupType = SFCWithInstall<typeof CheckboxGroup>;
export const FCheckboxGroup = withInstall<CheckboxGroupType>(
Expand Down
5 changes: 3 additions & 2 deletions components/checkbox-group/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type OptionValue } from './const';
import type { VModelEvent, ChangeEvent } from '../_util/interface';
import type { Option, VModelEvent, ChangeEvent } from '../_util/interface';

export type OptionValue = Option['value'];

export type CheckboxGroupEmits = {
(e: VModelEvent, value: OptionValue[]): void;
Expand Down
36 changes: 36 additions & 0 deletions components/checkbox-group/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ComponentObjectPropsOptions, PropType } from 'vue';
import type { OptionValue } from './interface';
import type {
ComponentInnerProps,
ExtractPublicPropTypes,
Option,
} from '../_util/interface';

export const checkboxGroupProps = {
modelValue: {
type: Array as PropType<OptionValue[]>,
default: () => [] as OptionValue[],
},
vertical: Boolean,
disabled: Boolean,
options: {
type: Array as PropType<Option[] | null>,
default: () => [] as Option[],
},
valueField: {
type: String,
default: 'value',
},
labelField: {
type: String,
default: 'label',
},
} as const satisfies ComponentObjectPropsOptions;

export type CheckboxGroupProps = ExtractPublicPropTypes<
typeof checkboxGroupProps
>;

export type CheckboxGroupInnerProps = ComponentInnerProps<
typeof checkboxGroupProps
>;
20 changes: 13 additions & 7 deletions components/checkbox-group/useCheckboxGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { provide, unref } from 'vue';
import { useArrayModel } from '../_util/use/useModel';
import useFormAdaptor from '../_util/use/useFormAdaptor';
import { CHANGE_EVENT } from '../_util/constants';
import { checkboxGroupKey, name } from './const';
import type { CheckboxGroupProps } from './const';

import { type UnboxInjection } from '../_util/interface';
import { checkboxGroupKey, COMPONENT_NAME } from './const';
import type { CheckboxGroupInnerProps } from './props';
import type { CheckboxGroupEmits } from './interface';

type GroupInjection = UnboxInjection<typeof checkboxGroupKey>;

export const useCheckboxGroup = (
props: CheckboxGroupProps,
props: CheckboxGroupInnerProps,
emit: CheckboxGroupEmits,
) => {
const { validate, isFormDisabled } = useFormAdaptor({
Expand All @@ -23,7 +25,7 @@ export const useCheckboxGroup = (
validate(CHANGE_EVENT);
};

const isSelect = (value: string | number | boolean) => {
const isSelect: GroupInjection['isSelect'] = (value) => {
const groupVal = unref(currentValue);
const itemVal = unref(value);
if (groupVal === null || itemVal === null) {
Expand All @@ -32,13 +34,17 @@ export const useCheckboxGroup = (
return groupVal.includes(itemVal);
};

const onSelect = (value: string | number | boolean) => {
const onSelect: GroupInjection['onSelect'] = (
value,
afterSelectHandler,
) => {
updateItem(unref(value));
handleChange();
afterSelectHandler();
};

provide(checkboxGroupKey, {
name,
name: COMPONENT_NAME,
isSelect,
onSelect,
props,
Expand Down
26 changes: 7 additions & 19 deletions components/checkbox/checkbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,19 @@
</template>

<script lang="ts">
import {
type PropType,
computed,
defineComponent,
type ComponentObjectPropsOptions,
} from 'vue';
import { computed, defineComponent } from 'vue';
import getPrefixCls from '../_util/getPrefixCls';
import useSelect from '../_util/use/useSelect';
import { name, checkboxGroupKey } from '../checkbox-group/const';
import {
COMPONENT_NAME as checkboxGroupName,
checkboxGroupKey,
} from '../checkbox-group/const';
import { useTheme } from '../_theme/useTheme';
import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '../_util/constants';
import type { ExtractPublicPropTypes } from '../_util/interface';
import { checkboxProps } from './props';
const prefixCls = getPrefixCls('checkbox');
export const checkboxProps = {
modelValue: Boolean,
indeterminate: Boolean,
value: [String, Number, Boolean] as PropType<string | number | boolean>,
label: [String, Number] as PropType<string | number>,
disabled: Boolean,
} as const satisfies ComponentObjectPropsOptions;
export type CheckboxProps = ExtractPublicPropTypes<typeof checkboxProps>;
export default defineComponent({
name: 'FCheckbox',
props: checkboxProps,
Expand All @@ -56,7 +44,7 @@ export default defineComponent({
} = useSelect({
props,
emit,
parent: { groupKey: checkboxGroupKey, name },
parent: { groupKey: checkboxGroupKey, name: checkboxGroupName },
});
const wrapperClass = computed(() => {
const arr = [`${prefixCls}`];
Expand Down
4 changes: 2 additions & 2 deletions components/checkbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import Checkbox from './checkbox.vue';

import type { SFCWithInstall } from '../_util/interface';

export { checkboxProps } from './checkbox.vue';
export type { CheckboxProps } from './checkbox.vue';
export { checkboxProps } from './props';
export type { CheckboxProps } from './props';

type CheckboxType = SFCWithInstall<typeof Checkbox>;
export const FCheckbox = withInstall<CheckboxType>(Checkbox as CheckboxType);
Expand Down
14 changes: 14 additions & 0 deletions components/checkbox/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type PropType, type ComponentObjectPropsOptions } from 'vue';
import type { ExtractPublicPropTypes } from '../_util/interface';

export type CheckboxValue = string | number | boolean;

export const checkboxProps = {
modelValue: Boolean,
indeterminate: Boolean,
value: [String, Number, Boolean] as PropType<CheckboxValue>,
label: [String, Number] as PropType<string | number>,
disabled: Boolean,
} as const satisfies ComponentObjectPropsOptions;

export type CheckboxProps = ExtractPublicPropTypes<typeof checkboxProps>;
4 changes: 2 additions & 2 deletions components/radio-button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type { SFCWithInstall } from '../_util/interface';

type RadioButtonType = SFCWithInstall<typeof RadioButton>;

export { radioButtonProps } from './radio-button';
export type { RadioButtonProps } from './radio-button';
export { radioButtonProps } from './props';
export type { RadioButtonProps } from './props';
export const FRadioButton = withInstall<RadioButtonType>(
RadioButton as RadioButtonType,
);
Expand Down
17 changes: 17 additions & 0 deletions components/radio-button/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type ComponentObjectPropsOptions, type PropType } from 'vue';
import { type RadioValue } from '../radio/props';
import type { ExtractPublicPropTypes } from '../_util/interface';

export const radioButtonProps = {
disabled: {
type: Boolean,
},
value: {
type: [String, Number, Boolean] as PropType<RadioValue>,
},
label: {
type: [String, Number] as PropType<string | number>,
},
} as const satisfies ComponentObjectPropsOptions;

export type RadioButtonProps = ExtractPublicPropTypes<typeof radioButtonProps>;
Loading

0 comments on commit 55a34a6

Please sign in to comment.