Merge pull request #1405 from TuSimple/main

sync main
This commit is contained in:
07akioni 2021-10-21 01:25:54 +08:00 committed by GitHub
commit 7ab5e0b32a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 372 additions and 41 deletions

View File

@ -1,5 +1,15 @@
# CHANGELOG
## 2.19.11 (2021-10-21)
## Fixes
- Fix `n-upload`'s file can't be removed when file count limit is reached, closes [#1401](https://github.com/TuSimple/naive-ui/issues/1401).
### Feats
- `n-tabs` add `on-before-leave` prop, closes [#1337](https://github.com/TuSimple/naive-ui/issues/1337).
## 2.19.9 (2021-10-18)
### Fixes

View File

@ -1,10 +1,20 @@
# CHANGELOG
## 2.19.11 (2021-10-21)
## Fixes
- 修复 `n-upload` 在达到最大文件数量后无法删除文件,关闭 [#1401](https://github.com/TuSimple/naive-ui/issues/1401)
### Feats
- `n-tabs` 新增 `on-before-leave` 属性,关闭 [#1337](https://github.com/TuSimple/naive-ui/issues/1337)
## 2.19.9 (2021-10-18)
### Fixes
- 修复 `n-collapse``n-collapse-item` 使用 `v-if` 是展开状态丢失,关闭 [#1387](https://github.com/TuSimple/naive-ui/issues/1387)
- 修复 `n-collapse``n-collapse-item` 使用 `v-if` 展开状态丢失,关闭 [#1387](https://github.com/TuSimple/naive-ui/issues/1387)
- 修复 `n-dialog` 的关闭按钮会被内容遮盖,关闭 [#1381](https://github.com/TuSimple/naive-ui/issues/1381)
- 修复 `n-upload` 上传失败后重试时文件为 `null`,关闭 [#1316](https://github.com/TuSimple/naive-ui/issues/1316)
- 修复 `n-cascader``filter` 属性不生效

View File

@ -1,6 +1,6 @@
{
"name": "naive-ui",
"version": "2.19.9",
"version": "2.19.11",
"description": "A Vue 3 Component Library. Fairly Complete, Customizable Themes, Uses TypeScript, Not Too Slow",
"main": "lib/index.js",
"module": "es/index.js",
@ -96,7 +96,6 @@
"eslint-plugin-markdown": "^2.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^7.6.0",
"express": "^4.17.1",
"fs-extra": "^10.0.0",

View File

@ -59,6 +59,36 @@ describe('n-carousel', () => {
}
})
it('should work with `interval` prop', async () => {
const wrapper = mount(NCarousel, {
props: {
interval: 100,
autoplay: true
},
slots: {
default: () => {
return [...Array(3).keys()].map((i) => {
return h('div', {}, i.toString())
})
}
}
})
await sleep(100)
expect(
wrapper
.find('.n-carousel__slides')
.find('[data-index="2"]')
.attributes('aria-hidden')
).toBe('false')
expect(
wrapper
.find('.n-carousel__dots')
.findAll('.n-carousel__dot')[1]
.attributes('aria-selected')
).toBe('true')
})
it('should work with `showArrow` prop', async () => {
const wrapper = mount(NCarousel)
@ -113,20 +143,18 @@ describe('n-carousel', () => {
expect(slidesDOMArray[1].attributes('aria-hidden')).toBe('false')
wrapper
.find('.n-carousel__arrow--right')
await wrapper.find('.n-carousel__arrow--right').trigger('click')
expect(slidesDOMArray[2].attributes('aria-hidden')).toBe('false')
await sleep(1000)
await nextTick()
void wrapper
.find('.n-carousel__arrow--left')
.trigger('click')
.then(async () => {
expect(slidesDOMArray[2].attributes('aria-hidden')).toBe('false')
await sleep(1000)
nextTick(() => {
wrapper
.find('.n-carousel__arrow--left')
.trigger('click')
.then(() => {
expect(slidesDOMArray[1].attributes('aria-hidden')).toBe('false')
})
})
.then(() => {
expect(slidesDOMArray[1].attributes('aria-hidden')).toBe('false')
})
})
})

View File

@ -21,6 +21,50 @@ describe('n-collapse', () => {
expect(wrapper.find('.my-icon').exists()).toEqual(true)
})
it('should work with `accordion` prop', async () => {
const wrapper = mount(NCollapse, {
slots: {
default: () => [
<NCollapseItem name="1">
{{ default: () => <div class="ci1">ci1</div> }}
</NCollapseItem>,
<NCollapseItem name="2">
{{ default: () => <div class="ci2">ci2</div> }}
</NCollapseItem>,
<NCollapseItem name="3">
{{ default: () => <div class="ci3">ci3</div> }}
</NCollapseItem>
]
}
})
const headerMains = wrapper.findAll('.n-collapse-item__header-main')
await headerMains[0].trigger('click')
await headerMains[1].trigger('click')
expect(wrapper.findAll('.n-collapse-item__header')[0].classes()).toContain(
'n-collapse-item__header--active'
)
expect(wrapper.findAll('.n-collapse-item__header')[1].classes()).toContain(
'n-collapse-item__header--active'
)
await wrapper.setProps({
accordion: true
})
await headerMains[2].trigger('click')
expect(
wrapper.findAll('.n-collapse-item__header')[0].classes()
).not.toContain('n-collapse-item__header--active')
expect(
wrapper.findAll('.n-collapse-item__header')[1].classes()
).not.toContain('n-collapse-item__header--active')
expect(wrapper.findAll('.n-collapse-item__header')[2].classes()).toContain(
'n-collapse-item__header--active'
)
})
it('should work with `arrow-placement` prop', async () => {
const wrapper = mount(NCollapse, {
slots: {
@ -38,28 +82,40 @@ describe('n-collapse', () => {
})
it('should work with nested structure', async () => {
mount(NCollapse, {
const wrapper = mount(NCollapse, {
slots: {
default: () =>
h(
NCollapseItem,
{ name: '1', title: 'test1' },
{ name: '1' },
{
default: () =>
h(NCollapse, null, {
default: () => h(NCollapseItem, { name: '2', title: 'test2' })
default: () => h(NCollapseItem, { name: '2' })
})
}
)
}
})
// todo: test display-directive
// I wanted to test this function, but I was bothered by the <transition-stub>
await wrapper.find('.n-collapse-item__header-main').trigger('click')
expect(wrapper.find('.n-collapse-item__header').classes()).toContain(
'n-collapse-item__header--active'
)
await wrapper
.find('.n-collapse-item__content-wrapper')
.find('.n-collapse-item__header-main')
.trigger('click')
expect(
wrapper
.find('.n-collapse-item__content-wrapper')
.find('.n-collapse-item__header')
.classes()
).toContain('n-collapse-item__header--active')
})
it('should work with `display-directive` prop', async () => {
mount(NCollapse, {
const wrapper = mount(NCollapse, {
props: {
displayDirective: 'show'
},
@ -67,13 +123,27 @@ describe('n-collapse', () => {
default: () =>
h(
NCollapseItem,
{ name: '1', title: 'test' },
{ name: '1' },
{ default: () => h('div', null, { default: () => 'test' }) }
)
}
// todo: test display-directive
// I wanted to test this function, but I was bothered by the <transition-stub>
})
await wrapper.find('.n-collapse-item__header-main').trigger('click')
await wrapper.find('.n-collapse-item__header-main').trigger('click')
expect(
wrapper.find('.n-collapse-item__content-wrapper').attributes('style')
).toBe('display: none;')
await wrapper.setProps({
displayDirective: 'if'
})
await wrapper.find('.n-collapse-item__header-main').trigger('click')
await wrapper.find('.n-collapse-item__header-main').trigger('click')
expect(wrapper.find('.n-collapse-item__content-wrapper').exists()).toBe(
false
)
})
it('should work with `on-item-header-click` prop', async () => {
@ -149,4 +219,15 @@ describe('n-collapse', () => {
expect(wrapper.find('.ci1').isVisible()).toEqual(true)
expect(wrapper.find('.ci2').exists()).toEqual(false)
})
it('should work with collapseItem component `title` prop', async () => {
const wrapper = mount(NCollapse, {
slots: {
default: () => <NCollapseItem title="test"></NCollapseItem>
}
})
await wrapper.find('.n-collapse-item__header-main').trigger('click')
expect(wrapper.find('.n-collapse-item__header-main').text()).toBe('test')
})
})

View File

@ -1087,4 +1087,26 @@ describe('props.columns', () => {
'test-age'
)
})
it('should work with `render` prop', async () => {
const columns: DataTableColumns = [
{
title: 'Name',
key: 'name',
render (rowData: any, rowIndex: number) {
return `${String(rowData.name)}-${rowIndex}`
}
}
]
const data = new Array(5).fill(0).map((_, index) => {
return {
name: index
}
})
const rowKey = (row: any): number => row.name
const wrapper = mount(() => (
<NDataTable columns={columns} data={data} row-key={rowKey} />
))
expect(wrapper.find('tbody [data-col-key="name"]').text()).toContain('0-0')
})
})

View File

@ -206,6 +206,8 @@ export default defineComponent({
return localeRef.value.datePlaceholder
} else if (props.type === 'datetime') {
return localeRef.value.datetimePlaceholder
} else if (props.type === 'month') {
return localeRef.value.monthPlaceholder
}
return props.placeholder
} else {

View File

@ -129,6 +129,7 @@ describe('n-descriptions', () => {
expect(wrapper.find('.n-descriptions-header').exists()).toBe(true)
expect(wrapper.find('.n-descriptions-header').text()).toBe('test')
})
it('should work with `separator` prop', async () => {
const wrapper = mount(NDescriptions, {
props: {
@ -141,4 +142,34 @@ describe('n-descriptions', () => {
await wrapper.setProps({ separator: '/' })
expect(wrapper.find('.n-descriptions-separator').text()).toEqual('/')
})
it('should work with `content-style` prop', () => {
const wrapper = mount(NDescriptions, {
props: {
contentStyle: { backgroundColor: 'red' }
},
slots: {
default: () => h(NDescriptionsItem, {}, 'test')
}
})
expect(
wrapper.find('.n-descriptions-table-content').attributes('style')
).toBe('background-color: red;')
})
it('should work with `label-style` prop', () => {
const wrapper = mount(NDescriptions, {
props: {
labelStyle: { fontSize: '30px' }
},
slots: {
default: () => h(NDescriptionsItem, {}, 'test')
}
})
expect(
wrapper.find('.n-descriptions-table-header').attributes('style')
).toBe('font-size: 30px;')
})
})

View File

@ -30,6 +30,7 @@ const enUS = {
selectDate: 'Select Date',
datePlaceholder: 'Select Date',
datetimePlaceholder: 'Select Date and Time',
monthPlaceholder: 'Select Month',
startDatePlaceholder: 'Start Date',
endDatePlaceholder: 'End Date',
startDatetimePlaceholder: 'Start Date and Time',

View File

@ -32,6 +32,7 @@ const jaJP: NLocale = {
selectDate: '日付を選択',
datePlaceholder: '日付を選択',
datetimePlaceholder: '選択',
monthPlaceholder: '月を選択',
startDatePlaceholder: '開始日',
endDatePlaceholder: '終了日',
startDatetimePlaceholder: '開始時間',

View File

@ -32,6 +32,7 @@ const ruRu: NLocale = {
selectDate: 'Выбрать дату',
datePlaceholder: 'Выбрать дату',
datetimePlaceholder: 'Выбрать дату и время',
monthPlaceholder: 'Выберите месяц',
startDatePlaceholder: 'Дата начала',
endDatePlaceholder: 'Дата окончания',
startDatetimePlaceholder: 'Дата и время начала',

View File

@ -32,6 +32,7 @@ const ukUA: NLocale = {
selectDate: 'Обрати дату',
datePlaceholder: 'Обрати дату',
datetimePlaceholder: 'Обрати дату і час',
monthPlaceholder: 'Виберіть місяць',
startDatePlaceholder: 'Дата початку',
endDatePlaceholder: 'Дата завершення',
startDatetimePlaceholder: 'Дата і час початку',

View File

@ -32,6 +32,7 @@ const zhCN: NLocale = {
selectDate: '选择日期',
datePlaceholder: '选择日期',
datetimePlaceholder: '选择日期时间',
monthPlaceholder: '选择月份',
startDatePlaceholder: '开始日期',
endDatePlaceholder: '结束日期',
startDatetimePlaceholder: '开始日期时间',

View File

@ -0,0 +1,49 @@
# Hook before tab switching
You can prevent or postpone tab switching.
```html
<n-tabs
type="line"
default-value="okay"
@before-leave="handleBeforeLeave"
@update:value="handleUpdateValue"
>
<n-tab-pane name="wait" tab="Wait for 1s"> +1s </n-tab-pane>
<n-tab-pane name="not-allowed" tab="Not allowed"> ??? </n-tab-pane>
<n-tab-pane name="okay" tab="Okay"> Just so so </n-tab-pane>
</n-tabs>
```
```js
import { defineComponent } from 'vue'
import { useMessage } from 'naive-ui'
export default defineComponent({
setup () {
const message = useMessage()
return {
handleBeforeLeave: (tabName) => {
switch (tabName) {
case 'not-allowed':
message.error('Not allowed')
return false
case 'wait':
return new Promise((resolve) => {
const messageInstance = message.loading('Wait for 1s')
setTimeout(() => {
messageInstance.destroy()
resolve(true)
}, 1000)
})
default:
return true
}
},
handleUpdateValue: (value) => {
message.info(value)
}
}
}
})
```

View File

@ -1,4 +1,4 @@
# Card Type
# Card type
Set `type='card'`.

View File

@ -1,4 +1,4 @@
# Display Directive
# Display directive
You can set tab-panel's display directive to `if` or `show`. When use show, the tab-panel's content won't be reset after tab changes.

View File

@ -1,4 +1,4 @@
# Flex Tabs
# Flex tabs
Only works with `line` typed tabs.

View File

@ -14,6 +14,7 @@ prefix
display-directive
addable
line-debug
before-leave
```
## API
@ -33,6 +34,7 @@ line-debug
| type | `'bar' \| 'line' \| 'card' \| 'segment'` | `'bar'` | Tabs type. |
| value | `string \| number` | `undefined` | Value in controlled mode. |
| on-add | `() => void` | `undefined` | Callback function triggered when add tag. |
| on-before-leave | `(activeName: string \| number, oldActiveName: string \| number \| null) => boolean \| Promise<boolean>` | `undefined` | Hook function before switching tab. Returning `false` or promise resolving `false` or promise rejection will prevent tab switching. |
| on-close | `(name: string \| number) => void` | `undefined` | Callback function triggered when close tag. |
| on-update:value | `(value: string \| number) => void` | `undefined` | Callback function triggered when the value changes. |

View File

@ -1,4 +1,4 @@
# Line Debug
# Line debug
```html
<n-button @click="name = 'the beatles'">Set Name to the Beatles</n-button>

View File

@ -1,4 +1,4 @@
# Prefix & Suffix
# Prefix & suffix
Use `prefix` & `suffix` slot to add prefix & suffix.

View File

@ -0,0 +1,49 @@
# 切换 Tab 前的回调
你可以延迟或阻止 Tab 切换。
```html
<n-tabs
type="line"
default-value="okay"
@before-leave="handleBeforeLeave"
@update:value="handleUpdateValue"
>
<n-tab-pane name="wait" tab="等 1 秒"> +1s </n-tab-pane>
<n-tab-pane name="not-allowed" tab="不许进来"> ??? </n-tab-pane>
<n-tab-pane name="okay" tab="可以">就那么回事吧</n-tab-pane>
</n-tabs>
```
```js
import { defineComponent } from 'vue'
import { useMessage } from 'naive-ui'
export default defineComponent({
setup () {
const message = useMessage()
return {
handleBeforeLeave: (tabName) => {
switch (tabName) {
case 'not-allowed':
message.error('不许进来')
return false
case 'wait':
return new Promise((resolve) => {
const messageInstance = message.loading('Wait for 1s')
setTimeout(() => {
messageInstance.destroy()
resolve(true)
}, 1000)
})
default:
return true
}
},
handleUpdateValue: (value) => {
message.info(value)
}
}
}
})
```

View File

@ -16,6 +16,7 @@ addable
line-debug
style-inherit-debug
shadow-debug
before-leave
```
## API
@ -35,6 +36,7 @@ shadow-debug
| type | `'bar' \| 'line' \| 'card' \| 'segment'` | `'bar'` | 标签类型 |
| value | `string \| number` | `undefined` | 受控模式下的值 |
| on-add | `() => void` | `undefined` | 添加标签的回调函数 |
| on-before-leave | `(name: string \| number, oldName: string \| number \| null) => boolean \| Promise<boolean>` | `undefined` | 切换标签之前的钩子函数,返回 `false` 或 promise resolve `false` 或 promise reject 会组织切换 |
| on-close | `(name: string \| number) => void` | `undefined` | 关闭标签的回调函数 |
| on-update:value | `(value: string \| number) => void` | `undefined` | 选中发生改变时的回调函数 |

View File

@ -2,7 +2,7 @@ import { h, defineComponent, inject, computed } from 'vue'
import { AddIcon } from '../../_internal/icons'
import { NBaseClose, NBaseIcon } from '../../_internal'
import { render } from '../../_utils'
import { tabsInjectionKey } from './interface'
import { OnBeforeLeaveImpl, tabsInjectionKey } from './interface'
import { tabPaneProps } from './TabPane'
export default defineComponent({
@ -21,6 +21,8 @@ export default defineComponent({
typeRef,
closableRef,
tabStyleRef,
nextTabNameRef,
onBeforeLeaveRef,
handleAdd,
handleTabClick,
handleClose
@ -48,7 +50,23 @@ export default defineComponent({
handleAdd()
return
}
handleTabClick(props.name)
const { name: nameProp } = props
if (nameProp !== valueRef.value) {
if (nameProp === nextTabNameRef.value) return
nextTabNameRef.value = nameProp
const { value: onBeforeLeave } = onBeforeLeaveRef
if (!onBeforeLeave) {
handleTabClick(nameProp)
} else {
void Promise.resolve(
(onBeforeLeave as OnBeforeLeaveImpl)(props.name, valueRef.value)
).then((allowLeave) => {
if (allowLeave) {
handleTabClick(nameProp)
}
})
}
}
}
}
},

View File

@ -28,6 +28,7 @@ import {
Addable,
OnClose,
OnCloseImpl,
OnBeforeLeave,
tabsInjectionKey,
TabsType
} from './interface'
@ -58,6 +59,7 @@ const tabsProps = {
type: Number,
default: 0
},
onBeforeLeave: Function as PropType<OnBeforeLeave>,
onAdd: Function as PropType<() => void>,
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
@ -136,6 +138,8 @@ export default defineComponent({
uncontrolledValueRef
)
const nextTabNameRef = { value: mergedValueRef.value }
const tabWrapperStyleRef = computed(() => {
if (!props.justifyContent || props.type === 'card') return undefined
return {
@ -275,6 +279,8 @@ export default defineComponent({
typeRef: toRef(props, 'type'),
closableRef: toRef(props, 'closable'),
valueRef: mergedValueRef,
nextTabNameRef,
onBeforeLeaveRef: toRef(props, 'onBeforeLeave'),
handleTabClick,
handleClose,
handleAdd

View File

@ -8,6 +8,15 @@ export type OnUpdateValueImpl = (value: string | number) => void
export type OnClose = (name: string & number) => void
export type OnCloseImpl = (name: string | number) => void
export type OnBeforeLeave = (
name: string & number,
oldName: string & number & null
) => boolean | Promise<boolean>
export type OnBeforeLeaveImpl = (
name: string | number,
oldName: string | number | null
) => boolean | Promise<boolean>
export interface TabsInjection {
mergedClsPrefixRef: Ref<string>
valueRef: Ref<string | number | null>
@ -15,6 +24,8 @@ export interface TabsInjection {
closableRef: Ref<boolean>
tabStyleRef: Ref<string | CSSProperties | undefined>
paneStyleRef: Ref<string | CSSProperties | undefined>
nextTabNameRef: { value: string | number | null }
onBeforeLeaveRef: Ref<OnBeforeLeave | undefined>
handleTabClick: (panelName: string | number) => void
handleClose: (panelName: string | number) => void
handleAdd: () => void

View File

@ -282,11 +282,8 @@ export default defineComponent({
mergedClsPrefixRef
)
const formItem = useFormItem(props)
const mergedDisabledRef = computed(() => {
const maxReachedRef = computed(() => {
const { max } = props
if (formItem.mergedDisabledRef.value) {
return true
}
if (max !== undefined) {
return mergedFileListRef.value.length >= max
}
@ -515,7 +512,8 @@ export default defineComponent({
openFileDialog,
draggerInsideRef,
handleFileAddition,
mergedDisabledRef,
mergedDisabledRef: formItem.mergedDisabledRef,
maxReachedRef,
fileListStyleRef: toRef(props, 'fileListStyle'),
abstractRef: toRef(props, 'abstract'),
cssVarsRef

View File

@ -22,6 +22,7 @@ export default defineComponent({
const {
mergedClsPrefixRef,
mergedDisabledRef,
maxReachedRef,
listTypeRef,
dragOverRef,
openFileDialog,
@ -34,7 +35,7 @@ export default defineComponent({
)
function handleTriggerClick (): void {
if (mergedDisabledRef.value) return
if (mergedDisabledRef.value || maxReachedRef.value) return
openFileDialog()
}
function handleTriggerDragOver (e: DragEvent): void {
@ -51,7 +52,13 @@ export default defineComponent({
}
function handleTriggerDrop (e: DragEvent): void {
e.preventDefault()
if (!draggerInsideRef.value || mergedDisabledRef.value) return
if (
!draggerInsideRef.value ||
mergedDisabledRef.value ||
maxReachedRef.value
) {
return
}
const dataTransfer = e.dataTransfer
const files = dataTransfer?.files
if (files) {
@ -74,7 +81,7 @@ export default defineComponent({
<div
class={[
`${mergedClsPrefix}-upload-trigger`,
mergedDisabledRef.value &&
(mergedDisabledRef.value || maxReachedRef.value) &&
`${mergedClsPrefix}-upload-trigger--disabled`,
isImageCardTypeRef.value &&
`${mergedClsPrefix}-upload-trigger--image-card`

View File

@ -72,6 +72,7 @@ export interface UploadInjection {
draggerInsideRef: { value: boolean }
fileListStyleRef: Ref<string | CSSProperties | undefined>
mergedDisabledRef: Ref<boolean>
maxReachedRef: Ref<boolean>
abstractRef: Ref<boolean>
cssVarsRef: Ref<CSSProperties>
submit: (fileId?: string) => void

View File

@ -1 +1 @@
export default '2.19.9'
export default '2.19.11'