Skip to content

Conversation

@edimitchel
Copy link

@edimitchel edimitchel commented Oct 15, 2025

πŸ”— Linked issue

Resolved #5233

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

Introducing this new on method permits to easy bind emits event before opening for let developer to only think about props in create/open/patch methods.

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@github-actions github-actions bot added the v4 #4488 label Oct 15, 2025
@edimitchel edimitchel changed the title refactor: implement event handler system for overlay components with … refactor: implement event handler system for overlay components Oct 15, 2025
@edimitchel edimitchel changed the title refactor: implement event handler system for overlay components feat(useOverlay): add on method to listen emits from component Oct 15, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 15, 2025

npm i https://pkg.pr.new/@nuxt/ui@5244

commit: afa5452

@edimitchel
Copy link
Author

edimitchel commented Oct 16, 2025

I forget the API instance documentation + Caveats section miss a warning about the limitation of the composable.

We need to discuss about its limitation is caused by typescript limitations

@genu genu self-requested a review October 19, 2025 14:53
const overlay = getOverlay(id)

if (!overlay.emits) overlay.emits = {}
overlay.emits[event as string] = callback
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
overlay.emits[event as string] = callback
// For close events, preserve the default close behavior by chaining callbacks
if (event === 'close') {
const existingHandler = overlay.emits[event as string]
if (existingHandler) {
overlay.emits[event as string] = (...args: Parameters<typeof callback>) => {
callback(...args)
existingHandler(...args)
}
} else {
overlay.emits[event as string] = callback
}
} else {
overlay.emits[event as string] = callback
}

The on() method replaces the default close event handler instead of preserving it, which breaks automatic overlay closure when registering close event listeners.

View Details

Analysis

useOverlay.on() replaces default close handler breaking automatic overlay closure

What fails: useOverlay.on('close', callback) in src/runtime/composables/useOverlay.ts:214 replaces the default close handler instead of chaining it, preventing automatic overlay closure when user callbacks don't explicitly call modal.close()

How to reproduce:

const { create } = useOverlay()
const modal = create(Component)
modal.open()

// Register close handler that doesn't call modal.close()
modal.on('close', (value) => {
  console.log('Close event received:', value)
  // No modal.close() call here
})

// When component emits close event, overlay stays open
component.$emit('close', 'result') // Overlay remains open

Result: Overlay stays open because default (value) => close(id, value) handler is replaced by user callback

Expected: Overlay should close automatically even when user callback doesn't call modal.close(), preserving the default close behavior established in create() at line 118

Root cause: Line 214 uses direct assignment overlay.emits[event as string] = callback instead of chaining handlers for close events

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@genu what about this constructive feedback?

Copy link
Member

@genu genu Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I see the issue -- this is valid.

The close event is a special kind of event that also should actually hide the modal.

In the original design (and briefly explained in the docs), the close event is used to call close(id, value).

This is the key issue with the close event:

There are two ways to close an overlay:
1. The component emits a close event.
2. The user clicks the X button which hides the modal

In both cases, we need to:
- Handle the event using the .on(...) callback (Missing this piece for option 2)
- Handle the event using the resolvedPromise

In the original design, we relied on the resolvePromise to handle both cases.

In this new event handling with the .on(...) system, we are treating the close event like any other component event. For close specifically, we need to also account for the second way of close an overlay.

Let me know if you need more clarity on this. I'm sure there is room to refactor some things to make it cleaner in this area, this might be a good opportunity for it.

For example,

  • Should we implement an internal close event handler and fire and handle it automatically in both scenarios in the onAfterLeave?. Then, treat any .on('close', ...) and const res = await modal.open(...) as a user event that gets called automatically.
  • Should we expand our built-in events for more granularity, like "close", "beforeClose", "afterClose". We may not do this now, but we can think about this if we do some refactoring.

Let me know what you think.

I can spend some more time this week to give you further feedback or help out with this PR.

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
@edimitchel
Copy link
Author

@genu can you take a look?

@genu
Copy link
Member

genu commented Nov 3, 2025

Thanks for this @edimitchel I think it looks good. The on syntax makes better sense and its cleaner.

I think it may be even a good idea to deprecate the old way of handling events. Having multiple ways could be confusing.

What do you think about that?

We could maybe, add a deprecated message for now, if possible, when users try to handle events like this:

const modal = overlay.create(LazyModalExample, {
  props: {
    count: count.value,
    onClose: (value: number) => {
      console.log('Modal closed with value:', value)
    }
  }
})

Otherwise, we could depreciate them in a future version as a breaking change

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useOverlay: bind emits on overlay instances

2 participants