From 2605dff50dd91338ef8ceaf44cd82c70d3428770 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 29 May 2026 15:52:52 +0200 Subject: [PATCH] feat(Modal): support unmountOnHide prop Forward the `unmountOnHide` prop (inherited from `DialogRootProps`) to the underlying `DialogRoot` in Modal and Slideover, and proxy it through ContentSearch and DashboardSearch. When set to `false`, the content stays in the DOM while closed (hidden via `display: none`) instead of being unmounted, avoiding remounts on every open. Defaults to `true` to keep the existing behavior. --- docs/app/components/search/Search.vue | 1 + docs/content/docs/2.components/modal.md | 32 +++++++++++++++++++ docs/content/docs/2.components/slideover.md | 32 +++++++++++++++++++ package.json | 2 +- pnpm-lock.yaml | 28 ++++++++-------- src/runtime/components/DashboardSearch.vue | 4 +-- src/runtime/components/Modal.vue | 5 +-- src/runtime/components/Slideover.vue | 5 +-- .../components/content/ContentSearch.vue | 4 +-- test/components/Modal.spec.ts | 18 +++++++++++ test/components/Slideover.spec.ts | 18 +++++++++++ 11 files changed, 126 insertions(+), 23 deletions(-) diff --git a/docs/app/components/search/Search.vue b/docs/app/components/search/Search.vue index 70ad030987..2e00119d2e 100644 --- a/docs/app/components/search/Search.vue +++ b/docs/app/components/search/Search.vue @@ -45,5 +45,6 @@ watchDebounced(searchTerm, (term) => { :search-status="status" :fuse="fuse" :transition="false" + :unmount-on-hide="false" /> diff --git a/docs/content/docs/2.components/modal.md b/docs/content/docs/2.components/modal.md index 70d424c374..ba9f8a1afd 100644 --- a/docs/content/docs/2.components/modal.md +++ b/docs/content/docs/2.components/modal.md @@ -294,6 +294,38 @@ slots: :placeholder{class="h-48"} :: +### Unmount :badge{label="Soon" class="align-text-top"} + +Use the `unmount-on-hide` prop to control whether the Modal is unmounted from the DOM when closed. Defaults to `true`. + +::note +When set to `false`, the Modal's content stays in the DOM when closed (hidden with `display: none`) instead of being unmounted. This can be useful for SEO and performance by avoiding remounts on every open. +:: + +::component-code +--- +prettier: true +ignore: + - title +props: + unmountOnHide: false + title: 'Modal mounted on hide' +slots: + default: | + + + + body: | + + +--- + +:u-button{label="Open" color="neutral" variant="subtle"} + +#body +:placeholder{class="h-48"} +:: + ### Scrollable :badge{label="4.2+" class="align-text-top"} Use the `scrollable` prop to make the Modal's content scrollable within the overlay. diff --git a/docs/content/docs/2.components/slideover.md b/docs/content/docs/2.components/slideover.md index 8a40211e9d..fd141cd629 100644 --- a/docs/content/docs/2.components/slideover.md +++ b/docs/content/docs/2.components/slideover.md @@ -351,6 +351,38 @@ slots: :placeholder{class="h-full"} :: +### Unmount :badge{label="Soon" class="align-text-top"} + +Use the `unmount-on-hide` prop to control whether the Slideover is unmounted from the DOM when closed. Defaults to `true`. + +::note +When set to `false`, the Slideover's content stays in the DOM when closed (hidden with `display: none`) instead of being unmounted. This can be useful for SEO and performance by avoiding remounts on every open. +:: + +::component-code +--- +prettier: true +ignore: + - title +props: + unmountOnHide: false + title: 'Slideover mounted on hide' +slots: + default: | + + + + body: | + + +--- + +:u-button{label="Open" color="neutral" variant="subtle"} + +#body +:placeholder{class="h-full"} +:: + ## Examples ### Control open state diff --git a/package.json b/package.json index 6fc52125fc..4d59cc64ad 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "motion-v": "^2.2.1", "ohash": "^2.0.11", "pathe": "^2.0.3", - "reka-ui": "2.9.8", + "reka-ui": "https://pkg.pr.new/reka-ui@fe4f7b5", "scule": "^1.3.0", "tailwind-merge": "^3.6.0", "tailwind-variants": "^3.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 749dc85b4d..2c3ef97e37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,8 +185,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 reka-ui: - specifier: 2.9.8 - version: 2.9.8(vue@3.5.34(typescript@6.0.3)) + specifier: https://pkg.pr.new/reka-ui@fe4f7b5 + version: https://pkg.pr.new/reka-ui@fe4f7b5(vue@3.5.34(typescript@6.0.3)) scule: specifier: ^1.3.0 version: 1.3.0 @@ -225,7 +225,7 @@ importers: version: 1.4.1(typescript@6.0.3) vaul-vue: specifier: 0.4.1 - version: 0.4.1(reka-ui@2.9.8(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) + version: 0.4.1(reka-ui@https://pkg.pr.new/reka-ui@fe4f7b5(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) vue-component-type-helpers: specifier: ^3.3.0 version: 3.3.0 @@ -376,10 +376,10 @@ importers: version: 3.23.6(@tiptap/core@3.23.6(@tiptap/pm@3.23.6)) '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue-router@5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) + version: 2.0.1(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3)) '@vercel/speed-insights': specifier: ^2.0.0 - version: 2.0.0(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue-router@5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) + version: 2.0.0(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3)) '@vueuse/integrations': specifier: ^14.3.0 version: 14.3.0(change-case@5.4.4)(fuse.js@7.3.0)(sortablejs@1.15.7)(vue@3.5.34(typescript@6.0.3)) @@ -7648,8 +7648,9 @@ packages: rehype-sort-attributes@5.0.1: resolution: {integrity: sha512-Bxo+AKUIELcnnAZwJDt5zUDDRpt4uzhfz9d0PVGhcxYWsbFj5Cv35xuWxu5r1LeYNFNhgGqsr9Q2QiIOM/Qctg==} - reka-ui@2.9.8: - resolution: {integrity: sha512-7dxaBJ6nQ0zOQZXPV45219tTEgZPstmihBLS9ABPhSiPiJ8SiF0sacfZHFaBptS0v9N4tzsevq+8MNBpE4p5JQ==} + reka-ui@https://pkg.pr.new/reka-ui@fe4f7b5: + resolution: {integrity: sha512-uvArQIvmgxiShY1j+n/Q/WVPaxgZhjUWGjJzTKHHGMwbiaYovj4yc9FL2ATQ078HDL8bd/9NiOw61xDxPRhXeg==, tarball: https://pkg.pr.new/reka-ui@fe4f7b5} + version: 2.9.8 peerDependencies: vue: '>= 3.4.0' @@ -8560,6 +8561,7 @@ packages: vaul-vue@0.4.1: resolution: {integrity: sha512-A6jOWOZX5yvyo1qMn7IveoWN91mJI5L3BUKsIwkg6qrTGgHs1Sb1JF/vyLJgnbN1rH4OOOxFbtqL9A46bOyGUQ==} + version: 0.4.1 peerDependencies: reka-ui: ^2.0.0 vue: ^3.3.0 @@ -12104,11 +12106,10 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vercel/analytics@2.0.1(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue-router@5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))': + '@vercel/analytics@2.0.1(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))': optionalDependencies: nuxt: 4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0) vue: 3.5.34(typescript@6.0.3) - vue-router: 5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)) '@vercel/nft@1.5.0(rollup@4.60.4)': dependencies: @@ -12131,11 +12132,10 @@ snapshots: '@vercel/oidc@3.2.0': {} - '@vercel/speed-insights@2.0.0(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue-router@5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))': + '@vercel/speed-insights@2.0.0(nuxt@4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))': optionalDependencies: nuxt: 4.4.6(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.9.0)(@vue/compiler-sfc@3.5.34)(better-sqlite3@12.10.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.10.0))(eslint@10.4.0(jiti@2.7.0))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.3)(meow@13.2.0)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.4))(rollup@4.60.4)(srvx@0.11.15)(terser@5.47.1)(typescript@6.0.3)(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue-tsc@3.3.0(typescript@6.0.3))(yaml@2.9.0) vue: 3.5.34(typescript@6.0.3) - vue-router: 5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)) '@vitejs/plugin-vue-jsx@5.1.5(vite@7.3.3(@types/node@25.9.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))': dependencies: @@ -16870,7 +16870,7 @@ snapshots: '@types/hast': 3.0.4 unist-util-visit: 5.1.0 - reka-ui@2.9.8(vue@3.5.34(typescript@6.0.3)): + reka-ui@https://pkg.pr.new/reka-ui@fe4f7b5(vue@3.5.34(typescript@6.0.3)): dependencies: '@floating-ui/dom': 1.7.6 '@floating-ui/vue': 1.1.11(vue@3.5.34(typescript@6.0.3)) @@ -18006,10 +18006,10 @@ snapshots: vary@1.1.2: {} - vaul-vue@0.4.1(reka-ui@2.9.8(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)): + vaul-vue@0.4.1(reka-ui@https://pkg.pr.new/reka-ui@fe4f7b5(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)): dependencies: '@vueuse/core': 10.11.1(vue@3.5.34(typescript@6.0.3)) - reka-ui: 2.9.8(vue@3.5.34(typescript@6.0.3)) + reka-ui: https://pkg.pr.new/reka-ui@fe4f7b5(vue@3.5.34(typescript@6.0.3)) vue: 3.5.34(typescript@6.0.3) transitivePeerDependencies: - '@vue/composition-api' diff --git a/src/runtime/components/DashboardSearch.vue b/src/runtime/components/DashboardSearch.vue index 61ddc9ce70..d3250abb6c 100644 --- a/src/runtime/components/DashboardSearch.vue +++ b/src/runtime/components/DashboardSearch.vue @@ -9,7 +9,7 @@ import type { ComponentConfig } from '../types/tv' type DashboardSearch = ComponentConfig -export interface DashboardSearchProps extends Pick, Pick, T>, 'icon' | 'trailingIcon' | 'selectedIcon' | 'childrenIcon' | 'placeholder' | 'autofocus' | 'loading' | 'loadingIcon' | 'closeIcon' | 'back' | 'backIcon' | 'disabled' | 'highlightOnHover' | 'labelKey' | 'descriptionKey' | 'preserveGroupOrder' | 'virtualize' | 'groups'> { +export interface DashboardSearchProps extends Pick, Pick, T>, 'icon' | 'trailingIcon' | 'selectedIcon' | 'childrenIcon' | 'placeholder' | 'autofocus' | 'loading' | 'loadingIcon' | 'closeIcon' | 'back' | 'backIcon' | 'disabled' | 'highlightOnHover' | 'labelKey' | 'descriptionKey' | 'preserveGroupOrder' | 'virtualize' | 'groups'> { /** * @defaultValue 'md' */ @@ -99,7 +99,7 @@ const colorMode = useColorMode() const appConfig = useAppConfig() as DashboardSearch['AppConfig'] const commandPaletteProps = useForwardProps(reactivePick(props, 'size', 'icon', 'trailingIcon', 'selectedIcon', 'childrenIcon', 'placeholder', 'autofocus', 'loading', 'loadingIcon', 'close', 'closeIcon', 'back', 'backIcon', 'disabled', 'highlightOnHover', 'labelKey', 'descriptionKey', 'preserveGroupOrder', 'virtualize', 'searchDelay')) -const modalProps = useForwardProps(reactivePick(props, 'overlay', 'transition', 'content', 'dismissible', 'fullscreen', 'modal', 'portal')) +const modalProps = useForwardProps(reactivePick(props, 'overlay', 'transition', 'content', 'dismissible', 'fullscreen', 'modal', 'portal', 'unmountOnHide')) const getProxySlots = () => omit(slots, ['content']) diff --git a/src/runtime/components/Modal.vue b/src/runtime/components/Modal.vue index f5cfb5fd55..2c1735722e 100644 --- a/src/runtime/components/Modal.vue +++ b/src/runtime/components/Modal.vue @@ -99,7 +99,8 @@ const _props = withDefaults(defineProps(), { overlay: true, transition: true, modal: true, - dismissible: true + dismissible: true, + unmountOnHide: true }) const emits = defineEmits() const slots = defineSlots() @@ -109,7 +110,7 @@ const props = useComponentProps('modal', _props) const { t } = useLocale() const appConfig = useAppConfig() as Modal['AppConfig'] -const rootProps = useForwardProps(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits) +const rootProps = useForwardProps(reactivePick(props, 'open', 'defaultOpen', 'modal', 'unmountOnHide'), emits) const portalProps = usePortal(toRef(() => props.portal)) const contentProps = toRef(() => props.content) const contentEvents = computed(() => { diff --git a/src/runtime/components/Slideover.vue b/src/runtime/components/Slideover.vue index 0af81c4d37..2672da2ff8 100644 --- a/src/runtime/components/Slideover.vue +++ b/src/runtime/components/Slideover.vue @@ -100,7 +100,8 @@ const _props = withDefaults(defineProps(), { transition: true, modal: true, dismissible: true, - side: 'right' + side: 'right', + unmountOnHide: true }) const emits = defineEmits() const slots = defineSlots() @@ -110,7 +111,7 @@ const props = useComponentProps('slideover', _props) const { t } = useLocale() const appConfig = useAppConfig() as Slideover['AppConfig'] -const rootProps = useForwardProps(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits) +const rootProps = useForwardProps(reactivePick(props, 'open', 'defaultOpen', 'modal', 'unmountOnHide'), emits) const portalProps = usePortal(toRef(() => props.portal)) const contentProps = toRef(() => props.content) const contentEvents = computed(() => { diff --git a/src/runtime/components/content/ContentSearch.vue b/src/runtime/components/content/ContentSearch.vue index dcf8bb091a..a8a2033294 100644 --- a/src/runtime/components/content/ContentSearch.vue +++ b/src/runtime/components/content/ContentSearch.vue @@ -55,7 +55,7 @@ export interface ContentSearchItem extends Omit, CommandPal icon?: IconProps['name'] } -export interface ContentSearchProps extends Pick, Pick, ContentSearchItem>, 'icon' | 'trailingIcon' | 'selectedIcon' | 'childrenIcon' | 'placeholder' | 'autofocus' | 'loading' | 'loadingIcon' | 'closeIcon' | 'back' | 'backIcon' | 'disabled' | 'highlightOnHover' | 'labelKey' | 'descriptionKey' | 'preserveGroupOrder' | 'virtualize' | 'groups'> { +export interface ContentSearchProps extends Pick, Pick, ContentSearchItem>, 'icon' | 'trailingIcon' | 'selectedIcon' | 'childrenIcon' | 'placeholder' | 'autofocus' | 'loading' | 'loadingIcon' | 'closeIcon' | 'back' | 'backIcon' | 'disabled' | 'highlightOnHover' | 'labelKey' | 'descriptionKey' | 'preserveGroupOrder' | 'virtualize' | 'groups'> { /** * @defaultValue 'md' */ @@ -158,7 +158,7 @@ const colorMode = useColorMode() const appConfig = useAppConfig() as ContentSearch['AppConfig'] const commandPaletteProps = useForwardProps(reactivePick(props, 'size', 'icon', 'trailingIcon', 'selectedIcon', 'childrenIcon', 'placeholder', 'autofocus', 'loading', 'loadingIcon', 'close', 'closeIcon', 'back', 'backIcon', 'disabled', 'highlightOnHover', 'labelKey', 'descriptionKey', 'preserveGroupOrder', 'virtualize', 'searchDelay')) -const modalProps = useForwardProps(reactivePick(props, 'overlay', 'transition', 'content', 'dismissible', 'fullscreen', 'modal', 'portal')) +const modalProps = useForwardProps(reactivePick(props, 'overlay', 'transition', 'content', 'dismissible', 'fullscreen', 'modal', 'portal', 'unmountOnHide')) const getProxySlots = () => omit(slots, ['content']) diff --git a/test/components/Modal.spec.ts b/test/components/Modal.spec.ts index 939adef4e2..4a7f94986f 100644 --- a/test/components/Modal.spec.ts +++ b/test/components/Modal.spec.ts @@ -46,4 +46,22 @@ describe('Modal', () => { expect(await axe(wrapper.element)).toHaveNoViolations() }) + + it('unmounts content when closed by default', async () => { + const wrapper = await mountSuspended(Modal, { + props: { open: false, portal: false }, + slots: { body: () => 'Body content' } + }) + + expect(wrapper.text()).not.toContain('Body content') + }) + + it('keeps content mounted when closed with unmountOnHide false', async () => { + const wrapper = await mountSuspended(Modal, { + props: { open: false, portal: false, unmountOnHide: false }, + slots: { body: () => 'Body content' } + }) + + expect(wrapper.text()).toContain('Body content') + }) }) diff --git a/test/components/Slideover.spec.ts b/test/components/Slideover.spec.ts index de1233a608..506babdd43 100644 --- a/test/components/Slideover.spec.ts +++ b/test/components/Slideover.spec.ts @@ -46,4 +46,22 @@ describe('Slideover', () => { expect(await axe(wrapper.element)).toHaveNoViolations() }) + + it('unmounts content when closed by default', async () => { + const wrapper = await mountSuspended(Slideover, { + props: { open: false, portal: false }, + slots: { body: () => 'Body content' } + }) + + expect(wrapper.text()).not.toContain('Body content') + }) + + it('keeps content mounted when closed with unmountOnHide false', async () => { + const wrapper = await mountSuspended(Slideover, { + props: { open: false, portal: false, unmountOnHide: false }, + slots: { body: () => 'Body content' } + }) + + expect(wrapper.text()).toContain('Body content') + }) })