feat(cascader): add check-strategy prop

This commit is contained in:
07akioni 2021-09-05 12:36:42 +08:00
parent fc1fdba064
commit 8c39476046
29 changed files with 207 additions and 77 deletions

View File

@ -4,7 +4,8 @@
### Breaking Changes
- `n-tree-select`'s `leaf-only` props is deprecated, please use `check-strategy="child"` instead.
- `n-tree-select`'s `leaf-only` prop is deprecated, please use `check-strategy="child"` instead.
- `n-cascader`'s `leaf-only` prop is deprecated, please use `check-strategy="child"` instead.
- `n-input`'s `show-password-toggle` is deprecated, please use `show-password-on="click"` instead.
### Fixes
@ -26,6 +27,7 @@
- `n-dropdown` option add `props` prop, closes [#813](https://github.com/TuSimple/naive-ui/issues/813).
- `n-data-table` supports multi-selection by holding down `shift`, closes [#554](https://github.com/TuSimple/naive-ui/issues/554).
- `n-tree-select` add `check-strategy` prop, closes [#624](https://github.com/TuSimple/naive-ui/issues/624).
- `n-cascader` add `check-strategy` prop.
- `n-message` option add `keepAliveOnHover`, closes [#1036](https://github.com/TuSimple/naive-ui/issues/1036).
- `n-message-provider` add `keep-alive-on-hover` prop, closes [#1036](https://github.com/TuSimple/naive-ui/issues/1036).
- `n-upload` export `UploadFile` type.

View File

@ -5,6 +5,7 @@
### Breaking Changes
- `n-tree-select``leaf-only` 属性被废弃,请使用 `check-strategy="child"`
- `n-cascader``leaf-only` 属性被废弃,请使用 `check-strategy="child"`
- `n-input``show-password-toggle` 属性被废弃,请使用 `show-password-on="click"`
### Fixes
@ -26,6 +27,7 @@
- `n-dropdown` 选项新增 `props` 属性,关闭 [#813](https://github.com/TuSimple/naive-ui/issues/813)
- `n-data-table` 支持按住 `shift` 进行多选操作,关闭 [#554](https://github.com/TuSimple/naive-ui/issues/554)
- `n-tree-select` 增加 `check-strategy` 属性,关闭 [#624](https://github.com/TuSimple/naive-ui/issues/624)
- `n-cascader` 增加 `check-strategy` 属性
- `n-message` 选项增加 `keepAliveOnHover` 属性,关闭 [#1036](https://github.com/TuSimple/naive-ui/issues/1036).
- `n-message-provider` 新增 `keep-alive-on-hover` 属性,关闭 [#1036](https://github.com/TuSimple/naive-ui/issues/1036).
- `n-upload` 导出 `UploadFile` 类型

View File

@ -128,7 +128,7 @@
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"seemly": "^0.3.1",
"treemate": "^0.3.3",
"treemate": "^0.3.4",
"vdirs": "^0.1.4",
"vfonts": "^0.1.0",
"vooks": "^0.2.6",

View File

@ -4,6 +4,7 @@ export function warnOnce (location: string, message: string): void {
const mergedMessage = `[naive/${location}]: ${message}`
if (warnedMessages.has(mergedMessage)) return
warnedMessages.add(mergedMessage)
console.error(mergedMessage)
}
export function warn (location: string, message: string): void {

View File

@ -51,7 +51,6 @@ const backTopProps = {
listenTo: [String, Object, Function] as PropType<
string | HTMLElement | (() => HTMLElement)
>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:show': {
type: Function,
default: () => {}
@ -69,7 +68,7 @@ const backTopProps = {
default: undefined
},
onShow: {
type: (Function as unknown) as PropType<(() => void) | undefined>,
type: Function as unknown as PropType<(() => void) | undefined>,
validator: () => {
warn(
'back-top',
@ -80,7 +79,7 @@ const backTopProps = {
default: undefined
},
onHide: {
type: (Function as unknown) as PropType<(() => void) | undefined>,
type: Function as unknown as PropType<(() => void) | undefined>,
validator: () => {
warn(
'back-top',
@ -117,15 +116,17 @@ export default defineComponent({
const mergedShowRef = useMergedState(controlledShowRef, uncontrolledShowRef)
const transitionDisabledRef = ref(true)
const placeholderRef = ref<HTMLElement | null>(null)
const styleRef = computed((): {
right: string
bottom: string
} => {
return {
right: formatLength(props.right),
bottom: formatLength(props.bottom)
const styleRef = computed(
(): {
right: string
bottom: string
} => {
return {
right: formatLength(props.right),
bottom: formatLength(props.bottom)
}
}
})
)
let scrollElement: HTMLElement
let scrollListenerRegistered: boolean
// deprecated
@ -174,7 +175,7 @@ export default defineComponent({
}
function handleClick (e: MouseEvent): void {
if (scrollElement.nodeName === '#document') {
;((scrollElement as unknown) as Document).documentElement.scrollTo({
;(scrollElement as unknown as Document).documentElement.scrollTo({
top: 0,
behavior: 'smooth'
})
@ -187,7 +188,9 @@ export default defineComponent({
}
function handleScroll (): void {
if (scrollElement.nodeName === '#document') {
scrollTopRef.value = ((scrollElement as unknown) as Document).documentElement.scrollTop
scrollTopRef.value = (
scrollElement as unknown as Document
).documentElement.scrollTop
} else {
scrollTopRef.value = scrollElement.scrollTop
}
@ -285,8 +288,8 @@ export default defineComponent({
class: [
`${mergedClsPrefix}-back-top`,
{
[`${mergedClsPrefix}-back-top--transition-disabled`]: this
.transitionDisabled
[`${mergedClsPrefix}-back-top--transition-disabled`]:
this.transitionDisabled
}
],
style: {
@ -301,9 +304,9 @@ export default defineComponent({
'default',
undefined,
() => [
<NBaseIcon clsPrefix={mergedClsPrefix}>
{{ default: () => BackTopIcon }}
</NBaseIcon>
<NBaseIcon clsPrefix={mergedClsPrefix}>
{{ default: () => BackTopIcon }}
</NBaseIcon>
]
)
]

View File

@ -0,0 +1,68 @@
# Set Check Strategy
Set the way to show checked options. `all` means showing all checked nodes. `parent` means showing all checked parent nodes when all child node are checked. `child` means showing all child nodes.
```html
<n-space vertical>
<n-space>
<n-radio-group v-model:value="checkStrategy">
<n-radio-button value="all">All</n-radio-button>
<n-radio-button value="parent">Parent</n-radio-button>
<n-radio-button value="child">Child</n-radio-button>
</n-radio-group>
</n-space>
<n-cascader
multiple
cascade
:check-strategy="checkStrategy"
:options="options"
:default-value="['1-1-1-1', '1-1-2-1', '1-1-2-2', '1-1-2-3']"
@update:value="handleUpdateValue"
/>
</n-space>
```
```js
import { defineComponent, ref } from 'vue'
function getOptions (depth = 4, iterator = 1, prefix = '') {
const length = 3
const options = []
for (let i = 1; i <= length; ++i) {
if (iterator === 1) {
options.push({
value: `${i}`,
label: `${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, '' + i)
})
} else if (iterator === depth) {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0
})
} else {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${prefix}-${i}`)
})
}
}
return options
}
export default defineComponent({
setup () {
return {
checkStrategy: ref('all'),
options: getOptions(),
handleUpdateValue: (values) => {
console.log(values)
}
}
}
})
```

View File

@ -12,6 +12,7 @@ single-lazy
multiple-lazy
action
virtual
check-strategy
```
## Props
@ -19,13 +20,13 @@ virtual
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| cascade | `boolean` | `true` | Whether to cascade the checkbox selection onto children. |
| check-strategy | `string` | `'all'` | The way to show checked options. `all` means showing all checked node. `parent` means showing all checked parent node when all child node are checked. `child` means showing all child node. |
| clearable | `boolean` | `false` | Whether the cascader is clearable. |
| default-value | `string \| number \| Array<number \| string> \| null` | `null` | Data selected by default if no value is set. |
| disabled | `boolean` | `false` | Whether to disable the cascader. |
| expand-trigger | `'click' \| 'hover'` | `'click'` | If `remote` is set, `'hover'` won't work. |
| filterable | `boolean` | `false` | Note: If `remote` is set, this won't have any effect. |
| filter | `(pattern: string, option: CascaderOption, path: Array<CascaderOption>) => boolean` | A string based filter algorithm. | Filter function of the cascader. |
| leaf-only | `boolean` | `false` | If only a leaf node can be selected `value`. |
| max-tag-count | `number \| 'responsive'` | `undefined` | Max tag count in multiple select mode. `responsive` will keep all the tags in single line. |
| multiple | `boolean` | `false` | Whether to allow multiple options being selected. |
| options | `Array<CascaderOption>` | required | Options of the cascader. |

View File

@ -0,0 +1,68 @@
# 指定勾选策略
设置勾选策略来指定显示的勾选节点,`all` 表示显示全部选中节点;`parent` 表示只显示父节点(当父节点下所有子节点都选中时);`child` 表示只显示子节点。
```html
<n-space vertical>
<n-space>
<n-radio-group v-model:value="checkStrategy">
<n-radio-button value="all">All</n-radio-button>
<n-radio-button value="parent">Parent</n-radio-button>
<n-radio-button value="child">Child</n-radio-button>
</n-radio-group>
</n-space>
<n-cascader
multiple
cascade
:check-strategy="checkStrategy"
:options="options"
:default-value="['1-1-1-1', '1-1-2-1', '1-1-2-2', '1-1-2-3']"
@update:value="handleUpdateValue"
/>
</n-space>
```
```js
import { defineComponent, ref } from 'vue'
function getOptions (depth = 4, iterator = 1, prefix = '') {
const length = 3
const options = []
for (let i = 1; i <= length; ++i) {
if (iterator === 1) {
options.push({
value: `${i}`,
label: `${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, '' + i)
})
} else if (iterator === depth) {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0
})
} else {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${prefix}-${i}`)
})
}
}
return options
}
export default defineComponent({
setup () {
return {
checkStrategy: ref('all'),
options: getOptions(),
handleUpdateValue: (values) => {
console.log(values)
}
}
}
})
```

View File

@ -12,6 +12,7 @@ single-lazy
multiple-lazy
action
virtual
check-strategy
```
## Props
@ -19,13 +20,13 @@ virtual
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| cascade | `boolean` | `true` | 在多选时是否关联选项 |
| check-strategy | `string` | `'all'` | 设置勾选策略来指定显示的勾选节点,`all` 表示显示全部选中节点;`parent` 表示只显示父节点(当父节点下所有子节点都选中时);`child` 表示只显示子节点 |
| clearable | `boolean` | `false` | 值是否可清除 |
| default-value | `string \| number \| Array<number \| string> \| null` | `null` | 级联菜单默认选中的数据 |
| disabled | `boolean` | `false` | 是否禁用 |
| expand-trigger | `'click' \| 'hover'` | `'click'` | 在 `remote` 被设定时 `'hover'` 不生效 |
| filterable | `boolean` | `false` | `remote` 被设定时不生效 |
| filter | `(pattern: string, option: CascaderOption, path: Array<CascaderOption>) => boolean` | 一个基于字符串的过滤算法 | 过滤选项的函数 |
| leaf-only | `boolean` | `false` | 是否只允许 `value` 出现叶节点的值 |
| max-tag-count | `number \| 'responsive'` | `undefined` | 多选标签的最大显示数量,`responsive` 会将所有标签保持在一行 |
| multiple | `boolean` | `false` | 是否支持多选 |
| options | `Array<CascaderOption>` | `[]` | 填充的 options 数据 |

View File

@ -8,9 +8,10 @@ import {
watch,
toRef,
CSSProperties,
isReactive
isReactive,
watchEffect
} from 'vue'
import { createTreeMate, SubtreeNotLoadedError } from 'treemate'
import { createTreeMate, SubtreeNotLoadedError, CheckStrategy } from 'treemate'
import {
VBinder,
VTarget,
@ -23,7 +24,7 @@ import { useIsMounted, useMergedState } from 'vooks'
import { NInternalSelection, InternalSelectionInst } from '../../_internal'
import { useLocale, useTheme, useConfig, useFormItem } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { warn, call, useAdjustedTo } from '../../_utils'
import { call, useAdjustedTo, warnOnce } from '../../_utils'
import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils'
import { cascaderLight } from '../styles'
import type { CascaderTheme } from '../styles'
@ -103,23 +104,16 @@ const cascaderProps = {
type: Boolean,
default: true
},
// eslint-disable-next-line vue/prop-name-casing
checkStrategy: {
type: String as PropType<CheckStrategy>,
default: 'all'
},
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
// deprecated
onChange: {
type: [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>,
validator: () => {
warn(
'cascader',
'`on-change` is deprecated, please use `on-update:value` instead.'
)
return true
},
default: undefined
},
onBlur: Function as PropType<(e: FocusEvent) => void>,
onFocus: Function as PropType<(e: FocusEvent) => void>
onFocus: Function as PropType<(e: FocusEvent) => void>,
// deprecated
onChange: [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>
} as const
export type CascaderProps = ExtractPublicPropTypes<typeof cascaderProps>
@ -129,6 +123,22 @@ export default defineComponent({
name: 'Cascader',
props: cascaderProps,
setup (props) {
if (__DEV__) {
watchEffect(() => {
if (props.leafOnly) {
warnOnce(
'cascader',
'`leaf-only` is deprecated, please use `check-strategy="child"` instead'
)
}
if (props.onChange !== undefined) {
warnOnce(
'cascader',
'`on-change` is deprecated, please use `on-update:value` instead.'
)
}
})
}
const { mergedBorderedRef, mergedClsPrefixRef, namespaceRef } =
useConfig(props)
const themeRef = useTheme(
@ -176,7 +186,8 @@ export default defineComponent({
const { cascade, multiple } = props
if (multiple && Array.isArray(mergedValueRef.value)) {
return treeMateRef.value.getCheckedKeys(mergedValueRef.value, {
cascade
cascade,
checkStrategy: props.leafOnly ? 'child' : props.checkStrategy
})
} else {
return {
@ -252,7 +263,7 @@ export default defineComponent({
mergedKeysRef.value.checkedKeys,
{
cascade,
leafOnly
checkStrategy: leafOnly ? 'child' : props.checkStrategy
}
)
doUpdateValue(checkedKeys)
@ -291,7 +302,7 @@ export default defineComponent({
mergedKeysRef.value.checkedKeys,
{
cascade,
leafOnly
checkStrategy: leafOnly ? 'child' : props.checkStrategy
}
)
doUpdateValue(checkedKeys)
@ -300,7 +311,7 @@ export default defineComponent({
const selectedOptionsRef = computed(() => {
if (props.multiple) {
const { showPath, separator } = props
const { value } = mergedValueRef
const { value } = checkedKeysRef
if (Array.isArray(value)) {
const { getNode } = treeMateRef.value
return value.map((key) => {

View File

@ -46,7 +46,6 @@ const checkboxProps = {
type: Boolean,
default: true
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:checked': [Function, Array] as PropType<
MaybeArray<(value: boolean, e: MouseEvent | KeyboardEvent) => void>
>,

View File

@ -41,7 +41,6 @@ const checkboxGroupProps = {
type: Boolean as PropType<boolean | undefined>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: Array<string | number>) => void>
>,

View File

@ -53,7 +53,6 @@ const collapseProps = {
onItemHeaderClick: [Function, Array] as PropType<
MaybeArray<OnItemHeaderClick>
>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:expandedNames': [Function, Array] as PropType<
MaybeArray<OnUpdateExpandedNames>
>,

View File

@ -55,7 +55,6 @@ const drawerProps = {
},
scrollbarProps: Object as PropType<ScrollbarProps>,
contentStyle: [Object, String] as PropType<string | CSSProperties>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:show': [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,

View File

@ -66,7 +66,6 @@ const dynamicInputProps = {
},
onCreate: Function as PropType<(index: number) => any>,
onRemove: Function as PropType<(index: number) => void>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
// deprecated

View File

@ -15,7 +15,6 @@ export default defineComponent({
},
parentPath: String,
path: String,
// eslint-disable-next-line vue/prop-name-casing
onUpdateValue: {
type: Function as PropType<(value: string) => void>,
required: true

View File

@ -40,7 +40,6 @@ const dynamicTagsProps = {
inputStyle: [String, Object] as PropType<string | CSSProperties>,
max: Number as PropType<number>,
tagStyle: [String, Object] as PropType<string | CSSProperties>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
// deprecated

View File

@ -107,7 +107,6 @@ const inputProps = {
onClick: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
onChange: [Function, Array] as PropType<OnUpdateValue>,
onClear: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
/** private */

View File

@ -73,7 +73,6 @@ const layoutSiderProps = {
Partial<ScrollbarProps> & { style: CSSProperties }
>,
triggerStyle: [String, Object] as PropType<string | CSSProperties>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:collapsed': [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,

View File

@ -45,7 +45,6 @@ const modalProps = {
},
...presetProps,
// events
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:show': [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,

View File

@ -113,7 +113,6 @@ const radioGroupProps = {
type: Boolean as PropType<boolean | undefined>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': Function as PropType<(value: string | number) => void>,
onUpdateValue: Function as PropType<(value: string | number) => void>,
// deprecated

View File

@ -37,7 +37,6 @@ const rateProps = {
default: 'medium'
},
color: String,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: number) => void>
>,

View File

@ -39,7 +39,6 @@ const switchProps = {
type: Boolean,
default: true
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,

View File

@ -64,7 +64,6 @@ const tabsProps = {
default: 0
},
onAdd: Function as PropType<() => void>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onClose: [Function, Array] as PropType<MaybeArray<OnClose>>,

View File

@ -99,7 +99,6 @@ const timePickerProps = {
isMinuteDisabled: Function as PropType<IsMinuteDisabled>,
isSecondDisabled: Function as PropType<IsSecondDisabled>,
clearable: Boolean,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onBlur: [Function, Array] as PropType<MaybeArray<(e: FocusEvent) => void>>,

View File

@ -46,16 +46,10 @@ const transferProps = {
type: Boolean as PropType<boolean | undefined>,
default: undefined
},
virtualScroll: {
type: Boolean,
default: false
},
virtualScroll: Boolean,
sourceTitle: String,
targetTitle: String,
filterable: {
type: Boolean,
default: false
},
filterable: Boolean,
sourceFilterPlaceholder: String,
targetFilterPlaceholder: String,
filter: {
@ -67,11 +61,7 @@ const transferProps = {
.indexOf(('' + pattern).toLowerCase())
}
},
size: {
type: String as PropType<'small' | 'medium' | 'large' | undefined>,
default: undefined
},
// eslint-disable-next-line vue/prop-name-casing
size: String as PropType<'small' | 'medium' | 'large'>,
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onChange: {

View File

@ -22,7 +22,7 @@ import {
} from 'vueuc'
import { useIsMounted, useMergedState } from 'vooks'
import { clickoutside } from 'vdirs'
import { createTreeMate } from 'treemate'
import { createTreeMate, CheckStrategy } from 'treemate'
import { Key, InternalTreeInst } from '../../tree/src/interface'
import type { SelectBaseOption } from '../../select/src/interface'
import { createTreeMateOptions, treeSharedProps } from '../../tree/src/Tree'
@ -49,7 +49,7 @@ import type {
TreeSelectOption,
Value
} from './interface'
import { treeSelectInjectionKey, CheckStrategy } from './interface'
import { treeSelectInjectionKey } from './interface'
import {
treeOption2SelectOption,
filterTree,

View File

@ -41,5 +41,3 @@ export interface TreeSelectInjection {
export const treeSelectInjectionKey: InjectionKey<TreeSelectInjection> =
Symbol('tree-select')
export type CheckStrategy = 'all' | 'parent' | 'child'

View File

@ -17,7 +17,8 @@ import {
flatten,
createIndexGetter,
TreeMate,
TreeMateOptions
TreeMateOptions,
CheckStrategy
} from 'treemate'
import { useMergedState } from 'vooks'
import { VirtualListInst, VVirtualList } from 'vueuc'
@ -28,7 +29,6 @@ import { call, createDataKey, warn } from '../../_utils'
import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils'
import { NxScrollbar } from '../../scrollbar'
import type { ScrollbarInst } from '../../scrollbar'
import { CheckStrategy } from '../../tree-select/src/interface'
import { treeLight } from '../styles'
import type { TreeTheme } from '../styles'
import NTreeNode from './TreeNode'