Skip to content

fix(popconfirm): restore compatibility and popup rendering#8525

Open
xjccc wants to merge 10 commits intovueComponent:feat/vaporfrom
xjccc:review/popconfirm
Open

fix(popconfirm): restore compatibility and popup rendering#8525
xjccc wants to merge 10 commits intovueComponent:feat/vaporfrom
xjccc:review/popconfirm

Conversation

@xjccc
Copy link
Copy Markdown

@xjccc xjccc commented Apr 3, 2026

Summary

  • Restore Popconfirm compatibility with previous behavior, including legacy prop and slot precedence, callback compatibility, async confirm loading, and Escape-to-close handling.
  • Fix playground preview parsing for script setup blocks so Popconfirm demos render slot content correctly.
  • Switch shared popup positioning from transform-based placement to left/top positioning to avoid jumpy placement behavior, and restore the legacy-style arrow structure and styling.
  • Add regression coverage for Popconfirm behavior, demo rendering, and tooltip popup positioning.

Testing

  • npm exec vitest run index.test.ts demo.test.ts index.test.ts

Related: #8497

restore Popconfirm legacy prop and slot precedence, callback compatibility, async confirm loading, and ESC close behavior

fix playground script setup parsing for preview demos

switch shared popup positioning to left/top and restore legacy-style arrow rendering

add regression coverage for popconfirm behavior, demos, and tooltip popup positioning

Co-Authored-By: GitHub Copilot <noreply@github.com>
Copilot AI review requested due to automatic review settings April 3, 2026 08:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR restores legacy-compatible Popconfirm behavior and fixes popup rendering/positioning regressions across Trigger-based overlays, with updated styling and regression tests to prevent reintroductions.

Changes:

  • Reworks Trigger popup positioning to use left/top (no transform) and restores a legacy-style arrow DOM/CSS structure.
  • Restores Popconfirm compatibility behaviors (content precedence, async confirm loading, Escape-to-close) and expands type/test coverage.
  • Fixes the playground SFC parser to better detect <script setup> blocks so demos render correctly.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/ui/src/_internal/trigger/Trigger.vue Switches Floating UI positioning to non-transform styles and updates arrow DOM.
packages/ui/src/_internal/trigger/style/index.css Rebuilds arrow styling; introduces --ant-trigger-arrow-background variable-driven fill.
packages/ui/src/components/tooltip/style/index.css Updates tooltip arrow styling to use the new trigger arrow background variable.
packages/ui/src/components/popover/style/index.css Updates popover arrow styling to use the new trigger arrow background variable.
packages/ui/src/components/popconfirm/style/index.css Updates popconfirm arrow styling to use the new trigger arrow background variable.
packages/ui/src/components/tooltip/tests/index.test.ts Adds regression test to ensure popup uses left/top instead of transform.
packages/ui/src/components/popconfirm/types.ts Adds callback prop types, slot prop types, and updates openChange/visibleChange event typing.
packages/ui/src/components/popconfirm/Popconfirm.vue Implements compatibility logic (content precedence, async confirm loading, Escape-to-close), updates button rendering and slot props.
packages/ui/src/components/popconfirm/tests/index.test.ts Expands Popconfirm regression coverage for rendering, handlers, async confirm loading, escape-to-close, and compatibility behaviors.
packages/ui/src/components/popconfirm/tests/snapshots/demo.test.ts.snap Updates demo snapshots to reflect Trigger/button rendering changes.
apps/playground/src/views/EditorView.vue Updates regex used to extract <script setup> blocks for demo compilation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +324 to +334
onClick: onCancel as (event: MouseEvent) => void,
size: 'small' as const,
...(props.cancelButtonProps ?? {}),
cancel: onCancel as (event: MouseEvent) => void,
}))

const okButtonSlotProps = computed(() => ({
onClick: onConfirm,
type: props.okType,
size: 'small' as const,
...(props.okButtonProps ?? {}),
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

cancelButtonSlotProps/okButtonSlotProps spread cancelButtonProps/okButtonProps after setting onClick, so a user-provided onClick inside those props will override the internal handler and the Popconfirm may no longer close / enter loading unless the slot implementation calls cancel/confirm manually. To keep the default behavior reliable, merge in the button props first and then set onClick last (or wrap both handlers so both run).

Suggested change
onClick: onCancel as (event: MouseEvent) => void,
size: 'small' as const,
...(props.cancelButtonProps ?? {}),
cancel: onCancel as (event: MouseEvent) => void,
}))
const okButtonSlotProps = computed(() => ({
onClick: onConfirm,
type: props.okType,
size: 'small' as const,
...(props.okButtonProps ?? {}),
size: 'small' as const,
...(props.cancelButtonProps ?? {}),
onClick: onCancel as (event: MouseEvent) => void,
cancel: onCancel as (event: MouseEvent) => void,
}))
const okButtonSlotProps = computed(() => ({
type: props.okType,
size: 'small' as const,
...(props.okButtonProps ?? {}),
onClick: onConfirm,

Copilot uses AI. Check for mistakes.

const scriptSetupMatch = source.match(
/<script\s+setup(?:\s+lang="ts")?\s*>([\s\S]*?)<\/script>/,
/<script\b(?=[^>]*\bsetup\b)[^>]*>([\s\S]*?)<\/script>/,
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The new <script setup> regex will also match any <script> tag whose attributes/values merely contain the word setup (e.g. src="setup.ts", data-setup="..."), which can cause the playground to mis-detect and miscompile non-setup script blocks. Consider tightening the pattern to specifically match the setup attribute name (optionally with =), rather than searching for a standalone setup token anywhere in the tag.

Suggested change
/<script\b(?=[^>]*\bsetup\b)[^>]*>([\s\S]*?)<\/script>/,
/<script\b(?=[^>]*\ssetup(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?(?=\s|>))[^>]*>([\s\S]*?)<\/script>/,

Copilot uses AI. Check for mistakes.
preserve internal confirm and cancel handlers when slot button props provide custom onClick callbacks

tighten playground script setup detection to only match the setup attribute

add regression coverage for custom slot buttons invoking default actions
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +101 to 115
it('does not warn for the default okType legacy mapping', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})

trackMount(mount(Popconfirm, {
attachTo: document.body,
props: { title: 'Are you sure?', open: true },
slots: { default: () => h('span', 'Delete') },
})
const buttons = wrapper.find('.ant-popconfirm-buttons')
expect(buttons.text()).toContain('OK')
expect(buttons.text()).toContain('Cancel')
}))

await flushPopup()

expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('Button: `type="primary"` is deprecated'),
)
})
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The console.warn spy created here is never restored. Since afterEach doesn’t call vi.restoreAllMocks() / warn.mockRestore(), this can leak into later tests and mask real warnings or change assertions. Restore the spy at the end of the test (or add a vi.restoreAllMocks() in afterEach).

Copilot uses AI. Check for mistakes.
restore mocked globals after each Popconfirm test case to avoid warning spy leakage between tests

Co-Authored-By: GitHub Copilot <noreply@github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 93 to 100
export interface PopconfirmEmits {
(e: 'update:open', open: boolean): void
(e: 'openChange', open: boolean): void
(e: 'confirm', event: MouseEvent): void
(e: 'cancel', event: MouseEvent): void
(e: 'openChange', open: boolean, event?: PopconfirmOpenChangeEvent): void
/** @deprecated */
(e: 'update:visible', open: boolean): void
/** @deprecated */
(e: 'visibleChange', open: boolean): void
(e: 'visibleChange', open: boolean, event?: PopconfirmOpenChangeEvent): void
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

PopconfirmEmits no longer declares confirm/cancel, but the codebase (e.g. demo/promise.vue) uses <a-popconfirm @confirm @cancel>. Removing these emits is a public typing/API regression: template type-checking will flag @confirm/@cancel as invalid, and consumers relying on event typing will break. Consider restoring confirm/cancel in the component’s declared events (or otherwise ensuring @confirm/@cancel remain type-safe), while still supporting Promise-returning handlers for async confirm loading.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +385 to +421
function invokeConfirmHandlers(e: MouseEvent) {
const handlers = getCompatHandlers(props.onConfirm, 'onConfirm')
const results = handlers.map(handler => handler(e))
return results.find(isThenable) ?? results.find(result => result !== undefined)
}

function invokeCancelHandlers(e: MouseEvent) {
const handlers = getCompatHandlers(props.onCancel, 'onCancel')
handlers.forEach(handler => handler(e))
}

function onConfirm(e: MouseEvent) {
emit('confirm', e)
setOpen(false)
if (confirmLoading.value) {
return
}

const result = invokeConfirmHandlers(e)
if (isThenable(result)) {
confirmLoading.value = true
result.then(
() => {
confirmLoading.value = false
setOpen(false)
},
() => {
confirmLoading.value = false
},
)
return
}

setOpen(false, e)
}

function onCancel(e: MouseEvent) {
invokeCancelHandlers(e)
setOpen(false, e)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

confirm / cancel are still declared in PopconfirmEmits, but the component no longer emits them. onConfirm/onCancel callbacks are invoked, and the popup closes, but emit('confirm', e) / emit('cancel', e) never run, which breaks consumers relying on emitted events (including Vue listener modifiers like @confirm.once / @cancel.once, which won't be picked up by the new onConfirm/onCancel prop handling). Consider emitting the events in onConfirm/onCancel (and keeping the callback invocation), or else removing the emits and updating typings/docs accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +280 to +315
it('calls confirm handler and closes popup', async () => {
const confirm = vi.fn()
const wrapper = trackMount(mount(Popconfirm, {
attachTo: document.body,
props: { title: 'Are you sure?', trigger: 'click' },
attrs: { onConfirm: confirm },
slots: { default: () => h('span', 'Delete') },
}))
await wrapper.find('.ant-trigger-wrapper').trigger('click')
await flushPopup()

getButtons()[1]?.click()
await flushPopup()

expect(confirm).toHaveBeenCalledTimes(1)
expect((wrapper.vm as { getPopupDomNode: () => HTMLElement | null }).getPopupDomNode()?.style.display).toBe('none')
})

it('emits cancel event', async () => {
const wrapper = mount(Popconfirm, {
it('calls cancel handler and closes popup', async () => {
const cancel = vi.fn()
const wrapper = trackMount(mount(Popconfirm, {
attachTo: document.body,
props: { title: 'Are you sure?', open: true },
attrs: { onCancel: cancel },
slots: { default: () => h('span', 'Delete') },
})
const buttons = wrapper.findAll('.ant-popconfirm-buttons .ant-btn')
const cancelBtn = buttons[0]
await cancelBtn.trigger('click')
expect(wrapper.emitted('cancel')).toBeTruthy()
}))

await flushPopup()
getButtons()[0]?.click()
await flushPopup()

expect(cancel).toHaveBeenCalledTimes(1)
expect(wrapper.emitted('openChange')?.at(-1)?.[0]).toBe(false)
expect(wrapper.emitted('openChange')?.at(-1)?.[1]).toBeInstanceOf(MouseEvent)
expect(wrapper.emitted('visibleChange')?.at(-1)?.[1]).toBeInstanceOf(MouseEvent)
})
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Current tests no longer assert that clicking OK/Cancel emits the confirm / cancel events (only callback props are verified). Since confirm/cancel are still part of the component's declared emits contract, it would be good to add regression assertions for wrapper.emitted('confirm') and wrapper.emitted('cancel') on button clicks (and optionally a .once listener case) to ensure event semantics remain supported alongside the new callback compatibility.

Copilot uses AI. Check for mistakes.
emit confirm and cancel events again without double-invoking callback props

add regression coverage for emitted events and once-modified listeners

Co-Authored-By: GitHub Copilot <noreply@github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 224 to 231
const isUserControlled = computed(() => {
const rawProps = instance.vnode.props || {}
return 'open' in rawProps || 'visible' in rawProps
return hasExplicitProp('open') || hasExplicitProp('visible')
})

const mergedOpen = computed(() => {
if (isUserControlled.value) {
return props.open ?? props.visible ?? false
}
if (hasExplicitProp('open')) return props.open ?? false
if (hasExplicitProp('visible')) return props.visible ?? false
return internalOpen.value
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

open is documented as “null = uncontrolled”, but the current controlled-mode detection treats the prop as controlled whenever the key exists. If a consumer passes :open="null", isUserControlled becomes true and mergedOpen always resolves to false, preventing the Popconfirm from ever opening. Consider treating open === null as “not explicitly controlled” (e.g., require props.open !== null when deciding controlled mode / when reading the controlled value).

Copilot uses AI. Check for mistakes.
result.then(
() => {
confirmLoading.value = false
setOpen(false)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

When onConfirm returns a Promise, the popup closes via setOpen(false) without passing the original click event. This makes openChange / visibleChange emissions inconsistent with the sync-confirm path (which passes e), and can break consumers that rely on the event parameter. Consider calling setOpen(false, e) on promise resolve (and you can also rely on setOpen to reset confirmLoading to avoid duplicated state updates).

Suggested change
setOpen(false)
setOpen(false, e)

Copilot uses AI. Check for mistakes.
Comment on lines +374 to +399
function emitCompatEvent(eventName: 'confirm' | 'cancel', event: MouseEvent) {
const handlerName = eventName === 'confirm' ? 'onConfirm' : 'onCancel'
const vnodeProps = instance.vnode.props as Record<string, unknown> | null
const emitEvent = () => {
if (eventName === 'confirm') {
emit('confirm', event)
return
}

emit('cancel', event)
}

if (!vnodeProps || !(handlerName in vnodeProps)) {
emitEvent()
return
}

const handler = vnodeProps[handlerName]

try {
vnodeProps[handlerName] = undefined
emitEvent()
} finally {
vnodeProps[handlerName] = handler
}
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

emitCompatEvent temporarily mutates instance.vnode.props to avoid double-invoking onConfirm/onCancel. vnode.props is an internal Vue structure and mutating it is brittle (may break with Vue internal changes, devtools assumptions, or concurrent renders). If possible, prefer an approach that avoids mutating vnode.props (e.g., distinguish callback props from emitted event listeners via a different prop name, or read callback handlers from a dedicated prop while leaving event emission to Vue’s normal listener invocation).

Copilot uses AI. Check for mistakes.
@xjccc xjccc requested a review from Copilot April 3, 2026 10:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +109 to +124
const baseStyles = floatingStyles.value as CSSProperties

if (props.zIndex == null && !props.popupStyle) {
return baseStyles
}

const styles: CSSProperties[] = [baseStyles]

if (props.zIndex != null) {
styles.push({ zIndex: props.zIndex })
}
if (props.popupStyle) {
styles.push(props.popupStyle as CSSProperties)
}

return styles
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

popupStyles merges floatingStyles with props.popupStyle, but this code has to cast props.popupStyle to CSSProperties. In TriggerProps (packages/ui/src/_internal/trigger/types.ts) popupStyle is currently typed as Record<string, string>, which is more restrictive than what this implementation now accepts (numbers, CSS variables, etc.) and forces unsafe casts. Consider updating the prop type to CSSProperties (or StyleValue) so callers and this component stay type-aligned.

Suggested change
const baseStyles = floatingStyles.value as CSSProperties
if (props.zIndex == null && !props.popupStyle) {
return baseStyles
}
const styles: CSSProperties[] = [baseStyles]
if (props.zIndex != null) {
styles.push({ zIndex: props.zIndex })
}
if (props.popupStyle) {
styles.push(props.popupStyle as CSSProperties)
}
return styles
if (props.zIndex == null && !props.popupStyle) {
return floatingStyles.value
}
return {
...floatingStyles.value,
...(props.zIndex != null ? { zIndex: props.zIndex } : {}),
...(props.popupStyle ?? {}),
}

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 35
:where(.ant-trigger-arrow)::after {
content: '';
position: absolute;
width: 8.970562748477143px;
height: 8.970562748477143px;
bottom: 0;
left: 0;
right: 0;
margin: auto;
border-radius: 0 0 2px 0;
transform: translateY(50%) rotate(-135deg);
box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The new arrow CSS uses several high-precision magic numbers (e.g. 8.970562748477143px) and an inline clip-path: path('...') string. This is hard to audit and maintain; please add an explanatory comment/source (e.g. derived from Ant Design geometry) or refactor to named CSS variables so future changes don’t risk subtly breaking the arrow shape.

Copilot uses AI. Check for mistakes.
Co-Authored-By: GitHub Copilot <noreply@github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +42 to +43
v-bind="props.cancelButtonProps"
@click="onCancel"
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

cancelButtonProps.onClick (and array handlers) will never run for the default cancel button because @click="onCancel" overrides any onClick coming from v-bind="props.cancelButtonProps". Consider binding cancelButtonSlotProps to the default <a-button> (or otherwise composing the handlers) so user-provided click handlers are preserved.

Suggested change
v-bind="props.cancelButtonProps"
@click="onCancel"
v-bind="cancelButtonSlotProps"

Copilot uses AI. Check for mistakes.
Comment on lines 53 to 55
v-bind="props.okButtonProps"
:loading="confirmLoading"
@click="onConfirm"
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

okButtonProps.onClick will be dropped for the default OK button because @click="onConfirm" overrides the onClick from v-bind="props.okButtonProps". Consider using the already-computed okButtonSlotProps for the default button (or composing handlers) so both user and internal logic run.

Suggested change
v-bind="props.okButtonProps"
:loading="confirmLoading"
@click="onConfirm"
v-bind="okButtonSlotProps"
:loading="confirmLoading"

Copilot uses AI. Check for mistakes.
Comment on lines +473 to +477
document.addEventListener('keydown', onDocumentKeydown)
return
}

document.removeEventListener('keydown', onDocumentKeydown)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

confirmLoading is only reset inside setOpen(false, ...). In fully controlled mode, the parent can close the Popconfirm by updating open directly without calling setOpen, leaving confirmLoading stuck true the next time it opens. Consider resetting confirmLoading whenever mergedOpen transitions to false in this watcher.

Copilot uses AI. Check for mistakes.
Co-Authored-By: GitHub Copilot <noreply@github.com>
@xjccc xjccc requested a review from Copilot April 3, 2026 11:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (isUserControlled.value) {
return props.open ?? props.visible ?? false
}
if (hasExplicitProp('open') && props.open !== null) return props.open
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

mergedOpen can return props.open directly when the open prop is explicitly present and not null. If a consumer passes :open="undefined" (or otherwise provides an explicit-but-undefined value), mergedOpen becomes undefined and is forwarded to Trigger’s open prop (which expects a boolean), making open-state logic rely on implicit truthiness. Consider coercing to a boolean (e.g. return !!props.open) or falling back to false when props.open is undefined while still treating null as uncontrolled.

Suggested change
if (hasExplicitProp('open') && props.open !== null) return props.open
if (hasExplicitProp('open') && props.open !== null) return props.open ?? false

Copilot uses AI. Check for mistakes.
Co-Authored-By: GitHub Copilot <noreply@github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +439 to +450
emitCompatEvent('confirm', e)
const result = invokeConfirmHandlers(e)
if (isThenable(result)) {
confirmLoading.value = true
result.then(
() => {
setOpen(false, e)
},
() => {
confirmLoading.value = false
},
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Async onConfirm promises can still call setOpen(false, e) after the Popconfirm has already been closed via another path (e.g., click outside / programmatic close). This can re-emit openChange/visibleChange a second time with a stale MouseEvent and may cause controlled parents to run close logic twice. Consider tracking the pending confirm (e.g., incrementing token / capturing the promise) and only closing/emitting when the popup is still open and the token matches, or canceling the pending close when mergedOpen becomes false.

Copilot uses AI. Check for mistakes.
Co-Authored-By: GitHub Copilot <noreply@github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants