mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-27 02:01:15 +08:00
refactor(components): [checkbox] refactor (#9594)
Co-authored-by: 三咲智子 <sxzz@sxzz.moe>
This commit is contained in:
parent
cd1e87acf0
commit
49109158c8
@ -13,5 +13,5 @@ export default ElCheckbox
|
||||
export const ElCheckboxButton = withNoopInstall(CheckboxButton)
|
||||
export const ElCheckboxGroup = withNoopInstall(CheckboxGroup)
|
||||
|
||||
export * from './src/checkbox-group'
|
||||
export * from './src/checkbox'
|
||||
export * from './src/checkbox.type'
|
||||
|
@ -2,10 +2,10 @@
|
||||
<label
|
||||
:class="[
|
||||
ns.b('button'),
|
||||
ns.bm('button', size),
|
||||
ns.bm('button', checkboxButtonSize),
|
||||
ns.is('disabled', isDisabled),
|
||||
ns.is('checked', isChecked),
|
||||
ns.is('focus', focus),
|
||||
ns.is('focus', isFocused),
|
||||
]"
|
||||
>
|
||||
<input
|
||||
@ -19,8 +19,8 @@
|
||||
:true-value="trueLabel"
|
||||
:false-value="falseLabel"
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
@ -32,8 +32,8 @@
|
||||
:disabled="isDisabled"
|
||||
:value="label"
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
|
||||
<span
|
||||
@ -47,14 +47,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useSlots } from 'vue'
|
||||
import { computed, inject, useSlots } from 'vue'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import {
|
||||
checkboxEmits,
|
||||
checkboxProps,
|
||||
useCheckbox,
|
||||
useCheckboxGroup,
|
||||
} from './checkbox'
|
||||
import { checkboxGroupContextKey } from '@element-plus/tokens/checkbox'
|
||||
import { useCheckbox } from './composables'
|
||||
import { checkboxEmits, checkboxProps } from './checkbox'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
@ -65,11 +62,15 @@ const props = defineProps(checkboxProps)
|
||||
defineEmits(checkboxEmits)
|
||||
const slots = useSlots()
|
||||
|
||||
const { focus, isChecked, isDisabled, size, model, handleChange } = useCheckbox(
|
||||
props,
|
||||
slots
|
||||
)
|
||||
const { checkboxGroup } = useCheckboxGroup()
|
||||
const {
|
||||
isFocused,
|
||||
isChecked,
|
||||
isDisabled,
|
||||
checkboxButtonSize,
|
||||
model,
|
||||
handleChange,
|
||||
} = useCheckbox(props, slots)
|
||||
const checkboxGroup = inject(checkboxGroupContextKey)
|
||||
const ns = useNamespace('checkbox')
|
||||
|
||||
const activeStyle = computed<CSSProperties>(() => {
|
||||
|
38
packages/components/checkbox/src/checkbox-group.ts
Normal file
38
packages/components/checkbox/src/checkbox-group.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import { useSizeProp } from '@element-plus/hooks'
|
||||
import { buildProps, definePropType, isArray } from '@element-plus/utils'
|
||||
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type checkboxGroup from './checkbox-group.vue'
|
||||
import type { CheckboxValueType } from './checkbox'
|
||||
|
||||
export const checkboxGroupProps = buildProps({
|
||||
modelValue: {
|
||||
type: definePropType<Array<string | number>>(Array),
|
||||
default: () => [],
|
||||
},
|
||||
disabled: Boolean,
|
||||
min: Number,
|
||||
max: Number,
|
||||
size: useSizeProp,
|
||||
label: String,
|
||||
fill: String,
|
||||
textColor: String,
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
validateEvent: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
} as const)
|
||||
|
||||
export const checkboxGroupEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (val: CheckboxValueType[]) => isArray(val),
|
||||
change: (val: CheckboxValueType[]) => isArray(val),
|
||||
}
|
||||
|
||||
export type CheckboxGroupProps = ExtractPropTypes<typeof checkboxGroupProps>
|
||||
export type CheckboxGroupEmits = typeof checkboxGroupEmits
|
||||
export type CheckboxGroupInstance = InstanceType<typeof checkboxGroup>
|
@ -5,7 +5,7 @@
|
||||
:class="ns.b('group')"
|
||||
role="group"
|
||||
:aria-label="!isLabeledByFormItem ? label || 'checkbox-group' : undefined"
|
||||
:aria-labelledby="isLabeledByFormItem ? elFormItem?.labelId : undefined"
|
||||
:aria-labelledby="isLabeledByFormItem ? formItem?.labelId : undefined"
|
||||
>
|
||||
<slot />
|
||||
</component>
|
||||
@ -13,36 +13,36 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, provide, toRefs, watch } from 'vue'
|
||||
import { pick } from 'lodash-unified'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import { debugWarn } from '@element-plus/utils'
|
||||
import { useNamespace, useSize } from '@element-plus/hooks'
|
||||
import {
|
||||
checkboxGroupEmits,
|
||||
useCheckboxGroup,
|
||||
useCheckboxGroupId,
|
||||
useCheckboxGroupProps,
|
||||
} from './checkbox'
|
||||
useFormItem,
|
||||
useFormItemInputId,
|
||||
useNamespace,
|
||||
} from '@element-plus/hooks'
|
||||
import { checkboxGroupContextKey } from '@element-plus/tokens'
|
||||
import { checkboxGroupEmits, checkboxGroupProps } from './checkbox-group'
|
||||
|
||||
import type { CheckboxValueType } from './checkbox'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElCheckboxGroup',
|
||||
})
|
||||
|
||||
const props = defineProps(useCheckboxGroupProps)
|
||||
const props = defineProps(checkboxGroupProps)
|
||||
const emit = defineEmits(checkboxGroupEmits)
|
||||
|
||||
const { elFormItem } = useCheckboxGroup()
|
||||
const { groupId, isLabeledByFormItem } = useCheckboxGroupId(props, {
|
||||
elFormItem,
|
||||
})
|
||||
const checkboxGroupSize = useSize()
|
||||
const ns = useNamespace('checkbox')
|
||||
|
||||
const changeEvent = (value: CheckboxValueType[]) => {
|
||||
const { formItem } = useFormItem()
|
||||
const { inputId: groupId, isLabeledByFormItem } = useFormItemInputId(props, {
|
||||
formItemContext: formItem,
|
||||
})
|
||||
|
||||
const changeEvent = async (value: CheckboxValueType[]) => {
|
||||
emit(UPDATE_MODEL_EVENT, value)
|
||||
nextTick(() => {
|
||||
emit('change', value)
|
||||
})
|
||||
await nextTick()
|
||||
emit('change', value)
|
||||
}
|
||||
|
||||
const modelValue = computed({
|
||||
@ -54,11 +54,17 @@ const modelValue = computed({
|
||||
},
|
||||
})
|
||||
|
||||
provide('CheckboxGroup', {
|
||||
name: 'ElCheckboxGroup',
|
||||
...toRefs(props),
|
||||
provide(checkboxGroupContextKey, {
|
||||
...pick(toRefs(props), [
|
||||
'size',
|
||||
'min',
|
||||
'max',
|
||||
'disabled',
|
||||
'validateEvent',
|
||||
'fill',
|
||||
'textColor',
|
||||
]),
|
||||
modelValue,
|
||||
checkboxGroupSize,
|
||||
changeEvent,
|
||||
})
|
||||
|
||||
@ -66,7 +72,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (props.validateEvent) {
|
||||
elFormItem?.validate('change').catch((err) => debugWarn(err))
|
||||
formItem?.validate('change').catch((err) => debugWarn(err))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1,81 +1,16 @@
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
inject,
|
||||
nextTick,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { toTypeString } from '@vue/shared'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import {
|
||||
useFormItem,
|
||||
useFormItemInputId,
|
||||
useSize,
|
||||
useSizeProp,
|
||||
} from '@element-plus/hooks'
|
||||
import {
|
||||
debugWarn,
|
||||
isArray,
|
||||
isBoolean,
|
||||
isNumber,
|
||||
isString,
|
||||
} from '@element-plus/utils'
|
||||
import { useSizeProp } from '@element-plus/hooks'
|
||||
import { isBoolean, isNumber, isString } from '@element-plus/utils'
|
||||
|
||||
import type { ComponentInternalInstance, ExtractPropTypes, PropType } from 'vue'
|
||||
import type { ICheckboxGroupInstance } from './checkbox.type'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type Checkbox from './checkbox.vue'
|
||||
|
||||
export const useCheckboxGroupProps = {
|
||||
modelValue: {
|
||||
type: Array as PropType<Array<string | number>>,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: Boolean,
|
||||
min: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
size: useSizeProp,
|
||||
id: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
textColor: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
validateEvent: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
}
|
||||
|
||||
export type IUseCheckboxGroupProps = ExtractPropTypes<
|
||||
typeof useCheckboxGroupProps
|
||||
>
|
||||
export type CheckboxValueType = string | number | boolean
|
||||
|
||||
export const checkboxProps = {
|
||||
modelValue: {
|
||||
type: [Number, String, Boolean],
|
||||
default: () => undefined,
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
type: [String, Boolean, Number, Object],
|
||||
@ -112,230 +47,6 @@ export const checkboxProps = {
|
||||
},
|
||||
}
|
||||
|
||||
export const useCheckboxGroup = () => {
|
||||
const { form: elForm, formItem: elFormItem } = useFormItem()
|
||||
const checkboxGroup = inject<ICheckboxGroupInstance>('CheckboxGroup', {})
|
||||
const isGroup = computed(
|
||||
() => checkboxGroup && checkboxGroup?.name === 'ElCheckboxGroup'
|
||||
)
|
||||
const elFormItemSize = computed(() => {
|
||||
return elFormItem?.size
|
||||
})
|
||||
return {
|
||||
isGroup,
|
||||
checkboxGroup,
|
||||
elForm,
|
||||
elFormItemSize,
|
||||
elFormItem,
|
||||
}
|
||||
}
|
||||
|
||||
export const useCheckboxGroupId = (
|
||||
props: IUseCheckboxGroupProps,
|
||||
{ elFormItem }: Partial<ReturnType<typeof useCheckboxGroup>>
|
||||
) => {
|
||||
const { inputId: groupId, isLabeledByFormItem } = useFormItemInputId(props, {
|
||||
formItemContext: elFormItem,
|
||||
})
|
||||
|
||||
return {
|
||||
isLabeledByFormItem,
|
||||
groupId,
|
||||
}
|
||||
}
|
||||
|
||||
const useModel = (props: CheckboxProps) => {
|
||||
const selfModel = ref<any>(false)
|
||||
const { emit } = getCurrentInstance()!
|
||||
const { isGroup, checkboxGroup, elFormItem } = useCheckboxGroup()
|
||||
const isLimitExceeded = ref(false)
|
||||
const model = computed({
|
||||
get() {
|
||||
return isGroup.value
|
||||
? checkboxGroup.modelValue?.value
|
||||
: props.modelValue ?? selfModel.value
|
||||
},
|
||||
|
||||
set(val: unknown) {
|
||||
if (isGroup.value && Array.isArray(val)) {
|
||||
isLimitExceeded.value =
|
||||
checkboxGroup.max !== undefined &&
|
||||
val.length > checkboxGroup.max.value
|
||||
isLimitExceeded.value === false && checkboxGroup?.changeEvent?.(val)
|
||||
} else {
|
||||
emit(UPDATE_MODEL_EVENT, val)
|
||||
selfModel.value = val
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
model,
|
||||
isGroup,
|
||||
isLimitExceeded,
|
||||
elFormItem,
|
||||
}
|
||||
}
|
||||
|
||||
const useCheckboxStatus = (
|
||||
props: CheckboxProps,
|
||||
slots: ComponentInternalInstance['slots'],
|
||||
{ model }: Partial<ReturnType<typeof useModel>>
|
||||
) => {
|
||||
const { isGroup, checkboxGroup } = useCheckboxGroup()
|
||||
const focus = ref(false)
|
||||
const size = useSize(checkboxGroup?.checkboxGroupSize, { prop: true })
|
||||
const isChecked = computed<boolean>(() => {
|
||||
const value = model!.value
|
||||
if (toTypeString(value) === '[object Boolean]') {
|
||||
return value
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.map(toRaw).includes(props.label)
|
||||
} else if (value !== null && value !== undefined) {
|
||||
return value === props.trueLabel
|
||||
} else {
|
||||
return !!value
|
||||
}
|
||||
})
|
||||
|
||||
const checkboxSize = useSize(
|
||||
computed(() =>
|
||||
isGroup.value ? checkboxGroup?.checkboxGroupSize?.value : undefined
|
||||
)
|
||||
)
|
||||
|
||||
const hasOwnLabel = computed<boolean>(() => {
|
||||
return !!(slots.default || props.label)
|
||||
})
|
||||
|
||||
return {
|
||||
isChecked,
|
||||
focus,
|
||||
size,
|
||||
checkboxSize,
|
||||
hasOwnLabel,
|
||||
}
|
||||
}
|
||||
|
||||
const useDisabled = (
|
||||
props: CheckboxProps,
|
||||
{
|
||||
model,
|
||||
isChecked,
|
||||
}: Partial<ReturnType<typeof useModel>> &
|
||||
Partial<ReturnType<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
|
||||
: disabled) ?? false
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
isDisabled,
|
||||
isLimitDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
const setStoreValue = (
|
||||
props: CheckboxProps,
|
||||
{ model }: Partial<ReturnType<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: CheckboxProps,
|
||||
{
|
||||
model,
|
||||
isLimitExceeded,
|
||||
hasOwnLabel,
|
||||
isDisabled,
|
||||
isLabeledByFormItem,
|
||||
}: Partial<
|
||||
ReturnType<typeof useModel> &
|
||||
ReturnType<typeof useCheckboxStatus> &
|
||||
ReturnType<typeof useDisabled> &
|
||||
ReturnType<typeof useFormItemInputId>
|
||||
>
|
||||
) => {
|
||||
const { elFormItem, checkboxGroup } = useCheckboxGroup()
|
||||
const { emit } = getCurrentInstance()!
|
||||
|
||||
function getLabeledValue(value: string | number | boolean) {
|
||||
return value === props.trueLabel || value === true
|
||||
? props.trueLabel ?? true
|
||||
: props.falseLabel ?? false
|
||||
}
|
||||
|
||||
function emitChangeEvent(
|
||||
checked: string | number | boolean,
|
||||
e: InputEvent | MouseEvent
|
||||
) {
|
||||
emit('change', getLabeledValue(checked), e)
|
||||
}
|
||||
|
||||
function handleChange(e: Event) {
|
||||
if (isLimitExceeded!.value) return
|
||||
const target = e.target as HTMLInputElement
|
||||
emit('change', getLabeledValue(target.checked), e)
|
||||
}
|
||||
|
||||
async function onClickRoot(e: MouseEvent) {
|
||||
if (isLimitExceeded!.value) return
|
||||
if (
|
||||
!hasOwnLabel!.value &&
|
||||
!isDisabled!.value &&
|
||||
isLabeledByFormItem!.value
|
||||
) {
|
||||
model!.value = getLabeledValue(
|
||||
[false, props.falseLabel].includes(model!.value)
|
||||
)
|
||||
await nextTick()
|
||||
emitChangeEvent(model!.value, e)
|
||||
}
|
||||
}
|
||||
|
||||
const validateEvent = computed(
|
||||
() => checkboxGroup.validateEvent?.value || props.validateEvent
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (validateEvent.value) {
|
||||
elFormItem?.validate('change').catch((err) => debugWarn(err))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
onClickRoot,
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckboxValueType = string | number | boolean
|
||||
|
||||
export const checkboxEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (val: CheckboxValueType) =>
|
||||
isString(val) || isNumber(val) || isBoolean(val),
|
||||
@ -343,53 +54,6 @@ export const checkboxEmits = {
|
||||
isString(val) || isNumber(val) || isBoolean(val),
|
||||
}
|
||||
|
||||
export const checkboxGroupEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (val: CheckboxValueType[]) => isArray(val),
|
||||
change: (val: CheckboxValueType[]) => isArray(val),
|
||||
}
|
||||
|
||||
export const useCheckbox = (
|
||||
props: CheckboxProps,
|
||||
slots: ComponentInternalInstance['slots']
|
||||
) => {
|
||||
const { model, isGroup, isLimitExceeded, elFormItem } = useModel(props)
|
||||
const { focus, size, isChecked, checkboxSize, hasOwnLabel } =
|
||||
useCheckboxStatus(props, slots, {
|
||||
model,
|
||||
})
|
||||
const { isDisabled } = useDisabled(props, { model, isChecked })
|
||||
const { inputId, isLabeledByFormItem } = useFormItemInputId(props, {
|
||||
formItemContext: elFormItem,
|
||||
disableIdGeneration: hasOwnLabel,
|
||||
disableIdManagement: isGroup,
|
||||
})
|
||||
const { handleChange, onClickRoot } = useEvent(props, {
|
||||
model,
|
||||
isLimitExceeded,
|
||||
hasOwnLabel,
|
||||
isDisabled,
|
||||
isLabeledByFormItem,
|
||||
})
|
||||
|
||||
setStoreValue(props, { model })
|
||||
|
||||
return {
|
||||
elFormItem,
|
||||
inputId,
|
||||
isLabeledByFormItem,
|
||||
isChecked,
|
||||
isDisabled,
|
||||
isGroup,
|
||||
checkboxSize,
|
||||
hasOwnLabel,
|
||||
model,
|
||||
handleChange,
|
||||
onClickRoot,
|
||||
focus,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckboxProps = ExtractPropTypes<typeof checkboxProps>
|
||||
export type CheckboxEmits = typeof checkboxEmits
|
||||
export type CheckboxInstance = InstanceType<typeof Checkbox>
|
||||
|
@ -1,15 +0,0 @@
|
||||
import type { ComputedRef } from 'vue'
|
||||
import type { ComponentSize } from '@element-plus/constants'
|
||||
export interface ICheckboxGroupInstance {
|
||||
name?: string
|
||||
modelValue?: ComputedRef
|
||||
disabled?: ComputedRef<boolean>
|
||||
min?: ComputedRef<string | number>
|
||||
max?: ComputedRef<string | number>
|
||||
size?: ComputedRef<string>
|
||||
fill?: ComputedRef<string>
|
||||
textColor?: ComputedRef<string>
|
||||
checkboxGroupSize?: ComputedRef<ComponentSize>
|
||||
validateEvent?: ComputedRef<boolean>
|
||||
changeEvent?: (...args: any[]) => any
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
ns.is('disabled', isDisabled),
|
||||
ns.is('checked', isChecked),
|
||||
ns.is('indeterminate', indeterminate),
|
||||
ns.is('focus', focus),
|
||||
ns.is('focus', isFocused),
|
||||
]"
|
||||
:tabindex="indeterminate ? 0 : undefined"
|
||||
:role="indeterminate ? 'checkbox' : undefined"
|
||||
@ -36,8 +36,8 @@
|
||||
:true-value="trueLabel"
|
||||
:false-value="falseLabel"
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
@ -51,8 +51,8 @@
|
||||
:name="name"
|
||||
:tabindex="tabindex"
|
||||
@change="handleChange"
|
||||
@focus="focus = true"
|
||||
@blur="focus = false"
|
||||
@focus="isFocused = true"
|
||||
@blur="isFocused = false"
|
||||
/>
|
||||
<span :class="ns.e('inner')" />
|
||||
</span>
|
||||
@ -66,7 +66,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSlots } from 'vue'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { checkboxEmits, checkboxProps, useCheckbox } from './checkbox'
|
||||
import { checkboxEmits, checkboxProps } from './checkbox'
|
||||
import { useCheckbox } from './composables'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElCheckbox',
|
||||
@ -81,12 +82,12 @@ const {
|
||||
isLabeledByFormItem,
|
||||
isChecked,
|
||||
isDisabled,
|
||||
isFocused,
|
||||
checkboxSize,
|
||||
hasOwnLabel,
|
||||
model,
|
||||
handleChange,
|
||||
onClickRoot,
|
||||
focus,
|
||||
} = useCheckbox(props, slots)
|
||||
|
||||
const ns = useNamespace('checkbox')
|
||||
|
5
packages/components/checkbox/src/composables/index.ts
Normal file
5
packages/components/checkbox/src/composables/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './use-checkbox-disabled'
|
||||
export * from './use-checkbox-event'
|
||||
export * from './use-checkbox-model'
|
||||
export * from './use-checkbox-status'
|
||||
export * from './use-checkbox'
|
@ -0,0 +1,33 @@
|
||||
import { computed, inject } from 'vue'
|
||||
import { useDisabled } from '@element-plus/hooks'
|
||||
import { isUndefined } from '@element-plus/utils'
|
||||
import { checkboxGroupContextKey } from '@element-plus/tokens'
|
||||
|
||||
import type { CheckboxModel, CheckboxStatus } from '../composables'
|
||||
|
||||
export const useCheckboxDisabled = ({
|
||||
model,
|
||||
isChecked,
|
||||
}: Pick<CheckboxModel, 'model'> & Pick<CheckboxStatus, 'isChecked'>) => {
|
||||
const checkboxGroup = inject(checkboxGroupContextKey, undefined)
|
||||
|
||||
const isLimitDisabled = computed(() => {
|
||||
const max = checkboxGroup?.max?.value
|
||||
const min = checkboxGroup?.min?.value
|
||||
return (
|
||||
(!isUndefined(max) && model.value.length >= max && !isChecked.value) ||
|
||||
(!isUndefined(min) && model.value.length <= min && isChecked.value)
|
||||
)
|
||||
})
|
||||
|
||||
const isDisabled = useDisabled(
|
||||
computed(() => checkboxGroup?.disabled.value || isLimitDisabled.value)
|
||||
)
|
||||
|
||||
return {
|
||||
isDisabled,
|
||||
isLimitDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckboxDisabled = ReturnType<typeof useCheckboxDisabled>
|
@ -0,0 +1,80 @@
|
||||
import { computed, getCurrentInstance, inject, nextTick, watch } from 'vue'
|
||||
import { useFormItem } from '@element-plus/hooks'
|
||||
import { checkboxGroupContextKey } from '@element-plus/tokens/checkbox'
|
||||
import { debugWarn } from '@element-plus/utils'
|
||||
|
||||
import type { useFormItemInputId } from '@element-plus/hooks'
|
||||
import type { CheckboxProps } from '../checkbox'
|
||||
import type {
|
||||
CheckboxDisabled,
|
||||
CheckboxModel,
|
||||
CheckboxStatus,
|
||||
} from '../composables'
|
||||
|
||||
export const useCheckboxEvent = (
|
||||
props: CheckboxProps,
|
||||
{
|
||||
model,
|
||||
isLimitExceeded,
|
||||
hasOwnLabel,
|
||||
isDisabled,
|
||||
isLabeledByFormItem,
|
||||
}: Pick<CheckboxModel, 'model' | 'isLimitExceeded'> &
|
||||
Pick<CheckboxStatus, 'hasOwnLabel'> &
|
||||
Pick<CheckboxDisabled, 'isDisabled'> &
|
||||
Pick<ReturnType<typeof useFormItemInputId>, 'isLabeledByFormItem'>
|
||||
) => {
|
||||
const checkboxGroup = inject(checkboxGroupContextKey, undefined)
|
||||
const { formItem } = useFormItem()
|
||||
const { emit } = getCurrentInstance()!
|
||||
|
||||
function getLabeledValue(value: string | number | boolean) {
|
||||
return value === props.trueLabel || value === true
|
||||
? props.trueLabel ?? true
|
||||
: props.falseLabel ?? false
|
||||
}
|
||||
|
||||
function emitChangeEvent(
|
||||
checked: string | number | boolean,
|
||||
e: InputEvent | MouseEvent
|
||||
) {
|
||||
emit('change', getLabeledValue(checked), e)
|
||||
}
|
||||
|
||||
function handleChange(e: Event) {
|
||||
if (isLimitExceeded.value) return
|
||||
|
||||
const target = e.target as HTMLInputElement
|
||||
emit('change', getLabeledValue(target.checked), e)
|
||||
}
|
||||
|
||||
async function onClickRoot(e: MouseEvent) {
|
||||
if (isLimitExceeded.value) return
|
||||
|
||||
if (!hasOwnLabel.value && !isDisabled.value && isLabeledByFormItem.value) {
|
||||
model.value = getLabeledValue(
|
||||
[false, props.falseLabel].includes(model.value)
|
||||
)
|
||||
await nextTick()
|
||||
emitChangeEvent(model.value, e)
|
||||
}
|
||||
}
|
||||
|
||||
const validateEvent = computed(
|
||||
() => checkboxGroup?.validateEvent || props.validateEvent
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (validateEvent.value) {
|
||||
formItem?.validate('change').catch((err) => debugWarn(err))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
onClickRoot,
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { computed, getCurrentInstance, inject, ref } from 'vue'
|
||||
import { isArray, isUndefined } from '@element-plus/utils'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||
import { checkboxGroupContextKey } from '@element-plus/tokens/checkbox'
|
||||
|
||||
import type { CheckboxProps } from '../checkbox'
|
||||
|
||||
export const useCheckboxModel = (props: CheckboxProps) => {
|
||||
const selfModel = ref<unknown>(false)
|
||||
const { emit } = getCurrentInstance()!
|
||||
const checkboxGroup = inject(checkboxGroupContextKey, undefined)
|
||||
const isGroup = computed(() => isUndefined(checkboxGroup) === false)
|
||||
const isLimitExceeded = ref(false)
|
||||
const model = computed({
|
||||
get() {
|
||||
return isGroup.value
|
||||
? checkboxGroup?.modelValue?.value
|
||||
: props.modelValue ?? selfModel.value
|
||||
},
|
||||
|
||||
set(val: unknown) {
|
||||
if (isGroup.value && isArray(val)) {
|
||||
isLimitExceeded.value =
|
||||
checkboxGroup?.max?.value !== undefined &&
|
||||
val.length > checkboxGroup?.max.value
|
||||
isLimitExceeded.value === false && checkboxGroup?.changeEvent?.(val)
|
||||
} else {
|
||||
emit(UPDATE_MODEL_EVENT, val)
|
||||
selfModel.value = val
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
model,
|
||||
isGroup,
|
||||
isLimitExceeded,
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckboxModel = ReturnType<typeof useCheckboxModel>
|
@ -0,0 +1,51 @@
|
||||
import { computed, inject, ref, toRaw } from 'vue'
|
||||
import { useSize } from '@element-plus/hooks'
|
||||
import { isArray, isBoolean } from '@element-plus/utils'
|
||||
import { checkboxGroupContextKey } from '@element-plus/tokens/checkbox'
|
||||
|
||||
import type { ComponentInternalInstance } from 'vue'
|
||||
import type { CheckboxProps } from '../checkbox'
|
||||
import type { CheckboxModel } from '../composables'
|
||||
|
||||
export const useCheckboxStatus = (
|
||||
props: CheckboxProps,
|
||||
slots: ComponentInternalInstance['slots'],
|
||||
{ model }: Pick<CheckboxModel, 'model'>
|
||||
) => {
|
||||
const checkboxGroup = inject(checkboxGroupContextKey, undefined)
|
||||
const isFocused = ref(false)
|
||||
const isChecked = computed<boolean>(() => {
|
||||
const value = model.value
|
||||
if (isBoolean(value)) {
|
||||
return value
|
||||
} else if (isArray(value)) {
|
||||
return value.map(toRaw).includes(props.label)
|
||||
} else if (value !== null && value !== undefined) {
|
||||
return value === props.trueLabel
|
||||
} else {
|
||||
return !!value
|
||||
}
|
||||
})
|
||||
|
||||
const checkboxButtonSize = useSize(
|
||||
computed(() => checkboxGroup?.size?.value),
|
||||
{
|
||||
prop: true,
|
||||
}
|
||||
)
|
||||
const checkboxSize = useSize(computed(() => checkboxGroup?.size?.value))
|
||||
|
||||
const hasOwnLabel = computed<boolean>(() => {
|
||||
return !!(slots.default || props.label)
|
||||
})
|
||||
|
||||
return {
|
||||
checkboxButtonSize,
|
||||
isChecked,
|
||||
isFocused,
|
||||
checkboxSize,
|
||||
hasOwnLabel,
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckboxStatus = ReturnType<typeof useCheckboxStatus>
|
70
packages/components/checkbox/src/composables/use-checkbox.ts
Normal file
70
packages/components/checkbox/src/composables/use-checkbox.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { useFormItem, useFormItemInputId } from '@element-plus/hooks'
|
||||
import { isArray } from '@element-plus/utils'
|
||||
import {
|
||||
useCheckboxDisabled,
|
||||
useCheckboxEvent,
|
||||
useCheckboxModel,
|
||||
useCheckboxStatus,
|
||||
} from '../composables'
|
||||
|
||||
import type { ComponentInternalInstance } from 'vue'
|
||||
import type { CheckboxProps } from '../checkbox'
|
||||
import type { CheckboxModel } from '../composables'
|
||||
|
||||
const setStoreValue = (
|
||||
props: CheckboxProps,
|
||||
{ model }: Pick<CheckboxModel, 'model'>
|
||||
) => {
|
||||
function addToStore() {
|
||||
if (isArray(model.value) && !model.value.includes(props.label)) {
|
||||
model.value.push(props.label)
|
||||
} else {
|
||||
model.value = props.trueLabel || true
|
||||
}
|
||||
}
|
||||
props.checked && addToStore()
|
||||
}
|
||||
|
||||
export const useCheckbox = (
|
||||
props: CheckboxProps,
|
||||
slots: ComponentInternalInstance['slots']
|
||||
) => {
|
||||
const { formItem: elFormItem } = useFormItem()
|
||||
const { model, isGroup, isLimitExceeded } = useCheckboxModel(props)
|
||||
const {
|
||||
isFocused,
|
||||
isChecked,
|
||||
checkboxButtonSize,
|
||||
checkboxSize,
|
||||
hasOwnLabel,
|
||||
} = useCheckboxStatus(props, slots, { model })
|
||||
const { isDisabled } = useCheckboxDisabled({ model, isChecked })
|
||||
const { inputId, isLabeledByFormItem } = useFormItemInputId(props, {
|
||||
formItemContext: elFormItem,
|
||||
disableIdGeneration: hasOwnLabel,
|
||||
disableIdManagement: isGroup,
|
||||
})
|
||||
const { handleChange, onClickRoot } = useCheckboxEvent(props, {
|
||||
model,
|
||||
isLimitExceeded,
|
||||
hasOwnLabel,
|
||||
isDisabled,
|
||||
isLabeledByFormItem,
|
||||
})
|
||||
|
||||
setStoreValue(props, { model })
|
||||
|
||||
return {
|
||||
inputId,
|
||||
isLabeledByFormItem,
|
||||
isChecked,
|
||||
isDisabled,
|
||||
isFocused,
|
||||
checkboxButtonSize,
|
||||
checkboxSize,
|
||||
hasOwnLabel,
|
||||
model,
|
||||
handleChange,
|
||||
onClickRoot,
|
||||
}
|
||||
}
|
15
packages/tokens/checkbox.ts
Normal file
15
packages/tokens/checkbox.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { InjectionKey, ToRefs, WritableComputedRef } from 'vue'
|
||||
import type { CheckboxGroupProps } from '@element-plus/components'
|
||||
|
||||
type CheckboxGroupContext = {
|
||||
modelValue?: WritableComputedRef<any>
|
||||
changeEvent?: (...args: any) => any
|
||||
} & ToRefs<
|
||||
Pick<
|
||||
CheckboxGroupProps,
|
||||
'size' | 'min' | 'max' | 'disabled' | 'validateEvent' | 'fill' | 'textColor'
|
||||
>
|
||||
>
|
||||
|
||||
export const checkboxGroupContextKey: InjectionKey<CheckboxGroupContext> =
|
||||
Symbol('checkboxGroupContextKey')
|
@ -1,6 +1,7 @@
|
||||
export * from './breadcrumb'
|
||||
export * from './button'
|
||||
export * from './carousel'
|
||||
export * from './checkbox'
|
||||
export * from './collapse'
|
||||
export * from './config-provider'
|
||||
export * from './dialog'
|
||||
|
Loading…
Reference in New Issue
Block a user