Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
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
import { type FC, type MutableRefObject, useMemo, useRef, useState } from "react";
import { cn } from "../../../utils/bem";
import { clamp } from "../../../utils/utilities";
import type { TimelineSideControlProps } from "../Types";
import "./FramesControl.scss";
 
export const FramesControl: FC<TimelineSideControlProps> = ({ position = 0, length = 0, onPositionChange }) => {
  const [inputMode, setInputMode] = useState(false);
  const duration = useMemo(() => {
    return length - 1;
  }, [length]);
 
  return (
    <div className={cn("frames-control").toClassName()} onClick={() => setInputMode(true)}>
      {inputMode ? (
        <FrameInput
          length={duration}
          position={position}
          onChange={(value) => {
            onPositionChange?.(clamp(value, 0, length));
          }}
          onFinishEditing={() => {
            setInputMode(false);
          }}
        />
      ) : (
        <>
          {clamp(Math.round(position + 1), 1, duration + 1)} <span>of {duration + 1}</span>
        </>
      )}
    </div>
  );
};
 
interface FrameInputProps {
  position: number;
  length: number;
  onChange: (value: number) => void;
  onFinishEditing: () => void;
}
 
const allowedKeys = ["ArrowUp", "ArrowDown", "Backspace", "Delete", "Enter", /[0-9]/];
 
const FrameInput: FC<FrameInputProps> = ({ length, position, onChange, onFinishEditing }) => {
  const input = useRef<HTMLInputElement>() as MutableRefObject<HTMLInputElement>;
 
  const notifyChange = (value: number) => {
    onChange?.(clamp(value, 1, length));
  };
 
  return (
    <input
      type="text"
      ref={input}
      defaultValue={position + 1}
      autoFocus
      onFocus={() => input.current?.select()}
      onKeyDown={(e) => {
        const allowedKey = allowedKeys.find((k) => (k instanceof RegExp ? k.test(e.key) : k === e.key));
 
        if (!allowedKey && !e.metaKey) e.preventDefault();
 
        const value = Number.parseInt(input.current!.value);
        const step = e.shiftKey ? 10 : 1;
 
        if (e.key === "Enter") {
          notifyChange?.(value);
          onFinishEditing?.();
        } else if (e.key === "Escape") {
          onFinishEditing?.();
        } else if (allowedKey === "ArrowUp") {
          input.current!.value = clamp(value + step, 1, length).toString();
          e.preventDefault();
        } else if (allowedKey === "ArrowDown") {
          input.current!.value = clamp(value - step, 1, length).toString();
          e.preventDefault();
        }
      }}
      onBlur={() => onFinishEditing?.()}
    />
  );
};