import { effect, h, signal } from '@ui-arch/frame'; import { button } from './button.js'; import { popover } from './popover.js'; import type { PopconfirmProps } from './types.js'; /*** 二次确认气泡:危险操作就地确认,不打断页面;确定后收起再回调,取消 / 点外部 / Esc 都走 onCancel ***/ export function popconfirm(props: PopconfirmProps, trigger: HTMLElement): HTMLElement { // —— 状态集中声明 —— const open = props.open ?? signal(false); // 开合信号:自己持有一份,确认与取消都要能写回 let accepted = false; // 本次收起是否由「确定」触发:用来区分确认与取消 /*** 收起弹层:写回信号即触发 popover 的显隐与 Esc 栈清理 ***/ const close = (): void => { open(false); }; const idle = props.confirmText ?? '确定'; // 确认按钮常态文案,异步结束后还原 /*** 确认:同步回调先收起再执行(避免回调里改数据时弹层还开着);返回 Promise 时就地 loading,成功才收起、失败留在弹层重试 ***/ const accept = (): void => { const out = props.onConfirm(); if (!(out instanceof Promise)) { accepted = true; close(); return; } okBtn.disabled = true; okBtn.textContent = '处理中…'; out.then(() => { accepted = true; close(); }) .catch(() => { /* 失败保持展开:提示交给调用方的 toast */ }) .finally(() => { okBtn.disabled = false; okBtn.textContent = idle; }); }; const okBtn = button({ label: idle, variant: props.variant === 'danger' ? 'danger' : 'primary', onClick: () => accept() }) as HTMLButtonElement; const body = h( 'div', { class: 'ua-popconfirm' }, h('div', { class: 'ua-popconfirm-text' }, props.description ?? '该操作不可撤销,确定继续吗?'), h( 'div', { class: 'ua-popconfirm-actions' }, button({ label: props.cancelText ?? '取消', variant: 'ghost', onClick: () => close() }), okBtn, ), ); const wrap = popover({ title: props.title, content: body, placement: props.placement ?? 'top', // 缺省压在上方:确认气泡不该盖住被操作的那一行 trigger: 'click', open, onOpenChange: (next) => { if (next || accepted) { accepted = false; return; } // 展开、或刚由确认收起:不回调取消 props.onCancel?.(); // 点外部 / Esc 收起等同取消 }, }, trigger); effect(() => { if (!open()) return; // 卫语句:收起时不动焦点 requestAnimationFrame(() => (body.querySelector('button') as HTMLElement | null)?.focus()); // 展开即落在「取消」上,误按回车不会误伤 }); return wrap; }