refactor(radio): support vue3

This commit is contained in:
07akioni 2020-09-29 00:56:27 +08:00
parent b460a9f4b8
commit f45f06206f
15 changed files with 177 additions and 126 deletions

View File

@ -1,25 +1,25 @@
# 基础用法
```html
<n-radio
v-model="value"
v-model:checked-value="value"
value="Definitely Maybe"
>
Definitely Maybe
</n-radio>
<n-radio
v-model="value"
v-model:checked-value="value"
value="Be Here Now"
>
Be Here Now
</n-radio>
<n-radio
v-model="value"
v-model:checked-value="value"
value="Be Here Now"
:disabled="disabled"
>
Be Here Now
</n-radio>
<n-switch v-model="disabled"/>
<n-switch v-model:value="disabled"/>
```
```js

View File

@ -2,7 +2,7 @@
有的时候用按钮显得更优雅一点。
```html
<div style="margin-bottom: 12px;">
<n-radio-group v-model="value" name="radiobuttongroup">
<n-radio-group v-model:value="value" name="radiobuttongroup">
<n-radio-button
v-for="song in songs"
:key="song.value"
@ -14,13 +14,13 @@
</n-radio-group>
</div>
<n-checkbox
v-model="disabled2"
v-model:checked="disabled2"
style="margin-right: 12px;"
>
禁用 Shakemaker
</n-checkbox>
<n-checkbox
v-model="disabled1"
v-model:checked="disabled1"
>
禁用 Live Forever
</n-checkbox>

View File

@ -1,7 +1,10 @@
# 选项组
一个选项组看起来就挺舒服。
```html
<n-radio-group v-model="value" name="radiogroup">
<n-radio-group
v-model:value="value"
name="radiogroup"
>
<n-radio
v-for="song in songs"
:key="song.value"

View File

@ -9,52 +9,24 @@ button-group
size
radio-focus-debug
```
## V-model
### Radio V-model
|Prop|Event|
|-|-|
|checked-value|change|
### Radio Group V-model
|Prop|Event|
|-|-|
|value|change|
## Props
### Radio Props
### Radio Props, Radio Button Props
|名称|类型|默认值|说明|
|-|-|-|-|
|theme|`'light' \| 'dark' \| null \| string`|`null`||
|name|`string`|`undefined`|单选 radio 元素的 name 属性。如果没有设定会使用 `radio-group``name`|
|name|`string`|`undefined`|单选按钮 radio 元素的 name 属性。如果没有设定会使用 `n-radio-group``name`|
|checked-value|`string \| number \| boolean`|`null`||
|value|`string \| number \| boolean`|`null`||
|value|`string \| number \| boolean`|required||
|disabled|`boolean`|`false`||
### Radio Button Props
|名称|类型|默认值|说明|
|-|-|-|-|
|name|`string`|`undefined`|单选按钮 radio 元素的 name 属性。如果没有设定会使用 `radio-group``name`|
|checked-value|`string \| number \| boolean`|`null`||
|value|`string \| number \| boolean`|`null`||
|disabled|`boolean`|`false`||
|size|`'small' \| 'medium' \| 'large'`|`'small'`||
|size|`'small' \| 'medium' \| 'large'`|`'medium'`|只用于 `n-radio`|
|on-update:checked-value|`(checkedValue: string \| number \| boolean) => any`|`undefined`||
### Radio Group Props
|名称|类型|默认值|说明|
|-|-|-|-|
|theme|`'light' \| 'dark' \| null \| string`|`null`||
|name|`string`|`null`|选项组内部 radio 元素的 name 属性|
|size|`'small' \| 'medium' \| 'large'`|`small`||
|value|`string \| number \| boolean`|`null`||
|disabled|`boolean`|`false`||
## Events
### Radio, Radio Button Events
|名称|参数|说明|
|-|-|-|
|change|`(checkedValue: string \| number \| boolean)`||
### Radio Group Events
|名称|参数|说明|
|-|-|-|
|change|`(checkedValue: string \| number \| boolean)`||
|name|`string`|`null`|选项组内部 radio 元素的 name 属性|
|size|`'small' \| 'medium' \| 'large'`|`medium`||
|theme|`'light' \| 'dark' \| null \| string`|`null`||
|value|`string \| number \| boolean`|`null`||
|on-update:value|`(checkedValue: string \| number \| boolean) => any`|`undefined`||

View File

@ -2,7 +2,7 @@
```html
<n-radio
v-model="value"
v-model:checked-value="value"
value="Definitely Maybe"
>
<n-input />

View File

@ -2,7 +2,7 @@
任君挑选。
```html
<div style="margin-bottom: 12px;">
<n-radio-group v-model="value" name="radiobuttongroup2" size="medium">
<n-radio-group v-model:value="value" name="radiobuttongroup2" size="medium">
<n-radio-button
v-for="song in songs"
:key="song.value"
@ -14,7 +14,7 @@
</n-radio-group>
</div>
<div style="margin-bottom: 12px;">
<n-radio-group v-model="value" name="radiobuttongroup3" size="large">
<n-radio-group v-model:value="value" name="radiobuttongroup3" size="large">
<n-radio-button
v-for="song in songs"
:key="song.value"
@ -26,13 +26,13 @@
</n-radio-group>
</div>
<n-checkbox
v-model="disabled2"
v-model:checked="disabled2"
style="margin-right: 12px;"
>
禁用 Shakemaker
</n-checkbox>
<n-checkbox
v-model="disabled1"
v-model:checked="disabled1"
>
禁用 Live Forever
</n-checkbox>

View File

@ -79,7 +79,7 @@ export function useDisabledUntilMounted (durationTickCount = 0) {
export function useMemo (valueGenerator, deps) {
const valueRef = ref(valueGenerator())
watch(deps, () => {
watch(deps.filter(dep => dep), () => {
valueRef.value = valueGenerator()
})
return valueRef

View File

@ -3,3 +3,4 @@ export { getVNodeChildren } from './src/get-v-node-children'
export { createId } from './src/create-id'
export { keep } from './src/keep'
export { omit } from './omit'
export { flatten } from './src/flatten'

View File

@ -0,0 +1,13 @@
import { Fragment } from 'vue'
export function flatten (vNodes) {
let result = []
vNodes.forEach(vNode => {
if (vNode.type === Fragment) {
result = result.concat(flatten(vNode.children))
} else {
result.push(vNode)
}
})
return result
}

View File

@ -3,7 +3,7 @@
class="n-radio"
:class="{
'n-radio--disabled': syntheticDisabled,
'n-radio--checked': syntheticChecked,
'n-radio--checked': renderSafeChecked,
'n-radio--focus': focus,
[`n-radio--${syntheticSize}-size`]: true,
[`n-${syntheticTheme}-theme`]: syntheticTheme
@ -17,7 +17,7 @@
type="radio"
class="n-radio__radio-input"
:name="syntheticName"
:checked="syntheticChecked"
:checked="renderSafeChecked"
:disabled="syntheticDisabled"
@change="handleRadioInputChange"
@focus="handleRadioInputFocus"
@ -26,7 +26,7 @@
<div
class="n-radio__dot"
:class="{
'n-radio__dot--checked': syntheticChecked
'n-radio__dot--checked': renderSafeChecked
}"
/>
<div class="n-radio__label">
@ -42,6 +42,7 @@ import themeable from '../../_mixins/themeable'
import radioMixin from './radio-mixin'
import usecssr from '../../_mixins/usecssr'
import styles from './styles/radio/index.js'
import setup from './radio-setup'
export default {
name: 'Radio',
@ -49,14 +50,9 @@ export default {
withapp,
themeable,
usecssr(styles),
asformitem(
{
change: 'change',
blur: 'blur',
focus: 'focus'
},
'medium',
function () {
asformitem({
defaultSize: 'medium',
syntheticSize () {
const size = this.size
if (size) return size
const NRadioGroup = this.NRadioGroup
@ -66,14 +62,14 @@ export default {
const NFormItem = this.NFormItem
if (
NFormItem &&
NFormItem !== '__FORM_ITEM_INNER__' &&
NFormItem.syntheticSize
NFormItem !== '__FORM_ITEM_INNER__' &&
NFormItem.syntheticSize
) {
return NFormItem.syntheticSize
}
return 'medium'
}
),
}),
radioMixin
],
props: {
@ -81,9 +77,10 @@ export default {
validator (value) {
return ['small', 'medium', 'large'].includes(value)
},
default: null
default: undefined
}
},
setup,
computed: {
syntheticTheme () {
const theme = this.theme

View File

@ -3,11 +3,11 @@
class="n-radio-button"
:class="{
'n-radio-button--disabled': syntheticDisabled,
'n-radio-button--checked': syntheticChecked,
'n-radio-button--checked': renderSafeChecked,
'n-radio-button--focus': focus
}"
:style="{
color: syntheticChecked ? syntheticAscendantBackgroundColor : null
color: renderSafeChecked ? syntheticAscendantBackgroundColor : null
}"
@keyup.enter="handleKeyUpEnter"
@click="handleClick"
@ -18,7 +18,7 @@
type="radio"
class="n-radio-button__radio-input"
:name="syntheticName"
:checked="syntheticChecked"
:checked="renderSafeChecked"
:disabled="syntheticDisabled"
@change="handleRadioInputChange"
@focus="handleRadioInputFocus"
@ -31,6 +31,7 @@
<script>
import radioMixin from './radio-mixin'
import setup from './radio-setup'
import withapp from '../../_mixins/withapp'
import themeable from '../../_mixins/themeable'
import usecssr from '../../_mixins/usecssr'
@ -46,11 +47,6 @@ export default {
radioMixin,
usecssr(styles)
],
created () {
this.NRadioGroup && this.NRadioGroup.radioButtonCount++
},
beforeUnmount () {
this.NRadioGroup && this.NRadioGroup.radioButtonCount--
}
setup
}
</script>

View File

@ -3,31 +3,35 @@ import withapp from '../../_mixins/withapp'
import themeable from '../../_mixins/themeable'
import hollowoutable from '../../_mixins/hollowoutable'
import asformitem from '../../_mixins/asformitem'
import getDefaultSlot from '../../_utils/vue/getDefaultSlot'
import { getSlot, flatten } from '../../_utils/vue'
import { warn } from '../../_utils/naive/warn'
import usecssr from '../../_mixins/usecssr'
import styles from './styles/radio-group/index.js'
import { warn } from '../../_utils/naive/warn'
function mapSlot (h, defaultSlot, groupInstance) {
const mappedSlot = []
defaultSlot = defaultSlot || []
const children = []
let isButtonGroup = false
for (let i = 0; i < defaultSlot.length; ++i) {
const wrappedInstance = defaultSlot[i]
const instanceOptions = wrappedInstance.type
const name = instanceOptions.name
if (
__DEV__ && (
!instanceOptions ||
!['Radio', 'RadioButton'].includes(instanceOptions.name)
!['Radio', 'RadioButton'].includes(name)
)
) {
warn('radio-group', '`n-radio-group` only taks `n-radio` and `n-radio-button` as children.')
continue
}
const instanceProps = wrappedInstance.props
if (i === 0 || instanceOptions.name === 'Radio') {
mappedSlot.push(wrappedInstance)
if (name === 'RadioButton') {
isButtonGroup = true
}
if (i === 0 || name === 'Radio') {
children.push(wrappedInstance)
} else {
const lastInstanceProps = mappedSlot[mappedSlot.length - 1].props
const lastInstanceProps = children[children.length - 1].props
const lastInstanceChecked = groupInstance.$props.value === lastInstanceProps.value
const lastInstanceDisabled = lastInstanceProps.disabled
const currentInstanceChecked = groupInstance.$props.value === instanceProps.value
@ -63,16 +67,21 @@ function mapSlot (h, defaultSlot, groupInstance) {
'n-radio-group__splitor--disabled': currentInstanceDisabled,
'n-radio-group__splitor--checked': currentInstanceChecked
}
let splitorClass
if (lastInstancePriority < currentInstancePriority) splitorClass = currentInstanceClass
else splitorClass = lastInstanceClass
mappedSlot.push(h('div', {
staticClass: 'n-radio-group__splitor',
class: splitorClass
const splitorClass = lastInstancePriority < currentInstancePriority
? currentInstanceClass
: lastInstanceClass
children.push(h('div', {
class: [
'n-radio-group__splitor',
splitorClass
]
}), wrappedInstance)
}
}
return mappedSlot
return {
children,
isButtonGroup
}
}
export default {
@ -85,13 +94,9 @@ export default {
hollowoutable,
usecssr(styles),
asformitem({
change: 'change'
}, 'small')
defaultSize: 'medium'
})
],
model: {
prop: 'value',
event: 'change'
},
props: {
name: {
type: String,
@ -102,24 +107,35 @@ export default {
default: null
},
size: {
default: undefined,
validator (value) {
return ['small', 'medium', 'large'].includes(value)
}
},
default: undefined
},
disabled: {
type: Boolean,
default: false
},
'onUpdate:value': {
type: Function,
default: undefined
},
// deprecated
onChange: {
validator () {
if (__DEV__) warn('radio-group', '`on-change` is deprecated, please use `on-update:value` instead.')
return true
},
default: undefined
}
},
data () {
return {
radioButtonCount: 0,
transitionDisabled: true
}
},
mounted () {
if (this.radioButtonCount > 0) {
if (this.isButtonGroup) {
this.$nextTick().then(() => {
this.transitionDisabled = false
})
@ -127,12 +143,15 @@ export default {
},
provide () {
return {
NRadioGroup: this,
NFormItem: null
NRadioGroup: this
}
},
render () {
const isButtonGroup = this.radioButtonCount > 0
const {
children,
isButtonGroup
} = mapSlot(h, flatten(getSlot(this)), this)
this.isButtonGroup = isButtonGroup
return h('div', {
class: [
'n-radio-group',
@ -143,6 +162,6 @@ export default {
[`n-radio-group--transition-disabled`]: isButtonGroup && this.transitionDisabled
}
]
}, mapSlot(h, getDefaultSlot(this), this))
}, children)
}
}

View File

@ -1,3 +1,5 @@
import { warn } from '../../_utils/naive/warn'
export default {
props: {
name: {
@ -15,15 +17,22 @@ export default {
disabled: {
type: Boolean,
default: false
}
},
model: {
prop: 'checkedValue',
event: 'change'
},
inject: {
NRadioGroup: {
default: null
},
onClick: {
type: Function,
default: undefined
},
'onUpdate:checkedValue': {
type: Function,
default: undefined
},
// deprecated
onChange: {
validator () {
if (__DEV__) warn('radio', '`on-change` is deprecated, please use `on-update:checked-value` instead.')
return true
},
default: undefined
}
},
data () {
@ -36,13 +45,6 @@ export default {
if (this.name !== undefined) return this.name
if (this.NRadioGroup) return this.NRadioGroup.name
},
syntheticChecked () {
if (this.NRadioGroup) {
return this.NRadioGroup.value === this.value
} else {
return this.checkedValue === this.value
}
},
syntheticDisabled () {
if (this.NRadioGroup && this.NRadioGroup.disabled) return true
if (this.disabled) return true
@ -84,14 +86,38 @@ export default {
}, 0)
},
handleClick (e) {
this.$emit('click', e)
const {
onClick
} = this
if (onClick) onClick(e)
this.toggle()
},
emitChangeEvent () {
const {
value
} = this
if (this.NRadioGroup) {
this.NRadioGroup.$emit('change', this.value)
const {
onChange,
'onUpdate:value': updateValue,
__triggerFormInput,
__triggerFormChange
} = this.NRadioGroup
if (updateValue) updateValue(value)
if (onChange) onChange(value) // deprecated
__triggerFormInput()
__triggerFormChange()
} else {
this.$emit('change', this.value)
const {
onChange,
'onUpdate:checkedValue': updateCheckedValue,
__triggerFormInput,
__triggerFormChange
} = this
if (updateCheckedValue) updateCheckedValue(value)
if (onChange) onChange(value) // deprecated
__triggerFormInput()
__triggerFormChange()
}
}
}

View File

@ -0,0 +1,17 @@
import { inject, toRef } from 'vue'
import { useMemo } from '../../_utils/composition'
export default function setup (props) {
const NRadioGroup = inject('NRadioGroup', null)
return {
NRadioGroup,
renderSafeChecked: useMemo(() => {
if (NRadioGroup) return NRadioGroup.value === props.value
return props.checkedValue === props.value
}, [
NRadioGroup ? toRef(NRadioGroup, 'value') : null,
toRef(props, 'value'),
toRef(props, 'checkedValue')
])
}
}

11
vue3.md
View File

@ -155,7 +155,14 @@ placeable 进行了大调整
- set default trigger to `null`
- [ ] popselect
- [x] progress
- [ ] radio
- [x] radio
- radio-group
- break
- default `size` `'small'` => `'medium'`
- deprecate
- `on-change` => `on-update:value`
- radio & radio-button
- `on-change` => `on-update:checked-value`
- [x] result
- [ ] scrollbar
- [ ] select
@ -167,7 +174,7 @@ placeable 进行了大调整
- remove
- `value` => `model-value`
- `change` => `on-update:model-value`
- [ ] table
- [x] table
- [x] tabs
- deprecate
- `active-name` => `value`