mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-06 12:17:13 +08:00
commit
229a4e37e2
18
.github/workflows/issue-add-bug.yml
vendored
Normal file
18
.github/workflows/issue-add-bug.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: Add Bug Labels
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
add-labels:
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.issue.body, '__BUG__') == true
|
||||
steps:
|
||||
- name: Add labels
|
||||
uses: actions-cool/issues-helper@v2.2.1
|
||||
with:
|
||||
actions: 'add-labels'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: 'untriaged'
|
@ -1,5 +1,33 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.15.5 (2021-07-16)
|
||||
|
||||
### Feats
|
||||
|
||||
- `n-tree` add `render-label`, `render-prefix` and `render-suffix` props.
|
||||
- `n-rate` add `allow-half` prop.
|
||||
- `n-carousel` add `show-arrow` prop.
|
||||
- `n-slider` add `format-tooltip` prop.
|
||||
- `n-upload` add `event` in `on-finish` callback params.
|
||||
- `n-rate` add `readonly` prop.
|
||||
- `n-time-picker` add `seconds`, `minutes`, `hours` props.
|
||||
- `n-notification` export `NotificationApi`, `NotificationOptions` and `NotificationReactive` type.
|
||||
- `n-avatar` add `on-error` prop, closes [#394](https://github.com/TuSimple/naive-ui/issues/394).
|
||||
- `n-image` add `on-error` prop, closes [#394](https://github.com/TuSimple/naive-ui/issues/394).
|
||||
- `n-image` add `object-fit` prop, closes [#394](https://github.com/TuSimple/naive-ui/issues/394).
|
||||
- `n-avatar` add `object-fit` prop, closes [#394](https://github.com/TuSimple/naive-ui/issues/394).
|
||||
- `n-menu` expands all the ascendant of selected item by default, closes [#481](https://github.com/TuSimple/naive-ui/issues/481).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix `n-calendar`'s `default-value` prop cannot be used.
|
||||
- Fix `n-pagination` page count is not correct when `item-count` is 0.
|
||||
- Fix `n-scrollbar` `content-style` can not override the default width of style.
|
||||
- Fix `n-select` placeholder transition.
|
||||
- Fix `n-loading-bar` `useLoadingBar`'s return type can be undefined.
|
||||
- Fix `n-tag`'s `type` prop add `primary` type.
|
||||
- Fix `n-dynamic-tag`'s `type` prop add `primary` type.
|
||||
|
||||
## 2.15.4 (2021-07-09)
|
||||
|
||||
### Feats
|
||||
@ -31,7 +59,7 @@
|
||||
### Feats
|
||||
|
||||
- `n-loading-bar` export `LoadingBarApi` type.
|
||||
- `n-image` add `imgProps` prop.
|
||||
- `n-image` add `img-props` prop.
|
||||
- Add native `title` attributes to some components to enhance the experience.
|
||||
- `n-tree` add `prefix` and `suffix` in TreeOption.
|
||||
- `n-carousel` add `dot-placement` prop.
|
||||
|
@ -1,5 +1,34 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.15.5 (2021-07-16)
|
||||
|
||||
### Feats
|
||||
|
||||
- `n-tree` 新增 `render-label`、`render-prefix` 和 `render-suffix` 属性
|
||||
- `n-rate` 新增 `allow-half` 属性
|
||||
- `n-carousel` 新增 `show-arrow` 属性
|
||||
- `n-slider` 新增 `format-tooltip` 属性
|
||||
- `n-upload` 在 `on-finish` 回调参数中新增 `event`
|
||||
- `n-slider` 新增 `format-tooltip` 属性
|
||||
- `n-rate` 新增 `readonly` 属性
|
||||
- `n-time-picker` 新增 `seconds`、`minutes`、`hours`属性
|
||||
- `n-notification` 导出 `NotificationApi`, `NotificationOptions` and `NotificationReactive` 类型
|
||||
- `n-avatar` 新增 `on-error` 属性,关闭[#394](https://github.com/TuSimple/naive-ui/issues/394)
|
||||
- `n-image` 新增 `on-error` 属性,关闭[#394](https://github.com/TuSimple/naive-ui/issues/394)
|
||||
- `n-image` 新增 `object-fit` 属性,关闭[#394](https://github.com/TuSimple/naive-ui/issues/394)
|
||||
- `n-avatar` 新增 `object-fit` 属性,关闭[#394](https://github.com/TuSimple/naive-ui/issues/394)
|
||||
- `n-menu` 默认展开选中项的全部父级,关闭[#481](https://github.com/TuSimple/naive-ui/issues/481)
|
||||
|
||||
### Fixes
|
||||
|
||||
- 修复 `n-calendar` 的 `default-value` 属性无法使用
|
||||
- 修复 `n-pagination` `item-count` 为 0 时页数不对
|
||||
- 修复 `n-scrollbar` `content-style` 无法覆盖默认样式的宽度
|
||||
- 修复 `n-select` placeholder transition
|
||||
- 修复 `n-loading-bar` `useLoadingBar` 返回类型可能为 undefined
|
||||
- 修复 `n-tag` 的 `type` 增加 `primary` 类型
|
||||
- 修复 `n-dynamic-tag` 的 `type` 增加 `primary` 类型
|
||||
|
||||
## 2.15.4 (2021-07-09)
|
||||
|
||||
### Feats
|
||||
@ -31,7 +60,7 @@
|
||||
### Feats
|
||||
|
||||
- `n-loading-bar` 导出 `LoadingBarApi` 类型
|
||||
- `n-image` 增加 `imgProps` 属性
|
||||
- `n-image` 增加 `img-props` 属性
|
||||
- 在部分组件上添加原生 `title` 属性,以提高用户体验
|
||||
- `n-tree` 在 TreeOption 中增加 `prefix` 和 `suffix` 属性
|
||||
- `n-carousel` 增加 `dot-placement` 属性
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "naive-ui",
|
||||
"version": "2.15.4",
|
||||
"version": "2.15.5",
|
||||
"description": "A Vue 3 Component Library. Fairly Complete, Customizable Themes, Uses TypeScript, Not Too Slow",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
@ -127,7 +127,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"seemly": "^0.3.1",
|
||||
"treemate": "^0.2.11",
|
||||
"treemate": "^0.2.12",
|
||||
"vdirs": "^0.1.4",
|
||||
"vfonts": "^0.1.0",
|
||||
"vooks": "^0.2.6",
|
||||
|
@ -746,6 +746,7 @@ export default defineComponent({
|
||||
this.active ? null : (
|
||||
<div
|
||||
class={`${clsPrefix}-base-selection-label__render-label ${clsPrefix}-base-render-dom`}
|
||||
key="input"
|
||||
>
|
||||
{renderLabel
|
||||
? renderLabel(this.selectedOption as SelectBaseOption, true)
|
||||
@ -755,6 +756,7 @@ export default defineComponent({
|
||||
{showPlaceholder ? (
|
||||
<div
|
||||
class={`${clsPrefix}-base-selection-placeholder ${clsPrefix}-base-render-dom`}
|
||||
key="placeholder"
|
||||
>
|
||||
{this.filterablePlaceholder}
|
||||
</div>
|
||||
|
@ -26,11 +26,18 @@ const avatarProps = {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: String,
|
||||
objectFit: {
|
||||
type: String as PropType<
|
||||
'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
|
||||
>,
|
||||
default: 'fill'
|
||||
},
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: String
|
||||
onError: Function as PropType<(e: Event) => void>
|
||||
} as const
|
||||
|
||||
export type AvatarProps = ExtractPublicPropTypes<typeof avatarProps>
|
||||
@ -112,7 +119,11 @@ export default defineComponent({
|
||||
style={this.cssVars as any}
|
||||
>
|
||||
{!$slots.default && src ? (
|
||||
<img src={src} />
|
||||
<img
|
||||
src={src}
|
||||
onError={this.onError}
|
||||
style={{ objectFit: this.objectFit }}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
ref="textRef"
|
||||
|
@ -118,4 +118,28 @@ describe('n-avatar', () => {
|
||||
)
|
||||
expect(wrapper.html()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('image avatar error handle when load failed', async () => {
|
||||
const onError = jest.fn()
|
||||
const wrapper = mount(NAvatar, {
|
||||
props: {
|
||||
src: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
onError
|
||||
}
|
||||
})
|
||||
await wrapper.find('img').trigger('error')
|
||||
expect(onError).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should work with `objectFit` prop', () => {
|
||||
const wrapper = mount(NAvatar, {
|
||||
props: {
|
||||
src: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
objectFit: 'contain'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('img').attributes('style')).toContain(
|
||||
'object-fit: contain;'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -6,7 +6,7 @@ exports[`n-avatar custom style 1`] = `"<span class=\\"n-avatar\\" style=\\"--fon
|
||||
|
||||
exports[`n-avatar icon avatar 1`] = `"<span class=\\"n-avatar\\" style=\\"--font-size: 14px; --border-radius: 3px; --color: rgba(204, 204, 204, 1); --bezier: cubic-bezier(.4, 0, .2, 1); --size: 34px;\\"><span class=\\"n-avatar__text\\" style=\\"transform: translateX(-50%) translateY(-50%) scale(1);\\"><i role=\\"img\\" class=\\"n-icon\\" style=\\"--bezier: cubic-bezier(.4, 0, .2, 1);\\"><svg xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\" viewBox=\\"0 0 512 512\\"><rect x=\\"32\\" y=\\"80\\" width=\\"448\\" height=\\"256\\" rx=\\"16\\" ry=\\"16\\" transform=\\"rotate(180 256 208)\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\"></rect><path fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\" d=\\"M64 384h384\\"></path><path fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\" d=\\"M96 432h320\\"></path><circle cx=\\"256\\" cy=\\"208\\" r=\\"80\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\"></circle><path d=\\"M480 160a80 80 0 0 1-80-80\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\"></path><path d=\\"M32 160a80 80 0 0 0 80-80\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\"></path><path d=\\"M480 256a80 80 0 0 0-80 80\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\"></path><path d=\\"M32 256a80 80 0 0 1 80 80\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" stroke-width=\\"32\\"></path></svg></i></span></span>"`;
|
||||
|
||||
exports[`n-avatar image avatar 1`] = `"<span class=\\"n-avatar\\" style=\\"--font-size: 14px; --border-radius: 3px; --color: rgba(204, 204, 204, 1); --bezier: cubic-bezier(.4, 0, .2, 1); --size: 34px;\\"><img src=\\"https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg\\"></span>"`;
|
||||
exports[`n-avatar image avatar 1`] = `"<span class=\\"n-avatar\\" style=\\"--font-size: 14px; --border-radius: 3px; --color: rgba(204, 204, 204, 1); --bezier: cubic-bezier(.4, 0, .2, 1); --size: 34px;\\"><img src=\\"https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg\\" style=\\"object-fit: fill;\\"></span>"`;
|
||||
|
||||
exports[`n-avatar round avatar 1`] = `"<span class=\\"n-avatar\\" style=\\"--font-size: 14px; --border-radius: 50%; --color: rgba(204, 204, 204, 1); --bezier: cubic-bezier(.4, 0, .2, 1); --size: 34px;\\"><span class=\\"n-avatar__text\\" style=\\"transform: translateX(-50%) translateY(-50%) scale(1);\\"></span></span>"`;
|
||||
|
||||
|
@ -3,21 +3,34 @@
|
||||
A basic calender.
|
||||
|
||||
```html
|
||||
<n-calendar @update:value="handleUpdateValue" #="{ year, month, date }">
|
||||
<n-calendar
|
||||
@update:value="handleUpdateValue"
|
||||
#="{ year, month, date }"
|
||||
v-model:value="value"
|
||||
:is-date-disabled="isDateDisabled"
|
||||
>
|
||||
{{ year }}-{{ month }}-{{ date }}
|
||||
</n-calendar>
|
||||
```
|
||||
|
||||
```js
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { isYesterday, addDays } from 'date-fns'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
const message = useMessage()
|
||||
return {
|
||||
value: ref(addDays(Date.now(), 1)),
|
||||
handleUpdateValue (_, { year, month, date }) {
|
||||
message.success(`${year}-${month}-${date}`)
|
||||
},
|
||||
isDateDisabled (timestamp) {
|
||||
if (isYesterday(timestamp)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,34 @@
|
||||
一个普通的日历。
|
||||
|
||||
```html
|
||||
<n-calendar @update:value="handleUpdateValue" #="{ year, month, date }">
|
||||
<n-calendar
|
||||
@update:value="handleUpdateValue"
|
||||
#="{ year, month, date }"
|
||||
v-model:value="value"
|
||||
:is-date-disabled="isDateDisabled"
|
||||
>
|
||||
{{ year }}-{{ month }}-{{ date }}
|
||||
</n-calendar>
|
||||
```
|
||||
|
||||
```js
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { isYesterday, addDays } from 'date-fns'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
const message = useMessage()
|
||||
return {
|
||||
value: ref(addDays(Date.now(), 1)),
|
||||
handleUpdateValue (_, { year, month, date }) {
|
||||
message.success(`${year}-${month}-${date}`)
|
||||
},
|
||||
isDateDisabled (timestamp) {
|
||||
if (isYesterday(timestamp)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export default defineComponent({
|
||||
const now = Date.now()
|
||||
// ts => timestamp
|
||||
const monthTsRef = ref(startOfMonth(now).valueOf())
|
||||
const uncontrolledValueRef = ref<number | null>(null)
|
||||
const uncontrolledValueRef = ref<number | null>(props.defaultValue || null)
|
||||
const mergedValueRef = useMergedState(
|
||||
toRef(props, 'value'),
|
||||
uncontrolledValueRef
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { h } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { NCalendar } from '../index'
|
||||
import { isYesterday } from 'date-fns'
|
||||
|
||||
describe('n-calendar', () => {
|
||||
const now = Date.now()
|
||||
|
||||
describe('n-button', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NCalendar)
|
||||
})
|
||||
@ -18,4 +21,35 @@ describe('n-button', () => {
|
||||
) => {}}
|
||||
/>
|
||||
})
|
||||
|
||||
it('should work with `default-value` prop', async () => {
|
||||
const wrapper = mount(NCalendar, { props: { defaultValue: now } })
|
||||
expect(wrapper.find('.n-calendar-cell--selected').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should work with `value` prop', async () => {
|
||||
const wrapper = mount(NCalendar, { props: { value: now } })
|
||||
expect(wrapper.find('.n-calendar-cell--selected').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should work with `is-date-disabled` prop', async () => {
|
||||
function disableFunction (timestamp: number): boolean {
|
||||
if (isYesterday(timestamp)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
const wrapper = mount(NCalendar, {
|
||||
props: { 'is-date-disabled': disableFunction }
|
||||
})
|
||||
expect(wrapper.find('.n-calendar-cell--disabled').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should work with `on-update:value` prop', async () => {
|
||||
const onUpdate = jest.fn()
|
||||
const wrapper = mount(NCalendar, { props: { 'on-update:value': onUpdate } })
|
||||
|
||||
await wrapper.findAll('.n-calendar-date')[1].trigger('click')
|
||||
expect(onUpdate).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@ -9,6 +9,7 @@ basic
|
||||
autoplay
|
||||
hover
|
||||
dot-placement
|
||||
show-arrow
|
||||
```
|
||||
|
||||
## API
|
||||
@ -20,6 +21,7 @@ dot-placement
|
||||
| autoplay | `boolean` | `false` | Whether to scroll automatically. |
|
||||
| interval | `number` | `5000` | Auto play interval. |
|
||||
| dot-placement | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Dot placement in the panel. |
|
||||
| show-arrow | `boolean` | `false` | Whether to show arrow button. |
|
||||
| trigger | `'click' \| 'hover'` | `'click'` | The way to trigger the switch. |
|
||||
|
||||
### Carousel Slots
|
||||
|
30
src/carousel/demos/enUS/show-arrow.demo.md
Normal file
30
src/carousel/demos/enUS/show-arrow.demo.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Show Arrow Button
|
||||
|
||||
```html
|
||||
<n-carousel show-arrow>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/fav/1623979004.jpg!/fw/600/quality/77/ignore-error/true"
|
||||
/>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/news/1623372884.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true"
|
||||
/>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/news/1623177220.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true"
|
||||
/>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/news/1623152423.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true"
|
||||
/>
|
||||
</n-carousel>
|
||||
```
|
||||
|
||||
```css
|
||||
.carousel-img {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
object-fit: cover;
|
||||
}
|
||||
```
|
@ -9,6 +9,7 @@ basic
|
||||
autoplay
|
||||
hover
|
||||
dot-placement
|
||||
show-arrow
|
||||
```
|
||||
|
||||
## API
|
||||
@ -20,6 +21,7 @@ dot-placement
|
||||
| autoplay | `boolean` | `false` | 是否自动播放 |
|
||||
| dot-placement | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | 轮播指示点位置 |
|
||||
| interval | `number` | `5000` | 自动播放的间隔 |
|
||||
| show-arrow | `boolean` | `false` | 是否显示箭头按钮 |
|
||||
| trigger | `'click' \| 'hover'` | `'click'` | 触发切换的方式 |
|
||||
|
||||
### Carousel Slots
|
||||
|
30
src/carousel/demos/zhCN/show-arrow.demo.md
Normal file
30
src/carousel/demos/zhCN/show-arrow.demo.md
Normal file
@ -0,0 +1,30 @@
|
||||
# 显示箭头按钮
|
||||
|
||||
```html
|
||||
<n-carousel show-arrow>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/fav/1623979004.jpg!/fw/600/quality/77/ignore-error/true"
|
||||
/>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/news/1623372884.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true"
|
||||
/>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/news/1623177220.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true"
|
||||
/>
|
||||
<img
|
||||
class="carousel-img"
|
||||
src="https://s.anw.red/news/1623152423.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true"
|
||||
/>
|
||||
</n-carousel>
|
||||
```
|
||||
|
||||
```css
|
||||
.carousel-img {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
object-fit: cover;
|
||||
}
|
||||
```
|
@ -14,6 +14,7 @@ import {
|
||||
} from 'vue'
|
||||
import { indexMap } from 'seemly'
|
||||
import { on, off } from 'evtd'
|
||||
import { BackwardIcon, ForwardIcon } from '../../_internal/icons'
|
||||
import { useConfig, useTheme } from '../../_mixins'
|
||||
import type { ThemeProps } from '../../_mixins'
|
||||
import { flatten } from '../../_utils'
|
||||
@ -24,6 +25,7 @@ import style from './styles/index.cssr'
|
||||
|
||||
const carouselProps = {
|
||||
...(useTheme.props as ThemeProps<CarouselTheme>),
|
||||
showArrow: Boolean,
|
||||
autoplay: Boolean,
|
||||
dotPlacement: {
|
||||
type: String as PropType<'top' | 'bottom' | 'left' | 'right'>,
|
||||
@ -258,19 +260,21 @@ export default defineComponent({
|
||||
cssVars: computed(() => {
|
||||
const {
|
||||
common: { cubicBezierEaseInOut },
|
||||
self: { dotColor, dotColorActive, dotSize }
|
||||
self: { dotColor, dotColorActive, dotSize, arrowColor }
|
||||
} = themeRef.value
|
||||
return {
|
||||
'--bezier': cubicBezierEaseInOut,
|
||||
'--dot-color': dotColor,
|
||||
'--dot-color-active': dotColorActive,
|
||||
'--dot-size': dotSize
|
||||
'--dot-size': dotSize,
|
||||
'--arrow-color': arrowColor
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const {
|
||||
showArrow,
|
||||
dotPlacement,
|
||||
mergedClsPrefix,
|
||||
current,
|
||||
@ -350,6 +354,34 @@ export default defineComponent({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{showArrow && [
|
||||
<div
|
||||
class={[
|
||||
`${mergedClsPrefix}-carousel__arrow`,
|
||||
`${mergedClsPrefix}-carousel__arrow--${
|
||||
vertical ? 'bottom' : 'right'
|
||||
}`
|
||||
]}
|
||||
role="button"
|
||||
onClick={() => {
|
||||
this.next()
|
||||
}}
|
||||
>
|
||||
<ForwardIcon />
|
||||
</div>,
|
||||
<div
|
||||
class={[
|
||||
`${mergedClsPrefix}-carousel__arrow`,
|
||||
`${mergedClsPrefix}-carousel__arrow--${vertical ? 'top' : 'left'}`
|
||||
]}
|
||||
role="button"
|
||||
onClick={() => {
|
||||
this.prev()
|
||||
}}
|
||||
>
|
||||
<BackwardIcon />
|
||||
</div>
|
||||
]}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { c, cB, cE, cM } from '../../../_utils/cssr'
|
||||
// --dot-color
|
||||
// --dot-color-active
|
||||
// --dot-size
|
||||
// --arrow-color
|
||||
export default cB('carousel', `
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -47,6 +48,58 @@ export default cB('carousel', `
|
||||
margin-right: 0;
|
||||
`)
|
||||
]),
|
||||
cE('arrow', `
|
||||
position: absolute;
|
||||
transition: transform .3s var(--bezier);
|
||||
transform: scale(1);
|
||||
cursor: pointer;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--arrow-color);
|
||||
`, [
|
||||
cM('right', `
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
right: 0;
|
||||
`, [
|
||||
c('&:hover', {
|
||||
transform: 'translateY(-50%) scale(1.1)'
|
||||
}),
|
||||
c('&:active', {
|
||||
transform: 'translateY(-50%) scale(1)'
|
||||
})
|
||||
]),
|
||||
cM('left', `
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
left: 0;
|
||||
`, [
|
||||
c('&:hover', {
|
||||
transform: 'translateY(-50%) scale(1.1)'
|
||||
})
|
||||
]),
|
||||
cM('top', `
|
||||
transform: translateX(-50%) rotate(90deg);
|
||||
top: 0;
|
||||
left: 50%;
|
||||
`, [
|
||||
c('&:hover', {
|
||||
transform: 'translateX(-50%) scale(1.1) rotate(90deg)'
|
||||
})
|
||||
]),
|
||||
cM('bottom', `
|
||||
transform: translateX(-50%) rotate(90deg);
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
`, [
|
||||
c('&:hover', {
|
||||
transform: 'translateX(-50%) scale(1.1) rotate(90deg)'
|
||||
})
|
||||
])
|
||||
]),
|
||||
cM('left', [
|
||||
cE('slides', `
|
||||
flex-direction: column;
|
||||
|
@ -6,7 +6,8 @@ export const self = (vars: ThemeCommonVars) => {
|
||||
return {
|
||||
dotSize: '8px',
|
||||
dotColor: 'rgba(255, 255, 255, .3)',
|
||||
dotColorActive: 'rgba(255, 255, 255, 1)'
|
||||
dotColorActive: 'rgba(255, 255, 255, 1)',
|
||||
arrowColor: 'rgba(255, 255, 255, .6)'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { h, nextTick } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { NCarousel } from '../index'
|
||||
import { sleep } from 'seemly'
|
||||
|
||||
describe('n-carousel', () => {
|
||||
it('should work with import on demand', () => {
|
||||
@ -15,4 +17,75 @@ describe('n-carousel', () => {
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with `showArrow` prop', async () => {
|
||||
const wrapper = mount(NCarousel)
|
||||
|
||||
const dotToArrow = [
|
||||
{
|
||||
dot: ['top', 'bottom'],
|
||||
arrow: ['left', 'right']
|
||||
},
|
||||
{
|
||||
dot: ['left', 'right'],
|
||||
arrow: ['top', 'bottom']
|
||||
}
|
||||
]
|
||||
|
||||
for (const item of dotToArrow) {
|
||||
for (const dotItem of item.dot) {
|
||||
await wrapper.setProps({ showArrow: true, dotPlacement: dotItem })
|
||||
|
||||
expect(
|
||||
wrapper.find(`.n-carousel__arrow--${item.arrow[0]}`).exists()
|
||||
).toBe(true)
|
||||
expect(
|
||||
wrapper.find(`.n-carousel__arrow--${item.arrow[1]}`).exists()
|
||||
).toBe(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('arrow button should work', async () => {
|
||||
const wrapper = mount(NCarousel, {
|
||||
slots: {
|
||||
default: () => {
|
||||
return [
|
||||
h('img', {
|
||||
style: 'width: 100%; height: 240px; object-fit: cover;',
|
||||
src: 'https://s.anw.red/news/1623152423.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true'
|
||||
}),
|
||||
h('img', {
|
||||
style: 'width: 100%; height: 240px; object-fit: cover;',
|
||||
src: 'https://s.anw.red/news/1623152423.jpg!/both/800x450/quality/78/progressive/true/ignore-error/true'
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await wrapper.setProps({
|
||||
showArrow: true
|
||||
})
|
||||
|
||||
const slidesDOMArray = wrapper.find('.n-carousel__slides').findAll('div')
|
||||
|
||||
expect(slidesDOMArray[1].attributes('aria-hidden')).toBe('false')
|
||||
|
||||
wrapper
|
||||
.find('.n-carousel__arrow--right')
|
||||
.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')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { nextTick } from 'vue'
|
||||
import { h, nextTick } from 'vue'
|
||||
import { mount, VueWrapper } from '@vue/test-utils'
|
||||
import { NCheckbox } from '../index'
|
||||
import { NCheckbox, NCheckboxGroup } from '../index'
|
||||
|
||||
function expectChecked (wrapper: VueWrapper<any>, value: boolean): void {
|
||||
expect(wrapper.classes().some((c) => c.includes('checked'))).toEqual(value)
|
||||
@ -10,6 +10,7 @@ describe('n-checkbox', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NCheckbox)
|
||||
})
|
||||
|
||||
describe('uncontrolled mode', () => {
|
||||
it('works', async () => {
|
||||
const wrapper = mount(NCheckbox)
|
||||
@ -30,4 +31,125 @@ describe('n-checkbox', () => {
|
||||
expectChecked(wrapper, true)
|
||||
})
|
||||
})
|
||||
|
||||
it('should work with `indeterminate` prop', () => {
|
||||
const wrapper = mount(NCheckbox, {
|
||||
props: {
|
||||
indeterminate: true
|
||||
},
|
||||
slots: {
|
||||
default: () => 'test'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('.n-checkbox').classes()).toContain(
|
||||
'n-checkbox--indeterminate'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with `disabled` prop', () => {
|
||||
const wrapper = mount(NCheckbox, {
|
||||
props: {
|
||||
disabled: true
|
||||
},
|
||||
slots: {
|
||||
default: () => 'test'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('.n-checkbox').classes()).toContain(
|
||||
'n-checkbox--disabled'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with `focusable` prop', async () => {
|
||||
const wrapper = mount(NCheckbox, {
|
||||
props: {
|
||||
focusable: false
|
||||
},
|
||||
slots: {
|
||||
default: () => 'test'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('[tabindex]').exists()).not.toBe(true)
|
||||
|
||||
await wrapper.setProps({ focusable: true })
|
||||
expect(wrapper.find('[tabindex]').exists()).toBe(true)
|
||||
expect(wrapper.find('.n-checkbox').attributes('tabindex')).toContain('0')
|
||||
})
|
||||
|
||||
it('should work with `label` prop', async () => {
|
||||
const wrapper = mount(NCheckbox, {
|
||||
props: {
|
||||
label: 'test'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('.n-checkbox__label').text()).toContain('test')
|
||||
})
|
||||
|
||||
it('should work with `on-update:checked` prop', async () => {
|
||||
const onClick = jest.fn()
|
||||
const wrapper = mount(NCheckbox, {
|
||||
props: {
|
||||
'onUpdate:checked': onClick
|
||||
},
|
||||
slots: {
|
||||
default: () => 'test'
|
||||
}
|
||||
})
|
||||
|
||||
wrapper.trigger('click')
|
||||
expect(onClick).toBeCalled()
|
||||
})
|
||||
|
||||
it('should work with default slots', async () => {
|
||||
const wrapper = mount(NCheckbox, {
|
||||
slots: {
|
||||
default: () => 'test'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('.n-checkbox__label').text()).toContain('test')
|
||||
})
|
||||
})
|
||||
|
||||
describe('n-checkbox-group', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NCheckboxGroup)
|
||||
})
|
||||
|
||||
it('should work with `disabled` prop', () => {
|
||||
const wrapper = mount(NCheckboxGroup, {
|
||||
props: {
|
||||
disabled: true
|
||||
},
|
||||
slots: {
|
||||
default: () => h(NCheckbox, null, { default: () => 'test' })
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('.n-checkbox--disabled').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should work with `on-update:value` prop', async () => {
|
||||
const onClick = jest.fn()
|
||||
const wrapper = mount(NCheckboxGroup, {
|
||||
props: {
|
||||
'on-update:value': onClick
|
||||
},
|
||||
slots: {
|
||||
default: () => h(NCheckbox, { value: 'test' })
|
||||
}
|
||||
})
|
||||
await wrapper.findComponent(NCheckbox).trigger('click')
|
||||
expect(onClick).toBeCalled()
|
||||
})
|
||||
|
||||
it('should work with default slots', async () => {
|
||||
const wrapper = mount(NCheckboxGroup, {
|
||||
props: {
|
||||
disabled: true
|
||||
},
|
||||
slots: {
|
||||
default: () => h(NCheckbox, null, { default: () => 'test' })
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('.n-checkbox__label').text()).toContain('test')
|
||||
})
|
||||
})
|
||||
|
@ -6,6 +6,6 @@ export type {
|
||||
DialogProviderInst,
|
||||
DialogOptions,
|
||||
DialogReactive,
|
||||
DialogApiInjection
|
||||
DialogApiInjection as DialogApi
|
||||
} from './src/DialogProvider'
|
||||
export { useDialog } from './src/use-dialog'
|
||||
|
@ -44,11 +44,11 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { $slots } = this
|
||||
const { tag, mergedClsPrefix, cssVars } = this
|
||||
const { tag, mergedClsPrefix, cssVars, $slots } = this
|
||||
return h(
|
||||
tag,
|
||||
{
|
||||
role: 'none',
|
||||
class: `${mergedClsPrefix}-element`,
|
||||
style: cssVars
|
||||
},
|
||||
|
@ -29,9 +29,16 @@ const imageProps = {
|
||||
alt: String,
|
||||
height: [String, Number] as PropType<string | number>,
|
||||
imgProps: Object as PropType<imgProps>,
|
||||
objectFit: {
|
||||
type: String as PropType<
|
||||
'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
|
||||
>,
|
||||
default: 'fill'
|
||||
},
|
||||
width: [String, Number] as PropType<string | number>,
|
||||
src: String,
|
||||
showToolbar: { type: Boolean, default: true }
|
||||
showToolbar: { type: Boolean, default: true },
|
||||
onError: Function as PropType<(e: Event) => void>
|
||||
}
|
||||
|
||||
export type ImageProps = ExtractPublicPropTypes<typeof imageProps>
|
||||
@ -86,6 +93,8 @@ export default defineComponent({
|
||||
alt={this.alt ? this.alt : imgProps.alt}
|
||||
aria-label={this.alt ? this.alt : imgProps.alt}
|
||||
onClick={this.handleClick}
|
||||
onError={this.onError}
|
||||
style={{ objectFit: this.objectFit }}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -73,6 +73,8 @@ export default c([
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
`, [
|
||||
c('img', 'border-radius: inherit;')
|
||||
c('img', `
|
||||
border-radius: inherit;
|
||||
`)
|
||||
])
|
||||
])
|
||||
|
@ -91,4 +91,28 @@ describe('n-image', () => {
|
||||
})
|
||||
expect(wrapper.find('[data-cool]').exists()).toEqual(true)
|
||||
})
|
||||
|
||||
it('should work with `onError` prop', async () => {
|
||||
const onError = jest.fn()
|
||||
const wrapper = mount(NImage, {
|
||||
props: {
|
||||
src: 'https:// 07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
onError
|
||||
}
|
||||
})
|
||||
await wrapper.find('img').trigger('error')
|
||||
expect(onError).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should work with `objectFit` prop', () => {
|
||||
const wrapper = mount(NImage, {
|
||||
props: {
|
||||
src: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
objectFit: 'contain'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('img').attributes('style')).toContain(
|
||||
'object-fit: contain;'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { inject } from 'vue'
|
||||
import { loadingBarApiInjectionKey } from './LoadingBarProvider'
|
||||
import type { LoadingBarApiInjection } from './LoadingBarProvider'
|
||||
import { throwError } from '../../_utils'
|
||||
|
||||
export function useLoadingBar (): LoadingBarApiInjection | undefined {
|
||||
return inject(loadingBarApiInjectionKey)
|
||||
export function useLoadingBar (): LoadingBarApiInjection {
|
||||
const loadingBar = inject(loadingBarApiInjectionKey, null)
|
||||
if (loadingBar === null) {
|
||||
throwError('use-loading-bar', 'No outer <n-loading-bar-provider /> founded.')
|
||||
}
|
||||
return loadingBar
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
You can set `default-expanded-keys` to make menu work in an uncontrolled manner or use `expanded-keys` and `@update:expanded-keys` to make it work in a controlled manner.
|
||||
|
||||
If you don't set `default-expanded-keys`, menu will expand all the ascendant of selected option by default.
|
||||
|
||||
```html
|
||||
<n-menu
|
||||
:options="menuOptions"
|
@ -10,7 +10,7 @@ No Food.
|
||||
horizontal
|
||||
select
|
||||
render-label
|
||||
default-expanded-names
|
||||
default-expanded-keys
|
||||
indent
|
||||
collapse
|
||||
inverted
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
你可以设定 `default-expanded-keys` 让菜单工作在非受控状态下或者使用 `expanded-keys` 和 `@update:expanded-keys` 以受控的方式控制菜单。
|
||||
|
||||
如果你不设定 `default-expanded-keys`,菜单会默认展开选中项的全部父级。
|
||||
|
||||
```html
|
||||
<n-menu
|
||||
:options="menuOptions"
|
||||
|
@ -71,10 +71,7 @@ const menuProps = {
|
||||
default: 32
|
||||
},
|
||||
defaultExpandAll: Boolean,
|
||||
defaultExpandedKeys: {
|
||||
type: Array as PropType<Key[]>,
|
||||
default: () => []
|
||||
},
|
||||
defaultExpandedKeys: Array as PropType<Key[]>,
|
||||
expandedKeys: {
|
||||
type: Array as PropType<Key[]>,
|
||||
default: undefined
|
||||
@ -205,10 +202,22 @@ export default defineComponent({
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const uncontrolledValueRef = ref(props.defaultValue)
|
||||
const controlledValueRef = toRef(props, 'value')
|
||||
const mergedValueRef = useMergedState(
|
||||
controlledValueRef,
|
||||
uncontrolledValueRef
|
||||
)
|
||||
|
||||
const uncontrolledExpandedKeysRef = ref(
|
||||
props.defaultExpandAll
|
||||
? treeMateRef.value.getNonLeafKeys()
|
||||
: props.defaultExpandedNames || props.defaultExpandedKeys
|
||||
: props.defaultExpandedNames ||
|
||||
props.defaultExpandedKeys ||
|
||||
treeMateRef.value.getPath(mergedValueRef.value, {
|
||||
includeSelf: false
|
||||
}).keyPath
|
||||
)
|
||||
const controlledExpandedKeysRef = useCompitable(props, [
|
||||
'expandedNames',
|
||||
@ -218,12 +227,6 @@ export default defineComponent({
|
||||
controlledExpandedKeysRef,
|
||||
uncontrolledExpandedKeysRef
|
||||
)
|
||||
const uncontrolledValueRef = ref(props.defaultValue)
|
||||
const controlledValueRef = toRef(props, 'value')
|
||||
const mergedValueRef = useMergedState(
|
||||
controlledValueRef,
|
||||
uncontrolledValueRef
|
||||
)
|
||||
const tmNodesRef = computed(() => treeMateRef.value.treeNodes)
|
||||
const activePathRef = computed(() => {
|
||||
return treeMateRef.value.getPath(mergedValueRef.value).keyPath
|
||||
|
@ -1,6 +1,9 @@
|
||||
export { default as NNotificationProvider } from './src/NotificationProvider'
|
||||
export type {
|
||||
NotificationProviderProps,
|
||||
NotificationProviderInst
|
||||
NotificationProviderInst,
|
||||
NotificationApiInjection as NotificationApi,
|
||||
NotificationOptions,
|
||||
NotificationReactive
|
||||
} from './src/NotificationProvider'
|
||||
export { useNotification } from './src/use-notification'
|
||||
|
@ -24,7 +24,7 @@ import NotificationEnvironment, {
|
||||
} from './NotificationEnvironment'
|
||||
import style from './styles/index.cssr'
|
||||
|
||||
type NotificationOptions = Partial<
|
||||
export type NotificationOptions = Partial<
|
||||
ExtractPropTypes<typeof notificationEnvOptions>
|
||||
>
|
||||
|
||||
@ -58,7 +58,7 @@ export const notificationApiInjectionKey: InjectionKey<NotificationApiInjection>
|
||||
'notificationApi'
|
||||
)
|
||||
|
||||
type NotificationReactive = {
|
||||
export type NotificationReactive = {
|
||||
readonly key: string
|
||||
readonly destroy: () => void
|
||||
/** @deprecated */
|
||||
|
@ -125,7 +125,7 @@ export default defineComponent({
|
||||
// item count has high priority, for it can affect prefix slot rendering
|
||||
const { itemCount } = props
|
||||
if (itemCount !== undefined) {
|
||||
return Math.ceil(itemCount / mergedPageSizeRef.value)
|
||||
return Math.max(1, Math.ceil(itemCount / mergedPageSizeRef.value))
|
||||
}
|
||||
const { pageCount } = props
|
||||
if (pageCount !== undefined) return pageCount
|
||||
|
@ -5,4 +5,17 @@ describe('n-pagination', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NPagination)
|
||||
})
|
||||
it('props.itemCount', async () => {
|
||||
const wrapper = mount(NPagination, {
|
||||
props: {
|
||||
itemCount: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
})
|
||||
expect(wrapper.findAll('.n-pagination-item').length).toEqual(3)
|
||||
await wrapper.setProps({
|
||||
itemCount: 11
|
||||
})
|
||||
expect(wrapper.findAll('.n-pagination-item').length).toEqual(4)
|
||||
})
|
||||
})
|
||||
|
@ -24,7 +24,7 @@ radio-focus-debug
|
||||
| default-checked | `boolean` | `false` | |
|
||||
| disabled | `boolean` | `false` | |
|
||||
| name | `string` | `undefined` | 单选按钮 radio 元素的 name 属性。如果没有设定会使用 `n-radio-group` 的 `name` |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 只用于 `n-radio` |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | |
|
||||
| value | `string` | `undefined` | |
|
||||
| on-update:checked-value | `(checked: boolean) => void` | `undefined` | |
|
||||
|
||||
|
@ -5,4 +5,31 @@ describe('n-radio', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NRadio)
|
||||
})
|
||||
|
||||
it('should work with `checked` prop', async () => {
|
||||
const wrapper = mount(NRadio, { props: { checked: false } })
|
||||
expect(wrapper.find('.n-radio').classes()).not.toContain('n-radio--checked')
|
||||
await wrapper.setProps({ checked: true })
|
||||
expect(wrapper.find('.n-radio').classes()).toContain('n-radio--checked')
|
||||
})
|
||||
|
||||
it('should work with `disabled` prop', async () => {
|
||||
const wrapper = mount(NRadio, { props: { disabled: false } })
|
||||
expect(wrapper.find('.n-radio').classes()).not.toContain(
|
||||
'n-radio--disabled'
|
||||
)
|
||||
await wrapper.setProps({ disabled: true })
|
||||
expect(wrapper.find('.n-radio').classes()).toContain('n-radio--disabled')
|
||||
})
|
||||
|
||||
it('should work with `size` prop', async () => {
|
||||
const wrapper = mount(NRadio, { props: { size: 'small' } })
|
||||
expect(wrapper.find('.n-radio').attributes('style')).toMatchSnapshot()
|
||||
|
||||
await wrapper.setProps({ size: 'medium' })
|
||||
expect(wrapper.find('.n-radio').attributes('style')).toMatchSnapshot()
|
||||
|
||||
await wrapper.setProps({ size: 'large' })
|
||||
expect(wrapper.find('.n-radio').attributes('style')).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
7
src/radio/tests/__snapshots__/Radio.spec.ts.snap
Normal file
7
src/radio/tests/__snapshots__/Radio.spec.ts.snap
Normal file
@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`n-radio should work with \`size\` prop 1`] = `"--bezier: cubic-bezier(.4, 0, .2, 1); --box-shadow: inset 0 0 0 1px rgb(224, 224, 230); --box-shadow-active: inset 0 0 0 1px #18a058; --box-shadow-disabled: inset 0 0 0 1px rgb(224, 224, 230); --box-shadow-focus: inset 0 0 0 1px #18a058, 0 0 0 2px rgba(24, 160, 88, 0.2); --box-shadow-hover: inset 0 0 0 1px #18a058; --color: #FFF; --color-disabled: rgb(250, 250, 252); --dot-color-active: #18a058; --dot-color-disabled: rgb(224, 224, 230); --font-size: 14px; --radio-size: 14px; --text-color: rgb(51, 54, 57); --text-color-disabled: rgba(194, 194, 194, 1); --label-padding: 0 8px;"`;
|
||||
|
||||
exports[`n-radio should work with \`size\` prop 2`] = `"--bezier: cubic-bezier(.4, 0, .2, 1); --box-shadow: inset 0 0 0 1px rgb(224, 224, 230); --box-shadow-active: inset 0 0 0 1px #18a058; --box-shadow-disabled: inset 0 0 0 1px rgb(224, 224, 230); --box-shadow-focus: inset 0 0 0 1px #18a058, 0 0 0 2px rgba(24, 160, 88, 0.2); --box-shadow-hover: inset 0 0 0 1px #18a058; --color: #FFF; --color-disabled: rgb(250, 250, 252); --dot-color-active: #18a058; --dot-color-disabled: rgb(224, 224, 230); --font-size: 14px; --radio-size: 16px; --text-color: rgb(51, 54, 57); --text-color-disabled: rgba(194, 194, 194, 1); --label-padding: 0 8px;"`;
|
||||
|
||||
exports[`n-radio should work with \`size\` prop 3`] = `"--bezier: cubic-bezier(.4, 0, .2, 1); --box-shadow: inset 0 0 0 1px rgb(224, 224, 230); --box-shadow-active: inset 0 0 0 1px #18a058; --box-shadow-disabled: inset 0 0 0 1px rgb(224, 224, 230); --box-shadow-focus: inset 0 0 0 1px #18a058, 0 0 0 2px rgba(24, 160, 88, 0.2); --box-shadow-hover: inset 0 0 0 1px #18a058; --color: #FFF; --color-disabled: rgb(250, 250, 252); --dot-color-active: #18a058; --dot-color-disabled: rgb(224, 224, 230); --font-size: 15px; --radio-size: 18px; --text-color: rgb(51, 54, 57); --text-color-disabled: rgba(194, 194, 194, 1); --label-padding: 0 8px;"`;
|
5
src/rate/demos/enUS/allow-half.demo.md
Normal file
5
src/rate/demos/enUS/allow-half.demo.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Allow Selecting Half Star
|
||||
|
||||
```html
|
||||
<n-rate allow-half />
|
||||
```
|
@ -9,15 +9,19 @@ basic
|
||||
size
|
||||
color
|
||||
icon
|
||||
allow-half
|
||||
readonly
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| allow-half | `boolean` | `false` | Allow activated half of the icon. |
|
||||
| color | `string` | `undefined` | Icon color activated(support `#FFF`, `#FFFFFF`, `yellow`,`rgb(0, 0, 0)` formatted colors). |
|
||||
| count | `number` | `5` | Icon count. |
|
||||
| default-value | `number` | `0` | Value of activated icons by default. |
|
||||
| readonly | `boolean` | `false` | Read only. |
|
||||
| size | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | Icon size. |
|
||||
| value | `number` | `undefined` | Value of activated icons. |
|
||||
| on-update:value | `(value: number) => void` | `undefined` | Callback when update value. |
|
||||
|
5
src/rate/demos/enUS/readonly.demo.md
Normal file
5
src/rate/demos/enUS/readonly.demo.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Read only
|
||||
|
||||
```html
|
||||
<n-rate readonly :default-value="3" />
|
||||
```
|
5
src/rate/demos/zhCN/allow-half.demo.md
Normal file
5
src/rate/demos/zhCN/allow-half.demo.md
Normal file
@ -0,0 +1,5 @@
|
||||
# 允许半颗
|
||||
|
||||
```html
|
||||
<n-rate allow-half />
|
||||
```
|
@ -9,15 +9,19 @@ basic
|
||||
size
|
||||
color
|
||||
icon
|
||||
allow-half
|
||||
readonly
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| allow-half | `boolean` | `false` | 允许只激活一半图标 |
|
||||
| color | `string` | `undefined` | 已激活图标颜色(支持形如 `#FFF`, `#FFFFFF`, `yellow`,`rgb(0, 0, 0)` 的颜色) |
|
||||
| count | `number` | `5` | 图标个数 |
|
||||
| default-value | `number` | `0` | 默认已激活图标个数 |
|
||||
| readonly | `boolean` | `false` | 只读,交互失效 |
|
||||
| size | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | 图标尺寸 |
|
||||
| value | `number` | `undefined` | 绑定已激活图标个数 |
|
||||
| on-update:value | `(value: number) => void` | `undefined` | 激活图标个数改变时触发 |
|
||||
|
5
src/rate/demos/zhCN/readonly.demo.md
Normal file
5
src/rate/demos/zhCN/readonly.demo.md
Normal file
@ -0,0 +1,5 @@
|
||||
# 只读
|
||||
|
||||
```html
|
||||
<n-rate readonly :default-value="3" />
|
||||
```
|
@ -21,6 +21,7 @@ import StarIcon from './StarIcon'
|
||||
|
||||
const rateProps = {
|
||||
...(useTheme.props as ThemeProps<RateTheme>),
|
||||
allowHalf: Boolean,
|
||||
count: {
|
||||
type: Number,
|
||||
default: 5
|
||||
@ -30,6 +31,7 @@ const rateProps = {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
readonly: Boolean,
|
||||
size: {
|
||||
type: [String, Number] as PropType<number | 'small' | 'medium' | 'large'>,
|
||||
default: 'medium'
|
||||
@ -76,20 +78,34 @@ export default defineComponent({
|
||||
nTriggerFormChange()
|
||||
nTriggerFormInput()
|
||||
}
|
||||
function handleMouseEnter (index: number): void {
|
||||
hoverIndexRef.value = index
|
||||
function getDerivedValue (index: number, e: MouseEvent): number {
|
||||
if (props.allowHalf) {
|
||||
if (
|
||||
e.offsetX >=
|
||||
Math.floor((e.currentTarget as HTMLDivElement).offsetWidth / 2)
|
||||
) {
|
||||
return index + 1
|
||||
} else {
|
||||
return index + 0.5
|
||||
}
|
||||
} else {
|
||||
return index + 1
|
||||
}
|
||||
}
|
||||
function handleMouseMove (index: number, e: MouseEvent): void {
|
||||
hoverIndexRef.value = getDerivedValue(index, e)
|
||||
}
|
||||
function handleMouseLeave (): void {
|
||||
hoverIndexRef.value = null
|
||||
}
|
||||
function handleClick (index: number): void {
|
||||
doUpdateValue(index + 1)
|
||||
function handleClick (index: number, e: MouseEvent): void {
|
||||
doUpdateValue(getDerivedValue(index, e))
|
||||
}
|
||||
return {
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
mergedValue: useMergedState(controlledValueRef, uncontrolledValueRef),
|
||||
hoverIndex: hoverIndexRef,
|
||||
handleMouseEnter,
|
||||
handleMouseMove,
|
||||
handleClick,
|
||||
handleMouseLeave,
|
||||
cssVars: computed(() => {
|
||||
@ -116,6 +132,7 @@ export default defineComponent({
|
||||
},
|
||||
render () {
|
||||
const {
|
||||
readonly,
|
||||
hoverIndex,
|
||||
mergedValue,
|
||||
mergedClsPrefix,
|
||||
@ -123,34 +140,69 @@ export default defineComponent({
|
||||
} = this
|
||||
return (
|
||||
<div
|
||||
class={`${mergedClsPrefix}-rate`}
|
||||
class={[
|
||||
`${mergedClsPrefix}-rate`,
|
||||
{
|
||||
[`${mergedClsPrefix}-rate--readonly`]: readonly
|
||||
}
|
||||
]}
|
||||
style={this.cssVars as CSSProperties}
|
||||
onMouseleave={this.handleMouseLeave}
|
||||
>
|
||||
{renderList(this.count, (_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
class={[
|
||||
`${mergedClsPrefix}-rate__item`,
|
||||
{
|
||||
[`${mergedClsPrefix}-rate__item--active`]:
|
||||
hoverIndex !== null
|
||||
? index <= hoverIndex
|
||||
: index < mergedValue
|
||||
{renderList(this.count, (_, index) => {
|
||||
const icon = defaultSlot ? (
|
||||
defaultSlot()
|
||||
) : (
|
||||
<NBaseIcon clsPrefix={mergedClsPrefix}>
|
||||
{{ default: () => StarIcon }}
|
||||
</NBaseIcon>
|
||||
)
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
class={[
|
||||
`${mergedClsPrefix}-rate__item`,
|
||||
{
|
||||
[`${mergedClsPrefix}-rate__item--active`]:
|
||||
hoverIndex !== null
|
||||
? index + 1 <= hoverIndex
|
||||
: index + 1 <= mergedValue
|
||||
}
|
||||
]}
|
||||
onClick={
|
||||
readonly
|
||||
? undefined
|
||||
: (e) => {
|
||||
this.handleClick(index, e)
|
||||
}
|
||||
}
|
||||
]}
|
||||
onClick={() => this.handleClick(index)}
|
||||
onMouseenter={() => this.handleMouseEnter(index)}
|
||||
>
|
||||
{defaultSlot ? (
|
||||
defaultSlot()
|
||||
) : (
|
||||
<NBaseIcon clsPrefix={mergedClsPrefix}>
|
||||
{{ default: () => StarIcon }}
|
||||
</NBaseIcon>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
onMousemove={
|
||||
readonly
|
||||
? undefined
|
||||
: (e) => {
|
||||
this.handleMouseMove(index, e)
|
||||
}
|
||||
}
|
||||
>
|
||||
{icon}
|
||||
{this.allowHalf ? (
|
||||
<div
|
||||
class={[
|
||||
`${mergedClsPrefix}-rate__half`,
|
||||
{
|
||||
[`${mergedClsPrefix}-rate__half--active`]:
|
||||
hoverIndex !== null
|
||||
? index + 0.5 <= hoverIndex
|
||||
: index + 0.5 <= mergedValue
|
||||
}
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { c, cB, cE, cM } from '../../../_utils/cssr'
|
||||
import { c, cB, cE, cM, cNotM } from '../../../_utils/cssr'
|
||||
|
||||
// vars:
|
||||
// --bezier
|
||||
@ -12,30 +12,51 @@ export default cB('rate', {
|
||||
cE('item', `
|
||||
transition:
|
||||
transform .1s var(--bezier),
|
||||
color .1s var(--bezier);
|
||||
color .3s var(--bezier);
|
||||
`)
|
||||
]),
|
||||
cE('item', `
|
||||
position: relative;
|
||||
display: flex;
|
||||
transition:
|
||||
transform .1s var(--bezier),
|
||||
color .3s var(--bezier);
|
||||
transform: scale(1);
|
||||
font-size: var(--item-size);
|
||||
cursor: pointer;
|
||||
color: var(--item-color);
|
||||
`, [
|
||||
c('&:hover', {
|
||||
transform: 'scale(1.05)'
|
||||
}),
|
||||
c('&:active', {
|
||||
transform: 'scale(0.96)'
|
||||
}),
|
||||
c('&:not(:first-child)', {
|
||||
marginLeft: '6px'
|
||||
}),
|
||||
cM('active', {
|
||||
color: 'var(--item-color-active)'
|
||||
})
|
||||
]),
|
||||
cNotM('readonly', `
|
||||
cursor: pointer;
|
||||
`, [
|
||||
cE('item', [
|
||||
c('&:hover', {
|
||||
transform: 'scale(1.05)'
|
||||
}),
|
||||
c('&:active', {
|
||||
transform: 'scale(0.96)'
|
||||
})
|
||||
])
|
||||
]),
|
||||
cE('half', `
|
||||
display: flex;
|
||||
transition: inherit;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
color: var(--item-color);
|
||||
`, [
|
||||
cM('active', {
|
||||
color: 'var(--item-color-active)'
|
||||
})
|
||||
])
|
||||
])
|
||||
|
@ -79,4 +79,23 @@ describe('n-rate', () => {
|
||||
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('should work with `readonly` prop', async () => {
|
||||
const wrapper = mount(NRate)
|
||||
|
||||
await wrapper.setProps({ readonly: true })
|
||||
|
||||
expect(wrapper.find('.n-rate').classes()).toContain('n-rate--readonly')
|
||||
|
||||
await wrapper.setProps({ readonly: true, value: 3 })
|
||||
expect(wrapper.findAll('.n-rate__item--active').length).toBe(3)
|
||||
|
||||
await wrapper.findAll('.n-rate__item')[3].trigger('click')
|
||||
expect(wrapper.findAll('.n-rate__item--active').length).toBe(3)
|
||||
|
||||
await wrapper.findAll('.n-rate__item')[3].trigger('mousemove')
|
||||
expect(wrapper.findAll('.n-rate__item--active').length).toBe(3)
|
||||
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
@ -619,10 +619,10 @@ const Scrollbar = defineComponent({
|
||||
ref="contentRef"
|
||||
style={
|
||||
[
|
||||
this.contentStyle,
|
||||
{
|
||||
width: this.xScrollable ? 'fit-content' : null
|
||||
}
|
||||
},
|
||||
this.contentStyle
|
||||
] as any
|
||||
}
|
||||
class={[
|
||||
|
@ -19,6 +19,7 @@ disable-tooltip
|
||||
| --- | --- | --- | --- |
|
||||
| default-value | `number \| [number, number] \| null` | `null` | Default value. |
|
||||
| disabled | `boolean` | `false` | Whether the slider is disabled. |
|
||||
| format-tooltip | `(value: number) => string \| number` | `undefined` | Format tooltip. |
|
||||
| marks | `{ [markValue: number]: string }` | `undefined` | Marks of the slider. |
|
||||
| max | `number` | `100` | Max value of the slider. |
|
||||
| min | `number` | `0` | Min value of the slider. |
|
||||
|
@ -19,6 +19,7 @@ disable-tooltip
|
||||
| --- | --- | --- | --- |
|
||||
| default-value | `number \| [number, number] \| null` | `null` | 默认值 |
|
||||
| disabled | `boolean` | `false` | 是否禁用 |
|
||||
| format-tooltip | `(value: number) => string \| number` | `undefined` | 格式化 tooltip |
|
||||
| marks | `{ [markValue: number]: string }` | `undefined` | Slider 上的标记 |
|
||||
| max | `number` | `100` | 最大值 |
|
||||
| min | `number` | `0` | 最小值 |
|
||||
|
@ -38,6 +38,7 @@ const sliderProps = {
|
||||
},
|
||||
marks: Object as PropType<Record<string, string>>,
|
||||
disabled: Boolean,
|
||||
formatTooltip: Function as PropType<(value: number) => string | number>,
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
@ -786,7 +787,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { mergedClsPrefix } = this
|
||||
const { mergedClsPrefix, formatTooltip } = this
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
@ -875,7 +876,9 @@ export default defineComponent({
|
||||
class={`${mergedClsPrefix}-slider-handle-indicator`}
|
||||
style={this.indicatorCssVars as CSSProperties}
|
||||
>
|
||||
{this.handleValue1}
|
||||
{typeof formatTooltip === 'function'
|
||||
? formatTooltip(this.handleValue1)
|
||||
: this.handleValue1}
|
||||
</div>
|
||||
) : null
|
||||
}}
|
||||
@ -931,7 +934,9 @@ export default defineComponent({
|
||||
class={`${mergedClsPrefix}-slider-handle-indicator`}
|
||||
style={this.indicatorCssVars as CSSProperties}
|
||||
>
|
||||
{this.handleValue2}
|
||||
{typeof formatTooltip === 'function'
|
||||
? formatTooltip(this.handleValue2)
|
||||
: this.handleValue2}
|
||||
</div>
|
||||
) : null
|
||||
}}
|
||||
|
@ -5,4 +5,41 @@ describe('n-switch', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NSwitch)
|
||||
})
|
||||
|
||||
it('should work with `disabled` prop', async () => {
|
||||
const wrapper = mount(NSwitch)
|
||||
expect(wrapper.find('.n-switch--disabled').exists()).not.toBe(true)
|
||||
|
||||
await wrapper.setProps({ disabled: true })
|
||||
expect(wrapper.find('.n-switch').classes()).toContain('n-switch--disabled')
|
||||
})
|
||||
|
||||
it('should work with `size` prop', async () => {
|
||||
const wrapper = mount(NSwitch)
|
||||
|
||||
await wrapper.setProps({ size: 'small' })
|
||||
expect(wrapper.find('.n-switch').attributes('style')).toMatchSnapshot()
|
||||
|
||||
await wrapper.setProps({ size: 'medium' })
|
||||
expect(wrapper.find('.n-switch').attributes('style')).toMatchSnapshot()
|
||||
|
||||
await wrapper.setProps({ size: 'large' })
|
||||
expect(wrapper.find('.n-switch').attributes('style')).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should work with `value` prop', async () => {
|
||||
const wrapper = mount(NSwitch, { props: { value: true } })
|
||||
expect(wrapper.find('.n-switch--active').exists()).toBe(true)
|
||||
|
||||
await wrapper.setProps({ value: false })
|
||||
expect(wrapper.find('.n-switch--active').exists()).not.toBe(true)
|
||||
})
|
||||
|
||||
it('should work with `on-update:value` prop', async () => {
|
||||
const onUpdate = jest.fn()
|
||||
const wrapper = mount(NSwitch, { props: { 'onUpdate:value': onUpdate } })
|
||||
|
||||
await wrapper.trigger('click')
|
||||
expect(onUpdate).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
7
src/switch/tests/__snapshots__/Switch.spec.ts.snap
Normal file
7
src/switch/tests/__snapshots__/Switch.spec.ts.snap
Normal file
@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`n-switch should work with \`size\` prop 1`] = `"--bezier: cubic-bezier(.4, 0, .2, 1); --button-border-radius: 3px; --button-box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.3), inset 0 0 1px 0 rgba(0, 0, 0, 0.05); --button-color: #FFF; --button-width: 14px; --button-width-pressed: 20px; --button-height: 14px; --height: 18px; --offset: 2px; --opacity-disabled: 0.5; --rail-border-radius: 3px; --rail-color: rgba(0, 0, 0, .14); --rail-color-active: #18a058; --rail-height: 18px; --rail-width: 32px; --width: 32px; --box-shadow-focus: 0 0 0 2px rgba(24, 160, 88, 0.2);"`;
|
||||
|
||||
exports[`n-switch should work with \`size\` prop 2`] = `"--bezier: cubic-bezier(.4, 0, .2, 1); --button-border-radius: 3px; --button-box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.3), inset 0 0 1px 0 rgba(0, 0, 0, 0.05); --button-color: #FFF; --button-width: 18px; --button-width-pressed: 24px; --button-height: 18px; --height: 22px; --offset: 2px; --opacity-disabled: 0.5; --rail-border-radius: 3px; --rail-color: rgba(0, 0, 0, .14); --rail-color-active: #18a058; --rail-height: 22px; --rail-width: 40px; --width: 40px; --box-shadow-focus: 0 0 0 2px rgba(24, 160, 88, 0.2);"`;
|
||||
|
||||
exports[`n-switch should work with \`size\` prop 3`] = `"--bezier: cubic-bezier(.4, 0, .2, 1); --button-border-radius: 3px; --button-box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.3), inset 0 0 1px 0 rgba(0, 0, 0, 0.05); --button-color: #FFF; --button-width: 22px; --button-width-pressed: 28px; --button-height: 22px; --height: 26px; --offset: 2px; --opacity-disabled: 0.5; --rail-border-radius: 3px; --rail-color: rgba(0, 0, 0, .14); --rail-color-active: #18a058; --rail-height: 26px; --rail-width: 48px; --width: 48px; --box-shadow-focus: 0 0 0 2px rgba(24, 160, 88, 0.2);"`;
|
@ -38,7 +38,7 @@ export default c([
|
||||
--merged-border-color: var(--border-color);
|
||||
`, [
|
||||
c('th', `
|
||||
white-spac: nowrap;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background-color .3s var(--bezier),
|
||||
border-color .3s var(--bezier),
|
||||
|
@ -3,7 +3,7 @@ import { PropType } from 'vue'
|
||||
export default {
|
||||
type: {
|
||||
type: String as PropType<
|
||||
'default' | 'success' | 'info' | 'warning' | 'error'
|
||||
'default' | 'primary' | 'success' | 'info' | 'warning' | 'error'
|
||||
>,
|
||||
default: 'default'
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ Like a digital clock.
|
||||
basic
|
||||
size
|
||||
disabled-time
|
||||
step-time
|
||||
format
|
||||
```
|
||||
|
||||
@ -19,6 +20,9 @@ format
|
||||
| default-value | `number \| null` | `null` | |
|
||||
| disabled | `boolean` | `false` | |
|
||||
| format | `string` | `'HH:mm:ss'` | |
|
||||
| hours | `number \| number[]` | `undefined` | The hours to be displayed. If it's a number, it'll be viewed as step. |
|
||||
| minutes | `number \| number[]` | `undefined` | The minutes to be displayed. If it's a number, it'll be viewed as step. |
|
||||
| seconds | `number \| number[]` | `undefined` | The seconds to be displayed. If it's a number, it'll be viewed as step. |
|
||||
| is-hour-disabled | `(hour: number) => boolean` | `() => false` | |
|
||||
| is-minute-disabled | `(minute: number, hour: number) => boolean` | `() => false` | |
|
||||
| is-second-disabled | `(second: number, minute: number, hour: number) => boolean` | `() => false` | |
|
||||
|
7
src/time-picker/demos/enUS/step-time.demo.md
Normal file
7
src/time-picker/demos/enUS/step-time.demo.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Step Time
|
||||
|
||||
Pass a number as step or use an array to specify the items you want to show.
|
||||
|
||||
```html
|
||||
<n-time-picker :seconds="[0]" :hours="[8,18]" :minutes="30" />
|
||||
```
|
@ -8,6 +8,7 @@
|
||||
basic
|
||||
size
|
||||
disabled-time
|
||||
step-time
|
||||
format
|
||||
```
|
||||
|
||||
@ -19,6 +20,9 @@ format
|
||||
| default-value | `number \| null` | `null` | |
|
||||
| disabled | `boolean` | `false` | |
|
||||
| format | `string` | `'HH:mm:ss'` | |
|
||||
| hours | `number \| number[]` | `undefined` | 通过数组指定显示的小时。当值为 number 时,将被当做时间步进处理 |
|
||||
| minutes | `number \| number[]` | `undefined` | 通过数组指定显示的分钟。当值为 number 时,将被当做时间步进处理 |
|
||||
| seconds | `number \| number[]` | `undefined` | 通过数组指定显示的秒。当值为 number 时,将被当做时间步进处理 |
|
||||
| is-hour-disabled | `(hour: number) => boolean` | `() => false` | |
|
||||
| is-minute-disabled | `(minute: number, hour: number) => boolean` | `() => false` | |
|
||||
| is-second-disabled | `(second: number, minute: number, hour: number) => boolean` | `() => false` | |
|
||||
|
7
src/time-picker/demos/zhCN/step-time.demo.md
Normal file
7
src/time-picker/demos/zhCN/step-time.demo.md
Normal file
@ -0,0 +1,7 @@
|
||||
# 展示某些时间
|
||||
|
||||
传递单独的数字来定义时间步进或用数组指定你需要显示的内容。
|
||||
|
||||
```html
|
||||
<n-time-picker :seconds="[0]" :hours="[8,18]" :minutes="30" />
|
||||
```
|
@ -2,7 +2,7 @@ import { h, ref, defineComponent, inject, PropType, computed } from 'vue'
|
||||
import { NScrollbar } from '../../scrollbar'
|
||||
import { NButton } from '../../button'
|
||||
import { NBaseFocusDetector } from '../../_internal'
|
||||
import { time } from './utils'
|
||||
import { time, getFixValue } from './utils'
|
||||
import {
|
||||
IsHourDisabled,
|
||||
IsMinuteDisabled,
|
||||
@ -10,6 +10,7 @@ import {
|
||||
timePickerInjectionKey
|
||||
} from './interface'
|
||||
import PanelCol, { Item } from './PanelCol'
|
||||
import { MaybeArray } from '../../_utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TimePickerPanel',
|
||||
@ -84,7 +85,10 @@ export default defineComponent({
|
||||
onFocusin: Function as PropType<(e: FocusEvent) => void>,
|
||||
onFocusout: Function as PropType<(e: FocusEvent) => void>,
|
||||
onFocusDetectorFocus: Function as PropType<() => void>,
|
||||
onKeydown: Function as PropType<(e: KeyboardEvent) => void>
|
||||
onKeydown: Function as PropType<(e: KeyboardEvent) => void>,
|
||||
hours: [Number, Array] as PropType<MaybeArray<number>>,
|
||||
minutes: [Number, Array] as PropType<MaybeArray<number>>,
|
||||
seconds: [Number, Array] as PropType<MaybeArray<number>>
|
||||
},
|
||||
setup (props) {
|
||||
const {
|
||||
@ -92,18 +96,34 @@ export default defineComponent({
|
||||
mergedClsPrefixRef
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
} = inject(timePickerInjectionKey)!
|
||||
const hoursRef = computed<Item[]>(() =>
|
||||
time.hours.map((hour) => {
|
||||
const { isHourDisabled } = props
|
||||
|
||||
function getTimeUnits (
|
||||
defaultValue: string[],
|
||||
stepOrList: MaybeArray<number> | undefined
|
||||
): string[] {
|
||||
if (Array.isArray(stepOrList)) {
|
||||
return stepOrList.map((v) => Math.floor(v)).map((v) => getFixValue(v))
|
||||
} else if (typeof stepOrList === 'number') {
|
||||
return defaultValue.filter((hour) => Number(hour) % stepOrList === 0)
|
||||
} else {
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
const hoursRef = computed<Item[]>(() => {
|
||||
const { isHourDisabled, hours } = props
|
||||
|
||||
return getTimeUnits(time.hours, hours).map((hour) => {
|
||||
return {
|
||||
value: hour,
|
||||
disabled: isHourDisabled ? isHourDisabled(Number(hour)) : false
|
||||
}
|
||||
})
|
||||
)
|
||||
const minutesRef = computed<Item[]>(() =>
|
||||
time.minutes.map((minute) => {
|
||||
const { isMinuteDisabled } = props
|
||||
})
|
||||
const minutesRef = computed<Item[]>(() => {
|
||||
const { isMinuteDisabled, minutes } = props
|
||||
|
||||
return getTimeUnits(time.minutes, minutes).map((minute) => {
|
||||
return {
|
||||
value: minute,
|
||||
disabled: isMinuteDisabled
|
||||
@ -111,10 +131,11 @@ export default defineComponent({
|
||||
: false
|
||||
}
|
||||
})
|
||||
)
|
||||
const secondsRef = computed<Item[]>(() =>
|
||||
time.seconds.map((second) => {
|
||||
const { isSecondDisabled } = props
|
||||
})
|
||||
const secondsRef = computed<Item[]>(() => {
|
||||
const { isSecondDisabled, seconds } = props
|
||||
|
||||
return getTimeUnits(time.seconds, seconds).map((second) => {
|
||||
return {
|
||||
value: second,
|
||||
disabled: isSecondDisabled
|
||||
@ -126,7 +147,7 @@ export default defineComponent({
|
||||
: false
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
return {
|
||||
mergedTheme: mergedThemeRef,
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
@ -156,10 +177,10 @@ export default defineComponent({
|
||||
class={[
|
||||
`${mergedClsPrefix}-time-picker-col`,
|
||||
{
|
||||
[`${mergedClsPrefix}-time-picker-col--invalid`]: this
|
||||
.isHourInvalid,
|
||||
[`${mergedClsPrefix}-time-picker-col--transition-disabled`]: this
|
||||
.transitionDisabled
|
||||
[`${mergedClsPrefix}-time-picker-col--invalid`]:
|
||||
this.isHourInvalid,
|
||||
[`${mergedClsPrefix}-time-picker-col--transition-disabled`]:
|
||||
this.transitionDisabled
|
||||
}
|
||||
]}
|
||||
>
|
||||
@ -189,10 +210,10 @@ export default defineComponent({
|
||||
class={[
|
||||
`${mergedClsPrefix}-time-picker-col`,
|
||||
{
|
||||
[`${mergedClsPrefix}-time-picker-col--transition-disabled`]: this
|
||||
.transitionDisabled,
|
||||
[`${mergedClsPrefix}-time-picker-col--invalid`]: this
|
||||
.isMinuteInvalid
|
||||
[`${mergedClsPrefix}-time-picker-col--transition-disabled`]:
|
||||
this.transitionDisabled,
|
||||
[`${mergedClsPrefix}-time-picker-col--invalid`]:
|
||||
this.isMinuteInvalid
|
||||
}
|
||||
]}
|
||||
>
|
||||
@ -222,10 +243,10 @@ export default defineComponent({
|
||||
class={[
|
||||
`${mergedClsPrefix}-time-picker-col`,
|
||||
{
|
||||
[`${mergedClsPrefix}-time-picker-col--invalid`]: this
|
||||
.isSecondInvalid,
|
||||
[`${mergedClsPrefix}-time-picker-col--transition-disabled`]: this
|
||||
.transitionDisabled
|
||||
[`${mergedClsPrefix}-time-picker-col--invalid`]:
|
||||
this.isSecondInvalid,
|
||||
[`${mergedClsPrefix}-time-picker-col--transition-disabled`]:
|
||||
this.transitionDisabled
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
@ -59,6 +59,18 @@ import {
|
||||
} from './interface'
|
||||
import { happensIn } from 'seemly'
|
||||
|
||||
// validate hours,minutes,seconds prop
|
||||
function validateUnits (value: MaybeArray<number>, max: number): boolean {
|
||||
if (value === undefined) {
|
||||
return true
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.every((v) => v >= 0 && v <= max)
|
||||
} else {
|
||||
return value >= 0 && value <= max
|
||||
}
|
||||
}
|
||||
|
||||
const timePickerProps = {
|
||||
...(useTheme.props as ThemeProps<TimePickerTheme>),
|
||||
to: useAdjustedTo.propTo,
|
||||
@ -119,6 +131,18 @@ const timePickerProps = {
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
hours: {
|
||||
type: [Number, Array] as PropType<MaybeArray<number>>,
|
||||
validator: (value: MaybeArray<number>) => validateUnits(value, 23)
|
||||
},
|
||||
minutes: {
|
||||
type: [Number, Array] as PropType<MaybeArray<number>>,
|
||||
validator: (value: MaybeArray<number>) => validateUnits(value, 59)
|
||||
},
|
||||
seconds: {
|
||||
type: [Number, Array] as PropType<MaybeArray<number>>,
|
||||
validator: (value: MaybeArray<number>) => validateUnits(value, 59)
|
||||
}
|
||||
}
|
||||
|
||||
@ -666,6 +690,9 @@ export default defineComponent({
|
||||
<Panel
|
||||
ref="panelInstRef"
|
||||
style={this.cssVars as CSSProperties}
|
||||
seconds={this.seconds}
|
||||
minutes={this.minutes}
|
||||
hours={this.hours}
|
||||
transitionDisabled={this.transitionDisabled}
|
||||
hourValue={this.hourValue}
|
||||
showHour={this.hourInFormat}
|
||||
|
@ -151,3 +151,7 @@ export const time = {
|
||||
],
|
||||
period: ['AM', 'PM']
|
||||
}
|
||||
|
||||
export function getFixValue (value: number): string {
|
||||
return `00${value}`.slice(-2)
|
||||
}
|
||||
|
72
src/tree/demos/enUS/batch-render.demo.md
Normal file
72
src/tree/demos/enUS/batch-render.demo.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Batch Rendering
|
||||
|
||||
As you can see, prefix, label, and suffix all have render functions.
|
||||
|
||||
```html
|
||||
<n-tree
|
||||
block-line
|
||||
:data="data"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
:render-prefix="renderPrefix"
|
||||
:render-label="renderLabel"
|
||||
:render-suffix="renderSuffix"
|
||||
:selectable="false"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { h, defineComponent, ref } from 'vue'
|
||||
import { NButton } from 'naive-ui'
|
||||
|
||||
function createData (level = 4, baseKey = '') {
|
||||
if (!level) return undefined
|
||||
return Array.apply(null, { length: 6 - level }).map((_, index) => {
|
||||
const key = '' + baseKey + level + index
|
||||
return {
|
||||
label: createLabel(level),
|
||||
key,
|
||||
children: createData(level - 1, key),
|
||||
level
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createLabel (level) {
|
||||
if (level === 4) return 'Out of Tao, One is born'
|
||||
if (level === 3) return 'Out of One, Two'
|
||||
if (level === 2) return 'Out of Two, Three'
|
||||
if (level === 1) return 'Out of Three, the created universe'
|
||||
}
|
||||
|
||||
function renderPrefix ({ option }) {
|
||||
return h(
|
||||
NButton,
|
||||
{ text: true, type: 'primary' },
|
||||
{ default: () => `Prefix-${option.level}` }
|
||||
)
|
||||
}
|
||||
|
||||
function renderLabel ({ option }) {
|
||||
return `${option.label} :)`
|
||||
}
|
||||
|
||||
function renderSuffix ({ option }) {
|
||||
return h(
|
||||
NButton,
|
||||
{ text: true, type: 'primary' },
|
||||
{ default: () => `Suffix-${option.level}` }
|
||||
)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
data: createData(),
|
||||
defaultExpandedKeys: ref(['40', '41']),
|
||||
renderPrefix,
|
||||
renderLabel,
|
||||
renderSuffix
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
@ -16,6 +16,7 @@ virtual
|
||||
async
|
||||
disabled
|
||||
prefix-and-suffix
|
||||
batch-render
|
||||
```
|
||||
|
||||
## API
|
||||
@ -44,6 +45,9 @@ prefix-and-suffix
|
||||
| on-load | `(node: TreeOption) => Promise<void>` | `undefined` | |
|
||||
| pattern | `string` | `''` | |
|
||||
| remote | `boolean` | `false` | Whether to load nodes async. It should work with `on-load` |
|
||||
| render-label | `(info: {option: TreeOption, checked: boolean, selected: boolean}) => VNodeChild` | `undefined` | Render function of all the options' label. |
|
||||
| render-prefix | `(info: {option: TreeOption, checked: boolean, selected: boolean}) => VNodeChild` | `undefined` | Render function of all the options' prefix. |
|
||||
| render-suffix | `(info: {option: TreeOption, checked: boolean, selected: boolean}) => VNodeChild` | `undefined` | Render function of all the options' suffix. |
|
||||
| selectable | `boolean` | `true` | |
|
||||
| selected-keys | `Array<string \| number>` | `undefined` | If set, selected status will work in controlled manner. |
|
||||
| virtual-scroll | `boolean` | `false` | Whether to enable virtual scroll. You need to set proper style height of the tree in advance. |
|
||||
|
72
src/tree/demos/zhCN/batch-render.demo.md
Normal file
72
src/tree/demos/zhCN/batch-render.demo.md
Normal file
@ -0,0 +1,72 @@
|
||||
# 批量渲染
|
||||
|
||||
如你所想,前缀、标签、后缀都可以批量渲染
|
||||
|
||||
```html
|
||||
<n-tree
|
||||
block-line
|
||||
:data="data"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
:render-prefix="renderPrefix"
|
||||
:render-label="renderLabel"
|
||||
:render-suffix="renderSuffix"
|
||||
:selectable="false"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { h, defineComponent, ref } from 'vue'
|
||||
import { NButton } from 'naive-ui'
|
||||
|
||||
function createData (level = 4, baseKey = '') {
|
||||
if (!level) return undefined
|
||||
return Array.apply(null, { length: 6 - level }).map((_, index) => {
|
||||
const key = '' + baseKey + level + index
|
||||
return {
|
||||
label: createLabel(level),
|
||||
key,
|
||||
children: createData(level - 1, key),
|
||||
level
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createLabel (level) {
|
||||
if (level === 4) return '道生一'
|
||||
if (level === 3) return '一生二'
|
||||
if (level === 2) return '二生三'
|
||||
if (level === 1) return '三生万物'
|
||||
}
|
||||
|
||||
function renderPrefix ({ option }) {
|
||||
return h(
|
||||
NButton,
|
||||
{ text: true, type: 'primary' },
|
||||
{ default: () => `Prefix-${option.level}` }
|
||||
)
|
||||
}
|
||||
|
||||
function renderLabel ({ option }) {
|
||||
return `${option.label} ^_^`
|
||||
}
|
||||
|
||||
function renderSuffix ({ option }) {
|
||||
return h(
|
||||
NButton,
|
||||
{ text: true, type: 'primary' },
|
||||
{ default: () => `Suffix-${option.level}` }
|
||||
)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
data: createData(),
|
||||
defaultExpandedKeys: ref(['40', '41']),
|
||||
renderPrefix,
|
||||
renderLabel,
|
||||
renderSuffix
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
@ -16,6 +16,7 @@ virtual
|
||||
async
|
||||
disabled
|
||||
prefix-and-suffix
|
||||
batch-render
|
||||
```
|
||||
|
||||
## API
|
||||
@ -45,6 +46,9 @@ prefix-and-suffix
|
||||
| on-load | `(node: TreeOption) => Promise<void>` | `undefined` | |
|
||||
| pattern | `string` | `''` | |
|
||||
| remote | `boolean` | `false` | 是否异步获取选项,和 onLoad 配合 |
|
||||
| render-label | `(info: {option: TreeOption, checked: boolean, selected: boolean}) => VNodeChild` | `undefined` | 节点内容的渲染函数 |
|
||||
| render-prefix | `(info: {option: TreeOption, checked: boolean, selected: boolean}) => VNodeChild` | `undefined` | 节点前缀的渲染函数 |
|
||||
| render-suffix | `(info: {option: TreeOption, checked: boolean, selected: boolean}) => VNodeChild` | `undefined` | 节点后缀的渲染函数 |
|
||||
| selectable | `boolean` | `true` | |
|
||||
| selected-keys | `Array<string \| number>` | `undefined` | 如果设定则 selected 状态受控 |
|
||||
| virtual-scroll | `boolean` | `false` | 是否启用虚拟滚动,启用前你需要设定好树的高度样式 |
|
||||
|
@ -40,7 +40,10 @@ import {
|
||||
AllowDrop,
|
||||
MotionData,
|
||||
treeInjectionKey,
|
||||
InternalTreeInst
|
||||
InternalTreeInst,
|
||||
RenderLabel,
|
||||
RenderPrefix,
|
||||
RenderSuffix
|
||||
} from './interface'
|
||||
import MotionWrapper from './MotionWrapper'
|
||||
import { defaultAllowDrop } from './dnd'
|
||||
@ -132,6 +135,9 @@ const treeProps = {
|
||||
default: true
|
||||
},
|
||||
virtualScroll: Boolean,
|
||||
renderLabel: Function as PropType<RenderLabel>,
|
||||
renderPrefix: Function as PropType<RenderPrefix>,
|
||||
renderSuffix: Function as PropType<RenderSuffix>,
|
||||
onDragenter: [Function, Array] as PropType<
|
||||
MaybeArray<(e: TreeDragInfo) => void>
|
||||
>,
|
||||
@ -981,6 +987,9 @@ export default defineComponent({
|
||||
pendingNodeKeyRef,
|
||||
internalScrollableRef: toRef(props, 'internalScrollable'),
|
||||
internalCheckboxFocusableRef: toRef(props, 'internalCheckboxFocusable'),
|
||||
renderLabelRef: toRef(props, 'renderLabel'),
|
||||
renderPrefixRef: toRef(props, 'renderPrefix'),
|
||||
renderSuffixRef: toRef(props, 'renderSuffix'),
|
||||
handleSwitcherClick,
|
||||
handleDragEnd,
|
||||
handleDragEnter,
|
||||
|
@ -216,6 +216,7 @@ const TreeNode = defineComponent({
|
||||
checkable,
|
||||
selectable,
|
||||
selected,
|
||||
checked,
|
||||
highlight,
|
||||
draggable,
|
||||
blockLine,
|
||||
@ -289,6 +290,8 @@ const TreeNode = defineComponent({
|
||||
<NTreeNodeContent
|
||||
ref="contentInstRef"
|
||||
clsPrefix={clsPrefix}
|
||||
checked={checked}
|
||||
selected={selected}
|
||||
onClick={
|
||||
blockLine || disabled ? undefined : this.handleContentClick
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { h, defineComponent, ref, PropType } from 'vue'
|
||||
import { h, defineComponent, ref, PropType, inject } from 'vue'
|
||||
import { render } from '../../_utils'
|
||||
import { TmNode } from './interface'
|
||||
import { TmNode, treeInjectionKey } from './interface'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TreeNodeContent',
|
||||
@ -13,6 +13,8 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
checked: Boolean,
|
||||
selected: Boolean,
|
||||
onClick: Function as PropType<(e: MouseEvent) => void>,
|
||||
onDragstart: Function as PropType<(e: DragEvent) => void>,
|
||||
tmNode: {
|
||||
@ -21,6 +23,9 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const { renderLabelRef, renderPrefixRef, renderSuffixRef } =
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
inject(treeInjectionKey)!
|
||||
const selfRef = ref<HTMLElement | null>(null)
|
||||
function doClick (e: MouseEvent): void {
|
||||
const { onClick } = props
|
||||
@ -31,15 +36,24 @@ export default defineComponent({
|
||||
}
|
||||
return {
|
||||
selfRef,
|
||||
renderLabel: renderLabelRef,
|
||||
renderPrefix: renderPrefixRef,
|
||||
renderSuffix: renderSuffixRef,
|
||||
handleClick
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const {
|
||||
clsPrefix,
|
||||
checked = false,
|
||||
selected = false,
|
||||
renderLabel,
|
||||
renderPrefix,
|
||||
renderSuffix,
|
||||
handleClick,
|
||||
onDragstart,
|
||||
tmNode: {
|
||||
rawNode,
|
||||
rawNode: { prefix, label, suffix }
|
||||
}
|
||||
} = this
|
||||
@ -51,17 +65,35 @@ export default defineComponent({
|
||||
draggable={onDragstart === undefined ? undefined : true}
|
||||
onDragstart={onDragstart}
|
||||
>
|
||||
{prefix ? (
|
||||
{renderPrefix || prefix ? (
|
||||
<div class={`${clsPrefix}-tree-node-content__prefix`}>
|
||||
{render(prefix)}
|
||||
{renderPrefix
|
||||
? renderPrefix({
|
||||
option: rawNode,
|
||||
selected,
|
||||
checked
|
||||
})
|
||||
: render(prefix)}
|
||||
</div>
|
||||
) : null}
|
||||
<div class={`${clsPrefix}-tree-node-content__text`}>
|
||||
{render(label)}
|
||||
{renderLabel
|
||||
? renderLabel({
|
||||
option: rawNode,
|
||||
selected,
|
||||
checked
|
||||
})
|
||||
: render(label)}
|
||||
</div>
|
||||
{suffix ? (
|
||||
{renderSuffix || suffix ? (
|
||||
<div class={`${clsPrefix}-tree-node-content__suffix`}>
|
||||
{render(suffix)}
|
||||
{renderSuffix
|
||||
? renderSuffix({
|
||||
option: rawNode,
|
||||
selected,
|
||||
checked
|
||||
})
|
||||
: render(suffix)}
|
||||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
|
@ -20,6 +20,24 @@ export type TreeOption = TreeOptionBase & { [k: string]: unknown }
|
||||
|
||||
export type TreeOptions = TreeOption[]
|
||||
|
||||
export interface TreeRenderProps {
|
||||
option: TreeOption
|
||||
checked: boolean
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
type RenderTreePart = ({
|
||||
option,
|
||||
checked,
|
||||
selected
|
||||
}: TreeRenderProps) => VNodeChild
|
||||
|
||||
export type RenderLabel = RenderTreePart
|
||||
|
||||
export type RenderPrefix = RenderTreePart
|
||||
|
||||
export type RenderSuffix = RenderTreePart
|
||||
|
||||
export interface TreeDragInfo {
|
||||
event: DragEvent
|
||||
node: TreeOption
|
||||
@ -78,6 +96,9 @@ export interface TreeInjection {
|
||||
pendingNodeKeyRef: Ref<null | Key>
|
||||
internalScrollableRef: Ref<boolean>
|
||||
internalCheckboxFocusableRef: Ref<boolean>
|
||||
renderLabelRef: Ref<RenderLabel | undefined>
|
||||
renderPrefixRef: Ref<RenderPrefix | undefined>
|
||||
renderSuffixRef: Ref<RenderSuffix | undefined>
|
||||
handleSwitcherClick: (node: TreeNode<TreeOption>) => void
|
||||
handleSelect: (node: TreeNode<TreeOption>) => void
|
||||
handleCheck: (node: TreeNode<TreeOption>, checked: boolean) => void
|
||||
|
@ -63,4 +63,33 @@ describe('n-tree', () => {
|
||||
expect(wrapper.find('.n-tree-node-content__suffix').exists()).toBe(true)
|
||||
expect(wrapper.find('.n-tree-node-content__suffix').text()).toBe('suffix')
|
||||
})
|
||||
|
||||
it('should work with `render-label`, `render-prefix` and `render-suffix`', async () => {
|
||||
const wrapper = mount(NTree, {
|
||||
props: {
|
||||
data: [
|
||||
{
|
||||
label: 'test',
|
||||
key: '123',
|
||||
children: [
|
||||
{
|
||||
label: '123',
|
||||
key: '123'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
renderPrefix: () => 'prefix',
|
||||
renderLabel: () => 'label',
|
||||
renderSuffix: () => 'suffix'
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('.n-tree-node-content__prefix').exists()).toBe(true)
|
||||
expect(wrapper.find('.n-tree-node-content__prefix').text()).toBe('prefix')
|
||||
expect(wrapper.find('.n-tree-node-content__text').exists()).toBe(true)
|
||||
expect(wrapper.find('.n-tree-node-content__text').text()).toBe('label')
|
||||
expect(wrapper.find('.n-tree-node-content__suffix').exists()).toBe(true)
|
||||
expect(wrapper.find('.n-tree-node-content__suffix').text()).toBe('suffix')
|
||||
})
|
||||
})
|
||||
|
@ -38,7 +38,7 @@ before-upload
|
||||
| with-credentials | `boolean` | `false` | If cookie attached. |
|
||||
| on-change | `(options: { file: UploadFile, fileList: Array<UploadFile>, event?: Event }) => void` | `() => {}` | The callback of status change of the component. Any file status change would fire the callback. |
|
||||
| on-update:file-list | `(fileList: UploadFile[]) => void` | `undefined` | Callback function triggered on fileList changes. |
|
||||
| on-finish | `(options: { file: UploadFile }) => UploadFile \| void` | `({ file }) => file` | The callback of file upload finish. You can modify the UploadFile or retun a new UploadFile. |
|
||||
| on-finish | `(options: { file: UploadFile, event: Event }) => UploadFile \| void` | `({ file }) => file` | The callback of file upload finish. You can modify the UploadFile or retun a new UploadFile. |
|
||||
| on-remove | `(options: { file: UploadFile, fileList: Array<UploadFile> }) => boolean \| Promise<boolean> \| any` | `() => true` | The callback of file removal. Return false, promise resolve false or promise reject will cancel this removal. |
|
||||
| on-before-upload | `(options: { file: UploadFile, fileList: Array<UploadFile> }) => (Promise<boolean \| void> \| boolean \| void)` | `true` | Callback before file is uploaded, return false or a Promise that resolve false or reject will cancel this upload. |
|
||||
|
||||
|
@ -12,11 +12,19 @@ You can change file's property when upload finishes.
|
||||
```
|
||||
|
||||
```js
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
handleFinish ({ file }) {
|
||||
setup() {
|
||||
const message = useMessage()
|
||||
const handleFinish = ({ file, event }) => {
|
||||
message.success(event.target.response)
|
||||
file.url = 'http://www.mocky.io/v2/5e4bafc63100007100d8b70f'
|
||||
}
|
||||
return {
|
||||
message,
|
||||
handleFinish
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -37,7 +37,7 @@ before-upload
|
||||
| show-retry-button | `boolean` | `true` | 是否显示重新上传按钮(在 error 时展示) |
|
||||
| with-credentials | `boolean` | `false` | 是否携带 Cookie |
|
||||
| on-change | `(options: { file: UploadFile, fileList: Array<UploadFile>, event?: Event }) => void` | `() => {}` | 组件状态变化的回调,组件的任何文件状态变化都会触发回调 |
|
||||
| on-finish | `(options: { file: UploadFile }) => UploadFile \| void` | `({ file }) => file` | 文件上传结束的回调,可以修改传入的 UploadFile 或者返回一个新的 UploadFile |
|
||||
| on-finish | `(options: { file: UploadFile, event: Event }) => UploadFile \| void` | `({ file }) => file` | 文件上传结束的回调,可以修改传入的 UploadFile 或者返回一个新的 UploadFile |
|
||||
| on-update:file-list | `(fileList: UploadFile[]) => void` | `undefined` | 当 file-list 改变时触发的回调函数 |
|
||||
| on-before-upload | `(options: { file: UploadFile, fileList: UploadFile[] }) => (Promise<boolean \| void> \| boolean \| void)` | `undefined` | 文件上传之前的回调,返回 `false`、`Promise resolve false`、`Promise rejected` 时会取消本次上传 |
|
||||
|
||||
|
@ -12,11 +12,19 @@
|
||||
```
|
||||
|
||||
```js
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
handleFinish ({ file }) {
|
||||
setup() {
|
||||
const message = useMessage()
|
||||
const handleFinish = ({ file, event }) => {
|
||||
message.success(event.target.response)
|
||||
file.url = 'http://www.mocky.io/v2/5e4bafc63100007100d8b70f'
|
||||
}
|
||||
return {
|
||||
message,
|
||||
handleFinish
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -58,7 +58,7 @@ function createXhrHandlers (
|
||||
})
|
||||
XhrMap.delete(file.id)
|
||||
fileAfterChange =
|
||||
inst.onFinish?.({ file: fileAfterChange }) || fileAfterChange
|
||||
inst.onFinish?.({ file: fileAfterChange, event: e }) || fileAfterChange
|
||||
doChange(fileAfterChange, e)
|
||||
},
|
||||
handleXHRAbort (e) {
|
||||
|
@ -21,7 +21,13 @@ export type OnChange = (data: {
|
||||
fileList: FileInfo[]
|
||||
event: ProgressEvent | Event | undefined
|
||||
}) => void
|
||||
export type OnFinish = ({ file }: { file: FileInfo }) => FileInfo | undefined
|
||||
export type OnFinish = ({
|
||||
file,
|
||||
event
|
||||
}: {
|
||||
file: FileInfo
|
||||
event: Event
|
||||
}) => FileInfo | undefined
|
||||
export type OnRemove = (data: {
|
||||
file: FileInfo
|
||||
fileList: FileInfo[]
|
||||
|
@ -1 +1 @@
|
||||
export default '2.15.4'
|
||||
export default '2.15.5'
|
||||
|
@ -7,3 +7,4 @@
|
||||
| 0.1.8 | 2.7.3 |
|
||||
| 0.1.9 | 2.10.0 |
|
||||
| 0.1.10 | 2.15.2 |
|
||||
| 0.1.11 | 2.15.4 |
|
||||
|
@ -416,7 +416,7 @@ export const themeOverridesDark: GlobalThemeOverrides = {
|
||||
inputWidth: '80px',
|
||||
selectWidth: '100px',
|
||||
inputMargin: '0 20px',
|
||||
itemMargin: '0 20px 0 0',
|
||||
itemMargin: '0 0 0 20px',
|
||||
itemBorder: '0 solid #0000',
|
||||
itemBorderActive: '0 solid #0000',
|
||||
itemBorderDisabled: '0 solid #0000',
|
||||
|
@ -316,7 +316,7 @@ export const themeOverridesLight: GlobalThemeOverrides = {
|
||||
inputWidth: '80px',
|
||||
selectWidth: '100px',
|
||||
inputMargin: '0 20px',
|
||||
itemMargin: '0 20px 0 0',
|
||||
itemMargin: '0 0 0 20px',
|
||||
itemBorder: '0 solid #0000',
|
||||
itemBorderHover: '0 solid #0000',
|
||||
itemBorderActive: '0 solid #0000',
|
||||
|
@ -2,7 +2,7 @@ import {
|
||||
useDialog as _useDialog,
|
||||
DialogOptions,
|
||||
DialogReactive,
|
||||
DialogApiInjection
|
||||
DialogApi
|
||||
} from 'naive-ui'
|
||||
import { icons } from './icons'
|
||||
|
||||
@ -10,7 +10,7 @@ export interface ExtendedApi {
|
||||
danger: (options: DialogOptions) => DialogReactive
|
||||
}
|
||||
|
||||
export type TsDialogApi = DialogApiInjection & ExtendedApi
|
||||
export type TsDialogApi = DialogApi & ExtendedApi
|
||||
|
||||
function useDialog (): TsDialogApi {
|
||||
const dialog = _useDialog()
|
||||
|
Loading…
Reference in New Issue
Block a user