mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-27 02:01:15 +08:00
parent
77c9d91eea
commit
a73dacc92b
@ -12,6 +12,7 @@ import ElBreadcrumb from '@element-plus/breadcrumb'
|
||||
import ElIcon from '@element-plus/icon'
|
||||
import ElLink from '@element-plus/link'
|
||||
import ElRate from '@element-plus/rate'
|
||||
import ElSwitch from '@element-plus/switch'
|
||||
|
||||
export {
|
||||
ElAvatar,
|
||||
@ -27,6 +28,7 @@ export {
|
||||
ElIcon,
|
||||
ElLink,
|
||||
ElRate,
|
||||
ElSwitch,
|
||||
}
|
||||
|
||||
export default function install(app: App): void {
|
||||
@ -43,4 +45,5 @@ export default function install(app: App): void {
|
||||
ElIcon(app)
|
||||
ElLink(app)
|
||||
ElRate(app)
|
||||
ElSwitch(app)
|
||||
}
|
||||
|
@ -26,6 +26,9 @@
|
||||
"@element-plus/progress": "^0.0.0",
|
||||
"@element-plus/tag": "^0.0.0",
|
||||
"@element-plus/time-line": "^0.0.0",
|
||||
"@element-plus/rate": "^0.0.0"
|
||||
"@element-plus/rate": "^0.0.0",
|
||||
"@element-plus/breadcrumb": "^0.0.0",
|
||||
"@element-plus/icon": "^0.0.0",
|
||||
"@element-plus/switch": "^0.0.0"
|
||||
}
|
||||
}
|
||||
|
231
packages/switch/__tests__/switch.spec.ts
Normal file
231
packages/switch/__tests__/switch.spec.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Switch from '../src/index.vue'
|
||||
|
||||
describe('Switch.vue', () => {
|
||||
|
||||
test('create', () => {
|
||||
const wrapper = mount(Switch, {
|
||||
props: {
|
||||
activeText: 'on',
|
||||
inactiveText: 'off',
|
||||
activeColor: '#0f0',
|
||||
inactiveColor: '#f00',
|
||||
width: 100,
|
||||
},
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
const coreEl = vm.$el.querySelector('.el-switch__core')
|
||||
expect(coreEl.style.backgroundColor).toEqual('rgb(255, 0, 0)')
|
||||
expect(coreEl.style.width).toEqual('100px')
|
||||
const leftLabelWrapper = wrapper.find('.el-switch__label--left span')
|
||||
expect(leftLabelWrapper.text()).toEqual('off')
|
||||
})
|
||||
|
||||
test('switch with icons', () => {
|
||||
const wrapper = mount(Switch, {
|
||||
props: {
|
||||
activeIconClass: 'el-icon-check',
|
||||
inactiveIconClass: 'el-icon-close',
|
||||
},
|
||||
})
|
||||
|
||||
const iconWrapper = wrapper.find('.el-switch__label--left i')
|
||||
expect(iconWrapper.classes('el-icon-close')).toBe(true)
|
||||
})
|
||||
|
||||
test('value correctly update', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch
|
||||
v-model="value"
|
||||
activeColor="#0f0"
|
||||
inactiveColor="#f00">
|
||||
</el-switch>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
value: true,
|
||||
}
|
||||
},
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
const coreEl = vm.$el.querySelector('.el-switch__core')
|
||||
expect(coreEl.style.backgroundColor).toEqual('rgb(0, 255, 0)')
|
||||
const coreWrapper = wrapper.find('.el-switch__core')
|
||||
await coreWrapper.trigger('click')
|
||||
expect(coreEl.style.backgroundColor).toEqual('rgb(255, 0, 0)')
|
||||
expect(vm.value).toEqual(false)
|
||||
await coreWrapper.trigger('click')
|
||||
expect(vm.value).toEqual(true)
|
||||
})
|
||||
|
||||
test('change event', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch
|
||||
v-model="value"
|
||||
@update:modelValue="handleChange">
|
||||
</el-switch>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
handleChange(val) {
|
||||
this.target = val
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
target: 1,
|
||||
value: true,
|
||||
}
|
||||
},
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
|
||||
expect(vm.target).toEqual(1)
|
||||
const coreWrapper = wrapper.find('.el-switch__core')
|
||||
await coreWrapper.trigger('click')
|
||||
const switchWrapper = wrapper.findComponent(Switch)
|
||||
expect(switchWrapper.emitted()['update:modelValue']).toBeTruthy()
|
||||
expect(vm.target).toEqual(false)
|
||||
})
|
||||
|
||||
test('disabled switch should not respond to user click', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch disabled v-model="value"></el-switch>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
value: true,
|
||||
}
|
||||
},
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
|
||||
expect(vm.value).toEqual(true)
|
||||
const coreWrapper = wrapper.find('.el-switch__core')
|
||||
await coreWrapper.trigger('click')
|
||||
expect(vm.value).toEqual(true)
|
||||
})
|
||||
|
||||
test('expand switch value', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch v-model="value" :active-value="onValue" :inactive-value="offValue"></el-switch>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
value: '100',
|
||||
onValue: '100',
|
||||
offValue: '0',
|
||||
}
|
||||
},
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
|
||||
const coreWrapper = wrapper.find('.el-switch__core')
|
||||
await coreWrapper.trigger('click')
|
||||
expect(vm.value).toEqual('0')
|
||||
await coreWrapper.trigger('click')
|
||||
expect(vm.value).toEqual('100')
|
||||
})
|
||||
|
||||
test('value is the single source of truth', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch :value="true"></el-switch>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
const coreWrapper = wrapper.find('.el-switch__core')
|
||||
const switchWrapper = wrapper.findComponent(Switch)
|
||||
const switchVm = switchWrapper.vm
|
||||
const inputEl = vm.$el.querySelector('input')
|
||||
|
||||
expect(switchVm.checked).toBe(true)
|
||||
expect(switchWrapper.classes('is-checked')).toEqual(true)
|
||||
expect(inputEl.checked).toEqual(true)
|
||||
await coreWrapper.trigger('click')
|
||||
expect(switchVm.checked).toBe(true)
|
||||
expect(switchWrapper.classes('is-checked')).toEqual(true)
|
||||
expect(inputEl.checked).toEqual(true)
|
||||
})
|
||||
|
||||
test('model-value is the single source of truth', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch :model-value="true"></el-switch>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
const coreWrapper = wrapper.find('.el-switch__core')
|
||||
const switchWrapper = wrapper.findComponent(Switch)
|
||||
const switchVm = switchWrapper.vm
|
||||
const inputEl = vm.$el.querySelector('input')
|
||||
|
||||
expect(switchVm.checked).toBe(true)
|
||||
expect(switchWrapper.classes('is-checked')).toEqual(true)
|
||||
expect(inputEl.checked).toEqual(true)
|
||||
await coreWrapper.trigger('click')
|
||||
expect(switchVm.checked).toBe(true)
|
||||
expect(switchWrapper.classes('is-checked')).toEqual(true)
|
||||
expect(inputEl.checked).toEqual(true)
|
||||
})
|
||||
|
||||
test('sets checkbox value', async () => {
|
||||
const wrapper = mount({
|
||||
components: {
|
||||
'el-switch': Switch,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<el-switch v-model="value"></el-switch>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
value: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
const vm = wrapper.vm
|
||||
const inputEl = vm.$el.querySelector('input')
|
||||
|
||||
vm.value = true
|
||||
await vm.$nextTick()
|
||||
expect(inputEl.checked).toEqual(true)
|
||||
vm.value = false
|
||||
await vm.$nextTick()
|
||||
expect(inputEl.checked).toEqual(false)
|
||||
})
|
||||
})
|
28
packages/switch/doc/basic.vue
Normal file
28
packages/switch/doc/basic.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
use v-model
|
||||
<el-switch v-model="v" />
|
||||
</div>
|
||||
<div>
|
||||
use :value
|
||||
<el-switch :value="true" />
|
||||
</div>
|
||||
<div>
|
||||
use :model-value
|
||||
<el-switch :model-value="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
v: true,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
6
packages/switch/doc/index.stories.ts
Normal file
6
packages/switch/doc/index.stories.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export { default as BasicUsage } from './basic.vue'
|
||||
|
||||
export default {
|
||||
title: 'Switch',
|
||||
}
|
||||
|
5
packages/switch/index.ts
Normal file
5
packages/switch/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { App } from 'vue'
|
||||
import Switch from './src/index.vue'
|
||||
export default (app: App): void => {
|
||||
app.component(Switch.name, Switch)
|
||||
}
|
12
packages/switch/package.json
Normal file
12
packages/switch/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@element-plus/switch",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0-rc.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^2.0.0-beta.0"
|
||||
}
|
||||
}
|
224
packages/switch/src/index.vue
Normal file
224
packages/switch/src/index.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div
|
||||
class="el-switch"
|
||||
:class="{ 'is-disabled': switchDisabled, 'is-checked': checked }"
|
||||
role="switch"
|
||||
:aria-checked="checked"
|
||||
:aria-disabled="switchDisabled"
|
||||
@click.prevent="switchValue"
|
||||
>
|
||||
<input
|
||||
:id="id"
|
||||
ref="input"
|
||||
class="el-switch__input"
|
||||
type="checkbox"
|
||||
:name="name"
|
||||
:true-value="activeValue"
|
||||
:false-value="inactiveValue"
|
||||
:disabled="switchDisabled"
|
||||
@change="handleChange"
|
||||
@keydown.enter="switchValue"
|
||||
>
|
||||
<span
|
||||
v-if="inactiveIconClass || inactiveText"
|
||||
:class="['el-switch__label', 'el-switch__label--left', !checked ? 'is-active' : '']"
|
||||
>
|
||||
<i v-if="inactiveIconClass" :class="[inactiveIconClass]"></i>
|
||||
<span v-if="!inactiveIconClass && inactiveText" :aria-hidden="checked">{{ inactiveText }}</span>
|
||||
</span>
|
||||
<span ref="core" class="el-switch__core" :style="{ 'width': coreWidth + 'px' }">
|
||||
</span>
|
||||
<span
|
||||
v-if="activeIconClass || activeText"
|
||||
:class="['el-switch__label', 'el-switch__label--right', checked ? 'is-active' : '']"
|
||||
>
|
||||
<i v-if="activeIconClass" :class="[activeIconClass]"></i>
|
||||
<span v-if="!activeIconClass && activeText" :aria-hidden="!checked">{{ activeText }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
import { defineComponent, computed, onMounted, ref, inject, nextTick, watch } from 'vue'
|
||||
|
||||
// TODOS: replace these interface definition with actual ElForm interface
|
||||
interface ElForm {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
type ValueType = boolean | string | number;
|
||||
|
||||
interface ISwitchProps {
|
||||
modelValue: ValueType,
|
||||
value: ValueType,
|
||||
disabled: boolean,
|
||||
width: number,
|
||||
activeIconClass: string,
|
||||
inactiveIconClass: string,
|
||||
activeText: string,
|
||||
inactiveText: string,
|
||||
activeColor: string,
|
||||
inactiveColor: string,
|
||||
activeValue: ValueType,
|
||||
inactiveValue: ValueType,
|
||||
name: string,
|
||||
validateEvent: boolean,
|
||||
id: string,
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElSwitch',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [Boolean, String, Number],
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: [Boolean, String, Number],
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 40,
|
||||
},
|
||||
activeIconClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inactiveIconClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
activeText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inactiveText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
activeValue: {
|
||||
type: [Boolean, String, Number],
|
||||
default: true,
|
||||
},
|
||||
inactiveValue: {
|
||||
type: [Boolean, String, Number],
|
||||
default: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
validateEvent: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change', 'input'],
|
||||
setup(props: ISwitchProps, ctx) {
|
||||
const elForm = inject<ElForm>('elForm', {})
|
||||
const coreWidth = ref(props.width)
|
||||
const isModelValue = ref(props.modelValue !== false)
|
||||
const input = ref(null)
|
||||
const core = ref(null)
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
isModelValue.value = true
|
||||
})
|
||||
|
||||
watch(() => props.value, () => {
|
||||
isModelValue.value = false
|
||||
})
|
||||
|
||||
const actualValue = computed((): ValueType => {
|
||||
return isModelValue.value ? props.modelValue : props.value
|
||||
})
|
||||
|
||||
const checked = computed((): boolean => {
|
||||
return actualValue.value === props.activeValue
|
||||
})
|
||||
|
||||
if (!~[props.activeValue, props.inactiveValue].indexOf(actualValue.value)) {
|
||||
ctx.emit('update:modelValue', props.inactiveValue)
|
||||
ctx.emit('change', props.inactiveValue)
|
||||
ctx.emit('input', props.inactiveValue)
|
||||
}
|
||||
|
||||
watch(checked, () => {
|
||||
input.value.checked = checked.value
|
||||
|
||||
if (props.activeColor || props.inactiveColor) {
|
||||
setBackgroundColor()
|
||||
}
|
||||
|
||||
if (props.validateEvent) {
|
||||
// TODO: should dispatch event to parent component <el-form-item>;
|
||||
// dispatch('ElFormItem', 'el.form.change', [this.value]);
|
||||
}
|
||||
})
|
||||
|
||||
const switchDisabled = computed((): boolean => {
|
||||
return props.disabled || (elForm || {}).disabled
|
||||
})
|
||||
|
||||
const handleChange = (): void => {
|
||||
const val = checked.value ? props.inactiveValue : props.activeValue
|
||||
ctx.emit('update:modelValue', val)
|
||||
ctx.emit('change', val)
|
||||
ctx.emit('input', val)
|
||||
nextTick(() => {
|
||||
input.value.checked = checked.value
|
||||
})
|
||||
}
|
||||
|
||||
const switchValue = (): void => {
|
||||
!switchDisabled.value && handleChange()
|
||||
}
|
||||
|
||||
const setBackgroundColor = (): void => {
|
||||
const newColor = checked.value ? props.activeColor : props.inactiveColor
|
||||
const coreEl = core.value
|
||||
coreEl.style.borderColor = newColor
|
||||
coreEl.style.backgroundColor = newColor
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
coreWidth.value = coreWidth.value || 40
|
||||
|
||||
if (props.activeValue || props.inactiveValue) {
|
||||
setBackgroundColor()
|
||||
}
|
||||
|
||||
input.value.checked = checked.value
|
||||
})
|
||||
|
||||
return {
|
||||
input,
|
||||
core,
|
||||
coreWidth,
|
||||
switchDisabled,
|
||||
checked,
|
||||
handleChange,
|
||||
switchValue,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user