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),
|
||||
props: switchProps,
|
||||
setup(props: SwitchProps, { attrs, slots, expose }) {
|
||||
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||
const refSwitchNode = ref();
|
||||
|
||||
const focus = () => {
|
||||
refSwitchNode.value?.focus();
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const blur = () => {
|
||||
refSwitchNode.value?.blur();
|
||||
};
|
||||
|
||||
expose({ focus, blur });
|
||||
|
||||
onBeforeMount(() => {
|
||||
if ('defaultChecked' in attrs) {
|
||||
console.warn(
|
||||
`[antdv: Switch]: 'defaultChecked' will be obsolete, please use 'v-model:checked'`,
|
||||
);
|
||||
}
|
||||
warning(
|
||||
hasProp(this, 'checked') || !('value' in this.$attrs),
|
||||
!('value' in 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;
|
||||
},
|
||||
},
|
||||
|
||||
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 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,
|
||||
};
|
||||
return (
|
||||
});
|
||||
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