summaryrefslogtreecommitdiff
path: root/apps/web/src/hooks/use-timeline-zoom.ts
blob: c8780f9a695cc7056b25cb75a5fff8a4556c7210 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { useState, useCallback, RefObject, useMemo } from "react";

interface UseTimelineZoomProps {
  containerRef: RefObject<HTMLDivElement>;
  isInTimeline?: boolean;
}

interface UseTimelineZoomReturn {
  zoomLevel: number;
  zoomStep: number;
  setZoomLevel: (zoomLevel: number | ((prev: number) => number)) => void;
  handleChangeZoomStep: (zoomStep: number) => void;
  handleChangeZoomLevel: (zoomStep: number) => void;
  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(
        TIMELINE_CONSTANTS.ZOOM_LEVEL_MIN,
        newStep * TIMELINE_CONSTANTS.ZOOM_STEP_BASE
      )
    );
  }, []);

  /**
   * 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(
        TIMELINE_CONSTANTS.ZOOM_LEVEL_MIN,
        Math.min(TIMELINE_CONSTANTS.ZOOM_LEVEL_MAX, newLevel)
      )
    );
  }, []);

  /**
   * 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?.();
      const delta =
        e.deltaY > 0
          ? -TIMELINE_CONSTANTS.ZOOM_STEP_BASE
          : TIMELINE_CONSTANTS.ZOOM_STEP_BASE;
      setZoomLevel((prev) =>
        Math.max(
          TIMELINE_CONSTANTS.ZOOM_LEVEL_MIN,
          Math.min(TIMELINE_CONSTANTS.ZOOM_LEVEL_MAX, prev + delta)
        )
      );
    }
  }, []);

  // Return all handlers and state values for use in timeline components
  return {
    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
  };
}