mirror of
https://github.com/element-plus/element-plus.git
synced 2025-02-17 11:49:41 +08:00
refactor(radio): refactor radio components
This commit is contained in:
parent
c408492b2b
commit
2163969059
@ -59,6 +59,8 @@ import ElPopover from '@element-plus/popover'
|
||||
import ElCascader from '@element-plus/cascader'
|
||||
import ElCascaderPanel from '@element-plus/cascader-panel'
|
||||
|
||||
import { ElFormItemSize } from '@element-plus/form/src/token'
|
||||
|
||||
export {
|
||||
ElAlert,
|
||||
ElAvatar,
|
||||
@ -118,7 +120,17 @@ export {
|
||||
ElCascaderPanel,
|
||||
}
|
||||
|
||||
const install = (app: App): void => {
|
||||
interface InstallOptions {
|
||||
size: ElFormItemSize
|
||||
zIndex: number
|
||||
}
|
||||
|
||||
const defaultInstallOpt = {
|
||||
size: '' as ElFormItemSize,
|
||||
zIndex: 2000,
|
||||
}
|
||||
|
||||
const install = (app: App, opt: InstallOptions = defaultInstallOpt): void => {
|
||||
ElAlert(app)
|
||||
ElAvatar(app)
|
||||
ElAutocomplete(app)
|
||||
@ -175,6 +187,8 @@ const install = (app: App): void => {
|
||||
ElPopover(app)
|
||||
ElCascader(app)
|
||||
ElCascaderPanel(app)
|
||||
|
||||
app.config.globalProperties.$ELEMENT = opt
|
||||
}
|
||||
|
||||
const elementUI = {
|
||||
|
@ -62,7 +62,7 @@ import { RuleItem } from 'async-validator'
|
||||
import LabelWrap from './label-wrap'
|
||||
import { getPropByPath, useGlobalConfig } from '@element-plus/utils/util'
|
||||
import mitt from 'mitt'
|
||||
import { elFormKey, elFormItemKey, ValidateFieldCallback } from './token'
|
||||
import { elFormKey, elFormItemKey, ValidateFieldCallback, ElFormItemSize } from './token'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElFormItem',
|
||||
@ -90,7 +90,7 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
size: String,
|
||||
size: String as PropType<ElFormItemSize>,
|
||||
},
|
||||
setup(props) {
|
||||
const formItemMitt = mitt()
|
||||
@ -316,8 +316,11 @@ export default defineComponent({
|
||||
formItemMitt.off('el.form.blur', onFieldBlur)
|
||||
formItemMitt.off('el.form.change', onFieldChange)
|
||||
}
|
||||
|
||||
const refedProps = toRefs(props)
|
||||
const elFormItem = reactive({
|
||||
...toRefs(props),
|
||||
...refedProps,
|
||||
elFormItemSize: refedProps.size,
|
||||
removeValidateEvents,
|
||||
addValidateEvents,
|
||||
resetField,
|
||||
|
@ -4,13 +4,18 @@ import type {
|
||||
FieldErrorList,
|
||||
} from 'async-validator'
|
||||
|
||||
export enum ElFormItemSize {
|
||||
MINI = 'mini',
|
||||
SMALL = 'small',
|
||||
MEDIUM = 'medium',
|
||||
}
|
||||
|
||||
export interface ElFormContext {
|
||||
registerLabelWidth(width: number, oldWidth: number): void
|
||||
deregisterLabelWidth(width: number): void
|
||||
autoLabelWidth: string | undefined
|
||||
formMitt: Emitter
|
||||
emit: (evt: string, ...args: any[]) => void
|
||||
|
||||
labelSuffix: string
|
||||
inline?: boolean
|
||||
model?: Record<string, unknown>
|
||||
@ -31,6 +36,7 @@ export interface ValidateFieldCallback {
|
||||
export interface ElFormItemContext {
|
||||
prop?: string
|
||||
formItemMitt: Emitter
|
||||
elFormItemSize: ElFormItemSize
|
||||
validate(trigger?: string, callback?: ValidateFieldCallback): void
|
||||
updateComputedLabelWidth(width: number): void
|
||||
addValidateEvents(): void
|
||||
|
@ -2,10 +2,11 @@
|
||||
<label
|
||||
class="el-radio-button"
|
||||
:class="[
|
||||
size ? 'el-radio-button--' + size.value : '',
|
||||
{ 'is-active': value === label },
|
||||
{ 'is-disabled': isDisabled },
|
||||
{ 'is-focus': focus }
|
||||
size ? 'el-radio-button--' + size : '',
|
||||
{ 'is-active': value === label,
|
||||
'is-disabled': isDisabled,
|
||||
'is-focus': focus,
|
||||
}
|
||||
]"
|
||||
role="radio"
|
||||
:aria-checked="value === label"
|
||||
@ -29,14 +30,15 @@
|
||||
:style="value === label ? activeStyle : null"
|
||||
@keydown.stop
|
||||
>
|
||||
<slot></slot>
|
||||
<template v-if="!$slots.default">{{ label }}</template>
|
||||
<slot>
|
||||
{{ label }}
|
||||
</slot>
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import useRadio from './useRadio'
|
||||
import { useRadio, useRadioAttrs } from './useRadio'
|
||||
|
||||
export default {
|
||||
name: 'ElRadioButton',
|
||||
@ -53,36 +55,46 @@ export default {
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const radioUse = useRadio()
|
||||
const isGroup = radioUse.isGroup
|
||||
const radioGroup_ = radioUse.radioGroup_
|
||||
const elFormItemSize_ = radioUse.elFormItemSize_
|
||||
const ELEMENT = radioUse.ELEMENT
|
||||
const focus = radioUse.focus
|
||||
const elForm = radioUse.elForm
|
||||
const {
|
||||
isGroup,
|
||||
radioGroup,
|
||||
elFormItemSize,
|
||||
ELEMENT,
|
||||
focus,
|
||||
elForm,
|
||||
} = useRadio()
|
||||
|
||||
const size = computed(() => {
|
||||
return radioGroup_.radioGroupSize || elFormItemSize_ || (ELEMENT || {} as any).size
|
||||
return radioGroup.radioGroupSize || elFormItemSize.value || ELEMENT.size
|
||||
})
|
||||
const isDisabled = computed(() => {
|
||||
return props.disabled || radioGroup_.disabled || (elForm || {} as any).disabled
|
||||
})
|
||||
const tabIndex = computed(() => {
|
||||
return (isDisabled.value || (radioGroup_ && value.value !== props.label)) ? -1 : 0
|
||||
})
|
||||
const value = computed({
|
||||
|
||||
console.log(size.value)
|
||||
|
||||
const value = computed<boolean | string | number>({
|
||||
get() {
|
||||
return radioGroup_.modelValue.value
|
||||
return radioGroup.modelValue
|
||||
},
|
||||
set(value) {
|
||||
radioGroup_.changeEvent(value)
|
||||
radioGroup.changeEvent(value)
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
isDisabled,
|
||||
tabIndex,
|
||||
} = useRadioAttrs(props, {
|
||||
model: value,
|
||||
elForm,
|
||||
radioGroup: radioGroup,
|
||||
isGroup,
|
||||
})
|
||||
|
||||
const activeStyle = computed(() => {
|
||||
return {
|
||||
backgroundColor: radioGroup_.fill || '',
|
||||
borderColor: radioGroup_.fill || '',
|
||||
boxShadow: radioGroup_.fill ? `-1px 0 0 0 ${radioGroup_.fill}` : '',
|
||||
color: radioGroup_.textColor || '',
|
||||
backgroundColor: radioGroup.fill || '',
|
||||
borderColor: radioGroup.fill || '',
|
||||
boxShadow: radioGroup.fill ? `-1px 0 0 0 ${radioGroup.fill}` : '',
|
||||
color: radioGroup.textColor || '',
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -17,8 +17,15 @@ import {
|
||||
onMounted,
|
||||
inject,
|
||||
ref,
|
||||
reactive,
|
||||
} from 'vue'
|
||||
import { EVENT_CODE } from '@element-plus/utils/aria'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import { ElFormItemContext, elFormItemKey } from '@element-plus/form/src/token'
|
||||
import radioGroupKey from './token'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
import type { ElFormItemSize } from '@element-plus/form/src/token'
|
||||
|
||||
export default {
|
||||
name: 'ElRadioGroup',
|
||||
@ -31,8 +38,10 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
type: String as PropType<ElFormItemSize>,
|
||||
validator: (val: string) => {
|
||||
return ['mini', 'small', 'medium'].includes(val)
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
@ -45,21 +54,18 @@ export default {
|
||||
disabled: Boolean,
|
||||
},
|
||||
|
||||
emits: ['update:modelValue', 'change'],
|
||||
emits: [UPDATE_MODEL_EVENT, 'change'],
|
||||
|
||||
setup(props, ctx) {
|
||||
const radioGroup = ref(null)
|
||||
//todo: ELEMENT
|
||||
const ELEMENT = {}
|
||||
const elFormItem = inject('elFormItem', {})
|
||||
const elFormItemSize_ = computed(() => {
|
||||
return (elFormItem || {} as any).elFormItemSize
|
||||
})
|
||||
const radioGroupSize = computed(() => {
|
||||
return props.size || elFormItemSize_ || (ELEMENT || {} as any).size
|
||||
|
||||
const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
|
||||
|
||||
const radioGroupSize = computed<ElFormItemSize>(() => {
|
||||
return props.size || elFormItem.elFormItemSize || '' as ElFormItemSize
|
||||
})
|
||||
|
||||
const modelValue = computed({
|
||||
const modelValue = computed<boolean | string | number>({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
@ -70,13 +76,13 @@ export default {
|
||||
|
||||
// methods
|
||||
const changeEvent = value => {
|
||||
ctx.emit('update:modelValue', value)
|
||||
ctx.emit(UPDATE_MODEL_EVENT, value)
|
||||
nextTick(() => {
|
||||
ctx.emit('change', value)
|
||||
})
|
||||
}
|
||||
|
||||
provide('RadioGroup', {
|
||||
provide(radioGroupKey, reactive({
|
||||
name: 'ElRadioGroup',
|
||||
changeEvent: changeEvent,
|
||||
radioGroupSize: radioGroupSize,
|
||||
@ -84,14 +90,14 @@ export default {
|
||||
textColor: props.textColor,
|
||||
disabled: props.disabled,
|
||||
modelValue,
|
||||
})
|
||||
}))
|
||||
|
||||
const handleKeydown = e => { // 左右上下按键 可以在radio组内切换不同选项
|
||||
const target = e.target
|
||||
const className = target.nodeName === 'INPUT' ? '[type=radio]' : '[role=radio]'
|
||||
const radios = radioGroup.value.querySelectorAll(className)
|
||||
const length = radios.length
|
||||
const index = [].indexOf.call(radios, target)
|
||||
const index = Array.from(radios).indexOf(target)
|
||||
const roleRadios = radioGroup.value.querySelectorAll('[role=radio]')
|
||||
let nextIndex = null
|
||||
switch (e.code) {
|
||||
@ -117,8 +123,8 @@ export default {
|
||||
|
||||
onMounted(() => {
|
||||
const radios = radioGroup.value.querySelectorAll('[type=radio]')
|
||||
const firstLabel = radioGroup.value.querySelectorAll('[role=radio]')[0]
|
||||
if (![].some.call(radios, radio => radio.checked) && firstLabel) {
|
||||
const firstLabel = radios[0]
|
||||
if (!Array.from(radios).some((radio: HTMLInputElement) => radio.checked) && firstLabel) {
|
||||
firstLabel.tabIndex = 0
|
||||
}
|
||||
})
|
||||
|
@ -23,7 +23,7 @@
|
||||
>
|
||||
<span class="el-radio__inner"></span>
|
||||
<input
|
||||
ref="radio"
|
||||
ref="radioRef"
|
||||
v-model="model"
|
||||
class="el-radio__original"
|
||||
:value="label"
|
||||
@ -46,8 +46,9 @@
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import { defineComponent, computed, getCurrentInstance, nextTick } from 'vue'
|
||||
import useRadio from './useRadio'
|
||||
import { defineComponent, computed, nextTick, ref } from 'vue'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import { useRadio, useRadioAttrs } from './useRadio'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElRadio',
|
||||
@ -74,45 +75,47 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['update:modelValue', 'change'],
|
||||
emits: [UPDATE_MODEL_EVENT, 'change'],
|
||||
|
||||
setup(props, ctx) {
|
||||
const radioUse = useRadio()
|
||||
const isGroup = radioUse.isGroup
|
||||
const radioGroup_ = radioUse.radioGroup_
|
||||
const elFormItemSize_ = radioUse.elFormItemSize_
|
||||
const ELEMENT = radioUse.ELEMENT
|
||||
const focus = radioUse.focus
|
||||
const elForm = radioUse.elForm
|
||||
const instance = getCurrentInstance()
|
||||
const model = computed({
|
||||
const {
|
||||
isGroup,
|
||||
radioGroup,
|
||||
elFormItemSize,
|
||||
ELEMENT,
|
||||
focus,
|
||||
elForm,
|
||||
} = useRadio()
|
||||
|
||||
const radioRef = ref<HTMLInputElement>()
|
||||
const model = computed<string | number | boolean>({
|
||||
get() {
|
||||
return isGroup.value ? radioGroup_.modelValue.value : props.modelValue
|
||||
return isGroup.value ? radioGroup.modelValue : props.modelValue
|
||||
},
|
||||
set(val) {
|
||||
if (isGroup.value) {
|
||||
radioGroup_.changeEvent(val)
|
||||
radioGroup.changeEvent(val)
|
||||
} else {
|
||||
ctx.emit('update:modelValue', val)
|
||||
ctx.emit(UPDATE_MODEL_EVENT, val)
|
||||
}
|
||||
instance.refs.radio && (instance.refs.radio.checked = props.modelValue === props.label)
|
||||
radioRef.value.checked = props.modelValue === props.label
|
||||
},
|
||||
})
|
||||
|
||||
const tabIndex = computed(() => {
|
||||
return (isDisabled.value || (isGroup.value && model.value !== props.label)) ? -1 : 0
|
||||
})
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return isGroup.value
|
||||
? radioGroup_.disabled || props.disabled || (elForm || {}).disabled
|
||||
: props.disabled || (elForm || {}).disabled
|
||||
const {
|
||||
tabIndex,
|
||||
isDisabled,
|
||||
} = useRadioAttrs(props, {
|
||||
isGroup,
|
||||
radioGroup: radioGroup,
|
||||
elForm,
|
||||
model,
|
||||
})
|
||||
|
||||
const radioSize = computed(() => {
|
||||
const temRadioSize = props.size || elFormItemSize_ || (ELEMENT || {}).size
|
||||
const temRadioSize = props.size || elFormItemSize.value || (ELEMENT).size
|
||||
return isGroup.value
|
||||
? radioGroup_.radioGroupSize || temRadioSize
|
||||
? radioGroup.radioGroupSize || temRadioSize
|
||||
: temRadioSize
|
||||
})
|
||||
|
||||
@ -130,6 +133,7 @@ export default defineComponent({
|
||||
tabIndex,
|
||||
radioSize,
|
||||
handleChange,
|
||||
radioRef,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
18
packages/radio/src/token.ts
Normal file
18
packages/radio/src/token.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { InjectionKey } from 'vue'
|
||||
import { ElFormItemSize } from '@element-plus/form/src/token'
|
||||
|
||||
type IModelType = boolean | string | number
|
||||
|
||||
export interface RadioGroupContext {
|
||||
name: string
|
||||
changeEvent: (val: IModelType) => void
|
||||
radioGroupSize: ElFormItemSize
|
||||
fill: string
|
||||
textColor: string
|
||||
disabled: boolean
|
||||
modelValue: IModelType
|
||||
}
|
||||
|
||||
const radioGroupKey: InjectionKey<RadioGroupContext> = 'RadioGroup' as any
|
||||
|
||||
export default radioGroupKey
|
@ -1,22 +1,63 @@
|
||||
import { ref, computed, inject } from 'vue'
|
||||
import { ref, computed, inject, WritableComputedRef } from 'vue'
|
||||
import { elFormKey, elFormItemKey } from '@element-plus/form/src/token'
|
||||
import { useGlobalConfig } from '@element-plus/utils/util'
|
||||
import radioGroupKey from './token'
|
||||
|
||||
export default () => {
|
||||
//todo: ELEMENT
|
||||
const ELEMENT = null
|
||||
const elForm = inject('elForm', {})
|
||||
const elFormItem = inject('elFormItem', {})
|
||||
const radioGroup_ = inject('RadioGroup', {}) as any
|
||||
import type { ComputedRef } from 'vue'
|
||||
import type { ElFormContext, ElFormItemContext } from '@element-plus/form/src/token'
|
||||
import type { RadioGroupContext } from './token'
|
||||
|
||||
export const useRadio = () => {
|
||||
|
||||
const ELEMENT = useGlobalConfig()
|
||||
const elForm = inject(elFormKey, {} as ElFormContext)
|
||||
const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
|
||||
const radioGroup = inject(radioGroupKey, {} as RadioGroupContext)
|
||||
const focus = ref(false)
|
||||
const isGroup = computed(() => radioGroup_ && radioGroup_.name === 'ElRadioGroup')
|
||||
const elFormItemSize_ = computed(() => {
|
||||
return (elFormItem || {} as any).elFormItemSize
|
||||
})
|
||||
const isGroup = computed(() => radioGroup?.name === 'ElRadioGroup')
|
||||
const elFormItemSize = computed(() => elFormItem.elFormItemSize || ELEMENT.size)
|
||||
|
||||
return {
|
||||
isGroup,
|
||||
focus,
|
||||
radioGroup_,
|
||||
radioGroup,
|
||||
elForm,
|
||||
ELEMENT,
|
||||
elFormItemSize_,
|
||||
elFormItemSize,
|
||||
}
|
||||
}
|
||||
|
||||
interface IUseRadioAttrsProps {
|
||||
disabled?: boolean
|
||||
label: string | number | boolean
|
||||
}
|
||||
|
||||
interface IUseRadioAttrsState {
|
||||
isGroup: ComputedRef<boolean>
|
||||
radioGroup: RadioGroupContext
|
||||
elForm: ElFormContext
|
||||
model: WritableComputedRef<string | number | boolean>
|
||||
}
|
||||
|
||||
export const useRadioAttrs = (props: IUseRadioAttrsProps, {
|
||||
isGroup,
|
||||
radioGroup,
|
||||
elForm,
|
||||
model,
|
||||
}: IUseRadioAttrsState) => {
|
||||
const isDisabled = computed(() => {
|
||||
return isGroup.value
|
||||
? radioGroup.disabled || props.disabled || elForm.disabled
|
||||
: props.disabled || elForm.disabled
|
||||
})
|
||||
|
||||
const tabIndex = computed(() => {
|
||||
return (isDisabled.value || (isGroup.value && model.value !== props.label)) ? -1 : 0
|
||||
})
|
||||
|
||||
return {
|
||||
isDisabled,
|
||||
tabIndex,
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import { castArray } from 'lodash'
|
||||
|
||||
import {
|
||||
@ -16,7 +17,6 @@ import {
|
||||
import isServer from './isServer'
|
||||
import type { AnyFunction } from './types'
|
||||
import type { Ref } from 'vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
export type PartialCSSStyleDeclaration = Partial<
|
||||
Pick<CSSStyleDeclaration, 'transform' | 'transition' | 'animation'>
|
||||
@ -180,16 +180,16 @@ export function useGlobalConfig() {
|
||||
}
|
||||
return {}
|
||||
}
|
||||
export const arrayFindIndex = function (
|
||||
arr: Array<unknown>,
|
||||
pred: (any) => boolean,
|
||||
export const arrayFindIndex = function<T = any> (
|
||||
arr: Array<T>,
|
||||
pred: (args: T) => boolean,
|
||||
): number {
|
||||
return arr.findIndex(pred)
|
||||
}
|
||||
|
||||
export const arrayFind = function (
|
||||
arr: Array<unknown>,
|
||||
pred: (any) => boolean,
|
||||
export const arrayFind = function<T = any> (
|
||||
arr: Array<T>,
|
||||
pred: (args: T) => boolean,
|
||||
): any {
|
||||
return arr.find(pred)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user