diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9731081bae5..2a84eff4d37 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -29,7 +29,7 @@ body: id: reproduction attributes: label: Link to minimal reproduction - description: Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example). Provide a streamlined CodePen/CodeSandbox or GitHub repository link. Please don't fill in a link randomly. + description: Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example). Provide a streamlined [Playground](https://play.pro-components.cn)/CodePen/CodeSandbox or GitHub repository link. Please don't fill in a link randomly. placeholder: Reproduction validations: required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.zh-CN.yml b/.github/ISSUE_TEMPLATE/bug_report.zh-CN.yml index d045d91100f..0754bfae7d4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.zh-CN.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.zh-CN.yml @@ -29,7 +29,7 @@ body: id: reproduction attributes: label: 最小复现链接 - description: 请提供[最小复现](https://stackoverflow.com/help/minimal-reproducible-example)链接。提供一个简化的 CodePen/CodeSandbox 或 GitHub 仓库链接。请不要随机填写链接。 + description: 请提供[最小复现](https://stackoverflow.com/help/minimal-reproducible-example)链接。提供一个简化的 [Playground](https://play.pro-components.cn)/CodePen/CodeSandbox 或 GitHub 仓库链接。请不要随机填写链接。 placeholder: 复现链接 validations: required: true diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 97dca1eb766..102a0ec5790 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - node-version: [18, 20] + node-version: [22] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v3 @@ -42,7 +42,7 @@ jobs: strategy: matrix: - node-version: [18, 20] + node-version: [22] steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index d3a3f62729d..742f704ca8d 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -2,10 +2,35 @@ ## NEXT_VERSION +### Breaking Changes + +- (**Vue 3.3+ required**) Add slot type for all components. + ### i18n - Add kmKH locale. +### Features + +- `n-modal` adds `draggable` prop, closes [#6525](https://github.com/tusen-ai/naive-ui/issues/6525), [#5792](https://github.com/tusen-ai/naive-ui/issues/5792), [#5711](https://github.com/tusen-ai/naive-ui/issues/5711), [#5501](https://github.com/tusen-ai/naive-ui/issues/5501) and [#2152](https://github.com/tusen-ai/naive-ui/issues/2152). +- `useDialog` supports `draggable` option. +- `useModal` supports `draggable` option. + +### Fixes + +- Fix `n-data-table` may have multiple expand trigger with tree data. +- Fix `n-date-picker`'s `confirm`, `now`, `clear` slots doesn't work with `'month'`, `'monthrange'`, `'quarter'`, `'quarterrange'`, `'year'` and `'yearrange'` type. +- Fix `n-input`'s `render-count` prop doesn't work when type is not `'textarea'`. + +## 2.40.4 + +`2024-12-20` + +### Fixes + +- Fix `inset` CSS property caused compatibility issues in some browsers, closes [#6604](https://github.com/tusen-ai/naive-ui/issues/6604),close [#6602](https://github.com/tusen-ai/naive-ui/issues/6602). +- Fix memory leak problem when used with new version of vue. Note: After the fix you may still find memory leak in Chrome >= 129, since they introduced a bug, see https://github.com/vuejs/core/issues/12306, https://issues.chromium.org/issues/376777343 + ## 2.40.3 `2024-12-02` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 29c29ab5196..2740c468397 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -2,10 +2,35 @@ ## NEXT_VERSION +### Breaking Changes + +- (需要 Vue 3.3+)为所有的组件增加插槽的类型标注 + ### i18n - 添加 kmKH 国际化 +### Features + +- `n-modal` 新增 `draggable` 属性,关闭 [#6525](https://github.com/tusen-ai/naive-ui/issues/6525),[#5792](https://github.com/tusen-ai/naive-ui/issues/5792),[#5711](https://github.com/tusen-ai/naive-ui/issues/5711),[#5501](https://github.com/tusen-ai/naive-ui/issues/5501),[#2152](https://github.com/tusen-ai/naive-ui/issues/2152) +- `useDialog` 支持 `draggable` 参数 +- `useModal` 支持 `draggable` 参数 + +### Fixes + +- 修复 `n-data-table` 在使用树形数据的时候出现多个展开 icon +- 修复 `n-date-picker` 的 `confirm`、`now`、`clear` 插槽对 `'month'`、`'monthrange'`、`'quarter'`、`'quarterrange'`、`'year'` 和 `'yearrange'` 类型不生效 +- 修复 `n-input` 的 `render-count` 属性在类型非 `'textarea'` 时不生效 + +## 2.40.4 + +`2024-12-20` + +### Fixes + +- 修复 `n-scrollbar`、`n-float-button`、`n-float-button-group`、`n-popover` 组件中的 `inset` 属性在部分浏览器中有兼容性问题,关闭 [#6604](https://github.com/tusen-ai/naive-ui/issues/6604),关闭 [#6602](https://github.com/tusen-ai/naive-ui/issues/6602) +- 修复和较新版本 vue 配合使用时的内存泄露问题。注意:修复后你可能仍然会在 Chrome >= 129 中发现内存泄漏,因为他们引入了一个 bug,参考:https://github.com/vuejs/core/issues/12306 https://issues.chromium.org/issues/376777343 + ## 2.40.3 `2024-12-02` diff --git a/package.json b/package.json index b12d50542c5..82a4066a248 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "naive-ui", - "version": "2.40.3", + "version": "2.40.4", "packageManager": "pnpm@9.5.0", "description": "A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast", "author": "07akioni", @@ -101,9 +101,9 @@ "@rollup/plugin-terser": "^0.4.3", "@types/estree": "^1.0.1", "@types/jest": "^29.5.4", - "@vicons/fluent": "^0.12.0", - "@vicons/ionicons4": "^0.12.0", - "@vicons/ionicons5": "^0.12.0", + "@vicons/fluent": "^0.13.0", + "@vicons/ionicons4": "^0.13.0", + "@vicons/ionicons5": "^0.13.0", "@vitejs/plugin-vue": "^5.0.3", "@vue/compiler-sfc": "^3.4.15", "@vue/server-renderer": "^3.5.13", diff --git a/scripts/utils/loader.js b/scripts/utils/loader.js index 27f4d21ad91..668e55d4895 100644 --- a/scripts/utils/loader.js +++ b/scripts/utils/loader.js @@ -48,8 +48,8 @@ function getPartsOfMdDemo(tokens) { function createBlockTemplate(tag, content, attrs) { const attrsStr = attrs ? Object.keys(attrs).reduce((attrsStr, key) => { - return `${attrsStr} ${key}="${attrs[key]}"` - }, '') + return `${attrsStr} ${key}="${attrs[key]}"` + }, '') : '' return `<${tag}${attrsStr}> ${content} diff --git a/src/_internal/icons/Attach.tsx b/src/_internal/icons/Attach.tsx index 991cff956fb..f8a73584071 100644 --- a/src/_internal/icons/Attach.tsx +++ b/src/_internal/icons/Attach.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'attach', +export default replaceable('attach', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Cancel.tsx b/src/_internal/icons/Cancel.tsx index f93c15c9fba..15b27a69c36 100644 --- a/src/_internal/icons/Cancel.tsx +++ b/src/_internal/icons/Cancel.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'cancel', +export default replaceable('cancel', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Clear.tsx b/src/_internal/icons/Clear.tsx index b3b86232406..8e1e88897e6 100644 --- a/src/_internal/icons/Clear.tsx +++ b/src/_internal/icons/Clear.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'clear', +export default replaceable('clear', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Close.tsx b/src/_internal/icons/Close.tsx index f1c418222cc..b605e01eabf 100644 --- a/src/_internal/icons/Close.tsx +++ b/src/_internal/icons/Close.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'close', +export default replaceable('close', () => ( -) +)) diff --git a/src/_internal/icons/Date.tsx b/src/_internal/icons/Date.tsx index 6f930cd15f8..a475960a962 100644 --- a/src/_internal/icons/Date.tsx +++ b/src/_internal/icons/Date.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'date', +export default replaceable('date', () => ( -) +)) diff --git a/src/_internal/icons/Download.tsx b/src/_internal/icons/Download.tsx index c2ba5f7c4e6..a1926ce6a75 100644 --- a/src/_internal/icons/Download.tsx +++ b/src/_internal/icons/Download.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'download', +export default replaceable('download', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Error.tsx b/src/_internal/icons/Error.tsx index b3af34e1edd..390b44f65f6 100644 --- a/src/_internal/icons/Error.tsx +++ b/src/_internal/icons/Error.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'error', +export default replaceable('error', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Info.tsx b/src/_internal/icons/Info.tsx index 42e098d79ac..104e82a3021 100644 --- a/src/_internal/icons/Info.tsx +++ b/src/_internal/icons/Info.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'info', +export default replaceable('info', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Retry.tsx b/src/_internal/icons/Retry.tsx index f272847ecd8..7bc2e29faef 100644 --- a/src/_internal/icons/Retry.tsx +++ b/src/_internal/icons/Retry.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'retry', +export default replaceable('retry', () => ( -) +)) diff --git a/src/_internal/icons/RotateClockwise.tsx b/src/_internal/icons/RotateClockwise.tsx index 82d7decc12f..fcc1f83dbff 100644 --- a/src/_internal/icons/RotateClockwise.tsx +++ b/src/_internal/icons/RotateClockwise.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'rotateClockwise', +export default replaceable('rotateClockwise', () => ( -) +)) diff --git a/src/_internal/icons/RotateCounterclockwise.tsx b/src/_internal/icons/RotateCounterclockwise.tsx index 9bae8b0f904..68d44e60d3d 100644 --- a/src/_internal/icons/RotateCounterclockwise.tsx +++ b/src/_internal/icons/RotateCounterclockwise.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'rotateClockwise', +export default replaceable('rotateClockwise', () => ( -) +)) diff --git a/src/_internal/icons/Success.tsx b/src/_internal/icons/Success.tsx index 9798473af31..2a6da757d01 100644 --- a/src/_internal/icons/Success.tsx +++ b/src/_internal/icons/Success.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'success', +export default replaceable('success', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Time.tsx b/src/_internal/icons/Time.tsx index e3f40debee4..1d4a91a05a6 100644 --- a/src/_internal/icons/Time.tsx +++ b/src/_internal/icons/Time.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'time', +export default replaceable('time', () => ( -) +)) diff --git a/src/_internal/icons/To.tsx b/src/_internal/icons/To.tsx index da531afcb78..06d9996581a 100644 --- a/src/_internal/icons/To.tsx +++ b/src/_internal/icons/To.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'to', +export default replaceable('to', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/Trash.tsx b/src/_internal/icons/Trash.tsx index 9a803494994..bf92702daf2 100644 --- a/src/_internal/icons/Trash.tsx +++ b/src/_internal/icons/Trash.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'trash', +export default replaceable('trash', () => ( -) +)) diff --git a/src/_internal/icons/Warning.tsx b/src/_internal/icons/Warning.tsx index 9579fc33503..e921cdaef1f 100644 --- a/src/_internal/icons/Warning.tsx +++ b/src/_internal/icons/Warning.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'warning', +export default replaceable('warning', () => ( @@ -10,4 +9,4 @@ export default replaceable( -) +)) diff --git a/src/_internal/icons/ZoomIn.tsx b/src/_internal/icons/ZoomIn.tsx index 574003d0c46..02310e12aa7 100644 --- a/src/_internal/icons/ZoomIn.tsx +++ b/src/_internal/icons/ZoomIn.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'zoomIn', +export default replaceable('zoomIn', () => ( -) +)) diff --git a/src/_internal/icons/ZoomOut.tsx b/src/_internal/icons/ZoomOut.tsx index b56592b2279..14d1ccf21f7 100644 --- a/src/_internal/icons/ZoomOut.tsx +++ b/src/_internal/icons/ZoomOut.tsx @@ -1,8 +1,7 @@ import { h } from 'vue' import { replaceable } from './replaceable' -export default replaceable( - 'zoomOut', +export default replaceable('zoomOut', () => ( -) +)) diff --git a/src/_internal/icons/replaceable.tsx b/src/_internal/icons/replaceable.tsx index 377b3aa122d..7fdf7ce2fe2 100644 --- a/src/_internal/icons/replaceable.tsx +++ b/src/_internal/icons/replaceable.tsx @@ -1,9 +1,15 @@ +import type { VNode } from 'vue' import type { GlobalIconConfig } from '../../config-provider/src/internal-interface' import { upperFirst } from 'lodash-es' -import { defineComponent, inject } from 'vue' +import { defineComponent, h, inject } from 'vue' import { configProviderInjectionKey } from '../../config-provider/src/context' -export function replaceable(name: keyof GlobalIconConfig, icon: JSX.Element) { +export function replaceable(name: keyof GlobalIconConfig, icon: () => VNode) { + const IconComponent = defineComponent({ + render() { + return icon() + } + }) return defineComponent({ name: upperFirst(name), setup() { @@ -13,7 +19,7 @@ export function replaceable(name: keyof GlobalIconConfig, icon: JSX.Element) { )?.mergedIconsRef return () => { const iconOverride = mergedIconsRef?.value?.[name] - return iconOverride ? iconOverride() : icon + return iconOverride ? iconOverride() : } } }) diff --git a/src/_internal/scrollbar/src/Scrollbar.tsx b/src/_internal/scrollbar/src/Scrollbar.tsx index ef2b6f8cdbb..86e5fa00015 100644 --- a/src/_internal/scrollbar/src/Scrollbar.tsx +++ b/src/_internal/scrollbar/src/Scrollbar.tsx @@ -6,7 +6,7 @@ import type { } from '../../../_utils' import type { ScrollbarTheme } from '../styles' import { off, on } from 'evtd' -import { depx, getPreciseEventTarget } from 'seemly' +import { depx, getPadding, getPreciseEventTarget } from 'seemly' import { useIsIos } from 'vooks' import { computed, @@ -701,6 +701,43 @@ const Scrollbar = defineComponent({ railColor } } = themeRef.value + + const { + top: railTopHorizontalTop, + right: railRightHorizontalTop, + bottom: railBottomHorizontalTop, + left: railLeftHorizontalTop + } = getPadding(railInsetHorizontalTop) + + const { + top: railTopHorizontalBottom, + right: railRightHorizontalBottom, + bottom: railBottomHorizontalBottom, + left: railLeftHorizontalBottom + } = getPadding(railInsetHorizontalBottom) + + const { + top: railTopVerticalRight, + right: railRightVerticalRight, + bottom: railBottomVerticalRight, + left: railLeftVerticalRight + } = getPadding( + rtlEnabledRef?.value + ? rtlInset(railInsetVerticalRight) + : railInsetVerticalRight + ) + + const { + top: railTopVerticalLeft, + right: railRightVerticalLeft, + bottom: railBottomVerticalLeft, + left: railLeftVerticalLeft + } = getPadding( + rtlEnabledRef?.value + ? rtlInset(railInsetVerticalLeft) + : railInsetVerticalLeft + ) + return { '--n-scrollbar-bezier': cubicBezierEaseInOut, '--n-scrollbar-color': color, @@ -708,14 +745,23 @@ const Scrollbar = defineComponent({ '--n-scrollbar-border-radius': borderRadius, '--n-scrollbar-width': width, '--n-scrollbar-height': height, - '--n-scrollbar-rail-inset-horizontal-top': railInsetHorizontalTop, - '--n-scrollbar-rail-inset-horizontal-bottom': railInsetHorizontalBottom, - '--n-scrollbar-rail-inset-vertical-right': rtlEnabledRef?.value - ? rtlInset(railInsetVerticalRight) - : railInsetVerticalRight, - '--n-scrollbar-rail-inset-vertical-left': rtlEnabledRef?.value - ? rtlInset(railInsetVerticalLeft) - : railInsetVerticalLeft, + '--n-scrollbar-rail-top-horizontal-top': railTopHorizontalTop, + '--n-scrollbar-rail-right-horizontal-top': railRightHorizontalTop, + '--n-scrollbar-rail-bottom-horizontal-top': railBottomHorizontalTop, + '--n-scrollbar-rail-left-horizontal-top': railLeftHorizontalTop, + '--n-scrollbar-rail-top-horizontal-bottom': railTopHorizontalBottom, + '--n-scrollbar-rail-right-horizontal-bottom': railRightHorizontalBottom, + '--n-scrollbar-rail-bottom-horizontal-bottom': + railBottomHorizontalBottom, + '--n-scrollbar-rail-left-horizontal-bottom': railLeftHorizontalBottom, + '--n-scrollbar-rail-top-vertical-right': railTopVerticalRight, + '--n-scrollbar-rail-right-vertical-right': railRightVerticalRight, + '--n-scrollbar-rail-bottom-vertical-right': railBottomVerticalRight, + '--n-scrollbar-rail-left-vertical-right': railLeftVerticalRight, + '--n-scrollbar-rail-top-vertical-left': railTopVerticalLeft, + '--n-scrollbar-rail-right-vertical-left': railRightVerticalLeft, + '--n-scrollbar-rail-bottom-vertical-left': railBottomVerticalLeft, + '--n-scrollbar-rail-left-vertical-left': railLeftVerticalLeft, '--n-scrollbar-rail-color': railColor } }) diff --git a/src/_internal/scrollbar/src/styles/index.cssr.ts b/src/_internal/scrollbar/src/styles/index.cssr.ts index 1c851ac3643..eb6dff7fac6 100644 --- a/src/_internal/scrollbar/src/styles/index.cssr.ts +++ b/src/_internal/scrollbar/src/styles/index.cssr.ts @@ -61,10 +61,16 @@ export default cB('scrollbar', ` ]) ]), cM('horizontal--top', ` - inset: var(--n-scrollbar-rail-inset-horizontal-top); + top: var(--n-scrollbar-rail-top-horizontal-top); + right: var(--n-scrollbar-rail-right-horizontal-top); + bottom: var(--n-scrollbar-rail-bottom-horizontal-top); + left: var(--n-scrollbar-rail-left-horizontal-top); `), cM('horizontal--bottom', ` - inset: var(--n-scrollbar-rail-inset-horizontal-bottom); + top: var(--n-scrollbar-rail-top-horizontal-bottom); + right: var(--n-scrollbar-rail-right-horizontal-bottom); + bottom: var(--n-scrollbar-rail-bottom-horizontal-bottom); + left: var(--n-scrollbar-rail-left-horizontal-bottom); `), cM('vertical', ` width: var(--n-scrollbar-width); @@ -78,10 +84,16 @@ export default cB('scrollbar', ` ]) ]), cM('vertical--left', ` - inset: var(--n-scrollbar-rail-inset-vertical-left); + top: var(--n-scrollbar-rail-top-vertical-left); + right: var(--n-scrollbar-rail-right-vertical-left); + bottom: var(--n-scrollbar-rail-bottom-vertical-left); + left: var(--n-scrollbar-rail-left-vertical-left); `), cM('vertical--right', ` - inset: var(--n-scrollbar-rail-inset-vertical-right); + top: var(--n-scrollbar-rail-top-vertical-right); + right: var(--n-scrollbar-rail-right-vertical-right); + bottom: var(--n-scrollbar-rail-bottom-vertical-right); + left: var(--n-scrollbar-rail-left-vertical-right); `), cM('disabled', [ c('>', [ diff --git a/src/_internal/select-menu/src/SelectMenu.tsx b/src/_internal/select-menu/src/SelectMenu.tsx index 232683fb279..eab3e46b7b1 100644 --- a/src/_internal/select-menu/src/SelectMenu.tsx +++ b/src/_internal/select-menu/src/SelectMenu.tsx @@ -411,11 +411,11 @@ export default defineComponent({ const { inlineThemeDisabled } = props const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'internal-select-menu', - computed(() => props.size[0]), - cssVarsRef, - props - ) + 'internal-select-menu', + computed(() => props.size[0]), + cssVarsRef, + props + ) : undefined const exposedProps: InternalExposedProps = { selfRef, diff --git a/src/_internal/selection/src/Selection.tsx b/src/_internal/selection/src/Selection.tsx index c3868ab6725..600f678cb5f 100644 --- a/src/_internal/selection/src/Selection.tsx +++ b/src/_internal/selection/src/Selection.tsx @@ -152,16 +152,16 @@ export default defineComponent({ return props.selectedOption ? props.renderTag ? props.renderTag({ - option: props.selectedOption, - handleClose: () => {} - }) + option: props.selectedOption, + handleClose: () => {} + }) : props.renderLabel ? props.renderLabel(props.selectedOption as never, true) : render( - props.selectedOption[props.labelField], - props.selectedOption, - true - ) + props.selectedOption[props.labelField], + props.selectedOption, + true + ) : props.placeholder }) const labelRef = computed(() => { @@ -541,13 +541,13 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'internal-selection', - computed(() => { - return props.size[0] - }), - cssVarsRef, - props - ) + 'internal-selection', + computed(() => { + return props.size[0] + }), + cssVarsRef, + props + ) : undefined return { mergedTheme: themeRef, @@ -903,9 +903,9 @@ export default defineComponent({
{renderTag ? renderTag({ - option: this.selectedOption!, - handleClose: () => {} - }) + option: this.selectedOption!, + handleClose: () => {} + }) : renderLabel ? renderLabel(this.selectedOption!, true) : render(this.label, this.selectedOption, true)} @@ -942,9 +942,9 @@ export default defineComponent({
{renderTag ? renderTag({ - option: this.selectedOption!, - handleClose: () => {} - }) + option: this.selectedOption!, + handleClose: () => {} + }) : renderLabel ? renderLabel(this.selectedOption!, true) : render(this.label, this.selectedOption, true)} diff --git a/src/_utils/composable/use-resize.ts b/src/_utils/composable/use-resize.ts index fad8fb8cea5..525a8682d14 100644 --- a/src/_utils/composable/use-resize.ts +++ b/src/_utils/composable/use-resize.ts @@ -1,4 +1,4 @@ -import { onBeforeUnmount, onMounted, type Ref } from 'vue' +import { onBeforeUnmount, onMounted, type Ref, watch } from 'vue' import { resizeObserverManager } from 'vueuc' export function useOnResize( @@ -13,6 +13,20 @@ export function useOnResize( resizeObserverManager.registerHandler(el, onResize) } }) + + // avoid memory leak + watch( + elRef, + (_, oldEl) => { + if (oldEl) { + resizeObserverManager.unregisterHandler(oldEl) + } + }, + { + deep: false + } + ) + onBeforeUnmount(() => { const { value: el } = elRef if (el) { diff --git a/src/_utils/css/rtl-inset.ts b/src/_utils/css/rtl-inset.ts index 17039e50ad0..4e9e3aeae5b 100644 --- a/src/_utils/css/rtl-inset.ts +++ b/src/_utils/css/rtl-inset.ts @@ -2,5 +2,5 @@ import { getPadding } from 'seemly' export function rtlInset(inset: string): string { const { left, right, top, bottom } = getPadding(inset) - return `${top} ${right} ${bottom} ${left}` + return `${top} ${left} ${bottom} ${right}` } diff --git a/src/_utils/cssr/index.ts b/src/_utils/cssr/index.ts index ac88c99a803..7c4782870bc 100644 --- a/src/_utils/cssr/index.ts +++ b/src/_utils/cssr/index.ts @@ -45,7 +45,7 @@ function createKey

( + (suffix === 'default' ? '' : suffix.replace(/^[a-z]/, startChar => - startChar.toUpperCase()))) as any + startChar.toUpperCase()))) as any } export { diff --git a/src/_utils/index.ts b/src/_utils/index.ts index ebebd3f4752..ad254de812a 100644 --- a/src/_utils/index.ts +++ b/src/_utils/index.ts @@ -25,6 +25,7 @@ export { createRefSetter, flatten, getFirstSlotVNode, + getFirstSlotVNodeWithTypedProps, getSlot, getVNodeChildren, isNodeVShowFalse, @@ -35,7 +36,7 @@ export { omit, render, resolveSlot, - resolveSlotWithProps, + resolveSlotWithTypedProps, resolveWrappedSlot, resolveWrappedSlotWithProps, Wrapper diff --git a/src/_utils/vue/get-first-slot-vnode.ts b/src/_utils/vue/get-first-slot-vnode.ts index b1facd0b748..b9616d8cebb 100644 --- a/src/_utils/vue/get-first-slot-vnode.ts +++ b/src/_utils/vue/get-first-slot-vnode.ts @@ -22,3 +22,22 @@ export function getFirstSlotVNode( return null } } + +export function getFirstSlotVNodeWithTypedProps( + slotName: string, + slot: ((props: T) => VNode[]) | undefined, + props: T +): VNode | null { + if (!slot) { + return null + } + const slotContent = flatten(slot(props)) + // vue will normalize the slot, so slot must be an array + if (slotContent.length === 1) { + return slotContent[0] + } + else { + warn('getFirstSlotVNode', `slot[${slotName}] should have exactly one child`) + return null + } +} diff --git a/src/_utils/vue/index.ts b/src/_utils/vue/index.ts index bc7c3ffd6e4..96b2d921b5d 100644 --- a/src/_utils/vue/index.ts +++ b/src/_utils/vue/index.ts @@ -4,7 +4,10 @@ export { createDataKey } from './create-data-key' export { createInjectionKey } from './create-injection-key' export { createRefSetter } from './create-ref-setter' export { flatten } from './flatten' -export { getFirstSlotVNode } from './get-first-slot-vnode' +export { + getFirstSlotVNode, + getFirstSlotVNodeWithTypedProps +} from './get-first-slot-vnode' export { getSlot } from './get-slot' export { getVNodeChildren } from './get-v-node-children' export { isNodeVShowFalse } from './is-node-v-show-false' @@ -16,7 +19,7 @@ export { render } from './render' export { isSlotEmpty, resolveSlot, - resolveSlotWithProps, + resolveSlotWithTypedProps, resolveWrappedSlot, resolveWrappedSlotWithProps } from './resolve-slot' diff --git a/src/_utils/vue/resolve-slot.ts b/src/_utils/vue/resolve-slot.ts index 3ca855afea9..bbb97096d85 100644 --- a/src/_utils/vue/resolve-slot.ts +++ b/src/_utils/vue/resolve-slot.ts @@ -39,8 +39,8 @@ export function resolveSlot( return (slot && ensureValidVNode(slot())) || fallback() } -export function resolveSlotWithProps( - slot: Slot | undefined, +export function resolveSlotWithTypedProps( + slot: Slot | undefined, props: T, fallback: (props: T) => VNodeArrayChildren ): VNodeArrayChildren { diff --git a/src/alert/index.ts b/src/alert/index.ts index 0391272d7b5..d3ccb42e19c 100644 --- a/src/alert/index.ts +++ b/src/alert/index.ts @@ -1,2 +1,2 @@ export { alertProps, default as NAlert } from './src/Alert' -export type { AlertProps } from './src/Alert' +export type { AlertProps, AlertSlots } from './src/Alert' diff --git a/src/alert/src/Alert.tsx b/src/alert/src/Alert.tsx index fea59045ac9..019c1fbbf23 100644 --- a/src/alert/src/Alert.tsx +++ b/src/alert/src/Alert.tsx @@ -10,6 +10,8 @@ import { mergeProps, type PropType, ref, + type SlotsType, + type VNode, watchEffect } from 'vue' import { NBaseClose, NBaseIcon, NFadeInExpandTransition } from '../../_internal' @@ -56,10 +58,17 @@ export const alertProps = { export type AlertProps = ExtractPublicPropTypes +export interface AlertSlots { + default?: () => VNode[] + icon?: () => VNode[] + header?: () => VNode[] +} + export default defineComponent({ name: 'Alert', inheritAttrs: false, props: alertProps, + slots: Object as SlotsType, setup(props) { if (__DEV__) { watchEffect(() => { @@ -141,13 +150,13 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'alert', - computed(() => { - return props.type[0] - }), - cssVarsRef, - props - ) + 'alert', + computed(() => { + return props.type[0] + }), + cssVarsRef, + props + ) : undefined const visibleRef = ref(true) const doAfterLeave = (): void => { diff --git a/src/auto-complete/index.ts b/src/auto-complete/index.ts index ae80014a922..9b89b382c75 100644 --- a/src/auto-complete/index.ts +++ b/src/auto-complete/index.ts @@ -1,6 +1,7 @@ export { autoCompleteProps, default as NAutoComplete } from './src/AutoComplete' -export type { AutoCompleteProps } from './src/AutoComplete' +export type { AutoCompleteProps, AutoCompleteSlots } from './src/AutoComplete' export type { + AutoCompleteDefaultSlotProps, AutoCompleteGroupOption, AutoCompleteInst, AutoCompleteOption diff --git a/src/auto-complete/src/AutoComplete.tsx b/src/auto-complete/src/AutoComplete.tsx index 506ca0b029b..c0ce1e22525 100644 --- a/src/auto-complete/src/AutoComplete.tsx +++ b/src/auto-complete/src/AutoComplete.tsx @@ -12,6 +12,7 @@ import type { } from '../../select/src/interface' import type { AutoCompleteTheme } from '../styles' import type { + AutoCompleteDefaultSlotProps, AutoCompleteInst, AutoCompleteOption, AutoCompleteOptions, @@ -33,8 +34,10 @@ import { type InputHTMLAttributes, type PropType, ref, + type SlotsType, toRef, Transition, + type VNode, watchEffect, withDirectives } from 'vue' @@ -47,7 +50,7 @@ import { useConfig, useFormItem, useTheme, useThemeClass } from '../../_mixins' import { call, type ExtractPublicPropTypes, - getFirstSlotVNode, + getFirstSlotVNodeWithTypedProps, type MaybeArray, useAdjustedTo, warnOnce @@ -114,9 +117,17 @@ export const autoCompleteProps = { export type AutoCompleteProps = ExtractPublicPropTypes +export interface AutoCompleteSlots { + default?: (options: AutoCompleteDefaultSlotProps) => VNode[] + empty?: () => VNode[] + prefix?: () => VNode[] + suffix?: () => VNode[] +} + export default defineComponent({ name: 'AutoComplete', props: autoCompleteProps, + slots: Object as SlotsType, setup(props) { if (__DEV__) { watchEffect(() => { @@ -363,12 +374,16 @@ export default defineComponent({ default: () => { const defaultSlot = this.$slots.default if (defaultSlot) { - return getFirstSlotVNode(this.$slots, 'default', { - handleInput: this.handleInput, - handleFocus: this.handleFocus, - handleBlur: this.handleBlur, - value: this.mergedValue - }) + return getFirstSlotVNodeWithTypedProps( + 'default', + defaultSlot, + { + handleInput: this.handleInput, + handleFocus: this.handleFocus, + handleBlur: this.handleBlur, + value: this.mergedValue + } + ) } const { mergedTheme } = this return ( diff --git a/src/auto-complete/src/interface.ts b/src/auto-complete/src/interface.ts index 0795ebc2f20..bed1b7443f6 100644 --- a/src/auto-complete/src/interface.ts +++ b/src/auto-complete/src/interface.ts @@ -22,3 +22,10 @@ export interface AutoCompleteInst { focus: () => void blur: () => void } + +export interface AutoCompleteDefaultSlotProps { + handleInput: (value: string) => void + handleFocus: (e: FocusEvent) => void + handleBlur: (e: FocusEvent) => void + value: string | null +} diff --git a/src/avatar-group/index.ts b/src/avatar-group/index.ts index 5f5357f709f..6c0be482478 100644 --- a/src/avatar-group/index.ts +++ b/src/avatar-group/index.ts @@ -1,2 +1,10 @@ export { avatarGroupProps, default as NAvatarGroup } from './src/AvatarGroup' -export type { AvatarGroupOption, AvatarGroupProps } from './src/AvatarGroup' +export type { + AvatarGroupOption, + AvatarGroupProps, + AvatarGroupSlots +} from './src/AvatarGroup' +export type { + AvatarGroupAvatarSlotProps, + AvatarGroupRestSlotProps +} from './src/interface' diff --git a/src/avatar-group/src/AvatarGroup.tsx b/src/avatar-group/src/AvatarGroup.tsx index 898e41ab81e..ea8519898de 100644 --- a/src/avatar-group/src/AvatarGroup.tsx +++ b/src/avatar-group/src/AvatarGroup.tsx @@ -2,13 +2,19 @@ import type { ThemeProps } from '../../_mixins' import type { ExtractPublicPropTypes } from '../../_utils' import type { Size } from '../../avatar/src/interface' import type { AvatarGroupTheme } from '../styles' +import type { + AvatarGroupAvatarSlotProps, + AvatarGroupRestSlotProps +} from './interface' import { computed, type CSSProperties, defineComponent, h, type PropType, - provide + provide, + type SlotsType, + type VNode } from 'vue' import { useConfig, useTheme } from '../../_mixins' import { useRtl } from '../../_mixins/use-rtl' @@ -40,9 +46,16 @@ export const avatarGroupProps = { export type AvatarGroupProps = ExtractPublicPropTypes +export interface AvatarGroupSlots { + avatar?: (props: AvatarGroupAvatarSlotProps) => VNode[] + rest?: (props: AvatarGroupRestSlotProps) => VNode[] + default?: () => VNode[] +} + export default defineComponent({ name: 'AvatarGroup', props: avatarGroupProps, + slots: Object as SlotsType, setup(props) { const { mergedClsPrefixRef, mergedRtlRef } = useConfig(props) const mergedThemeRef = useTheme( diff --git a/src/avatar-group/src/interface.ts b/src/avatar-group/src/interface.ts new file mode 100644 index 00000000000..825f5604058 --- /dev/null +++ b/src/avatar-group/src/interface.ts @@ -0,0 +1,10 @@ +import type { AvatarGroupOption } from './AvatarGroup' + +export interface AvatarGroupAvatarSlotProps { + option: AvatarGroupOption +} + +export interface AvatarGroupRestSlotProps { + options: Array + rest: number +} diff --git a/src/avatar/index.ts b/src/avatar/index.ts index a57511cdd5a..821f86b69fe 100644 --- a/src/avatar/index.ts +++ b/src/avatar/index.ts @@ -1,2 +1,2 @@ export { avatarProps, default as NAvatar } from './src/Avatar' -export type { AvatarProps } from './src/Avatar' +export type { AvatarProps, AvatarSlots } from './src/Avatar' diff --git a/src/avatar/src/Avatar.tsx b/src/avatar/src/Avatar.tsx index 43d28a85c88..84473833e02 100644 --- a/src/avatar/src/Avatar.tsx +++ b/src/avatar/src/Avatar.tsx @@ -12,6 +12,8 @@ import { onMounted, type PropType, ref, + type SlotsType, + type VNode, type VNodeChild, watch, watchEffect @@ -65,9 +67,16 @@ export const avatarProps = { export type AvatarProps = ExtractPublicPropTypes +export interface AvatarSlots { + default?: () => VNode[] + placeholder?: () => VNode[] + fallback?: () => VNode[] +} + export default defineComponent({ name: 'Avatar', props: avatarProps, + slots: Object as SlotsType, setup(props) { const { mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props) const hasLoadErrorRef = ref(false) @@ -165,35 +174,35 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'avatar', - computed(() => { - const size = mergedSizeRef.value - const round = mergedRoundRef.value - const bordered = mergedBorderedRef.value - const { color } = props - let hash = '' - if (size) { - if (typeof size === 'number') { - hash += `a${size}` + 'avatar', + computed(() => { + const size = mergedSizeRef.value + const round = mergedRoundRef.value + const bordered = mergedBorderedRef.value + const { color } = props + let hash = '' + if (size) { + if (typeof size === 'number') { + hash += `a${size}` + } + else { + hash += size[0] + } } - else { - hash += size[0] + if (round) { + hash += 'b' } - } - if (round) { - hash += 'b' - } - if (bordered) { - hash += 'c' - } - if (color) { - hash += color2Class(color) - } - return hash - }), - cssVarsRef, - props - ) + if (bordered) { + hash += 'c' + } + if (color) { + hash += color2Class(color) + } + return hash + }), + cssVarsRef, + props + ) : undefined const shouldStartLoadingRef = ref(!props.lazy) @@ -281,8 +290,8 @@ export default defineComponent({ img = this.renderFallback ? this.renderFallback() : resolveSlot($slots.fallback, () => [ - - ]) + + ]) } else { img = resolveWrappedSlot($slots.default, (children) => { diff --git a/src/back-top/src/BackTop.tsx b/src/back-top/src/BackTop.tsx index 0afff47b5b9..225d28bbeda 100644 --- a/src/back-top/src/BackTop.tsx +++ b/src/back-top/src/BackTop.tsx @@ -30,7 +30,7 @@ import { warnOnce } from '../../_utils' import { backTopLight } from '../styles' -import BackTopIcon from './BackTopIcon' +import renderBackTopIcon from './BackTopIcon' import style from './styles/index.cssr' export const backTopProps = { @@ -288,23 +288,23 @@ export default defineComponent({ this.onRender?.() return this.mergedShow ? h( - 'div', - mergeProps(this.$attrs, { - class: [ - `${mergedClsPrefix}-back-top`, - this.themeClass, - this.transitionDisabled - && `${mergedClsPrefix}-back-top--transition-disabled` - ], - style: [this.style, this.cssVars], - onClick: this.handleClick - }), - resolveSlot(this.$slots.default, () => [ - - {{ default: () => BackTopIcon }} - - ]) - ) + 'div', + mergeProps(this.$attrs, { + class: [ + `${mergedClsPrefix}-back-top`, + this.themeClass, + this.transitionDisabled + && `${mergedClsPrefix}-back-top--transition-disabled` + ], + style: [this.style, this.cssVars], + onClick: this.handleClick + }), + resolveSlot(this.$slots.default, () => [ + + {{ default: renderBackTopIcon }} + + ]) + ) : null } }} diff --git a/src/back-top/src/BackTopIcon.tsx b/src/back-top/src/BackTopIcon.tsx index e3af0570746..5935d350cb6 100644 --- a/src/back-top/src/BackTopIcon.tsx +++ b/src/back-top/src/BackTopIcon.tsx @@ -1,6 +1,6 @@ import { h } from 'vue' -export default ( +export default () => ( { - let hash = '' - const { type, color } = props - if (type) { - hash += type[0] - } - if (color) { - hash += color2Class(color) - } - return hash - }), - cssVarsRef, - props - ) + 'badge', + computed(() => { + let hash = '' + const { type, color } = props + if (type) { + hash += type[0] + } + if (color) { + hash += color2Class(color) + } + return hash + }), + cssVarsRef, + props + ) : undefined const offsetStyleRef = computed(() => { diff --git a/src/breadcrumb/index.ts b/src/breadcrumb/index.ts index dc228f42973..8e7dc9ade6e 100644 --- a/src/breadcrumb/index.ts +++ b/src/breadcrumb/index.ts @@ -4,4 +4,7 @@ export { breadcrumbItemProps, default as NBreadcrumbItem } from './src/BreadcrumbItem' -export type { BreadcrumbItemProps } from './src/BreadcrumbItem' +export type { + BreadcrumbItemProps, + BreadcrumbItemSlots +} from './src/BreadcrumbItem' diff --git a/src/breadcrumb/src/BreadcrumbItem.tsx b/src/breadcrumb/src/BreadcrumbItem.tsx index 854e3863f70..ee404eb080b 100644 --- a/src/breadcrumb/src/BreadcrumbItem.tsx +++ b/src/breadcrumb/src/BreadcrumbItem.tsx @@ -4,7 +4,9 @@ import { type ExtractPropTypes, h, inject, - type PropType + type PropType, + type SlotsType, + type VNode } from 'vue' import { resolveSlot, warn } from '../../_utils' import { useBrowserLocation } from '../../_utils/composable/use-browser-location' @@ -24,9 +26,15 @@ export type BreadcrumbItemProps = Partial< ExtractPropTypes > +export interface BreadcrumbItemSlots { + default?: () => VNode[] + separator?: () => VNode[] +} + export default defineComponent({ name: 'BreadcrumbItem', props: breadcrumbItemProps, + slots: Object as SlotsType, setup(props, { slots }) { const NBreadcrumb = inject(breadcrumbInjectionKey, null) if (!NBreadcrumb) { diff --git a/src/button/index.ts b/src/button/index.ts index 6985a9ff9f5..0bb775f78b5 100644 --- a/src/button/index.ts +++ b/src/button/index.ts @@ -3,4 +3,4 @@ export { default as NButton, XButton as NxButton } from './src/Button' -export type { ButtonProps } from './src/Button' +export type { ButtonProps, ButtonSlots } from './src/Button' diff --git a/src/button/src/Button.tsx b/src/button/src/Button.tsx index 191c788ea32..464782c66ba 100644 --- a/src/button/src/Button.tsx +++ b/src/button/src/Button.tsx @@ -15,6 +15,8 @@ import { inject, type PropType, ref, + type SlotsType, + type VNode, type VNodeChild, watchEffect } from 'vue' @@ -95,9 +97,15 @@ export const buttonProps = { export type ButtonProps = ExtractPublicPropTypes +export interface ButtonSlots { + default?: () => VNode[] + icon?: () => VNode[] +} + const Button = defineComponent({ name: 'Button', props: buttonProps, + slots: Object as SlotsType, setup(props) { if (__DEV__) { watchEffect(() => { @@ -316,23 +324,23 @@ const Button = defineComponent({ colorProps = { '--n-color': isColoredType ? changeColor(mergedTextColor, { - alpha: Number(self.colorOpacitySecondary) - }) + alpha: Number(self.colorOpacitySecondary) + }) : self.colorSecondary, '--n-color-hover': isColoredType ? changeColor(mergedTextColor, { - alpha: Number(self.colorOpacitySecondaryHover) - }) + alpha: Number(self.colorOpacitySecondaryHover) + }) : self.colorSecondaryHover, '--n-color-pressed': isColoredType ? changeColor(mergedTextColor, { - alpha: Number(self.colorOpacitySecondaryPressed) - }) + alpha: Number(self.colorOpacitySecondaryPressed) + }) : self.colorSecondaryPressed, '--n-color-focus': isColoredType ? changeColor(mergedTextColor, { - alpha: Number(self.colorOpacitySecondaryHover) - }) + alpha: Number(self.colorOpacitySecondaryHover) + }) : self.colorSecondaryHover, '--n-color-disabled': self.colorSecondary, '--n-ripple-color': '#0000', @@ -486,53 +494,53 @@ const Button = defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'button', - computed(() => { - let hash = '' - const { - dashed, - type, - ghost, - text, - color, - round, - circle, - textColor, - secondary, - tertiary, - quaternary, - strong - } = props - if (dashed) - hash += 'a' - if (ghost) - hash += 'b' - if (text) - hash += 'c' - if (round) - hash += 'd' - if (circle) - hash += 'e' - if (secondary) - hash += 'f' - if (tertiary) - hash += 'g' - if (quaternary) - hash += 'h' - if (strong) - hash += 'i' - if (color) - hash += `j${color2Class(color)}` - if (textColor) - hash += `k${color2Class(textColor)}` - const { value: size } = mergedSizeRef - hash += `l${size[0]}` - hash += `m${type[0]}` - return hash - }), - cssVarsRef, - props - ) + 'button', + computed(() => { + let hash = '' + const { + dashed, + type, + ghost, + text, + color, + round, + circle, + textColor, + secondary, + tertiary, + quaternary, + strong + } = props + if (dashed) + hash += 'a' + if (ghost) + hash += 'b' + if (text) + hash += 'c' + if (round) + hash += 'd' + if (circle) + hash += 'e' + if (secondary) + hash += 'f' + if (tertiary) + hash += 'g' + if (quaternary) + hash += 'h' + if (strong) + hash += 'i' + if (color) + hash += `j${color2Class(color)}` + if (textColor) + hash += `k${color2Class(textColor)}` + const { value: size } = mergedSizeRef + hash += `l${size[0]}` + hash += `m${type[0]}` + return hash + }), + cssVarsRef, + props + ) : undefined return { diff --git a/src/button/src/styles/index.cssr.ts b/src/button/src/styles/index.cssr.ts index e06eb2ab3d6..804ab429d4e 100644 --- a/src/button/src/styles/index.cssr.ts +++ b/src/button/src/styles/index.cssr.ts @@ -162,8 +162,8 @@ export default c([ ]), (isBrowser && 'MozBoxSizing' in document.createElement('div').style) ? c('&::moz-focus-inner', { - border: 0 - }) + border: 0 + }) : null, cE('border, state-border', ` position: absolute; diff --git a/src/calendar/src/Calendar.tsx b/src/calendar/src/Calendar.tsx index e4351875f2b..bdede35b0fb 100644 --- a/src/calendar/src/Calendar.tsx +++ b/src/calendar/src/Calendar.tsx @@ -1,7 +1,13 @@ import type { ThemeProps } from '../../_mixins' import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils' import type { CalendarTheme } from '../styles' -import type { DateItem, OnPanelChange, OnUpdateValue } from './interface' +import type { + CalendarDefaultSlotProps, + CalendarHeaderSlotProps, + DateItem, + OnPanelChange, + OnUpdateValue +} from './interface' import { addMonths, format, @@ -19,12 +25,14 @@ import { h, type PropType, ref, - toRef + type SlotsType, + toRef, + type VNode } from 'vue' import { NBaseIcon } from '../../_internal' import { ChevronLeftIcon, ChevronRightIcon } from '../../_internal/icons' import { useConfig, useLocale, useTheme, useThemeClass } from '../../_mixins' -import { call, resolveSlotWithProps } from '../../_utils' +import { call, resolveSlotWithTypedProps } from '../../_utils' import { NButton } from '../../button' import { NButtonGroup } from '../../button-group' import { dateArray } from '../../date-picker/src/utils' @@ -46,9 +54,15 @@ export const calendarProps = { export type CalendarProps = ExtractPublicPropTypes +export interface CalendarSlots { + default?: (props: CalendarDefaultSlotProps) => VNode[] + header?: (props: CalendarHeaderSlotProps) => VNode[] +} + export default defineComponent({ name: 'Calendar', props: calendarProps, + slots: Object as SlotsType, setup(props) { const { mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props) const themeRef = useTheme( @@ -217,7 +231,7 @@ export default defineComponent({ >

- {resolveSlotWithProps( + {resolveSlotWithTypedProps( $slots.header, { year, month: calendarMonth }, () => { diff --git a/src/calendar/src/interface.ts b/src/calendar/src/interface.ts index 864235813d2..38c88c1c554 100644 --- a/src/calendar/src/interface.ts +++ b/src/calendar/src/interface.ts @@ -7,3 +7,14 @@ export interface DateItem { } export type OnPanelChange = (info: { year: number, month: number }) => void + +export interface CalendarDefaultSlotProps { + year: number + month: number + date: number +} + +export interface CalendarHeaderSlotProps { + year: number + month: number +} diff --git a/src/card/index.ts b/src/card/index.ts index 22c04a8bfb1..429a3ef13c5 100644 --- a/src/card/index.ts +++ b/src/card/index.ts @@ -1,2 +1,2 @@ export { cardProps, default as NCard } from './src/Card' -export type { CardProps, CardSegmented } from './src/Card' +export type { CardProps, CardSegmented, CardSlots } from './src/Card' diff --git a/src/card/src/Card.tsx b/src/card/src/Card.tsx index 4371600a7fd..d3018e0c5fc 100644 --- a/src/card/src/Card.tsx +++ b/src/card/src/Card.tsx @@ -8,6 +8,8 @@ import { defineComponent, h, type PropType, + type SlotsType, + type VNode, type VNodeChild } from 'vue' import { NBaseClose } from '../../_internal' @@ -71,9 +73,19 @@ export const cardProps = { export type CardProps = ExtractPublicPropTypes +export interface CardSlots { + default?: () => VNode[] + cover?: () => VNode[] + header?: () => VNode[] + 'header-extra'?: () => VNode[] + footer?: () => VNode[] + action?: () => VNode[] +} + export default defineComponent({ name: 'Card', props: cardProps, + slots: Object as SlotsType, setup(props) { const handleCloseClick = (): void => { const { onClose } = props @@ -164,13 +176,13 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'card', - computed(() => { - return props.size[0] - }), - cssVarsRef, - props - ) + 'card', + computed(() => { + return props.size[0] + }), + cssVarsRef, + props + ) : undefined return { rtlEnabled: rtlEnabledRef, @@ -240,8 +252,8 @@ export default defineComponent({ const { title } = this const mergedChildren = title ? ensureValidVNode( - typeof title === 'function' ? [title()] : [title] - ) + typeof title === 'function' ? [title()] : [title] + ) : children return mergedChildren || this.closable ? (
+export interface CarouselSlots { + default?: () => VNode[] + arrow?: (props: CarouselArrowSlotProps) => VNode[] + dots?: (props: CarouselDotSlotProps) => VNode[] +} + // only one carousel is allowed to trigger touch globally let globalDragging = false export default defineComponent({ name: 'Carousel', props: carouselProps, + slots: Object as SlotsType, setup(props) { const { mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props) // Dom @@ -1007,17 +1023,17 @@ export default defineComponent({ > {userWantsControl ? slides.map((slide, i) => ( -
- {withDirectives( - - {{ - default: () => slide - }} - , - [[vShow, this.isActive(i)]] - )} -
- )) +
+ {withDirectives( + + {{ + default: () => slide + }} + , + [[vShow, this.isActive(i)]] + )} +
+ )) : slides}
) @@ -1025,7 +1041,7 @@ export default defineComponent({ {this.showDots && dotSlotProps.total > 1 - && resolveSlotWithProps(dotsSlot, dotSlotProps, () => [ + && resolveSlotWithTypedProps(dotsSlot, dotSlotProps, () => [ ])} {showArrow - && resolveSlotWithProps(arrowSlot, arrowSlotProps, () => [ + && resolveSlotWithTypedProps(arrowSlot, arrowSlotProps, () => [ ])}
diff --git a/src/carousel/src/CarouselArrow.tsx b/src/carousel/src/CarouselArrow.tsx index b353450fe8e..d216398abbe 100644 --- a/src/carousel/src/CarouselArrow.tsx +++ b/src/carousel/src/CarouselArrow.tsx @@ -2,29 +2,33 @@ import { defineComponent, h } from 'vue' import { useConfig } from '../../_mixins' import { useCarouselContext } from './CarouselContext' -const backwardIcon = ( - - - - - - -) +function renderBackwardIcon() { + return ( + + + + + + + ) +} -const forwardIcon = ( - - - - - - -) +function renderForwardIcon() { + return ( + + + + + + + ) +} export default defineComponent({ name: 'CarouselArrow', @@ -54,7 +58,7 @@ export default defineComponent({ role="button" onClick={this.prev} > - {backwardIcon} + {renderBackwardIcon()}
- {forwardIcon} + {renderForwardIcon()}
) diff --git a/src/carousel/src/interface.ts b/src/carousel/src/interface.ts index c6197512a89..a5f75f96ed0 100644 --- a/src/carousel/src/interface.ts +++ b/src/carousel/src/interface.ts @@ -25,3 +25,17 @@ export interface Size { width: number height: number } + +export interface CarouselArrowSlotProps { + total: number + currentIndex: number + to: (index: number) => void + prev: () => void + next: () => void +} + +export interface CarouselDotSlotProps { + total: number + currentIndex: number + to: (index: number) => void +} diff --git a/src/cascader/index.ts b/src/cascader/index.ts index a849023bb9b..b2586a11eba 100644 --- a/src/cascader/index.ts +++ b/src/cascader/index.ts @@ -1,3 +1,3 @@ export { cascaderProps, default as NCascader } from './src/Cascader' -export type { CascaderProps } from './src/Cascader' +export type { CascaderProps, CascaderSlots } from './src/Cascader' export type { CascaderInst, CascaderOption } from './src/interface' diff --git a/src/cascader/src/Cascader.tsx b/src/cascader/src/Cascader.tsx index f027dde292b..d2553caccba 100644 --- a/src/cascader/src/Cascader.tsx +++ b/src/cascader/src/Cascader.tsx @@ -36,6 +36,7 @@ import { type PropType, provide, ref, + type SlotsType, toRef, type VNode, type VNodeChild, @@ -193,9 +194,17 @@ export const cascaderProps = { export type CascaderProps = ExtractPublicPropTypes +export interface CascaderSlots { + action?: () => VNode[] + arrow?: () => VNode[] + empty?: () => VNode[] + 'not-found'?: () => VNode[] +} + export default defineComponent({ name: 'Cascader', props: cascaderProps, + slots: Object as SlotsType, setup(props, { slots }) { if (__DEV__) { watchEffect(() => { diff --git a/src/cascader/src/CascaderOption.tsx b/src/cascader/src/CascaderOption.tsx index bed6f4f5d6f..9e8844acaa6 100644 --- a/src/cascader/src/CascaderOption.tsx +++ b/src/cascader/src/CascaderOption.tsx @@ -214,10 +214,10 @@ export default defineComponent({
{renderPrefix ? renderPrefix({ - option: this.tmNode.rawNode, - checked: this.checked, - node: originalNode - }) + option: this.tmNode.rawNode, + checked: this.checked, + node: originalNode + }) : originalNode}
) @@ -269,10 +269,10 @@ export default defineComponent({
{renderSuffix ? renderSuffix({ - option: this.tmNode.rawNode, - checked: this.checked, - node: originalSuffixChild - }) + option: this.tmNode.rawNode, + checked: this.checked, + node: originalSuffixChild + }) : originalSuffixChild}
) diff --git a/src/cascader/src/CascaderSelectMenu.tsx b/src/cascader/src/CascaderSelectMenu.tsx index 04b4794d719..3cdba38c80c 100644 --- a/src/cascader/src/CascaderSelectMenu.tsx +++ b/src/cascader/src/CascaderSelectMenu.tsx @@ -102,8 +102,8 @@ export default defineComponent({ return ( pattern ? selectOptionsRef.value.filter((option) => { - return mergedFilter(pattern, option.rawNode, option.path) - }) + return mergedFilter(pattern, option.rawNode, option.path) + }) : selectOptionsRef.value ).map(option => ({ value: option.value, @@ -192,35 +192,35 @@ export default defineComponent({ default: () => this.show ? withDirectives( - - {{ - empty: () => - resolveSlot(cascaderSlots['not-found'], () => []) - }} - , - [ + + {{ + empty: () => + resolveSlot(cascaderSlots['not-found'], () => []) + }} + , [ - clickoutside, - this.handleClickOutside, - undefined as unknown as string, - { capture: true } + [ + clickoutside, + this.handleClickOutside, + undefined as unknown as string, + { capture: true } + ] ] - ] - ) + ) : null }} diff --git a/src/checkbox/src/CheckMark.tsx b/src/checkbox/src/CheckMark.tsx index 4897034d68b..c1c89eaf83e 100644 --- a/src/checkbox/src/CheckMark.tsx +++ b/src/checkbox/src/CheckMark.tsx @@ -1,6 +1,7 @@ +import type { VNode } from 'vue' import { h } from 'vue' -export default ( +export default (): VNode => ( diff --git a/src/checkbox/src/Checkbox.tsx b/src/checkbox/src/Checkbox.tsx index cf3258eece0..a92581efc51 100644 --- a/src/checkbox/src/Checkbox.tsx +++ b/src/checkbox/src/Checkbox.tsx @@ -32,8 +32,8 @@ import { } from '../../_utils' import { checkboxLight } from '../styles' import { checkboxGroupInjectionKey } from './CheckboxGroup' -import CheckMark from './CheckMark' -import LineMark from './LineMark' +import renderCheckMark from './CheckMark' +import renderLineMark from './LineMark' import style from './styles/index.cssr' export const checkboxProps = { @@ -295,11 +295,11 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'checkbox', - computed(() => mergedSizeRef.value[0]), - cssVarsRef, - props - ) + 'checkbox', + computed(() => mergedSizeRef.value[0]), + cssVarsRef, + props + ) : undefined return Object.assign(formItem, exposedMethods, { rtlEnabled: rtlEnabledRef, @@ -389,11 +389,11 @@ export default defineComponent({ key="indeterminate" class={`${mergedClsPrefix}-checkbox-icon`} > - {LineMark} + {renderLineMark()}
) : (
- {CheckMark} + {renderCheckMark()}
) }} diff --git a/src/checkbox/src/LineMark.tsx b/src/checkbox/src/LineMark.tsx index 2053beb42f9..802bda3254e 100644 --- a/src/checkbox/src/LineMark.tsx +++ b/src/checkbox/src/LineMark.tsx @@ -1,6 +1,7 @@ +import type { VNode } from 'vue' import { h } from 'vue' -export default ( +export default (): VNode => ( diff --git a/src/code/src/Code.tsx b/src/code/src/Code.tsx index 9b9672d8f05..f20dd6f9d58 100644 --- a/src/code/src/Code.tsx +++ b/src/code/src/Code.tsx @@ -171,13 +171,13 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'code', - computed(() => { - return `${props.internalFontSize || 'a'}` - }), - cssVarsRef, - props - ) + 'code', + computed(() => { + return `${props.internalFontSize || 'a'}` + }), + cssVarsRef, + props + ) : undefined return { mergedClsPrefix: mergedClsPrefixRef, diff --git a/src/collapse/demos/enUS/index.demo-entry.md b/src/collapse/demos/enUS/index.demo-entry.md index 90775f0aa90..a0bb7db21fe 100644 --- a/src/collapse/demos/enUS/index.demo-entry.md +++ b/src/collapse/demos/enUS/index.demo-entry.md @@ -46,14 +46,15 @@ trigger-areas.vue | Name | Parameters | Description | | --- | --- | --- | -| default | `()` | The contents of the collapsible panel. | | arrow | `(props: { collapsed: boolean })` | Custom icons for folding panels. | +| default | `()` | The contents of the collapsible panel. | +| header | `(props: { collapsed: boolean })` | The content of the header of the collapsed panel node. | ### CollapseItem Slots | Name | Parameters | Description | | --- | --- | --- | +| arrow | `(props: { collapsed: boolean })` | The custom icon of the node header of the collapsible panel. | | default | `()` | The contents of the collapsible panel node. | | header | `(props: { collapsed: boolean })` | The content of the header of the collapsed panel node. | | header-extra | `(props: { collapsed: boolean })` | The extra content of the header of the collapsed panel node. | -| arrow | `(props: { collapsed: boolean })` | The custom icon of the node header of the collapsible panel. | diff --git a/src/collapse/demos/zhCN/index.demo-entry.md b/src/collapse/demos/zhCN/index.demo-entry.md index b4a7483e614..5f5f2d02bf2 100644 --- a/src/collapse/demos/zhCN/index.demo-entry.md +++ b/src/collapse/demos/zhCN/index.demo-entry.md @@ -45,16 +45,17 @@ rtl-debug.vue ### Collapse Slots -| 名称 | 参数 | 说明 | -| ------- | --------------------------------- | -------------------- | -| default | `()` | 折叠面板的内容 | -| arrow | `(props: { collapsed: boolean })` | 折叠面板的自定义图标 | +| 名称 | 参数 | 说明 | +| --- | --- | --- | +| arrow | `(props: { collapsed: boolean })` | 折叠面板的自定义图标 | +| default | `()` | 折叠面板的内容 | +| header-extra | `(props: { collapsed: boolean })` | 折叠面板节点头部的额外内容 | ### CollapseItem Slots | 名称 | 参数 | 说明 | | --- | --- | --- | +| arrow | `(props: { collapsed: boolean })` | 折叠面板节点头部的自定义图标 | | default | `()` | 折叠面板节点的内容 | | header | `(props: { collapsed: boolean })` | 折叠面板节点头部的内容 | | header-extra | `(props: { collapsed: boolean })` | 折叠面板节点头部的额外内容 | -| arrow | `(props: { collapsed: boolean })` | 折叠面板节点头部的自定义图标 | diff --git a/src/collapse/index.ts b/src/collapse/index.ts index 5897ca13679..658ac6addb2 100644 --- a/src/collapse/index.ts +++ b/src/collapse/index.ts @@ -1,4 +1,10 @@ export { collapseProps, default as NCollapse } from './src/Collapse' -export type { CollapseProps } from './src/Collapse' +export type { CollapseProps, CollapseSlots } from './src/Collapse' export { collapseItemProps, default as NCollapseItem } from './src/CollapseItem' -export type { CollapseItemProps } from './src/CollapseItem' +export type { CollapseItemProps, CollapseItemSlots } from './src/CollapseItem' +export type { + CollapseArrowSlotProps, + CollapseItemArrowSlotProps, + CollapseItemHeaderExtraSlotProps, + CollapseItemHeaderSlotProps +} from './src/interface' diff --git a/src/collapse/src/Collapse.tsx b/src/collapse/src/Collapse.tsx index 56b4fa5a3df..5cd0e04448f 100644 --- a/src/collapse/src/Collapse.tsx +++ b/src/collapse/src/Collapse.tsx @@ -1,6 +1,9 @@ import type { ThemeProps } from '../../_mixins' import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils' import type { + CollapseArrowSlotProps, + CollapseItemHeaderExtraSlotProps, + CollapseItemHeaderSlotProps, HeaderClickInfo, OnItemHeaderClick, OnItemHeaderClickImpl, @@ -18,7 +21,8 @@ import { provide, type Ref, ref, - type Slots + type SlotsType, + type VNode } from 'vue' import { useConfig, useTheme, useThemeClass } from '../../_mixins' import { useRtl } from '../../_mixins/use-rtl' @@ -82,11 +86,18 @@ export const collapseProps = { export type CollapseProps = ExtractPublicPropTypes +export interface CollapseSlots { + default?: () => VNode[] + arrow?: (props: CollapseArrowSlotProps) => VNode[] + header?: (props: CollapseItemHeaderSlotProps) => VNode[] + 'header-extra'?: (props: CollapseItemHeaderExtraSlotProps) => VNode[] +} + export interface NCollapseInjection { props: ExtractPropTypes expandedNamesRef: Ref | null> mergedClsPrefixRef: Ref - slots: Slots + slots: CollapseSlots toggleItem: ( collapse: boolean, name: string | number, @@ -100,6 +111,7 @@ export const collapseInjectionKey export default defineComponent({ name: 'Collapse', props: collapseProps, + slots: Object as SlotsType, setup(props, { slots }) { const { mergedClsPrefixRef, inlineThemeDisabled, mergedRtlRef } = useConfig(props) diff --git a/src/collapse/src/CollapseItem.tsx b/src/collapse/src/CollapseItem.tsx index 2b9e51f9221..0ecaf582a43 100644 --- a/src/collapse/src/CollapseItem.tsx +++ b/src/collapse/src/CollapseItem.tsx @@ -1,7 +1,20 @@ import type { ExtractPublicPropTypes } from '../../_utils' +import type { + CollapseItemArrowSlotProps, + CollapseItemHeaderExtraSlotProps, + CollapseItemHeaderSlotProps +} from './interface' import { createId, happensIn } from 'seemly' import { useMemo } from 'vooks' -import { computed, defineComponent, h, inject, type PropType, toRef } from 'vue' +import { + computed, + defineComponent, + h, + inject, + type PropType, + toRef, + type VNode +} from 'vue' import { NBaseIcon } from '../../_internal' import { ChevronLeftIcon as ArrowLeftIcon, @@ -10,7 +23,7 @@ import { import { useConfig } from '../../_mixins' import { useRtl } from '../../_mixins/use-rtl' import { - resolveSlotWithProps, + resolveSlotWithTypedProps, resolveWrappedSlotWithProps, throwError } from '../../_utils' @@ -26,6 +39,13 @@ export const collapseItemProps = { export type CollapseItemProps = ExtractPublicPropTypes +export interface CollapseItemSlots { + default?: () => VNode[] + header?: (props: CollapseItemHeaderSlotProps) => VNode[] + 'header-extra'?: (props: CollapseItemHeaderExtraSlotProps) => VNode[] + arrow?: (props: CollapseItemArrowSlotProps) => VNode[] +} + export default defineComponent({ name: 'CollapseItem', props: collapseItemProps, @@ -109,7 +129,7 @@ export default defineComponent({ disabled, triggerAreas } = this - const headerNode = resolveSlotWithProps( + const headerNode = resolveSlotWithTypedProps( $slots.header, { collapsed }, () => [this.title] @@ -145,17 +165,11 @@ export default defineComponent({ key={this.rtlEnabled ? 0 : 1} data-arrow > - {resolveSlotWithProps(arrowSlot, { collapsed }, () => [ + {resolveSlotWithTypedProps(arrowSlot, { collapsed }, () => [ {{ - default: - collapseSlots.expandIcon - ?? (() => - this.rtlEnabled ? ( - - ) : ( - - )) + default: () => + this.rtlEnabled ? : }} ])} diff --git a/src/collapse/src/interface.ts b/src/collapse/src/interface.ts index 7fe3765d773..6b450438fcc 100644 --- a/src/collapse/src/interface.ts +++ b/src/collapse/src/interface.ts @@ -36,3 +36,19 @@ export interface HeaderClickInfo { expanded: boolean event: MouseEvent } + +export interface CollapseArrowSlotProps { + collapsed: boolean +} + +export interface CollapseItemHeaderSlotProps { + collapsed: boolean +} + +export interface CollapseItemHeaderExtraSlotProps { + collapsed: boolean +} + +export interface CollapseItemArrowSlotProps { + collapsed: boolean +} diff --git a/src/color-picker/index.ts b/src/color-picker/index.ts index 36d55b94744..72879f656ce 100644 --- a/src/color-picker/index.ts +++ b/src/color-picker/index.ts @@ -1,2 +1,2 @@ export { colorPickerProps, default as NColorPicker } from './src/ColorPicker' -export type { ColorPickerProps } from './src/ColorPicker' +export type { ColorPickerProps, ColorPickerSlots } from './src/ColorPicker' diff --git a/src/color-picker/src/ColorPicker.tsx b/src/color-picker/src/ColorPicker.tsx index f50d9c3fb7f..c02eb55f7fd 100644 --- a/src/color-picker/src/ColorPicker.tsx +++ b/src/color-picker/src/ColorPicker.tsx @@ -43,6 +43,7 @@ import { provide, type Ref, ref, + type SlotsType, toRef, Transition, type VNode, @@ -124,9 +125,16 @@ export const colorPickerProps = { export type ColorPickerProps = ExtractPublicPropTypes +export interface ColorPickerSlots { + default?: () => VNode[] + label?: (color: string | null) => VNode[] + action?: () => VNode[] +} + export default defineComponent({ name: 'ColorPicker', props: colorPickerProps, + slots: Object as SlotsType, setup(props, { slots }) { const selfRef = ref(null) let upcomingValue: string | null = null @@ -526,13 +534,13 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'color-picker', - computed(() => { - return mergedSizeRef.value[0] - }), - cssVarsRef, - props - ) + 'color-picker', + computed(() => { + return mergedSizeRef.value[0] + }), + cssVarsRef, + props + ) : undefined function renderPanel(): VNode { @@ -701,7 +709,7 @@ export default defineComponent({ } }, render() { - const { $slots, mergedClsPrefix, onRender } = this + const { mergedClsPrefix, onRender } = this onRender?.() return (
- {{ - label: $slots.label - }} - + /> ) }} , @@ -746,13 +750,13 @@ export default defineComponent({ default: () => this.mergedShow ? withDirectives(this.renderPanel(), [ - [ - clickoutside, - this.handleClickOutside, - undefined as any as string, - { capture: true } - ] - ]) + [ + clickoutside, + this.handleClickOutside, + undefined as any as string, + { capture: true } + ] + ]) : null }} diff --git a/src/color-picker/src/ColorPickerTrigger.tsx b/src/color-picker/src/ColorPickerTrigger.tsx index 7e15853e2d5..119f0ca9305 100644 --- a/src/color-picker/src/ColorPickerTrigger.tsx +++ b/src/color-picker/src/ColorPickerTrigger.tsx @@ -1,9 +1,10 @@ import { type HSLA, toHslaString } from 'seemly' -import { defineComponent, h, inject, type PropType } from 'vue' +import { defineComponent, h, inject, type PropType, type SlotsType } from 'vue' import { colorPickerInjectionKey } from './context' export default defineComponent({ name: 'ColorPickerTrigger', + slots: Object as SlotsType>, props: { clsPrefix: { type: String, diff --git a/src/color-picker/src/context.ts b/src/color-picker/src/context.ts index d0e0430a9aa..4f4a9fb69e9 100644 --- a/src/color-picker/src/context.ts +++ b/src/color-picker/src/context.ts @@ -1,11 +1,12 @@ -import type { ComputedRef, Ref, Slots } from 'vue' +import type { ComputedRef, Ref } from 'vue' import type { MergedTheme } from '../../_mixins' import type { ColorPickerTheme } from '../styles' +import type { ColorPickerSlots } from './ColorPicker' import type { RenderLabel } from './interface' import { createInjectionKey } from '../../_utils' export const colorPickerInjectionKey = createInjectionKey<{ themeRef: ComputedRef> - colorPickerSlots: Slots + colorPickerSlots: ColorPickerSlots renderLabelRef: Ref }>('n-color-picker') diff --git a/src/config-provider/src/ConfigProvider.ts b/src/config-provider/src/ConfigProvider.ts index 15bd4c6c0fe..7c4717ba21e 100644 --- a/src/config-provider/src/ConfigProvider.ts +++ b/src/config-provider/src/ConfigProvider.ts @@ -241,12 +241,12 @@ export default defineComponent({ render() { return !this.abstract ? h( - this.as || this.tag, - { - class: `${this.mergedClsPrefix || defaultClsPrefix}-config-provider` - }, - this.$slots.default?.() - ) + this.as || this.tag, + { + class: `${this.mergedClsPrefix || defaultClsPrefix}-config-provider` + }, + this.$slots.default?.() + ) : this.$slots.default?.() } }) diff --git a/src/data-table/demos/enUS/controlled-sorter.demo.md b/src/data-table/demos/enUS/controlled-sorter.demo.vue similarity index 51% rename from src/data-table/demos/enUS/controlled-sorter.demo.md rename to src/data-table/demos/enUS/controlled-sorter.demo.vue index 1c45c44bc2a..935b8a24b47 100644 --- a/src/data-table/demos/enUS/controlled-sorter.demo.md +++ b/src/data-table/demos/enUS/controlled-sorter.demo.vue @@ -1,34 +1,32 @@ + # Controlled sorter If one of the column objects' `sortOrder` is set to `'ascend'`, `'descend'` or `false`. The sorting behavior the table will be in a controlled manner. If multiple columns' `sortOrder` are set to `'ascend'` or `'descend'`, only first column of them will be applied. + -```html - - - Sort By Name (Ascend) - Sort By Name (Descend) - Clear Sorter - - - -``` - -```js + + + diff --git a/src/data-table/demos/enUS/index.demo-entry.md b/src/data-table/demos/enUS/index.demo-entry.md index e3b20ab0d0c..87a34fcdc1b 100644 --- a/src/data-table/demos/enUS/index.demo-entry.md +++ b/src/data-table/demos/enUS/index.demo-entry.md @@ -37,7 +37,7 @@ custom-select.vue group-header.vue controlled-page.vue controlled-filter.vue -controlled-sorter +controlled-sorter.vue controlled-multiple-sorter fixed-header.vue fixed-header-column.vue diff --git a/src/data-table/demos/enUS/switchable-editable.demo.md b/src/data-table/demos/enUS/switchable-editable.demo.md index 9e45bb5cbb0..21e4afbf669 100644 --- a/src/data-table/demos/enUS/switchable-editable.demo.md +++ b/src/data-table/demos/enUS/switchable-editable.demo.md @@ -51,14 +51,14 @@ const ShowOrEdit = defineComponent({ }, isEdit.value ? h(NInput, { - ref: inputRef, - value: inputValue.value, - onUpdateValue: (v) => { - inputValue.value = v - }, - onChange: handleChange, - onBlur: handleChange - }) + ref: inputRef, + value: inputValue.value, + onUpdateValue: (v) => { + inputValue.value = v + }, + onChange: handleChange, + onBlur: handleChange + }) : props.value ) } diff --git a/src/data-table/demos/zhCN/controlled-sorter.demo.md b/src/data-table/demos/zhCN/controlled-sorter.demo.vue similarity index 50% rename from src/data-table/demos/zhCN/controlled-sorter.demo.md rename to src/data-table/demos/zhCN/controlled-sorter.demo.vue index a3f62387238..92b256a61bf 100644 --- a/src/data-table/demos/zhCN/controlled-sorter.demo.md +++ b/src/data-table/demos/zhCN/controlled-sorter.demo.vue @@ -1,34 +1,32 @@ + # 受控的排序 如果列对象的 `sortOrder` 属性被设为 `'ascend'`、`'descend'` 或者 `false`,表格的排序将为受控状态。如果很多列的 `sortOrder` 都被设定了,那么只有他们之中的第一列会生效。 + -```html - - - Sort By Name (Ascend) - Sort By Name (Descend) - Clear Sorter - - - -``` - -```js + + + diff --git a/src/data-table/demos/zhCN/index.demo-entry.md b/src/data-table/demos/zhCN/index.demo-entry.md index d4e41802766..73978e01842 100644 --- a/src/data-table/demos/zhCN/index.demo-entry.md +++ b/src/data-table/demos/zhCN/index.demo-entry.md @@ -39,7 +39,7 @@ custom-select.vue group-header.vue controlled-page.vue controlled-filter.vue -controlled-sorter +controlled-sorter.vue controlled-multiple-sorter fixed-header.vue fixed-header-column.vue diff --git a/src/data-table/demos/zhCN/switchable-editable.demo.md b/src/data-table/demos/zhCN/switchable-editable.demo.md index 934e751a344..5531dcb092f 100644 --- a/src/data-table/demos/zhCN/switchable-editable.demo.md +++ b/src/data-table/demos/zhCN/switchable-editable.demo.md @@ -51,14 +51,14 @@ const ShowOrEdit = defineComponent({ }, isEdit.value ? h(NInput, { - ref: inputRef, - value: inputValue.value, - onUpdateValue: (v) => { - inputValue.value = v - }, - onChange: handleChange, - onBlur: handleChange - }) + ref: inputRef, + value: inputValue.value, + onUpdateValue: (v) => { + inputValue.value = v + }, + onChange: handleChange, + onBlur: handleChange + }) : props.value ) } diff --git a/src/data-table/index.ts b/src/data-table/index.ts index 2de0bb87b1e..6e44afd22db 100644 --- a/src/data-table/index.ts +++ b/src/data-table/index.ts @@ -21,6 +21,8 @@ export type { RowData as DataTableRowData, RowKey as DataTableRowKey, TableSelectionColumn as DataTableSelectionColumn, + DataTableSlots, + SortOrder as DataTableSortOrder, SortState as DataTableSortState } from './src/interface' export * from './src/publicTypes' diff --git a/src/data-table/src/DataTable.tsx b/src/data-table/src/DataTable.tsx index 3e7c9942027..1728b0a722c 100644 --- a/src/data-table/src/DataTable.tsx +++ b/src/data-table/src/DataTable.tsx @@ -1,6 +1,7 @@ import type { CsvOptionsType, DataTableInst, + DataTableSlots, MainTableRef, RowKey } from './interface' @@ -12,6 +13,7 @@ import { h, provide, ref, + type SlotsType, toRef, Transition, watchEffect @@ -42,6 +44,7 @@ export default defineComponent({ name: 'DataTable', alias: ['AdvancedTable'], props: dataTableProps, + slots: Object as SlotsType, setup(props, { slots }) { if (__DEV__) { watchEffect(() => { @@ -417,11 +420,11 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'data-table', - computed(() => props.size[0]), - cssVarsRef, - props - ) + 'data-table', + computed(() => props.size[0]), + cssVarsRef, + props + ) : undefined const mergedShowPaginationRef = computed(() => { if (!props.pagination) diff --git a/src/data-table/src/TableParts/Body.tsx b/src/data-table/src/TableParts/Body.tsx index d068694bdaa..2674c69913c 100644 --- a/src/data-table/src/TableParts/Body.tsx +++ b/src/data-table/src/TableParts/Body.tsx @@ -774,13 +774,13 @@ export default defineComponent({ : createRowClassName(rowData, actualRowIndex, rowClassName) const iteratedCols = isVirtualX ? cols.filter((col, index) => { - if (startColIndex <= index && index <= endColIndex) - return true - if (col.column.fixed) { - return true - } - return false - }) + if (startColIndex <= index && index <= endColIndex) + return true + if (col.column.fixed) { + return true + } + return false + }) : cols const virtualXRowHeight = isVirtualX ? pxfy(heightForRow?.(rowData, actualRowIndex) || minRowHeight) diff --git a/src/data-table/src/interface.ts b/src/data-table/src/interface.ts index a5b0e1ea362..b965367cd76 100644 --- a/src/data-table/src/interface.ts +++ b/src/data-table/src/interface.ts @@ -5,7 +5,7 @@ import type { HTMLAttributes, PropType, Ref, - Slots, + VNode, VNodeChild } from 'vue' import type { VirtualListInst } from 'vueuc' @@ -178,6 +178,12 @@ export const dataTableProps = { > } as const +export interface DataTableSlots { + default?: () => VNode[] + empty?: () => VNode[] + loading?: () => VNode[] +} + export type FilterOptionValue = string | number export type ColumnKey = string | number export type RowKey = string | number @@ -357,7 +363,7 @@ export type DataTableSelectionOptions = Array< > export interface DataTableInjection { props: DataTableSetupProps - slots: Slots + slots: DataTableSlots indentRef: Ref childTriggerColIndexRef: Ref componentId: string diff --git a/src/data-table/src/use-group-header.ts b/src/data-table/src/use-group-header.ts index 6b3e87af56b..febf763e448 100644 --- a/src/data-table/src/use-group-header.ts +++ b/src/data-table/src/use-group-header.ts @@ -50,12 +50,13 @@ function getRowsAndCols( let maxDepth = -1 let totalRowSpan = 0 let hasEllipsis = false + let currentLeafIndex = 0 function ensureMaxDepth(columns: TableColumns, currentDepth: number): void { if (currentDepth > maxDepth) { rows[currentDepth] = [] maxDepth = currentDepth } - columns.forEach((column, index) => { + columns.forEach((column) => { if ('children' in column) { ensureMaxDepth(column.children, currentDepth + 1) } @@ -68,7 +69,7 @@ function getRowsAndCols( key !== undefined ? formatLength(getResizableWidth(key)) : undefined ), column, - index, + index: currentLeafIndex++, // The width property is only applied to horizontally virtual scroll table width: column.width === undefined ? 128 : Number(column.width) }) @@ -81,7 +82,7 @@ function getRowsAndCols( }) } ensureMaxDepth(columns, 0) - let currentLeafIndex = 0 + currentLeafIndex = 0 function ensureColLayout(columns: TableColumns, currentDepth: number): void { let hideUntilIndex = 0 columns.forEach((column) => { diff --git a/src/data-table/src/use-table-data.ts b/src/data-table/src/use-table-data.ts index 8e9bd0fa1a4..30792b3207f 100644 --- a/src/data-table/src/use-table-data.ts +++ b/src/data-table/src/use-table-data.ts @@ -137,48 +137,48 @@ export function useTableData( }) return data ? data.filter((tmNode) => { - const { rawNode: row } = tmNode - // traverse all filters - for (const [columnKey, column] of columnEntries) { - let activeFilterOptionValues = mergedFilterState[columnKey] - if (activeFilterOptionValues == null) - continue - if (!Array.isArray(activeFilterOptionValues)) { - activeFilterOptionValues = [activeFilterOptionValues] - } - if (!activeFilterOptionValues.length) - continue - // When async, filter won't be set, so data won't be filtered - const filter + const { rawNode: row } = tmNode + // traverse all filters + for (const [columnKey, column] of columnEntries) { + let activeFilterOptionValues = mergedFilterState[columnKey] + if (activeFilterOptionValues == null) + continue + if (!Array.isArray(activeFilterOptionValues)) { + activeFilterOptionValues = [activeFilterOptionValues] + } + if (!activeFilterOptionValues.length) + continue + // When async, filter won't be set, so data won't be filtered + const filter = column.filter === 'default' ? createDefaultFilter(columnKey) : column.filter - if (column && typeof filter === 'function') { - if (column.filterMode === 'and') { - if ( - activeFilterOptionValues.some( - filterOptionValue => !filter(filterOptionValue, row) - ) - ) { - return false - } - } - else { - if ( - activeFilterOptionValues.some(filterOptionValue => - filter(filterOptionValue, row) - ) - ) { - continue + if (column && typeof filter === 'function') { + if (column.filterMode === 'and') { + if ( + activeFilterOptionValues.some( + filterOptionValue => !filter(filterOptionValue, row) + ) + ) { + return false + } } else { - return false + if ( + activeFilterOptionValues.some(filterOptionValue => + filter(filterOptionValue, row) + ) + ) { + continue + } + else { + return false + } } } } - } - return true - }) + return true + }) : [] }) @@ -241,12 +241,12 @@ export function useTableData( return props.remote ? page : Math.max( - 1, - Math.min( - Math.ceil(filteredDataRef.value.length / mergedPageSizeRef.value), - page + 1, + Math.min( + Math.ceil(filteredDataRef.value.length / mergedPageSizeRef.value), + page + ) ) - ) }) const mergedPageCountRef = computed(() => { diff --git a/src/date-picker/demos/enUS/index.demo-entry.md b/src/date-picker/demos/enUS/index.demo-entry.md index 049b2040b68..901337370d1 100644 --- a/src/date-picker/demos/enUS/index.demo-entry.md +++ b/src/date-picker/demos/enUS/index.demo-entry.md @@ -220,9 +220,9 @@ panel.vue | 名称 | 参数 | 说明 | 版本 | | --- | --- | --- | --- | -| now | `(props: { onNow: () => void, text: string })` | Now button of the panel. | 2.40.0 | | clear | `(props: { onClear: () => void, text: string })` | Clear button of the panel. | 2.40.0 | | confirm | `(props: { onConfirm: () => void, disabled: boolean, text: string })` | Confirm button of the panel. | 2.40.0 | +| now | `(props: { onNow: () => void, text: string })` | Now button of the panel. | 2.40.0 | ### DatePicker Methods diff --git a/src/date-picker/demos/zhCN/index.demo-entry.md b/src/date-picker/demos/zhCN/index.demo-entry.md index 5367b7994d0..0fa055211e2 100644 --- a/src/date-picker/demos/zhCN/index.demo-entry.md +++ b/src/date-picker/demos/zhCN/index.demo-entry.md @@ -220,9 +220,9 @@ form-debug.vue | 名称 | 参数 | 说明 | 版本 | | --- | --- | --- | --- | -| now | `(props: { onNow: () => void, text: string })` | 面板的此刻按钮 | 2.40.0 | | clear | `(props: { onClear: () => void, text: string })` | 面板的清除按钮 | 2.40.0 | | confirm | `(props: { onConfirm: () => void, disabled: boolean, text: string })` | 面板的确认按钮 | 2.40.0 | +| now | `(props: { onNow: () => void, text: string })` | 面板的此刻按钮 | 2.40.0 | ### DatePicker Methods diff --git a/src/date-picker/index.ts b/src/date-picker/index.ts index e68b709cf83..5b7ffca84f8 100644 --- a/src/date-picker/index.ts +++ b/src/date-picker/index.ts @@ -1,3 +1,4 @@ export { default as NDatePicker } from './src/DatePicker' +export type { DatePickerSlots } from './src/DatePicker' export { datePickerProps } from './src/props' export type * from './src/public-types' diff --git a/src/date-picker/src/DatePicker.tsx b/src/date-picker/src/DatePicker.tsx index 7a503337d21..1bc114f7266 100644 --- a/src/date-picker/src/DatePicker.tsx +++ b/src/date-picker/src/DatePicker.tsx @@ -23,6 +23,7 @@ import { provide, type Ref, ref, + type SlotsType, toRef, Transition, type VNode, @@ -68,9 +69,27 @@ import { export type DatePickerSetupProps = ExtractPropTypes +export interface DatePickerSlots { + 'date-icon'?: () => VNode[] + footer?: () => VNode[] + 'next-month'?: () => VNode[] + 'next-year'?: () => VNode[] + 'prev-month'?: () => VNode[] + 'prev-year'?: () => VNode[] + separator?: () => VNode[] + confirm?: (props: { + onConfirm: () => void + disabled: boolean + text: string + }) => VNode[] + clear?: (props: { onClear: () => void, text: string }) => VNode[] + now?: (props: { onNow: () => void, text: string }) => VNode[] +} + export default defineComponent({ name: 'DatePicker', props: datePickerProps, + slots: Object as SlotsType, setup(props, { slots }) { if (__DEV__) { watchEffect(() => { @@ -750,11 +769,11 @@ export default defineComponent({ }) const triggerThemeClassHandle = inlineThemeDisabled ? useThemeClass( - 'date-picker-trigger', - undefined, - triggerCssVarsRef, - props - ) + 'date-picker-trigger', + undefined, + triggerCssVarsRef, + props + ) : undefined const cssVarsRef = computed(() => { @@ -870,13 +889,13 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'date-picker', - computed(() => { - return props.type - }), - cssVarsRef, - props - ) + 'date-picker', + computed(() => { + return props.type + }), + cssVarsRef, + props + ) : undefined return { @@ -1075,15 +1094,15 @@ export default defineComponent({ separator: () => this.separator === undefined ? resolveSlot($slots.separator, () => [ - - {{ - default: () => - }} - - ]) + + {{ + default: () => + }} + + ]) : this.separator, [clearable ? 'clear-icon-placeholder' : 'suffix']: () => diff --git a/src/date-picker/src/interface.ts b/src/date-picker/src/interface.ts index 26c982c6afc..b0defadb6cd 100644 --- a/src/date-picker/src/interface.ts +++ b/src/date-picker/src/interface.ts @@ -1,4 +1,4 @@ -import type { Ref, Slots, UnwrapNestedRefs } from 'vue' +import type { Ref, UnwrapNestedRefs } from 'vue' import type { VirtualListInst } from 'vueuc' import type { ScrollbarInst } from '../../_internal' import type { MergedTheme } from '../../_mixins' @@ -11,6 +11,7 @@ import type { } from '../../time-picker/src/interface' import type { TimePickerProps } from '../../time-picker/src/TimePicker' import type { DatePickerTheme } from '../styles/light' +import type { DatePickerSlots } from './DatePicker' import type { dualCalendarValidation, uniCalendarValidation @@ -135,7 +136,7 @@ export type DatePickerInjection = { monthFormatRef: Ref yearFormatRef: Ref quarterFormatRef: Ref - datePickerSlots: Slots + datePickerSlots: DatePickerSlots yearRangeRef: Ref<[number, number]> } & ReturnType & ReturnType diff --git a/src/date-picker/src/panel/date.tsx b/src/date-picker/src/panel/date.tsx index 9e10c944a88..dbe0706de1d 100644 --- a/src/date-picker/src/panel/date.tsx +++ b/src/date-picker/src/panel/date.tsx @@ -10,7 +10,11 @@ import { FastForwardIcon, ForwardIcon } from '../../../_internal/icons' -import { resolveSlot, resolveSlotWithProps, warnOnce } from '../../../_utils' +import { + resolveSlot, + resolveSlotWithTypedProps, + warnOnce +} from '../../../_utils' import { NButton, NxButton } from '../../../button' import PanelHeader from './panelHeader' import { useCalendar, useCalendarProps } from './use-calendar' @@ -44,8 +48,14 @@ export default defineComponent({ return useCalendar(props, props.type) }, render() { - const { mergedClsPrefix, mergedTheme, shortcuts, onRender, $slots, type } - = this + const { + mergedClsPrefix, + mergedTheme, + shortcuts, + onRender, + datePickerSlots, + type + } = this onRender?.() return (
- {resolveSlot($slots['prev-year'], () => [])} + {resolveSlot(datePickerSlots['prev-year'], () => [ + + ])}
- {resolveSlot($slots['prev-month'], () => [])} + {resolveSlot(datePickerSlots['prev-month'], () => [ + + ])}
- {resolveSlot($slots['next-month'], () => [])} + {resolveSlot(datePickerSlots['next-month'], () => [ + + ])}
- {resolveSlot($slots['next-year'], () => [])} + {resolveSlot(datePickerSlots['next-year'], () => [ + + ])}
@@ -180,8 +198,8 @@ export default defineComponent({
{this.actions?.includes('clear') - ? resolveSlotWithProps( - this.$slots.clear, + ? resolveSlotWithTypedProps( + this.$slots.clear, { onClear: this.handleClearClick, text: this.locale.clear @@ -196,11 +214,11 @@ export default defineComponent({ {{ default: () => this.locale.clear }} ] - ) + ) : null} {this.actions?.includes('now') - ? resolveSlotWithProps( - this.$slots.now, + ? resolveSlotWithTypedProps( + this.$slots.now, { onNow: this.handleNowClick, text: this.locale.now @@ -215,7 +233,7 @@ export default defineComponent({ {{ default: () => this.locale.now }} ] - ) + ) : null} {/** we don't need a confirm button for date picking */}
diff --git a/src/date-picker/src/panel/daterange.tsx b/src/date-picker/src/panel/daterange.tsx index e000b9358e3..8180d416a72 100644 --- a/src/date-picker/src/panel/daterange.tsx +++ b/src/date-picker/src/panel/daterange.tsx @@ -10,7 +10,11 @@ import { FastForwardIcon, ForwardIcon } from '../../../_internal/icons' -import { resolveSlot, resolveSlotWithProps, warnOnce } from '../../../_utils' +import { + resolveSlot, + resolveSlotWithTypedProps, + warnOnce +} from '../../../_utils' import { NButton, NxButton } from '../../../button' import PanelHeader from './panelHeader' import { useDualCalendar, useDualCalendarProps } from './use-dual-calendar' @@ -32,7 +36,13 @@ export default defineComponent({ return useDualCalendar(props, 'daterange') }, render() { - const { mergedClsPrefix, mergedTheme, shortcuts, onRender, $slots } = this + const { + mergedClsPrefix, + mergedTheme, + shortcuts, + onRender, + datePickerSlots + } = this onRender?.() return ( @@ -57,13 +67,17 @@ export default defineComponent({ class={`${mergedClsPrefix}-date-panel-month__fast-prev`} onClick={this.startCalendarPrevYear} > - {resolveSlot($slots['prev-year'], () => [])} + {resolveSlot(datePickerSlots['prev-year'], () => [ + + ])}
- {resolveSlot($slots['prev-month'], () => [])} + {resolveSlot(datePickerSlots['prev-month'], () => [ + + ])}
- {resolveSlot($slots['next-month'], () => [])} + {resolveSlot(datePickerSlots['next-month'], () => [ + + ])}
- {resolveSlot($slots['next-year'], () => [])} + {resolveSlot(datePickerSlots['next-year'], () => [ + + ])}
@@ -148,13 +166,17 @@ export default defineComponent({ class={`${mergedClsPrefix}-date-panel-month__fast-prev`} onClick={this.endCalendarPrevYear} > - {resolveSlot($slots['prev-year'], () => [])} + {resolveSlot(datePickerSlots['prev-year'], () => [ + + ])}
- {resolveSlot($slots['prev-month'], () => [])} + {resolveSlot(datePickerSlots['prev-month'], () => [ + + ])}
- {resolveSlot($slots['next-month'], () => [])} + {resolveSlot(datePickerSlots['next-month'], () => [ + + ])}
- {resolveSlot($slots['next-year'], () => [])} + {resolveSlot(datePickerSlots['next-year'], () => [ + + ])}
@@ -261,8 +287,8 @@ export default defineComponent({
{this.actions?.includes('clear') - ? resolveSlotWithProps( - $slots.clear, + ? resolveSlotWithTypedProps( + datePickerSlots.clear, { onClear: this.handleClearClick, text: this.locale.clear @@ -277,11 +303,11 @@ export default defineComponent({ {{ default: () => this.locale.clear }} ] - ) + ) : null} {this.actions?.includes('confirm') - ? resolveSlotWithProps( - $slots.confirm, + ? resolveSlotWithTypedProps( + datePickerSlots.confirm, { onConfirm: this.handleConfirmClick, disabled: this.isRangeInvalid || this.isSelecting, @@ -299,7 +325,7 @@ export default defineComponent({ {{ default: () => this.locale.confirm }} ] - ) + ) : null}
diff --git a/src/date-picker/src/panel/datetime.tsx b/src/date-picker/src/panel/datetime.tsx index 7ee1e2fbd36..2f8c5910582 100644 --- a/src/date-picker/src/panel/datetime.tsx +++ b/src/date-picker/src/panel/datetime.tsx @@ -11,7 +11,7 @@ import { FastForwardIcon, ForwardIcon } from '../../../_internal/icons' -import { resolveSlot, resolveSlotWithProps } from '../../../_utils' +import { resolveSlot, resolveSlotWithTypedProps } from '../../../_utils' import { NButton, NxButton } from '../../../button' import { NInput } from '../../../input' import { NTimePicker } from '../../../time-picker' @@ -36,8 +36,8 @@ export default defineComponent({ mergedTheme, shortcuts, timePickerProps, - onRender, - $slots + datePickerSlots, + onRender } = this onRender?.() @@ -91,13 +91,17 @@ export default defineComponent({ class={`${mergedClsPrefix}-date-panel-month__fast-prev`} onClick={this.prevYear} > - {resolveSlot($slots['prev-year'], () => [])} + {resolveSlot(datePickerSlots['prev-year'], () => [ + + ])}
- {resolveSlot($slots['prev-month'], () => [])} + {resolveSlot(datePickerSlots['prev-month'], () => [ + + ])}
- {resolveSlot($slots['next-month'], () => [])} + {resolveSlot(datePickerSlots['next-month'], () => [ + + ])}
- {resolveSlot($slots['next-year'], () => [])} + {resolveSlot(datePickerSlots['next-year'], () => [ + + ])}
@@ -198,8 +206,8 @@ export default defineComponent({
{this.actions?.includes('clear') - ? resolveSlotWithProps( - this.$slots.clear, + ? resolveSlotWithTypedProps( + this.datePickerSlots.clear, { onClear: this.clearSelectedDateTime, text: this.locale.clear @@ -214,11 +222,11 @@ export default defineComponent({ {{ default: () => this.locale.clear }} ] - ) + ) : null} {this.actions?.includes('now') - ? resolveSlotWithProps( - $slots.now, + ? resolveSlotWithTypedProps( + datePickerSlots.now, { onNow: this.handleNowClick, text: this.locale.now @@ -233,11 +241,11 @@ export default defineComponent({ {{ default: () => this.locale.now }} ] - ) + ) : null} {this.actions?.includes('confirm') - ? resolveSlotWithProps( - $slots.confirm, + ? resolveSlotWithTypedProps( + datePickerSlots.confirm, { onConfirm: this.handleConfirmClick, disabled: this.isDateInvalid, @@ -255,7 +263,7 @@ export default defineComponent({ {{ default: () => this.locale.confirm }} ] - ) + ) : null}
diff --git a/src/date-picker/src/panel/datetimerange.tsx b/src/date-picker/src/panel/datetimerange.tsx index d74106011ec..fa79149384b 100644 --- a/src/date-picker/src/panel/datetimerange.tsx +++ b/src/date-picker/src/panel/datetimerange.tsx @@ -10,7 +10,11 @@ import { FastForwardIcon, ForwardIcon } from '../../../_internal/icons' -import { resolveSlot, resolveSlotWithProps, warnOnce } from '../../../_utils' +import { + resolveSlot, + resolveSlotWithTypedProps, + warnOnce +} from '../../../_utils' import { NButton, NxButton } from '../../../button' import { NInput } from '../../../input' import { NTimePicker } from '../../../time-picker' @@ -40,7 +44,7 @@ export default defineComponent({ shortcuts, timePickerProps, onRender, - $slots + datePickerSlots } = this onRender?.() @@ -132,13 +136,17 @@ export default defineComponent({ class={`${mergedClsPrefix}-date-panel-month__fast-prev`} onClick={this.startCalendarPrevYear} > - {resolveSlot($slots['prev-year'], () => [])} + {resolveSlot(datePickerSlots['prev-year'], () => [ + + ])}
- {resolveSlot($slots['prev-month'], () => [])} + {resolveSlot(datePickerSlots['prev-month'], () => [ + + ])}
- {resolveSlot($slots['next-month'], () => [])} + {resolveSlot(datePickerSlots['next-month'], () => [ + + ])}
- {resolveSlot($slots['next-year'], () => [])} + {resolveSlot(datePickerSlots['next-year'], () => [ + + ])}
@@ -233,13 +245,17 @@ export default defineComponent({ class={`${mergedClsPrefix}-date-panel-month__fast-prev`} onClick={this.endCalendarPrevYear} > - {resolveSlot($slots['prev-year'], () => [])} + {resolveSlot(datePickerSlots['prev-year'], () => [ + + ])}
- {resolveSlot($slots['prev-month'], () => [])} + {resolveSlot(datePickerSlots['prev-month'], () => [ + + ])}
- {resolveSlot($slots['next-month'], () => [])} + {resolveSlot(datePickerSlots['next-month'], () => [ + + ])}
- {resolveSlot($slots['next-year'], () => [])} + {resolveSlot(datePickerSlots['next-year'], () => [ + + ])}
@@ -356,8 +376,8 @@ export default defineComponent({
{this.actions?.includes('clear') - ? resolveSlotWithProps( - $slots.clear, + ? resolveSlotWithTypedProps( + datePickerSlots.clear, { onClear: this.handleClearClick, text: this.locale.clear @@ -372,11 +392,11 @@ export default defineComponent({ {{ default: () => this.locale.clear }} ] - ) + ) : null} {this.actions?.includes('confirm') - ? resolveSlotWithProps( - $slots.confirm, + ? resolveSlotWithTypedProps( + datePickerSlots.confirm, { onConfirm: this.handleConfirmClick, disabled: this.isRangeInvalid || this.isSelecting, @@ -394,7 +414,7 @@ export default defineComponent({ {{ default: () => this.locale.confirm }} ] - ) + ) : null}
diff --git a/src/date-picker/src/panel/month.tsx b/src/date-picker/src/panel/month.tsx index 64303d976ae..b63bd6c75dc 100644 --- a/src/date-picker/src/panel/month.tsx +++ b/src/date-picker/src/panel/month.tsx @@ -8,7 +8,7 @@ import { defineComponent, h, onMounted, type PropType, type VNode } from 'vue' import { VirtualList } from 'vueuc' import { NBaseFocusDetector, NScrollbar } from '../../../_internal' import { useLocale } from '../../../_mixins' -import { resolveSlotWithProps } from '../../../_utils' +import { resolveSlotWithTypedProps, resolveWrappedSlot } from '../../../_utils' import { NButton, NxButton } from '../../../button' import { MONTH_ITEM_HEIGHT } from '../config' import { @@ -212,13 +212,11 @@ export default defineComponent({ ) : null} - {this.datePickerSlots.footer ? ( -
- {{ - default: this.datePickerSlots.footer - }} -
- ) : null} + {resolveWrappedSlot(this.datePickerSlots.footer, (children) => { + return children ? ( +
{children}
+ ) : null + })} {actions?.length || shortcuts ? (
@@ -245,8 +243,8 @@ export default defineComponent({
{actions?.includes('clear') - ? resolveSlotWithProps( - this.$slots.now, + ? resolveSlotWithTypedProps( + this.datePickerSlots.clear, { onClear: this.handleClearClick, text: this.locale.clear @@ -261,11 +259,11 @@ export default defineComponent({ {{ default: () => this.locale.clear }} ] - ) + ) : null} {actions?.includes('now') - ? resolveSlotWithProps( - this.$slots.now, + ? resolveSlotWithTypedProps( + this.datePickerSlots.now, { onNow: this.handleNowClick, text: this.locale.now @@ -280,11 +278,11 @@ export default defineComponent({ {{ default: () => this.locale.now }} ] - ) + ) : null} {actions?.includes('confirm') - ? resolveSlotWithProps( - this.$slots.confirm, + ? resolveSlotWithTypedProps( + this.datePickerSlots.confirm, { onConfirm: this.handleConfirmClick, disabled: this.isDateInvalid, @@ -302,7 +300,7 @@ export default defineComponent({ {{ default: () => this.locale.confirm }} ] - ) + ) : null}
diff --git a/src/date-picker/src/panel/monthrange.tsx b/src/date-picker/src/panel/monthrange.tsx index e4b87484ca6..ba6e7bc2872 100644 --- a/src/date-picker/src/panel/monthrange.tsx +++ b/src/date-picker/src/panel/monthrange.tsx @@ -7,14 +7,17 @@ import { h, onMounted, type PropType, - renderSlot, type VNode, watchEffect } from 'vue' import { VirtualList } from 'vueuc' import { NBaseFocusDetector, NScrollbar } from '../../../_internal' import { useLocale } from '../../../_mixins' -import { resolveSlotWithProps, warnOnce } from '../../../_utils' +import { + resolveSlotWithTypedProps, + resolveWrappedSlot, + warnOnce +} from '../../../_utils' import { NxButton } from '../../../button' import { MONTH_ITEM_HEIGHT } from '../config' import { @@ -83,21 +86,21 @@ export default defineComponent({ > {item.type === 'month' ? getMonthString( - item.dateObject.month, - item.monthFormat, - dateLocaleRef.value.locale - ) - : item.type === 'quarter' - ? getQuarterString( - item.dateObject.quarter, - item.quarterFormat, + item.dateObject.month, + item.monthFormat, dateLocaleRef.value.locale ) + : item.type === 'quarter' + ? getQuarterString( + item.dateObject.quarter, + item.quarterFormat, + dateLocaleRef.value.locale + ) : getYearString( - item.dateObject.year, - item.yearFormat, - dateLocaleRef.value.locale - )} + item.dateObject.year, + item.yearFormat, + dateLocaleRef.value.locale + )} ) } @@ -270,11 +273,11 @@ export default defineComponent({ ) : null} - {this.datePickerSlots.footer ? ( -
- {renderSlot(this.datePickerSlots, 'footer')} -
- ) : null} + {resolveWrappedSlot(this.datePickerSlots.footer, (children) => { + return children ? ( +
{children}
+ ) : null + })} {this.actions?.length || shortcuts ? (
@@ -302,8 +305,8 @@ export default defineComponent({
{this.actions?.includes('clear') - ? resolveSlotWithProps( - this.$slots.clear, + ? resolveSlotWithTypedProps( + this.datePickerSlots.clear, { onClear: this.handleClearClick, text: this.locale.clear @@ -318,11 +321,11 @@ export default defineComponent({ {{ default: () => this.locale.clear }} ] - ) + ) : null} {this.actions?.includes('confirm') - ? resolveSlotWithProps( - this.$slots.confirm, + ? resolveSlotWithTypedProps( + this.datePickerSlots.confirm, { disabled: this.isRangeInvalid, onConfirm: this.handleConfirmClick, @@ -340,7 +343,7 @@ export default defineComponent({ {{ default: () => this.locale.confirm }} ] - ) + ) : null}
diff --git a/src/date-picker/src/panel/panelHeader.tsx b/src/date-picker/src/panel/panelHeader.tsx index e55700d564d..36104409aff 100644 --- a/src/date-picker/src/panel/panelHeader.tsx +++ b/src/date-picker/src/panel/panelHeader.tsx @@ -106,28 +106,28 @@ export default defineComponent({ default: () => this.show ? withDirectives( - , - [ + , [ - clickoutside, - handleClickOutside, - undefined as unknown as string, - { capture: true } + [ + clickoutside, + handleClickOutside, + undefined as unknown as string, + { capture: true } + ] ] - ] - ) + ) : null }} diff --git a/src/date-picker/src/panel/use-calendar.ts b/src/date-picker/src/panel/use-calendar.ts index d714c7fa3d9..987f1ba7aa0 100644 --- a/src/date-picker/src/panel/use-calendar.ts +++ b/src/date-picker/src/panel/use-calendar.ts @@ -382,9 +382,9 @@ function useCalendar( newValue = getTime( dateItem.type === 'quarter' && dateItem.dateObject.quarter ? setQuarter( - setYear(newValue, dateItem.dateObject.year), - dateItem.dateObject.quarter - ) + setYear(newValue, dateItem.dateObject.year), + dateItem.dateObject.quarter + ) : set(newValue, dateItem.dateObject) ) panelCommon.doUpdateValue( diff --git a/src/date-picker/src/panel/use-dual-calendar.ts b/src/date-picker/src/panel/use-dual-calendar.ts index 20a99b9af6f..1d8c84ec9c2 100644 --- a/src/date-picker/src/panel/use-dual-calendar.ts +++ b/src/date-picker/src/panel/use-dual-calendar.ts @@ -138,19 +138,19 @@ function useDualCalendar( const startDateInput = ref( Array.isArray(value) ? format( - value[0], - mergedDateFormatRef.value, - panelCommon.dateFnsOptions.value - ) + value[0], + mergedDateFormatRef.value, + panelCommon.dateFnsOptions.value + ) : '' ) const endDateInputRef = ref( Array.isArray(value) ? format( - value[1], - mergedDateFormatRef.value, - panelCommon.dateFnsOptions.value - ) + value[1], + mergedDateFormatRef.value, + panelCommon.dateFnsOptions.value + ) : '' ) @@ -795,19 +795,19 @@ function useDualCalendar( = dateItem.type === 'year' && type !== 'yearrange' ? noCurrentValue ? set(dateItem.ts, { - month: getMonth( - type === 'quarterrange' - ? startOfQuarter(new Date()) - : new Date() - ) - }).valueOf() + month: getMonth( + type === 'quarterrange' + ? startOfQuarter(new Date()) + : new Date() + ) + }).valueOf() : set(dateItem.ts, { - month: getMonth( - type === 'quarterrange' - ? startOfQuarter(value[clickType === 'start' ? 0 : 1]) - : value[clickType === 'start' ? 0 : 1] - ) - }).valueOf() + month: getMonth( + type === 'quarterrange' + ? startOfQuarter(value[clickType === 'start' ? 0 : 1]) + : value[clickType === 'start' ? 0 : 1] + ) + }).valueOf() : dateItem.ts if (noCurrentValue) { const partialValue = sanitizeValue(itemTs) diff --git a/src/descriptions/index.ts b/src/descriptions/index.ts index d7555eaa3b1..7a9f1d4d9fd 100644 --- a/src/descriptions/index.ts +++ b/src/descriptions/index.ts @@ -1,7 +1,14 @@ export { descriptionsProps, default as NDescriptions } from './src/Descriptions' -export type { DescriptionProps, DescriptionsProps } from './src/Descriptions' +export type { + DescriptionProps, + DescriptionsProps, + DescriptionsSlots +} from './src/Descriptions' export { descriptionsItemProps, default as NDescriptionsItem } from './src/DescriptionsItem' -export type { DescriptionItemProps } from './src/DescriptionsItem' +export type { + DescriptionItemProps, + DescriptionItemSlots +} from './src/DescriptionsItem' diff --git a/src/descriptions/src/Descriptions.tsx b/src/descriptions/src/Descriptions.tsx index a7491499045..2a787a25e16 100644 --- a/src/descriptions/src/Descriptions.tsx +++ b/src/descriptions/src/Descriptions.tsx @@ -9,6 +9,7 @@ import { defineComponent, h, type PropType, + type SlotsType, type VNode } from 'vue' import { useConfig, useTheme, useThemeClass } from '../../_mixins' @@ -58,9 +59,15 @@ export type DescriptionsProps = ExtractPublicPropTypes /** @deprecated You should use `DescriptionsProps` */ export type DescriptionProps = DescriptionsProps +export interface DescriptionsSlots { + default?: () => VNode[] + header?: () => VNode[] +} + export default defineComponent({ name: 'Descriptions', props: descriptionsProps, + slots: Object as SlotsType, setup(props) { const { mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props) const themeRef = useTheme( @@ -122,18 +129,18 @@ export default defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'descriptions', - computed(() => { - let hash = '' - const { size, bordered } = props - if (bordered) - hash += 'a' - hash += size[0] - return hash - }), - cssVarsRef, - props - ) + 'descriptions', + computed(() => { + let hash = '' + const { size, bordered } = props + if (bordered) + hash += 'a' + hash += size[0] + return hash + }), + cssVarsRef, + props + ) : undefined return { mergedClsPrefix: mergedClsPrefixRef, diff --git a/src/descriptions/src/DescriptionsItem.ts b/src/descriptions/src/DescriptionsItem.ts index 8628264f3ad..f09d7fe93b4 100644 --- a/src/descriptions/src/DescriptionsItem.ts +++ b/src/descriptions/src/DescriptionsItem.ts @@ -1,5 +1,11 @@ import type { ExtractPublicPropTypes } from '../../_utils' -import { type CSSProperties, defineComponent, type PropType } from 'vue' +import { + type CSSProperties, + defineComponent, + type PropType, + type SlotsType, + type VNode +} from 'vue' import { DESCRIPTION_ITEM_FLAG } from './utils' export const descriptionsItemProps = { @@ -18,10 +24,16 @@ export type DescriptionItemProps = ExtractPublicPropTypes< typeof descriptionsItemProps > +export interface DescriptionItemSlots { + default?: () => VNode[] + label?: () => VNode[] +} + export default defineComponent({ name: 'DescriptionsItem', [DESCRIPTION_ITEM_FLAG]: true, props: descriptionsItemProps, + slots: Object as SlotsType, render() { return null } diff --git a/src/dialog/demos/enUS/basic.demo.vue b/src/dialog/demos/enUS/basic.demo.vue index fb66665b9b1..4f208b1d8e8 100644 --- a/src/dialog/demos/enUS/basic.demo.vue +++ b/src/dialog/demos/enUS/basic.demo.vue @@ -19,6 +19,7 @@ export default defineComponent({ content: 'Are you sure?', positiveText: 'Sure', negativeText: 'Not Sure', + draggable: true, onPositiveClick: () => { message.success('Sure') }, diff --git a/src/dialog/demos/enUS/index.demo-entry.md b/src/dialog/demos/enUS/index.demo-entry.md index dceacb2e919..43c07334add 100644 --- a/src/dialog/demos/enUS/index.demo-entry.md +++ b/src/dialog/demos/enUS/index.demo-entry.md @@ -65,8 +65,8 @@ use-dialog-reactive-list.vue | Name | Type | Default | Description | Version | | --- | --- | --- | --- | --- | | 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 | +| actionClass | `string` | `undefined` | The class name of the action area. | 2.38.2 | +| actionStyle | `Object \| string` | `undefined` | 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`. | | @@ -74,8 +74,9 @@ use-dialog-reactive-list.vue | 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. | | -| contentClass | `string` | The class name of the content. | 2.38.2 | -| contentStyle | `Object \| string` | The style of the content. | 2.38.2 | +| contentClass | `string` | `undefined` | The class name of the content. | 2.38.2 | +| contentStyle | `Object \| string` | `undefined` | The style of the content. | 2.38.2 | +| draggable | `boolean \| { bounds?: 'none' }` | `false` | Whether it is draggable. | NEXT_VERSION | | iconPlacement | `'left' \| 'top'` | `'left'` | Icon placement. | | | icon | `() => VNodeChild` | `undefined` | `Render` function of `icon`. | | | loading | `boolean` | `false` | Whether to display `loading` status. | | @@ -87,8 +88,8 @@ use-dialog-reactive-list.vue | 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. | | -| titleClass | `string` | The class name of the content. | 2.38.2 | -| titleStyle | `Object \| string` | The style of the content. | 2.38.2 | +| titleClass | `string` | `undefined` | The class name of the content. | 2.38.2 | +| titleStyle | `Object \| string` | `undefined` | 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 | diff --git a/src/dialog/demos/zhCN/basic.demo.vue b/src/dialog/demos/zhCN/basic.demo.vue index fed2eb3a466..0a7c1a4cfa1 100644 --- a/src/dialog/demos/zhCN/basic.demo.vue +++ b/src/dialog/demos/zhCN/basic.demo.vue @@ -19,6 +19,7 @@ export default defineComponent({ content: '你确定?', positiveText: '确定', negativeText: '不确定', + draggable: true, onPositiveClick: () => { message.success('确定') }, diff --git a/src/dialog/demos/zhCN/index.demo-entry.md b/src/dialog/demos/zhCN/index.demo-entry.md index a43733e0c12..a37754a9555 100644 --- a/src/dialog/demos/zhCN/index.demo-entry.md +++ b/src/dialog/demos/zhCN/index.demo-entry.md @@ -67,8 +67,8 @@ rtl-debug.vue | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | | action | `() => VNodeChild` | `undefined` | 操作区域的内容,需要是渲染函数 | | -| actionClass | `string` | 操作区域的类名 | 2.38.2 | -| actionStyle | `Object \| string` | 操作区域的样式 | 2.38.2 | +| actionClass | `string` | `undefined` | 操作区域的类名 | 2.38.2 | +| actionStyle | `Object \| string` | `undefined` | 操作区域的样式 | 2.38.2 | | autoFocus | `boolean` | `true` | 是否自动聚焦 Modal 第一个可聚焦的元素 | 2.28.3 | | blockScroll | `boolean` | `true` | 是否在打开时禁用 body 滚动 | 2.28.3 | | bordered | `boolean` | `false` | 是否显示 `border` | | @@ -76,8 +76,9 @@ rtl-debug.vue | closable | `boolean` | `true` | 是否显示 `close` 图标 | | | closeOnEsc | `boolean` | `true` | 是否在摁下 Esc 键的时候关闭对话框 | 2.26.4 | | content | `string \| (() => VNodeChild)` | `undefined` | 对话框内容,可以是渲染函数 | | -| contentClass | `string` | 内容的类名 | 2.38.2 | -| contentStyle | `Object \| string` | 内容的样式 | 2.38.2 | +| contentClass | `string` | `undefined` | 内容的类名 | 2.38.2 | +| contentStyle | `Object \| string` | `undefined` | 内容的样式 | 2.38.2 | +| draggable | `boolean \| { bounds?: 'none' }` | `false` | 是否可拖拽 | NEXT_VERSION | | iconPlacement | `'left' \| 'top'` | `'left'` | 图标的位置 | | | icon | `() => VNodeChild` | `undefined` | 对话框 `icon`, 需要是渲染函数 | | | loading | `boolean` | `false` | 是否显示 `loading` 状态 | | @@ -89,8 +90,8 @@ rtl-debug.vue | showIcon | `boolean` | `true` | 是否显示 `icon` | | | style | `string \| Object` | `undefined` | 样式 | | | title | `string \| (() => VNodeChild)` | `undefined` | 标题,可以是渲染函数 | | -| titleClass | `string` | 标题的类名 | 2.38.2 | -| titleStyle | `Object \| string` | 标题的样式 | 2.38.2 | +| titleClass | `string` | `undefined` | 标题的类名 | 2.38.2 | +| titleStyle | `Object \| string` | `undefined` | 标题的样式 | 2.38.2 | | transformOrigin | `'mouse' \| 'center'` | `'mouse'` | 对话框动画出现的位置 | 2.34.0 | | type | `'error \| 'success' \| 'warning'` | `'warning'` | 对话框类型 | | | onAfterEnter | `() => void` | `undefined` | 出现动画完成执行的回调 | 2.33.0 | diff --git a/src/dialog/index.ts b/src/dialog/index.ts index a9acb1cbe92..4354d727557 100644 --- a/src/dialog/index.ts +++ b/src/dialog/index.ts @@ -1,5 +1,6 @@ export { useDialog, useDialogReactiveList } from './src/composables' export { NDialog } from './src/Dialog' +export type { DialogSlots } from './src/Dialog' export { dialogProps } from './src/dialogProps' export type { DialogProps } from './src/dialogProps' export { dialogProviderProps, NDialogProvider } from './src/DialogProvider' diff --git a/src/dialog/src/Dialog.tsx b/src/dialog/src/Dialog.tsx index 0a0e12ee9bc..50d61cbb16e 100644 --- a/src/dialog/src/Dialog.tsx +++ b/src/dialog/src/Dialog.tsx @@ -1,7 +1,14 @@ import type { ThemeProps } from '../../_mixins' import type { DialogTheme } from '../styles' import { getMargin } from 'seemly' -import { computed, type CSSProperties, defineComponent, h } from 'vue' +import { + computed, + type CSSProperties, + defineComponent, + h, + type SlotsType, + type VNode +} from 'vue' import { NBaseClose, NBaseIcon } from '../../_internal' import { ErrorIcon, @@ -29,6 +36,14 @@ const iconRenderMap = { error: () => } +export interface DialogSlots { + action?: () => VNode[] + default?: () => VNode[] + header?: () => VNode[] + icon?: () => VNode[] + close?: () => VNode[] +} + export const NDialog = defineComponent({ name: 'Dialog', alias: [ @@ -39,6 +54,7 @@ export const NDialog = defineComponent({ ...(useTheme.props as ThemeProps), ...dialogProps }, + slots: Object as SlotsType, setup(props) { const { mergedComponentPropsRef, @@ -146,11 +162,11 @@ export const NDialog = defineComponent({ }) const themeClassHandle = inlineThemeDisabled ? useThemeClass( - 'dialog', - computed(() => `${props.type[0]}${mergedIconPlacementRef.value[0]}`), - cssVarsRef, - props - ) + 'dialog', + computed(() => `${props.type[0]}${mergedIconPlacementRef.value[0]}`), + cssVarsRef, + props + ) : undefined return { mergedClsPrefix: mergedClsPrefixRef, @@ -265,20 +281,20 @@ export const NDialog = defineComponent({ > {closable ? resolveWrappedSlot(this.$slots.close, (node) => { - const classNames = [ - `${mergedClsPrefix}-dialog__close`, - this.rtlEnabled && `${mergedClsPrefix}-dialog--rtl` - ] - return node ? ( -
{node}
- ) : ( - - ) - }) + const classNames = [ + `${mergedClsPrefix}-dialog__close`, + this.rtlEnabled && `${mergedClsPrefix}-dialog--rtl` + ] + return node ? ( +
{node}
+ ) : ( + + ) + }) : null} {showIcon && mergedIconPlacement === 'top' ? (
{icon}
diff --git a/src/dialog/src/DialogEnvironment.tsx b/src/dialog/src/DialogEnvironment.tsx index 812ad5a392b..6d049fb27f4 100644 --- a/src/dialog/src/DialogEnvironment.tsx +++ b/src/dialog/src/DialogEnvironment.tsx @@ -1,6 +1,14 @@ +import type { ModalDraggableOptions } from '../../modal/src/interface' // use absolute path to make sure no circular ref of style // this -> modal-index -> modal-style -import { type CSSProperties, defineComponent, h, type PropType, ref } from 'vue' +import { + type CSSProperties, + defineComponent, + h, + normalizeClass, + type PropType, + ref +} from 'vue' import { keep } from '../../_utils' import NModal from '../../modal/src/Modal' import { NDialog } from './Dialog' @@ -30,7 +38,8 @@ export const exposedDialogEnvProps = { (e: MouseEvent) => Promise | unknown >, onClose: Function as PropType<() => Promise | unknown>, - onMaskClick: Function as PropType<(e: MouseEvent) => void> + onMaskClick: Function as PropType<(e: MouseEvent) => void>, + draggable: [Boolean, Object] as PropType } as const export const NDialogEnvironment = defineComponent({ @@ -156,13 +165,15 @@ export const NDialogEnvironment = defineComponent({ blockScroll={this.blockScroll} autoFocus={this.autoFocus} transformOrigin={this.transformOrigin} + draggable={this.draggable} internalAppear internalDialog > {{ - default: () => ( + default: ({ draggableClass }: { draggableClass: string }) => ( -