diff --git a/.eslintrc.js b/.eslintrc.js index 044934db6..d47a697a9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - extends: ['plugin:markdown/recommended', 'prettier'], + extends: ['plugin:markdown/recommended-legacy', 'prettier'], overrides: [ { files: '*.mjs', @@ -39,7 +39,7 @@ module.exports = { }, { files: ['*.ts', '*.tsx'], - extends: ['standard-with-typescript', 'plugin:import/typescript'], + extends: ['love', 'plugin:import/typescript'], parserOptions: { project: './tsconfig.json', ecmaFeatures: { diff --git a/.gitignore b/.gitignore index 0911325a9..563f1e395 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ yarn.lock .DS_Store .vscode .idea +.pulsar *.swp *.tgz coverage diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index d4467db55..0a5229491 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -1,5 +1,50 @@ # CHANGELOG +## 2.38.2 + +`2024-05-03` + +### Fixes + +- Fix `n-menu` Submenu's wai-aria role is not correct, closes [#5729](https://github.com/tusen-ai/naive-ui/issues/5729). +- Fix `n-tabs` style bug with type is `segment`,closes [#5728](https://github.com/tusen-ai/naive-ui/issues/5728). +- Fix the get\*String() methods for UTC/locale mismatch, closes [#5702](closes https://github.com/tusen-ai/naive-ui/issues/5702). +- Fix `n-dialog` / `n-modal` calling `destroy` method may throw error. +- Fix `useModal` setting `card` preset without corresponding props in `n-card` slots, closes [#5746](https://github.com/tusen-ai/naive-ui/issues/5746). +- Fix `Submenu` component's wai-aria role setting error of `n-menu`,closes [#5729](https://github.com/tusen-ai/naive-ui/issues/5729). +- Fix the `common` type error in the `theme-overrides` prop when modifying components' themes. +- Fix `n-split` may emit value less than `0`. + +### Features + +- `n-watermark` support multi-lines in content. +- Adds `n-infinite-scroll` component. +- `n-watermark` adds `text-align` prop. +- `n-qr-code` adds `type` prop, Customize rendering output by setting `type`, providing two options: `canvas` and `svg`. +- `n-card` adds `action`, `content`, `cover`, `footer` and `header-extra` props. +- `n-card`'s `title` prop supports render function. +- `n-upload` expose the `index` arg in `on-remove` function, closes [#5747](https://github.com/tusen-ai/naive-ui/issues/5747). +- `n-upload` exports `UploadOnDownload`, `UploadOnRemove`, `UploadOnFinish` and `UploadOnChange` types. +- `n-dialog` adds `action-class`, `action-style`, `content-class`, `content-style`, `title-class` and `title-style` props. +- `n-split` adds `pane1-class`, `pane1-style`, `pane2-class` and `pane2-style` props. +- `n-mention` adds `filter` method, closes [#5721](https://github.com/tusen-ai/naive-ui/pull/5721). +- `n-slider` adds wai-aria support. +- `n-date-picker` adds `time-picker-format` prop. +- `n-form-item` adds `feedback-class` and `feedback-style` props. +- `n-split` supports using pixel unit string as `value`. +- `n-scrollbar` adds `content-style` and `content-class` props, closes [#4497](https://github.com/tusen-ai/naive-ui/issues/4497). +- `n-image` adds `render-toolbar` prop. +- `n-cascader` adds `get-column-style` prop. +- `n-cascader` adds `get-render-prefix` prop. +- `n-cascader` adds `get-render-suffix` prop. +- `n-image` optimizes download icon style. +- `n-scrollbar` adds `height`, `width`, `radius`, `railInsetHorizontal`, `railInsetVertical` and `railColor` theme variables. + +### i18n + +- Add csCZ locale. +- Add missing itIT locale translations + ## 2.38.1 `2024-02-26` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 6673c5d0f..26fd2398a 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -1,5 +1,49 @@ # CHANGELOG +## 2.38.2 + +`2024-05-03` + +### Fixes + +- 修复 `n-menu` 中 Submenu 组件的 wai-aria role 设置错误,关闭 [#5729](https://github.com/tusen-ai/naive-ui/issues/5729) +- 修复 `n-tabs` type 为 `segment` 时样式存在问题,关闭 [#5728](https://github.com/tusen-ai/naive-ui/issues/5728) +- 修复 get\*String() 方法中 UTC/区域设置不匹配的问题,关闭 [#5702](https://github.com/tusen-ai/naive-ui/issues/5702) +- 修复 `n-dialog` / `n-modal` 调用 `destroy` 方法时可能会报错 +- 修复 `useModal` 设置 `card` 预设时 `n-card` 插槽缺少相应属性,关闭 [#5746](https://github.com/tusen-ai/naive-ui/issues/5746) +- 修复组件调整主题时 `theme-overrides` 属性中的 `common` 类型报错 +- 修复 `n-split` 可能产生小与 `0` 的值 + +### Features + +- `n-watermark` 支持多行文本 +- 新增 `n-infinite-scroll` 组件 +- `n-watermark` 新增 `text-align` 属性 +- `n-qr-code` 新增 `type` 属性,设置 `type` 自定义渲染结果,提供 `canvas` 和 `svg` 两个选项 +- `n-card` 新增 `action`、`content`、`cover`、`footer`、`header-extra` 属性 +- `n-card` 的 `title` 属性支持渲染函数 +- `n-upload` 导出 `on-remove` 方法的 `index` 属性,关闭 [#5747](https://github.com/tusen-ai/naive-ui/issues/5747) +- `n-upload` 导出 `UploadOnDownload`、`UploadOnRemove`、`UploadOnFinish` 和 `UploadOnChange` 类型 +- `n-dialog` 新增 `action-class`、`action-style`、`content-class`、`content-style`、`title-class` 和 `title-style` 属性 +- `n-split` 新增 `pane1-class`、`pane1-style`、`pane2-class` 和 `pane2-style` 属性 +- `n-mention` 新增 `filter` 方法,关闭 [#5721](https://github.com/tusen-ai/naive-ui/pull/5721) +- `n-slider` 新增 wai-aria 支持 +- `n-date-picker` 新增 `time-picker-format` 属性 +- `n-form-item` 新增 `feedback-class` 和 `feedback-style` 属性 +- `n-split` 支持设置像素值大小 +- `n-scrollbar` 新增 `content-style` 和 `content-class` 属性,关闭 [#4497](https://github.com/tusen-ai/naive-ui/issues/4497) +- `n-image` 新增 `render-toolbar` 属性 +- `n-cascader` 新增 `get-column-width` 属性 +- `n-cascader` 新增 `render-prefix` 属性 +- `n-cascader` 新增 `render-suffix` 属性 +- `n-image` 优化下载按钮图标 +- `n-scrollbar` 新增 `height`、`width`、`radius`、`railInsetHorizontal`、`railInsetVertical`、`railColor` 主题变量 + +### i18n + +- 新增 csCZ locale +- 增加缺少的 itIT locale + ## 2.38.1 `2024-02-26` diff --git a/demo/routes/routes.js b/demo/routes/routes.js index 1010c8f3c..14bf13009 100644 --- a/demo/routes/routes.js +++ b/demo/routes/routes.js @@ -552,6 +552,11 @@ export const enComponentRoutes = [ path: 'split', component: () => import('../../src/split/demos/enUS/index.demo-entry.md') }, + { + path: 'infinite-scroll', + component: () => + import('../../src/infinite-scroll/demos/enUS/index.demo-entry.md') + }, { path: 'float-button', component: () => @@ -951,6 +956,11 @@ export const zhComponentRoutes = [ path: 'split', component: () => import('../../src/split/demos/zhCN/index.demo-entry.md') }, + { + path: 'infinite-scroll', + component: () => + import('../../src/infinite-scroll/demos/zhCN/index.demo-entry.md') + }, { path: 'float-button', component: () => diff --git a/demo/store/menu-options.js b/demo/store/menu-options.js index 3f39a264c..6e0367d36 100644 --- a/demo/store/menu-options.js +++ b/demo/store/menu-options.js @@ -539,6 +539,13 @@ export function createComponentMenuOptions ({ lang, theme, mode }) { zh: '树', enSuffix: true, path: '/tree' + }, + { + en: 'Infinite Scroll', + zh: '无限滚动', + enSuffix: true, + path: '/infinite-scroll', + isNew: true } ] }), diff --git a/package.json b/package.json index 8e3534b5b..cdc580324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "naive-ui", - "version": "2.38.1", + "version": "2.38.2", "description": "A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast", "main": "lib/index.js", "module": "es/index.mjs", @@ -88,15 +88,15 @@ "@rollup/plugin-terser": "^0.4.3", "@types/estree": "^1.0.1", "@types/jest": "^29.5.4", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^7.0.2", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", "@vicons/fluent": "^0.12.0", "@vicons/ionicons4": "^0.12.0", "@vicons/ionicons5": "^0.12.0", "@vitejs/plugin-vue": "^5.0.3", "@vue/compiler-sfc": "^3.4.15", "@vue/eslint-config-standard": "^8.0.1", - "@vue/eslint-config-typescript": "^12.0.0", + "@vue/eslint-config-typescript": "^13.0.0", "@vue/server-renderer": "~3.4.15", "@vue/test-utils": "^2.4.1", "autoprefixer": "^10.4.15", @@ -106,11 +106,11 @@ "deepmerge": "^4.3.1", "esbuild": "0.20.1", "eslint": "^8.48.0", + "eslint-config-love": "^44.0.0", "eslint-config-prettier": "^9.0.0", "eslint-config-standard": "^17.1.0", - "eslint-config-standard-with-typescript": "^43.0.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-markdown": "^3.0.1", + "eslint-plugin-markdown": "^4.0.1", "eslint-plugin-n": "^16.0.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", @@ -133,12 +133,12 @@ "rollup-plugin-esbuild": "^6.1.0", "superagent": "^8.1.2", "ts-jest": "^29.1.1", - "typescript": "5.3.3", + "typescript": "5.4.2", "vfonts": "^0.0.3", "vite": "^5.0.4", "vue": "~3.4.15", "vue-router": "^4.2.4", - "vue-tsc": "^1.8.27" + "vue-tsc": "^2.0.6" }, "peerDependencies": { "vue": "^3.0.0" diff --git a/src/_internal/scrollbar/src/Scrollbar.tsx b/src/_internal/scrollbar/src/Scrollbar.tsx index 6926b0a02..12ac65af9 100644 --- a/src/_internal/scrollbar/src/Scrollbar.tsx +++ b/src/_internal/scrollbar/src/Scrollbar.tsx @@ -14,14 +14,14 @@ import type { PropType, CSSProperties, VNode, HTMLAttributes } from 'vue' import { on, off } from 'evtd' import { VResizeObserver } from 'vueuc' import { useIsIos } from 'vooks' -import { getPreciseEventTarget } from 'seemly' +import { depx, getPreciseEventTarget } from 'seemly' import { useConfig, useTheme, useThemeClass, useRtl } from '../../../_mixins' import type { ThemeProps } from '../../../_mixins' import type { ExtractInternalPropTypes, ExtractPublicPropTypes } from '../../../_utils' -import { useReactivated, Wrapper } from '../../../_utils' +import { rtlInset, useReactivated, Wrapper } from '../../../_utils' import { scrollbarLight } from '../styles' import type { ScrollbarTheme } from '../styles' import style from './styles/index.cssr' @@ -75,10 +75,6 @@ export interface ScrollbarInst extends ScrollbarInstMethods { const scrollbarProps = { ...(useTheme.props as ThemeProps), - size: { - type: Number, - default: 5 - }, duration: { type: Number, default: 0 @@ -155,6 +151,15 @@ const Scrollbar = defineComponent({ let memoMouseY: number = 0 const isIos = useIsIos() + const themeRef = useTheme( + 'Scrollbar', + '-scrollbar', + style, + scrollbarLight, + props, + mergedClsPrefixRef + ) + const yBarSizeRef = computed(() => { const { value: containerHeight } = containerHeightRef const { value: contentHeight } = contentHeightRef @@ -168,7 +173,8 @@ const Scrollbar = defineComponent({ } else { return Math.min( containerHeight, - (yRailSize * containerHeight) / contentHeight + props.size * 1.5 + (yRailSize * containerHeight) / contentHeight + + depx(themeRef.value.self.width) * 1.5 ) } }) @@ -186,7 +192,10 @@ const Scrollbar = defineComponent({ ) { return 0 } else { - return (xRailSize * containerWidth) / contentWidth + props.size * 1.5 + return ( + (xRailSize * containerWidth) / contentWidth + + depx(themeRef.value.self.height) * 1.5 + ) } }) const xBarSizePxRef = computed(() => { @@ -636,31 +645,32 @@ const Scrollbar = defineComponent({ off('mousemove', window, handleYScrollMouseMove, true) off('mouseup', window, handleYScrollMouseUp, true) }) - const themeRef = useTheme( - 'Scrollbar', - '-scrollbar', - style, - scrollbarLight, - props, - mergedClsPrefixRef - ) const cssVarsRef = computed(() => { const { - common: { - cubicBezierEaseInOut, - scrollbarBorderRadius, - scrollbarHeight, - scrollbarWidth - }, - self: { color, colorHover } + common: { cubicBezierEaseInOut }, + self: { + color, + colorHover, + height, + width, + borderRadius, + railInsetHorizontal, + railInsetVertical, + railColor + } } = themeRef.value return { '--n-scrollbar-bezier': cubicBezierEaseInOut, '--n-scrollbar-color': color, '--n-scrollbar-color-hover': colorHover, - '--n-scrollbar-border-radius': scrollbarBorderRadius, - '--n-scrollbar-width': scrollbarWidth, - '--n-scrollbar-height': scrollbarHeight + '--n-scrollbar-border-radius': borderRadius, + '--n-scrollbar-width': width, + '--n-scrollbar-height': height, + '--n-scrollbar-rail-inset-horizontal': railInsetHorizontal, + '--n-scrollbar-rail-inset-vertical': rtlEnabledRef?.value + ? rtlInset(railInsetVertical) + : railInsetVertical, + '--n-scrollbar-rail-color': railColor } }) const themeClassHandle = inlineThemeDisabled diff --git a/src/_internal/scrollbar/src/styles/index.cssr.ts b/src/_internal/scrollbar/src/styles/index.cssr.ts index c8f962f83..ae3a47f35 100644 --- a/src/_internal/scrollbar/src/styles/index.cssr.ts +++ b/src/_internal/scrollbar/src/styles/index.cssr.ts @@ -8,6 +8,9 @@ import { fadeInTransition } from '../../../../_styles/transitions/fade-in.cssr' // --n-scrollbar-width // --n-scrollbar-height // --n-scrollbar-border-radius +// --n-scrollbar-rail-inset-horizontal +// --n-scrollbar-rail-inset-vertical +// --n-scrollbar-rail-color export default cB('scrollbar', ` overflow: hidden; position: relative; @@ -30,6 +33,7 @@ export default cB('scrollbar', ` display: none; `), c('>', [ + // We can't set overflow hidden since it affects positioning. cB('scrollbar-content', ` box-sizing: border-box; min-width: 100%; @@ -42,12 +46,11 @@ export default cB('scrollbar', ` position: absolute; pointer-events: none; user-select: none; + background: var(--n-scrollbar-rail-color); -webkit-user-select: none; `, [ cM('horizontal', ` - left: 2px; - right: 2px; - bottom: 4px; + inset: var(--n-scrollbar-rail-inset-horizontal); height: var(--n-scrollbar-height); `, [ c('>', [ @@ -59,9 +62,7 @@ export default cB('scrollbar', ` ]) ]), cM('vertical', ` - right: 4px; - top: 2px; - bottom: 2px; + inset: var(--n-scrollbar-rail-inset-vertical); width: var(--n-scrollbar-width); `, [ c('>', [ diff --git a/src/_internal/scrollbar/src/styles/rtl.cssr.ts b/src/_internal/scrollbar/src/styles/rtl.cssr.ts index 232f5ad2c..7961d6c0c 100644 --- a/src/_internal/scrollbar/src/styles/rtl.cssr.ts +++ b/src/_internal/scrollbar/src/styles/rtl.cssr.ts @@ -13,11 +13,7 @@ export default cB('scrollbar', [ right: unset; `) ]) - ]), - cM('vertical', ` - left: 4px; - right: unset; - `) + ]) ]) ]) ]) diff --git a/src/_internal/scrollbar/styles/common.ts b/src/_internal/scrollbar/styles/common.ts new file mode 100644 index 000000000..501058802 --- /dev/null +++ b/src/_internal/scrollbar/styles/common.ts @@ -0,0 +1,5 @@ +export const commonVars = { + railInsetHorizontal: 'auto 2px 4px 2px', + railInsetVertical: '2px 4px 2px auto', + railColor: 'transparent' +} diff --git a/src/_internal/scrollbar/styles/light.ts b/src/_internal/scrollbar/styles/light.ts index 41c82a2ff..efc9ad1f5 100644 --- a/src/_internal/scrollbar/styles/light.ts +++ b/src/_internal/scrollbar/styles/light.ts @@ -1,10 +1,21 @@ import { commonLight } from '../../../_styles/common' import type { ThemeCommonVars } from '../../../_styles/common' import type { Theme } from '../../../_mixins' +import { commonVars } from './common' export const self = (vars: ThemeCommonVars) => { - const { scrollbarColor, scrollbarColorHover } = vars + const { + scrollbarColor, + scrollbarColorHover, + scrollbarHeight, + scrollbarWidth, + scrollbarBorderRadius + } = vars return { + ...commonVars, + height: scrollbarHeight, + width: scrollbarWidth, + borderRadius: scrollbarBorderRadius, color: scrollbarColor, colorHover: scrollbarColorHover } diff --git a/src/_mixins/use-theme.ts b/src/_mixins/use-theme.ts index 41c0fa75d..46e522790 100644 --- a/src/_mixins/use-theme.ts +++ b/src/_mixins/use-theme.ts @@ -35,33 +35,32 @@ export interface ThemePropsReactive { builtinThemeOverrides?: ExtractThemeOverrides } -export type ExtractThemeVars = T extends Theme - ? unknown extends U // self is undefined, ThemeVars is unknown - ? Record - : U - : Record +export type ExtractThemeVars = + T extends Theme + ? unknown extends U // self is undefined, ThemeVars is unknown + ? Record + : U + : Record -export type ExtractPeerOverrides = T extends Theme - ? { - peers?: { - [k in keyof V]?: ExtractThemeOverrides +export type ExtractPeerOverrides = + T extends Theme + ? { + peers?: { + [k in keyof V]?: ExtractThemeOverrides + } } - } - : T + : T // V is peers theme -export type ExtractMergedPeerOverrides = T extends Theme< -unknown, -unknown, -infer V -> - ? { - [k in keyof V]?: ExtractPeerOverrides - } - : T +export type ExtractMergedPeerOverrides = + T extends Theme + ? { + [k in keyof V]?: ExtractPeerOverrides + } + : T export type ExtractThemeOverrides = Partial> & -ExtractPeerOverrides & { common?: ThemeCommonVars } +ExtractPeerOverrides & { common?: Partial } export function createTheme ( theme: Theme @@ -75,14 +74,15 @@ type UseThemeProps = Readonly<{ builtinThemeOverrides?: ExtractThemeOverrides }> -export type MergedTheme = T extends Theme - ? { - common: ThemeCommonVars - self: V - peers: W - peerOverrides: ExtractMergedPeerOverrides - } - : T +export type MergedTheme = + T extends Theme + ? { + common: ThemeCommonVars + self: V + peers: W + peerOverrides: ExtractMergedPeerOverrides + } + : T function useTheme ( resolveId: Exclude, diff --git a/src/_utils/vue/resolve-slot.ts b/src/_utils/vue/resolve-slot.ts index e5eea4bd5..232b56e74 100644 --- a/src/_utils/vue/resolve-slot.ts +++ b/src/_utils/vue/resolve-slot.ts @@ -7,7 +7,7 @@ import { type VNodeChild } from 'vue' -function ensureValidVNode ( +export function ensureValidVNode ( vnodes: VNodeArrayChildren ): VNodeArrayChildren | null { return vnodes.some((child) => { diff --git a/src/auto-complete/tests/AutoComplete.spec.ts b/src/auto-complete/tests/AutoComplete.spec.ts index d67ae7b6c..35ec11407 100644 --- a/src/auto-complete/tests/AutoComplete.spec.ts +++ b/src/auto-complete/tests/AutoComplete.spec.ts @@ -114,10 +114,7 @@ describe('n-auto-complete', () => { const wrapper = mount(NAutoComplete) await wrapper.setProps({ getShow: (value: string | null) => { - if (value && value.endsWith('@')) { - return true - } - return false + return !!value?.endsWith('@') }, options }) diff --git a/src/card/demos/enUS/index.demo-entry.md b/src/card/demos/enUS/index.demo-entry.md index 47e149c89..09dee896f 100644 --- a/src/card/demos/enUS/index.demo-entry.md +++ b/src/card/demos/enUS/index.demo-entry.md @@ -25,22 +25,27 @@ embedded.vue | Name | Type | Default | Description | Version | | --- | --- | --- | --- | --- | +| action | `() => VNodeChild` | `undefined` | Operating area content, must be a render function. | NEXT_VERION | | bordered | `boolean` | `true` | Whether to show the card border. | | | closable | `boolean` | `false` | Is it allowed to close. | | +| content | `string \| (() => VNodeChild)` | `undefined` | Card content, can be a render function. | NEXT_VERION | | content-class | `string` | `undefined` | The class of the card content area. | 2.36.0 | | content-style | `Object \| string` | `undefined` | The style of the card content area. | | +| cover | `() => VNodeChild` | `undefined` | Cover content, must be a render function. | NEXT_VERION | | embedded | `boolean` | `false` | Use a darker background color to show the embedding effect (only for bright themes) | | +| footer | `() => VNodeChild` | `undefined` | Footer content, must be a render function. | NEXT_VERION | | footer-class | `string` | `undefined` | The class of the bottom area of the card. | 2.36.0 | | footer-style | `Object \| string` | `undefined` | The style of the bottom area of the card. | | | header-class | `string` | `undefined` | The class of the card head area. | 2.36.0 | | header-style | `Object \| string` | `undefined` | The style of the card head area. | | +| header-extra | `() => VNodeChild` | `undefined` | Header extra content, must be a render function. | NEXT_VERION | | header-extra-class | `string` | `undefined` | The class of the card head extra area. | 2.36.0 | | header-extra-style | `Object \| string` | `undefined` | The style of the card head extra area. | 2.25.0 | | hoverable | `boolean` | `false` | Whether to show shadow when hovering on the card. | | | segmented | `boolean \| { [part in 'content' \| 'footer' \| 'action']?: boolean \| 'soft' }` | `false` | Segment divider settings of the card. | | | size | `'small' \| 'medium' \| 'large' \| 'huge'` | `'medium'` | Card size. | | | tag | `string` | `'div'` | What tag need the card be rendered as. | 2.34.3 | -| title | `string` | `undefined` | Card title. | | +| title | `string \| (() => VNodeChild)` | `undefined` | Card title. | Render function since 2.38.2 | | on-close | `() => void` | `undefined` | Callback function triggered upon closing the card. | | ### Card Slots diff --git a/src/card/demos/zhCN/index.demo-entry.md b/src/card/demos/zhCN/index.demo-entry.md index cb17e124a..ad81bca77 100644 --- a/src/card/demos/zhCN/index.demo-entry.md +++ b/src/card/demos/zhCN/index.demo-entry.md @@ -27,22 +27,27 @@ embedded-debug.vue | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | +| action | `() => VNodeChild` | `undefined` | 操作区域内容。,需要是 render 函数 | NEXT_VERION | | bordered | `boolean` | `true` | 是否显示卡片边框 | | | closable | `boolean` | `false` | 是否允许关闭 | | +| content | `string \| (() => VNodeChild)` | `undefined` | 卡片内容,,可以是 render 函数 | NEXT_VERION | | content-class | `string` | `undefined` | 卡片内容区域的类名 | 2.36.0 | | content-style | `Object \| string` | `undefined` | 卡片内容区域的样式 | | +| cover | `() => VNodeChild` | `undefined` | 覆盖内容,需要是 render 函数 | NEXT_VERION | | embedded | `boolean` | `false` | 使用更深的背景色展现嵌入效果,只对亮色主题生效 | | +| footer | `() => VNodeChild` | `undefined` | 底部内容 | NEXT_VERION | | footer-class | `string` | `undefined` | 卡片底部区域的类名 | 2.36.0 | | footer-style | `Object \| string` | `undefined` | 卡片底部区域的样式 | | | header-class | `string` | `undefined` | 卡片头部区域的类名 | 2.36.0 | | header-style | `Object \| string` | `undefined` | 卡片头部区域的样式 | | +| header-extra | `() => VNodeChild` | `undefined` | 头部额外内容,需要是 render 函数 | NEXT_VERION | | header-extra-class | `string` | `undefined` | 卡片头部额外内容的类名 | 2.36.0 | | header-extra-style | `Object \| string` | `undefined` | 卡片头部额外内容的样式 | 2.25.0 | | hoverable | `boolean` | `false` | 卡片是否可悬浮 | | | segmented | `boolean \| { [part in 'content' \| 'footer' \| 'action']?: boolean \| 'soft' }` | `false` | 卡片的分段区域设置 | | | size | `'small' \| 'medium' \| 'large' \| 'huge'` | `'medium'` | 卡片的尺寸 | | | tag | `string` | `'div'` | 卡片组件要渲染为什么标签 | 2.34.3 | -| title | `string` | `undefined` | 卡片的标题 | | +| title | `string \| (() => VNodeChild)` | `undefined` | 卡片的标题,,可以是 render 函数 | 2.38.2 支持 render 函数 | | on-close | `() => void` | `undefined` | 点击卡片关闭图标时的回调 | | ### Card Slots diff --git a/src/card/src/Card.tsx b/src/card/src/Card.tsx index df812e0a3..7cef7b6fb 100644 --- a/src/card/src/Card.tsx +++ b/src/card/src/Card.tsx @@ -3,7 +3,8 @@ import { defineComponent, computed, type PropType, - type CSSProperties + type CSSProperties, + type VNodeChild } from 'vue' import { getPadding } from 'seemly' import { useRtl } from '../../_mixins/use-rtl' @@ -15,6 +16,7 @@ import { NBaseClose } from '../../_internal' import { cardLight } from '../styles' import type { CardTheme } from '../styles' import style from './styles/index.cssr' +import { ensureValidVNode } from '../../_utils/vue/resolve-slot' export interface CardSegmented { content?: boolean | 'soft' @@ -23,7 +25,7 @@ export interface CardSegmented { } export const cardBaseProps = { - title: String, + title: [String, Function] as PropType VNodeChild)>, contentClass: String, contentStyle: [Object, String] as PropType, headerClass: String, @@ -52,7 +54,12 @@ export const cardBaseProps = { tag: { type: String as PropType, default: 'div' - } + }, + cover: Function as PropType<() => VNodeChild>, + content: [String, Function] as PropType VNodeChild)>, + footer: Function as PropType<() => VNodeChild>, + action: Function as PropType<() => VNodeChild>, + headerExtra: Function as PropType<() => VNodeChild> } as const export const cardBasePropKeys = keysOf(cardBaseProps) @@ -216,31 +223,43 @@ export default defineComponent({ style={this.cssVars as CSSProperties} role={this.role} > - {resolveWrappedSlot( - $slots.cover, - (children) => - children && ( + {resolveWrappedSlot($slots.cover, (children) => { + const mergedChildren = this.cover + ? ensureValidVNode([this.cover()]) + : children + return ( + mergedChildren && (
- {children} + {mergedChildren}
) - )} + ) + })} {resolveWrappedSlot($slots.header, (children) => { - return children || this.title || this.closable ? ( + const { title } = this + const mergedChildren = title + ? ensureValidVNode( + typeof title === 'function' ? [title()] : [title] + ) + : children + return mergedChildren || this.closable ? (
- {children || this.title} + {mergedChildren}
- {resolveWrappedSlot( - $slots['header-extra'], - (children) => - children && ( + {resolveWrappedSlot($slots['header-extra'], (children) => { + const mergedChildren = this.headerExtra + ? ensureValidVNode([this.headerExtra()]) + : children + return ( + mergedChildren && (
- {children} + {mergedChildren}
) - )} - {this.closable ? ( + ) + })} + {this.closable && ( - ) : null} + )}
) : null })} - {resolveWrappedSlot( - $slots.default, - (children) => - children && ( + {resolveWrappedSlot($slots.default, (children) => { + const { content } = this + const mergedChildren = content + ? ensureValidVNode( + typeof content === 'function' ? [content()] : [content] + ) + : children + return ( + mergedChildren && (
- {children} + {mergedChildren}
) - )} - {resolveWrappedSlot( - $slots.footer, - (children) => - children && [ + ) + })} + {resolveWrappedSlot($slots.footer, (children) => { + const mergedChildren = this.footer + ? ensureValidVNode([this.footer()]) + : children + return ( + mergedChildren && (
- {children} -
- ] - )} - {resolveWrappedSlot( - $slots.action, - (children) => - children && ( -
- {children} + {mergedChildren}
) - )} + ) + })} + {resolveWrappedSlot($slots.action, (children) => { + const mergedChildren = this.action + ? ensureValidVNode([this.action()]) + : children + return ( + mergedChildren && ( +
+ {mergedChildren} +
+ ) + ) + })} ) } diff --git a/src/cascader/demos/enUS/index.demo-entry.md b/src/cascader/demos/enUS/index.demo-entry.md index 250be4e1a..e0211a1b9 100644 --- a/src/cascader/demos/enUS/index.demo-entry.md +++ b/src/cascader/demos/enUS/index.demo-entry.md @@ -39,6 +39,7 @@ status.vue | filterable | `boolean` | `false` | Note: If `remote` is set, this won't have any effect. | | | filter | `(pattern: string, option: CascaderOption, path: CascaderOption[]) => boolean` | A string based filter algorithm. | Filter function of the cascader. | | | filter-menu-props | `HTMLAttributes` | `undefined` | The filter menu's dom props. | 2.27.0 | +| get-column-style | `(detail: { level: number }) => string \| object` | `undefined` | Function that resolves column style. `level` starts from `0`. | 2.38.2 | | value-field | `string` | `'value'` | The value field in `CascaderOption`. | | | label-field | `string` | `'label'` | The label field in `CascaderOption`. | | | max-tag-count | `number \| 'responsive'` | `undefined` | Max tag count in multiple select mode. `responsive` will keep all the tags in single line. | | @@ -48,7 +49,9 @@ status.vue | placeholder | `string` | `'Please Select'` | Placeholder text. | | | placement | `'top-start' \| 'top' \| 'top-end' \| 'right-start' \| 'right' \| 'right-end' \| 'bottom-start' \| 'bottom' \| 'bottom-end' \| 'left-start' \| 'left' \| 'left-end'` | `'bottom-start'` | Cascader placement. | 2.25.0 | | remote | `boolean` | `false` | Whether to obtain data remotely. | | +| render-prefix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | Render function of all the options' prefix. | 2.38.2 | | render-label | `(option: CascaderOption, checked: boolean) => VNodeChild` | `undefined` | Render function for cascader menu option label. | 2.24.0 | +| render-suffix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | Render function of all the options' suffix. | 2.38.2 | | separator | `string` | `' / '` | Selected option path value separator (used with `show-path`). | | | show | `boolean` | `undefined` | Whether to show the menu. | | | show-path | `boolean` | `true` | Whether to show the selected options as a path. | | diff --git a/src/cascader/demos/zhCN/index.demo-entry.md b/src/cascader/demos/zhCN/index.demo-entry.md index 3fa50d2af..5a4fded38 100644 --- a/src/cascader/demos/zhCN/index.demo-entry.md +++ b/src/cascader/demos/zhCN/index.demo-entry.md @@ -40,6 +40,7 @@ default-value-debug.vue | filterable | `boolean` | `false` | `remote` 被设定时不生效 | | | filter | `(pattern: string, option: CascaderOption, path: CascaderOption[]) => boolean` | 一个基于字符串的过滤算法 | 过滤选项的函数 | | | filter-menu-props | `HTMLAttributes` | `undefined` | 可过滤菜单的 DOM 属性 | 2.27.0 | +| get-column-style | `(detail: { level: number }) => string \| object` | `undefined` | 获取列样式的函数,`level` 从 `0` 开始 | 2.38.2 | | value-field | `string` | `'value'` | 替代 `CascaderOption` 中的 value 字段名 | | | label-field | `string` | `'label'` | 替代 `CascaderOption` 中的 label 字段名 | | | max-tag-count | `number \| 'responsive'` | `undefined` | 多选标签的最大显示数量,`responsive` 会将所有标签保持在一行 | | @@ -49,7 +50,9 @@ default-value-debug.vue | placeholder | `string` | `'请选择'` | 提示信息 | | | placement | `'top-start' \| 'top' \| 'top-end' \| 'right-start' \| 'right' \| 'right-end' \| 'bottom-start' \| 'bottom' \| 'bottom-end' \| 'left-start' \| 'left' \| 'left-end'` | `'bottom-start'` | 弹出位置 | 2.25.0 | | remote | `boolean` | `false` | 是否远程获取数据 | | +| render-prefix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | 节点前缀的渲染函数 | 2.38.2 | | render-label | `(option: CascaderOption, checked: boolean) => VNodeChild` | `undefined` | Cascader 菜单选项标签渲染函数 | 2.24.0 | +| render-suffix | `(info: { option: CascaderOption, checked: boolean }) => VNodeChild` | `undefined` | 节点后缀的渲染函数 | 2.38.2 | | separator | `string` | `' / '` | 数据分隔符 | | | show | `boolean` | `undefined` | 是否打开菜单 | | | show-path | `boolean` | `true` | 是否在选择器中显示选项路径 | | diff --git a/src/cascader/src/Cascader.tsx b/src/cascader/src/Cascader.tsx index 7f8e335d6..2fc015d64 100644 --- a/src/cascader/src/Cascader.tsx +++ b/src/cascader/src/Cascader.tsx @@ -12,7 +12,8 @@ import { watchEffect, type VNodeChild, type HTMLAttributes, - nextTick + nextTick, + type VNode } from 'vue' import { createTreeMate, @@ -169,6 +170,23 @@ export const cascaderProps = { >, onBlur: Function as PropType<(e: FocusEvent) => void>, onFocus: Function as PropType<(e: FocusEvent) => void>, + getColumnStyle: Function as PropType< + (detail: { level: number }) => string | CSSProperties + >, + renderPrefix: Function as PropType< + (props: { + option: CascaderOption + checked: boolean + node: VNode | null + }) => VNodeChild + >, + renderSuffix: Function as PropType< + (props: { + option: CascaderOption + checked: boolean + node: VNode | null + }) => VNodeChild + >, // deprecated onChange: [Function, Array] as PropType | undefined> } as const @@ -873,6 +891,9 @@ export default defineComponent({ localeRef, labelFieldRef: toRef(props, 'labelField'), renderLabelRef: toRef(props, 'renderLabel'), + getColumnStyleRef: toRef(props, 'getColumnStyle'), + renderPrefixRef: toRef(props, 'renderPrefix'), + renderSuffixRef: toRef(props, 'renderSuffix'), syncCascaderMenuPosition, syncSelectMenuPosition, updateKeyboardKey, diff --git a/src/cascader/src/CascaderMenu.tsx b/src/cascader/src/CascaderMenu.tsx index 0eaaa3bd8..90bdd3f50 100644 --- a/src/cascader/src/CascaderMenu.tsx +++ b/src/cascader/src/CascaderMenu.tsx @@ -65,7 +65,8 @@ export default defineComponent({ mergedClsPrefixRef, syncCascaderMenuPosition, handleCascaderMenuClickOutside, - mergedThemeRef + mergedThemeRef, + getColumnStyleRef // eslint-disable-next-line @typescript-eslint/no-non-null-assertion } = inject(cascaderInjectionKey)! const submenuInstRefs: CascaderSubmenuInstance[] = [] @@ -114,6 +115,7 @@ export default defineComponent({ submenuInstRefs, maskInstRef, mergedTheme: mergedThemeRef, + getColumnStyle: getColumnStyleRef, handleFocusin, handleFocusout, handleClickOutside, @@ -141,6 +143,7 @@ export default defineComponent({
{this.menuModel.map((submenuOptions, index) => ( { if (instance) { diff --git a/src/cascader/src/CascaderOption.tsx b/src/cascader/src/CascaderOption.tsx index ba45384c5..4601dfe26 100644 --- a/src/cascader/src/CascaderOption.tsx +++ b/src/cascader/src/CascaderOption.tsx @@ -4,7 +4,8 @@ import { inject, defineComponent, type PropType, - Transition + Transition, + type VNode } from 'vue' import { useMemo } from 'vooks' import { NCheckbox } from '../../checkbox' @@ -39,6 +40,8 @@ export default defineComponent({ mergedThemeRef, labelFieldRef, showCheckboxRef, + renderPrefixRef, + renderSuffixRef, updateHoverKey, updateKeyboardKey, addLoadingKey, @@ -171,88 +174,121 @@ export default defineComponent({ handleCheckboxUpdateValue, mergedHandleMouseEnter: mergedHandleMouseEnterRef, mergedHandleMouseMove: mergedHandleMouseMoveRef, - renderLabel: renderLabelRef + renderLabel: renderLabelRef, + renderPrefix: renderPrefixRef, + renderSuffix: renderSuffixRef } }, render () { - const { mergedClsPrefix, renderLabel } = this + const { + mergedClsPrefix, + showCheckbox, + renderLabel, + renderPrefix, + renderSuffix + } = this + + let prefixNode: VNode | null = null + if (showCheckbox || renderPrefix) { + const originalNode = this.showCheckbox ? ( + + ) : null + prefixNode = ( +
+ {renderPrefix + ? renderPrefix({ + option: this.tmNode.rawNode, + checked: this.checked, + node: originalNode + }) + : originalNode} +
+ ) + } + let suffixNode: VNode | null = null + const originalSuffixChild = ( +
+ {!this.isLeaf ? ( + + {{ + default: () => ( + + {{ + default: () => + }} + + ) + }} + + ) : this.checkStrategy === 'child' && + !(this.multiple && this.cascade) ? ( + + {{ + default: () => + this.checked ? ( + + {{ default: () => }} + + ) : null + }} + + ) : null} +
+ ) + suffixNode = ( +
+ {renderSuffix + ? renderSuffix({ + option: this.tmNode.rawNode, + checked: this.checked, + node: originalSuffixChild + }) + : originalSuffixChild} +
+ ) return (
- {this.showCheckbox ? ( -
- -
- ) : null} + {prefixNode} {renderLabel ? renderLabel(this.tmNode.rawNode, this.checked) : this.label} -
-
- {!this.isLeaf ? ( - - {{ - default: () => ( - - {{ - default: () => - }} - - ) - }} - - ) : this.checkStrategy === 'child' && - !(this.multiple && this.cascade) ? ( - - {{ - default: () => - this.checked ? ( - - {{ default: () => }} - - ) : null - }} - - ) : null} -
-
+ {suffixNode}
) } diff --git a/src/cascader/src/interface.ts b/src/cascader/src/interface.ts index ece12926e..06bd9db1f 100644 --- a/src/cascader/src/interface.ts +++ b/src/cascader/src/interface.ts @@ -2,7 +2,7 @@ import type { CheckStrategy, TreeNode } from 'treemate' import type { MergedTheme } from '../../_mixins' import type { NLocale } from '../../locales' import type { CascaderTheme } from '../styles' -import type { Ref, Slots, VNodeChild } from 'vue' +import type { CSSProperties, Ref, Slots, VNode, VNodeChild } from 'vue' import { createInjectionKey } from '../../_utils' export type ValueAtom = string | number @@ -79,6 +79,25 @@ export interface CascaderInjection { optionHeightRef: Ref labelFieldRef: Ref showCheckboxRef: Ref + getColumnStyleRef: Ref< + ((detail: { level: number }) => string | CSSProperties) | undefined + > + renderPrefixRef: Ref< + | ((info: { + option: CascaderOption + checked: boolean + node: VNode | null + }) => VNodeChild) + | undefined + > + renderSuffixRef: Ref< + | ((info: { + option: CascaderOption + checked: boolean + node: VNode | null + }) => VNodeChild) + | undefined + > syncCascaderMenuPosition: () => void syncSelectMenuPosition: () => void updateKeyboardKey: (value: Key | null) => void diff --git a/src/cascader/src/styles/index.cssr.ts b/src/cascader/src/styles/index.cssr.ts index 3521ceeb7..2905ab047 100644 --- a/src/cascader/src/styles/index.cssr.ts +++ b/src/cascader/src/styles/index.cssr.ts @@ -37,17 +37,17 @@ export default c([ flex: 1; justify-content: center; `), - cB('scrollbar', { - // if width not set, cascader select menu's inner scroll area's width is - // not correct, which won't change after select menu width is set - width: '100%' - }), - cB('base-menu-mask', { - backgroundColor: 'var(--n-menu-mask-color)' - }), - cB('base-loading', { - color: 'var(--n-loading-color)' - }), + // if width not set, cascader select menu's inner scroll area's width is + // not correct, which won't change after select menu width is set + cB('scrollbar', ` + width: 100%; + `), + cB('base-menu-mask', ` + background-color: var(--n-menu-mask-color); + `), + cB('base-loading', ` + color: var(--n-loading-color); + `), cB('cascader-submenu-wrapper', ` position: relative; display: flex; @@ -61,9 +61,9 @@ export default c([ cM('virtual', ` width: var(--n-column-width); `), - cB('scrollbar-content', { - position: 'relative' - }), + cB('scrollbar-content', ` + position: relative; + `), c('&:first-child', ` border-top-left-radius: var(--n-menu-border-radius); border-bottom-left-radius: var(--n-menu-border-radius); @@ -98,68 +98,68 @@ export default c([ background-color .2s var(--n-bezier), color 0.2s var(--n-bezier); `, [ - cM('show-prefix', { - paddingLeft: 0 - }), + cM('show-prefix', ` + padding-left: 0; + `), cE('label', ` flex: 1 0 0; overflow: hidden; text-overflow: ellipsis; `), - cE('prefix', { - width: '32px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - }), - cE('suffix', { - width: '32px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - }), - cB('cascader-option-icon-placeholder', { - lineHeight: 0, - position: 'relative', - width: '16px', - height: '16px', - fontSize: '16px' - }, [ + cE('prefix', ` + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; + `), + cE('suffix', ` + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; + `), + cB('cascader-option-icon-placeholder', ` + line-height: 0; + position: relative; + width: 16px; + height: 16px; + font-size: 16px; + `, [ cB('cascader-option-icon', [ - cM('checkmark', { - color: 'var(--n-option-check-mark-color)' - }, [ + cM('checkmark', ` + color: var(--n-option-check-mark-color); + `, [ fadeInScaleUpTransition({ originalTransition: 'background-color .3s var(--n-bezier), box-shadow .3s var(--n-bezier)' }) ]), - cM('arrow', { - color: 'var(--n-option-arrow-color)' - }) + cM('arrow', ` + color: var(--n-option-arrow-color); + `) ]) ]), - cM('selected', { - color: 'var(--n-option-text-color-active)' - }), - cM('active', { - color: 'var(--n-option-text-color-active)', - backgroundColor: 'var(--n-option-color-hover)' - }), - cM('pending', { - backgroundColor: 'var(--n-option-color-hover)' - }), - c('&:hover', { - backgroundColor: 'var(--n-option-color-hover)' - }), + cM('selected', ` + color: var(--n-option-text-color-active); + `), + cM('active', ` + color: var(--n-option-text-color-active); + background-color: var(--n-option-color-hover); + `), + cM('pending', ` + background-color: var(--n-option-color-hover); + `), + c('&:hover', ` + background-color: var(--n-option-color-hover); + `), cM('disabled', ` color: var(--n-option-text-color-disabled); background-color: #0000; cursor: not-allowed; `, [ cB('cascader-option-icon', [ - cM('arrow', { - color: 'var(--n-option-text-color-disabled)' - }) + cM('arrow', ` + color: var(--n-option-text-color-disabled); + `) ]) ]) ]) diff --git a/src/code/tests/Code.spec.ts b/src/code/tests/Code.spec.tsx similarity index 82% rename from src/code/tests/Code.spec.ts rename to src/code/tests/Code.spec.tsx index ef30216a0..26a97c1e9 100644 --- a/src/code/tests/Code.spec.ts +++ b/src/code/tests/Code.spec.tsx @@ -1,3 +1,4 @@ +import { toRaw, h } from 'vue' import { mount } from '@vue/test-utils' import { NCode } from '../index' import hljs from 'highlight.js/lib/core' @@ -21,23 +22,19 @@ describe('n-code', () => { wrapper.unmount() }) it('should work with `language` prop', () => { - const wrapper = mount(NCode, { - props: { - code: 'console.log(a)', - language: 'javascript', - hljs - } + const wrapper = mount(() => { + return ( + + ) }) expect(wrapper.find('.hljs-variable').text()).toBe('console') wrapper.unmount() }) it('should work with `hljs` prop', () => { - const wrapper = mount(NCode, { - props: { - code: 'console.log(a)', - language: 'javascript', - hljs - } + const wrapper = mount(() => { + return ( + + ) }) expect(wrapper.find('.function_').text()).toBe('log') wrapper.unmount() diff --git a/src/components.ts b/src/components.ts index 5a1a2e972..97021e443 100644 --- a/src/components.ts +++ b/src/components.ts @@ -51,6 +51,7 @@ export * from './legacy-transfer' export * from './list' export * from './loading-bar' export * from './log' +export * from './infinite-scroll' export * from './menu' export * from './mention' export * from './message' diff --git a/src/date-picker/demos/enUS/index.demo-entry.md b/src/date-picker/demos/enUS/index.demo-entry.md index 3c047cd75..aa8325e74 100644 --- a/src/date-picker/demos/enUS/index.demo-entry.md +++ b/src/date-picker/demos/enUS/index.demo-entry.md @@ -53,6 +53,7 @@ panel.vue | show | `boolean` | `undefined` | Whether to show panel. | 2.28.3 | | size | `'small' \| 'medium' \| 'large'` | `'medium'` | Date picker size. | | | status | `'success' \| 'warning' \| 'error'` | `undefined` | Validation status. | 2.27.0 | +| time-picker-format | `string \| undefined` | `undefined` | Format of the binding value in time picker inside date picker of type `'datetime'` and `'datetimerange'`. See [format](https://date-fns.org/v2.23.0/docs/format). | 2.38.2 | | to | `string \| HTMLElement \| false` | `body` | Container node of the panel. `false` will keep it not detached. | | | type | `'date' \| 'datetime' \| 'daterange' \| 'datetimerange' \| 'month' \| 'monthrange' \| 'year' \| 'yearrange' \| 'quarter' \| 'quarterrange' \| 'week'` | `'date'` | Date picker type. | `'quarter'` v2.22.0, `'monthrange'` 2.28.3 | | value | `number \| [number, number] \| null` | `undefined` | Value of the date picker when being manually set. | | diff --git a/src/date-picker/demos/zhCN/index.demo-entry.md b/src/date-picker/demos/zhCN/index.demo-entry.md index 0f50491f6..69244261e 100644 --- a/src/date-picker/demos/zhCN/index.demo-entry.md +++ b/src/date-picker/demos/zhCN/index.demo-entry.md @@ -54,6 +54,7 @@ form-debug.vue | show | `boolean` | `undefined` | 是否展示面板 | 2.28.3 | | size | `'small' \| 'medium' \| 'large'` | `'medium'` | 尺寸 | | | status | `'success' \| 'warning' \| 'error'` | `undefined` | 验证状态 | 2.27.0 | +| time-picker-format | `string \| undefined` | `undefined` | 日期面板内时间的显示方式,详情见 [format](https://date-fns.org/v2.23.0/docs/format) | 2.38.2 | | to | `string \| HTMLElement \| false` | `body` | 面板的容器节点,`false` 会待在原地 | | | type | `'date' \| 'datetime' \| 'daterange' \| 'datetimerange' \| 'month' \| 'monthrange' \| 'year' \| 'yearrange' \| 'quarter' \| 'quarterrange' \| 'week'` | `'date'` | Date Picker 的类型 | `'quarter'` v2.22.0, `'monthrange'` 2.28.3 | | value | `number \| [number, number] \| null` | `undefined` | Date Picker 的值 | | diff --git a/src/date-picker/src/DatePicker.tsx b/src/date-picker/src/DatePicker.tsx index 6add2cbfc..f39274ce7 100644 --- a/src/date-picker/src/DatePicker.tsx +++ b/src/date-picker/src/DatePicker.tsx @@ -113,7 +113,7 @@ export const datePickerProps = { endPlaceholder: String, format: String, dateFormat: String, - timeFormat: String, + timerPickerFormat: String, actions: Array as PropType | null>, shortcuts: Object as PropType, isDateDisabled: Function as PropType, @@ -1022,7 +1022,8 @@ export default defineComponent({ onNextMonth: this.onNextMonth, onPrevMonth: this.onPrevMonth, onNextYear: this.onNextYear, - onPrevYear: this.onPrevYear + onPrevYear: this.onPrevYear, + timerPickerFormat: this.timerPickerFormat } const renderPanel = (): VNode => { const { type } = this diff --git a/src/date-picker/src/panel/datetime.tsx b/src/date-picker/src/panel/datetime.tsx index 526065399..aa519ad1e 100644 --- a/src/date-picker/src/panel/datetime.tsx +++ b/src/date-picker/src/panel/datetime.tsx @@ -64,7 +64,7 @@ export default defineComponent({ VNodeChild` | `undefined` | Content of the operation area, must be a `render` function. | | +| action | `() => VNodeChild` | `undefined` | Content of the operation area, must be a render function. | | +| actionClass | `string` | The class name of the action area. | 2.38.2 | +| actionStyle | `Object \| string` | The style of the action area. | 2.38.2 | | autoFocus | `boolean` | `true` | Whether to focus the first focusable element inside modal. | 2.28.3 | | blockScroll | `boolean` | `true` | Whether to disabled body scrolling when it's active. | 2.28.3 | | bordered | `boolean` | `false` | Whether to show `border`. | | | class | `any` | `undefined` | Class name of the dialog. | 2.33.0 | | closable | `boolean` | `true` | Whether to show `close` icon. | | | closeOnEsc | `boolean` | `true` | Whether to close the dialog when the Esc key is pressed | 2.26.4 | -| content | `string \| (() => VNodeChild)` | `undefined` | Content, can be a `render` function. | | +| content | `string \| (() => VNodeChild)` | `undefined` | Content, can be a render function. | | +| contentClass | `string` | The class name of the content. | 2.38.2 | +| contentStyle | `Object \| string` | The style of the content. | 2.38.2 | | iconPlacement | `'left' \| 'top'` | `'left'` | Icon placement. | | | icon | `() => VNodeChild` | `undefined` | `Render` function of `icon`. | | | loading | `boolean` | `false` | Whether to display `loading` status. | | @@ -82,7 +86,9 @@ use-dialog-reactive-list.vue | positiveText | `string` | `undefined` | Confirm button text. Corresponding button won't show if not set. | | | showIcon | `boolean` | `true` | Whether to show `icon`. | | | style | `string \| Object` | `undefined` | Style of the dialog. | | -| title | `string \| (() => VNodeChild)` | `undefined` | Title, can be a `render` function. | | +| title | `string \| (() => VNodeChild)` | `undefined` | Title, can be a render function. | | +| titleClass | `string` | The class name of the content. | 2.38.2 | +| titleStyle | `Object \| string` | The style of the content. | 2.38.2 | | transformOrigin | `'mouse' \| 'center'` | `'mouse'` | The transform origin of the dialog's display animation. | 2.34.0 | | type | `'error \| 'success' \| 'warning'` | `'warning'` | Dialog type. | | | onAfterEnter | `() => void` | `undefined` | Callback on enter animation ends. | 2.33.0 | @@ -100,11 +106,15 @@ All the properties can be modified dynamically. | Name | Type | Description | Version | | --- | --- | --- | --- | +| actionClass | `string` | The class name of the action area. | 2.38.2 | +| actionStyle | `Object \| string` | The style of the action area. | 2.38.2 | | bordered | `boolean` | Whether to show `border`. | | | class | `any` | Class name of the dialog. | 2.33.0 | | closable | `boolean` | Whether to show `close` icon. | | | closeOnEsc | `boolean` | Whether to close dialog on Esc is pressed. | 2.26.4 | -| content | `string \| (() => VNodeChild)` | Content, can be a `render` function. | | +| content | `string \| (() => VNodeChild)` | Content, can be a render function. | | +| contentClass | `string` | The class name of the content. | 2.38.2 | +| contentStyle | `Object \| string` | The style of the content. | 2.38.2 | | iconPlacement | `'left' \| 'top'` | Icon placement. | | | icon | `() => VNodeChild` | `Render` function of `icon`. | | | loading | `boolean` | Whether to display `loading` status. | | @@ -115,7 +125,9 @@ All the properties can be modified dynamically. | positiveText | `string` | Corresponding button won't show if not set. | | | show-icon | `boolean` | Whether to show `icon`. | | | style | `string \| Object` | Style of the dialog. | | -| title | `string \| (() => VNodeChild)` | Can be a `render` function. | | +| title | `string \| (() => VNodeChild)` | Can be a render function. | | +| titleClass | `string` | The class name of the content. | 2.38.2 | +| titleStyle | `Object \| string` | The style of the content. | 2.38.2 | | transformOrigin | `'mouse' \| 'center'` | The transform origin of the dialog's display animation. | 2.34.0 | | type | `'error \| 'success' \| 'warning'` | Dialog type. | | | onAfterEnter | `() => void \| undefined` | Callback on enter animation ends. | 2.33.0 | @@ -135,9 +147,13 @@ All the properties can be modified dynamically. | Name | Type | Default | Description | Version | | --- | --- | --- | --- | --- | +| action-class | `string` | `undefined` | The class name of the action area. | 2.38.2 | +| action-style | `Object \| string` | `undefined` | The style of the action area. | 2.38.2 | | bordered | `boolean` | `false` | Whether to show `border`. | | | closable | `boolean` | `true` | Whether to show `close` icon. | | -| content | `string \| (() => VNodeChild)` | `undefined` | Can be a `render` function. | | +| content | `string \| (() => VNodeChild)` | `undefined` | Can be a render function. | | +| content-class | `string` | `undefined` | The class name of the content. | 2.38.2 | +| content-style | `Object \| string` | `undefined` | The style of the content. | 2.38.2 | | icon-placement | `'left' \| 'top'` | `'left'` | Icon placement. | | | icon | `() => VNodeChild` | `undefined` | `Render` function of icon. | | | loading | `boolean` | `false` | Whether to display `loading` status. | | @@ -146,7 +162,9 @@ All the properties can be modified dynamically. | positive-button-props | `ButtonProps` | `undefined` | Confirm button's DOM props | 2.27.0 | | positive-text | `string` | `undefined` | Corresponding button won't show if not set. | | | show-icon | `boolean` | `true` | Whether to display the `icon`. | | -| title | `string \| (() => VNodeChild)` | `undefined` | Title, can be a `render` function. | | +| title | `string \| (() => VNodeChild)` | `undefined` | Title, can be a render function. | | +| title-class | `string` | `undefined` | The class name of the content. | 2.38.2 | +| title-style | `Object \| string` | `undefined` | The style of the content. | 2.38.2 | | type | `'error \| 'success' \| 'warning' \| 'info'` | `'warning'` | Dialog type. | | | on-close | `() => void` | `undefined` | Calback on close button clicked. | | | on-negative-click | `(e: MouseEvent) => void` | `undefined` | Callback on positive button clicked. | | diff --git a/src/dialog/demos/zhCN/index.demo-entry.md b/src/dialog/demos/zhCN/index.demo-entry.md index 00b79d7cd..7ed80eb2d 100644 --- a/src/dialog/demos/zhCN/index.demo-entry.md +++ b/src/dialog/demos/zhCN/index.demo-entry.md @@ -66,16 +66,20 @@ rtl-debug.vue | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | -| action | `() => VNodeChild` | `undefined` | 操作区域的内容,需要是 `render` 函数 | | +| action | `() => VNodeChild` | `undefined` | 操作区域的内容,需要是渲染函数 | | +| actionClass | `string` | 操作区域的类名 | 2.38.2 | +| actionStyle | `Object \| string` | 操作区域的样式 | 2.38.2 | | autoFocus | `boolean` | `true` | 是否自动聚焦 Modal 第一个可聚焦的元素 | 2.28.3 | | blockScroll | `boolean` | `true` | 是否在打开时禁用 body 滚动 | 2.28.3 | | bordered | `boolean` | `false` | 是否显示 `border` | | | class | `any` | `undefined` | 类名 | 2.33.0 | | closable | `boolean` | `true` | 是否显示 `close` 图标 | | | closeOnEsc | `boolean` | `true` | 是否在摁下 Esc 键的时候关闭对话框 | 2.26.4 | -| content | `string \| (() => VNodeChild)` | `undefined` | 对话框内容,可以是 `render` 函数 | | +| content | `string \| (() => VNodeChild)` | `undefined` | 对话框内容,可以是渲染函数 | | +| contentClass | `string` | 内容的类名 | 2.38.2 | +| contentStyle | `Object \| string` | 内容的样式 | 2.38.2 | | iconPlacement | `'left' \| 'top'` | `'left'` | 图标的位置 | | -| icon | `() => VNodeChild` | `undefined` | 对话框 `icon`, 需要是 `render` 函数 | | +| icon | `() => VNodeChild` | `undefined` | 对话框 `icon`, 需要是渲染函数 | | | loading | `boolean` | `false` | 是否显示 `loading` 状态 | | | maskClosable | `boolean` | `true` | 是否可以通过点击 `mask` 关闭对话框 | | | negativeButtonProps | `ButtonProps` | `undefined` | 取消按钮的属性 | 2.27.0 | @@ -84,7 +88,9 @@ rtl-debug.vue | positiveText | `string` | `undefined` | 确认按钮的文字,不填对应的按钮不会出现 | | | showIcon | `boolean` | `true` | 是否显示 `icon` | | | style | `string \| Object` | `undefined` | 样式 | | -| title | `string \| (() => VNodeChild)` | `undefined` | 标题,可以是 `render` 函数 | | +| title | `string \| (() => VNodeChild)` | `undefined` | 标题,可以是渲染函数 | | +| titleClass | `string` | 标题的类名 | 2.38.2 | +| titleStyle | `Object \| string` | 标题的样式 | 2.38.2 | | transformOrigin | `'mouse' \| 'center'` | `'mouse'` | 对话框动画出现的位置 | 2.34.0 | | type | `'error \| 'success' \| 'warning'` | `'warning'` | 对话框类型 | | | onAfterEnter | `() => void` | `undefined` | 出现动画完成执行的回调 | 2.33.0 | @@ -102,13 +108,17 @@ rtl-debug.vue | 名称 | 类型 | 说明 | 版本 | | --- | --- | --- | --- | +| actionClass | `string` | 操作区域的类名 | 2.38.2 | +| actionStyle | `Object \| string` | 操作区域的样式 | 2.38.2 | | bordered | `boolean` | 是否显示 `border` | | | class | `any` | 类名 | 2.33.0 | | closable | `boolean` | 是否显示 `close` 图标 | | | closeOnEsc | `boolean` | 是否在摁下 Esc 键的时候关闭对话框 | 2.26.4 | -| content | `string \| (() => VNodeChild)` | 对话框内容,可以是 `render` 函数 | | +| content | `string \| (() => VNodeChild)` | 对话框内容,可以是渲染函数 | | +| contentClass | `string` | 内容的类名 | 2.38.2 | +| contentStyle | `Object \| string` | 内容的样式 | 2.38.2 | | iconPlacement | `'left' \| 'top'` | 图标的位置 | | -| icon | `() => VNodeChild` | 对话框 `icon`,需要是 `render` 函数 | | +| icon | `() => VNodeChild` | 对话框 `icon`,需要是渲染函数 | | | loading | `boolean` | 是否显示 `loading` 状态 | | | maskClosable | `boolean` | 是否可以通过点击 `mask` 关闭对话框 | | | negativeButtonProps | `ButtonProps` | 取消按钮的属性 | 2.27.0 | @@ -117,7 +127,9 @@ rtl-debug.vue | positiveText | `string` | 确认按钮的文字,不填对应的按钮不会出现 | | | showIcon | `boolean` | 是否显示 `icon` | | | style | `string \| Object` | 样式 | | -| title | `string \| (() => VNodeChild)` | 可以是 `render` 函数 | | +| title | `string \| (() => VNodeChild)` | 可以是渲染函数 | | +| titleClass | `string` | 标题的类名 | 2.38.2 | +| titleStyle | `Object \| string` | 标题的样式 | 2.38.2 | | transformOrigin | `'mouse' \| 'center'` | 对话框动画出现的位置 | 2.34.0 | | type | `'error \| 'success' \| 'warning'` | 对话框类型 | | | onAfterEnter | `() => void \| undefined` | 出现动画完成执行的回调 | 2.33.0 | @@ -137,18 +149,24 @@ rtl-debug.vue | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | +| action-class | `string` | `undefined` | 操作区域的类名 | 2.38.2 | +| action-style | `Object \| string` | `undefined` | 操作区域的样式 | 2.38.2 | | bordered | `boolean` | `false` | 是否显示 `border` | | | closable | `boolean` | `true` | 是否显示 `close` 图标 | | -| content | `string \| (() => VNodeChild)` | `undefined` | 对话框内容,可以是 `render` 函数 | | +| content | `string \| (() => VNodeChild)` | `undefined` | 对话框内容,可以是渲染函数 | | +| content-class | `string` | `undefined` | 内容的类名 | 2.38.2 | +| content-style | `Object \| string` | `undefined` | 内容的样式 | 2.38.2 | | icon-placement | `'left' \| 'top'` | `'left'` | 图标放置的位置 | | -| icon | `() => VNodeChild` | `undefined` | 需要是 `render` 函数 | | +| icon | `() => VNodeChild` | `undefined` | 需要是渲染函数 | | | loading | `boolean` | `false` | 是否显示 `loading` 状态 | | | negative-button-props | `ButtonProps` | `undefined` | 取消按钮的属性 | 2.27.0 | | negative-text | `string` | `undefined` | 取消按钮的文字,不填对应的按钮不会出现 | | | positive-button-props | `ButtonProps` | `undefined` | 确认按钮的属性 | 2.27.0 | | positive-text | `string` | `undefined` | 确认按钮的文字,不填对应的按钮不会出现 | | | show-icon | `boolean` | `true` | 是否显示 `icon` | | -| title | `string \| (() => VNodeChild)` | `undefined` | 对话框标题,可以是 `render` 函数 | | +| title | `string \| (() => VNodeChild)` | `undefined` | 对话框标题,可以是渲染函数 | | +| title-class | `string` | `undefined` | 标题的类名 | 2.38.2 | +| title-style | `Object \| string` | `undefined` | 标题的样式 | 2.38.2 | | type | `'error \| 'success' \| 'warning' \| 'info'` | `'warning'` | 对话框类型 | | | on-close | `() => void` | `undefined` | 点击关闭时执行的回调函数 | | | on-negative-click | `(e: MouseEvent) => void` | `undefined` | 执行 `negative` 时执行的回调函数 | | diff --git a/src/dialog/src/Dialog.tsx b/src/dialog/src/Dialog.tsx index e4665edf2..bd7ab5c15 100644 --- a/src/dialog/src/Dialog.tsx +++ b/src/dialog/src/Dialog.tsx @@ -1,4 +1,5 @@ import { h, defineComponent, computed, type CSSProperties } from 'vue' +import { getMargin } from 'seemly' import { InfoIcon, SuccessIcon, @@ -19,7 +20,6 @@ import { dialogLight } from '../styles' import type { DialogTheme } from '../styles' import { dialogProps } from './dialogProps' import style from './styles/index.cssr' -import { getMargin } from 'seemly' const iconRenderMap = { default: () => , @@ -205,7 +205,10 @@ export const NDialog = defineComponent({ const actionNode = resolveWrappedSlot(this.$slots.action, (children) => children || positiveText || negativeText || action ? ( -
+
{children || (action ? [render(action)] @@ -278,15 +281,20 @@ export const NDialog = defineComponent({ {showIcon && mergedIconPlacement === 'top' ? (
{icon}
) : null} -
+
{showIcon && mergedIconPlacement === 'left' ? icon : null} {resolveSlot(this.$slots.header, () => [render(title)])}
{resolveSlot(this.$slots.default, () => [render(content)])}
diff --git a/src/dialog/src/DialogProvider.ts b/src/dialog/src/DialogProvider.ts index b1d70eb00..f2eac155b 100644 --- a/src/dialog/src/DialogProvider.ts +++ b/src/dialog/src/DialogProvider.ts @@ -83,14 +83,14 @@ export const NDialogProvider = defineComponent({ props: dialogProviderProps, setup () { const dialogListRef = ref([]) - const dialogInstRefs: Record = {} + const dialogInstRefs: Record = {} function create (options: DialogOptions = {}): DialogReactive { const key = createId() const dialogReactive = reactive({ ...options, key, destroy: () => { - dialogInstRefs[`n-dialog-${key}`].hide() + dialogInstRefs[`n-dialog-${key}`]?.hide() } }) dialogListRef.value.push(dialogReactive) @@ -114,7 +114,7 @@ export const NDialogProvider = defineComponent({ function destroyAll (): void { Object.values(dialogInstRefs).forEach((dialogInstRef) => { - dialogInstRef.hide() + dialogInstRef?.hide() }) } diff --git a/src/dialog/src/dialogProps.ts b/src/dialog/src/dialogProps.ts index 6a7d2e5cc..33b70547a 100644 --- a/src/dialog/src/dialogProps.ts +++ b/src/dialog/src/dialogProps.ts @@ -1,4 +1,4 @@ -import type { PropType, VNodeChild } from 'vue' +import type { CSSProperties, PropType, VNodeChild } from 'vue' import type { ButtonProps } from '../../button' import type { ExtractPublicPropTypes } from '../../_utils' import { keysOf } from '../../_utils' @@ -30,6 +30,12 @@ const dialogProps = { loading: Boolean, bordered: Boolean, iconPlacement: String as PropType, + titleClass: [String, Array] as PropType>, + titleStyle: [String, Object] as PropType, + contentClass: [String, Array] as PropType>, + contentStyle: [String, Object] as PropType, + actionClass: [String, Array] as PropType>, + actionStyle: [String, Object] as PropType, onPositiveClick: Function as PropType<(e: MouseEvent) => void>, onNegativeClick: Function as PropType<(e: MouseEvent) => void>, onClose: Function as PropType<() => void> diff --git a/src/form/demos/enUS/feedback-style.demo.vue b/src/form/demos/enUS/feedback-style.demo.vue new file mode 100644 index 000000000..dc51738e2 --- /dev/null +++ b/src/form/demos/enUS/feedback-style.demo.vue @@ -0,0 +1,35 @@ + +# Custom feedback style + +Using `feedback-style` and `feedback-class` to custom feedback. + + + + + diff --git a/src/form/demos/enUS/index.demo-entry.md b/src/form/demos/enUS/index.demo-entry.md index 356ad236f..752b94e59 100644 --- a/src/form/demos/enUS/index.demo-entry.md +++ b/src/form/demos/enUS/index.demo-entry.md @@ -25,6 +25,7 @@ show-label.vue partially-apply-rules.vue custom-messages.vue dynamic.vue +feedback-style.vue ``` ## API @@ -73,6 +74,8 @@ dynamic.vue | Name | Type | Default | Description | Version | | --- | --- | --- | --- | --- | | feedback | `string` | `undefined` | The feedback message of the form item. If set, it will replace any result of rule-based validation. | | +| feedback-class | `string` | `undefined` | Feedback check vertical display positioning | 2.38.2 | +| feedback-style | `string \| object` | `undefined` | Feedback check horizontal display positioning | 2.38.2 | | first | `boolean` | `false` | Whether to only show the first validation error message. | | | ignore-path-change | `boolean` | `false` | Usually, changing `path` will cause a re-render and naive-ui will clear the validation result. Setting `ignore-path-change` to `true` will disable that behavior. | | | label | `string` | `undefined` | Label. | | diff --git a/src/form/demos/zhCN/feedback-style.demo.vue b/src/form/demos/zhCN/feedback-style.demo.vue new file mode 100644 index 000000000..4597b63d1 --- /dev/null +++ b/src/form/demos/zhCN/feedback-style.demo.vue @@ -0,0 +1,35 @@ + +# 自定义反馈样式 + +使用 `feedback-style` 和 `feedback-class` 可以自定义反馈信息的样式。 + + + + + diff --git a/src/form/demos/zhCN/index.demo-entry.md b/src/form/demos/zhCN/index.demo-entry.md index ed9a843b0..c55e12247 100644 --- a/src/form/demos/zhCN/index.demo-entry.md +++ b/src/form/demos/zhCN/index.demo-entry.md @@ -25,6 +25,7 @@ show-label.vue partially-apply-rules.vue custom-messages.vue dynamic.vue +feedback-style.vue ``` ## API @@ -67,6 +68,8 @@ dynamic.vue | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | | feedback | `string` | `undefined` | 表项的反馈信息。不设为 `undefined` 时,会覆盖规则验证的结果 | | +| feedback-class | `string` | `undefined` | 反馈校验竖向展示定位 | 2.38.2 | +| feedback-style | `string \| object` | `undefined` | 反馈校验横向展示定位 | 2.38.2 | | first | `boolean` | `false` | 是否只展示首个出错信息 | | | ignore-path-change | `boolean` | `false` | 通常 `path` 的改变会导致数据来源的变化,所以 naive-ui 会清空验证信息。如果不期望这个行为,可以将其置为 `true` | | | label | `string` | `undefined` | 标签信息 | | diff --git a/src/form/src/Form.tsx b/src/form/src/Form.tsx index 928b9a094..a69e6c007 100644 --- a/src/form/src/Form.tsx +++ b/src/form/src/Form.tsx @@ -124,6 +124,7 @@ export default defineComponent({ }) } if (formInvalid) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(errors.length ? errors : undefined) } else { resolve({ diff --git a/src/form/src/FormItem.tsx b/src/form/src/FormItem.tsx index c2f7d5b86..52823f084 100644 --- a/src/form/src/FormItem.tsx +++ b/src/form/src/FormItem.tsx @@ -8,6 +8,7 @@ import { type ExtractPropTypes, ref, provide, + type Slot, inject, watch, Transition, @@ -78,6 +79,8 @@ export const formItemProps = { ignorePathChange: Boolean, validationStatus: String as PropType<'error' | 'warning' | 'success'>, feedback: String, + feedbackClass: String, + feedbackStyle: [String, Object] as PropType, showLabel: { type: Boolean as PropType, default: undefined @@ -255,6 +258,7 @@ export default defineComponent({ if (validateCallback) { validateCallback(errors, { warnings }) } + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(errors) } }) @@ -604,64 +608,71 @@ export default defineComponent({ {this.mergedShowFeedback ? (