From 24192ad12a57bdd1a3ffad3b6db85c1e56e99032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 24 Jul 2026 12:02:41 +0800 Subject: [PATCH] fix: ignore wheel event from text area to prevent canva move --- ...™¨ã€‘生æˆç±»é¢æ¿Lovart统一改造方案-2026-06-17.md | 1 + .../useImageCanvasViewportControls.test.tsx | 21 +++++++++++++++++++ .../useImageCanvasViewportControls.ts | 17 ++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/ã€ç¼–辑器】生æˆç±»é¢æ¿Lovart统一改造方案-2026-06-17.md b/docs/ã€ç¼–辑器】生æˆç±»é¢æ¿Lovart统一改造方案-2026-06-17.md index 6fd784f9f..26bc34cb6 100644 --- a/docs/ã€ç¼–辑器】生æˆç±»é¢æ¿Lovart统一改造方案-2026-06-17.md +++ b/docs/ã€ç¼–辑器】生æˆç±»é¢æ¿Lovart统一改造方案-2026-06-17.md @@ -66,6 +66,7 @@ - 底边æ ä¸­ä¼šåœ¨ä¸Šæ–¹å¼¹å‡ºäºŒçº§é€‰é¡¹çš„å…¥å£ä¸å†ä¾èµ–点击展开。鼠标悬åœåˆ°å…¥å£å³å¯æ‰“å¼€äºŒçº§é¢æ¿ï¼Œé¼ æ ‡ç¦»å¼€å…¥å£å’ŒäºŒçº§é¢æ¿åŽè‡ªåŠ¨æ”¶èµ·ï¼›å½“å‰èŒƒå›´åŒ…括 `生æˆè§„范` å’Œ `生æˆéŸ³ä¹`。 - 底边æ äºŒçº§é€‰é¡¹é¢æ¿å¿…é¡»é”šå®šåˆ°å¯¹åº”å…¥å£æŒ‰é’®æœ¬èº«ï¼Œä¸ä½¿ç”¨å±å¹•居中或固定底部å移;移动端窄å±ä¸‹ä¹Ÿåº”ä¿æŒè·Ÿéšå…¥å£ä½ç½®ã€‚ - 生æˆå›¾ç‰‡å’Œç”Ÿæˆè§†é¢‘文本输入框紧贴å‚è€ƒå›¾ä¸‹æ–¹ï¼Œå–æ¶ˆæ—§ç½‘格预留导致的空白高度。 +- 在生æˆç±»é¢æ¿æˆ–独立修改弹窗的文本输入框内滚动时,滚轮åªç”¨äºŽè¾“å…¥å†…å®¹æˆ–é¢æ¿è‡ªèº«ï¼Œä¸å¾—冒泡平移或缩放画布; - 生æˆè§„范类图片固定使用 `16:9·2K · gpt-image-2`ã€‚è¿™ä¸‰ä¸ªå‚æ•°åœ¨é¢æ¿åº•部沿用å¯ç¼–è¾‘å‚æ•°æŒ‰é’®çš„胶囊样å¼å±•ç¤ºï¼Œä½†æŽ§ä»¶ä¿æŒç¦ç”¨ä¸å¯ç‚¹å‡»ï¼Œä¸æä¾›æ¯”例ã€å°ºå¯¸æˆ–模型修改入å£ã€‚ - 宣å‘ç´ æçš„ `游æˆé¦–图`ã€`详情五图`ã€`è¿è¥æµ·æŠ¥` 固定使用 `gpt-image-2`ã€‚é¢æ¿åº•éƒ¨åªæ˜¾ç¤ºç¦ç”¨æ€ `gpt-image-2` æ¨¡åž‹èƒ¶å›Šå’Œç”ŸæˆæŒ‰é’®ï¼Œä¸å‡ºçް `nanobanana2` 选项;åŽç«¯æ”¶åˆ° `publication-material` 旧请求时也必须强制归一为 `gpt-image-2`。 - 图片快速编辑ä¿ç•™ä¸€ä¸ªæç¤ºè¯è¾“入框,并展示与常规图片生æˆä¸€è‡´çš„æ¯”例 / 尺寸和模型选择;æç¤ºè¯ placeholder 为 `你希望素æå¦‚何修改?`,æäº¤æŒ‰é’®æ˜¾ç¤º `修改`,ä¸å±•示é¢å¤–å‚è€ƒå›¾æŽ§ä»¶ã€‚æ‰“å¼€é¢æ¿æ—¶ä¼˜å…ˆç»§æ‰¿åŽŸå›¾å…³è”生æˆå™¨è®°å½•çš„æ¨¡åž‹ã€æ¯”例和尺寸;没有关è”生æˆå™¨æ—¶ä½¿ç”¨å›¾å±‚模型,并按原图真实分辨率推导比例和尺寸;模型缺失或已ä¸å—æ”¯æŒæ—¶å›žè½åˆ°å½“å‰é»˜è®¤å›¾ç‰‡æ¨¡åž‹ã€‚åˆ‡æ¢æ¨¡åž‹åŽåªå±•示该模型支æŒçš„傿•°ï¼Œä¸å…¼å®¹çš„当å‰å€¼å›žè½åˆ°è¯¥æ¨¡åž‹é»˜è®¤å€¼ï¼ŒæŒ‰é’®æ³¥ç‚¹æŒ‰é€‰å®šæ¨¡åž‹å’Œå°ºå¯¸åŒæ­¥åˆ·æ–°ã€‚æäº¤æ—¶å¿…é¡»åŒæ—¶ä¼ é€’ `model / aspectRatio / imageSize`ï¼›åŽç«¯æŒ‰æ¨¡åž‹é€‰æ‹© provider å议:`nanobanana2` 使用 `generateContent + inline_data`,`gpt-image-2` 使用 `/v1/images/edits` multipart,ä¸èƒ½æŠŠ nanobanana 模型 ID å‘å¾€ GPT edits 端点。 diff --git a/src/components/image-editor/useImageCanvasViewportControls.test.tsx b/src/components/image-editor/useImageCanvasViewportControls.test.tsx index 0f0ab01eb..6c4fed9f9 100644 --- a/src/components/image-editor/useImageCanvasViewportControls.test.tsx +++ b/src/components/image-editor/useImageCanvasViewportControls.test.tsx @@ -191,6 +191,27 @@ describe('useImageCanvasViewportControls', () => { }); }); + it('does not pan the canvas when a generation prompt receives wheel input', () => { + const { result, viewportElement } = renderViewportControls(); + const prompt = document.createElement('textarea'); + viewportElement.append(prompt); + const initialViewport = result.current.viewport; + + const wheelEvent = new WheelEvent('wheel', { + bubbles: true, + cancelable: true, + deltaY: 120, + clientX: 260, + clientY: 190, + }); + act(() => { + prompt.dispatchEvent(wheelEvent); + }); + + expect(result.current.viewport).toEqual(initialViewport); + expect(wheelEvent.defaultPrevented).toBe(false); + }); + it('handles native two-axis wheel pan, ctrl wheel zoom and minimap movement', () => { const rafCallbacks: FrameRequestCallback[] = []; vi.spyOn(window, 'requestAnimationFrame').mockImplementation( diff --git a/src/components/image-editor/useImageCanvasViewportControls.ts b/src/components/image-editor/useImageCanvasViewportControls.ts index 2f74c1b8f..dddbe6784 100644 --- a/src/components/image-editor/useImageCanvasViewportControls.ts +++ b/src/components/image-editor/useImageCanvasViewportControls.ts @@ -34,6 +34,15 @@ export const DEFAULT_IMAGE_CANVAS_VIEWPORT: CanvasViewport = { scale: 0.5, }; +function isCanvasWheelInteractionTarget(target: EventTarget | null) { + return ( + target instanceof Element && + target.closest( + 'input, textarea, select, [contenteditable="true"]', + ) !== null + ); +} + type UseImageCanvasViewportControlsOptions = { canvasViewportRef: RefObject; layers: CanvasLayer[]; @@ -253,12 +262,18 @@ export function useImageCanvasViewportControls({ const handleNativeWheel = useCallback( (event: WheelEvent) => { - event.preventDefault(); + // Let form controls and their scrollable panels keep native wheel input. + if (isCanvasWheelInteractionTarget(event.target)) { + return; + } + const viewportElement = canvasViewportRef.current; if (!viewportElement) { return; } + event.preventDefault(); + if (!event.ctrlKey && !event.metaKey) { const useShiftWheelHorizontalFallback = event.shiftKey && event.deltaX === 0; -- 2.52.0