@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,461 @@
1
+ import React, { useCallback } from 'react';
2
+ import './LoopZoneOverlay.css';
3
+
4
+ // ─────────────────────────────────────────────
5
+ // Public Types
6
+ // ─────────────────────────────────────────────
7
+
8
+ /**
9
+ * Visual and behavioral configuration for the {@link LoopZoneOverlay} component.
10
+ * All properties are optional — defaults produce a green-themed loop zone.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * const loopConfig: LoopZoneConfig = {
15
+ * bandColor: '#a855f7',
16
+ * bandOpacity: 0.1,
17
+ * handleColor: '#c084fc',
18
+ * showBoundaryLines: true,
19
+ * };
20
+ * <LoopZoneOverlay config={loopConfig} ... />
21
+ * ```
22
+ */
23
+ export interface LoopZoneConfig {
24
+ /**
25
+ * CSS color of the loop region shaded band.
26
+ * @default '#10b981'
27
+ */
28
+ bandColor?: string;
29
+
30
+ /**
31
+ * Opacity of the loop region band (0–1).
32
+ * Keep this low (< 0.15) so underlying blocks remain visible.
33
+ * @default 0.07
34
+ */
35
+ bandOpacity?: number;
36
+
37
+ /**
38
+ * Border/stripe color used on the band's edges and stripe pattern.
39
+ * Falls back to `bandColor` when not set.
40
+ */
41
+ bandBorderColor?: string;
42
+
43
+ /**
44
+ * CSS color of the default drag handle grip pills.
45
+ * Falls back to `bandColor` when not set.
46
+ * Has no effect when `renderHandle` is provided.
47
+ */
48
+ handleColor?: string;
49
+
50
+ /**
51
+ * Whether to render the dashed vertical boundary lines
52
+ * extending through the edit rows below the time ruler.
53
+ * @default true
54
+ */
55
+ showBoundaryLines?: boolean;
56
+
57
+ /**
58
+ * CSS color of the dashed boundary lines.
59
+ * @default 'rgba(16, 185, 129, 0.4)'
60
+ */
61
+ boundaryLineColor?: string;
62
+ }
63
+
64
+ /**
65
+ * Props passed into a custom handle renderer supplied via `LoopZoneOverlayProps.renderHandle`.
66
+ *
67
+ * Attach `onMouseDown` to your draggable element to enable the drag interaction.
68
+ * The overlay provides all pixel math — your renderer only needs to call `onMouseDown`.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * renderHandle={({ type, time, onMouseDown }) => (
73
+ * <div
74
+ * onMouseDown={onMouseDown}
75
+ * style={{ cursor: 'ew-resize', background: 'purple' }}
76
+ * title={`${type === 'start' ? 'Loop start' : 'Loop end'}: ${time.toFixed(2)}s`}
77
+ * >
78
+ * {type === 'start' ? '⟨' : '⟩'}
79
+ * </div>
80
+ * )}
81
+ * ```
82
+ */
83
+ export interface LoopHandleRenderProps {
84
+ /**
85
+ * Which boundary this handle controls.
86
+ * Use to differentiate styling between the two handles.
87
+ */
88
+ type: 'start' | 'end';
89
+
90
+ /**
91
+ * The current time value (in seconds) for this boundary.
92
+ * Useful for displaying a tooltip or label.
93
+ */
94
+ time: number;
95
+
96
+ /**
97
+ * Attach this to your draggable element's `onMouseDown` to activate dragging.
98
+ * The LoopZoneOverlay hooks into document `mousemove`/`mouseup` internally —
99
+ * you do not need to manage drag events yourself.
100
+ */
101
+ onMouseDown: (e: React.MouseEvent) => void;
102
+ }
103
+
104
+ /**
105
+ * Props for the {@link LoopZoneOverlay} component.
106
+ *
107
+ * Mount this absolutely inside the same container as your `<Timeline>` to render
108
+ * a draggable loop region on top of the timeline ruler and edit area.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * const [loopStart, setLoopStart] = useState(1);
113
+ * const [loopEnd, setLoopEnd] = useState(3);
114
+ * const [scrollLeft, setScrollLeft] = useState(0);
115
+ *
116
+ * // Inside your container:
117
+ * <div style={{ position: 'relative' }}>
118
+ * <Timeline
119
+ * ref={timelineRef}
120
+ * scale={1}
121
+ * scaleWidth={160}
122
+ * startLeft={20}
123
+ * onScroll={(p) => setScrollLeft(p.scrollLeft)}
124
+ * ...
125
+ * />
126
+ * {loopEnabled && (
127
+ * <LoopZoneOverlay
128
+ * scale={1}
129
+ * scaleWidth={160}
130
+ * startLeft={20}
131
+ * scrollLeft={scrollLeft}
132
+ * loopStart={loopStart}
133
+ * loopEnd={loopEnd}
134
+ * onLoopStartChange={setLoopStart}
135
+ * onLoopEndChange={setLoopEnd}
136
+ * />
137
+ * )}
138
+ * </div>
139
+ * ```
140
+ */
141
+ export interface LoopZoneOverlayProps {
142
+ // ── Timeline geometry (must match <Timeline> props) ────────────────────────
143
+
144
+ /**
145
+ * Duration represented by one scale unit (seconds).
146
+ * Must match the `scale` prop on `<Timeline>`.
147
+ * @default 1
148
+ */
149
+ scale: number;
150
+
151
+ /**
152
+ * Width in pixels of one scale unit.
153
+ * Must match the `scaleWidth` prop on `<Timeline>`.
154
+ * @default 160
155
+ */
156
+ scaleWidth: number;
157
+
158
+ /**
159
+ * Left inset in pixels before the first scale mark begins.
160
+ * Must match the `startLeft` prop on `<Timeline>`.
161
+ * @default 20
162
+ */
163
+ startLeft: number;
164
+
165
+ /**
166
+ * Current horizontal scroll offset of the timeline.
167
+ * Track this via the `onScroll` callback on `<Timeline>`:
168
+ * ```tsx
169
+ * onScroll={(params) => setScrollLeft(params.scrollLeft)}
170
+ * ```
171
+ */
172
+ scrollLeft: number;
173
+
174
+ // ── Controlled loop state ──────────────────────────────────────────────────
175
+
176
+ /**
177
+ * Loop region start time in seconds.
178
+ * This is a **controlled** prop — manage it in parent state.
179
+ */
180
+ loopStart: number;
181
+
182
+ /**
183
+ * Loop region end time in seconds.
184
+ * Must be greater than `loopStart`.
185
+ * This is a **controlled** prop — manage it in parent state.
186
+ */
187
+ loopEnd: number;
188
+
189
+ /**
190
+ * Called when the user drags the start handle to a new time value.
191
+ * Update your `loopStart` state here.
192
+ *
193
+ * @param time - New loop start time in seconds.
194
+ */
195
+ onLoopStartChange: (time: number) => void;
196
+
197
+ /**
198
+ * Called when the user drags the end handle to a new time value.
199
+ * Update your `loopEnd` state here.
200
+ *
201
+ * @param time - New loop end time in seconds.
202
+ */
203
+ onLoopEndChange: (time: number) => void;
204
+
205
+ // ── Customization ──────────────────────────────────────────────────────────
206
+
207
+ /**
208
+ * Fine-grained control over the band color, opacity, handle color, and
209
+ * boundary line appearance. All properties are optional and fall back
210
+ * to green-themed defaults.
211
+ *
212
+ * @see {@link LoopZoneConfig}
213
+ */
214
+ config?: LoopZoneConfig;
215
+
216
+ /**
217
+ * Custom render function for the drag handles.
218
+ *
219
+ * When provided, the default SVG grip pill is replaced by whatever
220
+ * your function returns. The function is called once for each handle
221
+ * (`'start'` and `'end'`).
222
+ *
223
+ * You **must** attach the provided `onMouseDown` to your draggable
224
+ * element to preserve the drag interaction.
225
+ *
226
+ * @see {@link LoopHandleRenderProps}
227
+ *
228
+ * @example
229
+ * ```tsx
230
+ * renderHandle={({ type, time, onMouseDown }) => (
231
+ * <MyHandle side={type} label={`${time.toFixed(2)}s`} onMouseDown={onMouseDown} />
232
+ * )}
233
+ * ```
234
+ */
235
+ renderHandle?: (props: LoopHandleRenderProps) => React.ReactNode;
236
+
237
+ /**
238
+ * Additional CSS class name appended to the overlay root element.
239
+ * Use to apply custom styles alongside or instead of `config`.
240
+ */
241
+ className?: string;
242
+
243
+ /**
244
+ * Inline styles applied to the overlay root element.
245
+ */
246
+ style?: React.CSSProperties;
247
+ }
248
+
249
+ // ─────────────────────────────────────────────
250
+ // Defaults
251
+ // ─────────────────────────────────────────────
252
+
253
+ const DEFAULT_CONFIG: Required<LoopZoneConfig> = {
254
+ bandColor: '#10b981',
255
+ bandOpacity: 0.07,
256
+ bandBorderColor: '#10b981',
257
+ handleColor: '#10b981',
258
+ showBoundaryLines: true,
259
+ boundaryLineColor: 'rgba(16, 185, 129, 0.4)',
260
+ };
261
+
262
+ // ─────────────────────────────────────────────
263
+ // Default handle
264
+ // ─────────────────────────────────────────────
265
+
266
+ /** Three-bar SVG grip icon — the default handle rendering. */
267
+ const GripIcon: React.FC<{ color: string }> = ({ color }) => (
268
+ <svg width="8" height="12" viewBox="0 0 8 12" fill={color} aria-hidden>
269
+ <rect x="0" y="1" width="8" height="1.5" rx="0.75" />
270
+ <rect x="0" y="5.25" width="8" height="1.5" rx="0.75" />
271
+ <rect x="0" y="9.5" width="8" height="1.5" rx="0.75" />
272
+ </svg>
273
+ );
274
+
275
+ interface DefaultHandleProps {
276
+ color: string;
277
+ onMouseDown: (e: React.MouseEvent) => void;
278
+ title: string;
279
+ }
280
+
281
+ const DefaultHandle: React.FC<DefaultHandleProps> = ({ color, onMouseDown, title }) => (
282
+ <div
283
+ className="loop-zone-handle__grip"
284
+ onMouseDown={onMouseDown}
285
+ title={title}
286
+ style={{
287
+ background: color,
288
+ boxShadow: `0 2px 10px ${color}8c, 0 0 0 1px ${color}4d`,
289
+ }}
290
+ >
291
+ <GripIcon color="rgba(255,255,255,0.9)" />
292
+ </div>
293
+ );
294
+
295
+ // ─────────────────────────────────────────────
296
+ // Component
297
+ // ─────────────────────────────────────────────
298
+
299
+ /**
300
+ * `LoopZoneOverlay` renders a draggable loop/repeat region on top of a `<Timeline>`.
301
+ *
302
+ * When mounted inside the same positioned container as `<Timeline>`, it draws:
303
+ * - A **semi-transparent band** spanning the ruler and edit rows between `loopStart` and `loopEnd`
304
+ * - Two **draggable handles** at the boundaries (or custom handles via `renderHandle`)
305
+ * - Optional **dashed boundary lines** extending through the edit rows
306
+ *
307
+ * The component is **fully controlled** — it calls `onLoopStartChange` /
308
+ * `onLoopEndChange` as the user drags and does not hold its own loop state.
309
+ *
310
+ * @example
311
+ * ```tsx
312
+ * <LoopZoneOverlay
313
+ * scale={1}
314
+ * scaleWidth={160}
315
+ * startLeft={20}
316
+ * scrollLeft={scrollLeft}
317
+ * loopStart={loopStart}
318
+ * loopEnd={loopEnd}
319
+ * onLoopStartChange={setLoopStart}
320
+ * onLoopEndChange={setLoopEnd}
321
+ * config={{ bandColor: '#6366f1', bandOpacity: 0.1 }}
322
+ * />
323
+ * ```
324
+ */
325
+ const LoopZoneOverlay: React.FC<LoopZoneOverlayProps> = ({
326
+ scale,
327
+ scaleWidth,
328
+ startLeft,
329
+ scrollLeft,
330
+ loopStart,
331
+ loopEnd,
332
+ onLoopStartChange,
333
+ onLoopEndChange,
334
+ config,
335
+ renderHandle,
336
+ className,
337
+ style,
338
+ }) => {
339
+ // Merge user config with defaults
340
+ const cfg: Required<LoopZoneConfig> = {
341
+ ...DEFAULT_CONFIG,
342
+ ...config,
343
+ handleColor: config?.handleColor ?? config?.bandColor ?? DEFAULT_CONFIG.handleColor,
344
+ bandBorderColor: config?.bandBorderColor ?? config?.bandColor ?? DEFAULT_CONFIG.bandBorderColor,
345
+ boundaryLineColor: config?.boundaryLineColor ?? `${config?.bandColor ?? DEFAULT_CONFIG.bandColor}66`,
346
+ };
347
+
348
+ /** Convert a time (seconds) to a pixel x-position relative to the overlay. */
349
+ const t2px = useCallback(
350
+ (t: number) => startLeft + (t / scale) * scaleWidth - scrollLeft,
351
+ [startLeft, scale, scaleWidth, scrollLeft],
352
+ );
353
+
354
+ const leftPx = t2px(loopStart);
355
+ const rightPx = t2px(loopEnd);
356
+ const bandWidth = Math.max(0, rightPx - leftPx);
357
+
358
+ /**
359
+ * Returns a mousedown handler that wires up document-level drag tracking
360
+ * for the given handle type.
361
+ */
362
+ const makeDragHandler = useCallback(
363
+ (which: 'start' | 'end') => (e: React.MouseEvent) => {
364
+ e.preventDefault();
365
+ const startClientX = e.clientX;
366
+ const initTime = which === 'start' ? loopStart : loopEnd;
367
+
368
+ const onMove = (me: MouseEvent) => {
369
+ const dt = ((me.clientX - startClientX) / scaleWidth) * scale;
370
+ const newTime = Math.max(0, parseFloat((initTime + dt).toFixed(3)));
371
+ if (which === 'start') {
372
+ const clamped = Math.min(newTime, loopEnd - 0.05);
373
+ onLoopStartChange(clamped);
374
+ } else {
375
+ const clamped = Math.max(newTime, loopStart + 0.05);
376
+ onLoopEndChange(clamped);
377
+ }
378
+ };
379
+
380
+ const onUp = () => {
381
+ document.removeEventListener('mousemove', onMove);
382
+ document.removeEventListener('mouseup', onUp);
383
+ };
384
+
385
+ document.addEventListener('mousemove', onMove);
386
+ document.addEventListener('mouseup', onUp);
387
+ },
388
+ [loopStart, loopEnd, scale, scaleWidth, onLoopStartChange, onLoopEndChange],
389
+ );
390
+
391
+ const startDrag = makeDragHandler('start');
392
+ const endDrag = makeDragHandler('end');
393
+
394
+ const renderHandleNode = (type: 'start' | 'end') => {
395
+ const time = type === 'start' ? loopStart : loopEnd;
396
+ const onMouseDown = type === 'start' ? startDrag : endDrag;
397
+
398
+ if (renderHandle) {
399
+ return renderHandle({ type, time, onMouseDown });
400
+ }
401
+
402
+ return (
403
+ <DefaultHandle
404
+ color={cfg.handleColor}
405
+ onMouseDown={onMouseDown}
406
+ title={`Drag to move loop ${type} (${time.toFixed(2)}s)`}
407
+ />
408
+ );
409
+ };
410
+
411
+ return (
412
+ <div
413
+ className={`loop-zone-overlay${className ? ` ${className}` : ''}`}
414
+ style={{ pointerEvents: 'none', ...style }}
415
+ >
416
+ {/* Shaded band */}
417
+ {bandWidth > 0 && (
418
+ <div
419
+ className="loop-zone-band"
420
+ style={{
421
+ left: leftPx,
422
+ width: bandWidth,
423
+ background: cfg.bandColor,
424
+ opacity: cfg.bandOpacity,
425
+ borderColor: cfg.bandBorderColor,
426
+ }}
427
+ />
428
+ )}
429
+
430
+ {/* Start handle */}
431
+ <div
432
+ className="loop-zone-handle"
433
+ style={{ left: leftPx }}
434
+ >
435
+ {cfg.showBoundaryLines && (
436
+ <div
437
+ className="loop-zone-handle__line"
438
+ style={{ borderColor: cfg.boundaryLineColor }}
439
+ />
440
+ )}
441
+ {renderHandleNode('start')}
442
+ </div>
443
+
444
+ {/* End handle */}
445
+ <div
446
+ className="loop-zone-handle"
447
+ style={{ left: rightPx }}
448
+ >
449
+ {cfg.showBoundaryLines && (
450
+ <div
451
+ className="loop-zone-handle__line"
452
+ style={{ borderColor: cfg.boundaryLineColor }}
453
+ />
454
+ )}
455
+ {renderHandleNode('end')}
456
+ </div>
457
+ </div>
458
+ );
459
+ };
460
+
461
+ export default LoopZoneOverlay;
@@ -0,0 +1,81 @@
1
+ import { DragEvent, ResizeEvent } from '@interactjs/types/index';
2
+ import { useRef } from 'react';
3
+
4
+ const DEFAULT_SPEED = 1;
5
+ const MAX_SPEED = 3;
6
+ const CRITICAL_SIZE = 10;
7
+
8
+ export function useAutoScroll(target: React.RefObject<HTMLDivElement>) {
9
+ const leftBoundRef = useRef(Number.MIN_SAFE_INTEGER);
10
+ const rightBoundRef = useRef(Number.MAX_SAFE_INTEGER);
11
+
12
+ const speed = useRef(DEFAULT_SPEED);
13
+ const frame = useRef<number>();
14
+
15
+ const initAutoScroll = () => {
16
+ if (target?.current) {
17
+ const { left, width } = target.current.getBoundingClientRect();
18
+ leftBoundRef.current = left;
19
+ rightBoundRef.current = left + width;
20
+ }
21
+ };
22
+
23
+ const dealDragAutoScroll = (e: DragEvent, deltaScroll?: (delta: number) => void) => {
24
+ // Out of bounds
25
+ if (e.clientX >= rightBoundRef.current || e.clientX <= leftBoundRef.current) {
26
+ frame.current && cancelAnimationFrame(frame.current);
27
+ const over = Math.abs(e.clientX >= rightBoundRef.current ? e.clientX - rightBoundRef.current : e.clientX - leftBoundRef.current);
28
+ speed.current = Math.min(Number((over / CRITICAL_SIZE).toFixed(0)) * DEFAULT_SPEED, MAX_SPEED);
29
+
30
+ const dir = e.clientX >= rightBoundRef.current ? 1 : -1;
31
+ const delta = dir * speed.current;
32
+ const loop = () => {
33
+ deltaScroll && deltaScroll(delta);
34
+ frame.current = requestAnimationFrame(loop);
35
+ };
36
+
37
+ frame.current = requestAnimationFrame(loop);
38
+ return false;
39
+ } else {
40
+ frame.current && cancelAnimationFrame(frame.current);
41
+ }
42
+
43
+ return true;
44
+ };
45
+
46
+ const dealResizeAutoScroll = (e: ResizeEvent, dir: 'left' | 'right', deltaScroll?: (delta: number) => void) => {
47
+ if (e.clientX >= rightBoundRef.current || e.clientX < leftBoundRef.current) {
48
+ frame.current && cancelAnimationFrame(frame.current);
49
+ const over = Math.abs(e.clientX >= rightBoundRef.current ? e.clientX - rightBoundRef.current : e.clientX - leftBoundRef.current);
50
+ speed.current = Math.min(Number((over / CRITICAL_SIZE).toFixed(0)) * DEFAULT_SPEED, MAX_SPEED);
51
+
52
+ const direction = e.clientX >= rightBoundRef.current ? 1 : -1;
53
+ const delta = direction * speed.current;
54
+ const loop = () => {
55
+ deltaScroll && deltaScroll(delta);
56
+ frame.current = requestAnimationFrame(loop);
57
+ };
58
+
59
+ frame.current = requestAnimationFrame(loop);
60
+
61
+ return false;
62
+ } else {
63
+ frame.current && cancelAnimationFrame(frame.current);
64
+ }
65
+ return true;
66
+ };
67
+
68
+ const stopAutoScroll = () => {
69
+ leftBoundRef.current = Number.MIN_SAFE_INTEGER;
70
+ rightBoundRef.current = Number.MAX_SAFE_INTEGER;
71
+ speed.current = DEFAULT_SPEED;
72
+ frame.current && cancelAnimationFrame(frame.current);
73
+ };
74
+
75
+ return {
76
+ initAutoScroll,
77
+ dealDragAutoScroll,
78
+ dealResizeAutoScroll,
79
+ stopAutoScroll,
80
+ };
81
+ }
@@ -0,0 +1,55 @@
1
+ import { DraggableOptions } from '@interactjs/actions/drag/plugin';
2
+ import { ResizableOptions } from '@interactjs/actions/resize/plugin';
3
+ import { DragEvent, Interactable } from '@interactjs/types';
4
+ import interact from 'interactjs';
5
+ import { cloneElement, FC, ReactElement, useEffect, useRef } from 'react';
6
+
7
+ interface InteractCompProps {
8
+ children: ReactElement;
9
+ interactRef: React.MutableRefObject<Interactable | null>;
10
+ draggable: boolean;
11
+ draggableOptions: DraggableOptions;
12
+ resizable: boolean;
13
+ resizableOptions: ResizableOptions;
14
+ }
15
+
16
+ export const InteractComp: FC<InteractCompProps> = ({ children, interactRef, draggable, resizable, draggableOptions, resizableOptions }) => {
17
+ const nodeRef = useRef<HTMLElement>(null);
18
+ const interactable = useRef<Interactable | null>();
19
+ const draggableOptionsRef = useRef<DraggableOptions>();
20
+ const resizableOptionsRef = useRef<ResizableOptions>();
21
+
22
+ useEffect(() => {
23
+ draggableOptionsRef.current = { ...draggableOptions };
24
+ resizableOptionsRef.current = { ...resizableOptions };
25
+ }, [draggableOptions, resizableOptions]);
26
+
27
+ useEffect(() => {
28
+ interactable.current && interactable.current.unset();
29
+ interactable.current = interact(nodeRef.current as HTMLElement);
30
+ interactRef.current = interactable.current;
31
+ setInteractions();
32
+ }, [draggable, resizable]);
33
+
34
+ const setInteractions = () => {
35
+ if (draggable)
36
+ interactable.current?.draggable({
37
+ ...draggableOptionsRef.current,
38
+ onstart: (e) => draggableOptionsRef.current?.onstart && (draggableOptionsRef.current.onstart as (e: DragEvent) => any)(e),
39
+ onmove: (e) => draggableOptionsRef.current?.onmove && (draggableOptionsRef.current.onmove as (e: DragEvent) => any)(e),
40
+ onend: (e) => draggableOptionsRef.current?.onend && (draggableOptionsRef.current.onend as (e: DragEvent) => any)(e),
41
+ });
42
+ if (resizable)
43
+ interactable.current?.resizable({
44
+ ...resizableOptionsRef.current,
45
+ onstart: (e) => resizableOptionsRef.current?.onstart && (resizableOptionsRef.current.onstart as (e: DragEvent) => any)(e),
46
+ onmove: (e) => resizableOptionsRef.current?.onmove && (resizableOptionsRef.current.onmove as (e: DragEvent) => any)(e),
47
+ onend: (e) => resizableOptionsRef.current?.onend && (resizableOptionsRef.current.onend as (e: DragEvent) => any)(e),
48
+ });
49
+ };
50
+
51
+ return cloneElement(children as ReactElement, {
52
+ ref: nodeRef,
53
+ draggable: false,
54
+ });
55
+ };