fix: 输入框内滚动会触发画布滚动 (#110)
Project CI / Frontend tests (push) Successful in 2m28s
Project CI / Repository checks (push) Successful in 1m30s
Project CI / Native shell tests (push) Successful in 2m49s
Project CI / Backend tests (push) Successful in 3m40s

fix:  ignore wheel event from text area to prevent canva move
before:
![shotmd-1784861686-compressed.webp](/attachments/916a0d1c-2ab0-4a16-ae35-0ee599ee9df2)
after:
![shotmd-1784861713-compressed.webp](/attachments/f0558ff5-55d6-471f-b048-18b623dfb911)

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/110
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
This commit was merged in pull request #110.
This commit is contained in:
2026-07-24 20:47:05 +08:00
committed by 段舒康
parent a2ee879fc8
commit 60a4548524
3 changed files with 38 additions and 1 deletions
@@ -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 端点。
@@ -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(
@@ -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<HTMLDivElement | null>;
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;