add comment for hooks
This commit is contained in:
parent
b266678fe4
commit
52f003f25a
|
|
@ -1,13 +1,32 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* useDisableBrowserZoom
|
||||
*
|
||||
* This React hook prevents users from zooming in/out the browser window using keyboard shortcuts,
|
||||
* mouse wheel, or gesture events (such as pinch-to-zoom on trackpads or touch devices).
|
||||
*
|
||||
* Typical use case: apps with fixed-size UIs, custom editors, or when zooming would break layout.
|
||||
*
|
||||
* NOTE: Disabling browser zoom may negatively affect accessibility and user experience.
|
||||
* Use with caution, especially for public-facing applications.
|
||||
*/
|
||||
export const useDisableBrowserZoom = () => {
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Prevents browser zoom when user holds Ctrl (or Cmd on Mac) and scrolls the mouse wheel.
|
||||
*/
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevents browser zoom via keyboard shortcuts:
|
||||
* Ctrl/Cmd + '+', '-', '=', or '0' (reset zoom)
|
||||
* Some keyboards emit '=' for '+' (without Shift).
|
||||
*/
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey) {
|
||||
const key = e.key;
|
||||
|
|
@ -17,23 +36,22 @@ export const useDisableBrowserZoom = () => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevents pinch-zoom gestures on supported browsers (mainly Safari/macOS/iOS).
|
||||
* gesturestart, gesturechange, and gestureend are non-standard and not supported everywhere.
|
||||
*/
|
||||
const handleGesture = (e: Event) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Attach event listeners (passive: false is required for preventDefault to work)
|
||||
window.addEventListener("wheel", handleWheel, { passive: false });
|
||||
window.addEventListener("keydown", handleKeyDown, { passive: false });
|
||||
window.addEventListener("gesturestart", handleGesture, { passive: false });
|
||||
window.addEventListener("gesturechange", handleGesture, { passive: false });
|
||||
window.addEventListener("gestureend", handleGesture, { passive: false });
|
||||
|
||||
window.addEventListener("gesturestart", handleGesture, {
|
||||
passive: false,
|
||||
});
|
||||
window.addEventListener("gesturechange", handleGesture, {
|
||||
passive: false,
|
||||
});
|
||||
window.addEventListener("gestureend", handleGesture, {
|
||||
passive: false,
|
||||
});
|
||||
|
||||
// Cleanup listeners on unmount to avoid memory leaks
|
||||
return () => {
|
||||
window.removeEventListener("wheel", handleWheel);
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ interface UseTimelinePlayheadProps {
|
|||
rulerScrollRef: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
/**
|
||||
* useTimelinePlayhead
|
||||
*
|
||||
* Custom hook to manage playhead (scrubbing) logic for a timeline editor.
|
||||
* Handles mouse interaction for timeline ruler, calculates time from mouse,
|
||||
* and enables smooth scrubbing without unnecessary re-renders.
|
||||
*/
|
||||
export function useTimelinePlayhead({
|
||||
currentTime,
|
||||
duration,
|
||||
|
|
@ -19,17 +26,35 @@ export function useTimelinePlayhead({
|
|||
rulerRef,
|
||||
rulerScrollRef,
|
||||
}: UseTimelinePlayheadProps) {
|
||||
// Get current project info (especially FPS) from global store
|
||||
const { activeProject } = useProjectStore();
|
||||
|
||||
// Ref to track if currently scrubbing
|
||||
const isScrubbingRef = useRef(false);
|
||||
|
||||
// Ref to hold the playhead time while scrubbing (does not trigger re-render)
|
||||
const scrubTimeRef = useRef<number | null>(null);
|
||||
|
||||
// State to force re-render when scrubbing ends
|
||||
const [_, forceRerender] = useState(0);
|
||||
|
||||
/**
|
||||
* Determines the actual playhead position:
|
||||
* - If scrubbing, use the value in scrubTimeRef
|
||||
* - Otherwise, use the currentTime (controlled by external state)
|
||||
*/
|
||||
const playheadPosition =
|
||||
isScrubbingRef.current && scrubTimeRef.current !== null
|
||||
? scrubTimeRef.current
|
||||
: currentTime;
|
||||
|
||||
/**
|
||||
* Calculate timeline time (in seconds) based on mouse X position.
|
||||
* - Gets bounding rect of the ruler
|
||||
* - Adjusts for scroll position if ruler is scrollable
|
||||
* - Converts X pixel offset to seconds using current zoom level
|
||||
* - Snaps to nearest frame using FPS
|
||||
*/
|
||||
const getTimeFromMouse = useCallback(
|
||||
(e: MouseEvent | React.MouseEvent) => {
|
||||
const ruler = rulerRef.current;
|
||||
|
|
@ -43,6 +68,7 @@ export function useTimelinePlayhead({
|
|||
const scrollLeft = scrollArea.scrollLeft;
|
||||
const x = e.clientX - rect.left + scrollLeft;
|
||||
|
||||
// Calculate how many pixels represent one second, depending on zoom
|
||||
const pixelsPerSecond = 50 * zoomLevel;
|
||||
const rawTime = Math.max(0, Math.min(duration, x / pixelsPerSecond));
|
||||
const time = snapTimeToFrame(rawTime, activeProject?.fps || 30);
|
||||
|
|
@ -51,6 +77,13 @@ export function useTimelinePlayhead({
|
|||
[rulerRef, rulerScrollRef, duration, zoomLevel, activeProject?.fps]
|
||||
);
|
||||
|
||||
/**
|
||||
* Handle mouse down event on the ruler:
|
||||
* - Starts scrubbing and updates playhead immediately
|
||||
* - Registers mousemove/mouseup events to allow scrubbing
|
||||
* - Updates time as mouse moves and seeks to new time
|
||||
* - Cleans up listeners and triggers re-render when scrubbing ends
|
||||
*/
|
||||
const handleRulerMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -61,6 +94,7 @@ export function useTimelinePlayhead({
|
|||
scrubTimeRef.current = time;
|
||||
seek(time);
|
||||
|
||||
// Mouse move handler: update playhead and seek to new time
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const t = getTimeFromMouse(e);
|
||||
if (t !== scrubTimeRef.current) {
|
||||
|
|
@ -69,6 +103,7 @@ export function useTimelinePlayhead({
|
|||
}
|
||||
};
|
||||
|
||||
// Mouse up handler: stop scrubbing, cleanup, and force re-render
|
||||
const onMouseUp = () => {
|
||||
isScrubbingRef.current = false;
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
|
|
@ -76,6 +111,7 @@ export function useTimelinePlayhead({
|
|||
forceRerender((v) => v + 1);
|
||||
};
|
||||
|
||||
// Attach listeners to window for drag outside the ruler area
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("mouseup", onMouseUp);
|
||||
},
|
||||
|
|
@ -83,7 +119,7 @@ export function useTimelinePlayhead({
|
|||
);
|
||||
|
||||
return {
|
||||
playheadPosition,
|
||||
handleRulerMouseDown,
|
||||
playheadPosition, // Current playhead position (real-time while scrubbing)
|
||||
handleRulerMouseDown, // Attach to the ruler's onMouseDown
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,30 @@ interface UseTimelineZoomReturn {
|
|||
handleWheel: (e: React.WheelEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* useTimelineZoom
|
||||
*
|
||||
* Custom hook to manage zoom logic for a timeline component.
|
||||
* Handles zoom state, step calculation, level changes, and mouse wheel zooming (with ctrl/meta).
|
||||
*/
|
||||
export function useTimelineZoom(): UseTimelineZoomReturn {
|
||||
// Current zoom level (1 = default)
|
||||
const [zoomLevel, setZoomLevel] = useState(1);
|
||||
|
||||
/**
|
||||
* Calculate the current zoom step based on the zoom level and a base step constant.
|
||||
* Ensures minimum step is 1 (prevents zero or negative step).
|
||||
*/
|
||||
const zoomStep = useMemo(
|
||||
() =>
|
||||
Math.max(1, Math.round(zoomLevel / TIMELINE_CONSTANTS.ZOOM_STEP_BASE)),
|
||||
[zoomLevel]
|
||||
);
|
||||
|
||||
/**
|
||||
* Update the zoom level using a given step.
|
||||
* The zoom level is clamped to a minimum value defined in constants.
|
||||
*/
|
||||
const handleChangeZoomStep = useCallback((newStep: number) => {
|
||||
setZoomLevel(
|
||||
Math.max(
|
||||
|
|
@ -33,6 +48,10 @@ export function useTimelineZoom(): UseTimelineZoomReturn {
|
|||
);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Update the zoom level directly.
|
||||
* The new value is clamped between the defined minimum and maximum levels.
|
||||
*/
|
||||
const handleChangeZoomLevel = useCallback((newLevel: number) => {
|
||||
setZoomLevel(
|
||||
Math.max(
|
||||
|
|
@ -42,7 +61,10 @@ export function useTimelineZoom(): UseTimelineZoomReturn {
|
|||
);
|
||||
}, []);
|
||||
|
||||
// Mouse wheel zoom, ctrl/meta gesture
|
||||
/**
|
||||
* Handle zooming using mouse wheel + ctrl/meta key (like browser zoom).
|
||||
* Prevents default browser behavior and updates zoom level accordingly.
|
||||
*/
|
||||
const handleWheel = useCallback((e: React.WheelEvent) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault?.();
|
||||
|
|
@ -59,12 +81,13 @@ export function useTimelineZoom(): UseTimelineZoomReturn {
|
|||
}
|
||||
}, []);
|
||||
|
||||
// Return all handlers and state values for use in timeline components
|
||||
return {
|
||||
zoomLevel,
|
||||
zoomStep,
|
||||
setZoomLevel,
|
||||
handleChangeZoomStep,
|
||||
handleChangeZoomLevel,
|
||||
handleWheel,
|
||||
zoomLevel, // Current zoom level
|
||||
zoomStep, // Current zoom step (calculated from zoomLevel)
|
||||
setZoomLevel, // Directly set the zoom level (accepts value or updater function)
|
||||
handleChangeZoomStep, // Change zoom by step value
|
||||
handleChangeZoomLevel, // Change zoom by level value
|
||||
handleWheel, // Handler for mouse wheel zooming
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue