refactor(radio): refactor radio components

This commit is contained in:
JeremyWuuuuu 2020-10-31 15:20:38 +08:00 committed by Herrington Darkholme
parent c408492b2b
commit 2163969059
9 changed files with 202 additions and 98 deletions

View File

@ -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 = {

View File

@ -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,

View File

@ -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

View File

@ -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 || '',
}
})

View File

@ -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
}
})

View File

@ -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,
}
},
})

View 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

View File

@ -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,
}
}

View File

@ -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)
}