@keplar-404/react-timeline-editor 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/components/control_area/index.d.ts +0 -0
  2. package/dist/components/cursor/cursor.d.ts +20 -0
  3. package/dist/components/cut-overlay/CutOverlay.d.ts +202 -0
  4. package/dist/components/edit_area/cross_row_drag.d.ts +50 -0
  5. package/dist/components/edit_area/drag_lines.d.ts +11 -0
  6. package/dist/components/edit_area/drag_preview.d.ts +14 -0
  7. package/dist/components/edit_area/drag_utils.d.ts +39 -0
  8. package/dist/components/edit_area/edit_action.d.ts +19 -0
  9. package/dist/components/edit_area/edit_area.d.ts +56 -0
  10. package/dist/components/edit_area/edit_row.d.ts +27 -0
  11. package/dist/components/edit_area/hooks/use_drag_line.d.ts +33 -0
  12. package/dist/components/edit_area/insertion_line.d.ts +12 -0
  13. package/dist/components/loop-zone/LoopZoneOverlay.d.ts +243 -0
  14. package/dist/components/row_rnd/hooks/useAutoScroll.d.ts +7 -0
  15. package/dist/components/row_rnd/interactable.d.ts +14 -0
  16. package/dist/components/row_rnd/row_rnd.d.ts +3 -0
  17. package/dist/components/row_rnd/row_rnd_interface.d.ts +47 -0
  18. package/dist/components/time_area/time_area.d.ts +17 -0
  19. package/dist/components/timeline.d.ts +3 -0
  20. package/dist/components/transport/TransportBar.d.ts +132 -0
  21. package/dist/components/transport/useTimelinePlayer.d.ts +164 -0
  22. package/dist/index.cjs.js +13 -0
  23. package/dist/index.d.ts +12 -0
  24. package/dist/index.es.js +10102 -0
  25. package/dist/index.umd.js +13 -0
  26. package/dist/interface/common_prop.d.ts +12 -0
  27. package/dist/interface/const.d.ts +28 -0
  28. package/dist/interface/timeline.d.ts +342 -0
  29. package/dist/react-timeline-editor.css +1 -0
  30. package/dist/utils/check_props.d.ts +2 -0
  31. package/dist/utils/deal_class_prefix.d.ts +1 -0
  32. package/dist/utils/deal_data.d.ts +58 -0
  33. package/dist/utils/logger.d.ts +132 -0
  34. package/package.json +70 -0
  35. package/src/components/control_area/index.tsx +1 -0
  36. package/src/components/cursor/cursor.css +26 -0
  37. package/src/components/cursor/cursor.tsx +105 -0
  38. package/src/components/cut-overlay/CutOverlay.css +68 -0
  39. package/src/components/cut-overlay/CutOverlay.tsx +491 -0
  40. package/src/components/edit_area/cross_row_drag.tsx +174 -0
  41. package/src/components/edit_area/drag_lines.css +13 -0
  42. package/src/components/edit_area/drag_lines.tsx +31 -0
  43. package/src/components/edit_area/drag_preview.tsx +50 -0
  44. package/src/components/edit_area/drag_utils.ts +77 -0
  45. package/src/components/edit_area/edit_action.css +56 -0
  46. package/src/components/edit_area/edit_action.tsx +362 -0
  47. package/src/components/edit_area/edit_area.css +24 -0
  48. package/src/components/edit_area/edit_area.tsx +606 -0
  49. package/src/components/edit_area/edit_row.css +78 -0
  50. package/src/components/edit_area/edit_row.tsx +128 -0
  51. package/src/components/edit_area/hooks/use_drag_line.ts +93 -0
  52. package/src/components/edit_area/insertion_line.tsx +39 -0
  53. package/src/components/loop-zone/LoopZoneOverlay.css +65 -0
  54. package/src/components/loop-zone/LoopZoneOverlay.tsx +461 -0
  55. package/src/components/row_rnd/hooks/useAutoScroll.ts +81 -0
  56. package/src/components/row_rnd/interactable.tsx +55 -0
  57. package/src/components/row_rnd/row_rnd.tsx +365 -0
  58. package/src/components/row_rnd/row_rnd_interface.ts +59 -0
  59. package/src/components/time_area/time_area.css +35 -0
  60. package/src/components/time_area/time_area.tsx +93 -0
  61. package/src/components/timeline.css +12 -0
  62. package/src/components/timeline.tsx +227 -0
  63. package/src/components/transport/TransportBar.css +171 -0
  64. package/src/components/transport/TransportBar.tsx +322 -0
  65. package/src/components/transport/useTimelinePlayer.ts +319 -0
  66. package/src/index.tsx +17 -0
  67. package/src/interface/common_prop.ts +13 -0
  68. package/src/interface/const.ts +32 -0
  69. package/src/interface/timeline.ts +329 -0
  70. package/src/utils/check_props.ts +77 -0
  71. package/src/utils/deal_class_prefix.ts +6 -0
  72. package/src/utils/deal_data.ts +159 -0
  73. package/src/utils/logger.ts +239 -0
@@ -0,0 +1,322 @@
1
+ import React from 'react';
2
+ import { TimelinePlayerState, formatTime } from './useTimelinePlayer';
3
+ import './TransportBar.css';
4
+
5
+ // ─────────────────────────────────────────────
6
+ // Public Types
7
+ // ─────────────────────────────────────────────
8
+
9
+ /**
10
+ * Loop control props for the {@link TransportBar} component.
11
+ * Supply this to enable the loop toggle button and start/end inputs.
12
+ */
13
+ export interface TransportBarLoopProps {
14
+ /**
15
+ * Whether the loop is currently active.
16
+ * Controls the visual state of the loop button (lit vs. dim).
17
+ */
18
+ enabled: boolean;
19
+
20
+ /**
21
+ * Loop region start time in seconds.
22
+ */
23
+ start: number;
24
+
25
+ /**
26
+ * Loop region end time in seconds.
27
+ */
28
+ end: number;
29
+
30
+ /**
31
+ * Called when the user clicks the loop toggle button.
32
+ */
33
+ onToggle: () => void;
34
+
35
+ /**
36
+ * Called when the user edits the loop start input.
37
+ * @param time - New start time in seconds.
38
+ */
39
+ onStartChange: (time: number) => void;
40
+
41
+ /**
42
+ * Called when the user edits the loop end input.
43
+ * @param time - New end time in seconds.
44
+ */
45
+ onEndChange: (time: number) => void;
46
+ }
47
+
48
+ /**
49
+ * Props for the {@link TransportBar} component.
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * const player = useTimelinePlayer(timelineRef, { loop: { enabled, start, end } });
54
+ *
55
+ * <TransportBar
56
+ * player={player}
57
+ * loop={{ enabled, start, end, onToggle, onStartChange, onEndChange }}
58
+ * />
59
+ * ```
60
+ */
61
+ export interface TransportBarProps {
62
+ /**
63
+ * The return value of `useTimelinePlayer()`.
64
+ * Provides all reactive state and control functions.
65
+ *
66
+ * @see {@link TimelinePlayerState}
67
+ */
68
+ player: TimelinePlayerState;
69
+
70
+ /**
71
+ * Optional loop controls.
72
+ * When provided, a loop toggle button (🔁) appears after the stop button,
73
+ * and start/end time inputs appear when the loop is active.
74
+ *
75
+ * @see {@link TransportBarLoopProps}
76
+ */
77
+ loop?: TransportBarLoopProps;
78
+
79
+ /**
80
+ * Playback rate options displayed as speed buttons.
81
+ * @default [0.25, 0.5, 1, 2, 4]
82
+ */
83
+ playRates?: number[];
84
+
85
+ /**
86
+ * Custom time format function.
87
+ * Receives the current time in seconds, should return a display string.
88
+ *
89
+ * @default Built-in `formatTime` (produces `'M:SS.mm'` format)
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * formatDisplayTime={(t) => `${(t * 1000).toFixed(0)} ms`}
94
+ * ```
95
+ */
96
+ formatDisplayTime?: (seconds: number) => string;
97
+
98
+ /**
99
+ * Additional CSS class name for the transport bar root element.
100
+ */
101
+ className?: string;
102
+
103
+ /**
104
+ * Inline styles for the transport bar root element.
105
+ */
106
+ style?: React.CSSProperties;
107
+ }
108
+
109
+ // ─────────────────────────────────────────────
110
+ // Default values
111
+ // ─────────────────────────────────────────────
112
+
113
+ const DEFAULT_PLAY_RATES = [0.25, 0.5, 1, 2, 4];
114
+
115
+ // ─────────────────────────────────────────────
116
+ // Component
117
+ // ─────────────────────────────────────────────
118
+
119
+ /**
120
+ * `TransportBar` is a pre-built playback control bar for use with `<Timeline>`.
121
+ *
122
+ * It renders transport controls (to-start, rewind, play/pause, forward, stop),
123
+ * a time display, playback rate buttons, and optionally a loop toggle with
124
+ * start/end inputs.
125
+ *
126
+ * Pair it with {@link useTimelinePlayer} to wire it to a `<Timeline>` ref:
127
+ *
128
+ * @example
129
+ * ```tsx
130
+ * const timelineRef = useRef<TimelineState>(null);
131
+ * const [loopOn, setLoopOn] = useState(false);
132
+ * const [loopStart, setLoopStart] = useState(1);
133
+ * const [loopEnd, setLoopEnd] = useState(3);
134
+ *
135
+ * const player = useTimelinePlayer(timelineRef, {
136
+ * loop: { enabled: loopOn, start: loopStart, end: loopEnd },
137
+ * });
138
+ *
139
+ * return (
140
+ * <>
141
+ * <TransportBar
142
+ * player={player}
143
+ * loop={{
144
+ * enabled: loopOn, start: loopStart, end: loopEnd,
145
+ * onToggle: () => setLoopOn((v) => !v),
146
+ * onStartChange: setLoopStart,
147
+ * onEndChange: setLoopEnd,
148
+ * }}
149
+ * />
150
+ * <Timeline ref={timelineRef} ... />
151
+ * </>
152
+ * );
153
+ * ```
154
+ *
155
+ * **Building your own UI?**
156
+ * Skip this component entirely and use just `useTimelinePlayer()` to get the state
157
+ * and handlers, then wire them to your own custom controls.
158
+ */
159
+ const TransportBar: React.FC<TransportBarProps> = ({
160
+ player,
161
+ loop,
162
+ playRates = DEFAULT_PLAY_RATES,
163
+ formatDisplayTime = formatTime,
164
+ className,
165
+ style,
166
+ }) => {
167
+ const {
168
+ isPlaying, currentTime, playRate,
169
+ play, pause, stop, toStart, rewind, forward, setPlayRate,
170
+ } = player;
171
+
172
+ return (
173
+ <div
174
+ className={`timeline-transport-bar${className ? ` ${className}` : ''}`}
175
+ style={style}
176
+ >
177
+ {/* ── Left: transport controls ── */}
178
+ <div className="timeline-transport-controls">
179
+ {/* To start */}
180
+ <button
181
+ className="timeline-transport-btn"
182
+ title="Return to start"
183
+ onClick={toStart}
184
+ >
185
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
186
+ <rect x="1" y="1" width="2" height="12" rx="1" />
187
+ <path d="M13 2.27L5.5 7 13 11.73V2.27z" />
188
+ </svg>
189
+ </button>
190
+
191
+ {/* Rewind */}
192
+ <button
193
+ className="timeline-transport-btn"
194
+ title="Rewind"
195
+ onClick={rewind}
196
+ >
197
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
198
+ <path d="M7.5 2.27L0 7l7.5 4.73V8.96L2.92 7l4.58-5zM14 2.27L6.5 7 14 11.73V2.27z" />
199
+ </svg>
200
+ </button>
201
+
202
+ {/* Play / Pause */}
203
+ {isPlaying ? (
204
+ <button
205
+ className="timeline-transport-btn timeline-transport-btn--primary"
206
+ title="Pause"
207
+ onClick={pause}
208
+ >
209
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
210
+ <rect x="2" y="1" width="4" height="12" rx="1" />
211
+ <rect x="8" y="1" width="4" height="12" rx="1" />
212
+ </svg>
213
+ </button>
214
+ ) : (
215
+ <button
216
+ className="timeline-transport-btn timeline-transport-btn--primary"
217
+ title="Play"
218
+ onClick={play}
219
+ >
220
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
221
+ <path d="M2 1.5l11 5.5-11 5.5z" />
222
+ </svg>
223
+ </button>
224
+ )}
225
+
226
+ {/* Forward */}
227
+ <button
228
+ className="timeline-transport-btn"
229
+ title="Forward"
230
+ onClick={forward}
231
+ >
232
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
233
+ <path d="M6.5 2.27V5.04L11.08 7 6.5 8.96v2.77L14 7 6.5 2.27zM0 2.27L7.5 7 0 11.73V2.27z" />
234
+ </svg>
235
+ </button>
236
+
237
+ {/* Stop */}
238
+ <button
239
+ className="timeline-transport-btn timeline-transport-btn--stop"
240
+ title="Stop"
241
+ onClick={stop}
242
+ >
243
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
244
+ <rect x="1" y="1" width="12" height="12" rx="2" />
245
+ </svg>
246
+ </button>
247
+
248
+ {/* ── Loop controls (optional) ── */}
249
+ {loop && (
250
+ <>
251
+ <div className="timeline-transport-divider" />
252
+
253
+ <button
254
+ className={`timeline-transport-btn timeline-transport-btn--loop${loop.enabled ? ' timeline-transport-btn--loop-active' : ''}`}
255
+ title={loop.enabled ? 'Disable loop' : 'Enable loop'}
256
+ onClick={loop.onToggle}
257
+ >
258
+ 🔁
259
+ </button>
260
+
261
+ {loop.enabled && (
262
+ <div className="timeline-transport-loop-inputs">
263
+ <input
264
+ className="timeline-transport-loop-input"
265
+ type="number"
266
+ step="0.1"
267
+ min="0"
268
+ value={loop.start}
269
+ title="Loop start (seconds)"
270
+ onChange={(e) => {
271
+ const v = parseFloat(e.target.value);
272
+ if (!isNaN(v) && v >= 0 && v < loop.end) loop.onStartChange(v);
273
+ }}
274
+ />
275
+ <span className="timeline-transport-loop-arrow">→</span>
276
+ <input
277
+ className="timeline-transport-loop-input"
278
+ type="number"
279
+ step="0.1"
280
+ min="0"
281
+ value={loop.end}
282
+ title="Loop end (seconds)"
283
+ onChange={(e) => {
284
+ const v = parseFloat(e.target.value);
285
+ if (!isNaN(v) && v > loop.start) loop.onEndChange(v);
286
+ }}
287
+ />
288
+ <span className="timeline-transport-loop-unit">s</span>
289
+ </div>
290
+ )}
291
+ </>
292
+ )}
293
+ </div>
294
+
295
+ {/* ── Centre: time display ── */}
296
+ <div className="timeline-transport-time">
297
+ <span className="timeline-transport-time-value">
298
+ {formatDisplayTime(currentTime)}
299
+ </span>
300
+ <span className="timeline-transport-time-label">TIME</span>
301
+ </div>
302
+
303
+ {/* ── Right: playback rate ── */}
304
+ <div className="timeline-transport-rate">
305
+ <span className="timeline-transport-rate-label">SPEED</span>
306
+ <div className="timeline-transport-rate-buttons">
307
+ {playRates.map((r) => (
308
+ <button
309
+ key={r}
310
+ className={`timeline-transport-rate-btn${playRate === r ? ' timeline-transport-rate-btn--active' : ''}`}
311
+ onClick={() => setPlayRate(r)}
312
+ >
313
+ {r}×
314
+ </button>
315
+ ))}
316
+ </div>
317
+ </div>
318
+ </div>
319
+ );
320
+ };
321
+
322
+ export default TransportBar;
@@ -0,0 +1,319 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import type { TimelineState } from '../../interface/timeline';
3
+
4
+ // ─────────────────────────────────────────────
5
+ // Utility
6
+ // ─────────────────────────────────────────────
7
+
8
+ /**
9
+ * Formats a time value in seconds to a `M:SS.mm` display string.
10
+ *
11
+ * @param seconds - Time in seconds (e.g. `75.4` → `'1:15.40'`).
12
+ * @returns Formatted time string.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * formatTime(0); // '0:00.00'
17
+ * formatTime(75.4); // '1:15.40'
18
+ * formatTime(3661.1); // '61:01.10'
19
+ * ```
20
+ */
21
+ export function formatTime(seconds: number): string {
22
+ const m = Math.floor(seconds / 60);
23
+ const s = Math.floor(seconds % 60);
24
+ const ms = Math.floor((seconds % 1) * 100);
25
+ return `${m}:${String(s).padStart(2, '0')}.${String(ms).padStart(2, '0')}`;
26
+ }
27
+
28
+ // ─────────────────────────────────────────────
29
+ // Public Types
30
+ // ─────────────────────────────────────────────
31
+
32
+ /**
33
+ * Loop configuration passed to {@link useTimelinePlayer}.
34
+ *
35
+ * When `enabled` is `true`, the hook automatically resets playback to `start`
36
+ * whenever the current time reaches `end`. The check uses React refs internally,
37
+ * so it never suffers from stale-closure issues even as values change.
38
+ */
39
+ export interface UseTimelinePlayerLoop {
40
+ /**
41
+ * Whether the loop is active. Safe to toggle while the timeline is playing.
42
+ */
43
+ enabled: boolean;
44
+
45
+ /**
46
+ * Loop region start time in seconds.
47
+ * Must be less than `end`.
48
+ */
49
+ start: number;
50
+
51
+ /**
52
+ * Loop region end time in seconds.
53
+ * Must be greater than `start`.
54
+ */
55
+ end: number;
56
+ }
57
+
58
+ /**
59
+ * Options for the {@link useTimelinePlayer} hook.
60
+ */
61
+ export interface UseTimelinePlayerOptions {
62
+ /**
63
+ * Number of seconds to jump when calling `rewind()` or `forward()`.
64
+ * @default 5
65
+ */
66
+ seekStep?: number;
67
+
68
+ /**
69
+ * Optional loop configuration.
70
+ * When provided, the hook handles the loop clock internally — no extra code needed.
71
+ *
72
+ * @see {@link UseTimelinePlayerLoop}
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * const player = useTimelinePlayer(ref, {
77
+ * loop: { enabled: loopOn, start: 1, end: 3 },
78
+ * });
79
+ * ```
80
+ */
81
+ loop?: UseTimelinePlayerLoop;
82
+ }
83
+
84
+ /**
85
+ * State and controls returned by {@link useTimelinePlayer}.
86
+ *
87
+ * Destructure what you need — use it with the built-in `<TransportBar>` component
88
+ * or wire it to your own custom controls.
89
+ *
90
+ * @example
91
+ * ```tsx
92
+ * const { isPlaying, currentTime, play, pause, stop } = useTimelinePlayer(ref);
93
+ *
94
+ * <button onClick={isPlaying ? pause : play}>
95
+ * {isPlaying ? '⏸' : '▶'}
96
+ * </button>
97
+ * <span>{formatTime(currentTime)}</span>
98
+ * ```
99
+ */
100
+ export interface TimelinePlayerState {
101
+ // ── Reactive state ─────────────────────────────────────────────────────────
102
+
103
+ /**
104
+ * Whether the timeline is currently playing.
105
+ * Updated automatically from engine events — no polling required.
106
+ */
107
+ isPlaying: boolean;
108
+
109
+ /**
110
+ * Current playback position in seconds.
111
+ * Updated on every engine tick — suitable for display.
112
+ */
113
+ currentTime: number;
114
+
115
+ /**
116
+ * Current playback rate multiplier (e.g. `1` = 1×, `2` = 2×).
117
+ * Updated when `setPlayRate` is called.
118
+ */
119
+ playRate: number;
120
+
121
+ // ── Playback controls ──────────────────────────────────────────────────────
122
+
123
+ /**
124
+ * Start playback from the current position.
125
+ * Plays to the end of the timeline and then stops.
126
+ */
127
+ play: () => void;
128
+
129
+ /**
130
+ * Pause playback at the current position.
131
+ */
132
+ pause: () => void;
133
+
134
+ /**
135
+ * Stop playback and reset the cursor to time `0`.
136
+ */
137
+ stop: () => void;
138
+
139
+ /**
140
+ * Pause and jump the cursor back to time `0`.
141
+ * Differs from `stop` only in semantics — both do the same thing currently.
142
+ */
143
+ toStart: () => void;
144
+
145
+ /**
146
+ * Jump backwards by `seekStep` seconds (clamped to 0).
147
+ */
148
+ rewind: () => void;
149
+
150
+ /**
151
+ * Jump forwards by `seekStep` seconds.
152
+ */
153
+ forward: () => void;
154
+
155
+ /**
156
+ * Jump to a specific time value (seconds).
157
+ *
158
+ * @param time - Target time in seconds.
159
+ */
160
+ setTime: (time: number) => void;
161
+
162
+ /**
163
+ * Change the playback rate multiplier.
164
+ *
165
+ * @param rate - Multiplier value (e.g. `0.5`, `1`, `2`, `4`).
166
+ */
167
+ setPlayRate: (rate: number) => void;
168
+ }
169
+
170
+ // ─────────────────────────────────────────────
171
+ // Hook
172
+ // ─────────────────────────────────────────────
173
+
174
+ /**
175
+ * `useTimelinePlayer` manages playback state and controls for a `<Timeline>` component.
176
+ *
177
+ * It attaches engine event listeners (`play`, `paused`, `setTimeByTick`) on mount,
178
+ * exposes reactive state (`isPlaying`, `currentTime`, `playRate`) and all control
179
+ * functions. Optionally handles loop playback internally via the `loop` option.
180
+ *
181
+ * @param ref - A React ref pointing to the `<Timeline>` component's imperative handle.
182
+ * @param options - Optional configuration (`seekStep`, `loop`).
183
+ * @returns {@link TimelinePlayerState} — reactive state + control functions.
184
+ *
185
+ * @example
186
+ * ```tsx
187
+ * const timelineRef = useRef<TimelineState>(null);
188
+ *
189
+ * const player = useTimelinePlayer(timelineRef, {
190
+ * seekStep: 5,
191
+ * loop: { enabled: loopOn, start: loopStart, end: loopEnd },
192
+ * });
193
+ *
194
+ * // Option A: use the pre-built TransportBar
195
+ * <TransportBar player={player} />
196
+ *
197
+ * // Option B: build your own UI
198
+ * <button onClick={player.isPlaying ? player.pause : player.play}>
199
+ * {player.isPlaying ? '⏸' : '▶'}
200
+ * </button>
201
+ * <span>{formatTime(player.currentTime)}</span>
202
+ * ```
203
+ */
204
+ export function useTimelinePlayer(
205
+ ref: React.RefObject<TimelineState>,
206
+ options: UseTimelinePlayerOptions = {},
207
+ ): TimelinePlayerState {
208
+ const { seekStep = 5, loop } = options;
209
+
210
+ const [isPlaying, setIsPlaying] = useState(false);
211
+ const [currentTime, setCurrentTime] = useState(0);
212
+ const [playRate, setPlayRateState] = useState(1);
213
+
214
+ // ── Refs for loop values ──
215
+ // Using refs instead of state in the tick callback avoids stale closures;
216
+ // we never need to re-create the listener when loop params change.
217
+ const loopEnabledRef = useRef(false);
218
+ const loopStartRef = useRef(0);
219
+ const loopEndRef = useRef(0);
220
+
221
+ // Keep refs in sync with options synchronously on every render
222
+ loopEnabledRef.current = loop?.enabled ?? false;
223
+ loopStartRef.current = loop?.start ?? 0;
224
+ loopEndRef.current = loop?.end ?? 0;
225
+
226
+ // ── Engine event listeners ──
227
+ useEffect(() => {
228
+ const timeline = ref.current;
229
+ if (!timeline) return;
230
+
231
+ const handlePlay = () => setIsPlaying(true);
232
+ const handlePaused = () => setIsPlaying(false);
233
+
234
+ const handleTick = ({ time }: { time: number }) => {
235
+ setCurrentTime(time);
236
+ // Loop: if loop is enabled and we've passed the end, snap back to start
237
+ if (loopEnabledRef.current && time >= loopEndRef.current) {
238
+ ref.current?.setTime(loopStartRef.current);
239
+ }
240
+ };
241
+
242
+ timeline.listener.on('play', handlePlay);
243
+ timeline.listener.on('paused', handlePaused);
244
+ timeline.listener.on('setTimeByTick', handleTick);
245
+
246
+ return () => {
247
+ timeline.listener.off('play', handlePlay);
248
+ timeline.listener.off('paused', handlePaused);
249
+ timeline.listener.off('setTimeByTick', handleTick);
250
+ };
251
+ }, [ref]); // ref object is stable across renders — runs once on mount
252
+
253
+ // ── Control callbacks ──
254
+ const play = useCallback(() => {
255
+ // When loop is enabled, playback must always start inside the loop zone.
256
+ // If the cursor is sitting before loopStart, snap it there first so the
257
+ // engine starts from the correct position (the tick handler already handles
258
+ // the case where the cursor is past loopEnd).
259
+ if (loopEnabledRef.current) {
260
+ const currentTime = ref.current?.getTime() ?? 0;
261
+ if (currentTime < loopStartRef.current) {
262
+ ref.current?.setTime(loopStartRef.current);
263
+ }
264
+ }
265
+ ref.current?.play({ autoEnd: true });
266
+ }, [ref]);
267
+
268
+ const pause = useCallback(() => {
269
+ ref.current?.pause();
270
+ }, [ref]);
271
+
272
+ const stop = useCallback(() => {
273
+ ref.current?.pause();
274
+ ref.current?.setTime(0);
275
+ setCurrentTime(0);
276
+ }, [ref]);
277
+
278
+ const toStart = useCallback(() => {
279
+ ref.current?.pause();
280
+ ref.current?.setTime(0);
281
+ setCurrentTime(0);
282
+ }, [ref]);
283
+
284
+ const rewind = useCallback(() => {
285
+ const cur = ref.current?.getTime() ?? 0;
286
+ const next = Math.max(0, cur - seekStep);
287
+ ref.current?.setTime(next);
288
+ setCurrentTime(next);
289
+ }, [ref, seekStep]);
290
+
291
+ const forward = useCallback(() => {
292
+ const cur = ref.current?.getTime() ?? 0;
293
+ ref.current?.setTime(cur + seekStep);
294
+ }, [ref, seekStep]);
295
+
296
+ const setTime = useCallback((time: number) => {
297
+ ref.current?.setTime(time);
298
+ setCurrentTime(time);
299
+ }, [ref]);
300
+
301
+ const setPlayRate = useCallback((rate: number) => {
302
+ ref.current?.setPlayRate(rate);
303
+ setPlayRateState(rate);
304
+ }, [ref]);
305
+
306
+ return {
307
+ isPlaying,
308
+ currentTime,
309
+ playRate,
310
+ play,
311
+ pause,
312
+ stop,
313
+ toStart,
314
+ rewind,
315
+ forward,
316
+ setTime,
317
+ setPlayRate,
318
+ };
319
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,17 @@
1
+ export * from './components/timeline';
2
+ export { default as CutOverlay } from './components/cut-overlay/CutOverlay';
3
+ export * from './components/cut-overlay/CutOverlay';
4
+ export { default as LoopZoneOverlay } from './components/loop-zone/LoopZoneOverlay';
5
+ export * from './components/loop-zone/LoopZoneOverlay';
6
+ export { default as TransportBar } from './components/transport/TransportBar';
7
+ export * from './components/transport/TransportBar';
8
+ export { useTimelinePlayer, formatTime } from './components/transport/useTimelinePlayer';
9
+ export type {
10
+ UseTimelinePlayerOptions,
11
+ UseTimelinePlayerLoop,
12
+ TimelinePlayerState,
13
+ } from './components/transport/useTimelinePlayer';
14
+ export { splitActionInRow } from './utils/deal_data';
15
+ export * from './interface/timeline';
16
+ export * from '@keplar-404/timeline-engine';
17
+
@@ -0,0 +1,13 @@
1
+ import { EditData, RequiredEditData } from "./timeline";
2
+
3
+ /** Common component parameters */
4
+ export interface CommonProp extends RequiredEditData {
5
+ /** Scale count */
6
+ scaleCount: number;
7
+ /** Set scale count */
8
+ setScaleCount: (scaleCount: number) => void;
9
+ /** Cursor time */
10
+ cursorTime: number;
11
+ /** Current timeline width */
12
+ timelineWidth: number;
13
+ }
@@ -0,0 +1,32 @@
1
+ export const PREFIX = `timeline-editor`;
2
+
3
+ /** Cursor time at start */
4
+ export const START_CURSOR_TIME = 0;
5
+ /** Default scale */
6
+ export const DEFAULT_SCALE = 1;
7
+ /** Default scale split count */
8
+ export const DEFAULT_SCALE_SPLIT_COUNT = 10;
9
+
10
+ /** Default scale display width */
11
+ export const DEFAULT_SCALE_WIDTH = 160;
12
+ /** Default scale start distance from left */
13
+ export const DEFAULT_START_LEFT = 20;
14
+ /** Default minimum movement in pixels */
15
+ export const DEFAULT_MOVE_GRID = 1;
16
+ /** Default adsorption distance in pixels */
17
+ export const DEFAULT_ADSORPTION_DISTANCE = 8;
18
+ /** Default action row height */
19
+ export const DEFAULT_ROW_HEIGHT = 32;
20
+
21
+ /** Minimum scale count */
22
+ export const MIN_SCALE_COUNT = 20;
23
+ /** Maximum scale count */
24
+ export const MAX_SCALE_COUNT = Infinity;
25
+ /** Number of scale marks to add each time */
26
+ export const ADD_SCALE_COUNT = 5;
27
+
28
+ /** Error messages */
29
+ export const ERROR = {
30
+ START_TIME_LESS_THEN_ZERO: 'Action start time cannot be less than 0!',
31
+ END_TIME_LESS_THEN_START_TIME: 'Action end time cannot be less then start time!',
32
+ }