@twick/timeline 0.14.2 → 0.14.4

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.
package/dist/index.mjs CHANGED
@@ -4,13 +4,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import React, { useState, createContext, useContext, useRef, useMemo, useEffect } from "react";
6
6
  const PLAYER_STATE = {
7
+ /** Player is refreshing/reloading content */
7
8
  REFRESH: "Refresh",
9
+ /** Player is actively playing content */
8
10
  PLAYING: "Playing",
11
+ /** Player is paused */
9
12
  PAUSED: "Paused"
10
13
  };
11
14
  const CAPTION_STYLE = {
15
+ /** Highlights background of each word */
12
16
  WORD_BG_HIGHLIGHT: "highlight_bg",
17
+ /** Animates text word by word */
13
18
  WORD_BY_WORD: "word_by_word",
19
+ /** Animates text word by word with background highlighting */
14
20
  WORD_BY_WORD_WITH_BG: "word_by_word_with_bg"
15
21
  };
16
22
  const CAPTION_STYLE_OPTIONS = {
@@ -28,34 +34,54 @@ const CAPTION_STYLE_OPTIONS = {
28
34
  }
29
35
  };
30
36
  const CAPTION_FONT = {
37
+ /** Font size in pixels */
31
38
  size: 40
32
39
  };
33
40
  const CAPTION_COLOR = {
41
+ /** Text color in hex format */
34
42
  text: "#ffffff",
43
+ /** Highlight color in hex format */
35
44
  highlight: "#ff4081",
45
+ /** Background color in hex format */
36
46
  bgColor: "#8C52FF"
37
47
  };
38
48
  const WORDS_PER_PHRASE = 4;
39
49
  const TIMELINE_ACTION = {
50
+ /** No action being performed */
40
51
  NONE: "none",
52
+ /** Setting the player state (play/pause) */
41
53
  SET_PLAYER_STATE: "setPlayerState",
54
+ /** Updating player data */
42
55
  UPDATE_PLAYER_DATA: "updatePlayerData",
56
+ /** Player has been updated */
43
57
  ON_PLAYER_UPDATED: "onPlayerUpdated"
44
58
  };
45
59
  const TIMELINE_ELEMENT_TYPE = {
60
+ /** Video element type */
46
61
  VIDEO: "video",
62
+ /** Caption element type */
47
63
  CAPTION: "caption",
64
+ /** Image element type */
48
65
  IMAGE: "image",
66
+ /** Audio element type */
49
67
  AUDIO: "audio",
68
+ /** Text element type */
50
69
  TEXT: "text",
70
+ /** Rectangle element type */
51
71
  RECT: "rect",
72
+ /** Circle element type */
52
73
  CIRCLE: "circle",
74
+ /** Icon element type */
53
75
  ICON: "icon"
54
76
  };
55
77
  const PROCESS_STATE = {
78
+ /** Process is idle */
56
79
  IDLE: "Idle",
80
+ /** Process is currently running */
57
81
  PROCESSING: "Processing",
82
+ /** Process has completed successfully */
58
83
  COMPLETED: "Completed",
84
+ /** Process has failed */
59
85
  FAILED: "Failed"
60
86
  };
61
87
  const imageDimensionsCache = {};
@@ -456,7 +482,9 @@ class TrackElement {
456
482
  this.type = type;
457
483
  this.props = {
458
484
  x: 0,
459
- y: 0
485
+ y: 0,
486
+ opacity: 1,
487
+ rotation: 0
460
488
  };
461
489
  }
462
490
  getId() {
@@ -493,6 +521,12 @@ class TrackElement {
493
521
  y: ((_b = this.props) == null ? void 0 : _b.y) ?? 0
494
522
  };
495
523
  }
524
+ getRotation() {
525
+ return this.props.rotation ?? 0;
526
+ }
527
+ getOpacity() {
528
+ return this.props.opacity ?? 1;
529
+ }
496
530
  setId(id) {
497
531
  this.id = id;
498
532
  return this;
@@ -526,6 +560,14 @@ class TrackElement {
526
560
  this.props.y = position.y;
527
561
  return this;
528
562
  }
563
+ setRotation(rotation) {
564
+ this.props.rotation = rotation;
565
+ return this;
566
+ }
567
+ setOpacity(opacity) {
568
+ this.props.opacity = opacity;
569
+ return this;
570
+ }
529
571
  setProps(props) {
530
572
  this.props = structuredClone(props);
531
573
  return this;
@@ -1014,13 +1056,35 @@ class CaptionElement extends TrackElement {
1014
1056
  }
1015
1057
  }
1016
1058
  class IconElement extends TrackElement {
1017
- constructor(src, size) {
1059
+ constructor(src, size, fill = "#866bbf") {
1018
1060
  super(TIMELINE_ELEMENT_TYPE.ICON);
1019
1061
  this.props = {
1020
1062
  src,
1063
+ fill,
1021
1064
  size
1022
1065
  };
1023
1066
  }
1067
+ getSrc() {
1068
+ return this.props.src;
1069
+ }
1070
+ getFill() {
1071
+ return this.props.fill;
1072
+ }
1073
+ getSize() {
1074
+ return this.props.size;
1075
+ }
1076
+ setSrc(src) {
1077
+ this.props.src = src;
1078
+ return this;
1079
+ }
1080
+ setFill(fill) {
1081
+ this.props.fill = fill;
1082
+ return this;
1083
+ }
1084
+ setSize(size) {
1085
+ this.props.size = size;
1086
+ return this;
1087
+ }
1024
1088
  accept(visitor) {
1025
1089
  return visitor.visitIconElement(this);
1026
1090
  }
@@ -1030,7 +1094,9 @@ class CircleElement extends TrackElement {
1030
1094
  super(TIMELINE_ELEMENT_TYPE.CIRCLE);
1031
1095
  this.props = {
1032
1096
  radius,
1033
- fill
1097
+ fill,
1098
+ strokeColor: fill,
1099
+ lineWidth: 1
1034
1100
  };
1035
1101
  }
1036
1102
  getFill() {
@@ -1039,6 +1105,12 @@ class CircleElement extends TrackElement {
1039
1105
  getRadius() {
1040
1106
  return this.props.radius;
1041
1107
  }
1108
+ getStrokeColor() {
1109
+ return this.props.strokeColor || this.props.fill;
1110
+ }
1111
+ getLineWidth() {
1112
+ return this.props.lineWidth ?? 0;
1113
+ }
1042
1114
  setFill(fill) {
1043
1115
  this.props.fill = fill;
1044
1116
  return this;
@@ -1047,6 +1119,14 @@ class CircleElement extends TrackElement {
1047
1119
  this.props.radius = radius;
1048
1120
  return this;
1049
1121
  }
1122
+ setStrokeColor(strokeColor) {
1123
+ this.props.strokeColor = strokeColor;
1124
+ return this;
1125
+ }
1126
+ setLineWidth(lineWidth) {
1127
+ this.props.lineWidth = lineWidth;
1128
+ return this;
1129
+ }
1050
1130
  accept(visitor) {
1051
1131
  return visitor.visitCircleElement(this);
1052
1132
  }
@@ -1057,18 +1137,48 @@ class RectElement extends TrackElement {
1057
1137
  this.props = {
1058
1138
  width: size.width,
1059
1139
  height: size.height,
1060
- fill
1140
+ fill,
1141
+ radius: 0,
1142
+ strokeColor: fill,
1143
+ lineWidth: 1
1061
1144
  };
1062
1145
  }
1146
+ getFill() {
1147
+ return this.props.fill;
1148
+ }
1063
1149
  setFill(fill) {
1064
1150
  this.props.fill = fill;
1065
1151
  return this;
1066
1152
  }
1153
+ getSize() {
1154
+ return { width: this.props.width, height: this.props.height };
1155
+ }
1156
+ getCornerRadius() {
1157
+ return this.props.radius;
1158
+ }
1159
+ getStrokeColor() {
1160
+ return this.props.strokeColor || this.props.fill;
1161
+ }
1162
+ getLineWidth() {
1163
+ return this.props.lineWidth ?? 0;
1164
+ }
1067
1165
  setSize(size) {
1068
1166
  this.props.width = size.width;
1069
1167
  this.props.height = size.height;
1070
1168
  return this;
1071
1169
  }
1170
+ setCornerRadius(cornerRadius) {
1171
+ this.props.radius = cornerRadius;
1172
+ return this;
1173
+ }
1174
+ setStrokeColor(strokeColor) {
1175
+ this.props.strokeColor = strokeColor;
1176
+ return this;
1177
+ }
1178
+ setLineWidth(lineWidth) {
1179
+ this.props.lineWidth = lineWidth;
1180
+ return this;
1181
+ }
1072
1182
  accept(visitor) {
1073
1183
  return visitor.visitRectElement(this);
1074
1184
  }
@@ -1077,6 +1187,7 @@ class ElementAnimation {
1077
1187
  constructor(name) {
1078
1188
  __publicField(this, "name");
1079
1189
  __publicField(this, "interval");
1190
+ __publicField(this, "duration");
1080
1191
  __publicField(this, "intensity");
1081
1192
  __publicField(this, "animate");
1082
1193
  __publicField(this, "mode");
@@ -1089,6 +1200,9 @@ class ElementAnimation {
1089
1200
  getInterval() {
1090
1201
  return this.interval;
1091
1202
  }
1203
+ getDuration() {
1204
+ return this.duration;
1205
+ }
1092
1206
  getIntensity() {
1093
1207
  return this.intensity;
1094
1208
  }
@@ -1105,6 +1219,10 @@ class ElementAnimation {
1105
1219
  this.interval = interval;
1106
1220
  return this;
1107
1221
  }
1222
+ setDuration(duration) {
1223
+ this.duration = duration;
1224
+ return this;
1225
+ }
1108
1226
  setIntensity(intensity) {
1109
1227
  this.intensity = intensity;
1110
1228
  return this;
@@ -1125,6 +1243,7 @@ class ElementAnimation {
1125
1243
  return {
1126
1244
  name: this.name,
1127
1245
  interval: this.interval,
1246
+ duration: this.duration,
1128
1247
  intensity: this.intensity,
1129
1248
  animate: this.animate,
1130
1249
  mode: this.mode,
@@ -1134,6 +1253,7 @@ class ElementAnimation {
1134
1253
  static fromJSON(json) {
1135
1254
  const animation = new ElementAnimation(json.name);
1136
1255
  animation.setInterval(json.interval);
1256
+ animation.setDuration(json.duration);
1137
1257
  animation.setIntensity(json.intensity);
1138
1258
  animation.setAnimate(json.animate);
1139
1259
  animation.setMode(json.mode);
@@ -1219,7 +1339,7 @@ class ElementTextEffect {
1219
1339
  return {
1220
1340
  name: this.name,
1221
1341
  delay: this.delay,
1222
- duration: this.duration,
1342
+ duration: this.duration ?? 1,
1223
1343
  bufferTime: this.bufferTime
1224
1344
  };
1225
1345
  }
@@ -1287,11 +1407,12 @@ class ElementDeserializer {
1287
1407
  return captionElement;
1288
1408
  }
1289
1409
  static deserializeIconElement(json) {
1290
- var _a, _b;
1291
- const size = ((_a = json.props) == null ? void 0 : _a.size) ? { width: json.props.size[0], height: json.props.size[1] } : { width: 0, height: 0 };
1410
+ var _a, _b, _c;
1411
+ const size = ((_a = json.props) == null ? void 0 : _a.size) ?? { width: 100, height: 100 };
1292
1412
  const iconElement = new IconElement(
1293
1413
  ((_b = json.props) == null ? void 0 : _b.src) || "",
1294
- size
1414
+ size,
1415
+ (_c = json.props) == null ? void 0 : _c.fill
1295
1416
  );
1296
1417
  ElementDeserializer.deserializeBaseElement(iconElement, json);
1297
1418
  return iconElement;
@@ -1527,10 +1648,6 @@ class ElementValidator {
1527
1648
  const basicValidation = this.validateBasicProperties(element);
1528
1649
  const errors = [...basicValidation.errors];
1529
1650
  const warnings = [...basicValidation.warnings];
1530
- const props = element.getProps();
1531
- if (!(props == null ? void 0 : props.icon)) {
1532
- errors.push("Icon element must have an icon name");
1533
- }
1534
1651
  return { errors, warnings };
1535
1652
  }
1536
1653
  validateCircleElement(element) {
@@ -1682,7 +1799,20 @@ class TrackFriend {
1682
1799
  }
1683
1800
  }
1684
1801
  class Track {
1685
- constructor(name, id) {
1802
+ /**
1803
+ * Creates a new Track instance.
1804
+ *
1805
+ * @param name - The display name for the track
1806
+ * @param type - The type of the track
1807
+ * @param id - Optional unique identifier (auto-generated if not provided)
1808
+ *
1809
+ * @example
1810
+ * ```js
1811
+ * const track = new Track("My Video Track");
1812
+ * const trackWithId = new Track("Audio Track", "element", "video-track-1");
1813
+ * ```
1814
+ */
1815
+ constructor(name, type = "element", id) {
1686
1816
  __publicField(this, "id");
1687
1817
  __publicField(this, "name");
1688
1818
  __publicField(this, "type");
@@ -1690,71 +1820,215 @@ class Track {
1690
1820
  __publicField(this, "validator");
1691
1821
  this.name = name;
1692
1822
  this.id = id ?? `t-${generateShortUuid}`;
1693
- this.type = "element";
1823
+ this.type = type;
1694
1824
  this.elements = [];
1695
1825
  this.validator = new ElementValidator();
1696
1826
  }
1697
1827
  /**
1698
- * Create a friend instance for explicit access to protected methods
1699
- * This implements the Friend Class Pattern
1700
- * @returns TrackFriend instance
1828
+ * Creates a friend instance for explicit access to protected methods.
1829
+ * This implements the Friend Class Pattern to allow controlled access
1830
+ * to protected methods while maintaining encapsulation.
1831
+ *
1832
+ * @returns TrackFriend instance that can access protected methods
1833
+ *
1834
+ * @example
1835
+ * ```js
1836
+ * const track = new Track("My Track");
1837
+ * const friend = track.createFriend();
1838
+ *
1839
+ * // Use friend to add elements
1840
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
1841
+ * friend.addElement(element);
1842
+ * ```
1701
1843
  */
1702
1844
  createFriend() {
1703
1845
  return new TrackFriend(this);
1704
1846
  }
1705
1847
  /**
1706
- * Friend method to add element (called by TrackFriend)
1707
- * @param element The element to add
1708
- * @param skipValidation If true, skips validation
1848
+ * Friend method to add element (called by TrackFriend).
1849
+ * Provides controlled access to the protected addElement method.
1850
+ *
1851
+ * @param element - The element to add to the track
1852
+ * @param skipValidation - If true, skips validation (use with caution)
1709
1853
  * @returns true if element was added successfully
1854
+ *
1855
+ * @example
1856
+ * ```js
1857
+ * const track = new Track("My Track");
1858
+ * const friend = track.createFriend();
1859
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
1860
+ *
1861
+ * const success = track.addElementViaFriend(element);
1862
+ * // success = true if element was added successfully
1863
+ * ```
1710
1864
  */
1711
1865
  addElementViaFriend(element, skipValidation = false) {
1712
1866
  return this.addElement(element, skipValidation);
1713
1867
  }
1714
1868
  /**
1715
- * Friend method to remove element (called by TrackFriend)
1716
- * @param element The element to remove
1869
+ * Friend method to remove element (called by TrackFriend).
1870
+ * Provides controlled access to the protected removeElement method.
1871
+ *
1872
+ * @param element - The element to remove from the track
1873
+ *
1874
+ * @example
1875
+ * ```js
1876
+ * const track = new Track("My Track");
1877
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
1878
+ *
1879
+ * track.removeElementViaFriend(element);
1880
+ * // Element is removed from the track
1881
+ * ```
1717
1882
  */
1718
1883
  removeElementViaFriend(element) {
1719
1884
  this.removeElement(element);
1720
1885
  }
1721
1886
  /**
1722
- * Friend method to update element (called by TrackFriend)
1723
- * @param element The element to update
1887
+ * Friend method to update element (called by TrackFriend).
1888
+ * Provides controlled access to the protected updateElement method.
1889
+ *
1890
+ * @param element - The updated element to replace the existing one
1724
1891
  * @returns true if element was updated successfully
1892
+ *
1893
+ * @example
1894
+ * ```js
1895
+ * const track = new Track("My Track");
1896
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
1897
+ *
1898
+ * // Update the element
1899
+ * element.setEnd(15);
1900
+ * const success = track.updateElementViaFriend(element);
1901
+ * // success = true if element was updated successfully
1902
+ * ```
1725
1903
  */
1726
1904
  updateElementViaFriend(element) {
1727
1905
  return this.updateElement(element);
1728
1906
  }
1907
+ /**
1908
+ * Gets the unique identifier of the track.
1909
+ *
1910
+ * @returns The track's unique ID string
1911
+ *
1912
+ * @example
1913
+ * ```js
1914
+ * const track = new Track("My Track", "element", "track-123");
1915
+ * const id = track.getId(); // "track-123"
1916
+ * ```
1917
+ */
1729
1918
  getId() {
1730
1919
  return this.id;
1731
1920
  }
1921
+ /**
1922
+ * Gets the display name of the track.
1923
+ *
1924
+ * @returns The track's display name
1925
+ *
1926
+ * @example
1927
+ * ```js
1928
+ * const track = new Track("Video Track");
1929
+ * const name = track.getName(); // "Video Track"
1930
+ * ```
1931
+ */
1732
1932
  getName() {
1733
1933
  return this.name;
1734
1934
  }
1935
+ /**
1936
+ * Gets the type of the track.
1937
+ *
1938
+ * @returns The track's type string
1939
+ *
1940
+ * @example
1941
+ * ```js
1942
+ * const track = new Track("My Track");
1943
+ * const type = track.getType(); // "element"
1944
+ * ```
1945
+ */
1735
1946
  getType() {
1736
1947
  return this.type;
1737
1948
  }
1949
+ /**
1950
+ * Gets a read-only array of all elements in the track.
1951
+ * Returns a copy of the elements array to prevent external modification.
1952
+ *
1953
+ * @returns Read-only array of track elements
1954
+ *
1955
+ * @example
1956
+ * ```js
1957
+ * const track = new Track("My Track");
1958
+ * const elements = track.getElements();
1959
+ * // elements is a read-only array of TrackElement instances
1960
+ *
1961
+ * elements.forEach(element => {
1962
+ * console.log(`Element: ${element.getId()}, Duration: ${element.getEnd() - element.getStart()}`);
1963
+ * });
1964
+ * ```
1965
+ */
1738
1966
  getElements() {
1739
1967
  return [...this.elements];
1740
1968
  }
1741
1969
  /**
1742
- * Validates an element
1743
- * @param element The element to validate
1970
+ * Validates a single element using the track's validator.
1971
+ * Checks if the element meets all validation requirements.
1972
+ *
1973
+ * @param element - The element to validate
1744
1974
  * @returns true if valid, throws ValidationError if invalid
1975
+ *
1976
+ * @example
1977
+ * ```js
1978
+ * const track = new Track("My Track");
1979
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
1980
+ *
1981
+ * try {
1982
+ * const isValid = track.validateElement(element);
1983
+ * console.log('Element is valid:', isValid);
1984
+ * } catch (error) {
1985
+ * if (error instanceof ValidationError) {
1986
+ * console.log('Validation failed:', error.errors);
1987
+ * }
1988
+ * }
1989
+ * ```
1745
1990
  */
1746
1991
  validateElement(element) {
1747
1992
  return element.accept(this.validator);
1748
1993
  }
1994
+ /**
1995
+ * Gets the total duration of the track.
1996
+ * Calculates the duration based on the end time of the last element.
1997
+ *
1998
+ * @returns The total duration of the track in seconds
1999
+ *
2000
+ * @example
2001
+ * ```js
2002
+ * const track = new Track("My Track");
2003
+ * const duration = track.getTrackDuration(); // 0 if no elements
2004
+ *
2005
+ * // After adding elements
2006
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 30 });
2007
+ * track.createFriend().addElement(element);
2008
+ * const newDuration = track.getTrackDuration(); // 30
2009
+ * ```
2010
+ */
1749
2011
  getTrackDuration() {
1750
2012
  var _a;
1751
2013
  return ((_a = this.elements) == null ? void 0 : _a.length) ? this.elements[this.elements.length - 1].getEnd() : 0;
1752
2014
  }
1753
2015
  /**
1754
- * Adds an element to the track with validation
1755
- * @param element The element to add
1756
- * @param skipValidation If true, skips validation (use with caution)
2016
+ * Adds an element to the track with validation.
2017
+ * Protected method that should be accessed through TrackFriend.
2018
+ *
2019
+ * @param element - The element to add to the track
2020
+ * @param skipValidation - If true, skips validation (use with caution)
1757
2021
  * @returns true if element was added successfully, throws ValidationError if validation fails
2022
+ *
2023
+ * @example
2024
+ * ```js
2025
+ * const track = new Track("My Track");
2026
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
2027
+ *
2028
+ * // Use friend to access this protected method
2029
+ * const friend = track.createFriend();
2030
+ * const success = friend.addElement(element);
2031
+ * ```
1758
2032
  */
1759
2033
  addElement(element, skipValidation = false) {
1760
2034
  element.setTrackId(this.id);
@@ -1776,6 +2050,22 @@ class Track {
1776
2050
  }
1777
2051
  return false;
1778
2052
  }
2053
+ /**
2054
+ * Removes an element from the track.
2055
+ * Protected method that should be accessed through TrackFriend.
2056
+ *
2057
+ * @param element - The element to remove from the track
2058
+ *
2059
+ * @example
2060
+ * ```js
2061
+ * const track = new Track("My Track");
2062
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
2063
+ *
2064
+ * // Use friend to access this protected method
2065
+ * const friend = track.createFriend();
2066
+ * friend.removeElement(element);
2067
+ * ```
2068
+ */
1779
2069
  removeElement(element) {
1780
2070
  const index = this.elements.findIndex((e2) => e2.getId() === element.getId());
1781
2071
  if (index !== -1) {
@@ -1783,9 +2073,22 @@ class Track {
1783
2073
  }
1784
2074
  }
1785
2075
  /**
1786
- * Updates an element in the track with validation
1787
- * @param element The element to update
2076
+ * Updates an element in the track with validation.
2077
+ * Protected method that should be accessed through TrackFriend.
2078
+ *
2079
+ * @param element - The updated element to replace the existing one
1788
2080
  * @returns true if element was updated successfully, throws ValidationError if validation fails
2081
+ *
2082
+ * @example
2083
+ * ```js
2084
+ * const track = new Track("My Track");
2085
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
2086
+ *
2087
+ * // Use friend to access this protected method
2088
+ * const friend = track.createFriend();
2089
+ * element.setEnd(15);
2090
+ * const success = friend.updateElement(element);
2091
+ * ```
1789
2092
  */
1790
2093
  updateElement(element) {
1791
2094
  try {
@@ -1807,14 +2110,55 @@ class Track {
1807
2110
  }
1808
2111
  return false;
1809
2112
  }
2113
+ /**
2114
+ * Finds an element in the track by its ID.
2115
+ *
2116
+ * @param id - The unique identifier of the element to find
2117
+ * @returns The found element or undefined if not found
2118
+ *
2119
+ * @example
2120
+ * ```js
2121
+ * const track = new Track("My Track");
2122
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
2123
+ * track.createFriend().addElement(element);
2124
+ *
2125
+ * const foundElement = track.getElementById(element.getId());
2126
+ * // foundElement is the same element instance
2127
+ * ```
2128
+ */
1810
2129
  getElementById(id) {
1811
2130
  const element = this.elements.find((e2) => e2.getId() === id);
1812
2131
  if (!element) return void 0;
1813
2132
  return element;
1814
2133
  }
1815
2134
  /**
1816
- * Validates all elements in the track and returns combined result and per-element status
2135
+ * Validates all elements in the track and returns combined result and per-element status.
2136
+ * Provides detailed validation information for each element including errors and warnings.
2137
+ *
1817
2138
  * @returns Object with overall isValid and array of per-element validation results
2139
+ *
2140
+ * @example
2141
+ * ```js
2142
+ * const track = new Track("My Track");
2143
+ * const element1 = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
2144
+ * const element2 = new TextElement({ text: "Hello", start: 5, end: 15 });
2145
+ *
2146
+ * track.createFriend().addElement(element1);
2147
+ * track.createFriend().addElement(element2);
2148
+ *
2149
+ * const validation = track.validateAllElements();
2150
+ * console.log('Overall valid:', validation.isValid);
2151
+ *
2152
+ * validation.results.forEach(result => {
2153
+ * console.log(`Element ${result.element.getId()}: ${result.isValid ? 'Valid' : 'Invalid'}`);
2154
+ * if (result.errors) {
2155
+ * console.log('Errors:', result.errors);
2156
+ * }
2157
+ * if (result.warnings) {
2158
+ * console.log('Warnings:', result.warnings);
2159
+ * }
2160
+ * });
2161
+ * ```
1818
2162
  */
1819
2163
  validateAllElements() {
1820
2164
  let validResult = true;
@@ -1844,6 +2188,30 @@ class Track {
1844
2188
  });
1845
2189
  return { isValid: validResult, results };
1846
2190
  }
2191
+ /**
2192
+ * Serializes the track and all its elements to JSON format.
2193
+ * Converts the track structure to a format that can be stored or transmitted.
2194
+ *
2195
+ * @returns TrackJSON object representing the track and its elements
2196
+ *
2197
+ * @example
2198
+ * ```js
2199
+ * const track = new Track("My Track");
2200
+ * const element = new VideoElement({ src: "video.mp4", start: 0, end: 10 });
2201
+ * track.createFriend().addElement(element);
2202
+ *
2203
+ * const trackData = track.serialize();
2204
+ * // trackData = {
2205
+ * // id: "t-abc123",
2206
+ * // name: "My Track",
2207
+ * // type: "element",
2208
+ * // elements: [{ ... }]
2209
+ * // }
2210
+ *
2211
+ * // Save to localStorage
2212
+ * localStorage.setItem('track-data', JSON.stringify(trackData));
2213
+ * ```
2214
+ */
1847
2215
  serialize() {
1848
2216
  const serializer = new ElementSerializer();
1849
2217
  return {
@@ -1855,8 +2223,37 @@ class Track {
1855
2223
  )
1856
2224
  };
1857
2225
  }
2226
+ /**
2227
+ * Creates a Track instance from JSON data.
2228
+ * Static factory method for deserializing track data.
2229
+ *
2230
+ * @param json - JSON object containing track data
2231
+ * @returns New Track instance with loaded data
2232
+ *
2233
+ * @example
2234
+ * ```js
2235
+ * const trackData = {
2236
+ * id: "t-abc123",
2237
+ * name: "My Track",
2238
+ * type: "element",
2239
+ * elements: [
2240
+ * {
2241
+ * id: "e-def456",
2242
+ * type: "video",
2243
+ * src: "video.mp4",
2244
+ * start: 0,
2245
+ * end: 10
2246
+ * }
2247
+ * ]
2248
+ * };
2249
+ *
2250
+ * const track = Track.fromJSON(trackData);
2251
+ * console.log(track.getName()); // "My Track"
2252
+ * console.log(track.getElements().length); // 1
2253
+ * ```
2254
+ */
1858
2255
  static fromJSON(json) {
1859
- const track = new Track(json.name, json.id);
2256
+ const track = new Track(json.name, json.type ?? "element", json.id);
1860
2257
  track.type = json.type;
1861
2258
  track.elements = (json.elements || []).map(ElementDeserializer.fromJSON);
1862
2259
  return track;
@@ -1934,12 +2331,37 @@ __publicField(_TimelineContextStore, "instance");
1934
2331
  let TimelineContextStore = _TimelineContextStore;
1935
2332
  const timelineContextStore = TimelineContextStore.getInstance();
1936
2333
  class ElementAdder {
2334
+ /**
2335
+ * Creates a new ElementAdder instance for the specified track.
2336
+ *
2337
+ * @param track - The track to add elements to
2338
+ *
2339
+ * @example
2340
+ * ```js
2341
+ * const adder = new ElementAdder(track);
2342
+ * const success = await adder.visitVideoElement(videoElement);
2343
+ * ```
2344
+ */
1937
2345
  constructor(track) {
1938
2346
  __publicField(this, "track");
1939
2347
  __publicField(this, "trackFriend");
1940
2348
  this.track = track;
1941
2349
  this.trackFriend = track.createFriend();
1942
2350
  }
2351
+ /**
2352
+ * Adds a video element to the track.
2353
+ * Updates video metadata and calculates appropriate start/end times
2354
+ * based on existing track elements.
2355
+ *
2356
+ * @param element - The video element to add
2357
+ * @returns Promise resolving to true if element was added successfully
2358
+ *
2359
+ * @example
2360
+ * ```js
2361
+ * const success = await adder.visitVideoElement(videoElement);
2362
+ * // success = true if element was added successfully
2363
+ * ```
2364
+ */
1943
2365
  async visitVideoElement(element) {
1944
2366
  await element.updateVideoMeta();
1945
2367
  const elements = this.track.getElements();
@@ -1952,6 +2374,20 @@ class ElementAdder {
1952
2374
  }
1953
2375
  return this.trackFriend.addElement(element);
1954
2376
  }
2377
+ /**
2378
+ * Adds an audio element to the track.
2379
+ * Updates audio metadata and calculates appropriate start/end times
2380
+ * based on existing track elements.
2381
+ *
2382
+ * @param element - The audio element to add
2383
+ * @returns Promise resolving to true if element was added successfully
2384
+ *
2385
+ * @example
2386
+ * ```js
2387
+ * const success = await adder.visitAudioElement(audioElement);
2388
+ * // success = true if element was added successfully
2389
+ * ```
2390
+ */
1955
2391
  async visitAudioElement(element) {
1956
2392
  await element.updateAudioMeta();
1957
2393
  const elements = this.track.getElements();
@@ -1964,6 +2400,20 @@ class ElementAdder {
1964
2400
  }
1965
2401
  return this.trackFriend.addElement(element);
1966
2402
  }
2403
+ /**
2404
+ * Adds an image element to the track.
2405
+ * Updates image metadata and calculates appropriate start/end times
2406
+ * based on existing track elements.
2407
+ *
2408
+ * @param element - The image element to add
2409
+ * @returns Promise resolving to true if element was added successfully
2410
+ *
2411
+ * @example
2412
+ * ```js
2413
+ * const success = await adder.visitImageElement(imageElement);
2414
+ * // success = true if element was added successfully
2415
+ * ```
2416
+ */
1967
2417
  async visitImageElement(element) {
1968
2418
  await element.updateImageMeta();
1969
2419
  const elements = this.track.getElements();
@@ -1976,6 +2426,19 @@ class ElementAdder {
1976
2426
  }
1977
2427
  return this.trackFriend.addElement(element);
1978
2428
  }
2429
+ /**
2430
+ * Adds a text element to the track.
2431
+ * Calculates appropriate start/end times based on existing track elements.
2432
+ *
2433
+ * @param element - The text element to add
2434
+ * @returns Promise resolving to true if element was added successfully
2435
+ *
2436
+ * @example
2437
+ * ```js
2438
+ * const success = await adder.visitTextElement(textElement);
2439
+ * // success = true if element was added successfully
2440
+ * ```
2441
+ */
1979
2442
  async visitTextElement(element) {
1980
2443
  const elements = this.track.getElements();
1981
2444
  const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
@@ -1987,6 +2450,19 @@ class ElementAdder {
1987
2450
  }
1988
2451
  return this.trackFriend.addElement(element);
1989
2452
  }
2453
+ /**
2454
+ * Adds a caption element to the track.
2455
+ * Calculates appropriate start/end times based on existing track elements.
2456
+ *
2457
+ * @param element - The caption element to add
2458
+ * @returns Promise resolving to true if element was added successfully
2459
+ *
2460
+ * @example
2461
+ * ```js
2462
+ * const success = await adder.visitCaptionElement(captionElement);
2463
+ * // success = true if element was added successfully
2464
+ * ```
2465
+ */
1990
2466
  async visitCaptionElement(element) {
1991
2467
  const elements = this.track.getElements();
1992
2468
  const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
@@ -1998,6 +2474,19 @@ class ElementAdder {
1998
2474
  }
1999
2475
  return this.trackFriend.addElement(element);
2000
2476
  }
2477
+ /**
2478
+ * Adds an icon element to the track.
2479
+ * Calculates appropriate start/end times based on existing track elements.
2480
+ *
2481
+ * @param element - The icon element to add
2482
+ * @returns Promise resolving to true if element was added successfully
2483
+ *
2484
+ * @example
2485
+ * ```js
2486
+ * const success = await adder.visitIconElement(iconElement);
2487
+ * // success = true if element was added successfully
2488
+ * ```
2489
+ */
2001
2490
  async visitIconElement(element) {
2002
2491
  const elements = this.track.getElements();
2003
2492
  const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
@@ -2009,6 +2498,19 @@ class ElementAdder {
2009
2498
  }
2010
2499
  return this.trackFriend.addElement(element);
2011
2500
  }
2501
+ /**
2502
+ * Adds a circle element to the track.
2503
+ * Calculates appropriate start/end times based on existing track elements.
2504
+ *
2505
+ * @param element - The circle element to add
2506
+ * @returns Promise resolving to true if element was added successfully
2507
+ *
2508
+ * @example
2509
+ * ```js
2510
+ * const success = await adder.visitCircleElement(circleElement);
2511
+ * // success = true if element was added successfully
2512
+ * ```
2513
+ */
2012
2514
  async visitCircleElement(element) {
2013
2515
  const elements = this.track.getElements();
2014
2516
  const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
@@ -2020,6 +2522,19 @@ class ElementAdder {
2020
2522
  }
2021
2523
  return this.trackFriend.addElement(element);
2022
2524
  }
2525
+ /**
2526
+ * Adds a rectangle element to the track.
2527
+ * Calculates appropriate start/end times based on existing track elements.
2528
+ *
2529
+ * @param element - The rectangle element to add
2530
+ * @returns Promise resolving to true if element was added successfully
2531
+ *
2532
+ * @example
2533
+ * ```js
2534
+ * const success = await adder.visitRectElement(rectElement);
2535
+ * // success = true if element was added successfully
2536
+ * ```
2537
+ */
2023
2538
  async visitRectElement(element) {
2024
2539
  const elements = this.track.getElements();
2025
2540
  const lastEndtime = (elements == null ? void 0 : elements.length) ? elements[elements.length - 1].getEnd() : 0;
@@ -2160,7 +2675,8 @@ class ElementCloner {
2160
2675
  visitIconElement(element) {
2161
2676
  const clonedElement = new IconElement(
2162
2677
  element.getProps().src,
2163
- element.getProps().size
2678
+ element.getProps().size,
2679
+ element.getProps().fill
2164
2680
  );
2165
2681
  this.cloneElementProperties(element, clonedElement);
2166
2682
  return clonedElement;
@@ -2349,10 +2865,10 @@ class TimelineEditor {
2349
2865
  this.context.updateChangeLog();
2350
2866
  return updatedTimelineData;
2351
2867
  }
2352
- addTrack(name) {
2868
+ addTrack(name, type = "element") {
2353
2869
  const prevTimelineData = this.getTimelineData();
2354
2870
  const id = `t-${generateShortUuid()}`;
2355
- const track = new Track(name, id);
2871
+ const track = new Track(name, type, id);
2356
2872
  const updatedTimelines = [...(prevTimelineData == null ? void 0 : prevTimelineData.tracks) || [], track];
2357
2873
  this.setTimelineData(updatedTimelines);
2358
2874
  return track;
@@ -2439,12 +2955,12 @@ class TimelineEditor {
2439
2955
  /**
2440
2956
  * Update an element in a specific track using the visitor pattern
2441
2957
  * @param element The updated element
2442
- * @returns boolean true if element was updated successfully
2958
+ * @returns TrackElement the updated element
2443
2959
  */
2444
2960
  updateElement(element) {
2445
2961
  const track = this.getTrackById(element.getTrackId());
2446
2962
  if (!track) {
2447
- return false;
2963
+ return element;
2448
2964
  }
2449
2965
  try {
2450
2966
  const elementUpdater = new ElementUpdater(track);
@@ -2455,9 +2971,9 @@ class TimelineEditor {
2455
2971
  this.setTimelineData(currentData.tracks);
2456
2972
  }
2457
2973
  }
2458
- return result;
2974
+ return element;
2459
2975
  } catch (error) {
2460
- return false;
2976
+ return element;
2461
2977
  }
2462
2978
  }
2463
2979
  /**
@@ -7270,6 +7786,7 @@ const TimelineContext = createContext(
7270
7786
  const TimelineProviderInner = ({
7271
7787
  contextId,
7272
7788
  children,
7789
+ resolution,
7273
7790
  initialData
7274
7791
  }) => {
7275
7792
  const [timelineAction, setTimelineActionState] = useState({
@@ -7279,6 +7796,7 @@ const TimelineProviderInner = ({
7279
7796
  const [selectedItem, setSelectedItem] = useState(
7280
7797
  null
7281
7798
  );
7799
+ const [videoResolution, setVideoResolution] = useState(resolution ?? { width: 720, height: 1280 });
7282
7800
  const [totalDuration, setTotalDuration] = useState(0);
7283
7801
  const [changeLog, setChangeLog] = useState(0);
7284
7802
  const undoRedoContext = useUndoRedo();
@@ -7333,9 +7851,11 @@ const TimelineProviderInner = ({
7333
7851
  timelineAction,
7334
7852
  totalDuration,
7335
7853
  changeLog,
7854
+ videoResolution,
7336
7855
  present: undoRedoContext.present,
7337
7856
  canUndo: undoRedoContext.canUndo,
7338
7857
  canRedo: undoRedoContext.canRedo,
7858
+ setVideoResolution,
7339
7859
  setSelectedItem,
7340
7860
  setTimelineAction,
7341
7861
  editor
@@ -7346,6 +7866,7 @@ const TimelineProviderInner = ({
7346
7866
  const TimelineProvider = ({
7347
7867
  contextId,
7348
7868
  children,
7869
+ resolution = { width: 720, height: 1280 },
7349
7870
  initialData,
7350
7871
  undoRedoPersistenceKey,
7351
7872
  maxHistorySize
@@ -7367,6 +7888,7 @@ const TimelineProvider = ({
7367
7888
  children: /* @__PURE__ */ jsx(
7368
7889
  TimelineProviderInner,
7369
7890
  {
7891
+ resolution,
7370
7892
  initialData,
7371
7893
  contextId,
7372
7894
  undoRedoPersistenceKey,