锁定音频预设轨道时禁用手动横向滚动

锁定态给预设轨道加 --locked 修饰类,收紧为不可滚动容器并只放行纵向手势。

补齐 BGM 与 SFX 跑马灯锁定态的轨道滚动与相位保留断言。
This commit is contained in:
2026-08-07 04:38:00 +00:00
parent cb7bcbc67e
commit 7b67d8e9b7
4 changed files with 68 additions and 2 deletions
@@ -394,7 +394,13 @@ export function ImageCanvasAudioPresetMarquee<
</button>
<div
ref={viewportRef}
className="image-canvas-editor__background-music-presets-viewport"
// 锁定要停的是整条轨道的滚动,不只是自动循环:轨道本身仍是可滚动容器,
// 不收紧就还能被触摸、触控板和 Shift+滚轮横向拖动。
className={`image-canvas-editor__background-music-presets-viewport${
isLocked
? ' image-canvas-editor__background-music-presets-viewport--locked'
: ''
}`}
onPointerMove={handleViewportPointerMove}
onPointerLeave={handleViewportPointerLeave}
onPointerDown={beginTouchScroll}
@@ -438,6 +438,53 @@ describe('ImageCanvasBackgroundMusicPresetMarquee', () => {
).toBe(true);
});
it('stops the track from being scrolled by hand while the panel is locked', () => {
const onSelectPreset = vi.fn();
const { container, rerender } = renderMarquee({ onSelectPreset });
expand();
const viewport = getViewport(container);
// 未锁定时轨道就是可滚动容器,交给原生横向滚动。
expect(
viewport.classList.contains(
'image-canvas-editor__background-music-presets-viewport--locked',
),
).toBe(false);
rerender(
<ImageCanvasBackgroundMusicPresetMarquee
presets={BACKGROUND_MUSIC_PROMPT_PRESETS}
isLocked
onSelectPreset={onSelectPreset}
/>,
);
// 锁定时不只停自动循环,轨道本身也不再接受触摸 / 触控板的横向滚动。
expect(frameCallbacks.size).toBe(0);
expect(
viewport.classList.contains(
'image-canvas-editor__background-music-presets-viewport--locked',
),
).toBe(true);
// 相位保留:解锁后从原处继续,不跳回队列头。
expect(viewport.scrollLeft).toBe(QUEUE_WIDTH);
rerender(
<ImageCanvasBackgroundMusicPresetMarquee
presets={BACKGROUND_MUSIC_PROMPT_PRESETS}
isLocked={false}
onSelectPreset={onSelectPreset}
/>,
);
expect(
viewport.classList.contains(
'image-canvas-editor__background-music-presets-viewport--locked',
),
).toBe(false);
expect(viewport.scrollLeft).toBe(QUEUE_WIDTH);
});
it('never starts the loop under reduced motion and reacts to runtime changes', () => {
prefersReducedMotion = true;
const { container } = renderMarquee();
@@ -44,7 +44,7 @@ describe('ImageCanvasSoundEffectPresetMarquee', () => {
expect(onSelectPreset).toHaveBeenCalledWith(SOUND_EFFECT_PROMPT_PRESETS[0]);
});
it('keeps expansion and preset controls truly disabled while locked', () => {
it('keeps expansion, preset controls and the track scroll truly disabled while locked', () => {
const view = render(
<ImageCanvasSoundEffectPresetMarquee
presets={SOUND_EFFECT_PROMPT_PRESETS}
@@ -72,5 +72,11 @@ describe('ImageCanvasSoundEffectPresetMarquee', () => {
}) as HTMLButtonElement
).disabled,
).toBe(true);
// 「优化中锁定滚动」按轨道自身不可被手动横向滚动验收,不只是停掉自动循环。
expect(
view.container.querySelector(
'.image-canvas-editor__background-music-presets-viewport--locked',
),
).toBeTruthy();
});
});
+7
View File
@@ -17105,6 +17105,13 @@ button {
display: none;
}
.image-canvas-editor__background-music-presets-viewport--locked {
/* 锁定期间轨道不再是用户可滚动容器,但保留 scrollLeft 相位,解锁后循环从原处继续。 */
overflow-x: hidden;
/* 横向手势不再作用于轨道,纵向手势仍旧交给画布。 */
touch-action: pan-y;
}
.image-canvas-editor__background-music-presets-queue {
display: flex;
flex: 0 0 auto;