mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
fix(timeline): make horizontal scroll easier
This commit is contained in:
@@ -10,13 +10,13 @@ export function KeyboardShortcutsHelp() {
|
||||
const t = useScopedT("editor");
|
||||
|
||||
const [scrollLabels, setScrollLabels] = useState({
|
||||
pan: "Shift + Ctrl + Scroll",
|
||||
pan: "Shift + Scroll",
|
||||
zoom: "Ctrl + Scroll",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
formatShortcut(["shift", "mod", "Scroll"]),
|
||||
formatShortcut(["shift", "Scroll"]),
|
||||
formatShortcut(["mod", "Scroll"]),
|
||||
]).then(([pan, zoom]) => setScrollLabels({ pan, zoom }));
|
||||
}, []);
|
||||
|
||||
@@ -149,13 +149,13 @@ export function KeyboardShortcutsDialog({
|
||||
const { shortcuts, isMac, openConfig } = useShortcuts();
|
||||
const t = useScopedT("editor");
|
||||
const [scrollLabels, setScrollLabels] = useState({
|
||||
pan: "Shift + Ctrl + Scroll",
|
||||
pan: "Shift + Scroll",
|
||||
zoom: "Ctrl + Scroll",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
formatShortcut(["shift", "mod", "Scroll"]),
|
||||
formatShortcut(["shift", "Scroll"]),
|
||||
formatShortcut(["mod", "Scroll"]),
|
||||
]).then(([pan, zoom]) => setScrollLabels({ pan, zoom }));
|
||||
}, []);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveTimelineWheelPanDeltaPx } from "./useTimelineRange";
|
||||
|
||||
describe("resolveTimelineWheelPanDeltaPx", () => {
|
||||
it("uses trackpad horizontal wheel movement for timeline panning", () => {
|
||||
expect(
|
||||
resolveTimelineWheelPanDeltaPx({
|
||||
deltaX: 24,
|
||||
deltaY: 0,
|
||||
deltaMode: 0,
|
||||
}),
|
||||
).toBe(24);
|
||||
});
|
||||
|
||||
it("uses shifted vertical wheel movement for timeline panning", () => {
|
||||
expect(
|
||||
resolveTimelineWheelPanDeltaPx({
|
||||
deltaX: 0,
|
||||
deltaY: 3,
|
||||
deltaMode: 1,
|
||||
shiftKey: true,
|
||||
}),
|
||||
).toBe(48);
|
||||
});
|
||||
|
||||
it("keeps ctrl wheel available for timeline zoom unless shift is also held", () => {
|
||||
expect(
|
||||
resolveTimelineWheelPanDeltaPx({
|
||||
deltaX: 0,
|
||||
deltaY: 3,
|
||||
deltaMode: 1,
|
||||
ctrlKey: true,
|
||||
}),
|
||||
).toBe(0);
|
||||
expect(
|
||||
resolveTimelineWheelPanDeltaPx({
|
||||
deltaX: 0,
|
||||
deltaY: 3,
|
||||
deltaMode: 1,
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
}),
|
||||
).toBe(48);
|
||||
});
|
||||
|
||||
it("uses regular wheel movement when the timeline has no vertical overflow", () => {
|
||||
expect(
|
||||
resolveTimelineWheelPanDeltaPx({
|
||||
deltaX: 0,
|
||||
deltaY: 20,
|
||||
deltaMode: 0,
|
||||
canScrollVertically: false,
|
||||
}),
|
||||
).toBe(20);
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,40 @@ interface UseTimelineRangeParams {
|
||||
timelineContainerRef: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export interface TimelineWheelPanDeltaInput {
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
deltaMode: number;
|
||||
shiftKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
canScrollVertically?: boolean;
|
||||
}
|
||||
|
||||
export function resolveTimelineWheelPanDeltaPx({
|
||||
deltaX,
|
||||
deltaY,
|
||||
deltaMode,
|
||||
shiftKey = false,
|
||||
ctrlKey = false,
|
||||
metaKey = false,
|
||||
canScrollVertically = true,
|
||||
}: TimelineWheelPanDeltaInput) {
|
||||
if ((ctrlKey || metaKey) && !shiftKey) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Math.abs(deltaX) > 0) {
|
||||
return normalizeWheelDeltaToPixels(deltaX, deltaMode);
|
||||
}
|
||||
|
||||
if ((shiftKey || !canScrollVertically) && Math.abs(deltaY) > 0) {
|
||||
return normalizeWheelDeltaToPixels(deltaY, deltaMode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function useTimelineRange({ totalMs, timelineContainerRef }: UseTimelineRangeParams) {
|
||||
const [range, setRange] = useState<Range>(() => createInitialRange(totalMs));
|
||||
|
||||
@@ -42,29 +76,34 @@ export function useTimelineRange({ totalMs, timelineContainerRef }: UseTimelineR
|
||||
|
||||
const handleTimelineWheel = useCallback(
|
||||
(event: WheelEvent<HTMLDivElement>) => {
|
||||
if (event.ctrlKey || event.metaKey || totalMs <= 0) {
|
||||
if (((event.ctrlKey || event.metaKey) && !event.shiftKey) || totalMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawHorizontalDelta =
|
||||
Math.abs(event.deltaX) > 0
|
||||
? event.deltaX
|
||||
: event.shiftKey && Math.abs(event.deltaY) > 0
|
||||
? event.deltaY
|
||||
: 0;
|
||||
const container = timelineContainerRef.current;
|
||||
const horizontalDeltaPx = resolveTimelineWheelPanDeltaPx({
|
||||
deltaX: event.deltaX,
|
||||
deltaY: event.deltaY,
|
||||
deltaMode: event.deltaMode,
|
||||
shiftKey: event.shiftKey,
|
||||
ctrlKey: event.ctrlKey,
|
||||
metaKey: event.metaKey,
|
||||
canScrollVertically: container
|
||||
? container.scrollHeight > container.clientHeight + 1
|
||||
: true,
|
||||
});
|
||||
|
||||
if (rawHorizontalDelta === 0) {
|
||||
if (horizontalDeltaPx === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containerWidth = timelineContainerRef.current?.clientWidth ?? 0;
|
||||
const containerWidth = container?.clientWidth ?? 0;
|
||||
const visibleRangeMs = clampedRange.end - clampedRange.start;
|
||||
if (containerWidth <= 0 || visibleRangeMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const horizontalDeltaPx = normalizeWheelDeltaToPixels(rawHorizontalDelta, event.deltaMode);
|
||||
const deltaMs = (horizontalDeltaPx / containerWidth) * visibleRangeMs;
|
||||
panTimelineRange(deltaMs);
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ export const FIXED_SHORTCUTS: FixedShortcut[] = [
|
||||
display: "Del / ⌫",
|
||||
bindings: [{ key: "delete" }, { key: "backspace" }],
|
||||
},
|
||||
{ label: "Pan Timeline", display: "Shift + Ctrl + Scroll", bindings: [] },
|
||||
{ label: "Pan Timeline", display: "Shift + Scroll", bindings: [] },
|
||||
{ label: "Zoom Timeline", display: "Ctrl + Scroll", bindings: [] },
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user