226 lines
6.1 KiB
TypeScript
226 lines
6.1 KiB
TypeScript
import type { PropType, VNode } from 'vue'
|
|
import type { CheckboxGroupContext } from './context'
|
|
import { computed, defineComponent, inject, ref, watch } from 'vue'
|
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
|
import Icon from '../icon/index'
|
|
import { checkboxGroupKey } from './context'
|
|
|
|
export default defineComponent({
|
|
name: 'Checkbox',
|
|
props: {
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: undefined,
|
|
},
|
|
checked: {
|
|
type: Boolean,
|
|
default: undefined,
|
|
},
|
|
value: {
|
|
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
|
default: true,
|
|
},
|
|
indeterminate: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
|
|
defaultChecked: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
type: {
|
|
type: String as PropType<'default' | 'card' | 'pureCard'>,
|
|
default: 'default',
|
|
},
|
|
extra: {
|
|
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
|
default: '',
|
|
},
|
|
class: {
|
|
type: [String, Array, Object],
|
|
default: '',
|
|
},
|
|
style: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
},
|
|
emits: ['update:modelValue', 'update:checked', 'change'],
|
|
setup(props, { slots, emit }) {
|
|
const prefixCls = getPrefixCls('checkbox')
|
|
|
|
const checkboxGroupCtx = inject<CheckboxGroupContext | undefined>(checkboxGroupKey, undefined)
|
|
const inGroup = computed(() => !!checkboxGroupCtx)
|
|
|
|
const isDisabled = computed(() => props.disabled || checkboxGroupCtx?.disabled)
|
|
const _checked = ref(props.defaultChecked)
|
|
|
|
const mergedType = computed(() =>
|
|
props.type || checkboxGroupCtx?.type || 'default',
|
|
)
|
|
|
|
const isChecked = computed(() => {
|
|
if (inGroup.value && checkboxGroupCtx) {
|
|
const groupValue = checkboxGroupCtx.value
|
|
if (Array.isArray(groupValue)) {
|
|
return groupValue.includes(props.value)
|
|
}
|
|
return groupValue === props.value
|
|
}
|
|
return props.checked ?? props.modelValue ?? _checked.value
|
|
})
|
|
|
|
const handleClick = (event: Event) => {
|
|
if (isDisabled.value) {
|
|
return
|
|
}
|
|
if (inGroup.value && checkboxGroupCtx) {
|
|
checkboxGroupCtx.handleChange(props.value, event)
|
|
}
|
|
else {
|
|
const newChecked = !isChecked.value
|
|
if (props.checked === undefined)
|
|
_checked.value = newChecked
|
|
|
|
emit('update:modelValue', newChecked)
|
|
emit('update:checked', newChecked)
|
|
}
|
|
emit('change', { value: props.value, event })
|
|
}
|
|
|
|
watch(() => props.checked, (val) => {
|
|
if (val !== undefined) {
|
|
_checked.value = val
|
|
}
|
|
})
|
|
|
|
const renderDefault = () => (
|
|
<label
|
|
class={[
|
|
`${prefixCls}-default`,
|
|
{ [`${prefixCls}-checked`]: isChecked.value },
|
|
{ [`${prefixCls}-disabled`]: isDisabled.value },
|
|
props.class,
|
|
]}
|
|
style={{ ...props.style }}
|
|
onClick={handleClick}
|
|
>
|
|
<span class={`${prefixCls}-main`}>
|
|
<input
|
|
class={`${prefixCls}-input`}
|
|
type="checkbox"
|
|
onClick={e => e.stopPropagation()}
|
|
/>
|
|
<span class={`${prefixCls}-inner`}>
|
|
{
|
|
props.indeterminate
|
|
? (
|
|
<Icon icon="garden:dash-fill-12" />
|
|
)
|
|
: isChecked.value
|
|
? (
|
|
<Icon icon="garden:check-sm-fill-12" />
|
|
)
|
|
: null
|
|
}
|
|
</span>
|
|
</span>
|
|
<div class={`${prefixCls}-content`}>
|
|
<div class={`${prefixCls}-label`}>
|
|
{slots.default?.()}
|
|
</div>
|
|
{props.extra && (
|
|
<div class={`${prefixCls}-extra`}>
|
|
{typeof props.extra === 'function' ? props.extra() : props.extra}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</label>
|
|
)
|
|
|
|
const renderCard = () => (
|
|
<label
|
|
class={[
|
|
`${prefixCls}-card`,
|
|
{ [`${prefixCls}-checked`]: isChecked.value },
|
|
{ [`${prefixCls}-disabled`]: isDisabled.value },
|
|
props.class,
|
|
]}
|
|
style={{ ...props.style }}
|
|
onClick={handleClick}
|
|
>
|
|
<span class={`${prefixCls}-main`}>
|
|
<input
|
|
class={`${prefixCls}-input`}
|
|
type="checkbox"
|
|
onClick={e => e.stopPropagation()}
|
|
/>
|
|
<span class={`${prefixCls}-inner`}>
|
|
{
|
|
props.indeterminate
|
|
? (
|
|
<Icon icon="garden:dash-fill-12" />
|
|
)
|
|
: isChecked.value
|
|
? (
|
|
<Icon icon="garden:check-sm-fill-12" />
|
|
)
|
|
: null
|
|
}
|
|
</span>
|
|
</span>
|
|
<div class={`${prefixCls}-content`}>
|
|
<div class={`${prefixCls}-label`}>
|
|
{slots.default?.()}
|
|
</div>
|
|
{props.extra && (
|
|
<div class={`${prefixCls}-extra`}>
|
|
{typeof props.extra === 'function' ? props.extra() : props.extra}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</label>
|
|
)
|
|
|
|
const renderPureCard = () => (
|
|
<label
|
|
class={[
|
|
`${prefixCls}-pureCard`,
|
|
{ [`${prefixCls}-checked`]: isChecked.value },
|
|
{ [`${prefixCls}-disabled`]: isDisabled.value },
|
|
props.class,
|
|
]}
|
|
style={{ ...props.style }}
|
|
onClick={handleClick}
|
|
>
|
|
<div class={`${prefixCls}-content`}>
|
|
<div class={`${prefixCls}-label`}>
|
|
{slots.default?.()}
|
|
</div>
|
|
{props.extra && (
|
|
<div class={`${prefixCls}-extra`}>
|
|
{typeof props.extra === 'function' ? props.extra() : props.extra}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</label>
|
|
)
|
|
|
|
return () => {
|
|
switch (mergedType.value) {
|
|
case 'card':
|
|
return renderCard()
|
|
case 'pureCard':
|
|
return renderPureCard()
|
|
default:
|
|
return renderDefault()
|
|
}
|
|
}
|
|
},
|
|
})
|