refactor(switch): use composition api (#3885)
* refactor(switch): use composition api * test: fix * fix: lint * fix: remove prefixCls * fix: use getPropsSlot * fix: use emitspull/3923/head
							parent
							
								
									4c370edf24
								
							
						
					
					
						commit
						67b9da71eb
					
				|  | @ -389,6 +389,10 @@ function isValidElement(element) { | |||
|   return element && element.__v_isVNode && typeof element.type !== 'symbol'; // remove text node
 | ||||
| } | ||||
| 
 | ||||
| function getPropsSlot(slots, props, prop = 'default') { | ||||
|   return slots[prop]?.() ?? props[prop]; | ||||
| } | ||||
| 
 | ||||
| export { | ||||
|   splitAttrs, | ||||
|   hasProp, | ||||
|  | @ -411,5 +415,6 @@ export { | |||
|   getAllChildren, | ||||
|   findDOMNode, | ||||
|   flattenChildren, | ||||
|   getPropsSlot, | ||||
| }; | ||||
| export default hasProp; | ||||
|  |  | |||
|  | @ -9,7 +9,11 @@ describe('Switch', () => { | |||
|   mountTest(Switch); | ||||
| 
 | ||||
|   it('should has click wave effect', async () => { | ||||
|     const wrapper = mount(Switch); | ||||
|     const wrapper = mount({ | ||||
|       render() { | ||||
|         return <Switch />; | ||||
|       }, | ||||
|     }); | ||||
|     wrapper.find('.ant-switch').trigger('click'); | ||||
|     await new Promise(resolve => setTimeout(resolve, 0)); | ||||
|     expect(wrapper.html()).toMatchSnapshot(); | ||||
|  | @ -19,7 +23,11 @@ describe('Switch', () => { | |||
|     resetWarned(); | ||||
| 
 | ||||
|     const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); | ||||
|     mount(Switch, { props: { value: '' } }); | ||||
|     mount({ | ||||
|       render() { | ||||
|         return <Switch value="" />; | ||||
|       }, | ||||
|     }); | ||||
|     expect(errorSpy).toHaveBeenCalledWith( | ||||
|       'Warning: [antdv: Switch] `value` is not validate prop, do you mean `checked`?', | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,86 +1,85 @@ | |||
| import { defineComponent, inject } from 'vue'; | ||||
| import { defineComponent, inject, onBeforeMount, ref, ExtractPropTypes, computed } from 'vue'; | ||||
| import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import hasProp, { getOptionProps, getComponent } from '../_util/props-util'; | ||||
| import VcSwitch from '../vc-switch'; | ||||
| import Wave from '../_util/wave'; | ||||
| import { defaultConfigProvider } from '../config-provider'; | ||||
| import warning from '../_util/warning'; | ||||
| import { tuple, withInstall } from '../_util/type'; | ||||
| import { getPropsSlot } from '../_util/props-util'; | ||||
| import Omit from 'omit.js'; | ||||
| 
 | ||||
| export const SwitchSizes = tuple('small', 'default', 'large'); | ||||
| 
 | ||||
| const switchProps = { | ||||
|   prefixCls: PropTypes.string, | ||||
|   size: PropTypes.oneOf(SwitchSizes), | ||||
|   disabled: PropTypes.looseBool, | ||||
|   checkedChildren: PropTypes.any, | ||||
|   unCheckedChildren: PropTypes.any, | ||||
|   tabindex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||||
|   // defaultChecked: PropTypes.looseBool, | ||||
|   autofocus: PropTypes.looseBool, | ||||
|   loading: PropTypes.looseBool, | ||||
|   checked: PropTypes.looseBool, | ||||
| }; | ||||
| 
 | ||||
| export type SwitchProps = Partial<ExtractPropTypes<typeof switchProps>>; | ||||
| 
 | ||||
| const Switch = defineComponent({ | ||||
|   name: 'ASwitch', | ||||
|   __ANT_SWITCH: true, | ||||
|   inheritAttrs: false, | ||||
|   props: { | ||||
|     prefixCls: PropTypes.string, | ||||
|     // size=default and size=large are the same | ||||
|     size: PropTypes.oneOf(tuple('small', 'default', 'large')), | ||||
|     disabled: PropTypes.looseBool, | ||||
|     checkedChildren: PropTypes.any, | ||||
|     unCheckedChildren: PropTypes.any, | ||||
|     tabindex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||||
|     checked: PropTypes.looseBool, | ||||
|     defaultChecked: PropTypes.looseBool, | ||||
|     autofocus: PropTypes.looseBool, | ||||
|     loading: PropTypes.looseBool, | ||||
|     onChange: PropTypes.func, | ||||
|     onClick: PropTypes.func, | ||||
|     'onUpdate:checked': PropTypes.func, | ||||
|   }, | ||||
|   // emits: ['change', 'click', 'update:checked'], | ||||
|   setup() { | ||||
|     return { | ||||
|       refSwitchNode: undefined, | ||||
|       configProvider: inject('configProvider', defaultConfigProvider), | ||||
|     }; | ||||
|   }, | ||||
|   created() { | ||||
|     warning( | ||||
|       hasProp(this, 'checked') || !('value' in this.$attrs), | ||||
|       'Switch', | ||||
|       '`value` is not validate prop, do you mean `checked`?', | ||||
|     ); | ||||
|   }, | ||||
|   methods: { | ||||
|     focus() { | ||||
|       this.refSwitchNode?.focus(); | ||||
|     }, | ||||
|     blur() { | ||||
|       this.refSwitchNode?.blur(); | ||||
|     }, | ||||
|     saveRef(c) { | ||||
|       this.refSwitchNode = c; | ||||
|     }, | ||||
|   }, | ||||
|   props: switchProps, | ||||
|   setup(props: SwitchProps, { attrs, slots, expose }) { | ||||
|     const configProvider = inject('configProvider', defaultConfigProvider); | ||||
|     const refSwitchNode = ref(); | ||||
| 
 | ||||
|   render() { | ||||
|     const { prefixCls: customizePrefixCls, size, loading, disabled, ...restProps } = getOptionProps( | ||||
|       this, | ||||
|     ); | ||||
|     const { getPrefixCls } = this.configProvider; | ||||
|     const prefixCls = getPrefixCls('switch', customizePrefixCls); | ||||
|     const { $attrs } = this; | ||||
|     const classes = { | ||||
|       [$attrs.class as string]: $attrs.class, | ||||
|       [`${prefixCls}-small`]: size === 'small', | ||||
|       [`${prefixCls}-loading`]: loading, | ||||
|     const focus = () => { | ||||
|       refSwitchNode.value?.focus(); | ||||
|     }; | ||||
|     const loadingIcon = loading ? <LoadingOutlined class={`${prefixCls}-loading-icon`} /> : null; | ||||
|     const switchProps = { | ||||
|       ...restProps, | ||||
|       ...$attrs, | ||||
|       prefixCls, | ||||
|       loadingIcon, | ||||
|       checkedChildren: getComponent(this, 'checkedChildren'), | ||||
|       unCheckedChildren: getComponent(this, 'unCheckedChildren'), | ||||
|       disabled: disabled || loading, | ||||
|       class: classes, | ||||
|       ref: this.saveRef, | ||||
|     const blur = () => { | ||||
|       refSwitchNode.value?.blur(); | ||||
|     }; | ||||
|     return ( | ||||
| 
 | ||||
|     expose({ focus, blur }); | ||||
| 
 | ||||
|     onBeforeMount(() => { | ||||
|       if ('defaultChecked' in attrs) { | ||||
|         console.warn( | ||||
|           `[antdv: Switch]: 'defaultChecked' will be obsolete, please use 'v-model:checked'`, | ||||
|         ); | ||||
|       } | ||||
|       warning( | ||||
|         !('value' in attrs), | ||||
|         'Switch', | ||||
|         '`value` is not validate prop, do you mean `checked`?', | ||||
|       ); | ||||
|     }); | ||||
|     const { getPrefixCls } = configProvider; | ||||
|     const prefixCls = computed(() => { | ||||
|       return getPrefixCls('switch', props.prefixCls); | ||||
|     }); | ||||
|     return () => ( | ||||
|       <Wave insertExtraNode> | ||||
|         <VcSwitch {...switchProps} /> | ||||
|         <VcSwitch | ||||
|           {...Omit(props, ['prefixCls', 'size', 'loading', 'disabled'])} | ||||
|           {...attrs} | ||||
|           checked={props.checked} | ||||
|           prefixCls={prefixCls.value} | ||||
|           loadingIcon={ | ||||
|             props.loading ? <LoadingOutlined class={`${prefixCls.value}-loading-icon`} /> : null | ||||
|           } | ||||
|           checkedChildren={getPropsSlot(slots, props, 'checkedChildren')} | ||||
|           unCheckedChildren={getPropsSlot(slots, props, 'unCheckedChildren')} | ||||
|           disabled={props.disabled || props.loading} | ||||
|           class={{ | ||||
|             [attrs.class as string]: attrs.class, | ||||
|             [`${prefixCls.value}-small`]: props.size === 'small', | ||||
|             [`${prefixCls.value}-loading`]: props.loading, | ||||
|           }} | ||||
|           ref={refSwitchNode} | ||||
|         /> | ||||
|       </Wave> | ||||
|     ); | ||||
|   }, | ||||
|  |  | |||
|  | @ -1,124 +0,0 @@ | |||
| import { switchPropTypes } from './PropTypes'; | ||||
| import BaseMixin from '../_util/BaseMixin'; | ||||
| import { hasProp, getOptionProps, getComponent } from '../_util/props-util'; | ||||
| import Omit from 'omit.js'; | ||||
| import { defineComponent } from 'vue'; | ||||
| 
 | ||||
| // function noop () { | ||||
| // } | ||||
| export default defineComponent({ | ||||
|   name: 'VcSwitch', | ||||
|   mixins: [BaseMixin], | ||||
|   inheritAttrs: false, | ||||
|   props: { | ||||
|     ...switchPropTypes, | ||||
|     prefixCls: switchPropTypes.prefixCls.def('rc-switch'), | ||||
|     // onChange: switchPropTypes.onChange.def(noop), | ||||
|     // onClick: switchPropTypes.onClick.def(noop), | ||||
|   }, | ||||
|   data() { | ||||
|     let checked = false; | ||||
|     if (hasProp(this, 'checked')) { | ||||
|       checked = !!this.checked; | ||||
|     } else { | ||||
|       checked = !!this.defaultChecked; | ||||
|     } | ||||
|     return { | ||||
|       stateChecked: checked, | ||||
|     }; | ||||
|   }, | ||||
|   watch: { | ||||
|     checked(val) { | ||||
|       this.stateChecked = val; | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.$nextTick(() => { | ||||
|       const { autofocus, disabled } = this; | ||||
|       if (autofocus && !disabled) { | ||||
|         this.focus(); | ||||
|       } | ||||
|     }); | ||||
|   }, | ||||
|   methods: { | ||||
|     saveRef(c) { | ||||
|       this.refSwitchNode = c; | ||||
|     }, | ||||
|     setChecked(checked, e) { | ||||
|       if (this.disabled) { | ||||
|         return; | ||||
|       } | ||||
|       if (!hasProp(this, 'checked')) { | ||||
|         this.stateChecked = checked; | ||||
|       } | ||||
|       this.__emit('update:checked', checked); | ||||
|       this.__emit('change', checked, e); | ||||
|     }, | ||||
|     handleClick(e) { | ||||
|       const checked = !this.stateChecked; | ||||
|       this.setChecked(checked, e); | ||||
|       this.__emit('click', checked, e); | ||||
|     }, | ||||
|     handleKeyDown(e) { | ||||
|       if (e.keyCode === 37) { | ||||
|         // Left | ||||
|         this.setChecked(false, e); | ||||
|       } else if (e.keyCode === 39) { | ||||
|         // Right | ||||
|         this.setChecked(true, e); | ||||
|       } | ||||
|     }, | ||||
|     handleMouseUp(e) { | ||||
|       this.refSwitchNode?.blur(); | ||||
| 
 | ||||
|       this.__emit('mouseup', e); | ||||
|     }, | ||||
|     focus() { | ||||
|       this.refSwitchNode?.focus(); | ||||
|     }, | ||||
|     blur() { | ||||
|       this.refSwitchNode?.blur(); | ||||
|     }, | ||||
|   }, | ||||
|   render() { | ||||
|     const { prefixCls, disabled, loadingIcon, ...restProps } = getOptionProps(this); | ||||
|     const checked = this.stateChecked; | ||||
|     const { $attrs } = this; | ||||
|     const switchClassName = { | ||||
|       [$attrs.class]: $attrs.class, | ||||
|       [prefixCls]: true, | ||||
|       [`${prefixCls}-checked`]: checked, | ||||
|       [`${prefixCls}-disabled`]: disabled, | ||||
|     }; | ||||
|     const spanProps = { | ||||
|       ...Omit(restProps, [ | ||||
|         'checkedChildren', | ||||
|         'unCheckedChildren', | ||||
|         'checked', | ||||
|         'autofocus', | ||||
|         'defaultChecked', | ||||
|       ]), | ||||
|       ...$attrs, | ||||
|       onKeydown: this.handleKeyDown, | ||||
|       onClick: this.handleClick, | ||||
|       onMouseup: this.handleMouseUp, | ||||
|       type: 'button', | ||||
|       role: 'switch', | ||||
|       'aria-checked': checked, | ||||
|       disabled, | ||||
|       class: switchClassName, | ||||
|       ref: this.saveRef, | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|       <button {...spanProps}> | ||||
|         {loadingIcon} | ||||
|         <span class={`${prefixCls}-inner`}> | ||||
|           {checked | ||||
|             ? getComponent(this, 'checkedChildren') | ||||
|             : getComponent(this, 'unCheckedChildren')} | ||||
|         </span> | ||||
|       </button> | ||||
|     ); | ||||
|   }, | ||||
| }); | ||||
|  | @ -1,4 +1,4 @@ | |||
| // base rc-switch 1.9.0
 | ||||
| import Switch from './Switch'; | ||||
| import Switch from './src/Switch'; | ||||
| 
 | ||||
| export default Switch; | ||||
|  | @ -1,4 +1,4 @@ | |||
| import PropTypes from '../_util/vue-types'; | ||||
| import PropTypes from '../../_util/vue-types'; | ||||
| 
 | ||||
| export const switchPropTypes = { | ||||
|   prefixCls: PropTypes.string, | ||||
|  | @ -0,0 +1,99 @@ | |||
| import { switchPropTypes } from './PropTypes'; | ||||
| import Omit from 'omit.js'; | ||||
| import { defineComponent, nextTick, onMounted, ref } from 'vue'; | ||||
| import KeyCode from '../../_util/KeyCode'; | ||||
| import { getPropsSlot } from '../../_util/props-util'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'VcSwitch', | ||||
|   inheritAttrs: false, | ||||
|   props: { | ||||
|     ...switchPropTypes, | ||||
|     prefixCls: switchPropTypes.prefixCls.def('rc-switch'), | ||||
|   }, | ||||
|   emits: ['update:checked', 'mouseup', 'change', 'click'], | ||||
|   setup(props, { attrs, slots, emit, expose }) { | ||||
|     const checked = ref('checked' in props ? !!props.checked : !!props.defaultChecked); | ||||
| 
 | ||||
|     const refSwitchNode = ref(); | ||||
| 
 | ||||
|     onMounted(() => { | ||||
|       nextTick(() => { | ||||
|         if (props.autofocus && !props.disabled) { | ||||
|           refSwitchNode.value.focus(); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     const setChecked = (check: boolean, e: MouseEvent | KeyboardEvent) => { | ||||
|       if (props.disabled) { | ||||
|         return; | ||||
|       } | ||||
|       checked.value = !checked.value; | ||||
|       emit('update:checked', checked); | ||||
|       emit('change', check, e); | ||||
|     }; | ||||
| 
 | ||||
|     const handleClick = (e: MouseEvent) => { | ||||
|       setChecked(checked.value, e); | ||||
|       emit('click', checked.value, e); | ||||
|     }; | ||||
| 
 | ||||
|     const handleKeyDown = (e: KeyboardEvent) => { | ||||
|       if (e.keyCode === KeyCode.LEFT) { | ||||
|         setChecked(false, e); | ||||
|       } else if (e.keyCode === KeyCode.RIGHT) { | ||||
|         setChecked(true, e); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     const handleMouseUp = (e: MouseEvent) => { | ||||
|       refSwitchNode.value?.blur(); | ||||
|       emit('mouseup', e); | ||||
|     }; | ||||
| 
 | ||||
|     const focus = () => { | ||||
|       refSwitchNode.value?.focus(); | ||||
|     }; | ||||
|     const blur = () => { | ||||
|       refSwitchNode.value?.blur(); | ||||
|     }; | ||||
| 
 | ||||
|     expose({ focus, blur }); | ||||
| 
 | ||||
|     return () => ( | ||||
|       <button | ||||
|         {...Omit(props, [ | ||||
|           'prefixCls', | ||||
|           'checkedChildren', | ||||
|           'unCheckedChildren', | ||||
|           'checked', | ||||
|           'autofocus', | ||||
|           'defaultChecked', | ||||
|         ])} | ||||
|         {...attrs} | ||||
|         onKeydown={handleKeyDown} | ||||
|         onClick={handleClick} | ||||
|         onMouseup={handleMouseUp} | ||||
|         type="button" | ||||
|         role="switch" | ||||
|         aria-checked={checked.value} | ||||
|         disabled={props.disabled} | ||||
|         class={{ | ||||
|           [attrs.class as string]: attrs.class, | ||||
|           [props.prefixCls]: true, | ||||
|           [`${props.prefixCls}-checked`]: checked.value, | ||||
|           [`${props.prefixCls}-disabled`]: props.disabled, | ||||
|         }} | ||||
|         ref={refSwitchNode} | ||||
|       > | ||||
|         {props.loadingIcon} | ||||
|         <span class={`${props.prefixCls}-inner`}> | ||||
|           {checked.value | ||||
|             ? getPropsSlot(slots, props, 'checkedChildren') | ||||
|             : getPropsSlot(slots, props, 'unCheckedChildren')} | ||||
|         </span> | ||||
|       </button> | ||||
|     ); | ||||
|   }, | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	 ajuner
						ajuner