diff --git a/packages/components/radio/__tests__/__snapshots__/radioGroup.spec.ts.snap b/packages/components/radio/__tests__/__snapshots__/radioGroup.spec.ts.snap index 2f45cfc44..0f46b72d3 100644 --- a/packages/components/radio/__tests__/__snapshots__/radioGroup.spec.ts.snap +++ b/packages/components/radio/__tests__/__snapshots__/radioGroup.spec.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RadioGroup render work 1`] = `""`; +exports[`RadioGroup render work 1`] = `"
"`; diff --git a/packages/components/radio/__tests__/radioGroup.spec.ts b/packages/components/radio/__tests__/radioGroup.spec.ts index 971e6e17b..27a27682e 100644 --- a/packages/components/radio/__tests__/radioGroup.spec.ts +++ b/packages/components/radio/__tests__/radioGroup.spec.ts @@ -8,7 +8,7 @@ import RadioGroup from '../src/RadioGroup' import { RadioGroupProps } from '../src/types' describe('RadioGroup', () => { - const defaultOptions = [ + const defaultDataSource = [ { label: 'Beijing', value: 'a' }, { label: 'Shanghai', value: 'b' }, { label: 'Guangzhou', value: 'c' }, @@ -16,10 +16,10 @@ describe('RadioGroup', () => { ] const RadioGroupMount = (groupOptions?: MountingOptions>) => { const { props, ...rest } = groupOptions || {} - return mount(RadioGroup, { props: { options: defaultOptions, ...props }, ...rest }) + return mount(RadioGroup, { props: { dataSource: defaultDataSource, ...props }, ...rest }) } - renderWork(Radio, { props: { label: 'Test', options: defaultOptions } }) + renderWork(RadioGroup, { props: { dataSource: defaultDataSource } }) test('value work', async () => { const wrapper = RadioGroupMount({ props: { value: 'a' } }) @@ -31,7 +31,7 @@ describe('RadioGroup', () => { expect(wrapper.find('.ix-radio-checked').text()).toBe('Shanghai') }) - test('onUpdate:checked work', async () => { + test('onUpdate:value work', async () => { const onUpdate = jest.fn() const wrapper = RadioGroupMount({ props: { 'onUpdate:value': onUpdate } }) @@ -96,22 +96,30 @@ describe('RadioGroup', () => { expect(wrapper.findAll('.ix-radio-default').length).toBe(4) }) - test('options work', async () => { - let options = [ + test('dataSource work', async () => { + let dataSource = [ { label: 'Beijing', value: 'a' }, { label: 'Shanghai', value: 'b' }, ] - const wrapper = RadioGroupMount({ props: { options } }) + const wrapper = RadioGroupMount({ props: { dataSource } }) expect(wrapper.findAll('.ix-radio').length).toBe(2) - options = [ + dataSource = [ { label: 'Beijing', value: 'a' }, { label: 'Shanghai', value: 'b' }, { label: 'Guangzhou', value: 'c' }, ] - await wrapper.setProps({ options }) + await wrapper.setProps({ dataSource }) + + expect(wrapper.findAll('.ix-radio').length).toBe(3) + }) + + test('default slot work', async () => { + const dataSource = undefined + const slots = [h(Radio, { label: 'A' }), h(Radio, { label: 'B' }), h(Radio, { label: 'C' })] + const wrapper = RadioGroupMount({ props: { dataSource }, slots: { default: () => slots } }) expect(wrapper.findAll('.ix-radio').length).toBe(3) }) @@ -129,12 +137,4 @@ describe('RadioGroup', () => { expect(wrapper.findAll('.ix-radio-md').length).toBe(4) }) - - test('default slot work', async () => { - const options = undefined - const slots = [h(Radio, { label: 'A' }), h(Radio, { label: 'B' }), h(Radio, { label: 'C' })] - const wrapper = RadioGroupMount({ props: { options }, slots: { default: () => slots } }) - - expect(wrapper.findAll('.ix-radio').length).toBe(3) - }) }) diff --git a/packages/components/radio/demo/Buttoned.vue b/packages/components/radio/demo/Buttoned.vue index 051db1ce5..2c7986a8f 100644 --- a/packages/components/radio/demo/Buttoned.vue +++ b/packages/components/radio/demo/Buttoned.vue @@ -1,23 +1,8 @@ @@ -25,4 +10,11 @@ import { ref } from 'vue' const value = ref('beijing') + +const dataSource = [ + { label: 'Beijing', value: 'beijing' }, + { label: 'Shanghai', value: 'shanghai' }, + { label: 'Guangzhou', value: 'guangzhou' }, + { label: 'Shenzhen', value: 'shenzhen' }, +] diff --git a/packages/components/radio/demo/Gap.vue b/packages/components/radio/demo/Gap.vue index 8c54d92b0..bff33afad 100644 --- a/packages/components/radio/demo/Gap.vue +++ b/packages/components/radio/demo/Gap.vue @@ -1,7 +1,7 @@ @@ -10,7 +10,7 @@ import { ref } from 'vue' const value = ref('beijing') -const options = [ +const dataSource = [ { label: 'Beijing', value: 'beijing' }, { label: 'Shanghai', value: 'shanghai' }, { label: 'Guangzhou', value: 'guangzhou' }, diff --git a/packages/components/radio/demo/Group.vue b/packages/components/radio/demo/Group.vue index 3b5c348f4..3741b7a5a 100644 --- a/packages/components/radio/demo/Group.vue +++ b/packages/components/radio/demo/Group.vue @@ -1,15 +1,20 @@ diff --git a/packages/components/radio/demo/Name.vue b/packages/components/radio/demo/Name.vue index 8dec18481..d8cf49330 100644 --- a/packages/components/radio/demo/Name.vue +++ b/packages/components/radio/demo/Name.vue @@ -1,8 +1,8 @@ @@ -13,7 +13,7 @@ const value = ref('beijing') const value2 = ref('beijing') const value3 = ref('beijing') -const options = [ +const dataSource = [ { label: 'Beijing', value: 'beijing' }, { label: 'Shanghai', value: 'shanghai' }, { label: 'Guangzhou', value: 'guangzhou' }, diff --git a/packages/components/radio/demo/Options.md b/packages/components/radio/demo/Options.md deleted file mode 100644 index 1131cd3ae..000000000 --- a/packages/components/radio/demo/Options.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -order: 5 -title: - zh: Options 配置 - en: Use Options ---- - -## zh - -通过设置 `options` 参数来渲染单选框。 - -## en - -Render radios by setting `options`. diff --git a/packages/components/radio/demo/Options.vue b/packages/components/radio/demo/Options.vue deleted file mode 100644 index dff3290f1..000000000 --- a/packages/components/radio/demo/Options.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/packages/components/radio/demo/Size.vue b/packages/components/radio/demo/Size.vue index 3c0792c70..3ba5af19f 100644 --- a/packages/components/radio/demo/Size.vue +++ b/packages/components/radio/demo/Size.vue @@ -1,8 +1,8 @@ @@ -11,7 +11,7 @@ import { ref } from 'vue' const value = ref('beijing') -const options = [ +const dataSource = [ { label: 'Beijing', value: 'beijing' }, { label: 'Shanghai', value: 'shanghai' }, { label: 'Guangzhou', value: 'guangzhou' }, diff --git a/packages/components/radio/docs/Index.zh.md b/packages/components/radio/docs/Index.zh.md index 3ae6f8459..dac010157 100644 --- a/packages/components/radio/docs/Index.zh.md +++ b/packages/components/radio/docs/Index.zh.md @@ -13,8 +13,8 @@ subtitle: 单选框 | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | | --- | --- | --- | --- | --- | --- | -| `v-model:checked` | 是否选中 | `boolean` | - | - | 使用 `control` 时,此配置无效 | | `control` | 控件控制器 | `string \| number \| AbstractControl` | - | - | 配合 `@idux/cdk/forms` 使用, 参考 [Form](/components/form/zh) | +| `v-model:checked` | 是否选中 | `boolean` | - | - | 使用 `control` 时,此配置无效 | | `autofocus` | 是否以自动聚焦 | `boolean` | `false` | - | - | | `buttoned` | 是否以按钮显示 | `boolean` | `false` | - | - | | `disabled` | 是否为禁用状态 | `boolean` | `false` | - | 使用 `control` 时,此配置无效 | @@ -22,7 +22,7 @@ subtitle: 单选框 | `mode` | 按钮类型 | `'default' \| 'primary'`| `'default'` | - | 仅 `buttoned` 为 `true` 时生效 | | `size` | 按钮大小 | `'sm' \| 'md' \| 'lg'`| `'md'` | ✅ | 仅 `buttoned` 为 `true` 时生效 | | `value` | 设置单选框的值,与 `IxRadioGroup` 配合使用 | `any`| - | - | - | -| `onChange` | 选中状态发生变化后的回调 | `(checked: boolean) => void`| - | - | - | +| `onChange` | 选中状态发生变化后的回调 | `(checked: boolean, oldChecked: boolean) => void`| - | - | - | | `onBlur` | 失去焦点后触发 | `(evt: FocusEvent) => void`| - | - | - | | `onFocus` | 获取焦点后触发 | `(evt: FocusEvent) => void`| - | - | - | @@ -39,16 +39,16 @@ subtitle: 单选框 | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | | --- | --- | --- | --- | --- | --- | +| `control` | 控件控制器 | `string \| number \| AbstractControl` | - | - | 配合 `@idux/cdk/forms` 使用, 参考 [Form](/components/form/zh) | | `v-model:value` | 当前选中的值 | `any` | - | - | 使用 `control` 时,此配置无效 | | `buttoned` | 设置单选框组内 `IxRadio` 的 `buttoned` | `boolean` | - | - | - | -| `control` | 控件控制器 | `string \| number \| AbstractControl` | - | - | 配合 `@idux/cdk/forms` 使用, 参考 [Form](/components/form/zh) | | `disabled` | 设置单选框组内 `IxRadio` 的 `disabled` | `boolean` | - | - | 使用 `control` 时,此配置无效 | | `gap` | 设置单选框组内的 `IxRadio` 的间隔 | `number \| string` | - | - | - | | `name` | 设置单选框组内的 `IxRadio` 的原生 `name` 属性 | `string` | - | - | - | | `mode` | 设置单选框组内 `IxRadio` 的 `mode` | `'default' \| 'primary'`| - | - | - | -| `options` | 以配置形式设置子元素 | `RadioOption[]`| - | 优先级高于 `default` 插槽 | | +| `dataSource` | 以配置形式设置子元素 | `RadioData[]`| - | 优先级高于 `default` 插槽 | | | `size` | 设置单选框组内 `IxRadio` 的 `size` | `'sm' \| 'md' \| 'lg'`| `'md'` | - | - | -| `onChange` | 选中值发生变化后的回调 | `(value: any) => void`| - | - | - | +| `onChange` | 选中值发生变化后的回调 | `(value: any, oldValue: any) => void`| - | - | - | ## 主题变量 diff --git a/packages/components/radio/index.ts b/packages/components/radio/index.ts index f545c6207..5a00e20ad 100644 --- a/packages/components/radio/index.ts +++ b/packages/components/radio/index.ts @@ -23,5 +23,5 @@ export type { RadioGroupComponent, RadioGroupPublicProps as RadioGroupProps, RadioMode, - RadioOption, + RadioData, } from './src/types' diff --git a/packages/components/radio/src/Radio.tsx b/packages/components/radio/src/Radio.tsx index 5dfc8f85d..d4e360fe0 100644 --- a/packages/components/radio/src/Radio.tsx +++ b/packages/components/radio/src/Radio.tsx @@ -28,7 +28,7 @@ export default defineComponent({ const mergedPrefixCls = computed(() => `${common.prefixCls}-radio`) const config = useGlobalConfig('radio') - const { elementRef, focus, blur } = useFormElement() + const { elementRef, focus, blur } = useFormElement() expose({ focus, blur }) const formContext = inject(FORM_TOKEN, null) @@ -125,11 +125,12 @@ const useRadio = ( if (elementRef.value) { const checked = (evt.target as HTMLInputElement).checked const value = props.value + const oldValue = accessor.valueRef.value accessor.setValue(value) // 为了保持受控模式下保持原生input状态和数据一致 elementRef.value.checked = false - callEmit(props.onChange, checked) - callEmit(groupProps.onChange, value) + callEmit(props.onChange, checked, !checked) + callEmit(groupProps.onChange, value, oldValue) } } } else { @@ -146,7 +147,7 @@ const useRadio = ( const checked = (evt.target as HTMLInputElement).checked accessor.setValue(checked) elementRef.value.checked = false - callEmit(props.onChange, checked) + callEmit(props.onChange, checked, !checked) } } } diff --git a/packages/components/radio/src/RadioGroup.tsx b/packages/components/radio/src/RadioGroup.tsx index 055ddf1f8..ee9da650c 100644 --- a/packages/components/radio/src/RadioGroup.tsx +++ b/packages/components/radio/src/RadioGroup.tsx @@ -5,11 +5,11 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { computed, defineComponent, provide } from 'vue' +import { computed, defineComponent, normalizeClass, provide } from 'vue' import { isNil } from 'lodash-es' -import { convertCssPixel } from '@idux/cdk/utils' +import { Logger, convertCssPixel } from '@idux/cdk/utils' import { useGlobalConfig } from '@idux/components/config' import { useFormAccessor } from '@idux/components/utils' @@ -31,20 +31,24 @@ export default defineComponent({ const classes = computed(() => { const { gap } = props const prefixCls = mergedPrefixCls.value - return { + return normalizeClass({ [prefixCls]: true, [`${prefixCls}-with-gap`]: !isNil(gap), - } + }) }) const style = computed(() => { const { gap } = props - return gap !== 0 ? `gap: ${convertCssPixel(gap)};` : undefined + return gap != null ? `gap: ${convertCssPixel(gap)};` : undefined }) return () => { - const { options } = props - const children = options ? options.map(option => ) : slots.default?.() + const { options, dataSource } = props + if (options) { + Logger.warn('components/radio', '`options` was deprecated, please use `dataSource` instead') + } + const data = options ?? dataSource + const children = data ? data.map(item => ) : slots.default?.() return (
{children} diff --git a/packages/components/radio/src/types.ts b/packages/components/radio/src/types.ts index 75f68591f..f9750d134 100644 --- a/packages/components/radio/src/types.ts +++ b/packages/components/radio/src/types.ts @@ -7,22 +7,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { IxInnerPropTypes, IxPublicPropTypes } from '@idux/cdk/utils' -import type { FormSize } from '@idux/components/form' -import type { DefineComponent, HTMLAttributes, LabelHTMLAttributes } from 'vue' +import { type DefineComponent, type HTMLAttributes, type LabelHTMLAttributes } from 'vue' import { controlPropDef } from '@idux/cdk/forms' -import { IxPropTypes } from '@idux/cdk/utils' - -export type RadioMode = 'default' | 'primary' -export type RadioOption = Omit +import { type IxInnerPropTypes, IxPropTypes, type IxPublicPropTypes, type VKey } from '@idux/cdk/utils' +import { type FormSize } from '@idux/components/form' export const radioProps = { + control: controlPropDef, checked: IxPropTypes.bool, autofocus: IxPropTypes.bool.def(false), buttoned: IxPropTypes.bool, - control: controlPropDef, + disabled: IxPropTypes.bool, label: IxPropTypes.string, mode: IxPropTypes.oneOf(['default', 'primary']), @@ -31,7 +28,7 @@ export const radioProps = { // events 'onUpdate:checked': IxPropTypes.emit<(checked: boolean) => void>(), - onChange: IxPropTypes.emit<(checked: boolean) => void>(), + onChange: IxPropTypes.emit<(checked: boolean, oldChecked: boolean) => void>(), onBlur: IxPropTypes.emit<(evt: FocusEvent) => void>(), onFocus: IxPropTypes.emit<(evt: FocusEvent) => void>(), } @@ -49,19 +46,21 @@ export type RadioComponent = DefineComponent< export type RadioInstance = InstanceType> export const radioGroupProps = { - value: IxPropTypes.any, control: controlPropDef, + value: IxPropTypes.any, + buttoned: IxPropTypes.bool.def(false), + dataSource: IxPropTypes.array(), disabled: IxPropTypes.bool.def(false), gap: IxPropTypes.oneOfType([Number, String]), name: IxPropTypes.string, mode: IxPropTypes.oneOf(['default', 'primary']), - options: IxPropTypes.array(), + options: IxPropTypes.array(), size: IxPropTypes.oneOf(['sm', 'md', 'lg']).def('md'), // events 'onUpdate:value': IxPropTypes.emit<(value: any) => void>(), - onChange: IxPropTypes.emit<(value: any) => void>(), + onChange: IxPropTypes.emit<(value: any, oldValue: any) => void>(), } export type RadioGroupProps = IxInnerPropTypes @@ -70,3 +69,8 @@ export type RadioGroupComponent = DefineComponent< Omit & RadioGroupPublicProps > export type RadioGroupInstance = InstanceType> + +export type RadioMode = 'default' | 'primary' +export interface RadioData extends RadioPublicProps { + key?: VKey +}