@webpacked-timeline/core 1.0.0-beta.1

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 (77) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +21 -0
  3. package/README.md +162 -0
  4. package/dist/chunk-27XCNVPR.js +5969 -0
  5. package/dist/chunk-6PDBJDHM.js +2263 -0
  6. package/dist/chunk-BWPS6NQT.js +7465 -0
  7. package/dist/chunk-FBOYSUYV.js +1280 -0
  8. package/dist/chunk-FR632TZX.js +1870 -0
  9. package/dist/chunk-HW4Z7YLJ.js +1242 -0
  10. package/dist/chunk-HWW62IFH.js +5424 -0
  11. package/dist/chunk-I2GZXRH4.js +4790 -0
  12. package/dist/chunk-JQZE3OK4.js +1255 -0
  13. package/dist/chunk-KF7JNK2F.js +1864 -0
  14. package/dist/chunk-KR3P2DYK.js +5655 -0
  15. package/dist/chunk-MO5DSFSW.js +2214 -0
  16. package/dist/chunk-MQAW33RJ.js +5530 -0
  17. package/dist/chunk-N4WUWZZX.js +2833 -0
  18. package/dist/chunk-NRJV7I4C.js +1331 -0
  19. package/dist/chunk-NXG52532.js +2230 -0
  20. package/dist/chunk-PVXF67CN.js +1278 -0
  21. package/dist/chunk-QSB6DHIF.js +5429 -0
  22. package/dist/chunk-QYWJT7HR.js +5837 -0
  23. package/dist/chunk-SWBRCMW7.js +7466 -0
  24. package/dist/chunk-TAT3ULSV.js +2214 -0
  25. package/dist/chunk-TTDP5JUM.js +2228 -0
  26. package/dist/chunk-UAGP4VPG.js +1739 -0
  27. package/dist/chunk-WIG6SY7A.js +1183 -0
  28. package/dist/chunk-YJ2K5N2R.js +6187 -0
  29. package/dist/index-3Lr_vKBd.d.cts +2810 -0
  30. package/dist/index-3Lr_vKBd.d.ts +2810 -0
  31. package/dist/index-7IPJn1yM.d.cts +1146 -0
  32. package/dist/index-7IPJn1yM.d.ts +1146 -0
  33. package/dist/index-B0xOv0V0.d.cts +3259 -0
  34. package/dist/index-B0xOv0V0.d.ts +3259 -0
  35. package/dist/index-B2m3zwg7.d.cts +1381 -0
  36. package/dist/index-B2m3zwg7.d.ts +1381 -0
  37. package/dist/index-B3sUrU_X.d.cts +1249 -0
  38. package/dist/index-B3sUrU_X.d.ts +1249 -0
  39. package/dist/index-B6wla7ZJ.d.cts +2751 -0
  40. package/dist/index-B6wla7ZJ.d.ts +2751 -0
  41. package/dist/index-BIv8RWWT.d.cts +1574 -0
  42. package/dist/index-BIv8RWWT.d.ts +1574 -0
  43. package/dist/index-BJv6hDHL.d.cts +3255 -0
  44. package/dist/index-BJv6hDHL.d.ts +3255 -0
  45. package/dist/index-BUCimS2e.d.cts +1393 -0
  46. package/dist/index-BUCimS2e.d.ts +1393 -0
  47. package/dist/index-Bw_nvNcG.d.cts +1275 -0
  48. package/dist/index-Bw_nvNcG.d.ts +1275 -0
  49. package/dist/index-ByG0gOtd.d.cts +1167 -0
  50. package/dist/index-ByG0gOtd.d.ts +1167 -0
  51. package/dist/index-CDGd2XXv.d.cts +2492 -0
  52. package/dist/index-CDGd2XXv.d.ts +2492 -0
  53. package/dist/index-CznAVeJ6.d.cts +1145 -0
  54. package/dist/index-CznAVeJ6.d.ts +1145 -0
  55. package/dist/index-DQD9IMh7.d.cts +2534 -0
  56. package/dist/index-DQD9IMh7.d.ts +2534 -0
  57. package/dist/index-Dl3qtJEI.d.cts +2178 -0
  58. package/dist/index-Dl3qtJEI.d.ts +2178 -0
  59. package/dist/index-DnE2A-Nz.d.cts +2603 -0
  60. package/dist/index-DnE2A-Nz.d.ts +2603 -0
  61. package/dist/index-DrOA6QmW.d.cts +2492 -0
  62. package/dist/index-DrOA6QmW.d.ts +2492 -0
  63. package/dist/index-Vpa3rPEM.d.cts +1402 -0
  64. package/dist/index-Vpa3rPEM.d.ts +1402 -0
  65. package/dist/index-jP6BomSd.d.cts +2640 -0
  66. package/dist/index-jP6BomSd.d.ts +2640 -0
  67. package/dist/index-wiGRwVyY.d.cts +3259 -0
  68. package/dist/index-wiGRwVyY.d.ts +3259 -0
  69. package/dist/index.cjs +7386 -0
  70. package/dist/index.d.cts +1 -0
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +263 -0
  73. package/dist/internal.cjs +7721 -0
  74. package/dist/internal.d.cts +704 -0
  75. package/dist/internal.d.ts +704 -0
  76. package/dist/internal.js +405 -0
  77. package/package.json +58 -0
@@ -0,0 +1,1183 @@
1
+ // src/types/clip.ts
2
+ function createClip(params) {
3
+ const clip = {
4
+ id: params.id,
5
+ assetId: params.assetId,
6
+ trackId: params.trackId,
7
+ timelineStart: params.timelineStart,
8
+ timelineEnd: params.timelineEnd,
9
+ mediaIn: params.mediaIn,
10
+ mediaOut: params.mediaOut
11
+ };
12
+ if (params.label !== void 0) {
13
+ clip.label = params.label;
14
+ }
15
+ if (params.metadata !== void 0) {
16
+ clip.metadata = params.metadata;
17
+ }
18
+ return clip;
19
+ }
20
+ function getClipDuration(clip) {
21
+ return clip.timelineEnd - clip.timelineStart;
22
+ }
23
+ function getClipMediaDuration(clip) {
24
+ return clip.mediaOut - clip.mediaIn;
25
+ }
26
+ function clipContainsFrame(clip, frame2) {
27
+ return frame2 >= clip.timelineStart && frame2 < clip.timelineEnd;
28
+ }
29
+ function clipsOverlap(clip1, clip2) {
30
+ return clip1.timelineStart < clip2.timelineEnd && clip2.timelineStart < clip1.timelineEnd;
31
+ }
32
+
33
+ // src/types/validation.ts
34
+ function validResult() {
35
+ return {
36
+ valid: true,
37
+ errors: []
38
+ };
39
+ }
40
+ function invalidResult(code, message, context) {
41
+ const error = { code, message };
42
+ if (context !== void 0) {
43
+ error.context = context;
44
+ }
45
+ return {
46
+ valid: false,
47
+ errors: [error]
48
+ };
49
+ }
50
+ function invalidResults(errors) {
51
+ return {
52
+ valid: false,
53
+ errors
54
+ };
55
+ }
56
+ function combineResults(...results) {
57
+ const allErrors = [];
58
+ for (const result of results) {
59
+ if (!result.valid) {
60
+ allErrors.push(...result.errors);
61
+ }
62
+ }
63
+ if (allErrors.length > 0) {
64
+ return invalidResults(allErrors);
65
+ }
66
+ return validResult();
67
+ }
68
+
69
+ // src/systems/asset-registry.ts
70
+ function registerAsset(state, asset) {
71
+ const newAssets = new Map(state.assets);
72
+ newAssets.set(asset.id, asset);
73
+ return {
74
+ ...state,
75
+ assets: newAssets
76
+ };
77
+ }
78
+ function getAsset(state, assetId) {
79
+ return state.assets.get(assetId);
80
+ }
81
+ function hasAsset(state, assetId) {
82
+ return state.assets.has(assetId);
83
+ }
84
+ function getAllAssets(state) {
85
+ return Array.from(state.assets.values());
86
+ }
87
+ function unregisterAsset(state, assetId) {
88
+ const newAssets = new Map(state.assets);
89
+ newAssets.delete(assetId);
90
+ return {
91
+ ...state,
92
+ assets: newAssets
93
+ };
94
+ }
95
+
96
+ // src/systems/validation.ts
97
+ function validateClip(state, clip) {
98
+ const errors = [];
99
+ const asset = getAsset(state, clip.assetId);
100
+ if (!asset) {
101
+ errors.push(invalidResult(
102
+ "ASSET_NOT_FOUND",
103
+ `Asset '${clip.assetId}' not found in registry`,
104
+ { clipId: clip.id, assetId: clip.assetId }
105
+ ));
106
+ return combineResults(...errors);
107
+ }
108
+ if (clip.timelineEnd <= clip.timelineStart) {
109
+ errors.push(invalidResult(
110
+ "INVALID_TIMELINE_BOUNDS",
111
+ `Clip timeline end (${clip.timelineEnd}) must be greater than start (${clip.timelineStart})`,
112
+ { clipId: clip.id, timelineStart: clip.timelineStart, timelineEnd: clip.timelineEnd }
113
+ ));
114
+ }
115
+ if (clip.mediaOut <= clip.mediaIn) {
116
+ errors.push(invalidResult(
117
+ "INVALID_MEDIA_BOUNDS",
118
+ `Clip media out (${clip.mediaOut}) must be greater than media in (${clip.mediaIn})`,
119
+ { clipId: clip.id, mediaIn: clip.mediaIn, mediaOut: clip.mediaOut }
120
+ ));
121
+ }
122
+ if (clip.mediaOut > asset.duration) {
123
+ errors.push(invalidResult(
124
+ "MEDIA_EXCEEDS_ASSET",
125
+ `Clip media out (${clip.mediaOut}) exceeds asset duration (${asset.duration})`,
126
+ { clipId: clip.id, mediaOut: clip.mediaOut, assetDuration: asset.duration }
127
+ ));
128
+ }
129
+ const timelineDuration = getClipDuration(clip);
130
+ const mediaDuration = getClipMediaDuration(clip);
131
+ if (timelineDuration !== mediaDuration) {
132
+ errors.push(invalidResult(
133
+ "DURATION_MISMATCH",
134
+ `Clip timeline duration (${timelineDuration}) must match media duration (${mediaDuration}) in Phase 1`,
135
+ { clipId: clip.id, timelineDuration, mediaDuration }
136
+ ));
137
+ }
138
+ return combineResults(...errors);
139
+ }
140
+ function validateTrack(state, track) {
141
+ const errors = [];
142
+ for (const clip of track.clips) {
143
+ const clipResult = validateClip(state, clip);
144
+ if (!clipResult.valid) {
145
+ errors.push(clipResult);
146
+ }
147
+ }
148
+ for (let i = 0; i < track.clips.length; i++) {
149
+ for (let j = i + 1; j < track.clips.length; j++) {
150
+ const clip1 = track.clips[i];
151
+ const clip2 = track.clips[j];
152
+ if (!clip1 || !clip2) {
153
+ continue;
154
+ }
155
+ if (clipsOverlap(clip1, clip2)) {
156
+ errors.push(invalidResult(
157
+ "CLIPS_OVERLAP",
158
+ `Clips '${clip1.id}' and '${clip2.id}' overlap on track '${track.id}'`,
159
+ {
160
+ trackId: track.id,
161
+ clip1Id: clip1.id,
162
+ clip2Id: clip2.id,
163
+ clip1Start: clip1.timelineStart,
164
+ clip1End: clip1.timelineEnd,
165
+ clip2Start: clip2.timelineStart,
166
+ clip2End: clip2.timelineEnd
167
+ }
168
+ ));
169
+ }
170
+ }
171
+ }
172
+ return combineResults(...errors);
173
+ }
174
+ function validateTimeline(state) {
175
+ const errors = [];
176
+ if (state.timeline.fps <= 0) {
177
+ errors.push(invalidResult(
178
+ "INVALID_FPS",
179
+ `Timeline FPS must be positive, got ${state.timeline.fps}`,
180
+ { fps: state.timeline.fps }
181
+ ));
182
+ }
183
+ if (state.timeline.duration <= 0) {
184
+ errors.push(invalidResult(
185
+ "INVALID_DURATION",
186
+ `Timeline duration must be positive, got ${state.timeline.duration}`,
187
+ { duration: state.timeline.duration }
188
+ ));
189
+ }
190
+ for (const track of state.timeline.tracks) {
191
+ const trackResult = validateTrack(state, track);
192
+ if (!trackResult.valid) {
193
+ errors.push(trackResult);
194
+ }
195
+ }
196
+ return combineResults(...errors);
197
+ }
198
+ function validateNoOverlap(track, clip) {
199
+ for (const existingClip of track.clips) {
200
+ if (existingClip.id === clip.id) {
201
+ continue;
202
+ }
203
+ if (clipsOverlap(existingClip, clip)) {
204
+ return invalidResult(
205
+ "CLIPS_OVERLAP",
206
+ `Clip '${clip.id}' would overlap with existing clip '${existingClip.id}' on track '${track.id}'`,
207
+ {
208
+ trackId: track.id,
209
+ newClipId: clip.id,
210
+ existingClipId: existingClip.id,
211
+ newClipStart: clip.timelineStart,
212
+ newClipEnd: clip.timelineEnd,
213
+ existingClipStart: existingClip.timelineStart,
214
+ existingClipEnd: existingClip.timelineEnd
215
+ }
216
+ );
217
+ }
218
+ }
219
+ return validResult();
220
+ }
221
+
222
+ // src/systems/queries.ts
223
+ function findClipById(state, clipId) {
224
+ for (const track of state.timeline.tracks) {
225
+ const clip = track.clips.find((c) => c.id === clipId);
226
+ if (clip) {
227
+ return clip;
228
+ }
229
+ }
230
+ return void 0;
231
+ }
232
+ function findTrackById(state, trackId) {
233
+ return state.timeline.tracks.find((t) => t.id === trackId);
234
+ }
235
+ function getClipsOnTrack(state, trackId) {
236
+ const track = findTrackById(state, trackId);
237
+ return track ? track.clips : [];
238
+ }
239
+ function getClipsAtFrame(state, frame2) {
240
+ const clips = [];
241
+ for (const track of state.timeline.tracks) {
242
+ for (const clip of track.clips) {
243
+ if (clipContainsFrame(clip, frame2)) {
244
+ clips.push(clip);
245
+ }
246
+ }
247
+ }
248
+ return clips;
249
+ }
250
+ function getClipsInRange(state, start, end) {
251
+ const clips = [];
252
+ for (const track of state.timeline.tracks) {
253
+ for (const clip of track.clips) {
254
+ if (clip.timelineStart < end && clip.timelineEnd > start) {
255
+ clips.push(clip);
256
+ }
257
+ }
258
+ }
259
+ return clips;
260
+ }
261
+ function getAllClips(state) {
262
+ const clips = [];
263
+ for (const track of state.timeline.tracks) {
264
+ clips.push(...track.clips);
265
+ }
266
+ return clips;
267
+ }
268
+ function getAllTracks(state) {
269
+ return state.timeline.tracks;
270
+ }
271
+ function findTrackIndex(state, trackId) {
272
+ return state.timeline.tracks.findIndex((t) => t.id === trackId);
273
+ }
274
+
275
+ // src/types/track.ts
276
+ function createTrack(params) {
277
+ const track = {
278
+ id: params.id,
279
+ name: params.name,
280
+ type: params.type,
281
+ clips: params.clips ?? [],
282
+ locked: params.locked ?? false,
283
+ muted: params.muted ?? false
284
+ };
285
+ if (params.metadata !== void 0) {
286
+ track.metadata = params.metadata;
287
+ }
288
+ return track;
289
+ }
290
+ function sortTrackClips(track) {
291
+ return {
292
+ ...track,
293
+ clips: [...track.clips].sort((a, b) => a.timelineStart - b.timelineStart)
294
+ };
295
+ }
296
+
297
+ // src/operations/clip-operations.ts
298
+ function addClip(state, trackId, clip) {
299
+ const trackIndex = state.timeline.tracks.findIndex((t) => t.id === trackId);
300
+ if (trackIndex === -1) {
301
+ return state;
302
+ }
303
+ const track = state.timeline.tracks[trackIndex];
304
+ if (!track) {
305
+ return state;
306
+ }
307
+ const newTrack = sortTrackClips({
308
+ ...track,
309
+ clips: [...track.clips, clip]
310
+ });
311
+ const newTracks = [...state.timeline.tracks];
312
+ newTracks[trackIndex] = newTrack;
313
+ return {
314
+ ...state,
315
+ timeline: {
316
+ ...state.timeline,
317
+ tracks: newTracks
318
+ }
319
+ };
320
+ }
321
+ function removeClip(state, clipId) {
322
+ const newTracks = state.timeline.tracks.map((track) => ({
323
+ ...track,
324
+ clips: track.clips.filter((c) => c.id !== clipId)
325
+ }));
326
+ return {
327
+ ...state,
328
+ timeline: {
329
+ ...state.timeline,
330
+ tracks: newTracks
331
+ }
332
+ };
333
+ }
334
+ function moveClip(state, clipId, newStart) {
335
+ const clip = findClipById(state, clipId);
336
+ if (!clip) {
337
+ return state;
338
+ }
339
+ const duration = clip.timelineEnd - clip.timelineStart;
340
+ const newEnd = newStart + duration;
341
+ return updateClip(state, clipId, {
342
+ timelineStart: newStart,
343
+ timelineEnd: newEnd
344
+ });
345
+ }
346
+ function resizeClip(state, clipId, newStart, newEnd) {
347
+ return updateClip(state, clipId, {
348
+ timelineStart: newStart,
349
+ timelineEnd: newEnd
350
+ });
351
+ }
352
+ function trimClip(state, clipId, newMediaIn, newMediaOut) {
353
+ return updateClip(state, clipId, {
354
+ mediaIn: newMediaIn,
355
+ mediaOut: newMediaOut
356
+ });
357
+ }
358
+ function updateClip(state, clipId, updates) {
359
+ const newTracks = state.timeline.tracks.map((track) => {
360
+ const clipIndex = track.clips.findIndex((c) => c.id === clipId);
361
+ if (clipIndex === -1) {
362
+ return track;
363
+ }
364
+ const newClips = [...track.clips];
365
+ const existingClip = newClips[clipIndex];
366
+ if (!existingClip) {
367
+ return track;
368
+ }
369
+ newClips[clipIndex] = {
370
+ ...existingClip,
371
+ ...updates
372
+ };
373
+ return sortTrackClips({
374
+ ...track,
375
+ clips: newClips
376
+ });
377
+ });
378
+ return {
379
+ ...state,
380
+ timeline: {
381
+ ...state.timeline,
382
+ tracks: newTracks
383
+ }
384
+ };
385
+ }
386
+ function moveClipToTrack(state, clipId, targetTrackId) {
387
+ const clip = findClipById(state, clipId);
388
+ if (!clip) {
389
+ return state;
390
+ }
391
+ let newState = removeClip(state, clipId);
392
+ const updatedClip = { ...clip, trackId: targetTrackId };
393
+ newState = addClip(newState, targetTrackId, updatedClip);
394
+ return newState;
395
+ }
396
+
397
+ // src/operations/track-operations.ts
398
+ function addTrack(state, track) {
399
+ return {
400
+ ...state,
401
+ timeline: {
402
+ ...state.timeline,
403
+ tracks: [...state.timeline.tracks, track]
404
+ }
405
+ };
406
+ }
407
+ function removeTrack(state, trackId) {
408
+ return {
409
+ ...state,
410
+ timeline: {
411
+ ...state.timeline,
412
+ tracks: state.timeline.tracks.filter((t) => t.id !== trackId)
413
+ }
414
+ };
415
+ }
416
+ function moveTrack(state, trackId, newIndex) {
417
+ const currentIndex = findTrackIndex(state, trackId);
418
+ if (currentIndex === -1) {
419
+ return state;
420
+ }
421
+ const newTracks = [...state.timeline.tracks];
422
+ const [track] = newTracks.splice(currentIndex, 1);
423
+ if (!track) {
424
+ return state;
425
+ }
426
+ newTracks.splice(newIndex, 0, track);
427
+ return {
428
+ ...state,
429
+ timeline: {
430
+ ...state.timeline,
431
+ tracks: newTracks
432
+ }
433
+ };
434
+ }
435
+ function updateTrack(state, trackId, updates) {
436
+ const trackIndex = findTrackIndex(state, trackId);
437
+ if (trackIndex === -1) {
438
+ return state;
439
+ }
440
+ const newTracks = [...state.timeline.tracks];
441
+ const existingTrack = newTracks[trackIndex];
442
+ if (!existingTrack) {
443
+ return state;
444
+ }
445
+ newTracks[trackIndex] = {
446
+ ...existingTrack,
447
+ ...updates
448
+ };
449
+ return {
450
+ ...state,
451
+ timeline: {
452
+ ...state.timeline,
453
+ tracks: newTracks
454
+ }
455
+ };
456
+ }
457
+ function toggleTrackMute(state, trackId) {
458
+ const trackIndex = findTrackIndex(state, trackId);
459
+ if (trackIndex === -1) {
460
+ return state;
461
+ }
462
+ const track = state.timeline.tracks[trackIndex];
463
+ if (!track) {
464
+ return state;
465
+ }
466
+ return updateTrack(state, trackId, { muted: !track.muted });
467
+ }
468
+ function toggleTrackLock(state, trackId) {
469
+ const trackIndex = findTrackIndex(state, trackId);
470
+ if (trackIndex === -1) {
471
+ return state;
472
+ }
473
+ const track = state.timeline.tracks[trackIndex];
474
+ if (!track) {
475
+ return state;
476
+ }
477
+ return updateTrack(state, trackId, { locked: !track.locked });
478
+ }
479
+
480
+ // src/operations/timeline-operations.ts
481
+ function setTimelineDuration(state, duration) {
482
+ return {
483
+ ...state,
484
+ timeline: {
485
+ ...state.timeline,
486
+ duration
487
+ }
488
+ };
489
+ }
490
+ function setTimelineName(state, name) {
491
+ return {
492
+ ...state,
493
+ timeline: {
494
+ ...state.timeline,
495
+ name
496
+ }
497
+ };
498
+ }
499
+ function updateTimelineMetadata(state, metadata) {
500
+ return {
501
+ ...state,
502
+ timeline: {
503
+ ...state.timeline,
504
+ metadata: {
505
+ ...state.timeline.metadata,
506
+ ...metadata
507
+ }
508
+ }
509
+ };
510
+ }
511
+
512
+ // src/engine/history.ts
513
+ function createHistory(initialState, limit = 50) {
514
+ return {
515
+ past: [],
516
+ present: initialState,
517
+ future: [],
518
+ limit
519
+ };
520
+ }
521
+ function pushHistory(history, newState) {
522
+ const newPast = [...history.past, history.present];
523
+ if (newPast.length > history.limit) {
524
+ newPast.shift();
525
+ }
526
+ return {
527
+ ...history,
528
+ past: newPast,
529
+ present: newState,
530
+ future: []
531
+ // Clear future on new action
532
+ };
533
+ }
534
+ function undo(history) {
535
+ if (history.past.length === 0) {
536
+ return history;
537
+ }
538
+ const newPast = [...history.past];
539
+ const previous = newPast.pop();
540
+ return {
541
+ ...history,
542
+ past: newPast,
543
+ present: previous,
544
+ future: [history.present, ...history.future]
545
+ };
546
+ }
547
+ function redo(history) {
548
+ if (history.future.length === 0) {
549
+ return history;
550
+ }
551
+ const newFuture = [...history.future];
552
+ const next = newFuture.shift();
553
+ return {
554
+ ...history,
555
+ past: [...history.past, history.present],
556
+ present: next,
557
+ future: newFuture
558
+ };
559
+ }
560
+ function canUndo(history) {
561
+ return history.past.length > 0;
562
+ }
563
+ function canRedo(history) {
564
+ return history.future.length > 0;
565
+ }
566
+ function getCurrentState(history) {
567
+ return history.present;
568
+ }
569
+
570
+ // src/engine/dispatcher.ts
571
+ function dispatch(history, operation) {
572
+ const currentState = getCurrentState(history);
573
+ const newState = operation(currentState);
574
+ const validationResult = validateTimeline(newState);
575
+ if (!validationResult.valid) {
576
+ return {
577
+ success: false,
578
+ history,
579
+ // Return unchanged history
580
+ errors: validationResult.errors
581
+ };
582
+ }
583
+ const newHistory = pushHistory(history, newState);
584
+ return {
585
+ success: true,
586
+ history: newHistory
587
+ };
588
+ }
589
+
590
+ // src/engine/timeline-engine.ts
591
+ var TimelineEngine = class {
592
+ history;
593
+ listeners = /* @__PURE__ */ new Set();
594
+ /**
595
+ * Create a new timeline engine
596
+ *
597
+ * @param initialState - Initial timeline state
598
+ * @param historyLimit - Maximum number of undo steps (default: 50)
599
+ */
600
+ constructor(initialState, historyLimit = 50) {
601
+ this.history = createHistory(initialState, historyLimit);
602
+ }
603
+ // ===== SUBSCRIPTION =====
604
+ /**
605
+ * Subscribe to state changes
606
+ *
607
+ * The listener will be called whenever the timeline state changes.
608
+ * This is used by framework adapters (e.g., React) to trigger re-renders.
609
+ *
610
+ * @param listener - Function to call on state changes
611
+ * @returns Unsubscribe function
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * const unsubscribe = engine.subscribe(() => {
616
+ * console.log('State changed:', engine.getState());
617
+ * });
618
+ *
619
+ * // Later...
620
+ * unsubscribe();
621
+ * ```
622
+ */
623
+ subscribe(listener) {
624
+ this.listeners.add(listener);
625
+ return () => {
626
+ this.listeners.delete(listener);
627
+ };
628
+ }
629
+ /**
630
+ * Notify all subscribers of a state change
631
+ *
632
+ * This is called internally after any operation that modifies state.
633
+ * Framework adapters use this to trigger re-renders.
634
+ */
635
+ notify() {
636
+ this.listeners.forEach((listener) => listener());
637
+ }
638
+ // ===== STATE ACCESS =====
639
+ /**
640
+ * Get the current timeline state
641
+ *
642
+ * @returns Current timeline state
643
+ */
644
+ getState() {
645
+ return getCurrentState(this.history);
646
+ }
647
+ // ===== ASSET OPERATIONS =====
648
+ /**
649
+ * Register an asset
650
+ *
651
+ * @param asset - Asset to register
652
+ * @returns Dispatch result
653
+ */
654
+ registerAsset(asset) {
655
+ const result = dispatch(
656
+ this.history,
657
+ (state) => registerAsset(state, asset)
658
+ );
659
+ if (result.success) {
660
+ this.history = result.history;
661
+ this.notify();
662
+ }
663
+ return result;
664
+ }
665
+ /**
666
+ * Get an asset by ID
667
+ *
668
+ * @param assetId - Asset ID
669
+ * @returns The asset, or undefined if not found
670
+ */
671
+ getAsset(assetId) {
672
+ return getAsset(this.getState(), assetId);
673
+ }
674
+ // ===== CLIP OPERATIONS =====
675
+ /**
676
+ * Add a clip to a track
677
+ *
678
+ * @param trackId - ID of the track to add to
679
+ * @param clip - Clip to add
680
+ * @returns Dispatch result
681
+ */
682
+ addClip(trackId, clip) {
683
+ const result = dispatch(
684
+ this.history,
685
+ (state) => addClip(state, trackId, clip)
686
+ );
687
+ if (result.success) {
688
+ this.history = result.history;
689
+ this.notify();
690
+ }
691
+ return result;
692
+ }
693
+ /**
694
+ * Remove a clip
695
+ *
696
+ * @param clipId - ID of the clip to remove
697
+ * @returns Dispatch result
698
+ */
699
+ removeClip(clipId) {
700
+ const result = dispatch(
701
+ this.history,
702
+ (state) => removeClip(state, clipId)
703
+ );
704
+ if (result.success) {
705
+ this.history = result.history;
706
+ this.notify();
707
+ }
708
+ return result;
709
+ }
710
+ /**
711
+ * Move a clip to a new timeline position
712
+ *
713
+ * @param clipId - ID of the clip to move
714
+ * @param newStart - New timeline start frame
715
+ * @returns Dispatch result
716
+ */
717
+ moveClip(clipId, newStart) {
718
+ const result = dispatch(
719
+ this.history,
720
+ (state) => moveClip(state, clipId, newStart)
721
+ );
722
+ if (result.success) {
723
+ this.history = result.history;
724
+ this.notify();
725
+ }
726
+ return result;
727
+ }
728
+ /**
729
+ * Resize a clip
730
+ *
731
+ * @param clipId - ID of the clip to resize
732
+ * @param newStart - New timeline start frame
733
+ * @param newEnd - New timeline end frame
734
+ * @returns Dispatch result
735
+ */
736
+ resizeClip(clipId, newStart, newEnd) {
737
+ const result = dispatch(
738
+ this.history,
739
+ (state) => resizeClip(state, clipId, newStart, newEnd)
740
+ );
741
+ if (result.success) {
742
+ this.history = result.history;
743
+ this.notify();
744
+ }
745
+ return result;
746
+ }
747
+ /**
748
+ * Trim a clip (change media bounds)
749
+ *
750
+ * @param clipId - ID of the clip to trim
751
+ * @param newMediaIn - New media in frame
752
+ * @param newMediaOut - New media out frame
753
+ * @returns Dispatch result
754
+ */
755
+ trimClip(clipId, newMediaIn, newMediaOut) {
756
+ const result = dispatch(
757
+ this.history,
758
+ (state) => trimClip(state, clipId, newMediaIn, newMediaOut)
759
+ );
760
+ if (result.success) {
761
+ this.history = result.history;
762
+ this.notify();
763
+ }
764
+ return result;
765
+ }
766
+ /**
767
+ * Move a clip to a different track
768
+ *
769
+ * @param clipId - ID of the clip to move
770
+ * @param targetTrackId - ID of the target track
771
+ * @returns Dispatch result
772
+ */
773
+ moveClipToTrack(clipId, targetTrackId) {
774
+ const result = dispatch(
775
+ this.history,
776
+ (state) => moveClipToTrack(state, clipId, targetTrackId)
777
+ );
778
+ if (result.success) {
779
+ this.history = result.history;
780
+ this.notify();
781
+ }
782
+ return result;
783
+ }
784
+ // ===== TRACK OPERATIONS =====
785
+ /**
786
+ * Add a track
787
+ *
788
+ * @param track - Track to add
789
+ * @returns Dispatch result
790
+ */
791
+ addTrack(track) {
792
+ const result = dispatch(
793
+ this.history,
794
+ (state) => addTrack(state, track)
795
+ );
796
+ if (result.success) {
797
+ this.history = result.history;
798
+ this.notify();
799
+ }
800
+ return result;
801
+ }
802
+ /**
803
+ * Remove a track
804
+ *
805
+ * @param trackId - ID of the track to remove
806
+ * @returns Dispatch result
807
+ */
808
+ removeTrack(trackId) {
809
+ const result = dispatch(
810
+ this.history,
811
+ (state) => removeTrack(state, trackId)
812
+ );
813
+ if (result.success) {
814
+ this.history = result.history;
815
+ this.notify();
816
+ }
817
+ return result;
818
+ }
819
+ /**
820
+ * Move a track to a new position
821
+ *
822
+ * @param trackId - ID of the track to move
823
+ * @param newIndex - New index position
824
+ * @returns Dispatch result
825
+ */
826
+ moveTrack(trackId, newIndex) {
827
+ const result = dispatch(
828
+ this.history,
829
+ (state) => moveTrack(state, trackId, newIndex)
830
+ );
831
+ if (result.success) {
832
+ this.history = result.history;
833
+ this.notify();
834
+ }
835
+ return result;
836
+ }
837
+ /**
838
+ * Toggle track mute
839
+ *
840
+ * @param trackId - ID of the track
841
+ * @returns Dispatch result
842
+ */
843
+ toggleTrackMute(trackId) {
844
+ const result = dispatch(
845
+ this.history,
846
+ (state) => toggleTrackMute(state, trackId)
847
+ );
848
+ if (result.success) {
849
+ this.history = result.history;
850
+ this.notify();
851
+ }
852
+ return result;
853
+ }
854
+ /**
855
+ * Toggle track lock
856
+ *
857
+ * @param trackId - ID of the track
858
+ * @returns Dispatch result
859
+ */
860
+ toggleTrackLock(trackId) {
861
+ const result = dispatch(
862
+ this.history,
863
+ (state) => toggleTrackLock(state, trackId)
864
+ );
865
+ if (result.success) {
866
+ this.history = result.history;
867
+ this.notify();
868
+ }
869
+ return result;
870
+ }
871
+ // ===== TIMELINE OPERATIONS =====
872
+ /**
873
+ * Set timeline duration
874
+ *
875
+ * @param duration - New duration in frames
876
+ * @returns Dispatch result
877
+ */
878
+ setTimelineDuration(duration) {
879
+ const result = dispatch(
880
+ this.history,
881
+ (state) => setTimelineDuration(state, duration)
882
+ );
883
+ if (result.success) {
884
+ this.history = result.history;
885
+ this.notify();
886
+ }
887
+ return result;
888
+ }
889
+ /**
890
+ * Set timeline name
891
+ *
892
+ * @param name - New timeline name
893
+ * @returns Dispatch result
894
+ */
895
+ setTimelineName(name) {
896
+ const result = dispatch(
897
+ this.history,
898
+ (state) => setTimelineName(state, name)
899
+ );
900
+ if (result.success) {
901
+ this.history = result.history;
902
+ this.notify();
903
+ }
904
+ return result;
905
+ }
906
+ // ===== HISTORY OPERATIONS =====
907
+ /**
908
+ * Undo the last action
909
+ *
910
+ * @returns true if undo was performed
911
+ */
912
+ undo() {
913
+ if (!this.canUndo()) {
914
+ return false;
915
+ }
916
+ this.history = undo(this.history);
917
+ this.notify();
918
+ return true;
919
+ }
920
+ /**
921
+ * Redo the last undone action
922
+ *
923
+ * @returns true if redo was performed
924
+ */
925
+ redo() {
926
+ if (!this.canRedo()) {
927
+ return false;
928
+ }
929
+ this.history = redo(this.history);
930
+ this.notify();
931
+ return true;
932
+ }
933
+ /**
934
+ * Check if undo is available
935
+ *
936
+ * @returns true if undo is available
937
+ */
938
+ canUndo() {
939
+ return canUndo(this.history);
940
+ }
941
+ /**
942
+ * Check if redo is available
943
+ *
944
+ * @returns true if redo is available
945
+ */
946
+ canRedo() {
947
+ return canRedo(this.history);
948
+ }
949
+ // ===== QUERY OPERATIONS =====
950
+ /**
951
+ * Find a clip by ID
952
+ *
953
+ * @param clipId - Clip ID
954
+ * @returns The clip, or undefined if not found
955
+ */
956
+ findClipById(clipId) {
957
+ return findClipById(this.getState(), clipId);
958
+ }
959
+ /**
960
+ * Find a track by ID
961
+ *
962
+ * @param trackId - Track ID
963
+ * @returns The track, or undefined if not found
964
+ */
965
+ findTrackById(trackId) {
966
+ return findTrackById(this.getState(), trackId);
967
+ }
968
+ /**
969
+ * Get all clips on a track
970
+ *
971
+ * @param trackId - Track ID
972
+ * @returns Array of clips on the track
973
+ */
974
+ getClipsOnTrack(trackId) {
975
+ return getClipsOnTrack(this.getState(), trackId);
976
+ }
977
+ /**
978
+ * Get all clips at a specific frame
979
+ *
980
+ * @param frame - Frame to check
981
+ * @returns Array of clips at that frame
982
+ */
983
+ getClipsAtFrame(frame2) {
984
+ return getClipsAtFrame(this.getState(), frame2);
985
+ }
986
+ /**
987
+ * Get all clips in a frame range
988
+ *
989
+ * @param start - Start frame
990
+ * @param end - End frame
991
+ * @returns Array of clips in the range
992
+ */
993
+ getClipsInRange(start, end) {
994
+ return getClipsInRange(this.getState(), start, end);
995
+ }
996
+ /**
997
+ * Get all clips in the timeline
998
+ *
999
+ * @returns Array of all clips
1000
+ */
1001
+ getAllClips() {
1002
+ return getAllClips(this.getState());
1003
+ }
1004
+ /**
1005
+ * Get all tracks in the timeline
1006
+ *
1007
+ * @returns Array of all tracks
1008
+ */
1009
+ getAllTracks() {
1010
+ return getAllTracks(this.getState());
1011
+ }
1012
+ };
1013
+
1014
+ // src/types/timeline.ts
1015
+ function createTimeline(params) {
1016
+ const timeline = {
1017
+ id: params.id,
1018
+ name: params.name,
1019
+ fps: params.fps,
1020
+ duration: params.duration,
1021
+ tracks: params.tracks ?? []
1022
+ };
1023
+ if (params.metadata !== void 0) {
1024
+ timeline.metadata = params.metadata;
1025
+ }
1026
+ return timeline;
1027
+ }
1028
+
1029
+ // src/types/asset.ts
1030
+ function createAsset(params) {
1031
+ const asset = {
1032
+ id: params.id,
1033
+ type: params.type,
1034
+ duration: params.duration,
1035
+ sourceUrl: params.sourceUrl
1036
+ };
1037
+ if (params.metadata !== void 0) {
1038
+ asset.metadata = params.metadata;
1039
+ }
1040
+ return asset;
1041
+ }
1042
+
1043
+ // src/types/state.ts
1044
+ function createTimelineState(params) {
1045
+ const state = {
1046
+ timeline: params.timeline,
1047
+ assets: params.assets ?? /* @__PURE__ */ new Map(),
1048
+ linkGroups: params.linkGroups ?? /* @__PURE__ */ new Map(),
1049
+ groups: params.groups ?? /* @__PURE__ */ new Map(),
1050
+ markers: params.markers ?? {
1051
+ timeline: [],
1052
+ clips: [],
1053
+ regions: []
1054
+ }
1055
+ };
1056
+ if (params.workArea !== void 0) {
1057
+ state.workArea = params.workArea;
1058
+ }
1059
+ return state;
1060
+ }
1061
+
1062
+ // src/types/frame.ts
1063
+ function frame(value) {
1064
+ const rounded = Math.round(value);
1065
+ if (rounded < 0) {
1066
+ throw new Error(`Frame value must be non-negative, got: ${value}`);
1067
+ }
1068
+ return rounded;
1069
+ }
1070
+ function frameRate(value) {
1071
+ if (value <= 0) {
1072
+ throw new Error(`FrameRate must be positive, got: ${value}`);
1073
+ }
1074
+ return value;
1075
+ }
1076
+ function isValidFrame(value) {
1077
+ return Number.isInteger(value) && value >= 0;
1078
+ }
1079
+ function isValidFrameRate(value) {
1080
+ return value > 0;
1081
+ }
1082
+
1083
+ // src/utils/frame.ts
1084
+ function framesToSeconds(frames, fps) {
1085
+ return frames / fps;
1086
+ }
1087
+ function secondsToFrames(seconds, fps) {
1088
+ return frame(seconds * fps);
1089
+ }
1090
+ function framesToTimecode(frames, fps) {
1091
+ const totalFrames = frames;
1092
+ const framesPart = totalFrames % fps;
1093
+ const totalSeconds = Math.floor(totalFrames / fps);
1094
+ const secondsPart = totalSeconds % 60;
1095
+ const totalMinutes = Math.floor(totalSeconds / 60);
1096
+ const minutesPart = totalMinutes % 60;
1097
+ const hoursPart = Math.floor(totalMinutes / 60);
1098
+ return `${pad(hoursPart)}:${pad(minutesPart)}:${pad(secondsPart)}:${pad(framesPart)}`;
1099
+ }
1100
+ function framesToMinutesSeconds(frames, fps) {
1101
+ const totalSeconds = Math.floor(frames / fps);
1102
+ const minutes = Math.floor(totalSeconds / 60);
1103
+ const seconds = totalSeconds % 60;
1104
+ return `${minutes}:${pad(seconds)}`;
1105
+ }
1106
+ function clampFrame(value, min, max) {
1107
+ return frame(Math.max(min, Math.min(max, value)));
1108
+ }
1109
+ function addFrames(a, b) {
1110
+ return frame(a + b);
1111
+ }
1112
+ function subtractFrames(a, b) {
1113
+ return frame(Math.max(0, a - b));
1114
+ }
1115
+ function frameDuration(start, end) {
1116
+ return frame(end - start);
1117
+ }
1118
+ function pad(num, width = 2) {
1119
+ return num.toString().padStart(width, "0");
1120
+ }
1121
+
1122
+ export {
1123
+ createClip,
1124
+ getClipDuration,
1125
+ getClipMediaDuration,
1126
+ clipContainsFrame,
1127
+ clipsOverlap,
1128
+ validResult,
1129
+ invalidResult,
1130
+ invalidResults,
1131
+ combineResults,
1132
+ registerAsset,
1133
+ getAsset,
1134
+ hasAsset,
1135
+ getAllAssets,
1136
+ unregisterAsset,
1137
+ validateClip,
1138
+ validateTrack,
1139
+ validateTimeline,
1140
+ validateNoOverlap,
1141
+ findClipById,
1142
+ findTrackById,
1143
+ getClipsOnTrack,
1144
+ getClipsAtFrame,
1145
+ getClipsInRange,
1146
+ getAllClips,
1147
+ getAllTracks,
1148
+ findTrackIndex,
1149
+ createTrack,
1150
+ sortTrackClips,
1151
+ addClip,
1152
+ removeClip,
1153
+ moveClip,
1154
+ resizeClip,
1155
+ trimClip,
1156
+ updateClip,
1157
+ moveClipToTrack,
1158
+ addTrack,
1159
+ removeTrack,
1160
+ moveTrack,
1161
+ updateTrack,
1162
+ toggleTrackMute,
1163
+ toggleTrackLock,
1164
+ setTimelineDuration,
1165
+ setTimelineName,
1166
+ updateTimelineMetadata,
1167
+ TimelineEngine,
1168
+ createTimeline,
1169
+ createAsset,
1170
+ createTimelineState,
1171
+ frame,
1172
+ frameRate,
1173
+ isValidFrame,
1174
+ isValidFrameRate,
1175
+ framesToSeconds,
1176
+ secondsToFrames,
1177
+ framesToTimecode,
1178
+ framesToMinutesSeconds,
1179
+ clampFrame,
1180
+ addFrames,
1181
+ subtractFrames,
1182
+ frameDuration
1183
+ };