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
|
return element && element.__v_isVNode && typeof element.type !== 'symbol'; // remove text node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPropsSlot(slots, props, prop = 'default') {
|
||||||
|
return slots[prop]?.() ?? props[prop];
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
splitAttrs,
|
splitAttrs,
|
||||||
hasProp,
|
hasProp,
|
||||||
|
@ -411,5 +415,6 @@ export {
|
||||||
getAllChildren,
|
getAllChildren,
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
flattenChildren,
|
flattenChildren,
|
||||||
|
getPropsSlot,
|
||||||
};
|
};
|
||||||
export default hasProp;
|
export default hasProp;
|
||||||
|
|
|
@ -9,7 +9,11 @@ describe('Switch', () => {
|
||||||
mountTest(Switch);
|
mountTest(Switch);
|
||||||
|
|
||||||
it('should has click wave effect', async () => {
|
it('should has click wave effect', async () => {
|
||||||
const wrapper = mount(Switch);
|
const wrapper = mount({
|
||||||
|
render() {
|
||||||
|
return <Switch />;
|
||||||
|
},
|
||||||
|
});
|
||||||
wrapper.find('.ant-switch').trigger('click');
|
wrapper.find('.ant-switch').trigger('click');
|
||||||
await new Promise(resolve => setTimeout(resolve, 0));
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
@ -19,7 +23,11 @@ describe('Switch', () => {
|
||||||
resetWarned();
|
resetWarned();
|
||||||
|
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
mount(Switch, { props: { value: '' } });
|
mount({
|
||||||
|
render() {
|
||||||
|
return <Switch value="" />;
|
||||||
|
},
|
||||||
|
});
|
||||||
expect(errorSpy).toHaveBeenCalledWith(
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
'Warning: [antdv: Switch] `value` is not validate prop, do you mean `checked`?',
|
'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 LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import hasProp, { getOptionProps, getComponent } from '../_util/props-util';
|
|
||||||
import VcSwitch from '../vc-switch';
|
import VcSwitch from '../vc-switch';
|
||||||
import Wave from '../_util/wave';
|
import Wave from '../_util/wave';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import { defaultConfigProvider } from '../config-provider';
|
||||||
import warning from '../_util/warning';
|
import warning from '../_util/warning';
|
||||||
import { tuple, withInstall } from '../_util/type';
|
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({
|
const Switch = defineComponent({
|
||||||
name: 'ASwitch',
|
name: 'ASwitch',
|
||||||
__ANT_SWITCH: true,
|
__ANT_SWITCH: true,
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: {
|
props: switchProps,
|
||||||
prefixCls: PropTypes.string,
|
setup(props: SwitchProps, { attrs, slots, expose }) {
|
||||||
// size=default and size=large are the same
|
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||||
size: PropTypes.oneOf(tuple('small', 'default', 'large')),
|
const refSwitchNode = ref();
|
||||||
disabled: PropTypes.looseBool,
|
|
||||||
checkedChildren: PropTypes.any,
|
const focus = () => {
|
||||||
unCheckedChildren: PropTypes.any,
|
refSwitchNode.value?.focus();
|
||||||
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),
|
|
||||||
};
|
};
|
||||||
},
|
const blur = () => {
|
||||||
created() {
|
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(
|
warning(
|
||||||
hasProp(this, 'checked') || !('value' in this.$attrs),
|
!('value' in attrs),
|
||||||
'Switch',
|
'Switch',
|
||||||
'`value` is not validate prop, do you mean `checked`?',
|
'`value` is not validate prop, do you mean `checked`?',
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
methods: {
|
const { getPrefixCls } = configProvider;
|
||||||
focus() {
|
const prefixCls = computed(() => {
|
||||||
this.refSwitchNode?.focus();
|
return getPrefixCls('switch', props.prefixCls);
|
||||||
},
|
});
|
||||||
blur() {
|
return () => (
|
||||||
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 (
|
|
||||||
<Wave insertExtraNode>
|
<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>
|
</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
|
// base rc-switch 1.9.0
|
||||||
import Switch from './Switch';
|
import Switch from './src/Switch';
|
||||||
|
|
||||||
export default Switch;
|
export default Switch;
|
|
@ -1,4 +1,4 @@
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../../_util/vue-types';
|
||||||
|
|
||||||
export const switchPropTypes = {
|
export const switchPropTypes = {
|
||||||
prefixCls: PropTypes.string,
|
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