@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,227 @@
1
+ import { ITimelineEngine, TimelineEngine, TimelineRow } from '@keplar-404/timeline-engine';
2
+ import React, { useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
3
+ import { ScrollSync } from 'react-virtualized';
4
+ import { MIN_SCALE_COUNT, PREFIX, START_CURSOR_TIME } from '../interface/const';
5
+ import { TimelineEditor, TimelineState } from '../interface/timeline';
6
+ import { checkProps } from '../utils/check_props';
7
+ import { getScaleCountByRows, parserPixelToTime, parserTimeToPixel } from '../utils/deal_data';
8
+ import { Cursor } from './cursor/cursor';
9
+ import { EditArea } from './edit_area/edit_area';
10
+ import { TimeArea } from './time_area/time_area';
11
+ import './timeline.css';
12
+
13
+ export const Timeline = React.forwardRef<TimelineState, TimelineEditor>((props, ref) => {
14
+ const checkedProps = checkProps(props);
15
+ const { style } = props;
16
+ let {
17
+ effects,
18
+ editorData: data,
19
+ scrollTop,
20
+ autoScroll,
21
+ hideCursor,
22
+ disableDrag,
23
+ scale,
24
+ scaleWidth,
25
+ startLeft,
26
+ minScaleCount,
27
+ maxScaleCount,
28
+ onChange,
29
+ engine,
30
+ autoReRender = true,
31
+ onScroll: onScrollVertical,
32
+ } = checkedProps;
33
+
34
+ const engineRef = useRef<ITimelineEngine>(engine || new TimelineEngine());
35
+ const domRef = useRef<HTMLDivElement>(null);
36
+ const areaRef = useRef<HTMLDivElement>(null);
37
+
38
+ const scrollSync = useRef<ScrollSync>(null);
39
+
40
+ // Editor data
41
+ const [editorData, setEditorData] = useState(data);
42
+ // Scale count
43
+ const [scaleCount, setScaleCount] = useState(MIN_SCALE_COUNT);
44
+ // Cursor time
45
+ const [cursorTime, setCursorTime] = useState(START_CURSOR_TIME);
46
+ // Whether it is currently playing
47
+ const [isPlaying, setIsPlaying] = useState(false);
48
+ // Current timeline width
49
+ const [width, setWidth] = useState(Number.MAX_SAFE_INTEGER);
50
+
51
+ /** Listen for data changes */
52
+ useLayoutEffect(() => {
53
+ handleSetScaleCount(getScaleCountByRows(data, { scale }));
54
+ setEditorData(data);
55
+ }, [data, minScaleCount, maxScaleCount, scale]);
56
+
57
+ useEffect(() => {
58
+ engineRef.current.effects = effects;
59
+ }, [effects]);
60
+
61
+ useEffect(() => {
62
+ engineRef.current.data = editorData;
63
+ }, [editorData]);
64
+
65
+ useEffect(() => {
66
+ autoReRender && engineRef.current.reRender();
67
+ }, [editorData]);
68
+
69
+ // deprecated
70
+ useEffect(() => {
71
+ scrollSync.current && scrollSync.current.setState({ scrollTop: scrollTop });
72
+ }, [scrollTop]);
73
+
74
+ /** Dynamically set scale count */
75
+ const handleSetScaleCount = (value: number) => {
76
+ const data = Math.min(maxScaleCount, Math.max(minScaleCount, value));
77
+ setScaleCount(data);
78
+ };
79
+
80
+ /** Handle active data changes */
81
+ const handleEditorDataChange = (editorData: TimelineRow[]) => {
82
+ const result = onChange?.(editorData);
83
+ if (result !== false) {
84
+ engineRef.current.data = editorData;
85
+ autoReRender && engineRef.current.reRender();
86
+ }
87
+ };
88
+
89
+ /** Handle cursor */
90
+ const handleSetCursor = (param: { left?: number; time?: number; updateTime?: boolean }) => {
91
+ let { left, time = 0, updateTime = true } = param;
92
+ if (typeof left === 'undefined' && typeof time === 'undefined') return false;
93
+
94
+ if (typeof time === 'undefined') {
95
+ if (typeof left === 'undefined') left = parserTimeToPixel(time, { startLeft, scale, scaleWidth });
96
+ time = parserPixelToTime(left, { startLeft, scale, scaleWidth });
97
+ }
98
+
99
+ let result = true;
100
+ if (updateTime) {
101
+ result = engineRef.current.setTime(time);
102
+ autoReRender && engineRef.current.reRender();
103
+ }
104
+ result && setCursorTime(time);
105
+ return result;
106
+ };
107
+
108
+ /** Set scrollLeft */
109
+ const handleDeltaScrollLeft = (delta: number) => {
110
+ // When exceeding the maximum distance, prohibit automatic scrolling
111
+ const data = (scrollSync.current?.state?.scrollLeft ?? 0) + delta;
112
+ if (data > scaleCount * (scaleWidth - 1) + startLeft - width) return;
113
+ scrollSync.current && scrollSync.current.setState({ scrollLeft: Math.max(scrollSync.current.state.scrollLeft + delta, 0) });
114
+ };
115
+
116
+ // Handle engine-related data
117
+ useEffect(() => {
118
+ const handleTime = ({ time }: { time: number }) => {
119
+ handleSetCursor({ time, updateTime: false });
120
+ };
121
+ const handlePlay = () => setIsPlaying(true);
122
+ const handlePaused = () => setIsPlaying(false);
123
+ engineRef.current.on('setTimeByTick', handleTime);
124
+ engineRef.current.on('play', handlePlay);
125
+ engineRef.current.on('paused', handlePaused);
126
+ }, []);
127
+
128
+ // ref data
129
+ useImperativeHandle(ref, () => ({
130
+ get target() {
131
+ return domRef.current;
132
+ },
133
+ get listener() {
134
+ return engineRef.current;
135
+ },
136
+ get isPlaying() {
137
+ return engineRef.current.isPlaying;
138
+ },
139
+ get isPaused() {
140
+ return engineRef.current.isPaused;
141
+ },
142
+ setPlayRate: engineRef.current.setPlayRate.bind(engineRef.current),
143
+ getPlayRate: engineRef.current.getPlayRate.bind(engineRef.current),
144
+ setTime: (time: number) => handleSetCursor({ time }),
145
+ getTime: engineRef.current.getTime.bind(engineRef.current),
146
+ reRender: engineRef.current.reRender.bind(engineRef.current),
147
+ play: (param: Parameters<TimelineState['play']>[0]) => engineRef.current.play({ ...param }),
148
+ pause: engineRef.current.pause.bind(engineRef.current),
149
+ setScrollLeft: (val) => {
150
+ scrollSync.current && scrollSync.current.setState({ scrollLeft: Math.max(val, 0) });
151
+ },
152
+ setScrollTop: (val) => {
153
+ scrollSync.current && scrollSync.current.setState({ scrollTop: Math.max(val, 0) });
154
+ },
155
+ }));
156
+
157
+ // Listen for width changes in the timeline area
158
+ useEffect(() => {
159
+ if (areaRef.current) {
160
+ const resizeObserver = new ResizeObserver(() => {
161
+ if (!areaRef.current) return;
162
+ setWidth(areaRef.current.getBoundingClientRect().width);
163
+ });
164
+ resizeObserver.observe(areaRef.current!);
165
+ return () => {
166
+ resizeObserver && resizeObserver.disconnect();
167
+ };
168
+ }
169
+ }, []);
170
+
171
+ return (
172
+ <div ref={domRef} style={style} className={`${PREFIX} ${disableDrag ? PREFIX + '-disable' : ''}`}>
173
+ <ScrollSync ref={scrollSync}>
174
+ {({ scrollLeft, scrollTop, onScroll }) => (
175
+ <>
176
+ <TimeArea
177
+ {...checkedProps}
178
+ timelineWidth={width}
179
+ disableDrag={disableDrag || isPlaying}
180
+ setCursor={handleSetCursor}
181
+ cursorTime={cursorTime}
182
+ editorData={editorData}
183
+ scaleCount={scaleCount}
184
+ setScaleCount={handleSetScaleCount}
185
+ onScroll={onScroll}
186
+ scrollLeft={scrollLeft}
187
+ />
188
+ <EditArea
189
+ {...checkedProps}
190
+ timelineWidth={width}
191
+ ref={(ref) => { (areaRef.current as any) = ref?.domRef.current; }}
192
+ disableDrag={disableDrag || isPlaying}
193
+ editorData={editorData}
194
+ cursorTime={cursorTime}
195
+ scaleCount={scaleCount}
196
+ setScaleCount={handleSetScaleCount}
197
+ scrollTop={scrollTop}
198
+ scrollLeft={scrollLeft}
199
+ setEditorData={handleEditorDataChange}
200
+ deltaScrollLeft={autoScroll ? handleDeltaScrollLeft : () => {}}
201
+ onScroll={(params) => {
202
+ onScroll(params);
203
+ onScrollVertical && onScrollVertical(params);
204
+ }}
205
+ />
206
+ {!hideCursor && (
207
+ <Cursor
208
+ {...checkedProps}
209
+ timelineWidth={width}
210
+ disableDrag={isPlaying}
211
+ scrollLeft={scrollLeft}
212
+ scaleCount={scaleCount}
213
+ setScaleCount={handleSetScaleCount}
214
+ setCursor={handleSetCursor}
215
+ cursorTime={cursorTime}
216
+ editorData={editorData}
217
+ areaRef={areaRef}
218
+ scrollSync={scrollSync}
219
+ deltaScrollLeft={autoScroll ? handleDeltaScrollLeft : undefined}
220
+ />
221
+ )}
222
+ </>
223
+ )}
224
+ </ScrollSync>
225
+ </div>
226
+ );
227
+ });
@@ -0,0 +1,171 @@
1
+ .timeline-transport-bar {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ padding: 0 16px;
6
+ height: 48px;
7
+ background: rgba(10, 10, 20, 0.85);
8
+ backdrop-filter: blur(12px);
9
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
10
+ gap: 16px;
11
+ box-sizing: border-box;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif;
13
+ }
14
+ .timeline-transport-controls {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 4px;
18
+ }
19
+ .timeline-transport-btn {
20
+ display: inline-flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ width: 30px;
24
+ height: 30px;
25
+ border-radius: 6px;
26
+ border: 1px solid rgba(255, 255, 255, 0.07);
27
+ background: rgba(255, 255, 255, 0.03);
28
+ color: #64748b;
29
+ cursor: pointer;
30
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
31
+ }
32
+ .timeline-transport-btn:hover {
33
+ background: rgba(255, 255, 255, 0.08);
34
+ color: #94a3b8;
35
+ border-color: rgba(255, 255, 255, 0.12);
36
+ }
37
+ .timeline-transport-btn--primary {
38
+ color: #c4c9d4;
39
+ }
40
+ .timeline-transport-btn--primary:hover {
41
+ background: rgba(255, 255, 255, 0.1);
42
+ color: #e2e8f0;
43
+ }
44
+ .timeline-transport-btn--stop:hover {
45
+ background: rgba(239, 68, 68, 0.12);
46
+ border-color: rgba(239, 68, 68, 0.3);
47
+ color: #ef4444;
48
+ }
49
+ .timeline-transport-btn--loop {
50
+ font-size: 14px;
51
+ line-height: 1;
52
+ width: auto;
53
+ padding: 0 6px;
54
+ color: #4b5563;
55
+ }
56
+ .timeline-transport-btn--loop:hover {
57
+ background: rgba(16, 185, 129, 0.1);
58
+ border-color: rgba(16, 185, 129, 0.3);
59
+ color: #10b981;
60
+ }
61
+ .timeline-transport-btn--loop-active {
62
+ background: rgba(16, 185, 129, 0.18) !important;
63
+ border-color: rgba(16, 185, 129, 0.5) !important;
64
+ color: #10b981 !important;
65
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.25) !important;
66
+ }
67
+ .timeline-transport-divider {
68
+ width: 1px;
69
+ height: 20px;
70
+ background: rgba(255, 255, 255, 0.08);
71
+ margin: 0 4px;
72
+ flex-shrink: 0;
73
+ }
74
+ .timeline-transport-loop-inputs {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 4px;
78
+ margin-left: 4px;
79
+ }
80
+ .timeline-transport-loop-input {
81
+ width: 52px;
82
+ background: rgba(0, 0, 0, 0.35);
83
+ border: 1px solid rgba(16, 185, 129, 0.3);
84
+ border-radius: 5px;
85
+ color: #10b981;
86
+ font-size: 12px;
87
+ font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
88
+ font-weight: 600;
89
+ padding: 2px 6px;
90
+ text-align: center;
91
+ outline: none;
92
+ transition: border-color 0.12s;
93
+ }
94
+ .timeline-transport-loop-input:focus {
95
+ border-color: rgba(16, 185, 129, 0.7);
96
+ box-shadow: 0 0 6px rgba(16, 185, 129, 0.2);
97
+ }
98
+ .timeline-transport-loop-input::-webkit-inner-spin-button,
99
+ .timeline-transport-loop-input::-webkit-outer-spin-button {
100
+ -webkit-appearance: none;
101
+ margin: 0;
102
+ }
103
+ .timeline-transport-loop-arrow {
104
+ font-size: 11px;
105
+ color: #334155;
106
+ }
107
+ .timeline-transport-loop-unit {
108
+ font-size: 10px;
109
+ color: #334155;
110
+ font-family: 'SF Mono', 'Cascadia Code', monospace;
111
+ }
112
+ .timeline-transport-time {
113
+ display: flex;
114
+ flex-direction: column;
115
+ align-items: center;
116
+ gap: 1px;
117
+ min-width: 100px;
118
+ }
119
+ .timeline-transport-time-value {
120
+ font-size: 22px;
121
+ font-weight: 700;
122
+ font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
123
+ color: #e2e8f0;
124
+ letter-spacing: 0.05em;
125
+ line-height: 1;
126
+ }
127
+ .timeline-transport-time-label {
128
+ font-size: 9px;
129
+ font-weight: 600;
130
+ letter-spacing: 0.15em;
131
+ color: #475569;
132
+ text-transform: uppercase;
133
+ }
134
+ .timeline-transport-rate {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 8px;
138
+ }
139
+ .timeline-transport-rate-label {
140
+ font-size: 9px;
141
+ font-weight: 600;
142
+ letter-spacing: 0.15em;
143
+ color: #475569;
144
+ text-transform: uppercase;
145
+ }
146
+ .timeline-transport-rate-buttons {
147
+ display: flex;
148
+ gap: 3px;
149
+ }
150
+ .timeline-transport-rate-btn {
151
+ padding: 3px 8px;
152
+ border-radius: 5px;
153
+ border: 1px solid rgba(255, 255, 255, 0.07);
154
+ background: rgba(255, 255, 255, 0.03);
155
+ color: #475569;
156
+ font-size: 11px;
157
+ font-weight: 600;
158
+ font-family: 'SF Mono', 'Cascadia Code', monospace;
159
+ cursor: pointer;
160
+ transition: all 0.12s;
161
+ }
162
+ .timeline-transport-rate-btn:hover {
163
+ background: rgba(255, 255, 255, 0.07);
164
+ color: #94a3b8;
165
+ }
166
+ .timeline-transport-rate-btn--active {
167
+ background: rgba(59, 130, 246, 0.15);
168
+ border-color: rgba(59, 130, 246, 0.4);
169
+ color: #3b82f6;
170
+ box-shadow: 0 0 6px rgba(59, 130, 246, 0.15);
171
+ }