version:element-plus 1.0.1-beta.0
useCheckbox.ts
import {
ref,
computed,
inject,
getCurrentInstance,
watch,
} from 'vue'
import { toTypeString } from '@vue/shared'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { useGlobalConfig } from '@element-plus/utils/util'
import { PartialReturnType } from '@element-plus/utils/types'
import { elFormKey, elFormItemKey } from '@element-plus/form'
import { ICheckboxGroupInstance, ICheckboxProps } from './checkbox.type'
import type { ElFormContext, ElFormItemContext } from '@element-plus/form'
export const useCheckboxGroup = () => {
// 拿到初始化的element全局配置
const ELEMENT = useGlobalConfig()
// 拿到elForm中provide的数据
const elForm = inject(elFormKey, {} as ElFormContext)
// 拿到elFormItem中provide的数据
const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
// 拿到 checkboxGroup 的 provide
const checkboxGroup = inject<ICheckboxGroupInstance>('CheckboxGroup', {})
// 判断是不是group
const isGroup = computed(() => checkboxGroup && checkboxGroup?.name === 'ElCheckboxGroup')
const elFormItemSize = computed(() => {
return elFormItem.size
})
return {
isGroup,
checkboxGroup,
elForm,
ELEMENT,
elFormItemSize,
elFormItem,
}
}
const useModel = (props: ICheckboxProps) => {
let selfModel = false
const { emit } = getCurrentInstance()
const { isGroup, checkboxGroup } = useCheckboxGroup()
const isLimitExceeded = ref(false)
// 如果是checkboxGroup 那么如果checkboxGroup.modelValue.value存在就取,不然就取props传进来的modelValue
const store = computed(() => checkboxGroup ? checkboxGroup.modelValue?.value : props.modelValue)
const model = computed({
// ?? Null 判断运算符
// ?? 只有运算符左侧的值为null或undefined时,才会返回右侧的值
get() {
return isGroup.value
? store.value
: props.modelValue ?? selfModel
},
set(val: unknown) {
if (isGroup.value && Array.isArray(val)) {
isLimitExceeded.value = false
if (checkboxGroup.min !== undefined && val.length < checkboxGroup.min.value) {
isLimitExceeded.value = true
}
if (checkboxGroup.max !== undefined && val.length > checkboxGroup.max.value) {
isLimitExceeded.value = true
}
isLimitExceeded.value === false && checkboxGroup?.changeEvent?.(val)
} else {
emit(UPDATE_MODEL_EVENT, val)
selfModel = val as boolean
}
},
})
return {
model,
isLimitExceeded,
}
}
const useCheckboxStatus = (props: ICheckboxProps, { model }: PartialReturnType<typeof useModel>) => {
const { isGroup, checkboxGroup, elFormItemSize, ELEMENT } = useCheckboxGroup()
// 默认false
const focus = ref(false)
// 读取size 就近原则
const size = computed<string | undefined>(() => checkboxGroup?.checkboxGroupSize?.value || elFormItemSize.value || ELEMENT.size)
// 是否选中状态
const isChecked = computed(() => {
const value = model.value
if (toTypeString(value) === '[object Boolean]') {
// boolean 类型直接返回
return value
} else if (Array.isArray(value)) {
// 数组类型 返回 数组中是否存在props.label
return value.includes(props.label)
} else if (value !== null && value !== undefined) {
return value === props.trueLabel
}
})
const checkboxSize = computed(() => {
const temCheckboxSize = props.size || elFormItemSize.value || ELEMENT.size
return isGroup.value
? checkboxGroup?.checkboxGroupSize?.value || temCheckboxSize
: temCheckboxSize
})
return {
isChecked,
focus,
size,
checkboxSize,
}
}
const useDisabled = (
props: ICheckboxProps,
{ model, isChecked }: PartialReturnType<typeof useModel> & PartialReturnType<typeof useCheckboxStatus>,
) => {
const { elForm, isGroup, checkboxGroup } = useCheckboxGroup()
const isLimitDisabled = computed(() => {
const max = checkboxGroup.max?.value
const min = checkboxGroup.min?.value
return !!(max || min) && (model.value.length >= max && !isChecked.value) ||
(model.value.length <= min && isChecked.value)
})
const isDisabled = computed(() => {
const disabled = props.disabled || elForm.disabled
return isGroup.value
? checkboxGroup.disabled?.value || disabled || isLimitDisabled.value
: props.disabled || elForm.disabled
})
return {
isDisabled,
isLimitDisabled,
}
}
const setStoreValue = (props: ICheckboxProps, { model }: PartialReturnType<typeof useModel>) => {
function addToStore() {
if (
Array.isArray(model.value) &&
!model.value.includes(props.label)
) {
model.value.push(props.label)
} else {
model.value = props.trueLabel || true
}
}
props.checked && addToStore()
}
const useEvent = (props: ICheckboxProps, { isLimitExceeded }: PartialReturnType<typeof useModel>) => {
const { elFormItem } = useCheckboxGroup()
const { emit } = getCurrentInstance()
function handleChange(e: InputEvent) {
if (isLimitExceeded.value) return
const target = e.target as HTMLInputElement
const value = target.checked
? props.trueLabel ?? true
: props.falseLabel ?? false
emit('change', value, e)
}
watch(() => props.modelValue, val => {
elFormItem.formItemMitt?.emit('el.form.change', [val])
})
return {
handleChange,
}
}
export const useCheckbox = (props: ICheckboxProps) => {
const { model, isLimitExceeded } = useModel(props)
const { focus, size, isChecked, checkboxSize } = useCheckboxStatus(props, { model })
const { isDisabled } = useDisabled(props, { model, isChecked })
const { handleChange } = useEvent(props, { isLimitExceeded })
setStoreValue(props, { model })
return {
isChecked,
isDisabled,
checkboxSize,
model,
handleChange,
focus,
size,
}
}
checkbox
<template>
<label
:id="id"
class="el-checkbox"
:class="[
border && checkboxSize ? 'el-checkbox--' + checkboxSize : '',
{ 'is-disabled': isDisabled },
{ 'is-bordered': border },
{ 'is-checked': isChecked }
]"
>
<span
class="el-checkbox__input"
:class="{
'is-disabled': isDisabled,
'is-checked': isChecked,
'is-indeterminate': indeterminate,
'is-focus': focus
}"
:tabindex="indeterminate ? 0 : false"
:role="indeterminate ? 'checkbox' : false"
:aria-checked="indeterminate ? 'mixed' : false"
>
<span class="el-checkbox__inner"></span>
<input
v-if="trueLabel || falseLabel"
v-model="model"
:checked="isChecked"
class="el-checkbox__original"
type="checkbox"
:aria-hidden="indeterminate ? 'true' : 'false'"
:name="name"
:disabled="isDisabled"
:true-value="trueLabel"
:false-value="falseLabel"
@change="handleChange"
@focus="focus = true"
@blur="focus = false"
>
<input
v-else
v-model="model"
class="el-checkbox__original"
type="checkbox"
:aria-hidden="indeterminate ? 'true' : 'false'"
:disabled="isDisabled"
:value="label"
:name="name"
@change="handleChange"
@focus="focus = true"
@blur="focus = false"
>
</span>
<!-- 插槽 > 属性 -->
<span v-if="$slots.default || label" class="el-checkbox__label">
<slot></slot>
<!-- 没有默认插槽才会使用属性 -->
<template v-if="!$slots.default">{{ label }}</template>
</span>
</label>
</template>
<script lang='ts'>
import {
defineComponent,
getCurrentInstance,
onMounted,
PropType,
} from 'vue'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { isValidComponentSize } from '@element-plus/utils/validators'
import { useCheckbox } from './useCheckbox'
export default defineComponent({
name: 'ElCheckbox',
props: {
modelValue: {
type: [Boolean, Number, String],
default: () => undefined,
},
label: {
type: [Boolean, Number, String],
},
// 表示 checkbox 的不确定状态,一般用于实现全选的效果
// 也就是是否是部分选中的状态
indeterminate: Boolean,
disabled: Boolean,
checked: Boolean,
name: {
type: String,
default: undefined,
},
// 选中时的值
trueLabel: {
type: [String, Number],
default: undefined,
},
// 没有选中时的值
falseLabel: {
type: [String, Number],
default: undefined,
},
// 文档对id没有说明 id在HTML中也应该是唯一的
id: {
type: String,
default: undefined,
},
controls: {
type: String,
default: undefined,
},
border: Boolean,
size: {
type: String as PropType<ComponentSize>,
validator: isValidComponentSize,
},
},
emits: [UPDATE_MODEL_EVENT, 'change'],
setup(props) {
const { focus, isChecked, isDisabled, checkboxSize, model, handleChange } = useCheckbox(props)
const instance = getCurrentInstance()
onMounted(() => {
instance.vnode.el.setAttribute('aria-controls', props.controls)
})
return {
focus,
isChecked,
isDisabled,
checkboxSize,
model,
handleChange,
}
},
})
</script>
checkbox-group
<template>
<div class="el-checkbox-group" role="group" aria-label="checkbox-group">
<slot></slot>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, watch, provide, nextTick, toRefs } from 'vue'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { isValidComponentSize } from '@element-plus/utils/validators'
import { useCheckboxGroup } from './useCheckbox'
import type { PropType } from 'vue'
export default defineComponent({
name: 'ElCheckboxGroup',
props: {
modelValue: {
type: [Object, Boolean, Array],
default: () => undefined,
},
disabled: Boolean,
min: {
type: Number,
default: undefined,
},
max: {
type: Number,
default: undefined,
},
// type ComponentSize = "large" | "medium" | "small" | "mini"
size: {
type: String as PropType<ComponentSize>,
validator: isValidComponentSize,
},
// 按钮形式的 Checkbox 激活时的填充色和边框色
fill: {
type: String,
default: undefined,
},
// 按钮形式的 Checkbox 激活时的文本颜色
textColor: {
type: String,
default: undefined,
},
},
emits: [UPDATE_MODEL_EVENT, 'change'],
setup(props, ctx) {
const { elFormItem, elFormItemSize, ELEMENT } = useCheckboxGroup()
// 就近原则取size
const checkboxGroupSize = computed(() => props.size || elFormItemSize.value || ELEMENT.size)
// 改变就会修改v-model和触发change事件
const changeEvent = value => {
ctx.emit(UPDATE_MODEL_EVENT, value)
nextTick(() => {
ctx.emit('change', value)
})
}
const modelValue = computed({
get() {
return props.modelValue
},
set(val) {
changeEvent(val)
},
})
// 注入 CheckboxGroup 方便checkbox取
provide('CheckboxGroup', {
name: 'ElCheckboxGroup',
modelValue,
...toRefs(props),
checkboxGroupSize,
changeEvent,
})
// form中传递value修改 个人感觉应该是用来传给form,然后form做表单验证的
watch(() => props.modelValue, val => {
elFormItem.formItemMitt?.emit('el.form.change', [val])
})
},
})
</script>
checkbox-group
<template>
<label
class="el-checkbox-button"
:class="[
size ? 'el-checkbox-button--' + size : '',
{ 'is-disabled': isDisabled },
{ 'is-checked': isChecked },
{ 'is-focus': focus },
]"
role="checkbox"
:aria-checked="isChecked"
:aria-disabled="isDisabled"
>
<input
v-if="trueLabel || falseLabel"
v-model="model"
:checked="isChecked"
class="el-checkbox-button__original"
type="checkbox"
:name="name"
:disabled="isDisabled"
:true-value="trueLabel"
:false-value="falseLabel"
@change="handleChange"
@focus="focus = true"
@blur="focus = false"
>
<input
v-else
v-model="model"
class="el-checkbox-button__original"
type="checkbox"
:name="name"
:disabled="isDisabled"
:value="label"
@change="handleChange"
@focus="focus = true"
@blur="focus = false"
>
<span
v-if="$slots.default || label"
class="el-checkbox-button__inner"
:style="isChecked ? activeStyle : null"
>
<slot>{{ label }}</slot>
</span>
</label>
</template>
<script lang='ts'>
import {
defineComponent,
computed,
} from 'vue'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { useCheckbox, useCheckboxGroup } from './useCheckbox'
export default defineComponent({
name: 'ElCheckboxButton',
props: {
modelValue: {
type: [Boolean, Number, String],
default: () => undefined,
},
label: {
type: [Boolean, Number, String],
},
indeterminate: Boolean,
disabled: Boolean,
checked: Boolean,
name: {
type: String,
default: undefined,
},
// 选中时的值
trueLabel: {
type: [String, Number],
default: undefined,
},
// 没有选中时的值
falseLabel: {
type: [String, Number],
default: undefined,
},
},
emits: [UPDATE_MODEL_EVENT, 'change'],
setup(props) {
const { focus, isChecked, isDisabled, size, model, handleChange } = useCheckbox(props)
const { checkboxGroup } = useCheckboxGroup()
// button的激活style
const activeStyle = computed(() => {
return {
backgroundColor: checkboxGroup?.fill?.value ?? '',
borderColor: checkboxGroup?.fill?.value ?? '',
color: checkboxGroup?.textColor?.value ?? '',
boxShadow: '-1px 0 0 0 ' + checkboxGroup?.fill?.value ?? '',
}
})
return {
focus,
isChecked,
isDisabled,
model,
handleChange,
activeStyle,
size,
}
},
})
</script>