Merge branch 'main' of github.com:TuSimple/naive-ui into main

This commit is contained in:
caoyugang 2021-06-12 16:46:04 +08:00
commit c89aa66f02
71 changed files with 792 additions and 239 deletions

View File

@ -4,12 +4,12 @@ about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
### Environment Info
- Naive UI version: (eg. 2.11.0)
- Vue version: (eg. 3.0.11)
- Browser Info: (eg. Chome 91)
- System Info: (eg. Mac OS 11.2.3, Windows)

View File

@ -4,9 +4,10 @@ about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
## What problem does the feature solve?
## What does the proposed API look like?
## Some pictures that can demonstrate the feature.

View File

@ -1,8 +1,18 @@
<!--
Please add the the changelog to `CHANGELOG.zh-CN.md` & `CHANGELOG.en-US.md` in the PR.
You can only add detailed info in one of them and add a placeholder item in the other (for example `- ***`).
!!! Read the following content if this is your first pull request !!!
1. If you are working on docs, please set `main` branch as the target branch.
2. If you are working on bug fixes, please set `main` branch as the target branch.
3. If you are working on new features, please set `feat` branch as the target branch.
For people who are working on 2 & 3, please add changelog to `CHANGELOG.zh-CN.md` & `CHANGELOG.en-US.md` in the PR. You need to add changelog to both files. You can use English in both file. The develop team will translate it later.
-->
<!--
请在这个 PR 里向 `CHANGELOG.zh-CN.md``CHANGELOG.en-US.md` 添加这个 PR 的变更记录,
你可以只在其中一个添加详细的信息,然后在另一个文件中留着一个占位的项,例如 `- ***`
!!! 如果这是你第一次提交 PR请阅读下面的内容 !!!
1. 如果你在修改文档,请提交到 `main` 分支
2. 如果你在修复 Bug请提交到 `main` 分支
3. 如果你在开发新的特性,请提交到 `feat` 分支
对于进行工作 2 & 3 的人,请在 `CHANGELOG.zh-CN.md` & `CHANGELOG.en-US.md` 中添加变更日志,你需要在两个文件中都添加变更日志。你可以只使用中文,开发团队会在之后进行翻译。
-->

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View File

@ -1,6 +1,31 @@
# CHANGELOG
## Pending
## 2.11.7
### Fixes
- Fix `n-slider` doesn't prevent scrolling when touchstart.
- Fix `n-color-picker`'s default value doesn't follow modes.
- Fix not `lodash` & `lodash-es` type.
## 2.11.6 (2021-06-11)
### Feats
- `n-spin`'s `size` prop support number.
- `n-date-picker` add `footer` slot.
### Fixes
- Fix `n-slider` doesn't support touch events
- Fix `n-button` causes crash when it's imported in script inside head tag. [#68](https://github.com/TuSimple/naive-ui/pull/68)
- Fix `n-spin` animation shifts.
- Fix `n-menu` lack `on-update-value` and `on-update-expanded-keys` props.
- Fix `n-popconfirm` icon slot not working.
- Fix `n-tabs` logs useless info.
- Fix `n-color-picker` set `modes` not working. [#77](https://github.com/TuSimple/naive-ui/issues/77)
## 2.11.5 (2021-06-10)
### Feats
@ -15,6 +40,7 @@
- Fix `n-calendar` date calculate incorrectly
- Fix `n-input` missing the `password` type declaration.
- Fix `n-menu` the type definition of `extra` property of menu and submenu.
- Fix `n-dropdown` mouse cursor is not pointer.
## 2.11.4

View File

@ -1,6 +1,31 @@
# CHANGELOG
## Pending
## 2.11.7
### Fixes
- 修复 `n-slider` 在 touchstart 发生时没有阻止滚动
- 修复 `n-color-picker` 默认值不跟随模式设定
- 修复缺少 `lodash` & `lodash-es` 类型
## 2.11.6 (2021-06-11)
### Feats
- `n-spin``size` 属性支持 number 类型
- `n-date-picker` 支持 `footer` 插槽
### Fixes
- 修正 `n-slider` 不支持触摸事件
- 修正 `n-button` 在 head 内部的 script 被引入造成崩溃 [#68](https://github.com/TuSimple/naive-ui/pull/68)
- 修正 `n-spin` 动画闪烁
- 修正 `n-menu` 缺少 `on-update-value``on-update-expanded-keys` 属性
- 修正 `n-popconfirm` icon slot 不生效
- 修正 `n-tabs` 在控制台输出无用信息
- 修正 `n-color-picker` 设定 `modes` 无效 [#77](https://github.com/TuSimple/naive-ui/issues/77)
## 2.11.5 (2021-06-10)
### Feats
@ -15,6 +40,7 @@
- 修复 `n-calendar` 展示日期计算错误
- 修复 `n-input` 缺失 `password` 的声明
- 修复 `n-menu` 的菜单和子菜单的 `extra` 属性的类型定义
- 修复 `n-dropdown` 选项鼠标形状不是 pointer
## 2.11.4

View File

@ -13,6 +13,11 @@
[www.naiveui.com](http://www.naiveui.com)
## Community
- [Discord](https://discord.gg/Pqv7Mev5Dd)
- DingTalk Group 33482509
## Features
### Fairly Complete
@ -23,13 +28,13 @@ What's more, they are all treeshakable.
### Customizable Themes
We provide an advanced type safe theme system that built Uses TypeScript. All you need is to provide a theme overrides object in JS. Then all the stuffs will be done by us.
We provide an advanced type safe theme system built using TypeScript. All you need is to provide a theme overrides object in JS. Then all the stuff will be done by us.
What's more, no less/sass/css variables, no webpack loaders are required.
### Uses TypeScript
All the staff in Naive UI is written in TypeScript. It can work with your typescript project seamlessly.
All the stuff in Naive UI is written in TypeScript. It can work with your typescript project seamlessly.
What's more, you don't need to import any CSS to use the components.
@ -63,11 +68,6 @@ Naive UI recommends using [xicons](https://www.xicons.org) as icon library.
Working in progress.
## Community
- [Discord](https://discord.gg/Pqv7Mev5Dd)
- DingTalk Group 33482509
## Contributing
Please see [CONTRIBUTING.md](https://github.com/TuSimple/naive-ui/blob/main/CONTRIBUTING.md).

View File

@ -13,6 +13,11 @@
[www.naiveui.com](http://www.naiveui.com)
## 社区
- [Discord](https://discord.gg/Pqv7Mev5Dd)
- 钉钉群 33482509
## 特性
### 比较完整
@ -63,11 +68,6 @@ naive-ui 建议使用 [xicons](https://www.xicons.org) 作为图标库。
正在搞。
## 社区
- [Discord](https://discord.gg/Pqv7Mev5Dd)
- 钉钉群 33482509
## 贡献
请参考 [CONTRIBUTING.md](https://github.com/TuSimple/naive-ui/blob/main/CONTRIBUTING.md)。

View File

@ -1,6 +1,6 @@
{
"name": "naive-ui",
"version": "2.11.4",
"version": "2.11.7",
"description": "A Vue 3 Component Library. Fairly Complete, Customizable Themes, Uses TypeScript, Not Too Slow",
"main": "lib/index.js",
"module": "es/index.js",
@ -22,7 +22,9 @@
"test:cov": "cross-env NODE_ENV=test jest",
"test:watch": "cross-env NODE_ENV=test jest ---watch --verbose --coverage",
"gen-version": "node scripts/gen-version",
"build:site:ts": "./scripts/pre-build-site/pre-build-site.sh && cross-env TUSIMPLE=true NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 vite build && ./scripts/post-build-site/post-build-site.sh"
"post-changelog": "node scripts/post-changelog",
"build:site:ts": "./scripts/pre-build-site/pre-build-site.sh && cross-env TUSIMPLE=true NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 vite build && ./scripts/post-build-site/post-build-site.sh",
"prepare": "husky install"
},
"author": "07akioni",
"license": "MIT",
@ -66,7 +68,6 @@
"@rollup/plugin-babel": "^5.3.0",
"@types/estree": "^0.0.48",
"@types/jest": "^26.0.20",
"@types/lodash-es": "^4.17.4",
"@typescript-eslint/eslint-plugin": "^4.15.1",
"@typescript-eslint/parser": "^4.15.1",
"@vicons/fluent": "^0.8.0",
@ -95,23 +96,27 @@
"eslint-plugin-vue": "^7.6.0",
"express": "^4.17.1",
"fs-extra": "^10.0.0",
"husky": "^4.3.5",
"husky": "^6.0.0",
"inquirer": "^8.1.0",
"jest": "^27.0.4",
"lint-staged": "^11.0.0",
"marked": "^2.0.1",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
"superagent": "^6.1.0",
"typescript": "^4.3.2",
"vite": "^2.1.3"
},
"dependencies": {
"@css-render/plugin-bem": "^0.15.2",
"@css-render/vue3-ssr": "^0.15.2",
"@types/lodash": "^4.14.170",
"@types/lodash-es": "^4.17.4",
"async-validator": "^3.5.1",
"css-render": "^0.15.2",
"date-fns": "^2.19.0",
"evtd": "^0.2.2",
"highlight.js": "^10.7.1",
"highlight.js": "^11.0.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"seemly": "^0.3.1",
@ -123,11 +128,6 @@
"vue-router": "^4.0.5",
"vueuc": "^0.4.7"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"sideEffects": false,
"homepage": "https://www.naiveui.com",
"repository": {

46
scripts/post-changelog.js Normal file
View File

@ -0,0 +1,46 @@
const request = require('superagent')
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const { DINGTALK_TOKEN } = process.env
if (!DINGTALK_TOKEN) {
console.log('No DINGTALK_TOKEN in your env.')
process.exit(0)
}
const changelog = fs
.readFileSync(path.resolve(__dirname, '../CHANGELOG.zh-CN.md'), 'utf-8')
.split(/^## /gm)[1]
.replace(/^##/gm, '')
const message = `变更日志 ${changelog}`
inquirer
.prompt([
{
type: 'confirm',
name: 'post-changelog',
message: `发布以下变更日志到钉钉群:\n\n${message}`
}
])
.then((ans) => {
if (ans['post-changelog']) {
request
.post('https://oapi.dingtalk.com/robot/send')
.query({
access_token: DINGTALK_TOKEN
})
.type('application/json')
.send({
msgtype: 'text',
text: {
content: message
}
})
.then((res) => {
console.log(res.text)
})
}
})

View File

@ -3,6 +3,10 @@ import { useStyle } from '../../../_mixins'
import NIconSwitchTransition from '../../icon-switch-transition'
import style from './styles/index.cssr'
const duration = '1.6s'
// The loading svg dom comes from https://codepen.io/FezVrasta/pen/oXrgdR
export default defineComponent({
name: 'BaseLoading',
props: {
@ -35,33 +39,62 @@ export default defineComponent({
useStyle('BaseLoading', style, toRef(props, 'clsPrefix'))
},
render () {
const { clsPrefix } = this
const { clsPrefix, radius, strokeWidth, stroke, scale } = this
const scaledRadius = radius / scale
return (
<div
class={`${clsPrefix}-base-loading`}
style={{ stroke: this.stroke }}
role="img"
aria-label="loading"
>
<div class={`${clsPrefix}-base-loading`} role="img" aria-label="loading">
<NIconSwitchTransition>
{{
default: () =>
this.show ? (
<svg
key="loading"
class={`${clsPrefix}-base-loading-circular ${clsPrefix}-base-loading__icon`}
viewBox={`0 0 ${(this.radius * 2) / this.scale} ${
(this.radius * 2) / this.scale
}`}
class={`${clsPrefix}-base-loading__icon`}
viewBox={`0 0 ${2 * scaledRadius} ${2 * scaledRadius}`}
xmlns="http://www.w3.org/2000/svg"
style={{ color: stroke }}
>
<circle
style={{ strokeWidth: this.strokeWidth }}
class={`${clsPrefix}-base-loading-circular-path`}
cx={this.radius / this.scale}
cy={this.radius / this.scale}
fill="none"
r={this.radius - this.strokeWidth / 2}
/>
<g>
<animateTransform
attributeName="transform"
type="rotate"
values={`0 ${scaledRadius} ${scaledRadius};270 ${scaledRadius} ${scaledRadius}`}
begin="0s"
dur={duration}
fill="freeze"
repeatCount="indefinite"
/>
<circle
fill="none"
stroke="currentColor"
stroke-width={strokeWidth}
stroke-linecap="round"
cx={scaledRadius}
cy={scaledRadius}
r={radius - strokeWidth / 2}
stroke-dasharray={5.67 * radius}
stroke-dashoffset={18.48 * radius}
>
<animateTransform
attributeName="transform"
type="rotate"
values={`0 ${scaledRadius} ${scaledRadius};135 ${scaledRadius} ${scaledRadius};450 ${scaledRadius} ${scaledRadius}`}
begin="0s"
dur={duration}
fill="freeze"
repeatCount="indefinite"
/>
<animate
attributeName="stroke-dashoffset"
values={`${5.67 * radius};${1.42 * radius};${
5.67 * radius
}`}
begin="0s"
dur={duration}
fill="freeze"
repeatCount="indefinite"
/>
</circle>
</g>
</svg>
) : (
<div

View File

@ -1,65 +1,25 @@
import { c, cB, cE } from '../../../../_utils/cssr'
import { cB, cE } from '../../../../_utils/cssr'
import iconSwitchTransition from '../../../../_styles/transitions/icon-switch.cssr'
const dashOffset = 500
export default c([
cB('base-loading', `
position: relative;
line-height: 0;
width: 1em;
height: 1em;
`, [
cE('placeholder', {
position: 'absolute',
export default cB('base-loading', `
position: relative;
line-height: 0;
width: 1em;
height: 1em;
`, [
cE('placeholder', {
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translateX(-50%) translateY(-50%)'
}, [
iconSwitchTransition({
left: '50%',
top: '50%',
transform: 'translateX(-50%) translateY(-50%)'
}, [
iconSwitchTransition({
left: '50%',
top: '50%',
originalTransform: 'translateX(-50%) translateY(-50%)'
})
]),
cE('icon', [
iconSwitchTransition()
]),
cB('base-loading-circular', `
stroke: currentColor;
height: 100%;
width: 100%;
animation: n-base-loading-rotate 1.5s linear infinite;
transform-origin: center;
`, [
cB('base-loading-circular-path', `
transform-origin: center;
animation: n-base-loading-dash 1.5s ease-in-out infinite;
stroke-dasharray: ${dashOffset};
stroke-dashoffset: 0;
stroke-linecap: round;
`)
])
originalTransform: 'translateX(-50%) translateY(-50%)'
})
]),
c('@keyframes n-base-loading-rotate', `
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(270deg);
}
`),
c('@keyframes n-base-loading-dash', `
0% {
stroke-dashoffset: ${dashOffset};
}
50% {
stroke-dashoffset: ${dashOffset / 4};
transform: rotate(135deg);
}
100% {
stroke-dashoffset: ${dashOffset};
transform: rotate(450deg);
}
`)
cE('icon', [
iconSwitchTransition()
])
])

View File

@ -32,8 +32,8 @@ export default defineComponent({
<NBaseLoading
clsPrefix={clsPrefix}
class={`${clsPrefix}-base-selection__mark`}
strokeWidth={20}
scale={0.8}
strokeWidth={24}
scale={0.85}
show={props.loading}
>
{{

View File

@ -1,5 +1,5 @@
import { inject, computed, ComputedRef } from 'vue'
import type { highlight, getLanguage } from 'highlight.js'
import type { HLJSApi } from 'highlight.js'
import { configProviderInjectionKey } from '../config-provider/src/ConfigProvider'
import { warn } from '../_utils'
@ -9,8 +9,8 @@ interface UseHljsProps {
}
export interface Hljs {
highlight: typeof highlight
getLanguage: typeof getLanguage
highlight: HLJSApi['highlight']
getLanguage: HLJSApi['getLanguage']
}
export default function useHljs (
props: UseHljsProps

51
src/_mixins/use-rtl.ts Normal file
View File

@ -0,0 +1,51 @@
import { Ref, onBeforeMount, inject, watchEffect, computed } from 'vue'
import { ssrInjectionKey } from '../ssr/context'
import {
RtlEnabledState,
RtlItem
} from '../config-provider/src/internal-interface'
// The current implemention will take extra perf & memory usage. I just want to
// make it work now. If we can determine whether the style is already mounted,
// we won't need to watch effect. However, we need to make css-render support
// it. We need to refactor ssrAdapter and expose a exists function
export default function useRtl (
mountId: string,
rtlStateRef: Ref<RtlEnabledState | undefined> | undefined,
clsPrefixRef: Ref<string>
): Ref<RtlItem | undefined> | undefined {
if (!rtlStateRef) return undefined
const ssrAdapter = inject(ssrInjectionKey, undefined)
const componentRtlStateRef = computed(() => {
const { value: rtlState } = rtlStateRef
if (!rtlState) {
return undefined
}
const componentRtlState = rtlState[mountId as keyof RtlEnabledState]
if (!componentRtlState) {
return undefined
}
return componentRtlState
})
const mountStyle = (): void => {
watchEffect(() => {
const { value: clsPrefix } = clsPrefixRef
const { value: componentRtlState } = componentRtlStateRef
if (!componentRtlState) return
componentRtlState.style.mount({
id: `${clsPrefix}${mountId}Rtl`,
head: true,
props: {
bPrefix: clsPrefix ? `.${clsPrefix}-` : undefined
},
ssr: ssrAdapter
})
})
}
if (ssrAdapter) {
mountStyle()
} else {
onBeforeMount(mountStyle)
}
return componentRtlStateRef
}

View File

@ -18,6 +18,7 @@ ghost
loading
color
group
rtl-debug
debug
```

View File

@ -0,0 +1,24 @@
# Rtl Debug
```html
<n-space vertical>
<n-space><n-switch v-model:value="rtlEnabled" />Rtl</n-space>
<n-config-provider :rtl="rtlEnabled ? rtlStyles : undefined">
<n-button>Rtl Test</n-button>
</n-config-provider>
</n-space>
```
```js
import { defineComponent, ref } from 'vue'
import { unstableButtonRtl } from 'naive-ui'
export default defineComponent({
setup () {
return {
rtlEnabled: ref(false),
rtlStyles: [unstableButtonRtl]
}
}
})
```

View File

@ -28,6 +28,7 @@ import type { ButtonTheme } from '../styles'
import { buttonGroupInjectionKey } from './ButtonGroup'
import type { Type, Size } from './interface'
import style from './styles/button.cssr'
import useRtl from '../../_mixins/use-rtl'
const buttonProps = {
...(useTheme.props as ThemeProps<ButtonTheme>),
@ -157,7 +158,7 @@ const Button = defineComponent({
const handleBlur = (): void => {
enterPressedRef.value = false
}
const { mergedClsPrefixRef } = useConfig(props)
const { mergedClsPrefixRef, NConfigProvider } = useConfig(props)
const themeRef = useTheme(
'Button',
'Button',
@ -166,6 +167,11 @@ const Button = defineComponent({
props,
mergedClsPrefixRef
)
const rtlEnabledRef = useRtl(
'Button',
NConfigProvider?.mergedRtlRef,
mergedClsPrefixRef
)
return {
selfRef,
waveRef,
@ -174,6 +180,7 @@ const Button = defineComponent({
mergedSize: mergedSizeRef,
showBorder: showBorderRef,
enterPressed: enterPressedRef,
rtlEnabled: rtlEnabledRef,
handleMouseDown,
handleKeyDown,
handleBlur,
@ -390,6 +397,7 @@ const Button = defineComponent({
`${mergedClsPrefix}-button`,
`${mergedClsPrefix}-button--${this.type}-type`,
{
[`${mergedClsPrefix}-button--rtl`]: this.rtlEnabled,
[`${mergedClsPrefix}-button--disabled`]: this.disabled,
[`${mergedClsPrefix}-button--block`]: this.block,
[`${mergedClsPrefix}-button--pressed`]: this.enterPressed,
@ -429,7 +437,7 @@ const Button = defineComponent({
clsPrefix={mergedClsPrefix}
key="loading"
class={`${mergedClsPrefix}-icon-slot`}
strokeWidth={24}
strokeWidth={20}
/>
) : (
<div

View File

@ -0,0 +1,7 @@
import { cB, cM } from '../../../_utils/cssr'
export default cB('button', [
cM('rtl', `
direction: rtl;
`)
])

View File

@ -154,7 +154,7 @@ export default c([
animationName: 'button-wave-spread, button-wave-opacity'
})
]),
(typeof window !== 'undefined' && 'MozBoxSizing' in document.body.style)
(typeof window !== 'undefined' && 'MozBoxSizing' in document.createElement('div').style)
? c('&::moz-focus-inner', {
border: 0
})

View File

@ -1,3 +1,4 @@
export { default as buttonDark } from './dark'
export { default as buttonLight } from './light'
export { default as buttonRtl } from './rtl'
export type { ButtonThemeVars, ButtonTheme } from './light'

6
src/button/styles/rtl.ts Normal file
View File

@ -0,0 +1,6 @@
import rtlStyle from '../src/styles/button-rtl.cssr'
export default {
name: 'Button',
style: rtlStyle
}

View File

@ -16,6 +16,7 @@ closable
no-title
loading
custom-style
rtl-debug
```
## Props

View File

@ -0,0 +1,30 @@
# Rtl Debug
```html
<n-space vertical>
<n-space><n-switch v-model:value="rtlEnabled" />Rtl</n-space>
<n-config-provider :rtl="rtlEnabled ? rtlStyles : undefined">
<n-card closable>
<template #header>Rtl Header Test</template>
<template #header-extra>Rtl Header Extra Test</template>
Rtl Content Test
<template #footer>Rtl Header Test</template>
<template #action>Rtl Action Test</template>
</n-card>
</n-config-provider>
</n-space>
```
```js
import { defineComponent, ref } from 'vue'
import { unstableCardRtl } from 'naive-ui'
export default defineComponent({
setup () {
return {
rtlEnabled: ref(false),
rtlStyles: [unstableCardRtl]
}
}
})
```

View File

@ -6,6 +6,7 @@ import {
renderSlot,
CSSProperties
} from 'vue'
import { getPadding } from 'seemly'
import { useConfig, useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { call, createKey, keysOf } from '../../_utils'
@ -14,7 +15,7 @@ import { NBaseClose } from '../../_internal'
import { cardLight } from '../styles'
import type { CardTheme } from '../styles'
import style from './styles/index.cssr'
import { getPadding } from 'seemly'
import useRtl from '../../_mixins/use-rtl'
export interface Segmented {
content?: boolean | 'soft'
@ -64,7 +65,7 @@ export default defineComponent({
const { onClose } = props
if (onClose) call(onClose)
}
const { mergedClsPrefixRef } = useConfig(props)
const { mergedClsPrefixRef, NConfigProvider } = useConfig(props)
const themeRef = useTheme(
'Card',
'Card',
@ -73,7 +74,13 @@ export default defineComponent({
props,
mergedClsPrefixRef
)
const rtlEnabledRef = useRtl(
'Card',
NConfigProvider?.mergedRtlRef,
mergedClsPrefixRef
)
return {
rtlEnabled: rtlEnabledRef,
mergedClsPrefix: mergedClsPrefixRef,
mergedTheme: themeRef,
handleCloseClick,
@ -137,12 +144,20 @@ export default defineComponent({
}
},
render () {
const { segmented, bordered, hoverable, $slots, mergedClsPrefix } = this
const {
segmented,
bordered,
hoverable,
mergedClsPrefix,
rtlEnabled,
$slots
} = this
return (
<div
class={[
`${mergedClsPrefix}-card`,
{
[`${mergedClsPrefix}-card--rtl`]: rtlEnabled,
[`${mergedClsPrefix}-card--content${
typeof segmented !== 'boolean' && segmented.content === 'soft'
? '-soft'

View File

@ -0,0 +1,7 @@
import { cB, cM } from '../../../_utils/cssr'
export default cB('card', [
cM('rtl', `
direction: rtl;
`)
])

View File

@ -1,3 +1,4 @@
export { default as cardDark } from './dark'
export { default as cardLight } from './light'
export { default as cardRtl } from './rtl'
export type { CardTheme, CardThemeVars } from './light'

6
src/card/styles/rtl.ts Normal file
View File

@ -0,0 +1,6 @@
import rtlStyle from '../src/styles/rtl.cssr'
export default {
name: 'Card',
style: rtlStyle
}

View File

@ -176,8 +176,8 @@ export default defineComponent({
[`${mergedClsPrefix}-cascader-option--pending`]:
this.keyboardPending || this.hoverPending,
[`${mergedClsPrefix}-cascader-option--disabled`]: this.disabled,
[`${mergedClsPrefix}-cascader-option--show-prefix`]: this
.showCheckbox
[`${mergedClsPrefix}-cascader-option--show-prefix`]:
this.showCheckbox
}
]}
onMouseenter={this.mergedHandleMouseEnter}
@ -206,8 +206,8 @@ export default defineComponent({
{!this.isLeaf ? (
<NBaseLoading
clsPrefix={mergedClsPrefix}
scale={0.8}
strokeWidth={20}
scale={0.85}
strokeWidth={24}
show={this.isLoading}
class={`${mergedClsPrefix}-cascader-option-icon`}
>

View File

@ -8,6 +8,7 @@ Compared with real world, its space is discrete.
basic
alpha
size
modes
form
```
@ -16,7 +17,7 @@ form
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| default-show | `boolean` | `undefined` | Whether to show panel by default. |
| default-value | `string` | `#000000` | Default value of the picker. |
| default-value | `string` | Black color value of 1st mode's corresponding value. | Default value of the picker. |
| modes | `Array<'rgb' \| 'hex' \| 'hsl' \| 'hsv'>` | `['rgb', 'hex', 'hsl']` | The value format of the picker. Notice that value will follow the mode once you select a new value from the picker. |
| to | `string \| HTMLElement` | `'body'` | Where to detach the panel. |
| show | `boolean` | `undefined` | Whether to show the panel. |

View File

@ -0,0 +1,7 @@
# Set Mode
Use `modes` to set available modes.
```html
<n-color-picker :modes="['hex']" />
```

View File

@ -8,6 +8,7 @@
basic
alpha
size
modes
form
```
@ -16,7 +17,7 @@ form
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| default-show | `boolean` | `undefined` | 默认是否展示弹出层 |
| default-value | `string` | `#000000` | 默认的颜色值 |
| default-value | `string \| null` | 和第一个 mode 对应的黑色值 | 默认的颜色值 |
| modes | `Array<'rgb' \| 'hex' \| 'hsl' \| 'hsv'>` | `['rgb', 'hex', 'hsl']` | 颜色选择器支持颜色的格式,注意一旦你在某个模式下选择了值,颜色选择器值的格式将跟随这个格式 |
| to | `string \| HTMLElement` | `'body'` | 面板的卸载位置 |
| show | `boolean` | `undefined` | 是否展示面板 |

View File

@ -0,0 +1,7 @@
# 设定模式
使用 `modes` 设定可选模式。
```html
<n-color-picker :modes="['hex']" />
```

View File

@ -27,6 +27,10 @@ export default defineComponent({
type: String as PropType<ColorPickerMode>,
required: true
},
modes: {
type: Array as PropType<ColorPickerMode[]>,
required: true
},
showAlpha: {
type: Boolean,
required: true
@ -37,7 +41,7 @@ export default defineComponent({
default: null
},
valueArr: {
type: (Array as unknown) as PropType<HSVA | RGBA | HSLA | null>,
type: Array as unknown as PropType<HSVA | RGBA | HSLA | null>,
default: null
},
onUpdateValue: {
@ -89,26 +93,32 @@ export default defineComponent({
}
},
render () {
const { clsPrefix } = this
const { clsPrefix, modes } = this
return (
<div class={`${clsPrefix}-color-picker-input`}>
<div
class={`${clsPrefix}-color-picker-input__mode`}
onClick={this.onUpdateMode}
style={{
cursor: modes.length === 1 ? '' : 'pointer'
}}
>
{this.mode.toUpperCase() + (this.showAlpha ? 'A' : '')}
</div>
<NInputGroup>
{{
default: () => {
const { mode, valueArr, value, showAlpha } = this
const { mode, valueArr, showAlpha } = this
if (mode === 'hex') {
// hex and rgba shares the same value arr
let hexValue = null
try {
hexValue =
value === null
valueArr === null
? null
: (showAlpha ? toHexaString : toHexString)(value)
: (showAlpha ? toHexaString : toHexString)(
valueArr as RGBA
)
} catch {}
return (
<ColorInputUnit

View File

@ -58,7 +58,7 @@ import AlphaSlider from './AlphaSlider'
import Pallete from './Pallete'
import ColorInput from './ColorInput'
import ColorPickerTrigger from './ColorPickerTrigger'
import { getModeFromValue } from './utils'
import { deriveDefaultValue, getModeFromValue } from './utils'
import type { ColorPickerMode } from './utils'
import style from './styles/index.cssr'
import { OnUpdateValue, OnUpdateValueImpl } from './interface'
@ -75,10 +75,7 @@ export const colorPickerPanelProps = {
type: Boolean,
default: false
},
defaultValue: {
type: String as PropType<string | null>,
default: '#000000'
},
defaultValue: String as PropType<string | null>,
modes: {
type: Array as PropType<ColorPickerMode[]>,
// no hsva by default since browser doesn't support it
@ -145,7 +142,12 @@ export default defineComponent({
uncontrolledShowRef.value = value
}
const uncontrolledValueRef = ref(props.defaultValue)
const { defaultValue } = props
const uncontrolledValueRef = ref(
defaultValue === undefined
? deriveDefaultValue(props.modes, props.showAlpha)
: defaultValue
)
const mergedValueRef = useMergedState(
toRef(props, 'value'),
uncontrolledValueRef
@ -163,33 +165,11 @@ export default defineComponent({
function handleUpdateDisplayedMode (): void {
const { modes } = props
const { value: displayedMode } = displayedModeRef
switch (displayedMode) {
case 'rgb':
if (modes.includes('hex')) {
displayedModeRef.value = 'hex'
break
}
// eslint-disable-next-line no-fallthrough
case 'hex':
if (modes.includes('hsv')) {
displayedModeRef.value = 'hsv'
break
}
// eslint-disable-next-line no-fallthrough
case 'hsv':
if (modes.includes('hsl')) {
displayedModeRef.value = 'hsl'
break
}
// eslint-disable-next-line no-fallthrough
case 'hsl':
if (modes.includes('rgb')) {
displayedModeRef.value = 'rgb'
break
}
// eslint-disable-next-line no-fallthrough
default:
displayedModeRef.value = 'rgb'
const currentModeIndex = modes.findIndex((mode) => mode === displayedMode)
if (~currentModeIndex) {
displayedModeRef.value = modes[(currentModeIndex + 1) % modes.length]
} else {
displayedModeRef.value = 'rgb'
}
}
@ -501,7 +481,7 @@ export default defineComponent({
function renderPanel (): VNode {
const { value: rgba } = rgbaRef
const { value: displayedHue } = displayedHueRef
const { internalActions } = props
const { internalActions, modes } = props
const { value: mergedTheme } = themeRef
const { value: mergedClsPrefix } = mergedClsPrefixRef
return (
@ -540,6 +520,7 @@ export default defineComponent({
clsPrefix={mergedClsPrefix}
showAlpha={props.showAlpha}
mode={displayedModeRef.value}
modes={modes}
onUpdateMode={handleUpdateDisplayedMode}
value={mergedValueRef.value}
valueArr={mergedValueArrRef.value}

View File

@ -120,7 +120,6 @@ export default c([
flex-basis: 0;
`),
cE('mode', `
cursor: pointer;
width: 72px;
text-align: center;
`)

View File

@ -1,5 +1,27 @@
import { warn } from '../../_utils'
export type ColorPickerMode = 'rgb' | 'hsl' | 'hsv' | 'hex'
export function deriveDefaultValue (
modes: ColorPickerMode[],
showAlpha: boolean
): string {
const mode = modes[0]
switch (mode) {
case 'hex':
return showAlpha ? '#000000FF' : '#000000'
case 'rgb':
return showAlpha ? 'rgba(0, 0, 0, 1)' : 'rgb(0, 0, 0)'
case 'hsl':
return showAlpha ? 'hsla(0, 0%, 0%, 1)' : 'hsl(0, 0%, 0%)'
case 'hsv':
return showAlpha ? 'hsva(0, 0%, 0%, 1)' : 'hsv(0, 0%, 0%)'
}
if (__DEV__) warn('color-picker', 'props.modes is invalid.')
// in case of invalid modes
return '#000000'
}
export function getModeFromValue (color: string | null): ColorPickerMode | null {
if (color === null) return null
if (/^ *#/.test(color)) return 'hex'

View File

@ -0,0 +1,75 @@
import { nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import { NColorPicker } from '../index'
import { ColorPickerMode } from '../src/utils'
describe('n-color-picker', () => {
it('should work with import on demand', () => {
mount(NColorPicker)
})
describe('props.modes', () => {
it('multiple modes', async () => {
const wrapper = mount(NColorPicker, {
attachTo: document.body,
props: {
modes: ['hex', 'hsl']
}
})
await wrapper.find('.n-color-picker-trigger').trigger('click')
expect(document.querySelector('.n-color-picker-panel')).not.toEqual(null)
const modeDom = document.querySelector('.n-color-picker-input__mode')
expect(modeDom?.textContent).toEqual('HEXA')
;(modeDom as HTMLElement).click()
await nextTick()
expect(modeDom?.textContent).toEqual('HSLA')
;(modeDom as HTMLElement).click()
await nextTick()
expect(modeDom?.textContent).toEqual('HEXA')
wrapper.unmount()
})
it('single mode', async () => {
const wrapper = mount(NColorPicker, {
attachTo: document.body,
props: {
modes: ['hsl']
}
})
await wrapper.find('.n-color-picker-trigger').trigger('click')
expect(document.querySelector('.n-color-picker-panel')).not.toEqual(null)
const modeDom = document.querySelector('.n-color-picker-input__mode')
expect(modeDom?.textContent).toEqual('HSLA')
;(modeDom as HTMLElement).click()
await nextTick()
expect(modeDom?.textContent).toEqual('HSLA')
wrapper.unmount()
})
it('has correct default value', () => {
const input: Array<{ modes: ColorPickerMode[], value: string }> = [
{
modes: ['hsl'],
value: 'hsla(0, 0%, 0%, 1)'
},
{
modes: ['rgb'],
value: 'rgb(0, 00, 0, 1)'
},
{
modes: ['hex'],
value: '#00000000'
},
{
modes: ['hsv', 'hsl'],
value: 'hsv(0, 0%, 0%, 1)'
}
]
input.forEach(({ modes, value }) => {
const wrapper = mount(NColorPicker, {
props: {
modes
}
})
expect(wrapper.text().includes(value))
})
})
})
})

View File

@ -6,7 +6,9 @@ import {
PropType,
provide,
InjectionKey,
renderSlot
renderSlot,
ComputedRef,
markRaw
} from 'vue'
import { useMemo } from 'vooks'
import { merge } from 'lodash-es'
@ -18,17 +20,18 @@ import type {
GlobalComponentConfig,
GlobalIconConfig
} from './interface'
import type { ConfigProviderInjection } from './internal-interface'
import type {
ConfigProviderInjection,
RtlProp,
RtlEnabledState
} from './internal-interface'
import { NDateLocale, NLocale } from '../../locales'
export const configProviderInjectionKey: InjectionKey<ConfigProviderInjection> =
Symbol('configProviderInjection')
export const configProviderProps = {
abstract: {
type: Boolean,
default: false
},
abstract: Boolean,
bordered: {
type: Boolean as PropType<boolean | undefined>,
default: undefined
@ -37,6 +40,7 @@ export const configProviderProps = {
locale: Object as PropType<NLocale | null>,
dateLocale: Object as PropType<NDateLocale | null>,
namespace: String,
rtl: Array as PropType<RtlProp>,
tag: {
type: String,
default: 'div'
@ -160,7 +164,21 @@ export default defineComponent({
const { clsPrefix } = props
return NConfigProvider?.mergedClsPrefixRef.value ?? clsPrefix
})
const mergedRtlRef: ComputedRef<RtlEnabledState | undefined> = computed(
() => {
const { rtl } = props
if (rtl === undefined) {
return NConfigProvider?.mergedRtlRef.value
}
const rtlEnabledState: RtlEnabledState = {}
for (const rtlInfo of rtl) {
rtlEnabledState[rtlInfo.name] = markRaw(rtlInfo)
}
return rtlEnabledState
}
)
provide(configProviderInjectionKey, {
mergedRtlRef,
mergedIconsRef,
mergedComponentPropsRef,
mergedBorderedRef,

View File

@ -1,4 +1,5 @@
import { VNodeChild, Ref } from 'vue'
import { CNode } from 'css-render'
import type { AlertTheme } from '../../alert/styles'
import type { AnchorTheme } from '../../anchor/styles'
import type { AutoCompleteTheme } from '../../auto-complete/styles'
@ -200,6 +201,16 @@ export interface GlobalIconConfig {
zoomOut?: () => VNodeChild
}
export interface RtlItem {
name: keyof GlobalThemeWithoutCommon
style: CNode
}
export type RtlProp = RtlItem[]
export type RtlEnabledState = Partial<
Record<keyof GlobalThemeWithoutCommon, RtlItem>
>
export interface ConfigProviderInjection {
mergedClsPrefixRef: Ref<string | undefined>
mergedBorderedRef: Ref<boolean | undefined>
@ -211,6 +222,7 @@ export interface ConfigProviderInjection {
mergedIconsRef: Ref<GlobalIconConfig | undefined>
mergedThemeRef: Ref<GlobalTheme | undefined>
mergedThemeOverridesRef: Ref<GlobalThemeOverrides | undefined>
mergedRtlRef: Ref<RtlEnabledState | undefined>
// deprecated
/** @deprecated */
mergedLegacyThemeRef: Ref<string | undefined>

View File

@ -0,0 +1,18 @@
# Extra Footer
```html
<n-space vertical>
<n-date-picker type="date">
<template #footer> extra footer </template>
</n-date-picker>
<n-date-picker type="datetime">
<template #footer> extra footer </template>
</n-date-picker>
<n-date-picker type="daterange">
<template #footer> extra footer </template>
</n-date-picker>
<n-date-picker type="datetimerange">
<template #footer> extra footer </template>
</n-date-picker>
</n-space>
```

View File

@ -16,6 +16,7 @@ actions
events
format
ranges
footerslot
```
## Props
@ -81,3 +82,9 @@ ranges
| separator | `string` | `'to'` | |
| start-placeholder | `string` | `'Start Date and Time'` | |
| on-update:value | `(value: [number, number] \| null) => void` | `undefined` | |
## Slots
| 名称 | 参数 | 说明 |
| ------ | ---- | ------------- |
| footer | `()` | Extra Footer. |

View File

@ -0,0 +1,18 @@
# 额外内容
```html
<n-space vertical>
<n-date-picker type="date">
<template #footer> extra footer </template>
</n-date-picker>
<n-date-picker type="datetime">
<template #footer> extra footer </template>
</n-date-picker>
<n-date-picker type="daterange">
<template #footer> extra footer </template>
</n-date-picker>
<n-date-picker type="datetimerange">
<template #footer> extra footer </template>
</n-date-picker>
</n-space>
```

View File

@ -16,6 +16,7 @@ actions
events
format
ranges
footerslot
```
## Props
@ -81,3 +82,9 @@ ranges
| separator | `string` | `'to'` | |
| start-placeholder | `string` | `'开始日期时间'` | |
| on-update:value | `(value: [number, number] \| null) => void` | `undefined` | |
## Slots
| 名称 | 参数 | 说明 |
| ------ | ---- | -------------- |
| footer | `()` | 添加额外的页脚 |

View File

@ -135,7 +135,7 @@ export type DatePickerProps = ExtractPublicPropTypes<typeof datePickerProps>
export default defineComponent({
name: 'DatePicker',
props: datePickerProps,
setup (props) {
setup (props, { slots }) {
const { localeRef, dateLocaleRef } = useLocale('DatePicker')
const formItem = useFormItem(props)
const {
@ -464,7 +464,8 @@ export default defineComponent({
isDateDisabledRef: toRef(props, 'isDateDisabled'),
rangesRef: toRef(props, 'ranges'),
...uniVaidation,
...dualValidation
...dualValidation,
datePickerSlots: slots
})
return {
mergedClsPrefix: mergedClsPrefixRef,
@ -541,6 +542,7 @@ export default defineComponent({
panelBoxShadow,
panelBorderRadius,
calendarTitleFontWeight,
panelExtraFooterPadding,
panelActionPadding,
itemSize,
itemCellWidth,
@ -589,6 +591,7 @@ export default defineComponent({
// panel action
'--panel-action-padding': panelActionPadding,
'--panel-extra-footer-padding': panelExtraFooterPadding,
'--panel-action-divider-color': panelActionDividerColor,
// panel item

View File

@ -1,4 +1,4 @@
import { InjectionKey, Ref } from 'vue'
import { InjectionKey, Ref, Slots } from 'vue'
import { NLocale, NDateLocale } from '../../locales'
import {
IsHourDisabled,
@ -45,6 +45,7 @@ export type DatePickerInjection = {
dateLocaleRef: Ref<NDateLocale>
isDateDisabledRef: Ref<IsDateDisabled | undefined>
rangesRef: Ref<Record<string, [number, number]> | undefined>
datePickerSlots: Slots
} & ReturnType<typeof uniCalendarValidation> &
ReturnType<typeof dualCalendarValidation>

View File

@ -1,4 +1,4 @@
import { h, defineComponent } from 'vue'
import { h, defineComponent, renderSlot } from 'vue'
import { NButton } from '../../../button'
import {
BackwardIcon,
@ -101,6 +101,11 @@ export default defineComponent({
))}
</div>
</div>
{this.datePickerSlots.footer ? (
<div class={`${mergedClsPrefix}-date-panel-footer`}>
{renderSlot(this.datePickerSlots, 'footer')}
</div>
) : null}
{this.actions?.length ? (
<div class={`${mergedClsPrefix}-date-panel-actions`}>
<div class={`${mergedClsPrefix}-date-panel-actions__prefix`}></div>

View File

@ -1,4 +1,4 @@
import { defineComponent, h } from 'vue'
import { defineComponent, h, renderSlot } from 'vue'
import { NButton, NxButton } from '../../../button'
import {
BackwardIcon,
@ -189,6 +189,11 @@ export default defineComponent({
))}
</div>
</div>
{this.datePickerSlots.footer ? (
<div class={`${mergedClsPrefix}-date-panel-footer`}>
{renderSlot(this.datePickerSlots, 'footer')}
</div>
) : null}
{this.actions?.length || ranges ? (
<div class={`${mergedClsPrefix}-date-panel-actions`}>
<div class={`${mergedClsPrefix}-date-panel-actions__prefix`}>

View File

@ -1,4 +1,4 @@
import { h, defineComponent } from 'vue'
import { h, defineComponent, renderSlot } from 'vue'
import { NButton } from '../../../button'
import { NTimePicker } from '../../../time-picker'
import { NInput } from '../../../input'
@ -132,6 +132,11 @@ export default defineComponent({
))}
</div>
</div>
{this.datePickerSlots.footer ? (
<div class={`${mergedClsPrefix}-date-panel-footer`}>
{renderSlot(this.datePickerSlots, 'footer')}
</div>
) : null}
{this.actions?.length ? (
<div class={`${mergedClsPrefix}-date-panel-actions`}>
<div class={`${mergedClsPrefix}-date-panel-actions__prefix`}></div>

View File

@ -1,4 +1,4 @@
import { defineComponent, h } from 'vue'
import { defineComponent, h, renderSlot } from 'vue'
import { NButton, NxButton } from '../../../button'
import { NInput } from '../../../input'
import { NTimePicker } from '../../../time-picker'
@ -247,6 +247,11 @@ export default defineComponent({
))}
</div>
</div>
{this.datePickerSlots.footer ? (
<div class={`${mergedClsPrefix}-date-panel-footer`}>
{renderSlot(this.datePickerSlots, 'footer')}
</div>
) : null}
{this.actions?.length ? (
<div class={`${mergedClsPrefix}-date-panel-actions`}>
<div class={`${mergedClsPrefix}-date-panel-actions__prefix`}>

View File

@ -41,7 +41,8 @@ function useCalendar (
isHourDisabledRef,
isMinuteDisabledRef,
isSecondDisabledRef,
localeRef
localeRef,
datePickerSlots
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
} = inject(datePickerInjectionKey)!
const validation = {
@ -268,7 +269,8 @@ function useCalendar (
handleTimePickerChange,
clearSelectedDateTime,
timePickerSize: panelCommon.timePickerSize,
dateInputValue: dateInputValueRef
dateInputValue: dateInputValueRef,
datePickerSlots
}
}

View File

@ -46,7 +46,8 @@ function useDualCalendar (
isEndValueInvalidRef,
isRangeInvalidRef,
localeRef,
rangesRef
rangesRef,
datePickerSlots
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
} = inject(datePickerInjectionKey)!
const validation = {
@ -572,7 +573,8 @@ function useDualCalendar (
handleStartDateInput,
handleStartDateInputBlur,
handleEndDateInput,
handleEndDateInputBlur
handleEndDateInputBlur,
datePickerSlots
}
}

View File

@ -96,12 +96,14 @@ export default c([
cM('date', {
gridTemplateAreas: `
"left-calendar"
"footer"
"action"
`
}),
cM('daterange', {
gridTemplateAreas: `
"left-calendar divider right-calendar"
"footer footer footer"
"action action action"
`
}),
@ -109,6 +111,7 @@ export default c([
gridTemplateAreas: `
"header"
"left-calendar"
"footer"
"action"
`
}),
@ -116,15 +119,19 @@ export default c([
gridTemplateAreas: `
"header header header"
"left-calendar divider right-calendar"
"footer footer footer"
"action action action"
`
}),
cB('date-panel-header', {
gridArea: 'header'
cB('date-panel-footer', {
gridArea: 'footer'
}),
cB('date-panel-actions', {
gridArea: 'action'
}),
cB('date-panel-header', {
gridArea: 'header'
}),
cB('date-panel-header', `
box-sizing: border-box;
width: 100%;
@ -329,6 +336,10 @@ export default c([
width: 1px;
background-color: var(--calendar-divider-color);
`),
cB('date-panel-footer', {
borderTop: '1px solid var(--panel-action-divider-color)',
padding: 'var(--panel-extra-footer-padding)'
}),
cB('date-panel-actions', `
flex: 1;
padding: var(--panel-action-padding);

View File

@ -2,6 +2,7 @@ export default {
itemSize: '24px',
itemCellWidth: '38px',
itemCellHeight: '32px',
panelExtraFooterPadding: '8px 12px',
panelActionPadding: '8px 12px',
calendarTitlePadding: '0',
calendarTitleHeight: '28px',

View File

@ -19,7 +19,7 @@ export default defineComponent({
const { clsPrefix } = this
return (
<div class={`${clsPrefix}-log-loader`}>
<NBaseLoading clsPrefix={clsPrefix} strokeWidth={24} />
<NBaseLoading clsPrefix={clsPrefix} strokeWidth={24} scale={0.85} />
<span class={`${clsPrefix}-log-loader__content`}>
{this.locale.loading}
</span>

View File

@ -88,11 +88,11 @@ const menuProps = {
},
disabled: Boolean,
inverted: Boolean,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:expandedKeys': [Function, Array] as PropType<
MaybeArray<OnUpdateKeys>
>,
// eslint-disable-next-line vue/prop-name-casing
onUpdateExpandedKeys: [Function, Array] as PropType<MaybeArray<OnUpdateKeys>>,
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
// deprecated
onOpenNamesChange: {
@ -233,10 +233,17 @@ export default defineComponent({
toggleExpand
})
function doSelect (value: Key, item: MenuOption): void {
const { 'onUpdate:value': onUpdateValue, onSelect } = props
const {
'onUpdate:value': _onUpdateValue,
onUpdateValue,
onSelect
} = props
if (onUpdateValue) {
call(onUpdateValue as OnUpdateValueImpl, value, item)
}
if (_onUpdateValue) {
call(_onUpdateValue as OnUpdateValueImpl, value, item)
}
if (onSelect) {
call(onSelect as OnUpdateValueImpl, value, item)
}
@ -244,10 +251,14 @@ export default defineComponent({
}
function doUpdateExpandedKeys (value: Key[]): void {
const {
'onUpdate:expandedKeys': onUpdateExpandedKeys,
'onUpdate:expandedKeys': _onUpdateExpandedKeys,
onUpdateExpandedKeys,
onExpandedNamesChange,
onOpenNamesChange
} = props
if (_onUpdateExpandedKeys) {
call(_onUpdateExpandedKeys as OnUpdateKeysImpl, value)
}
if (onUpdateExpandedKeys) {
call(onUpdateExpandedKeys as OnUpdateKeysImpl, value)
}

View File

@ -35,22 +35,19 @@ export type MenuGroupOption =
export type TmNode = TreeNode<MenuOption, MenuGroupOption>
export type OnUpdateValue = <T extends string & number & (string | number)>(
value: T,
export type OnUpdateValue = (
value: string & number & (string | number),
item: MenuOption
) => void
export type OnUpdateKeys = <
T extends string[] & number[] & Array<string | number>
>(
keys: T
) => void
export type OnUpdateValueImpl = <T extends string | number | (string | number)>(
value: T,
export type OnUpdateKeys = (
keys: string[] & number[] & Array<string | number>
) => void
export type OnUpdateValueImpl = (
value: string | number | (string | number),
item: MenuOption
) => void
export type OnUpdateKeysImpl = <
T extends string[] | number[] | Array<string | number>
>(
keys: T
export type OnUpdateKeysImpl = (
keys: string[] | number[] | Array<string | number>
) => void

View File

@ -5,4 +5,30 @@ describe('n-menu', () => {
it('should work with import on demand', () => {
mount(NMenu)
})
it('props.onUpdateValue type', () => {
const stringCb = (v: string): void => {}
const numberCb = (v: number): void => {}
const snCb = (v: string | number): void => {}
const stringArrCb = (v: string[]): void => {}
const numberArrCb = (v: number[]): void => {}
const snArrCb = (v: Array<string | number>): void => {}
mount(NMenu, {
props: {
onUpdateValue: stringCb,
onUpdateExpandedKeys: stringArrCb
}
})
mount(NMenu, {
props: {
onUpdateValue: numberCb,
onUpdateExpandedKeys: numberArrCb
}
})
mount(NMenu, {
props: {
onUpdateValue: snCb,
onUpdateExpandedKeys: snArrCb
}
})
})
})

View File

@ -165,7 +165,11 @@ function createIconVNode (
{{
default: () =>
type === 'loading' ? (
<NBaseLoading clsPrefix={clsPrefix} scale={0.85} />
<NBaseLoading
clsPrefix={clsPrefix}
strokeWidth={24}
scale={0.85}
/>
) : (
iconMap[type]
)

View File

@ -72,32 +72,32 @@ export default defineComponent({
}
},
render () {
const { mergedClsPrefix } = this
const { mergedClsPrefix, $slots } = this
return (
<div style={this.cssVars as CSSProperties}>
<div class={`${mergedClsPrefix}-popconfirm__body`}>
{this.showIcon ? (
<div class={`${mergedClsPrefix}-popconfirm__icon`}>
<slot name="icon">
{renderSlot($slots, 'icon', undefined, () => [
<NBaseIcon clsPrefix={mergedClsPrefix}>
{{ default: () => <WarningIcon /> }}
</NBaseIcon>
</slot>
])}
</div>
) : null}
{renderSlot(this.$slots, 'default')}
{renderSlot($slots, 'default')}
</div>
<div class={`${mergedClsPrefix}-popconfirm__action`}>
{renderSlot(this.$slots, 'action', undefined, () => [
{renderSlot($slots, 'action', undefined, () => [
<NButton size="small" onClick={this.handleNegativeClick}>
{this.localizedNegativeText}
{{ default: () => this.localizedNegativeText }}
</NButton>,
<NButton
size="small"
type="primary"
onClick={this.handlePositiveClick}
>
{this.localizedPositiveText}
{{ default: () => this.localizedPositiveText }}
</NButton>
])}
</div>

View File

@ -28,7 +28,6 @@ processing
| indicator-text-color | `string` | `undefined` | |
| percentage | `number \| Array<number>` | `0` | |
| processing | `boolean` | `false` | |
| processing | `boolean` | `false` | |
| rail-color | `string \| string[]` | `undefined` | |
| rail-style | `string \| CSS \| Array<string \| CSS>` | `undefined` | |
| show-indicator | `boolean` | `true` | |

View File

@ -28,7 +28,6 @@ processing
| indicator-text-color | `string` | `undefined` | |
| percentage | `number \| Array<number>` | `0` | |
| processing | `boolean` | `false` | |
| processing | `boolean` | `false` | |
| rail-color | `string \| string[]` | `undefined` | |
| rail-style | `string \| CSS \| Array<string \| CSS>` | `undefined` | |
| show-indicator | `boolean` | `true` | |

View File

@ -27,6 +27,7 @@ import type { MaybeArray, ExtractPublicPropTypes } from '../../_utils'
import { sliderLight, SliderTheme } from '../styles'
import style from './styles/index.cssr'
import { OnUpdateValueImpl } from './interface'
import { isTouchEvent } from './utils'
const sliderProps = {
...(useTheme.props as ThemeProps<SliderTheme>),
@ -295,14 +296,18 @@ export default defineComponent({
}
}
}
function handleHandleMouseMove (e: MouseEvent, handleIndex: 0 | 1): void {
function handleHandleMouseMove (
e: MouseEvent | TouchEvent,
handleIndex: 0 | 1
): void {
if (!handleRef1.value || !railRef.value) return
const x = 'touches' in e ? e.touches[0].clientX : e.clientX
const { width: handleWidth } = handleRef1.value.getBoundingClientRect()
const { width: railWidth, left: railLeft } =
railRef.value.getBoundingClientRect()
const { min, max, range } = props
const offsetRatio =
(e.clientX - railLeft - handleWidth / 2) / (railWidth - handleWidth)
(x - railLeft - handleWidth / 2) / (railWidth - handleWidth)
const newValue = min + (max - min) * offsetRatio
if (range) {
if (handleIndex === 0) {
@ -462,34 +467,44 @@ export default defineComponent({
}
return justifiedValue
}
function handleFirstHandleMouseDown (): void {
function handleFirstHandleMouseDown (e: MouseEvent | TouchEvent): void {
if (isTouchEvent(e)) e.preventDefault()
if (props.range) {
memoziedOtherValueRef.value = handleValue2Ref.value
}
doUpdateShow(true, false)
handleClicked1Ref.value = true
on('touchend', document, handleHandleMouseUp)
on('mouseup', document, handleHandleMouseUp)
on('touchmove', document, handleFirstHandleMouseMove)
on('mousemove', document, handleFirstHandleMouseMove)
}
function handleSecondHandleMouseDown (): void {
function handleSecondHandleMouseDown (e: MouseEvent | TouchEvent): void {
if (isTouchEvent(e)) e.preventDefault()
if (props.range) {
memoziedOtherValueRef.value = handleValue1Ref.value
}
doUpdateShow(false, true)
handleClicked2Ref.value = true
on('touchend', document, handleHandleMouseUp)
on('mouseup', document, handleHandleMouseUp)
on('touchmove', document, handleSecondHandleMouseMove)
on('mousemove', document, handleSecondHandleMouseMove)
}
function handleHandleMouseUp (e: MouseEvent): void {
function handleHandleMouseUp (e: MouseEvent | TouchEvent): void {
if (
!handleRef1.value?.contains(e.target as Node) &&
(props.range ? !handleRef2.value?.contains(e.target as Node) : true)
isTouchEvent(e) ||
(!handleRef1.value?.contains(e.target as Node) &&
(props.range ? !handleRef2.value?.contains(e.target as Node) : true))
) {
doUpdateShow(false, false)
}
handleClicked2Ref.value = false
handleClicked1Ref.value = false
off('touchend', document, handleHandleMouseUp)
off('mouseup', document, handleHandleMouseUp)
off('touchmove', document, handleFirstHandleMouseMove)
off('touchmove', document, handleSecondHandleMouseMove)
off('mousemove', document, handleFirstHandleMouseMove)
off('mousemove', document, handleSecondHandleMouseMove)
}
@ -540,10 +555,10 @@ export default defineComponent({
}
}
}
function handleFirstHandleMouseMove (e: MouseEvent): void {
function handleFirstHandleMouseMove (e: MouseEvent | TouchEvent): void {
handleHandleMouseMove(e, 0)
}
function handleSecondHandleMouseMove (e: MouseEvent): void {
function handleSecondHandleMouseMove (e: MouseEvent | TouchEvent): void {
handleHandleMouseMove(e, 1)
}
function handleFirstHandleMouseEnter (): void {
@ -652,8 +667,11 @@ export default defineComponent({
})
})
onBeforeUnmount(() => {
off('touchmove', document, handleFirstHandleMouseMove)
off('touchmove', document, handleSecondHandleMouseMove)
off('mousemove', document, handleFirstHandleMouseMove)
off('mousemove', document, handleSecondHandleMouseMove)
off('touchend', document, handleHandleMouseUp)
off('mouseup', document, handleHandleMouseUp)
})
return {
@ -829,6 +847,7 @@ export default defineComponent({
style={this.firstHandleStyle}
onFocus={this.handleHandleFocus1}
onBlur={this.handleHandleBlur1}
onTouchstart={this.handleFirstHandleMouseDown}
onMousedown={this.handleFirstHandleMouseDown}
onMouseenter={this.handleFirstHandleMouseEnter}
onMouseleave={this.handleFirstHandleMouseLeave}
@ -883,6 +902,7 @@ export default defineComponent({
style={this.secondHandleStyle}
onFocus={this.handleHandleFocus2}
onBlur={this.handleHandleBlur2}
onTouchstart={this.handleSecondHandleMouseDown}
onMousedown={this.handleSecondHandleMouseDown}
onMouseenter={this.handleSecondHandleMouseEnter}
onMouseleave={this.handleSecondHandleMouseLeave}

3
src/slider/src/utils.ts Normal file
View File

@ -0,0 +1,3 @@
export function isTouchEvent (e: MouseEvent | TouchEvent): boolean {
return window.TouchEvent && e instanceof window.TouchEvent
}

View File

@ -13,10 +13,10 @@ wrap
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | |
| show | `boolean` | `true` | If spin is active |
| stroke-width | `number` | `undefined` | Relative width of spin's stroke, you can assume the outer radius of spin is 100 |
| stroke | `string` | `undefined` | Color of spin |
| size | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | |
| show | `boolean` | `true` | If spin is active. |
| stroke-width | `number` | `undefined` | Relative width of spin's stroke, you can assume the outer radius of spin is 100. |
| stroke | `string` | `undefined` | Color of the spin. |
## Slots

View File

@ -13,7 +13,7 @@ wrap
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | |
| size | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | |
| show | `boolean` | `true` | Spin 在填入内容的状态是否激活 |
| stroke-width | `number` | `undefined` | Spin 边缘的相对宽度,假定 Spin 的外侧半径是 100 |
| stroke | `string` | `undefined` | Spin 的颜色 |

View File

@ -7,6 +7,7 @@ import {
CSSProperties
} from 'vue'
import { useCompitable } from 'vooks'
import { pxfy } from 'seemly'
import { NBaseLoading } from '../../_internal'
import { useConfig, useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
@ -16,9 +17,9 @@ import type { SpinTheme } from '../styles'
import style from './styles/index.cssr'
const STROKE_WIDTH = {
small: 22,
medium: 20,
large: 18
small: 20,
medium: 18,
large: 16
}
const spinProps = {
@ -28,7 +29,7 @@ const spinProps = {
default: undefined
},
size: {
type: [String, Number] as PropType<'small' | 'medium' | 'large'>,
type: [String, Number] as PropType<'small' | 'medium' | 'large' | number>,
default: 'medium'
},
show: {
@ -71,14 +72,19 @@ export default defineComponent({
const { strokeWidth } = props
if (strokeWidth !== undefined) return strokeWidth
const { size } = props
return STROKE_WIDTH[size]
return STROKE_WIDTH[typeof size === 'number' ? 'medium' : size]
}),
cssVars: computed(() => {
const { size: spinSize } = props
const {
common: { cubicBezierEaseInOut },
self: { opacitySpinning, color, [createKey('size', spinSize)]: size }
self
} = themeRef.value
const { opacitySpinning, color } = self
const size =
typeof spinSize === 'number'
? pxfy(spinSize)
: self[createKey('size', spinSize)]
return {
'--bezier': cubicBezierEaseInOut,
'--opacity-spinning': opacitySpinning,

View File

@ -6,8 +6,8 @@ export { avatarDark } from './avatar/styles'
export { backTopDark } from './back-top/styles'
export { badgeDark } from './badge/styles'
export { breadcrumbDark } from './breadcrumb/styles'
export { buttonDark } from './button/styles'
export { cardDark } from './card/styles'
export { buttonDark, buttonRtl as unstableButtonRtl } from './button/styles'
export { cardDark, cardRtl as unstableCardRtl } from './card/styles'
export { cascaderDark } from './cascader/styles'
export { checkboxDark } from './checkbox/styles'
export { codeDark } from './code/styles'

View File

@ -116,8 +116,9 @@ export default defineComponent({
const barElRef = ref<HTMLElement | null>(null)
const scrollWrapperElRef = ref<HTMLElement | null>(null)
const addTabInstRef = ref<ComponentPublicInstance | null>(null)
const xScrollInstRef =
ref<(VXScrollInst & ComponentPublicInstance) | null>(null)
const xScrollInstRef = ref<(VXScrollInst & ComponentPublicInstance) | null>(
null
)
const leftReachedRef = ref(true)
const rightReachedRef = ref(true)
@ -511,7 +512,6 @@ function filterMapTabPanes (
if (vNode.key !== undefined) {
vNode.key = name
}
console.log(vNode.props)
if (useVShow) {
children.push(withDirectives(vNode, [[vShow, show]]))
} else if (show) {

View File

@ -1 +1 @@
export default '2.11.4'
export default '2.11.7'