diff --git a/demo/documentation/components/radio/zhCN/size.demo.md b/demo/documentation/components/radio/zhCN/size.demo.md
index 607083e62..dab710d42 100644
--- a/demo/documentation/components/radio/zhCN/size.demo.md
+++ b/demo/documentation/components/radio/zhCN/size.demo.md
@@ -2,7 +2,7 @@
任君挑选。
```html
-
+
-
+
禁用 Shakemaker
禁用 Live Forever
diff --git a/src/_utils/composition/index.js b/src/_utils/composition/index.js
index c8793a2ee..c018be045 100644
--- a/src/_utils/composition/index.js
+++ b/src/_utils/composition/index.js
@@ -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
diff --git a/src/_utils/vue/index.js b/src/_utils/vue/index.js
index c4d0c1b63..a79aa7025 100644
--- a/src/_utils/vue/index.js
+++ b/src/_utils/vue/index.js
@@ -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'
diff --git a/src/_utils/vue/src/flatten.js b/src/_utils/vue/src/flatten.js
new file mode 100644
index 000000000..d59a470b9
--- /dev/null
+++ b/src/_utils/vue/src/flatten.js
@@ -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
+}
diff --git a/src/radio/src/Radio.vue b/src/radio/src/Radio.vue
index b66321151..a2648ee60 100644
--- a/src/radio/src/Radio.vue
+++ b/src/radio/src/Radio.vue
@@ -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 @@
@@ -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
diff --git a/src/radio/src/RadioButton.vue b/src/radio/src/RadioButton.vue
index c25086df4..b51d88536 100644
--- a/src/radio/src/RadioButton.vue
+++ b/src/radio/src/RadioButton.vue
@@ -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 @@
diff --git a/src/radio/src/RadioGroup.js b/src/radio/src/RadioGroup.js
index 2fb1a9971..4a5fccb42 100644
--- a/src/radio/src/RadioGroup.js
+++ b/src/radio/src/RadioGroup.js
@@ -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)
}
}
diff --git a/src/radio/src/radio-mixin.js b/src/radio/src/radio-mixin.js
index 62c34a6a3..38bc7610a 100644
--- a/src/radio/src/radio-mixin.js
+++ b/src/radio/src/radio-mixin.js
@@ -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()
}
}
}
diff --git a/src/radio/src/radio-setup.js b/src/radio/src/radio-setup.js
new file mode 100644
index 000000000..b8383350c
--- /dev/null
+++ b/src/radio/src/radio-setup.js
@@ -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')
+ ])
+ }
+}
diff --git a/vue3.md b/vue3.md
index 20f55e7d8..8b1920205 100644
--- a/vue3.md
+++ b/vue3.md
@@ -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`