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