@twick/timeline 0.14.0

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 (38) hide show
  1. package/README.md +175 -0
  2. package/dist/context/timeline-context.d.ts +33 -0
  3. package/dist/context/undo-redo-context.d.ts +21 -0
  4. package/dist/core/addOns/animation.d.ts +24 -0
  5. package/dist/core/addOns/frame-effect.d.ts +14 -0
  6. package/dist/core/addOns/text-effect.d.ts +19 -0
  7. package/dist/core/editor/timeline.editor.d.ts +94 -0
  8. package/dist/core/elements/audio.element.d.ts +20 -0
  9. package/dist/core/elements/base.element.d.ts +35 -0
  10. package/dist/core/elements/caption.element.d.ts +10 -0
  11. package/dist/core/elements/circle.element.d.ts +13 -0
  12. package/dist/core/elements/icon.element.d.ts +9 -0
  13. package/dist/core/elements/image.element.d.ts +32 -0
  14. package/dist/core/elements/rect.element.d.ts +11 -0
  15. package/dist/core/elements/text.element.d.ts +26 -0
  16. package/dist/core/elements/video.element.d.ts +41 -0
  17. package/dist/core/track/track.d.ts +77 -0
  18. package/dist/core/track/track.friend.d.ts +34 -0
  19. package/dist/core/visitor/element-adder.d.ts +29 -0
  20. package/dist/core/visitor/element-cloner.d.ts +22 -0
  21. package/dist/core/visitor/element-deserializer.d.ts +23 -0
  22. package/dist/core/visitor/element-remover.d.ts +28 -0
  23. package/dist/core/visitor/element-serializer.d.ts +23 -0
  24. package/dist/core/visitor/element-splitter.d.ts +28 -0
  25. package/dist/core/visitor/element-updater.d.ts +28 -0
  26. package/dist/core/visitor/element-validator.d.ts +34 -0
  27. package/dist/core/visitor/element-visitor.d.ts +19 -0
  28. package/dist/index.d.ts +36 -0
  29. package/dist/index.js +2630 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/index.mjs +2628 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/services/data.service.d.ts +25 -0
  34. package/dist/types/index.d.ts +169 -0
  35. package/dist/utils/constants.d.ts +55 -0
  36. package/dist/utils/register-editor.d.ts +8 -0
  37. package/dist/utils/timeline.utils.d.ts +11 -0
  38. package/package.json +40 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,2628 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useState, createContext, useContext, useRef, useMemo, useEffect } from "react";
6
+ const PLAYER_STATE = {
7
+ REFRESH: "Refresh",
8
+ PLAYING: "Playing",
9
+ PAUSED: "Paused"
10
+ };
11
+ const CAPTION_STYLE = {
12
+ WORD_BG_HIGHLIGHT: "highlight_bg",
13
+ WORD_BY_WORD: "word_by_word",
14
+ WORD_BY_WORD_WITH_BG: "word_by_word_with_bg"
15
+ };
16
+ const CAPTION_STYLE_OPTIONS = {
17
+ [CAPTION_STYLE.WORD_BG_HIGHLIGHT]: {
18
+ label: "Highlight Background",
19
+ value: CAPTION_STYLE.WORD_BG_HIGHLIGHT
20
+ },
21
+ [CAPTION_STYLE.WORD_BY_WORD]: {
22
+ label: "Word by Word",
23
+ value: CAPTION_STYLE.WORD_BY_WORD
24
+ },
25
+ [CAPTION_STYLE.WORD_BY_WORD_WITH_BG]: {
26
+ label: "Word with Background",
27
+ value: CAPTION_STYLE.WORD_BY_WORD_WITH_BG
28
+ }
29
+ };
30
+ const CAPTION_FONT = {
31
+ size: 40
32
+ };
33
+ const CAPTION_COLOR = {
34
+ text: "#ffffff",
35
+ highlight: "#ff4081",
36
+ bgColor: "#8C52FF"
37
+ };
38
+ const WORDS_PER_PHRASE = 4;
39
+ const TIMELINE_ACTION = {
40
+ NONE: "none",
41
+ SET_PLAYER_STATE: "setPlayerState",
42
+ UPDATE_PLAYER_DATA: "updatePlayerData",
43
+ ON_PLAYER_UPDATED: "onPlayerUpdated"
44
+ };
45
+ const TIMELINE_ELEMENT_TYPE = {
46
+ VIDEO: "video",
47
+ CAPTION: "caption",
48
+ IMAGE: "image",
49
+ AUDIO: "audio",
50
+ TEXT: "text",
51
+ RECT: "rect",
52
+ CIRCLE: "circle",
53
+ ICON: "icon"
54
+ };
55
+ const PROCESS_STATE = {
56
+ IDLE: "Idle",
57
+ PROCESSING: "Processing",
58
+ COMPLETED: "Completed",
59
+ FAILED: "Failed"
60
+ };
61
+ const getDecimalNumber = (num, precision = 3) => {
62
+ return Number(num.toFixed(precision));
63
+ };
64
+ const getTotalDuration = (tracks) => {
65
+ return (tracks || []).reduce(
66
+ (maxDuration, timeline) => Math.max(
67
+ maxDuration,
68
+ ((timeline == null ? void 0 : timeline.elements) || []).reduce(
69
+ (timelineDuration, element) => Math.max(timelineDuration, element.e),
70
+ 0
71
+ )
72
+ ),
73
+ 0
74
+ );
75
+ };
76
+ const generateShortUuid = () => {
77
+ return "xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
78
+ const r = Math.random() * 16 | 0, v = c === "x" ? r : r & 3 | 8;
79
+ return v.toString(16);
80
+ });
81
+ };
82
+ const getCurrentElements = (currentTime, tracks) => {
83
+ const currentElements = [];
84
+ if (tracks == null ? void 0 : tracks.length) {
85
+ for (let i = 0; i < tracks.length; i++) {
86
+ if (tracks[i]) {
87
+ const elements = tracks[i].getElements();
88
+ for (let j = 0; j < elements.length; j++) {
89
+ const element = elements[j];
90
+ if (element.getStart() <= currentTime && element.getEnd() >= currentTime) {
91
+ currentElements.push(element);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ return currentElements;
98
+ };
99
+ const canSplitElement = (element, currentTime) => {
100
+ return element.getStart() <= currentTime && element.getEnd() >= currentTime;
101
+ };
102
+ const isElementId = (id) => id.startsWith("e-");
103
+ const isTrackId = (id) => id.startsWith("t-");
104
+ const imageDimensionsCache = {};
105
+ const videoMetaCache = {};
106
+ const audioDurationCache = {};
107
+ const getAudioDuration = (audioSrc) => {
108
+ if (audioDurationCache[audioSrc]) {
109
+ return Promise.resolve(audioDurationCache[audioSrc]);
110
+ }
111
+ return new Promise((resolve, reject) => {
112
+ const audio = document.createElement("audio");
113
+ audio.preload = "metadata";
114
+ const isSafeUrl = /^(https?:|blob:|data:audio\/)/i.test(audioSrc);
115
+ if (!isSafeUrl) {
116
+ throw new Error("Unsafe audio source URL");
117
+ }
118
+ audio.src = audioSrc;
119
+ audio.onloadedmetadata = () => {
120
+ const duration = audio.duration;
121
+ audioDurationCache[audioSrc] = duration;
122
+ resolve(duration);
123
+ };
124
+ audio.onerror = () => {
125
+ reject(new Error("Failed to load audio metadata"));
126
+ };
127
+ });
128
+ };
129
+ const concurrencyLimit = 5;
130
+ let activeCount = 0;
131
+ const queue = [];
132
+ function runNext() {
133
+ if (queue.length === 0 || activeCount >= concurrencyLimit) return;
134
+ const next = queue.shift();
135
+ if (next) {
136
+ activeCount++;
137
+ next();
138
+ }
139
+ }
140
+ function limit(fn) {
141
+ return new Promise((resolve, reject) => {
142
+ const task = () => {
143
+ fn().then(resolve).catch(reject).finally(() => {
144
+ activeCount--;
145
+ runNext();
146
+ });
147
+ };
148
+ if (activeCount < concurrencyLimit) {
149
+ activeCount++;
150
+ task();
151
+ } else {
152
+ queue.push(task);
153
+ }
154
+ });
155
+ }
156
+ const loadImageDimensions = (url) => {
157
+ return new Promise((resolve, reject) => {
158
+ if (typeof document === "undefined") {
159
+ reject(new Error("getImageDimensions() is only available in the browser."));
160
+ return;
161
+ }
162
+ const img = new Image();
163
+ img.onload = () => {
164
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
165
+ };
166
+ img.onerror = reject;
167
+ img.src = url;
168
+ });
169
+ };
170
+ const getImageDimensions = (url) => {
171
+ if (imageDimensionsCache[url]) {
172
+ return Promise.resolve(imageDimensionsCache[url]);
173
+ }
174
+ return limit(() => loadImageDimensions(url)).then((dimensions) => {
175
+ imageDimensionsCache[url] = dimensions;
176
+ return dimensions;
177
+ });
178
+ };
179
+ const getVideoMeta = (videoSrc) => {
180
+ if (videoMetaCache[videoSrc]) {
181
+ return Promise.resolve(videoMetaCache[videoSrc]);
182
+ }
183
+ return new Promise((resolve, reject) => {
184
+ const video = document.createElement("video");
185
+ video.preload = "metadata";
186
+ const isSafeUrl = /^(https?:|blob:|data:video\/)/i.test(videoSrc);
187
+ if (!isSafeUrl) {
188
+ reject(new Error("Unsafe video source URL"));
189
+ return;
190
+ }
191
+ video.src = videoSrc;
192
+ video.onloadedmetadata = () => {
193
+ const meta = {
194
+ width: video.videoWidth,
195
+ height: video.videoHeight,
196
+ duration: video.duration
197
+ };
198
+ videoMetaCache[videoSrc] = meta;
199
+ resolve(meta);
200
+ };
201
+ video.onerror = () => reject(new Error("Failed to load video metadata"));
202
+ });
203
+ };
204
+ const getObjectFitSize = (objectFit, elementSize, containerSize) => {
205
+ const elementAspectRatio = elementSize.width / elementSize.height;
206
+ const containerAspectRatio = containerSize.width / containerSize.height;
207
+ switch (objectFit) {
208
+ case "contain":
209
+ if (elementAspectRatio > containerAspectRatio) {
210
+ return {
211
+ width: containerSize.width,
212
+ height: containerSize.width / elementAspectRatio
213
+ };
214
+ } else {
215
+ return {
216
+ width: containerSize.height * elementAspectRatio,
217
+ height: containerSize.height
218
+ };
219
+ }
220
+ case "cover":
221
+ if (elementAspectRatio > containerAspectRatio) {
222
+ return {
223
+ width: containerSize.height * elementAspectRatio,
224
+ height: containerSize.height
225
+ };
226
+ } else {
227
+ return {
228
+ width: containerSize.width,
229
+ height: containerSize.width / elementAspectRatio
230
+ };
231
+ }
232
+ case "fill":
233
+ return {
234
+ width: containerSize.width,
235
+ height: containerSize.height
236
+ };
237
+ default:
238
+ return {
239
+ width: elementSize.width,
240
+ height: elementSize.height
241
+ };
242
+ }
243
+ };
244
+ class TrackElement {
245
+ constructor(type, id) {
246
+ __publicField(this, "id");
247
+ __publicField(this, "type");
248
+ __publicField(this, "s");
249
+ __publicField(this, "e");
250
+ __publicField(this, "trackId");
251
+ __publicField(this, "name");
252
+ __publicField(this, "animation");
253
+ __publicField(this, "props");
254
+ this.id = id ?? `e-${generateShortUuid()}`;
255
+ this.type = type;
256
+ this.props = {
257
+ x: 0,
258
+ y: 0
259
+ };
260
+ }
261
+ getId() {
262
+ return this.id;
263
+ }
264
+ getType() {
265
+ return this.type;
266
+ }
267
+ getStart() {
268
+ return this.s;
269
+ }
270
+ getEnd() {
271
+ return this.e;
272
+ }
273
+ getDuration() {
274
+ return this.e - this.s;
275
+ }
276
+ getTrackId() {
277
+ return this.trackId;
278
+ }
279
+ getProps() {
280
+ return this.props;
281
+ }
282
+ getName() {
283
+ return this.name;
284
+ }
285
+ getAnimation() {
286
+ return this.animation;
287
+ }
288
+ getPosition() {
289
+ var _a, _b;
290
+ return {
291
+ x: ((_a = this.props) == null ? void 0 : _a.x) ?? 0,
292
+ y: ((_b = this.props) == null ? void 0 : _b.y) ?? 0
293
+ };
294
+ }
295
+ setId(id) {
296
+ this.id = id;
297
+ return this;
298
+ }
299
+ setType(type) {
300
+ this.type = type;
301
+ return this;
302
+ }
303
+ setStart(s) {
304
+ this.s = Math.max(0, s);
305
+ return this;
306
+ }
307
+ setEnd(e) {
308
+ this.e = Math.max(this.s ?? 0, e);
309
+ return this;
310
+ }
311
+ setTrackId(trackId) {
312
+ this.trackId = trackId;
313
+ return this;
314
+ }
315
+ setName(name) {
316
+ this.name = name;
317
+ return this;
318
+ }
319
+ setAnimation(animation) {
320
+ this.animation = animation;
321
+ return this;
322
+ }
323
+ setPosition(position) {
324
+ this.props.x = position.x;
325
+ this.props.y = position.y;
326
+ return this;
327
+ }
328
+ setProps(props) {
329
+ this.props = structuredClone(props);
330
+ return this;
331
+ }
332
+ }
333
+ class VideoElement extends TrackElement {
334
+ constructor(src, parentSize) {
335
+ super(TIMELINE_ELEMENT_TYPE.VIDEO);
336
+ __publicField(this, "baseSize");
337
+ __publicField(this, "mediaDuration");
338
+ __publicField(this, "parentSize");
339
+ __publicField(this, "backgroundColor");
340
+ __publicField(this, "objectFit");
341
+ __publicField(this, "frameEffects");
342
+ __publicField(this, "frame");
343
+ this.objectFit = "cover";
344
+ this.frameEffects = [];
345
+ this.parentSize = parentSize;
346
+ this.props = {
347
+ src,
348
+ play: true,
349
+ playbackRate: 1,
350
+ time: 0,
351
+ mediaFilter: "none",
352
+ volume: 1
353
+ };
354
+ }
355
+ getParentSize() {
356
+ return this.parentSize;
357
+ }
358
+ getFrame() {
359
+ return this.frame;
360
+ }
361
+ getFrameEffects() {
362
+ return this.frameEffects;
363
+ }
364
+ getBackgroundColor() {
365
+ return this.backgroundColor;
366
+ }
367
+ getObjectFit() {
368
+ return this.objectFit;
369
+ }
370
+ getMediaDuration() {
371
+ return this.mediaDuration;
372
+ }
373
+ getStartAt() {
374
+ return this.props.time || 0;
375
+ }
376
+ getPosition() {
377
+ return {
378
+ x: this.frame.x ?? 0,
379
+ y: this.frame.y ?? 0
380
+ };
381
+ }
382
+ async updateVideoMeta(updateFrame = true) {
383
+ const meta = await getVideoMeta(this.props.src);
384
+ if (updateFrame) {
385
+ const baseSize = getObjectFitSize(
386
+ "contain",
387
+ { width: meta.width, height: meta.height },
388
+ this.parentSize
389
+ );
390
+ this.frame = {
391
+ ...this.frame,
392
+ size: [baseSize.width, baseSize.height]
393
+ };
394
+ }
395
+ this.mediaDuration = meta.duration;
396
+ }
397
+ setPosition(position) {
398
+ this.frame.x = position.x;
399
+ this.frame.y = position.y;
400
+ return this;
401
+ }
402
+ async setSrc(src) {
403
+ this.props.src = src;
404
+ await this.updateVideoMeta();
405
+ return this;
406
+ }
407
+ setMediaDuration(mediaDuration) {
408
+ this.mediaDuration = mediaDuration;
409
+ return this;
410
+ }
411
+ setParentSize(parentSize) {
412
+ this.parentSize = structuredClone(parentSize);
413
+ return this;
414
+ }
415
+ setObjectFit(objectFit) {
416
+ this.objectFit = objectFit;
417
+ return this;
418
+ }
419
+ setFrame(frame) {
420
+ this.frame = structuredClone(frame);
421
+ return this;
422
+ }
423
+ setPlay(play) {
424
+ this.props.play = play;
425
+ return this;
426
+ }
427
+ setPlaybackRate(playbackRate) {
428
+ this.props.playbackRate = playbackRate;
429
+ return this;
430
+ }
431
+ setStartAt(time) {
432
+ this.props.time = Math.max(0, time);
433
+ return this;
434
+ }
435
+ setMediaFilter(mediaFilter) {
436
+ this.props.mediaFilter = mediaFilter;
437
+ return this;
438
+ }
439
+ setVolume(volume) {
440
+ this.props.volume = volume;
441
+ return this;
442
+ }
443
+ setBackgroundColor(backgroundColor) {
444
+ this.backgroundColor = backgroundColor;
445
+ return this;
446
+ }
447
+ setProps(props) {
448
+ this.props = {
449
+ play: this.props.play,
450
+ ...structuredClone(props),
451
+ src: this.props.src
452
+ };
453
+ return this;
454
+ }
455
+ setFrameEffects(frameEffects) {
456
+ this.frameEffects = frameEffects;
457
+ return this;
458
+ }
459
+ addFrameEffect(frameEffect) {
460
+ var _a;
461
+ (_a = this.frameEffects) == null ? void 0 : _a.push(frameEffect);
462
+ return this;
463
+ }
464
+ accept(visitor) {
465
+ return visitor.visitVideoElement(this);
466
+ }
467
+ }
468
+ class AudioElement extends TrackElement {
469
+ constructor(src) {
470
+ super(TIMELINE_ELEMENT_TYPE.AUDIO);
471
+ __publicField(this, "mediaDuration");
472
+ this.props = {
473
+ src,
474
+ time: 0,
475
+ play: true,
476
+ playbackRate: 1,
477
+ volume: 1,
478
+ loop: false
479
+ };
480
+ }
481
+ getMediaDuration() {
482
+ return this.mediaDuration;
483
+ }
484
+ getStartAt() {
485
+ return this.props.time || 0;
486
+ }
487
+ async updateAudioMeta() {
488
+ this.mediaDuration = await getAudioDuration(this.props.src);
489
+ }
490
+ async setSrc(src) {
491
+ this.props.src = src;
492
+ await this.updateAudioMeta();
493
+ return this;
494
+ }
495
+ setMediaDuration(mediaDuration) {
496
+ this.mediaDuration = mediaDuration;
497
+ return this;
498
+ }
499
+ setVolume(volume) {
500
+ this.props.volume = volume;
501
+ return this;
502
+ }
503
+ setLoop(loop) {
504
+ this.props.loop = loop;
505
+ return this;
506
+ }
507
+ setStartAt(time) {
508
+ this.props.time = Math.max(0, time);
509
+ return this;
510
+ }
511
+ setPlaybackRate(playbackRate) {
512
+ this.props.playbackRate = playbackRate;
513
+ return this;
514
+ }
515
+ setProps(props) {
516
+ this.props = {
517
+ play: this.props.play,
518
+ ...structuredClone(props),
519
+ src: this.props.src
520
+ };
521
+ return this;
522
+ }
523
+ accept(visitor) {
524
+ return visitor.visitAudioElement(this);
525
+ }
526
+ }
527
+ class ImageElement extends TrackElement {
528
+ constructor(src, parentSize) {
529
+ super(TIMELINE_ELEMENT_TYPE.IMAGE);
530
+ __publicField(this, "backgroundColor");
531
+ __publicField(this, "parentSize");
532
+ __publicField(this, "objectFit");
533
+ __publicField(this, "frameEffects");
534
+ __publicField(this, "frame");
535
+ this.parentSize = parentSize;
536
+ this.objectFit = "cover";
537
+ this.frameEffects = [];
538
+ this.props = {
539
+ src,
540
+ mediaFilter: "none"
541
+ };
542
+ this.frame = {
543
+ x: 0,
544
+ y: 0
545
+ };
546
+ }
547
+ getParentSize() {
548
+ return this.parentSize;
549
+ }
550
+ getFrame() {
551
+ return this.frame;
552
+ }
553
+ getFrameEffects() {
554
+ return this.frameEffects;
555
+ }
556
+ getBackgroundColor() {
557
+ return this.backgroundColor;
558
+ }
559
+ getObjectFit() {
560
+ return this.objectFit;
561
+ }
562
+ getPosition() {
563
+ return {
564
+ x: this.frame.x ?? 0,
565
+ y: this.frame.y ?? 0
566
+ };
567
+ }
568
+ async updateImageMeta(updateFrame = true) {
569
+ const meta = await getImageDimensions(this.props.src);
570
+ if (updateFrame) {
571
+ const baseSize = getObjectFitSize(
572
+ "contain",
573
+ { width: meta.width, height: meta.height },
574
+ this.parentSize
575
+ );
576
+ this.frame = {
577
+ size: [baseSize.width, baseSize.height],
578
+ ...this.frame
579
+ };
580
+ }
581
+ }
582
+ setPosition(position) {
583
+ this.frame.x = position.x;
584
+ this.frame.y = position.y;
585
+ return this;
586
+ }
587
+ async setSrc(src) {
588
+ this.props.src = src;
589
+ await this.updateImageMeta();
590
+ return this;
591
+ }
592
+ setObjectFit(objectFit) {
593
+ this.objectFit = objectFit;
594
+ return this;
595
+ }
596
+ setFrame(frame) {
597
+ this.frame = structuredClone(frame);
598
+ return this;
599
+ }
600
+ setParentSize(parentSize) {
601
+ this.parentSize = structuredClone(parentSize);
602
+ return this;
603
+ }
604
+ setMediaFilter(mediaFilter) {
605
+ this.props.mediaFilter = mediaFilter;
606
+ return this;
607
+ }
608
+ setBackgroundColor(backgroundColor) {
609
+ this.backgroundColor = backgroundColor;
610
+ return this;
611
+ }
612
+ setProps(props) {
613
+ this.props = { ...structuredClone(props), src: this.props.src };
614
+ return this;
615
+ }
616
+ setFrameEffects(frameEffects) {
617
+ this.frameEffects = frameEffects;
618
+ return this;
619
+ }
620
+ addFrameEffect(frameEffect) {
621
+ var _a;
622
+ (_a = this.frameEffects) == null ? void 0 : _a.push(frameEffect);
623
+ return this;
624
+ }
625
+ accept(visitor) {
626
+ return visitor.visitImageElement(this);
627
+ }
628
+ }
629
+ class TextElement extends TrackElement {
630
+ constructor(text) {
631
+ super(TIMELINE_ELEMENT_TYPE.TEXT);
632
+ __publicField(this, "textEffect");
633
+ this.props = {
634
+ text,
635
+ fill: "#888888"
636
+ //default-grey
637
+ };
638
+ }
639
+ getTextEffect() {
640
+ return this.textEffect;
641
+ }
642
+ getText() {
643
+ return this.props.text;
644
+ }
645
+ getStrokeColor() {
646
+ return this.props.stroke;
647
+ }
648
+ getLineWidth() {
649
+ return this.props.lineWidth;
650
+ }
651
+ setText(text) {
652
+ this.props.text = text;
653
+ return this;
654
+ }
655
+ setFill(fill) {
656
+ this.props.fill = fill;
657
+ return this;
658
+ }
659
+ setRotation(rotation) {
660
+ this.props.rotation = rotation;
661
+ return this;
662
+ }
663
+ setFontSize(fontSize) {
664
+ this.props.fontSize = fontSize;
665
+ return this;
666
+ }
667
+ setFontFamily(fontFamily) {
668
+ this.props.fontFamily = fontFamily;
669
+ return this;
670
+ }
671
+ setFontWeight(fontWeight) {
672
+ this.props.fontWeight = fontWeight;
673
+ return this;
674
+ }
675
+ setFontStyle(fontStyle) {
676
+ this.props.fontStyle = fontStyle;
677
+ return this;
678
+ }
679
+ setTextEffect(textEffect) {
680
+ this.textEffect = textEffect;
681
+ return this;
682
+ }
683
+ setTextAlign(textAlign) {
684
+ this.props.textAlign = textAlign;
685
+ return this;
686
+ }
687
+ setStrokeColor(stroke) {
688
+ this.props.stroke = stroke;
689
+ return this;
690
+ }
691
+ setLineWidth(lineWidth) {
692
+ this.props.lineWidth = lineWidth;
693
+ return this;
694
+ }
695
+ accept(visitor) {
696
+ return visitor.visitTextElement(this);
697
+ }
698
+ }
699
+ class CaptionElement extends TrackElement {
700
+ constructor(t, start, end) {
701
+ super(TIMELINE_ELEMENT_TYPE.CAPTION);
702
+ __publicField(this, "t");
703
+ this.t = t;
704
+ this.s = start;
705
+ this.e = end;
706
+ }
707
+ getText() {
708
+ return this.t;
709
+ }
710
+ setText(t) {
711
+ this.t = t;
712
+ return this;
713
+ }
714
+ accept(visitor) {
715
+ return visitor.visitCaptionElement(this);
716
+ }
717
+ }
718
+ class IconElement extends TrackElement {
719
+ constructor(src, size) {
720
+ super(TIMELINE_ELEMENT_TYPE.ICON);
721
+ this.props = {
722
+ src,
723
+ size
724
+ };
725
+ }
726
+ accept(visitor) {
727
+ return visitor.visitIconElement(this);
728
+ }
729
+ }
730
+ class CircleElement extends TrackElement {
731
+ constructor(fill, radius) {
732
+ super(TIMELINE_ELEMENT_TYPE.CIRCLE);
733
+ this.props = {
734
+ radius,
735
+ fill
736
+ };
737
+ }
738
+ getFill() {
739
+ return this.props.fill;
740
+ }
741
+ getRadius() {
742
+ return this.props.radius;
743
+ }
744
+ setFill(fill) {
745
+ this.props.fill = fill;
746
+ return this;
747
+ }
748
+ setRadius(radius) {
749
+ this.props.radius = radius;
750
+ return this;
751
+ }
752
+ accept(visitor) {
753
+ return visitor.visitCircleElement(this);
754
+ }
755
+ }
756
+ class RectElement extends TrackElement {
757
+ constructor(fill, size) {
758
+ super(TIMELINE_ELEMENT_TYPE.RECT);
759
+ this.props = {
760
+ width: size.width,
761
+ height: size.height,
762
+ fill
763
+ };
764
+ }
765
+ setFill(fill) {
766
+ this.props.fill = fill;
767
+ return this;
768
+ }
769
+ setSize(size) {
770
+ this.props.width = size.width;
771
+ this.props.height = size.height;
772
+ return this;
773
+ }
774
+ accept(visitor) {
775
+ return visitor.visitRectElement(this);
776
+ }
777
+ }
778
+ class ElementAnimation {
779
+ constructor(name) {
780
+ __publicField(this, "name");
781
+ __publicField(this, "interval");
782
+ __publicField(this, "intensity");
783
+ __publicField(this, "animate");
784
+ __publicField(this, "mode");
785
+ __publicField(this, "direction");
786
+ this.name = name;
787
+ }
788
+ getName() {
789
+ return this.name;
790
+ }
791
+ getInterval() {
792
+ return this.interval;
793
+ }
794
+ getIntensity() {
795
+ return this.intensity;
796
+ }
797
+ getAnimate() {
798
+ return this.animate;
799
+ }
800
+ getMode() {
801
+ return this.mode;
802
+ }
803
+ getDirection() {
804
+ return this.direction;
805
+ }
806
+ setInterval(interval) {
807
+ this.interval = interval;
808
+ }
809
+ setIntensity(intensity) {
810
+ this.intensity = intensity;
811
+ }
812
+ setAnimate(animate) {
813
+ this.animate = animate;
814
+ }
815
+ setMode(mode) {
816
+ this.mode = mode;
817
+ }
818
+ setDirection(direction) {
819
+ this.direction = direction;
820
+ }
821
+ toJSON() {
822
+ return {
823
+ name: this.name,
824
+ interval: this.interval,
825
+ intensity: this.intensity,
826
+ animate: this.animate,
827
+ mode: this.mode,
828
+ direction: this.direction
829
+ };
830
+ }
831
+ static fromJSON(json) {
832
+ const animation = new ElementAnimation(json.name);
833
+ animation.setInterval(json.interval);
834
+ animation.setIntensity(json.intensity);
835
+ animation.setAnimate(json.animate);
836
+ animation.setMode(json.mode);
837
+ animation.setDirection(json.direction);
838
+ return animation;
839
+ }
840
+ }
841
+ class ElementFrameEffect {
842
+ constructor(start, end) {
843
+ __publicField(this, "s");
844
+ __publicField(this, "e");
845
+ __publicField(this, "props");
846
+ this.s = start;
847
+ this.e = end;
848
+ }
849
+ setProps(props) {
850
+ this.props = props;
851
+ }
852
+ getProps() {
853
+ return this.props;
854
+ }
855
+ getStart() {
856
+ return this.s;
857
+ }
858
+ getEnd() {
859
+ return this.e;
860
+ }
861
+ toJSON() {
862
+ return {
863
+ s: this.s,
864
+ e: this.e,
865
+ props: this.props
866
+ };
867
+ }
868
+ static fromJSON(json) {
869
+ const effect = new ElementFrameEffect(json.s, json.e);
870
+ effect.setProps(json.props);
871
+ return effect;
872
+ }
873
+ }
874
+ class ElementTextEffect {
875
+ constructor(name) {
876
+ __publicField(this, "name");
877
+ __publicField(this, "duration");
878
+ __publicField(this, "delay");
879
+ __publicField(this, "bufferTime");
880
+ this.name = name;
881
+ }
882
+ getName() {
883
+ return this.name;
884
+ }
885
+ getDuration() {
886
+ return this.duration;
887
+ }
888
+ getDelay() {
889
+ return this.delay;
890
+ }
891
+ getBufferTime() {
892
+ return this.bufferTime;
893
+ }
894
+ setName(name) {
895
+ this.name = name;
896
+ }
897
+ setDuration(duration) {
898
+ this.duration = duration;
899
+ }
900
+ setDelay(delay) {
901
+ this.delay = delay;
902
+ }
903
+ setBufferTime(bufferTime) {
904
+ this.bufferTime = bufferTime;
905
+ }
906
+ toJSON() {
907
+ return {
908
+ name: this.name,
909
+ delay: this.delay,
910
+ duration: this.duration,
911
+ bufferTime: this.bufferTime
912
+ };
913
+ }
914
+ static fromJSON(json) {
915
+ const effect = new ElementTextEffect(json.name);
916
+ effect.setDelay(json.delay);
917
+ effect.setDuration(json.duration);
918
+ effect.setBufferTime(json.bufferTime);
919
+ return effect;
920
+ }
921
+ }
922
+ class ElementDeserializer {
923
+ static deserializeBaseElement(element, json) {
924
+ if (json.id) element.setId(json.id);
925
+ if (json.trackId) element.setTrackId(json.trackId);
926
+ if (json.s !== void 0) element.setStart(json.s);
927
+ if (json.e !== void 0) element.setEnd(json.e);
928
+ if (json.props) element.setProps(json.props);
929
+ if (json.animation) element.setAnimation(ElementAnimation.fromJSON(json.animation));
930
+ }
931
+ static deserializeVideoElement(json) {
932
+ var _a;
933
+ const parentSize = json.frame && json.frame.size ? { width: json.frame.size[0], height: json.frame.size[1] } : { width: 0, height: 0 };
934
+ const videoElement = new VideoElement(((_a = json.props) == null ? void 0 : _a.src) || "", parentSize);
935
+ ElementDeserializer.deserializeBaseElement(videoElement, json);
936
+ if (json.mediaDuration !== void 0) videoElement.setMediaDuration(json.mediaDuration);
937
+ if (json.objectFit) videoElement.setObjectFit(json.objectFit);
938
+ if (json.frame) videoElement.setFrame(json.frame);
939
+ if (json.frameEffects) videoElement.setFrameEffects(json.frameEffects.map((frameEffect) => ElementFrameEffect.fromJSON(frameEffect)));
940
+ if (json.backgroundColor) videoElement.setBackgroundColor(json.backgroundColor);
941
+ return videoElement;
942
+ }
943
+ static deserializeAudioElement(json) {
944
+ var _a;
945
+ const audioElement = new AudioElement(((_a = json.props) == null ? void 0 : _a.src) || "");
946
+ ElementDeserializer.deserializeBaseElement(audioElement, json);
947
+ if (json.mediaDuration !== void 0) audioElement.setMediaDuration(json.mediaDuration);
948
+ return audioElement;
949
+ }
950
+ static deserializeImageElement(json) {
951
+ var _a;
952
+ const parentSize = json.frame && json.frame.size ? { width: json.frame.size[0], height: json.frame.size[1] } : { width: 0, height: 0 };
953
+ const imageElement = new ImageElement(((_a = json.props) == null ? void 0 : _a.src) || "", parentSize);
954
+ ElementDeserializer.deserializeBaseElement(imageElement, json);
955
+ if (json.objectFit) imageElement.setObjectFit(json.objectFit);
956
+ if (json.frame) imageElement.setFrame(json.frame);
957
+ if (json.frameEffects) imageElement.setFrameEffects(json.frameEffects.map((frameEffect) => ElementFrameEffect.fromJSON(frameEffect)));
958
+ if (json.backgroundColor) imageElement.setBackgroundColor(json.backgroundColor);
959
+ return imageElement;
960
+ }
961
+ static deserializeTextElement(json) {
962
+ var _a;
963
+ const textElement = new TextElement(((_a = json.props) == null ? void 0 : _a.text) || "");
964
+ ElementDeserializer.deserializeBaseElement(textElement, json);
965
+ if (json.textEffect) textElement.setTextEffect(ElementTextEffect.fromJSON(json.textEffect));
966
+ return textElement;
967
+ }
968
+ static deserializeCaptionElement(json) {
969
+ const captionElement = new CaptionElement(
970
+ json.t || "",
971
+ json.s || 0,
972
+ json.e || 0
973
+ );
974
+ ElementDeserializer.deserializeBaseElement(captionElement, json);
975
+ return captionElement;
976
+ }
977
+ static deserializeIconElement(json) {
978
+ var _a, _b;
979
+ const size = ((_a = json.props) == null ? void 0 : _a.size) ? { width: json.props.size[0], height: json.props.size[1] } : { width: 0, height: 0 };
980
+ const iconElement = new IconElement(
981
+ ((_b = json.props) == null ? void 0 : _b.src) || "",
982
+ size
983
+ );
984
+ ElementDeserializer.deserializeBaseElement(iconElement, json);
985
+ return iconElement;
986
+ }
987
+ static deserializeCircleElement(json) {
988
+ var _a, _b;
989
+ const circleElement = new CircleElement(
990
+ ((_a = json.props) == null ? void 0 : _a.fill) || "",
991
+ ((_b = json.props) == null ? void 0 : _b.radius) || 0
992
+ );
993
+ ElementDeserializer.deserializeBaseElement(circleElement, json);
994
+ return circleElement;
995
+ }
996
+ static deserializeRectElement(json) {
997
+ var _a, _b, _c;
998
+ const rectElement = new RectElement(
999
+ ((_a = json.props) == null ? void 0 : _a.fill) || "",
1000
+ {
1001
+ width: ((_b = json.props) == null ? void 0 : _b.width) || 0,
1002
+ height: ((_c = json.props) == null ? void 0 : _c.height) || 0
1003
+ }
1004
+ );
1005
+ ElementDeserializer.deserializeBaseElement(rectElement, json);
1006
+ return rectElement;
1007
+ }
1008
+ static fromJSON(json) {
1009
+ try {
1010
+ switch (json.type) {
1011
+ case "video":
1012
+ return ElementDeserializer.deserializeVideoElement(json);
1013
+ case "audio":
1014
+ return ElementDeserializer.deserializeAudioElement(json);
1015
+ case "image":
1016
+ return ElementDeserializer.deserializeImageElement(json);
1017
+ case "text":
1018
+ return ElementDeserializer.deserializeTextElement(json);
1019
+ case "caption":
1020
+ return ElementDeserializer.deserializeCaptionElement(json);
1021
+ case "icon":
1022
+ return ElementDeserializer.deserializeIconElement(json);
1023
+ case "circle":
1024
+ return ElementDeserializer.deserializeCircleElement(json);
1025
+ case "rect":
1026
+ return ElementDeserializer.deserializeRectElement(json);
1027
+ default:
1028
+ throw new Error(`Unknown element type: ${json.type}`);
1029
+ }
1030
+ } catch (error) {
1031
+ console.error("Error deserializing element:", error);
1032
+ return null;
1033
+ }
1034
+ }
1035
+ }
1036
+ class ElementSerializer {
1037
+ serializeElement(element) {
1038
+ var _a;
1039
+ return {
1040
+ id: element.getId(),
1041
+ trackId: element.getTrackId(),
1042
+ type: element.getType(),
1043
+ name: element.getName(),
1044
+ s: element.getStart(),
1045
+ e: element.getEnd(),
1046
+ props: structuredClone(element.getProps()),
1047
+ animation: (_a = element.getAnimation()) == null ? void 0 : _a.toJSON()
1048
+ };
1049
+ }
1050
+ visitVideoElement(element) {
1051
+ var _a;
1052
+ return {
1053
+ ...this.serializeElement(element),
1054
+ frame: structuredClone(element.getFrame()),
1055
+ frameEffects: (_a = element.getFrameEffects()) == null ? void 0 : _a.map((frameEffect) => frameEffect.toJSON()),
1056
+ backgroundColor: element.getBackgroundColor(),
1057
+ objectFit: element.getObjectFit(),
1058
+ mediaDuration: element.getMediaDuration()
1059
+ };
1060
+ }
1061
+ visitAudioElement(element) {
1062
+ return {
1063
+ ...this.serializeElement(element),
1064
+ mediaDuration: element.getMediaDuration()
1065
+ };
1066
+ }
1067
+ visitImageElement(element) {
1068
+ var _a;
1069
+ return {
1070
+ ...this.serializeElement(element),
1071
+ frame: structuredClone(element.getFrame()),
1072
+ frameEffects: (_a = element.getFrameEffects()) == null ? void 0 : _a.map((frameEffect) => frameEffect.toJSON()),
1073
+ backgroundColor: element.getBackgroundColor(),
1074
+ objectFit: element.getObjectFit()
1075
+ };
1076
+ }
1077
+ visitTextElement(element) {
1078
+ var _a;
1079
+ return {
1080
+ ...this.serializeElement(element),
1081
+ textEffect: (_a = element.getTextEffect()) == null ? void 0 : _a.toJSON()
1082
+ };
1083
+ }
1084
+ visitCaptionElement(element) {
1085
+ return {
1086
+ ...this.serializeElement(element),
1087
+ t: structuredClone(element.getText())
1088
+ };
1089
+ }
1090
+ visitIconElement(element) {
1091
+ return {
1092
+ ...this.serializeElement(element)
1093
+ };
1094
+ }
1095
+ visitCircleElement(element) {
1096
+ return {
1097
+ ...this.serializeElement(element)
1098
+ };
1099
+ }
1100
+ visitRectElement(element) {
1101
+ return {
1102
+ ...this.serializeElement(element)
1103
+ };
1104
+ }
1105
+ }
1106
+ class ValidationError extends Error {
1107
+ constructor(message, errors, warnings = []) {
1108
+ super(message);
1109
+ this.errors = errors;
1110
+ this.warnings = warnings;
1111
+ this.name = "ValidationError";
1112
+ }
1113
+ }
1114
+ class ElementValidator {
1115
+ validateBasicProperties(element) {
1116
+ const errors = [];
1117
+ const warnings = [];
1118
+ if (!element.getId()) {
1119
+ errors.push("Element must have an ID");
1120
+ }
1121
+ if (!element.getType()) {
1122
+ errors.push("Element must have a type");
1123
+ }
1124
+ if (element.getStart() === void 0 || element.getStart() === null) {
1125
+ errors.push("Element must have a start time (s)");
1126
+ }
1127
+ if (element.getEnd() === void 0 || element.getEnd() === null) {
1128
+ errors.push("Element must have an end time (e)");
1129
+ }
1130
+ if (element.getStart() !== void 0 && element.getEnd() !== void 0) {
1131
+ if (element.getStart() < 0) {
1132
+ errors.push("Start time cannot be negative");
1133
+ }
1134
+ if (element.getEnd() <= element.getStart()) {
1135
+ errors.push("End time must be greater than start time");
1136
+ }
1137
+ }
1138
+ if (!element.getName()) {
1139
+ warnings.push("Element should have a name for better identification");
1140
+ }
1141
+ if (!element.getTrackId()) {
1142
+ warnings.push("Element should have a track Id");
1143
+ }
1144
+ return { errors, warnings };
1145
+ }
1146
+ validateTextElement(element) {
1147
+ const basicValidation = this.validateBasicProperties(element);
1148
+ const errors = [...basicValidation.errors];
1149
+ const warnings = [...basicValidation.warnings];
1150
+ const props = element.getProps();
1151
+ if (!(props == null ? void 0 : props.text)) {
1152
+ errors.push("Text element must have text content");
1153
+ }
1154
+ if ((props == null ? void 0 : props.fontSize) !== void 0 && props.fontSize <= 0) {
1155
+ errors.push("Font size must be greater than 0");
1156
+ }
1157
+ if ((props == null ? void 0 : props.fontWeight) !== void 0 && props.fontWeight < 0) {
1158
+ errors.push("Font weight cannot be negative");
1159
+ }
1160
+ return { errors, warnings };
1161
+ }
1162
+ validateVideoElement(element) {
1163
+ const basicValidation = this.validateBasicProperties(element);
1164
+ const errors = [...basicValidation.errors];
1165
+ const warnings = [...basicValidation.warnings];
1166
+ const props = element.getProps();
1167
+ if (!(props == null ? void 0 : props.src)) {
1168
+ errors.push("Video element must have a source URL");
1169
+ }
1170
+ if ((props == null ? void 0 : props.volume) !== void 0 && (props.volume < 0 || props.volume > 1)) {
1171
+ errors.push("Volume must be between 0 and 1");
1172
+ }
1173
+ if ((props == null ? void 0 : props.playbackRate) !== void 0 && props.playbackRate <= 0) {
1174
+ errors.push("Playback rate must be greater than 0");
1175
+ }
1176
+ return { errors, warnings };
1177
+ }
1178
+ validateAudioElement(element) {
1179
+ const basicValidation = this.validateBasicProperties(element);
1180
+ const errors = [...basicValidation.errors];
1181
+ const warnings = [...basicValidation.warnings];
1182
+ const props = element.getProps();
1183
+ if (!(props == null ? void 0 : props.src)) {
1184
+ errors.push("Audio element must have a source URL");
1185
+ }
1186
+ if ((props == null ? void 0 : props.volume) !== void 0 && (props.volume < 0 || props.volume > 1)) {
1187
+ errors.push("Volume must be between 0 and 1");
1188
+ }
1189
+ if ((props == null ? void 0 : props.playbackRate) !== void 0 && props.playbackRate <= 0) {
1190
+ errors.push("Playback rate must be greater than 0");
1191
+ }
1192
+ return { errors, warnings };
1193
+ }
1194
+ validateImageElement(element) {
1195
+ const basicValidation = this.validateBasicProperties(element);
1196
+ const errors = [...basicValidation.errors];
1197
+ const warnings = [...basicValidation.warnings];
1198
+ const props = element.getProps();
1199
+ if (!(props == null ? void 0 : props.src)) {
1200
+ errors.push("Image element must have a source URL");
1201
+ }
1202
+ return { errors, warnings };
1203
+ }
1204
+ validateCaptionElement(element) {
1205
+ const basicValidation = this.validateBasicProperties(element);
1206
+ const errors = [...basicValidation.errors];
1207
+ const warnings = [...basicValidation.warnings];
1208
+ const props = element.getProps();
1209
+ if (!(props == null ? void 0 : props.text)) {
1210
+ errors.push("Caption element must have text content");
1211
+ }
1212
+ return { errors, warnings };
1213
+ }
1214
+ validateIconElement(element) {
1215
+ const basicValidation = this.validateBasicProperties(element);
1216
+ const errors = [...basicValidation.errors];
1217
+ const warnings = [...basicValidation.warnings];
1218
+ const props = element.getProps();
1219
+ if (!(props == null ? void 0 : props.icon)) {
1220
+ errors.push("Icon element must have an icon name");
1221
+ }
1222
+ return { errors, warnings };
1223
+ }
1224
+ validateCircleElement(element) {
1225
+ const basicValidation = this.validateBasicProperties(element);
1226
+ const errors = [...basicValidation.errors];
1227
+ const warnings = [...basicValidation.warnings];
1228
+ const props = element.getProps();
1229
+ if ((props == null ? void 0 : props.radius) !== void 0 && props.radius <= 0) {
1230
+ errors.push("Circle radius must be greater than 0");
1231
+ }
1232
+ return { errors, warnings };
1233
+ }
1234
+ validateRectElement(element) {
1235
+ const basicValidation = this.validateBasicProperties(element);
1236
+ const errors = [...basicValidation.errors];
1237
+ const warnings = [...basicValidation.warnings];
1238
+ const props = element.getProps();
1239
+ if ((props == null ? void 0 : props.width) !== void 0 && props.width <= 0) {
1240
+ errors.push("Rectangle width must be greater than 0");
1241
+ }
1242
+ if ((props == null ? void 0 : props.height) !== void 0 && props.height <= 0) {
1243
+ errors.push("Rectangle height must be greater than 0");
1244
+ }
1245
+ return { errors, warnings };
1246
+ }
1247
+ visitVideoElement(element) {
1248
+ const validation = this.validateVideoElement(element);
1249
+ if (validation.errors.length > 0) {
1250
+ throw new ValidationError(
1251
+ `Video element validation failed: ${validation.errors.join(", ")}`,
1252
+ validation.errors,
1253
+ validation.warnings
1254
+ );
1255
+ }
1256
+ return true;
1257
+ }
1258
+ visitAudioElement(element) {
1259
+ const validation = this.validateAudioElement(element);
1260
+ if (validation.errors.length > 0) {
1261
+ throw new ValidationError(
1262
+ `Audio element validation failed: ${validation.errors.join(", ")}`,
1263
+ validation.errors,
1264
+ validation.warnings
1265
+ );
1266
+ }
1267
+ return true;
1268
+ }
1269
+ visitImageElement(element) {
1270
+ const validation = this.validateImageElement(element);
1271
+ if (validation.errors.length > 0) {
1272
+ throw new ValidationError(
1273
+ `Image element validation failed: ${validation.errors.join(", ")}`,
1274
+ validation.errors,
1275
+ validation.warnings
1276
+ );
1277
+ }
1278
+ return true;
1279
+ }
1280
+ visitTextElement(element) {
1281
+ const validation = this.validateTextElement(element);
1282
+ if (validation.errors.length > 0) {
1283
+ throw new ValidationError(
1284
+ `Text element validation failed: ${validation.errors.join(", ")}`,
1285
+ validation.errors,
1286
+ validation.warnings
1287
+ );
1288
+ }
1289
+ return true;
1290
+ }
1291
+ visitCaptionElement(element) {
1292
+ const validation = this.validateCaptionElement(element);
1293
+ if (validation.errors.length > 0) {
1294
+ throw new ValidationError(
1295
+ `Caption element validation failed: ${validation.errors.join(", ")}`,
1296
+ validation.errors,
1297
+ validation.warnings
1298
+ );
1299
+ }
1300
+ return true;
1301
+ }
1302
+ visitIconElement(element) {
1303
+ const validation = this.validateIconElement(element);
1304
+ if (validation.errors.length > 0) {
1305
+ throw new ValidationError(
1306
+ `Icon element validation failed: ${validation.errors.join(", ")}`,
1307
+ validation.errors,
1308
+ validation.warnings
1309
+ );
1310
+ }
1311
+ return true;
1312
+ }
1313
+ visitCircleElement(element) {
1314
+ const validation = this.validateCircleElement(element);
1315
+ if (validation.errors.length > 0) {
1316
+ throw new ValidationError(
1317
+ `Circle element validation failed: ${validation.errors.join(", ")}`,
1318
+ validation.errors,
1319
+ validation.warnings
1320
+ );
1321
+ }
1322
+ return true;
1323
+ }
1324
+ visitRectElement(element) {
1325
+ const validation = this.validateRectElement(element);
1326
+ if (validation.errors.length > 0) {
1327
+ throw new ValidationError(
1328
+ `Rectangle element validation failed: ${validation.errors.join(", ")}`,
1329
+ validation.errors,
1330
+ validation.warnings
1331
+ );
1332
+ }
1333
+ return true;
1334
+ }
1335
+ }
1336
+ class TrackFriend {
1337
+ constructor(track) {
1338
+ this.track = track;
1339
+ }
1340
+ /**
1341
+ * Add an element to the track with validation
1342
+ * @param element The element to add
1343
+ * @param skipValidation If true, skips validation (use with caution)
1344
+ * @returns true if element was added successfully, throws ValidationError if validation fails
1345
+ */
1346
+ addElement(element, skipValidation = false) {
1347
+ return this.track.addElementViaFriend(element, skipValidation);
1348
+ }
1349
+ /**
1350
+ * Remove an element from the track
1351
+ * @param element The element to remove
1352
+ */
1353
+ removeElement(element) {
1354
+ this.track.removeElementViaFriend(element);
1355
+ }
1356
+ /**
1357
+ * Update an element in the track with validation
1358
+ * @param element The element to update
1359
+ * @returns true if element was updated successfully, throws ValidationError if validation fails
1360
+ */
1361
+ updateElement(element) {
1362
+ return this.track.updateElementViaFriend(element);
1363
+ }
1364
+ /**
1365
+ * Get the track instance (for advanced operations)
1366
+ * @returns The track instance
1367
+ */
1368
+ getTrack() {
1369
+ return this.track;
1370
+ }
1371
+ }
1372
+ class Track {
1373
+ constructor(name, id) {
1374
+ __publicField(this, "id");
1375
+ __publicField(this, "name");
1376
+ __publicField(this, "type");
1377
+ __publicField(this, "elements");
1378
+ __publicField(this, "validator");
1379
+ this.name = name;
1380
+ this.id = id ?? `t-${generateShortUuid}`;
1381
+ this.type = "element";
1382
+ this.elements = [];
1383
+ this.validator = new ElementValidator();
1384
+ }
1385
+ /**
1386
+ * Create a friend instance for explicit access to protected methods
1387
+ * This implements the Friend Class Pattern
1388
+ * @returns TrackFriend instance
1389
+ */
1390
+ createFriend() {
1391
+ return new TrackFriend(this);
1392
+ }
1393
+ /**
1394
+ * Friend method to add element (called by TrackFriend)
1395
+ * @param element The element to add
1396
+ * @param skipValidation If true, skips validation
1397
+ * @returns true if element was added successfully
1398
+ */
1399
+ addElementViaFriend(element, skipValidation = false) {
1400
+ return this.addElement(element, skipValidation);
1401
+ }
1402
+ /**
1403
+ * Friend method to remove element (called by TrackFriend)
1404
+ * @param element The element to remove
1405
+ */
1406
+ removeElementViaFriend(element) {
1407
+ this.removeElement(element);
1408
+ }
1409
+ /**
1410
+ * Friend method to update element (called by TrackFriend)
1411
+ * @param element The element to update
1412
+ * @returns true if element was updated successfully
1413
+ */
1414
+ updateElementViaFriend(element) {
1415
+ return this.updateElement(element);
1416
+ }
1417
+ getId() {
1418
+ return this.id;
1419
+ }
1420
+ getName() {
1421
+ return this.name;
1422
+ }
1423
+ getType() {
1424
+ return this.type;
1425
+ }
1426
+ getElements() {
1427
+ return [...this.elements];
1428
+ }
1429
+ /**
1430
+ * Validates an element
1431
+ * @param element The element to validate
1432
+ * @returns true if valid, throws ValidationError if invalid
1433
+ */
1434
+ validateElement(element) {
1435
+ return element.accept(this.validator);
1436
+ }
1437
+ getTrackDuration() {
1438
+ var _a;
1439
+ return ((_a = this.elements) == null ? void 0 : _a.length) ? this.elements[this.elements.length - 1].getEnd() : 0;
1440
+ }
1441
+ /**
1442
+ * Adds an element to the track with validation
1443
+ * @param element The element to add
1444
+ * @param skipValidation If true, skips validation (use with caution)
1445
+ * @returns true if element was added successfully, throws ValidationError if validation fails
1446
+ */
1447
+ addElement(element, skipValidation = false) {
1448
+ element.setTrackId(this.id);
1449
+ if (skipValidation) {
1450
+ this.elements.push(element);
1451
+ return true;
1452
+ }
1453
+ try {
1454
+ const isValid = this.validateElement(element);
1455
+ if (isValid) {
1456
+ this.elements.push(element);
1457
+ return true;
1458
+ }
1459
+ } catch (error) {
1460
+ if (error instanceof ValidationError) {
1461
+ throw error;
1462
+ }
1463
+ throw error;
1464
+ }
1465
+ return false;
1466
+ }
1467
+ removeElement(element) {
1468
+ const index = this.elements.findIndex((e) => e.getId() === element.getId());
1469
+ if (index !== -1) {
1470
+ this.elements.splice(index, 1);
1471
+ }
1472
+ }
1473
+ /**
1474
+ * Updates an element in the track with validation
1475
+ * @param element The element to update
1476
+ * @returns true if element was updated successfully, throws ValidationError if validation fails
1477
+ */
1478
+ updateElement(element) {
1479
+ try {
1480
+ const isValid = this.validateElement(element);
1481
+ if (isValid) {
1482
+ const index = this.elements.findIndex(
1483
+ (e) => e.getId() === element.getId()
1484
+ );
1485
+ if (index !== -1) {
1486
+ this.elements[index] = element;
1487
+ return true;
1488
+ }
1489
+ }
1490
+ } catch (error) {
1491
+ if (error instanceof ValidationError) {
1492
+ throw error;
1493
+ }
1494
+ throw error;
1495
+ }
1496
+ return false;
1497
+ }
1498
+ getElementById(id) {
1499
+ const element = this.elements.find((e) => e.getId() === id);
1500
+ if (!element) return void 0;
1501
+ return element;
1502
+ }
1503
+ /**
1504
+ * Validates all elements in the track and returns combined result and per-element status
1505
+ * @returns Object with overall isValid and array of per-element validation results
1506
+ */
1507
+ validateAllElements() {
1508
+ let validResult = true;
1509
+ const results = this.elements.map((element) => {
1510
+ try {
1511
+ const isValid = this.validateElement(element);
1512
+ if (!isValid) {
1513
+ validResult = false;
1514
+ }
1515
+ return { element, isValid };
1516
+ } catch (error) {
1517
+ if (error instanceof ValidationError) {
1518
+ validResult = false;
1519
+ return {
1520
+ element,
1521
+ isValid: false,
1522
+ errors: error.errors,
1523
+ warnings: error.warnings
1524
+ };
1525
+ }
1526
+ return {
1527
+ element,
1528
+ isValid: false,
1529
+ errors: [error instanceof Error ? error.message : "Unknown error"]
1530
+ };
1531
+ }
1532
+ });
1533
+ return { isValid: validResult, results };
1534
+ }
1535
+ serialize() {
1536
+ const serializer = new ElementSerializer();
1537
+ return {
1538
+ id: this.id,
1539
+ name: this.name,
1540
+ type: this.type,
1541
+ elements: this.elements.map(
1542
+ (element) => element.accept(serializer)
1543
+ )
1544
+ };
1545
+ }
1546
+ static fromJSON(json) {
1547
+ const track = new Track(json.name, json.id);
1548
+ track.type = json.type;
1549
+ track.elements = (json.elements || []).map(ElementDeserializer.fromJSON);
1550
+ return track;
1551
+ }
1552
+ }
1553
+ const _TimelineContextStore = class _TimelineContextStore {
1554
+ constructor() {
1555
+ __publicField(this, "storeMap");
1556
+ this.storeMap = /* @__PURE__ */ new Map();
1557
+ }
1558
+ static getInstance() {
1559
+ if (!_TimelineContextStore.instance) {
1560
+ _TimelineContextStore.instance = new _TimelineContextStore();
1561
+ }
1562
+ return _TimelineContextStore.instance;
1563
+ }
1564
+ initializeContext(contextId) {
1565
+ if (!this.storeMap.has(contextId)) {
1566
+ this.storeMap.set(contextId, {
1567
+ tracks: [],
1568
+ version: 0,
1569
+ elementMap: {},
1570
+ trackMap: {},
1571
+ captionProps: {}
1572
+ });
1573
+ }
1574
+ }
1575
+ getTimelineData(contextId) {
1576
+ const timelineStore = this.storeMap.get(contextId);
1577
+ return timelineStore ? {
1578
+ tracks: timelineStore.tracks,
1579
+ version: timelineStore.version
1580
+ } : null;
1581
+ }
1582
+ setTimelineData(contextId, timelineData) {
1583
+ this.ensureContext(contextId);
1584
+ this.storeMap.get(contextId).tracks = timelineData.tracks;
1585
+ this.storeMap.get(contextId).version = timelineData.version;
1586
+ return timelineData;
1587
+ }
1588
+ getElementMap(contextId) {
1589
+ this.ensureContext(contextId);
1590
+ return this.storeMap.get(contextId).elementMap;
1591
+ }
1592
+ setElementMap(contextId, elementMap) {
1593
+ this.ensureContext(contextId);
1594
+ this.storeMap.get(contextId).elementMap = elementMap;
1595
+ }
1596
+ getTrackMap(contextId) {
1597
+ this.ensureContext(contextId);
1598
+ return this.storeMap.get(contextId).trackMap;
1599
+ }
1600
+ setTrackMap(contextId, trackMap) {
1601
+ this.ensureContext(contextId);
1602
+ this.storeMap.get(contextId).trackMap = trackMap;
1603
+ }
1604
+ getCaptionProps(contextId) {
1605
+ this.ensureContext(contextId);
1606
+ return this.storeMap.get(contextId).captionProps;
1607
+ }
1608
+ setCaptionProps(contextId, captionProps) {
1609
+ this.ensureContext(contextId);
1610
+ this.storeMap.get(contextId).captionProps = captionProps;
1611
+ }
1612
+ clearContext(contextId) {
1613
+ this.storeMap.delete(contextId);
1614
+ }
1615
+ ensureContext(contextId) {
1616
+ if (!this.storeMap.has(contextId)) {
1617
+ this.initializeContext(contextId);
1618
+ }
1619
+ }
1620
+ };
1621
+ __publicField(_TimelineContextStore, "instance");
1622
+ let TimelineContextStore = _TimelineContextStore;
1623
+ const timelineContextStore = TimelineContextStore.getInstance();
1624
+ class ElementAdder {
1625
+ constructor(track) {
1626
+ __publicField(this, "track");
1627
+ __publicField(this, "trackFriend");
1628
+ this.track = track;
1629
+ this.trackFriend = track.createFriend();
1630
+ }
1631
+ async visitVideoElement(element) {
1632
+ await element.updateVideoMeta();
1633
+ const elements = this.track.getElements();
1634
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1635
+ if (isNaN(element.getStart())) {
1636
+ element.setStart(lastEndtime);
1637
+ }
1638
+ if (isNaN(element.getEnd())) {
1639
+ element.setEnd(element.getStart() + element.getMediaDuration());
1640
+ }
1641
+ return this.trackFriend.addElement(element);
1642
+ }
1643
+ async visitAudioElement(element) {
1644
+ await element.updateAudioMeta();
1645
+ const elements = this.track.getElements();
1646
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1647
+ if (isNaN(element.getStart())) {
1648
+ element.setStart(lastEndtime);
1649
+ }
1650
+ if (isNaN(element.getEnd())) {
1651
+ element.setEnd(element.getStart() + element.getMediaDuration());
1652
+ }
1653
+ return this.trackFriend.addElement(element);
1654
+ }
1655
+ async visitImageElement(element) {
1656
+ await element.updateImageMeta();
1657
+ const elements = this.track.getElements();
1658
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1659
+ if (isNaN(element.getStart())) {
1660
+ element.setStart(lastEndtime);
1661
+ }
1662
+ if (isNaN(element.getEnd())) {
1663
+ element.setEnd(element.getStart() + 1);
1664
+ }
1665
+ return this.trackFriend.addElement(element);
1666
+ }
1667
+ async visitTextElement(element) {
1668
+ const elements = this.track.getElements();
1669
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1670
+ if (isNaN(element.getStart())) {
1671
+ element.setStart(lastEndtime);
1672
+ }
1673
+ if (isNaN(element.getEnd())) {
1674
+ element.setEnd(element.getStart() + 1);
1675
+ }
1676
+ return this.trackFriend.addElement(element);
1677
+ }
1678
+ async visitCaptionElement(element) {
1679
+ const elements = this.track.getElements();
1680
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1681
+ if (isNaN(element.getStart())) {
1682
+ element.setStart(lastEndtime);
1683
+ }
1684
+ if (isNaN(element.getEnd())) {
1685
+ element.setEnd(element.getStart() + 1);
1686
+ }
1687
+ return this.trackFriend.addElement(element);
1688
+ }
1689
+ async visitIconElement(element) {
1690
+ const elements = this.track.getElements();
1691
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1692
+ if (isNaN(element.getStart())) {
1693
+ element.setStart(lastEndtime);
1694
+ }
1695
+ if (isNaN(element.getEnd())) {
1696
+ element.setEnd(element.getStart() + 1);
1697
+ }
1698
+ return this.trackFriend.addElement(element);
1699
+ }
1700
+ async visitCircleElement(element) {
1701
+ const elements = this.track.getElements();
1702
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1703
+ if (isNaN(element.getStart())) {
1704
+ element.setStart(lastEndtime);
1705
+ }
1706
+ if (isNaN(element.getEnd())) {
1707
+ element.setEnd(element.getStart() + 1);
1708
+ }
1709
+ return this.trackFriend.addElement(element);
1710
+ }
1711
+ async visitRectElement(element) {
1712
+ const elements = this.track.getElements();
1713
+ const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
1714
+ if (isNaN(element.getStart())) {
1715
+ element.setStart(lastEndtime);
1716
+ }
1717
+ if (isNaN(element.getEnd())) {
1718
+ element.setEnd(element.getStart() + 1);
1719
+ }
1720
+ return this.trackFriend.addElement(element);
1721
+ }
1722
+ }
1723
+ class ElementRemover {
1724
+ constructor(track) {
1725
+ __publicField(this, "trackFriend");
1726
+ this.trackFriend = track.createFriend();
1727
+ }
1728
+ visitVideoElement(element) {
1729
+ this.trackFriend.removeElement(element);
1730
+ return true;
1731
+ }
1732
+ visitAudioElement(element) {
1733
+ this.trackFriend.removeElement(element);
1734
+ return true;
1735
+ }
1736
+ visitImageElement(element) {
1737
+ this.trackFriend.removeElement(element);
1738
+ return true;
1739
+ }
1740
+ visitTextElement(element) {
1741
+ this.trackFriend.removeElement(element);
1742
+ return true;
1743
+ }
1744
+ visitCaptionElement(element) {
1745
+ this.trackFriend.removeElement(element);
1746
+ return true;
1747
+ }
1748
+ visitIconElement(element) {
1749
+ this.trackFriend.removeElement(element);
1750
+ return true;
1751
+ }
1752
+ visitCircleElement(element) {
1753
+ this.trackFriend.removeElement(element);
1754
+ return true;
1755
+ }
1756
+ visitRectElement(element) {
1757
+ this.trackFriend.removeElement(element);
1758
+ return true;
1759
+ }
1760
+ }
1761
+ class ElementUpdater {
1762
+ constructor(track) {
1763
+ __publicField(this, "trackFriend");
1764
+ this.trackFriend = track.createFriend();
1765
+ }
1766
+ visitVideoElement(element) {
1767
+ return this.trackFriend.updateElement(element);
1768
+ }
1769
+ visitAudioElement(element) {
1770
+ return this.trackFriend.updateElement(element);
1771
+ }
1772
+ visitImageElement(element) {
1773
+ return this.trackFriend.updateElement(element);
1774
+ }
1775
+ visitTextElement(element) {
1776
+ return this.trackFriend.updateElement(element);
1777
+ }
1778
+ visitCaptionElement(element) {
1779
+ return this.trackFriend.updateElement(element);
1780
+ }
1781
+ visitIconElement(element) {
1782
+ return this.trackFriend.updateElement(element);
1783
+ }
1784
+ visitCircleElement(element) {
1785
+ return this.trackFriend.updateElement(element);
1786
+ }
1787
+ visitRectElement(element) {
1788
+ return this.trackFriend.updateElement(element);
1789
+ }
1790
+ }
1791
+ class ElementCloner {
1792
+ cloneElementProperties(srcElement, destElement) {
1793
+ return destElement.setName(srcElement.getName()).setType(srcElement.getType()).setStart(srcElement.getStart()).setEnd(srcElement.getEnd()).setProps(srcElement.getProps()).setAnimation(srcElement.getAnimation());
1794
+ }
1795
+ visitVideoElement(element) {
1796
+ const props = element.getProps();
1797
+ const clonedElement = new VideoElement(props.src, element.getParentSize());
1798
+ this.cloneElementProperties(element, clonedElement);
1799
+ clonedElement.setParentSize(element.getParentSize()).setMediaDuration(element.getMediaDuration()).setFrame(element.getFrame()).setFrameEffects(element.getFrameEffects() ?? []).setBackgroundColor(element.getBackgroundColor()).setObjectFit(element.getObjectFit());
1800
+ return clonedElement;
1801
+ }
1802
+ visitAudioElement(element) {
1803
+ const clonedElement = new AudioElement(element.getProps().src);
1804
+ this.cloneElementProperties(element, clonedElement);
1805
+ clonedElement.setMediaDuration(element.getMediaDuration());
1806
+ return clonedElement;
1807
+ }
1808
+ visitImageElement(element) {
1809
+ const clonedElement = new ImageElement(
1810
+ element.getProps().src,
1811
+ element.getParentSize()
1812
+ );
1813
+ this.cloneElementProperties(element, clonedElement);
1814
+ clonedElement.setParentSize(element.getParentSize()).setFrame(element.getFrame()).setFrameEffects(element.getFrameEffects()).setBackgroundColor(element.getBackgroundColor()).setObjectFit(element.getObjectFit());
1815
+ return clonedElement;
1816
+ }
1817
+ visitTextElement(element) {
1818
+ const clonedElement = new TextElement(element.getProps().text);
1819
+ this.cloneElementProperties(element, clonedElement);
1820
+ clonedElement.setTextEffect(element.getTextEffect());
1821
+ return clonedElement;
1822
+ }
1823
+ visitCaptionElement(element) {
1824
+ const clonedElement = new CaptionElement(
1825
+ element.getProps().text,
1826
+ element.getStart(),
1827
+ element.getEnd()
1828
+ );
1829
+ this.cloneElementProperties(element, clonedElement);
1830
+ return clonedElement;
1831
+ }
1832
+ visitRectElement(element) {
1833
+ const clonedElement = new RectElement(
1834
+ element.getProps().fill,
1835
+ element.getProps().size
1836
+ );
1837
+ this.cloneElementProperties(element, clonedElement);
1838
+ return clonedElement;
1839
+ }
1840
+ visitCircleElement(element) {
1841
+ const clonedElement = new CircleElement(
1842
+ element.getProps().fill,
1843
+ element.getProps().radius
1844
+ );
1845
+ this.cloneElementProperties(element, clonedElement);
1846
+ return clonedElement;
1847
+ }
1848
+ visitIconElement(element) {
1849
+ const clonedElement = new IconElement(
1850
+ element.getProps().src,
1851
+ element.getProps().size
1852
+ );
1853
+ this.cloneElementProperties(element, clonedElement);
1854
+ return clonedElement;
1855
+ }
1856
+ }
1857
+ class ElementSplitter {
1858
+ constructor(splitTime) {
1859
+ __publicField(this, "splitTime");
1860
+ __publicField(this, "elementCloner");
1861
+ this.splitTime = splitTime;
1862
+ this.elementCloner = new ElementCloner();
1863
+ }
1864
+ visitVideoElement(element) {
1865
+ if (!canSplitElement(element, this.splitTime)) {
1866
+ return { firstElement: null, secondElement: null, success: false };
1867
+ }
1868
+ const firstElement = this.elementCloner.visitVideoElement(
1869
+ element
1870
+ );
1871
+ const secondElement = this.elementCloner.visitVideoElement(
1872
+ element
1873
+ );
1874
+ const props = element.getProps();
1875
+ const secondStartAt = (props.time ?? 0) + (this.splitTime - element.getStart()) * (props.playbackRate ?? 1);
1876
+ firstElement.setEnd(this.splitTime);
1877
+ secondElement.setStart(this.splitTime).setStartAt(secondStartAt);
1878
+ return { firstElement, secondElement, success: true };
1879
+ }
1880
+ visitAudioElement(element) {
1881
+ if (!canSplitElement(element, this.splitTime)) {
1882
+ return { firstElement: null, secondElement: null, success: false };
1883
+ }
1884
+ const firstElement = this.elementCloner.visitAudioElement(
1885
+ element
1886
+ );
1887
+ const secondElement = this.elementCloner.visitAudioElement(
1888
+ element
1889
+ );
1890
+ const props = element.getProps();
1891
+ const secondStartAt = (props.time ?? 0) + (this.splitTime - element.getStart()) * (props.playbackRate ?? 1);
1892
+ firstElement.setEnd(this.splitTime);
1893
+ secondElement.setStart(this.splitTime).setStartAt(secondStartAt);
1894
+ return { firstElement, secondElement, success: true };
1895
+ }
1896
+ visitImageElement(element) {
1897
+ if (!canSplitElement(element, this.splitTime)) {
1898
+ return { firstElement: null, secondElement: null, success: false };
1899
+ }
1900
+ const firstElement = this.elementCloner.visitImageElement(
1901
+ element
1902
+ );
1903
+ const secondElement = this.elementCloner.visitImageElement(
1904
+ element
1905
+ );
1906
+ firstElement.setEnd(this.splitTime);
1907
+ secondElement.setStart(this.splitTime);
1908
+ return { firstElement, secondElement, success: true };
1909
+ }
1910
+ visitTextElement(element) {
1911
+ if (!canSplitElement(element, this.splitTime)) {
1912
+ return { firstElement: null, secondElement: null, success: false };
1913
+ }
1914
+ const originalText = element.getText() || "";
1915
+ const originalTextArray = originalText.split(" ");
1916
+ const percentage = (this.splitTime - element.getStart()) / element.getDuration();
1917
+ const firstElement = this.elementCloner.visitTextElement(
1918
+ element
1919
+ );
1920
+ firstElement.setText(
1921
+ originalTextArray.slice(0, Math.floor(originalTextArray.length * percentage)).join(" ")
1922
+ );
1923
+ firstElement.setEnd(this.splitTime);
1924
+ const secondElement = this.elementCloner.visitTextElement(
1925
+ element
1926
+ );
1927
+ secondElement.setText(
1928
+ originalTextArray.slice(
1929
+ Math.floor(originalTextArray.length * percentage),
1930
+ originalTextArray.length
1931
+ ).join(" ")
1932
+ );
1933
+ secondElement.setStart(this.splitTime);
1934
+ return { firstElement, secondElement, success: true };
1935
+ }
1936
+ visitCaptionElement(element) {
1937
+ if (!canSplitElement(element, this.splitTime)) {
1938
+ return { firstElement: null, secondElement: null, success: false };
1939
+ }
1940
+ const originalText = element.getText() || "";
1941
+ const originalTextArray = originalText.split(" ");
1942
+ const percentage = (this.splitTime - element.getStart()) / element.getDuration();
1943
+ const firstElement = this.elementCloner.visitCaptionElement(
1944
+ element
1945
+ );
1946
+ firstElement.setText(
1947
+ originalTextArray.slice(0, Math.floor(originalTextArray.length * percentage)).join(" ")
1948
+ );
1949
+ firstElement.setEnd(this.splitTime);
1950
+ const secondElement = this.elementCloner.visitCaptionElement(
1951
+ element
1952
+ );
1953
+ secondElement.setText(
1954
+ originalTextArray.slice(
1955
+ Math.floor(originalTextArray.length * percentage),
1956
+ originalTextArray.length
1957
+ ).join(" ")
1958
+ );
1959
+ secondElement.setStart(this.splitTime);
1960
+ return { firstElement, secondElement, success: true };
1961
+ }
1962
+ visitRectElement(element) {
1963
+ if (!canSplitElement(element, this.splitTime)) {
1964
+ return { firstElement: null, secondElement: null, success: false };
1965
+ }
1966
+ const firstElement = this.elementCloner.visitRectElement(
1967
+ element
1968
+ );
1969
+ const secondElement = this.elementCloner.visitRectElement(
1970
+ element
1971
+ );
1972
+ firstElement.setEnd(this.splitTime);
1973
+ secondElement.setStart(this.splitTime);
1974
+ return { firstElement, secondElement, success: true };
1975
+ }
1976
+ visitCircleElement(element) {
1977
+ if (!canSplitElement(element, this.splitTime)) {
1978
+ return { firstElement: null, secondElement: null, success: false };
1979
+ }
1980
+ const firstElement = this.elementCloner.visitCircleElement(element);
1981
+ const secondElement = this.elementCloner.visitCircleElement(element);
1982
+ firstElement.setEnd(this.splitTime);
1983
+ secondElement.setStart(this.splitTime);
1984
+ return { firstElement, secondElement, success: true };
1985
+ }
1986
+ visitIconElement(element) {
1987
+ if (!canSplitElement(element, this.splitTime)) {
1988
+ return { firstElement: null, secondElement: null, success: false };
1989
+ }
1990
+ const firstElement = this.elementCloner.visitIconElement(element);
1991
+ const secondElement = this.elementCloner.visitIconElement(element);
1992
+ firstElement.setEnd(this.splitTime);
1993
+ secondElement.setStart(this.splitTime);
1994
+ return { firstElement, secondElement, success: true };
1995
+ }
1996
+ }
1997
+ class TimelineEditor {
1998
+ constructor(context) {
1999
+ __publicField(this, "context");
2000
+ this.context = context;
2001
+ timelineContextStore.initializeContext(this.context.contextId);
2002
+ }
2003
+ getContext() {
2004
+ return this.context;
2005
+ }
2006
+ pauseVideo() {
2007
+ var _a;
2008
+ if ((_a = this.context) == null ? void 0 : _a.setTimelineAction) {
2009
+ this.context.setTimelineAction(
2010
+ TIMELINE_ACTION.SET_PLAYER_STATE,
2011
+ PLAYER_STATE.PAUSED
2012
+ );
2013
+ }
2014
+ }
2015
+ getTimelineData() {
2016
+ const contextId = this.context.contextId;
2017
+ return timelineContextStore.getTimelineData(contextId);
2018
+ }
2019
+ getLatestVersion() {
2020
+ const contextId = this.context.contextId;
2021
+ const timelineData = timelineContextStore.getTimelineData(contextId);
2022
+ return (timelineData == null ? void 0 : timelineData.version) || 0;
2023
+ }
2024
+ setTimelineData(tracks, version) {
2025
+ const prevTimelineData = this.getTimelineData();
2026
+ const updatedVersion = version ?? ((prevTimelineData == null ? void 0 : prevTimelineData.version) || 0) + 1;
2027
+ const updatedTimelineData = {
2028
+ tracks,
2029
+ version: updatedVersion
2030
+ };
2031
+ timelineContextStore.setTimelineData(
2032
+ this.context.contextId,
2033
+ updatedTimelineData
2034
+ );
2035
+ this.updateHistory(updatedTimelineData);
2036
+ this.context.updateChangeLog();
2037
+ return updatedTimelineData;
2038
+ }
2039
+ addTrack(name) {
2040
+ const prevTimelineData = this.getTimelineData();
2041
+ const id = `t-${generateShortUuid()}`;
2042
+ const track = new Track(name, id);
2043
+ const updatedTimelines = [...(prevTimelineData == null ? void 0 : prevTimelineData.tracks) || [], track];
2044
+ this.setTimelineData(updatedTimelines);
2045
+ return track;
2046
+ }
2047
+ getTrackById(id) {
2048
+ const prevTimelineData = this.getTimelineData();
2049
+ const track = prevTimelineData == null ? void 0 : prevTimelineData.tracks.find((t) => t.getId() === id);
2050
+ return track;
2051
+ }
2052
+ getTrackByName(name) {
2053
+ const prevTimelineData = this.getTimelineData();
2054
+ const track = prevTimelineData == null ? void 0 : prevTimelineData.tracks.find((t) => t.getName() === name);
2055
+ return track;
2056
+ }
2057
+ removeTrackById(id) {
2058
+ var _a;
2059
+ const tracks = ((_a = this.getTimelineData()) == null ? void 0 : _a.tracks) || [];
2060
+ const updatedTracks = tracks.filter((t) => t.getId() !== id);
2061
+ this.setTimelineData(updatedTracks);
2062
+ }
2063
+ removeTrack(track) {
2064
+ var _a;
2065
+ const tracks = ((_a = this.getTimelineData()) == null ? void 0 : _a.tracks) || [];
2066
+ const updatedTracks = tracks.filter((t) => t.getId() !== track.getId());
2067
+ this.setTimelineData(updatedTracks);
2068
+ }
2069
+ /**
2070
+ * Refresh the timeline data
2071
+ */
2072
+ refresh() {
2073
+ const currentData = this.getTimelineData();
2074
+ if (currentData) {
2075
+ this.setTimelineData(currentData.tracks);
2076
+ }
2077
+ }
2078
+ /**
2079
+ * Add an element to a specific track using the visitor pattern
2080
+ * @param track The track to add the element to
2081
+ * @param element The element to add
2082
+ * @returns Promise<boolean> true if element was added successfully
2083
+ */
2084
+ async addElementToTrack(track, element) {
2085
+ if (!track) {
2086
+ return false;
2087
+ }
2088
+ try {
2089
+ const elementAdder = new ElementAdder(track);
2090
+ const result = await element.accept(elementAdder);
2091
+ if (result) {
2092
+ const currentData = this.getTimelineData();
2093
+ if (currentData) {
2094
+ this.setTimelineData(currentData.tracks);
2095
+ }
2096
+ }
2097
+ return result;
2098
+ } catch (error) {
2099
+ return false;
2100
+ }
2101
+ }
2102
+ /**
2103
+ * Remove an element from a specific track using the visitor pattern
2104
+ * @param element The element to remove
2105
+ * @returns boolean true if element was removed successfully
2106
+ */
2107
+ removeElement(element) {
2108
+ const track = this.getTrackById(element.getTrackId());
2109
+ if (!track) {
2110
+ return false;
2111
+ }
2112
+ try {
2113
+ const elementRemover = new ElementRemover(track);
2114
+ const result = element.accept(elementRemover);
2115
+ if (result) {
2116
+ const currentData = this.getTimelineData();
2117
+ if (currentData) {
2118
+ this.setTimelineData(currentData.tracks);
2119
+ }
2120
+ }
2121
+ return result;
2122
+ } catch (error) {
2123
+ return false;
2124
+ }
2125
+ }
2126
+ /**
2127
+ * Update an element in a specific track using the visitor pattern
2128
+ * @param element The updated element
2129
+ * @returns boolean true if element was updated successfully
2130
+ */
2131
+ updateElement(element) {
2132
+ const track = this.getTrackById(element.getTrackId());
2133
+ if (!track) {
2134
+ return false;
2135
+ }
2136
+ try {
2137
+ const elementUpdater = new ElementUpdater(track);
2138
+ const result = element.accept(elementUpdater);
2139
+ if (result) {
2140
+ const currentData = this.getTimelineData();
2141
+ if (currentData) {
2142
+ this.setTimelineData(currentData.tracks);
2143
+ }
2144
+ }
2145
+ return result;
2146
+ } catch (error) {
2147
+ return false;
2148
+ }
2149
+ }
2150
+ /**
2151
+ * Split an element at a specific time point using the visitor pattern
2152
+ * @param element The element to split
2153
+ * @param splitTime The time point to split at
2154
+ * @returns SplitResult with first element, second element, and success status
2155
+ */
2156
+ async splitElement(element, splitTime) {
2157
+ const track = this.getTrackById(element.getTrackId());
2158
+ if (!track) {
2159
+ return { firstElement: element, secondElement: null, success: false };
2160
+ }
2161
+ try {
2162
+ const elementSplitter = new ElementSplitter(splitTime);
2163
+ const result = element.accept(elementSplitter);
2164
+ if (result.success) {
2165
+ const elementRemover = new ElementRemover(track);
2166
+ element.accept(elementRemover);
2167
+ const elementAdder = new ElementAdder(track);
2168
+ result.firstElement.accept(elementAdder);
2169
+ result.secondElement.accept(elementAdder);
2170
+ const currentData = this.getTimelineData();
2171
+ if (currentData) {
2172
+ this.setTimelineData(currentData.tracks);
2173
+ }
2174
+ }
2175
+ return result;
2176
+ } catch (error) {
2177
+ return { firstElement: element, secondElement: null, success: false };
2178
+ }
2179
+ }
2180
+ /**
2181
+ * Clone an element using the visitor pattern
2182
+ * @param element The element to clone
2183
+ * @returns TrackElement | null - the cloned element or null if cloning failed
2184
+ */
2185
+ cloneElement(element) {
2186
+ try {
2187
+ const elementCloner = new ElementCloner();
2188
+ return element.accept(elementCloner);
2189
+ } catch (error) {
2190
+ return null;
2191
+ }
2192
+ }
2193
+ reorderTracks(tracks) {
2194
+ this.setTimelineData(tracks);
2195
+ }
2196
+ updateHistory(timelineTrackData) {
2197
+ const tracks = timelineTrackData.tracks.map((t) => t.serialize());
2198
+ this.context.setTotalDuration(getTotalDuration(tracks));
2199
+ const version = timelineTrackData.version;
2200
+ this.context.setPresent({
2201
+ tracks,
2202
+ version
2203
+ });
2204
+ }
2205
+ /**
2206
+ * Trigger undo operation and update timeline data
2207
+ */
2208
+ undo() {
2209
+ var _a;
2210
+ const result = this.context.handleUndo();
2211
+ if (result && result.tracks) {
2212
+ const tracks = result.tracks.map((t) => Track.fromJSON(t));
2213
+ timelineContextStore.setTimelineData(this.context.contextId, {
2214
+ tracks,
2215
+ version: result.version
2216
+ });
2217
+ this.context.setTotalDuration(getTotalDuration(result.tracks));
2218
+ this.context.updateChangeLog();
2219
+ if ((_a = this.context) == null ? void 0 : _a.setTimelineAction) {
2220
+ this.context.setTimelineAction(TIMELINE_ACTION.UPDATE_PLAYER_DATA, {
2221
+ tracks: result.tracks,
2222
+ version: result.version
2223
+ });
2224
+ }
2225
+ }
2226
+ }
2227
+ /**
2228
+ * Trigger redo operation and update timeline data
2229
+ */
2230
+ redo() {
2231
+ var _a;
2232
+ const result = this.context.handleRedo();
2233
+ if (result && result.tracks) {
2234
+ const tracks = result.tracks.map((t) => Track.fromJSON(t));
2235
+ timelineContextStore.setTimelineData(this.context.contextId, {
2236
+ tracks,
2237
+ version: result.version
2238
+ });
2239
+ this.context.setTotalDuration(getTotalDuration(result.tracks));
2240
+ this.context.updateChangeLog();
2241
+ if ((_a = this.context) == null ? void 0 : _a.setTimelineAction) {
2242
+ this.context.setTimelineAction(TIMELINE_ACTION.UPDATE_PLAYER_DATA, {
2243
+ tracks: result.tracks,
2244
+ version: result.version
2245
+ });
2246
+ }
2247
+ }
2248
+ }
2249
+ /**
2250
+ * Reset history and clear timeline data
2251
+ */
2252
+ resetHistory() {
2253
+ var _a;
2254
+ this.context.handleResetHistory();
2255
+ timelineContextStore.setTimelineData(this.context.contextId, {
2256
+ tracks: [],
2257
+ version: 0
2258
+ });
2259
+ this.context.setTotalDuration(0);
2260
+ this.context.updateChangeLog();
2261
+ if ((_a = this.context) == null ? void 0 : _a.setTimelineAction) {
2262
+ this.context.setTimelineAction(TIMELINE_ACTION.UPDATE_PLAYER_DATA, {
2263
+ tracks: [],
2264
+ version: 0
2265
+ });
2266
+ }
2267
+ }
2268
+ loadProject({
2269
+ tracks,
2270
+ version
2271
+ }) {
2272
+ var _a;
2273
+ this.pauseVideo();
2274
+ this.context.handleResetHistory();
2275
+ const timelineTracks = tracks.map((t) => Track.fromJSON(t));
2276
+ this.setTimelineData(timelineTracks, version);
2277
+ if ((_a = this.context) == null ? void 0 : _a.setTimelineAction) {
2278
+ this.context.setTimelineAction(TIMELINE_ACTION.UPDATE_PLAYER_DATA, {
2279
+ tracks,
2280
+ version,
2281
+ forceUpdate: true
2282
+ });
2283
+ }
2284
+ }
2285
+ }
2286
+ const MAX_HISTORY = 20;
2287
+ const deepClone = (obj) => {
2288
+ return JSON.parse(JSON.stringify(obj));
2289
+ };
2290
+ const UndoRedoContext = createContext(
2291
+ void 0
2292
+ );
2293
+ const STORAGE_KEY_PREFIX = "twick_undo_redo_";
2294
+ const saveToStorage = (key, state) => {
2295
+ try {
2296
+ localStorage.setItem(key, JSON.stringify(state));
2297
+ } catch (error) {
2298
+ console.warn("Failed to save undo-redo state to localStorage:", error);
2299
+ }
2300
+ };
2301
+ const loadFromStorage = (key) => {
2302
+ try {
2303
+ const stored = localStorage.getItem(key);
2304
+ if (!stored) return null;
2305
+ return JSON.parse(stored);
2306
+ } catch (error) {
2307
+ console.warn("Failed to load undo-redo state from localStorage:", error);
2308
+ return null;
2309
+ }
2310
+ };
2311
+ const UndoRedoProvider = ({
2312
+ children,
2313
+ persistenceKey,
2314
+ maxHistorySize = MAX_HISTORY
2315
+ }) => {
2316
+ const [state, setState] = useState(() => {
2317
+ if (persistenceKey) {
2318
+ const stored = loadFromStorage(STORAGE_KEY_PREFIX + persistenceKey);
2319
+ if (stored) {
2320
+ return {
2321
+ past: stored.past,
2322
+ present: stored.present,
2323
+ future: stored.future
2324
+ };
2325
+ }
2326
+ }
2327
+ return {
2328
+ past: [],
2329
+ present: null,
2330
+ future: []
2331
+ };
2332
+ });
2333
+ const saveState = (newState) => {
2334
+ if (persistenceKey) {
2335
+ saveToStorage(STORAGE_KEY_PREFIX + persistenceKey, newState);
2336
+ }
2337
+ };
2338
+ const setPresent = (data) => {
2339
+ setState((prevState) => {
2340
+ let newPast = [...prevState.past];
2341
+ if (prevState.present) {
2342
+ newPast.push(deepClone(prevState.present));
2343
+ }
2344
+ const newState = {
2345
+ past: newPast,
2346
+ present: deepClone(data),
2347
+ future: []
2348
+ // Clear future because it's a new change
2349
+ };
2350
+ if (newState.past.length > maxHistorySize) {
2351
+ newState.past.shift();
2352
+ }
2353
+ saveState(newState);
2354
+ return newState;
2355
+ });
2356
+ };
2357
+ const undo = () => {
2358
+ let undoResult = null;
2359
+ setState((prevState) => {
2360
+ if (prevState.past.length === 0) return prevState;
2361
+ const previous = prevState.past[prevState.past.length - 1];
2362
+ const newState = {
2363
+ past: prevState.past.slice(0, -1),
2364
+ // Remove last item
2365
+ present: previous,
2366
+ future: prevState.present ? [deepClone(prevState.present), ...prevState.future] : prevState.future
2367
+ };
2368
+ undoResult = previous;
2369
+ saveState(newState);
2370
+ return newState;
2371
+ });
2372
+ return undoResult;
2373
+ };
2374
+ const redo = () => {
2375
+ let redoResult = null;
2376
+ setState((prevState) => {
2377
+ if (prevState.future.length === 0) return prevState;
2378
+ const next = prevState.future[0];
2379
+ const newState = {
2380
+ past: prevState.present ? [...prevState.past, deepClone(prevState.present)] : prevState.past,
2381
+ present: next,
2382
+ future: prevState.future.slice(1)
2383
+ // Remove first item
2384
+ };
2385
+ if (newState.past.length > maxHistorySize) {
2386
+ newState.past.shift();
2387
+ }
2388
+ redoResult = next;
2389
+ saveState(newState);
2390
+ return newState;
2391
+ });
2392
+ return redoResult;
2393
+ };
2394
+ const getLastPersistedState = () => {
2395
+ if (persistenceKey) {
2396
+ const stored = loadFromStorage(STORAGE_KEY_PREFIX + persistenceKey);
2397
+ return (stored == null ? void 0 : stored.present) || null;
2398
+ }
2399
+ return null;
2400
+ };
2401
+ const resetHistory = () => {
2402
+ const newState = {
2403
+ past: [],
2404
+ present: null,
2405
+ future: []
2406
+ };
2407
+ setState(newState);
2408
+ if (persistenceKey) {
2409
+ localStorage.removeItem(STORAGE_KEY_PREFIX + persistenceKey);
2410
+ }
2411
+ };
2412
+ const disablePersistence = () => {
2413
+ if (persistenceKey) {
2414
+ localStorage.removeItem(STORAGE_KEY_PREFIX + persistenceKey);
2415
+ }
2416
+ };
2417
+ const contextValue = {
2418
+ canUndo: state.past.length > 0,
2419
+ canRedo: state.future.length > 0,
2420
+ present: state.present,
2421
+ setPresent,
2422
+ undo,
2423
+ redo,
2424
+ resetHistory,
2425
+ getLastPersistedState,
2426
+ disablePersistence
2427
+ };
2428
+ return /* @__PURE__ */ jsx(UndoRedoContext.Provider, { value: contextValue, children });
2429
+ };
2430
+ const useUndoRedo = () => {
2431
+ const context = useContext(UndoRedoContext);
2432
+ if (context === void 0) {
2433
+ throw new Error("useUndoRedo must be used within an UndoRedoProvider");
2434
+ }
2435
+ return context;
2436
+ };
2437
+ const editorRegistry = /* @__PURE__ */ new Map();
2438
+ if (typeof window !== "undefined") {
2439
+ window.twickTimelineEditors = editorRegistry;
2440
+ }
2441
+ const TimelineContext = createContext(
2442
+ void 0
2443
+ );
2444
+ const TimelineProviderInner = ({
2445
+ contextId,
2446
+ children,
2447
+ initialData
2448
+ }) => {
2449
+ const [timelineAction, setTimelineActionState] = useState({
2450
+ type: TIMELINE_ACTION.NONE,
2451
+ payload: null
2452
+ });
2453
+ const [selectedItem, setSelectedItem] = useState(
2454
+ null
2455
+ );
2456
+ const [totalDuration, setTotalDuration] = useState(0);
2457
+ const [changeLog, setChangeLog] = useState(0);
2458
+ const undoRedoContext = useUndoRedo();
2459
+ const dataInitialized = useRef(false);
2460
+ const updateChangeLog = () => {
2461
+ setChangeLog((prev) => prev + 1);
2462
+ };
2463
+ const editor = useMemo(() => {
2464
+ if (editorRegistry.has(contextId)) {
2465
+ editorRegistry.delete(contextId);
2466
+ }
2467
+ const newEditor = new TimelineEditor({
2468
+ contextId,
2469
+ setTotalDuration,
2470
+ setPresent: undoRedoContext.setPresent,
2471
+ handleUndo: undoRedoContext.undo,
2472
+ handleRedo: undoRedoContext.redo,
2473
+ handleResetHistory: undoRedoContext.resetHistory,
2474
+ updateChangeLog,
2475
+ setTimelineAction: (action, payload) => {
2476
+ setTimelineActionState({ type: action, payload });
2477
+ }
2478
+ });
2479
+ editorRegistry.set(contextId, newEditor);
2480
+ return newEditor;
2481
+ }, [contextId]);
2482
+ const setTimelineAction = (type, payload) => {
2483
+ setTimelineActionState({ type, payload });
2484
+ };
2485
+ const initialize = (data) => {
2486
+ const lastPersistedState = undoRedoContext.getLastPersistedState();
2487
+ if (lastPersistedState) {
2488
+ editor.loadProject(lastPersistedState);
2489
+ return;
2490
+ } else {
2491
+ editor.loadProject(data);
2492
+ }
2493
+ };
2494
+ useEffect(() => {
2495
+ if (initialData && !dataInitialized.current) {
2496
+ initialize(initialData);
2497
+ dataInitialized.current = true;
2498
+ }
2499
+ }, [initialData]);
2500
+ const contextValue = {
2501
+ contextId,
2502
+ selectedItem,
2503
+ timelineAction,
2504
+ totalDuration,
2505
+ changeLog,
2506
+ present: undoRedoContext.present,
2507
+ canUndo: undoRedoContext.canUndo,
2508
+ canRedo: undoRedoContext.canRedo,
2509
+ setSelectedItem,
2510
+ setTimelineAction,
2511
+ editor
2512
+ // Include the editor instance
2513
+ };
2514
+ return /* @__PURE__ */ jsx(TimelineContext.Provider, { value: contextValue, children });
2515
+ };
2516
+ const TimelineProvider = ({
2517
+ contextId,
2518
+ children,
2519
+ initialData,
2520
+ undoRedoPersistenceKey,
2521
+ maxHistorySize
2522
+ }) => {
2523
+ return /* @__PURE__ */ jsx(
2524
+ UndoRedoProvider,
2525
+ {
2526
+ persistenceKey: undoRedoPersistenceKey,
2527
+ maxHistorySize,
2528
+ children: /* @__PURE__ */ jsx(
2529
+ TimelineProviderInner,
2530
+ {
2531
+ initialData,
2532
+ contextId,
2533
+ undoRedoPersistenceKey,
2534
+ maxHistorySize,
2535
+ children
2536
+ }
2537
+ )
2538
+ }
2539
+ );
2540
+ };
2541
+ const useTimelineContext = () => {
2542
+ const context = useContext(TimelineContext);
2543
+ if (context === void 0) {
2544
+ throw new Error(
2545
+ "useTimelineContext must be used within a TimelineProvider"
2546
+ );
2547
+ }
2548
+ return context;
2549
+ };
2550
+ if (typeof window !== "undefined") {
2551
+ window.Twick = {
2552
+ Track,
2553
+ TrackElement,
2554
+ ElementDeserializer,
2555
+ ElementSerializer,
2556
+ ElementValidator,
2557
+ ElementAdder,
2558
+ ElementRemover,
2559
+ ElementUpdater,
2560
+ ElementSplitter,
2561
+ ElementCloner,
2562
+ TimelineEditor,
2563
+ TimelineProvider,
2564
+ TIMELINE_ELEMENT_TYPE,
2565
+ // Element types
2566
+ CaptionElement,
2567
+ RectElement,
2568
+ TextElement,
2569
+ ImageElement,
2570
+ AudioElement,
2571
+ CircleElement,
2572
+ IconElement,
2573
+ VideoElement,
2574
+ ElementAnimation,
2575
+ ElementFrameEffect,
2576
+ ElementTextEffect,
2577
+ // Utility functions
2578
+ generateShortUuid,
2579
+ getTotalDuration,
2580
+ getCurrentElements,
2581
+ isTrackId,
2582
+ isElementId
2583
+ };
2584
+ }
2585
+ export {
2586
+ AudioElement,
2587
+ CAPTION_COLOR,
2588
+ CAPTION_FONT,
2589
+ CAPTION_STYLE,
2590
+ CAPTION_STYLE_OPTIONS,
2591
+ CaptionElement,
2592
+ CircleElement,
2593
+ ElementAdder,
2594
+ ElementAnimation,
2595
+ ElementCloner,
2596
+ ElementDeserializer,
2597
+ ElementFrameEffect,
2598
+ ElementRemover,
2599
+ ElementSerializer,
2600
+ ElementSplitter,
2601
+ ElementTextEffect,
2602
+ ElementUpdater,
2603
+ ElementValidator,
2604
+ IconElement,
2605
+ ImageElement,
2606
+ PLAYER_STATE,
2607
+ PROCESS_STATE,
2608
+ RectElement,
2609
+ TIMELINE_ACTION,
2610
+ TIMELINE_ELEMENT_TYPE,
2611
+ TextElement,
2612
+ TimelineEditor,
2613
+ TimelineProvider,
2614
+ Track,
2615
+ TrackElement,
2616
+ ValidationError,
2617
+ VideoElement,
2618
+ WORDS_PER_PHRASE,
2619
+ canSplitElement,
2620
+ generateShortUuid,
2621
+ getCurrentElements,
2622
+ getDecimalNumber,
2623
+ getTotalDuration,
2624
+ isElementId,
2625
+ isTrackId,
2626
+ useTimelineContext
2627
+ };
2628
+ //# sourceMappingURL=index.mjs.map