fix(desktop): Windows 위젯 추가 팝업 즉시 표시

창 크기 조절 완료를 기다리지 않고 picker를 먼저 열고, body portal·
resize 타임아웃으로 WebView2에서 팝업이 뜨지 않던 문제를 해결합니다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Macbook
2026-06-15 16:44:17 +09:00
parent 02799cc8f8
commit 491fc8c267
3 changed files with 89 additions and 54 deletions
+32 -26
View File
@@ -139,33 +139,39 @@ const WidgetApp: React.FC = () => {
const handlePickerOpenChange = useCallback(async (open: boolean): Promise<void> => { const handlePickerOpenChange = useCallback(async (open: boolean): Promise<void> => {
const win = getCurrentWindow(); const win = getCurrentWindow();
if (open) { try {
const size = await win.innerSize(); if (open) {
sizeBeforePickerRef.current = { width: size.width, height: size.height }; const size = await win.innerSize();
const maxW = Math.max(480, window.screen.availWidth - 48); sizeBeforePickerRef.current = { width: size.width, height: size.height };
const maxH = Math.max(520, window.screen.availHeight - 48); const maxW = Math.max(480, window.screen.availWidth - 48);
const w = Math.min(NATIVE_WIDGET_PICKER_WINDOW.width, maxW); const maxH = Math.max(520, window.screen.availHeight - 48);
const h = Math.min(NATIVE_WIDGET_PICKER_WINDOW.height, maxH); const w = Math.min(NATIVE_WIDGET_PICKER_WINDOW.width, maxW);
await win.setSize(new LogicalSize(w, h)); const h = Math.min(NATIVE_WIDGET_PICKER_WINDOW.height, maxH);
await win.center(); await win.setSize(new LogicalSize(w, h));
await new Promise<void>(resolve => { await win.center();
let settled = false; await Promise.race([
const finish = () => { new Promise<void>(resolve => {
if (settled) return; let settled = false;
settled = true; const finish = () => {
unlisten?.(); if (settled) return;
resolve(); settled = true;
}; unlisten?.();
let unlisten: (() => void) | undefined; resolve();
void win.onResized(finish).then(fn => { unlisten = fn; }); };
window.setTimeout(finish, 150); let unlisten: (() => void) | undefined;
}); void win.onResized(finish).then(fn => { unlisten = fn; });
return; }),
new Promise<void>(resolve => { window.setTimeout(resolve, 400); }),
]);
return;
}
const prev = sizeBeforePickerRef.current;
if (!prev) return;
sizeBeforePickerRef.current = null;
await win.setSize(new LogicalSize(prev.width, prev.height));
} catch (err) {
console.warn('[widget-main] picker window resize failed', err);
} }
const prev = sizeBeforePickerRef.current;
if (!prev) return;
sizeBeforePickerRef.current = null;
await win.setSize(new LogicalSize(prev.width, prev.height));
}, []); }, []);
if (!instance) { if (!instance) {
@@ -40,29 +40,36 @@ const FloatingWidgetWindow: React.FC<Props> = ({
onPickerOpenChange, onPickerOpenChange,
}) => { }) => {
const [pickSlotId, setPickSlotId] = useState<string | null>(null); const [pickSlotId, setPickSlotId] = useState<string | null>(null);
const [pickerOpening, setPickerOpening] = useState(false);
const openPicker = useCallback(async (slotId: string) => { const openPicker = useCallback((slotId: string) => {
if (nativeShell && onPickerOpenChange) {
setPickerOpening(true);
try {
await onPickerOpenChange(true);
await new Promise<void>(resolve => {
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
});
} finally {
setPickerOpening(false);
}
}
setPickSlotId(slotId); setPickSlotId(slotId);
if (nativeShell) {
document.documentElement.classList.add('fw-native-picker-open');
}
if (nativeShell && onPickerOpenChange) {
void onPickerOpenChange(true).catch(err => {
console.warn('[FloatingWidgetWindow] picker window resize failed', err);
});
}
}, [nativeShell, onPickerOpenChange]); }, [nativeShell, onPickerOpenChange]);
const closePicker = useCallback(async () => { const closePicker = useCallback(async () => {
setPickSlotId(null); setPickSlotId(null);
if (nativeShell) {
document.documentElement.classList.remove('fw-native-picker-open');
}
if (nativeShell && onPickerOpenChange) { if (nativeShell && onPickerOpenChange) {
await onPickerOpenChange(false); try {
await onPickerOpenChange(false);
} catch (err) {
console.warn('[FloatingWidgetWindow] picker window restore failed', err);
}
} }
}, [nativeShell, onPickerOpenChange]); }, [nativeShell, onPickerOpenChange]);
useEffect(() => () => {
document.documentElement.classList.remove('fw-native-picker-open');
}, []);
const bodyWrapRef = useRef<HTMLDivElement>(null); const bodyWrapRef = useRef<HTMLDivElement>(null);
const bodyRef = useRef<HTMLDivElement>(null); const bodyRef = useRef<HTMLDivElement>(null);
const [bodySize, setBodySize] = useState({ width: 0, height: 0 }); const [bodySize, setBodySize] = useState({ width: 0, height: 0 });
@@ -312,23 +319,35 @@ const FloatingWidgetWindow: React.FC<Props> = ({
</div> </div>
)} )}
</div> </div>
{(pickerOpening || pickSlotId != null) && (
<WidgetPickerModal
open={pickSlotId != null}
category="all"
nativeCompact={nativeShell}
onClose={() => { void closePicker(); }}
onSelect={applyWidgetType}
/>
)}
{pickerOpening && pickSlotId == null && nativeShell && (
<div className="wd-picker-native-loading" aria-live="polite"> </div>
)}
</> </>
); );
return nativeShell ? content : createPortal(content, document.body); const pickerModal = pickSlotId != null ? (
<WidgetPickerModal
open
category="all"
nativeCompact={nativeShell}
onClose={() => { void closePicker(); }}
onSelect={applyWidgetType}
/>
) : null;
if (nativeShell) {
return (
<>
{content}
{pickerModal && createPortal(pickerModal, document.body)}
</>
);
}
return createPortal(
<>
{content}
{pickerModal}
</>,
document.body,
);
}; };
export default FloatingWidgetWindow; export default FloatingWidgetWindow;
+10
View File
@@ -1329,6 +1329,16 @@ html.fw-native-widget .wd-picker-native-loading {
font-size: 13px; font-size: 13px;
} }
html.fw-native-widget.fw-native-picker-open .fw-widget-native-root .fw-window {
visibility: hidden;
pointer-events: none;
}
html.fw-native-widget.fw-native-picker-open .wd-picker-native-overlay {
padding: 0;
background: var(--bg, #1a1b26);
}
.bps-page--wd-no-left .bps-side-wrap--left, .bps-page--wd-no-left .bps-side-wrap--left,
.bps-page--wd-no-left .bps-side-wrap--left + .bps-splitter { .bps-page--wd-no-left .bps-side-wrap--left + .bps-splitter {
display: none !important; display: none !important;