Files
element-plus/packages/components/switch/src/switch.vue
2022-01-19 15:04:01 +08:00

256 lines
6.6 KiB
Vue

<template>
<div
:class="switchKls"
role="switch"
:aria-checked="checked"
:aria-disabled="switchDisabled"
@click.prevent="switchValue"
>
<input
:id="id"
ref="input"
:class="ns.e('input')"
type="checkbox"
:name="name"
:true-value="activeValue"
:false-value="inactiveValue"
:disabled="switchDisabled"
@change="handleChange"
@keydown.enter="switchValue"
/>
<span
v-if="!inlinePrompt && (inactiveIcon || inactiveText)"
:class="[
ns.e('label'),
ns.em('label', 'left'),
ns.is('active', !checked),
]"
>
<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="{ width: (width || 40) + 'px' }"
>
<div v-if="inlinePrompt" :class="ns.e('inner')">
<template v-if="activeIcon || inactiveIcon">
<el-icon
v-if="activeIcon"
:class="[ns.is('icon'), checked ? ns.is('show') : ns.is('hide')]"
>
<component :is="activeIcon" />
</el-icon>
<el-icon
v-if="inactiveIcon"
:class="[ns.is('icon'), !checked ? ns.is('show') : ns.is('hide')]"
>
<component :is="inactiveIcon" />
</el-icon>
</template>
<template v-else-if="activeText || inactiveIcon">
<span
v-if="activeText"
:class="[ns.is('text'), checked ? ns.is('show') : ns.is('hide')]"
:aria-hidden="!checked"
>
{{ activeText.substr(0, 1) }}
</span>
<span
v-if="inactiveText"
:class="[ns.is('text'), !checked ? ns.is('show') : ns.is('hide')]"
:aria-hidden="checked"
>
{{ inactiveText.substr(0, 1) }}
</span>
</template>
</div>
<div :class="ns.e('action')">
<el-icon v-if="loading" :class="ns.is('loading')"><loading /></el-icon>
</div>
</span>
<span
v-if="!inlinePrompt && (activeIcon || activeText)"
:class="[
ns.e('label'),
ns.em('label', 'right'),
ns.is('active', checked),
]"
>
<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">
import { defineComponent, computed, onMounted, ref, nextTick, watch } from 'vue'
import { isPromise } from '@vue/shared'
import { isBool } from '@element-plus/utils/util'
import { throwError, debugWarn } from '@element-plus/utils/error'
import ElIcon from '@element-plus/components/icon'
import { Loading } from '@element-plus/icons-vue'
import {
UPDATE_MODEL_EVENT,
CHANGE_EVENT,
INPUT_EVENT,
} from '@element-plus/utils/constants'
import {
useDisabled,
useFormItem,
useNamespace,
useSize,
} from '@element-plus/hooks'
import { switchProps, switchEmits } from './switch'
const COMPONENT_NAME = 'ElSwitch'
export default defineComponent({
name: COMPONENT_NAME,
components: { ElIcon, Loading },
props: switchProps,
emits: switchEmits,
setup(props, { emit }) {
const { formItem } = useFormItem()
const switchDisabled = useDisabled(computed(() => props.loading))
const ns = useNamespace('switch')
const switchSize = useSize()
const isModelValue = 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),
])
watch(
() => props.modelValue,
() => {
isModelValue.value = true
}
)
watch(
() => props.value,
() => {
isModelValue.value = false
}
)
const actualValue = computed(() => {
return isModelValue.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, () => {
input.value!.checked = checked.value
if (props.activeColor || props.inactiveColor) {
setBackgroundColor()
}
if (props.validateEvent) {
formItem?.validate?.('change')
}
})
const handleChange = (): void => {
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 = (): void => {
if (switchDisabled.value) return
const { beforeChange } = props
if (!beforeChange) {
handleChange()
return
}
const shouldChange = beforeChange()
const isExpectType = [isPromise(shouldChange), isBool(shouldChange)].some(
(i) => i
)
if (!isExpectType) {
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 setBackgroundColor = (): void => {
const newColor = checked.value ? props.activeColor : props.inactiveColor
const coreEl = core.value
if (props.borderColor) coreEl!.style.borderColor = props.borderColor
else if (!props.borderColor) coreEl!.style.borderColor = newColor
coreEl!.style.backgroundColor = newColor
;(coreEl!.children[0] as HTMLDivElement).style.color = newColor
}
const focus = (): void => {
input.value?.focus?.()
}
onMounted(() => {
if (props.activeColor || props.inactiveColor || props.borderColor) {
setBackgroundColor()
}
input.value!.checked = checked.value
})
return {
ns,
input,
core,
switchDisabled,
checked,
switchKls,
handleChange,
switchValue,
focus,
}
},
})
</script>