@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,362 @@
1
+ import React, { FC, useLayoutEffect, useRef, useState, useCallback, useEffect } from 'react';
2
+ import { TimelineAction, TimelineRow } from '@keplar-404/timeline-engine';
3
+ import { CommonProp } from '../../interface/common_prop';
4
+ import { DEFAULT_ADSORPTION_DISTANCE, DEFAULT_MOVE_GRID } from '../../interface/const';
5
+ import { prefix } from '../../utils/deal_class_prefix';
6
+ import { getScaleCountByPixel, parserTimeToPixel, parserTimeToTransform, parserTransformToTime } from '../../utils/deal_data';
7
+ import { RowDnd } from '../row_rnd/row_rnd';
8
+ import {
9
+ RndDragCallback,
10
+ RndDragEndCallback,
11
+ RndDragStartCallback,
12
+ RndResizeCallback,
13
+ RndResizeEndCallback,
14
+ RndResizeStartCallback,
15
+ RowRndApi,
16
+ } from '../row_rnd/row_rnd_interface';
17
+ import { DragLineData } from './drag_lines';
18
+ import { useCrossRowDrag } from './cross_row_drag';
19
+ import './edit_action.css';
20
+
21
+ export type EditActionProps = CommonProp & {
22
+ row: TimelineRow;
23
+ action: TimelineAction;
24
+ dragLineData: DragLineData;
25
+ setEditorData: (params: TimelineRow[]) => void;
26
+ handleTime: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => number;
27
+ areaRef: React.RefObject<HTMLDivElement>;
28
+ /** Scroll delta, used for auto-scroll sync */
29
+ deltaScrollLeft?: (delta: number) => void;
30
+ /** Enable dragging a block to a different row */
31
+ enableCrossRowDrag?: boolean;
32
+ /** Show ghost element while dragging */
33
+ enableGhostPreview?: boolean;
34
+ };
35
+
36
+ export const EditAction: FC<EditActionProps> = ({
37
+ editorData,
38
+ row,
39
+ action,
40
+ effects,
41
+ rowHeight,
42
+ scale,
43
+ scaleWidth,
44
+ scaleSplitCount,
45
+ startLeft,
46
+ gridSnap,
47
+ disableDrag,
48
+
49
+ scaleCount,
50
+ maxScaleCount,
51
+ setScaleCount,
52
+ onActionMoveStart,
53
+ onActionMoving,
54
+ onActionMoveEnd,
55
+ onActionResizeStart,
56
+ onActionResizeEnd,
57
+ onActionResizing,
58
+
59
+ dragLineData,
60
+ setEditorData,
61
+ onClickAction,
62
+ onClickActionOnly,
63
+ onDoubleClickAction,
64
+ onContextMenuAction,
65
+ getActionRender,
66
+ handleTime,
67
+ areaRef,
68
+ deltaScrollLeft,
69
+ enableCrossRowDrag = false,
70
+ enableGhostPreview = true,
71
+ }) => {
72
+ const rowRnd = useRef<RowRndApi>(null);
73
+ const isDragWhenClick = useRef(false);
74
+ // Track if we used the cross-row system (to suppress the native drag commit)
75
+ const isCrossRowDragging = useRef(false);
76
+ // Visual glow while this specific block is being cross-row dragged
77
+ const [isGlowing, setIsGlowing] = useState(false);
78
+
79
+ const { id, maxEnd, minStart, end, start, selected, flexible = true, movable = true, effectId } = action;
80
+
81
+ // ───── pixel bounds ─────
82
+ const leftLimit = parserTimeToPixel(minStart || 0, { startLeft, scale, scaleWidth });
83
+ const rightLimit = Math.min(
84
+ maxScaleCount * scaleWidth + startLeft,
85
+ parserTimeToPixel(maxEnd || Number.MAX_VALUE, { startLeft, scale, scaleWidth }),
86
+ );
87
+
88
+ // ───── transform state ─────
89
+ const [transform, setTransform] = useState(() =>
90
+ parserTimeToTransform({ start, end }, { startLeft, scale, scaleWidth }),
91
+ );
92
+
93
+ useLayoutEffect(() => {
94
+ setTransform(parserTimeToTransform({ start, end }, { startLeft, scale, scaleWidth }));
95
+ }, [end, start, startLeft, scaleWidth, scale]);
96
+
97
+ const gridSize = scaleWidth / scaleSplitCount;
98
+
99
+ // ───── class names ─────
100
+ const classNames = ['action'];
101
+ if (movable) classNames.push('action-movable');
102
+ if (selected) classNames.push('action-selected');
103
+ if (flexible) classNames.push('action-flexible');
104
+ if (effects[effectId]) classNames.push(`action-effect-${effectId}`);
105
+ if (isGlowing) classNames.push('action-cross-row-dragging');
106
+
107
+ // ───── cross-row drag context ─────
108
+ let crossRowDrag: ReturnType<typeof useCrossRowDrag> | null = null;
109
+ try {
110
+ crossRowDrag = useCrossRowDrag();
111
+ } catch {
112
+ // not inside provider, cross-row drag is disabled
113
+ }
114
+
115
+ // ───── scale count helper ─────
116
+ const handleScaleCount = (left: number, width: number) => {
117
+ const curScaleCount = getScaleCountByPixel(left + width, { startLeft, scaleCount, scaleWidth });
118
+ if (curScaleCount !== scaleCount) setScaleCount(curScaleCount);
119
+ };
120
+
121
+ // ─────────────────────────────────────────────────────────────
122
+ // Native (same-row) drag callbacks
123
+ // ─────────────────────────────────────────────────────────────
124
+ const handleDragStart: RndDragStartCallback = () => {
125
+ if (isCrossRowDragging.current) return;
126
+ onActionMoveStart && onActionMoveStart({ action, row });
127
+ };
128
+
129
+ const handleDrag: RndDragCallback = ({ left, width }) => {
130
+ if (isCrossRowDragging.current) return false;
131
+ isDragWhenClick.current = true;
132
+
133
+ if (onActionMoving) {
134
+ const { start, end } = parserTransformToTime({ left, width }, { scaleWidth, scale, startLeft });
135
+ const result = onActionMoving({ action, row, start, end });
136
+ if (result === false) return false;
137
+ }
138
+ setTransform({ left, width });
139
+ handleScaleCount(left, width);
140
+ };
141
+
142
+ const handleDragEnd: RndDragEndCallback = ({ left, width }) => {
143
+ if (isCrossRowDragging.current) {
144
+ // Cross-row system handles the commit; just reset the interactjs element position
145
+ setTransform(parserTimeToTransform({ start, end }, { startLeft, scale, scaleWidth }));
146
+ return;
147
+ }
148
+
149
+ const { start: newStart, end: newEnd } = parserTransformToTime({ left, width }, { scaleWidth, scale, startLeft });
150
+
151
+ const rowItem = editorData.find((item: TimelineRow) => item.id === row.id);
152
+ if (!rowItem) return;
153
+ const foundAction = rowItem.actions.find((item: TimelineAction) => item.id === id);
154
+ if (!foundAction) return;
155
+ foundAction.start = newStart;
156
+ foundAction.end = newEnd;
157
+ setEditorData([...editorData]);
158
+
159
+ if (onActionMoveEnd) onActionMoveEnd({ action, row, start: newStart, end: newEnd });
160
+ };
161
+
162
+ const handleResizeStart: RndResizeStartCallback = (dir) => {
163
+ onActionResizeStart && onActionResizeStart({ action, row, dir });
164
+ };
165
+
166
+ const handleResizing: RndResizeCallback = (dir, { left, width }) => {
167
+ isDragWhenClick.current = true;
168
+ if (onActionResizing) {
169
+ const { start, end } = parserTransformToTime({ left, width }, { scaleWidth, scale, startLeft });
170
+ const result = onActionResizing({ action, row, start, end, dir });
171
+ if (result === false) return false;
172
+ }
173
+ setTransform({ left, width });
174
+ handleScaleCount(left, width);
175
+ };
176
+
177
+ const handleResizeEnd: RndResizeEndCallback = (dir, { left, width }) => {
178
+ const { start: newStart, end: newEnd } = parserTransformToTime({ left, width }, { scaleWidth, scale, startLeft });
179
+
180
+ const rowItem = editorData.find((item: TimelineRow) => item.id === row.id);
181
+ if (!rowItem) return;
182
+ const foundAction = rowItem.actions.find((item: TimelineAction) => item.id === id);
183
+ if (!foundAction) return;
184
+ foundAction.start = newStart;
185
+ foundAction.end = newEnd;
186
+ setEditorData([...editorData]);
187
+
188
+ if (onActionResizeEnd) onActionResizeEnd({ action, row, start: newStart, end: newEnd, dir });
189
+ };
190
+
191
+ // ─────────────────────────────────────────────────────────────
192
+ // Cross-row block drag — initiated from the whole block body
193
+ // ─────────────────────────────────────────────────────────────
194
+ const handleBlockMouseDown = useCallback(
195
+ (e: React.MouseEvent<HTMLDivElement>) => {
196
+ // Only fire for cross-row drag when enabled and not from a resize handle
197
+ if (!enableCrossRowDrag || !movable || disableDrag) return;
198
+ if (!crossRowDrag) return;
199
+
200
+ // Don't steal right-handle resize clicks (resize handles have their own selector)
201
+ const target = e.target as HTMLElement;
202
+ if (target.classList.contains(prefix('action-left-stretch')) || target.classList.contains(prefix('action-right-stretch'))) {
203
+ return;
204
+ }
205
+
206
+ e.preventDefault();
207
+ e.stopPropagation();
208
+
209
+ isDragWhenClick.current = false;
210
+ isCrossRowDragging.current = false;
211
+
212
+ // Measure the DOM element that RowDnd wraps
213
+ const actionEl = (e.currentTarget as HTMLDivElement).closest('[data-width]') as HTMLElement | null;
214
+ const ghostW = actionEl ? parseFloat(actionEl.dataset.width || '0') || actionEl.offsetWidth : transform.width;
215
+ const ghostH = rowHeight;
216
+
217
+ // Where the user clicked relative to the block
218
+ const blockRect = e.currentTarget.getBoundingClientRect();
219
+ const grabOffsetX = e.clientX - blockRect.left;
220
+
221
+ let hasMoved = false;
222
+ let committed = false;
223
+ setIsGlowing(false);
224
+
225
+ const onMouseMove = (me: MouseEvent) => {
226
+ if (!hasMoved && Math.abs(me.clientX - e.clientX) < 4 && Math.abs(me.clientY - e.clientY) < 4) return;
227
+ hasMoved = true;
228
+ isDragWhenClick.current = true;
229
+
230
+ if (!isCrossRowDragging.current) {
231
+ isCrossRowDragging.current = true;
232
+ setIsGlowing(true);
233
+ crossRowDrag!.startCrossRowDrag({
234
+ action,
235
+ sourceRow: row,
236
+ ghostWidth: ghostW,
237
+ ghostHeight: ghostH,
238
+ grabOffsetX,
239
+ initialX: me.clientX,
240
+ initialY: me.clientY,
241
+ });
242
+ } else {
243
+ // Update cursor position in context by firing a custom event the EditArea listens to
244
+ crossRowDrag!.startCrossRowDrag({
245
+ action,
246
+ sourceRow: row,
247
+ ghostWidth: ghostW,
248
+ ghostHeight: ghostH,
249
+ grabOffsetX,
250
+ initialX: me.clientX,
251
+ initialY: me.clientY,
252
+ });
253
+ }
254
+ };
255
+
256
+ const onMouseUp = (me: MouseEvent) => {
257
+ document.removeEventListener('mousemove', onMouseMove);
258
+ document.removeEventListener('mouseup', onMouseUp);
259
+ setIsGlowing(false);
260
+
261
+ if (!isCrossRowDragging.current || !hasMoved) {
262
+ isCrossRowDragging.current = false;
263
+ crossRowDrag!.endCrossRowDrag();
264
+ return;
265
+ }
266
+
267
+ committed = true;
268
+ isCrossRowDragging.current = false;
269
+
270
+ // Delegate the commit to EditArea via context
271
+ crossRowDrag!.onCommit(action, row, row /* placeholder; EditArea resolves target */, me.clientX, me.clientY);
272
+ crossRowDrag!.endCrossRowDrag();
273
+ };
274
+
275
+ document.addEventListener('mousemove', onMouseMove);
276
+ document.addEventListener('mouseup', onMouseUp);
277
+ },
278
+ [enableCrossRowDrag, movable, disableDrag, crossRowDrag, action, row, transform.width, rowHeight, startLeft, scaleWidth, scale],
279
+ );
280
+
281
+ // ───── render ─────
282
+ const nowAction = {
283
+ ...action,
284
+ ...parserTransformToTime({ left: transform.left, width: transform.width }, { startLeft, scaleWidth, scale }),
285
+ };
286
+
287
+ const nowRow: TimelineRow = {
288
+ ...row,
289
+ actions: [...row.actions],
290
+ };
291
+ if (row.actions.includes(action)) {
292
+ nowRow.actions[row.actions.indexOf(action)] = nowAction;
293
+ }
294
+
295
+ return (
296
+ <RowDnd
297
+ ref={rowRnd}
298
+ parentRef={areaRef}
299
+ start={startLeft}
300
+ left={transform.left}
301
+ width={transform.width}
302
+ grid={(gridSnap && gridSize) || DEFAULT_MOVE_GRID}
303
+ adsorptionDistance={
304
+ gridSnap ? Math.max((gridSize || DEFAULT_MOVE_GRID) / 2, DEFAULT_ADSORPTION_DISTANCE) : DEFAULT_ADSORPTION_DISTANCE
305
+ }
306
+ adsorptionPositions={dragLineData.assistPositions}
307
+ bounds={{ left: leftLimit, right: rightLimit }}
308
+ edges={{
309
+ left: !disableDrag && flexible && `.${prefix('action-left-stretch')}`,
310
+ right: !disableDrag && flexible && `.${prefix('action-right-stretch')}`,
311
+ }}
312
+ enableDragging={!disableDrag && movable && !enableCrossRowDrag}
313
+ enableResizing={!disableDrag && flexible}
314
+ onDragStart={handleDragStart}
315
+ onDrag={handleDrag}
316
+ onDragEnd={handleDragEnd}
317
+ onResizeStart={handleResizeStart}
318
+ onResize={handleResizing}
319
+ onResizeEnd={handleResizeEnd}
320
+ deltaScrollLeft={deltaScrollLeft}
321
+ >
322
+ <div
323
+ onMouseDown={(e) => {
324
+ isDragWhenClick.current = false;
325
+ // Initiate cross-row drag from the whole block
326
+ if (enableCrossRowDrag && movable && !disableDrag) {
327
+ handleBlockMouseDown(e);
328
+ }
329
+ }}
330
+ onClick={(e) => {
331
+ let time: number | undefined;
332
+ if (onClickAction) {
333
+ time = handleTime(e);
334
+ onClickAction(e, { row, action, time });
335
+ }
336
+ if (!isDragWhenClick.current && onClickActionOnly) {
337
+ if (!time) time = handleTime(e);
338
+ onClickActionOnly(e, { row, action, time });
339
+ }
340
+ }}
341
+ onDoubleClick={(e) => {
342
+ if (onDoubleClickAction) {
343
+ const time = handleTime(e);
344
+ onDoubleClickAction(e, { row, action, time });
345
+ }
346
+ }}
347
+ onContextMenu={(e) => {
348
+ if (onContextMenuAction) {
349
+ const time = handleTime(e);
350
+ onContextMenuAction(e, { row, action, time });
351
+ }
352
+ }}
353
+ className={prefix((classNames || []).join(' '))}
354
+ style={{ height: rowHeight, cursor: enableCrossRowDrag && movable && !disableDrag ? 'grab' : undefined }}
355
+ >
356
+ {getActionRender && getActionRender(nowAction, nowRow)}
357
+ {flexible && <div className={prefix('action-left-stretch')} />}
358
+ {flexible && <div className={prefix('action-right-stretch')} />}
359
+ </div>
360
+ </RowDnd>
361
+ );
362
+ };
@@ -0,0 +1,24 @@
1
+ .timeline-editor:hover .timeline-editor-edit-area .ReactVirtualized__Grid::-webkit-scrollbar {
2
+ height: 4px;
3
+ }
4
+ .timeline-editor-edit-area {
5
+ flex: 1 1 auto;
6
+ margin-top: 10px;
7
+ overflow: hidden;
8
+ position: relative;
9
+ }
10
+ .timeline-editor-edit-area .ReactVirtualized__Grid {
11
+ outline: none !important;
12
+ overflow: overlay !important;
13
+ }
14
+ .timeline-editor-edit-area .ReactVirtualized__Grid::-webkit-scrollbar {
15
+ width: 0;
16
+ height: 0;
17
+ }
18
+ .timeline-editor-edit-area .ReactVirtualized__Grid::-webkit-scrollbar-track {
19
+ background-color: transparent !important;
20
+ }
21
+ .timeline-editor-edit-area .ReactVirtualized__Grid::-webkit-scrollbar-thumb {
22
+ background: #313132;
23
+ border-radius: 16px;
24
+ }