diff --git a/src/platform/assets/composables/useAssetBrowserDialog.stories.ts b/src/platform/assets/composables/useAssetBrowserDialog.stories.ts
index e0095b6196..aa23fa583d 100644
--- a/src/platform/assets/composables/useAssetBrowserDialog.stories.ts
+++ b/src/platform/assets/composables/useAssetBrowserDialog.stories.ts
@@ -175,11 +175,17 @@ export default {
onAssetSelected: (assetPath) => {
console.log('Selected:', assetPath)
// Update your component state
+ // Dialog auto-closes with animation via animateHide()
}
})
}
- return { openBrowser }
+ // Manual close with animation (if needed)
+ const closeBrowser = () => {
+ assetBrowserDialog.hide() // Triggers animateHide() internally
+ }
+
+ return { openBrowser, closeBrowser }
}
}
@@ -188,6 +194,12 @@ export default {
💡 Try it: Use the interactive buttons above to see this code in action!
+
+
+ ✨ Animation: The close button now uses animateHide() for smooth transitions,
+ just like pressing ESC. Both auto-close on selection and manual close trigger proper animations.
+
+
`
diff --git a/src/platform/assets/composables/useAssetBrowserDialog.ts b/src/platform/assets/composables/useAssetBrowserDialog.ts
index cc2a217a80..f76998546f 100644
--- a/src/platform/assets/composables/useAssetBrowserDialog.ts
+++ b/src/platform/assets/composables/useAssetBrowserDialog.ts
@@ -20,15 +20,21 @@ interface AssetBrowserDialogProps {
export const useAssetBrowserDialog = () => {
const dialogStore = useDialogStore()
const dialogKey = 'global-asset-browser'
+ let onHideComplete: (() => void) | null = null
- function hide() {
- dialogStore.closeDialog({ key: dialogKey })
+ function hide(): Promise {
+ return new Promise((resolve) => {
+ onHideComplete = resolve
+ dialogStore.animateHide({ key: dialogKey })
+ })
}
async function show(props: AssetBrowserDialogProps) {
- const handleAssetSelected = (assetPath: string) => {
- hide() // Auto-close on selection before async operations
+ const handleAssetSelected = async (assetPath: string) => {
+ // Update the widget value immediately - don't wait for animation
props.onAssetSelected?.(assetPath)
+ // Then trigger the hide animation
+ await hide()
}
// Default dialog configuration for AssetBrowserModal
@@ -36,6 +42,13 @@ export const useAssetBrowserDialog = () => {
headless: true,
modal: true,
closable: true,
+ onAfterHide: () => {
+ // Resolve the hide() promise when animation completes
+ if (!onHideComplete) return
+
+ onHideComplete()
+ onHideComplete = null
+ },
pt: {
root: {
class: 'rounded-2xl overflow-hidden'
diff --git a/src/stores/dialogStore.ts b/src/stores/dialogStore.ts
index f74e44c7d2..754fb67ad2 100644
--- a/src/stores/dialogStore.ts
+++ b/src/stores/dialogStore.ts
@@ -104,6 +104,39 @@ export const useDialogStore = defineStore('dialog', () => {
}
}
+ /**
+ * Triggers the dialog hide animation without immediately removing from stack.
+ * This is the preferred way to hide dialogs as it provides smooth visual transitions.
+ *
+ * Flow: animateHide() → PrimeVue animation → PrimeVue calls onAfterHide → closeDialog()
+ *
+ * Use this when:
+ * - User clicks close button
+ * - Programmatically hiding a dialog
+ * - You want the same smooth animation as ESC key
+ */
+ function animateHide(options?: { key: string }) {
+ const targetDialog = options
+ ? dialogStack.value.find((d) => d.key === options.key)
+ : dialogStack.value.find((d) => d.key === activeKey.value)
+ if (!targetDialog) return
+
+ // Set visible to false to trigger PrimeVue's close animation
+ // PrimeVue will call onAfterHide when animation completes, which calls closeDialog()
+ targetDialog.visible = false
+ }
+
+ /**
+ * Immediately removes dialog from stack without animation.
+ * This is called internally after animations complete.
+ *
+ * Use this when:
+ * - Called from onAfterHide callback (PrimeVue animation already done)
+ * - Force-closing without animation (rare)
+ * - Cleaning up dialog state
+ *
+ * For user-initiated closes, prefer animateClose() instead.
+ */
function closeDialog(options?: { key: string }) {
const targetDialog = options
? dialogStack.value.find((d) => d.key === options.key)
@@ -246,6 +279,7 @@ export const useDialogStore = defineStore('dialog', () => {
dialogStack,
riseDialog,
showDialog,
+ animateHide,
closeDialog,
showExtensionDialog,
isDialogOpen,
diff --git a/tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts b/tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts
index 06bbbd71a7..e947fdc3cd 100644
--- a/tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts
+++ b/tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts
@@ -6,6 +6,13 @@ import { useDialogStore } from '@/stores/dialogStore'
// Mock the dialog store
vi.mock('@/stores/dialogStore')
+// Mock the asset service
+vi.mock('@/platform/assets/services/assetService', () => ({
+ assetService: {
+ getAssetsForNodeType: vi.fn().mockResolvedValue([])
+ }
+}))
+
// Test factory functions
interface AssetBrowserProps {
nodeType: string
@@ -25,14 +32,14 @@ function createAssetBrowserProps(
describe('useAssetBrowserDialog', () => {
describe('Asset Selection Flow', () => {
- it('auto-closes dialog when asset is selected', () => {
+ it('auto-closes dialog when asset is selected', async () => {
// Create fresh mocks for this test
const mockShowDialog = vi.fn()
- const mockCloseDialog = vi.fn()
+ const mockAnimateHide = vi.fn()
vi.mocked(useDialogStore).mockReturnValue({
showDialog: mockShowDialog,
- closeDialog: mockCloseDialog
+ animateHide: mockAnimateHide
} as Partial> as ReturnType<
typeof useDialogStore
>)
@@ -41,7 +48,7 @@ describe('useAssetBrowserDialog', () => {
const onAssetSelected = vi.fn()
const props = createAssetBrowserProps({ onAssetSelected })
- assetBrowserDialog.show(props)
+ await assetBrowserDialog.show(props)
// Get the onSelect handler that was passed to the dialog
const dialogCall = mockShowDialog.mock.calls[0][0]
@@ -50,21 +57,21 @@ describe('useAssetBrowserDialog', () => {
// Simulate asset selection
onSelectHandler('selected-asset-path')
- // Should call the original callback and close dialog
+ // Should call the original callback and trigger hide animation
expect(onAssetSelected).toHaveBeenCalledWith('selected-asset-path')
- expect(mockCloseDialog).toHaveBeenCalledWith({
+ expect(mockAnimateHide).toHaveBeenCalledWith({
key: 'global-asset-browser'
})
})
- it('closes dialog when close handler is called', () => {
+ it('closes dialog when close handler is called', async () => {
// Create fresh mocks for this test
const mockShowDialog = vi.fn()
- const mockCloseDialog = vi.fn()
+ const mockAnimateHide = vi.fn()
vi.mocked(useDialogStore).mockReturnValue({
showDialog: mockShowDialog,
- closeDialog: mockCloseDialog
+ animateHide: mockAnimateHide
} as Partial> as ReturnType<
typeof useDialogStore
>)
@@ -72,7 +79,7 @@ describe('useAssetBrowserDialog', () => {
const assetBrowserDialog = useAssetBrowserDialog()
const props = createAssetBrowserProps()
- assetBrowserDialog.show(props)
+ await assetBrowserDialog.show(props)
// Get the onClose handler that was passed to the dialog
const dialogCall = mockShowDialog.mock.calls[0][0]
@@ -81,10 +88,9 @@ describe('useAssetBrowserDialog', () => {
// Simulate dialog close
onCloseHandler()
- expect(mockCloseDialog).toHaveBeenCalledWith({
+ expect(mockAnimateHide).toHaveBeenCalledWith({
key: 'global-asset-browser'
})
})
-
})
})
diff --git a/tests-ui/tests/store/dialogStore.test.ts b/tests-ui/tests/store/dialogStore.test.ts
index 3d21ff695b..64260822c9 100644
--- a/tests-ui/tests/store/dialogStore.test.ts
+++ b/tests-ui/tests/store/dialogStore.test.ts
@@ -146,6 +146,21 @@ describe('dialogStore', () => {
expect(store.isDialogOpen('test-dialog')).toBe(false)
})
+ it('should hide dialog by setting visible to false', () => {
+ const store = useDialogStore()
+ store.showDialog({ key: 'test-dialog', component: MockComponent })
+
+ const dialog = store.dialogStack[0]
+ expect(dialog.visible).toBe(true)
+
+ store.animateHide({ key: 'test-dialog' })
+
+ // Dialog should be hidden but still in stack
+ expect(dialog.visible).toBe(false)
+ expect(store.dialogStack).toHaveLength(1)
+ expect(store.isDialogOpen('test-dialog')).toBe(true)
+ })
+
it('should reuse existing dialog when showing with same key', () => {
const store = useDialogStore()