Files
element-plus/packages/components/switch/src/switch.vue

272 lines
6.7 KiB
Vue

<template>
<div :class="switchKls" :style="styles" @click.prevent="switchValue">
<input
:id="inputId"
ref="input"
:class="ns.e('input')"
type="checkbox"
role="switch"
:aria-checked="checked"
:aria-disabled="switchDisabled"
:name="name"
:true-value="activeValue"
:false-value="inactiveValue"
:disabled="switchDisabled"
:tabindex="tabindex"
@change="handleChange"
@keydown.enter="switchValue"
/>
<span
v-if="!inlinePrompt && (inactiveIcon || inactiveText)"
:class="labelLeftKls"
>
<el-icon v-if="inactiveIcon">
<component :is="inactiveIcon" />
</el-icon>
<span v-if="!inactiveIcon && inactiveText" :aria-hidden="checked">{{
inactiveText
}}</span>
</span>
<span ref="core" :class="ns.e('core')" :style="coreStyle">
<div v-if="inlinePrompt" :class="ns.e('inner')">
<template v-if="activeIcon || inactiveIcon">
<el-icon :class="ns.is('icon')">
<component :is="checked ? activeIcon : inactiveIcon" />
</el-icon>
</template>
<template v-else-if="activeText || inactiveText">
<span :class="ns.is('text')" :aria-hidden="!checked">
{{ checked ? activeText : inactiveText }}
</span>
</template>
</div>
<div :class="ns.e('action')">
<el-icon v-if="loading" :class="ns.is('loading')">
<loading />
</el-icon>
<el-icon v-else-if="activeActionIcon && checked">
<component :is="activeActionIcon" />
</el-icon>
<el-icon v-else-if="inactiveActionIcon && !checked">
<component :is="inactiveActionIcon" />
</el-icon>
</div>
</span>
<span
v-if="!inlinePrompt && (activeIcon || activeText)"
:class="labelRightKls"
>
<el-icon v-if="activeIcon">
<component :is="activeIcon" />
</el-icon>
<span v-if="!activeIcon && activeText" :aria-hidden="!checked">{{
activeText
}}</span>
</span>
</div>
</template>
<script lang="ts" setup>
import {
computed,
getCurrentInstance,
nextTick,
onMounted,
ref,
watch,
} from 'vue'
import { isPromise } from '@vue/shared'
import { addUnit, debugWarn, isBoolean, throwError } from '@element-plus/utils'
import ElIcon from '@element-plus/components/icon'
import {
useFormDisabled,
useFormItem,
useFormItemInputId,
useFormSize,
} from '@element-plus/components/form'
import { Loading } from '@element-plus/icons-vue'
import {
CHANGE_EVENT,
INPUT_EVENT,
UPDATE_MODEL_EVENT,
} from '@element-plus/constants'
import { useDeprecated, useNamespace } from '@element-plus/hooks'
import { switchEmits, switchProps } from './switch'
import type { CSSProperties } from 'vue'
const COMPONENT_NAME = 'ElSwitch'
defineOptions({
name: COMPONENT_NAME,
})
const props = defineProps(switchProps)
const emit = defineEmits(switchEmits)
const vm = getCurrentInstance()!
const { formItem } = useFormItem()
const switchSize = useFormSize()
const ns = useNamespace('switch')
const useBatchDeprecated = (list: string[][]) => {
list.forEach((param) => {
useDeprecated(
{
from: param[0],
replacement: param[1],
scope: COMPONENT_NAME,
version: '2.3.0',
ref: 'https://element-plus.org/en-US/component/switch.html#attributes',
type: 'Attribute',
},
computed(() => !!vm.vnode.props?.[param[2]])
)
})
}
useBatchDeprecated([
['"value"', '"model-value" or "v-model"', 'value'],
['"active-color"', 'CSS var `--el-switch-on-color`', 'activeColor'],
['"inactive-color"', 'CSS var `--el-switch-off-color`', 'inactiveColor'],
['"border-color"', 'CSS var `--el-switch-border-color`', 'borderColor'],
])
const { inputId } = useFormItemInputId(props, {
formItemContext: formItem,
})
const switchDisabled = useFormDisabled(computed(() => props.loading))
const isControlled = ref(props.modelValue !== false)
const input = ref<HTMLInputElement>()
const core = ref<HTMLSpanElement>()
const switchKls = computed(() => [
ns.b(),
ns.m(switchSize.value),
ns.is('disabled', switchDisabled.value),
ns.is('checked', checked.value),
])
const labelLeftKls = computed(() => [
ns.e('label'),
ns.em('label', 'left'),
ns.is('active', !checked.value),
])
const labelRightKls = computed(() => [
ns.e('label'),
ns.em('label', 'right'),
ns.is('active', checked.value),
])
const coreStyle = computed<CSSProperties>(() => ({
width: addUnit(props.width),
}))
watch(
() => props.modelValue,
() => {
isControlled.value = true
}
)
watch(
() => props.value,
() => {
isControlled.value = false
}
)
const actualValue = computed(() => {
return isControlled.value ? props.modelValue : props.value
})
const checked = computed(() => actualValue.value === props.activeValue)
if (![props.activeValue, props.inactiveValue].includes(actualValue.value)) {
emit(UPDATE_MODEL_EVENT, props.inactiveValue)
emit(CHANGE_EVENT, props.inactiveValue)
emit(INPUT_EVENT, props.inactiveValue)
}
watch(checked, (val) => {
input.value!.checked = val
if (props.validateEvent) {
formItem?.validate?.('change').catch((err) => debugWarn(err))
}
})
const handleChange = () => {
const val = checked.value ? props.inactiveValue : props.activeValue
emit(UPDATE_MODEL_EVENT, val)
emit(CHANGE_EVENT, val)
emit(INPUT_EVENT, val)
nextTick(() => {
input.value!.checked = checked.value
})
}
const switchValue = () => {
if (switchDisabled.value) return
const { beforeChange } = props
if (!beforeChange) {
handleChange()
return
}
const shouldChange = beforeChange()
const isPromiseOrBool = [
isPromise(shouldChange),
isBoolean(shouldChange),
].includes(true)
if (!isPromiseOrBool) {
throwError(
COMPONENT_NAME,
'beforeChange must return type `Promise<boolean>` or `boolean`'
)
}
if (isPromise(shouldChange)) {
shouldChange
.then((result) => {
if (result) {
handleChange()
}
})
.catch((e) => {
debugWarn(COMPONENT_NAME, `some error occurred: ${e}`)
})
} else if (shouldChange) {
handleChange()
}
}
const styles = computed(() => {
return ns.cssVarBlock({
...(props.activeColor ? { 'on-color': props.activeColor } : null),
...(props.inactiveColor ? { 'off-color': props.inactiveColor } : null),
...(props.borderColor ? { 'border-color': props.borderColor } : null),
})
})
const focus = (): void => {
input.value?.focus?.()
}
onMounted(() => {
input.value!.checked = checked.value
})
defineExpose({
/**
* @description manual focus to the switch component
**/
focus,
/**
* @description whether Switch is checked
*/
checked,
})
</script>