lexgui 0.7.10 → 0.7.12

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.
@@ -6,6 +6,9 @@ if(!LX) {
6
6
 
7
7
  LX.extensions.push( 'Timeline' );
8
8
 
9
+ LX.registerIcon("TimelineLock", '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path fill="none" d="M7 11V7a4 4 0 0 1 9 0v4 M5,11h13 a2 2 0 0 1 2 2 v7 a2 2 0 0 1 -2 2 h-13 a2 2 0 0 1 -2 -2 v-7 a2 2 0 0 1 2 -2 M12 16 v2"/></svg>' );
10
+ LX.registerIcon("TimelineLockOpen", '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path fill="none" d="M14 11V7a4 4 0 0 1 9 0v2 M3,11h13 a2 2 0 0 1 2 2 v7 a2 2 0 0 1 -2 2 h-13 a2 2 0 0 1 -2 -2 v-7 a2 2 0 0 1 2 -2 M8 17 h3"/></svg>' );
11
+
9
12
  /**
10
13
  * @class Timeline
11
14
  * @description Agnostic timeline, do not impose any timeline content. Renders to a canvas
@@ -14,8 +17,8 @@ LX.extensions.push( 'Timeline' );
14
17
  class Timeline {
15
18
 
16
19
  /**
17
- * @param {string} name = string unique id
18
- * @param {object} options = {skipLock, skipVisibility}
20
+ * @param {String} name = string unique id
21
+ * @param {Object} options = {skipLock, skipVisibility}
19
22
  */
20
23
  constructor( id, options = {} ) {
21
24
 
@@ -101,6 +104,7 @@ class Timeline {
101
104
  this.canvasArea = right;
102
105
  this.canvasArea.root.classList.add("lextimelinearea");
103
106
 
107
+ this.selectedTracks = []; // [track, track] contains selected (highlighted) tracks. That is, tracks with .isSelected == true. Elements in array are not ordered. Only visible tracks should be selected
104
108
  this.selectedItems = []; // [trackInfo, "groupId"], contains the visible items (tracks or groups) of the timeline
105
109
  this.leftPanel = left.addPanel( { className: 'lextimelinepanel', width: "100%", height: "100%" } );
106
110
  this.trackTreesPanel = null;
@@ -180,7 +184,6 @@ class Timeline {
180
184
 
181
185
  /**
182
186
  * @method updateHeader
183
- * @param {*}
184
187
  */
185
188
 
186
189
  updateHeader() {
@@ -312,8 +315,7 @@ class Timeline {
312
315
  if ( this.onAddNewTrackButton ){
313
316
  this.onAddNewTrackButton();
314
317
  }else{
315
- const trackIdx = this.addNewTrack();
316
- this.changeSelectedItems( [trackIdx] );
318
+ this.addNewTrack();
317
319
  }
318
320
  }, { hideName: true, title: "Add Track", icon: "Plus" });
319
321
  }
@@ -332,31 +334,37 @@ class Timeline {
332
334
  this.trackTreesComponent = p.addTree(null, treeTracks, {filter: false, rename: false, draggable: false, onevent: (e) => {
333
335
  switch(e.type) {
334
336
  case LX.TreeEvent.NODE_SELECTED:
337
+ if ( !e.event.shiftKey ){
338
+ this.deselectAllTracks( false ); // no need to update left panel
339
+ }
335
340
  if (e.node.trackData){
336
- this.selectTrack(e.node.trackData.trackIdx);
341
+ const flag = e.event.shiftKey? !e.node.trackData.isSelected : true;
342
+ this.setTrackSelection( e.node.trackData.trackIdx, flag, false, false ); // do callback, do not update left panel
337
343
  }
338
344
  break;
339
345
  case LX.TreeEvent.NODE_VISIBILITY:
340
346
  if (e.node.trackData){
341
- this.setTrackState( e.node.trackData.trackIdx, e.value );
347
+ this.setTrackState( e.node.trackData.trackIdx, e.value, false, false ); // do not update left panel
342
348
  }
343
349
  break;
344
- case LX.TreeEvent.NODE_CARETCHANGED:
345
- if ( !this.trackTreesComponent ){ return;}
346
- /* hack
347
- On NODE_CARETCHANGED, the html are regenerated. TrackHeight need to be forced on the htmls again.
348
- This event is called BEFORE the regeneration, so a setTimeout of 1ms is called.
349
- To avoid a flicker, hide the element while it is changing and show it again afterwards
350
- */
351
- this.trackTreesComponent.root.classList.add("hidden");
352
- setTimeout( ()=>{
353
- this.setTrackHeight(this.trackHeight);
354
- this.trackTreesComponent.root.classList.remove("hidden");
355
- }, 1);
350
+ }
356
351
 
357
- break;
352
+ if ( this.onTrackTreeEvent ){
353
+ this.onTrackTreeEvent(e);
358
354
  }
359
355
  }});
356
+
357
+ const that = this;
358
+ this.trackTreesComponent.innerTree._refresh = this.trackTreesComponent.innerTree.refresh;
359
+ this.trackTreesComponent.innerTree.refresh = function( newData, selectedId ){
360
+ this._refresh( newData, selectedId );
361
+ that.setTrackHeight( that.trackHeight );
362
+ }
363
+
364
+ if ( this.selectedTracks.length ){
365
+ this._updateTrackTreeSelection(); // select visible tracks
366
+ }
367
+
360
368
  // setting a name in the addTree function adds an undesired node
361
369
  this.trackTreesComponent.name = "tracksTrees";
362
370
  p.components[this.trackTreesComponent.name] = this.trackTreesComponent;
@@ -382,6 +390,9 @@ class Timeline {
382
390
  }
383
391
 
384
392
  this.resizeCanvas();
393
+
394
+ this.setScroll( this.currentScroll ); // avoid scroll bugs
395
+
385
396
  }
386
397
 
387
398
  setTrackHeight( trackHeight ){
@@ -402,7 +413,7 @@ class Timeline {
402
413
  }
403
414
 
404
415
  /**
405
- * @param {object} options options for the new track
416
+ * @param {Object} options options for the new track
406
417
  * { id: string, active: bool, locked: bool, }
407
418
  * @returns
408
419
  */
@@ -421,8 +432,8 @@ class Timeline {
421
432
  /**
422
433
  * Finds tracks (wholy and partially) inside the range minY maxY.
423
434
  * (Full) Canvas local coordinates.
424
- * @param {number} minY
425
- * @param {number} maxY
435
+ * @param {Number} minY
436
+ * @param {Number} maxY
426
437
  * @returns array of trackDatas
427
438
  */
428
439
  getTracksInRange( minY, maxY ) {
@@ -462,14 +473,14 @@ class Timeline {
462
473
  /**
463
474
  * @method setAnimationClip
464
475
  * @param {*} animation
465
- * @param {boolean} needsToProcess
466
- * @param {obj} processOptions
476
+ * @param {Boolean} needsToProcess
477
+ * @param {Object} processOptions
467
478
  * [KeyFrameTimeline] - each track should contain an attribute "dim" to indicate the value dimension (e.g. vector3 -> dim=3). Otherwise dimensions will be infered from track's values and times. Default is 1
468
479
  */
469
480
  setAnimationClip( animation, needsToProcess = true ) {
470
481
 
471
482
  this.deselectAllElements();
472
- this.deselectAllTracks();
483
+ this.deselectAllTracks( false ); // no need to update left panel yet
473
484
 
474
485
  this.selectedItems = [];
475
486
 
@@ -798,7 +809,7 @@ class Timeline {
798
809
  * @method setScroll
799
810
  * not delta from last state, but full scroll amount.
800
811
  * @param {Number} scrollY either pixels or [0,1]
801
- * @param {Bool} normalized if true, scrollY is in range[0,1] being 1 fully scrolled. Otherwised scrollY represents pixels
812
+ * @param {Boolean} normalized if true, scrollY is in range[0,1] being 1 fully scrolled. Otherwised scrollY represents pixels
802
813
  * @returns
803
814
  */
804
815
 
@@ -926,7 +937,6 @@ class Timeline {
926
937
  this.movingKeys = false;
927
938
  this.timeBeforeMove = null;
928
939
  this.boxSelection = false; // after mouseup
929
- this.deselectAllTracks();
930
940
  }
931
941
 
932
942
 
@@ -1053,7 +1063,7 @@ class Timeline {
1053
1063
 
1054
1064
  /**
1055
1065
  * @method changeState
1056
- * @param {bool} skipCallback defaults false
1066
+ * @param {Boolean} skipCallback defaults false
1057
1067
  * @description change play/pause state
1058
1068
  **/
1059
1069
  changeState(skipCallback = false) {
@@ -1061,8 +1071,8 @@ class Timeline {
1061
1071
  }
1062
1072
  /**
1063
1073
  * @method setState
1064
- * @param {bool} state
1065
- * @param {bool} skipCallback defaults false
1074
+ * @param {Boolean} state
1075
+ * @param {Boolean} skipCallback defaults false
1066
1076
  * @description change play/pause state
1067
1077
  **/
1068
1078
  setState(state, skipCallback = false) {
@@ -1077,8 +1087,8 @@ class Timeline {
1077
1087
 
1078
1088
  /**
1079
1089
  * @method setLoopMode
1080
- * @param {bool} loopState
1081
- * @param {bool} skipCallback defaults false
1090
+ * @param {Boolean} loopState
1091
+ * @param {Boolean} skipCallback defaults false
1082
1092
  * @description change loop mode of the timeline
1083
1093
  */
1084
1094
  setLoopMode(loopState, skipCallback = false){
@@ -1112,11 +1122,14 @@ class Timeline {
1112
1122
  }
1113
1123
 
1114
1124
  /**
1115
- * @param {*} itemsToAdd [ trackIdx ], array of numbers identifying tracks by their index
1116
- * @param {*} itemsToRemove [ trackIdx ], array of numbers identifying tracks by their index
1125
+ * @param {Array} itemsToAdd [ trackIdx ], array of numbers identifying tracks by their index
1126
+ * @param {Array} itemsToRemove [ trackIdx ], array of numbers identifying tracks by their index
1117
1127
  */
1118
1128
  changeSelectedItems( itemsToAdd = null, itemsToRemove = null, skipCallback = false ) {
1119
1129
 
1130
+ this.deselectAllElements();
1131
+ this.deselectAllTracks( false ); // no need to update left panel. It is going to be rebuilt anyways
1132
+
1120
1133
  const tracks = this.animationClip.tracks;
1121
1134
 
1122
1135
  if ( itemsToRemove ){
@@ -1140,7 +1153,6 @@ class Timeline {
1140
1153
  }
1141
1154
  }
1142
1155
 
1143
-
1144
1156
  this.updateLeftPanel();
1145
1157
 
1146
1158
  if(this.onItemSelected && !skipCallback){
@@ -1150,7 +1162,7 @@ class Timeline {
1150
1162
 
1151
1163
  /**
1152
1164
  * It will find the first occurrence of trackId in animationClip.tracks
1153
- * @param {string} trackId
1165
+ * @param {String} trackId
1154
1166
  * @returns
1155
1167
  */
1156
1168
  getTrack( trackId ){
@@ -1164,38 +1176,83 @@ class Timeline {
1164
1176
  }
1165
1177
 
1166
1178
  /**
1167
- * Only affects render visualisation
1168
- * @method selectTrack
1169
- * @param {int} trackIdx
1170
- * // NOTE: to select a track from outside of the timeline, a this.trackTreesComponent.innerTree.select(item) needs to be called.
1171
- */
1172
- selectTrack( trackIdx ) {
1179
+ * @param {Boolean} updateTrackTree whether the track tree needs a refresh
1180
+ * @returns
1181
+ */
1182
+ deselectAllTracks( updateTrackTree = true ) {
1173
1183
 
1174
1184
  if( !this.animationClip ){
1175
1185
  return;
1176
1186
  }
1177
1187
 
1178
- this.deselectAllTracks();
1179
-
1180
- let track = this.animationClip.tracks[ trackIdx ];
1181
- track.isSelected = true;
1182
-
1183
- if( this.onSelectTrack ){
1184
- this.onSelectTrack(track);
1188
+ const tracks = this.animationClip.tracks;
1189
+ for(let i = 0; i < tracks.length; i++){
1190
+ tracks[ i ].isSelected = false;
1191
+ }
1192
+
1193
+ this.selectedTracks.length = 0;
1194
+
1195
+ if ( updateTrackTree ){
1196
+ this._updateTrackTreeSelection();
1185
1197
  }
1186
1198
  }
1187
1199
 
1188
- // Only affects render visualisation
1189
- deselectAllTracks() {
1200
+ /**
1201
+ * @param {Int} trackIdx
1202
+ * @param {Boolean} isSelected new "selected" state of the track
1203
+ * @param {Boolean} skipCallback whether to call onSetTrackSelection
1204
+ * @param {Boolean} updateTrackTree whether track tree panel needs a refresh
1205
+ * @returns
1206
+ */
1207
+ setTrackSelection( trackIdx, isSelected, skipCallback = false, updateTrackTree = true ){
1208
+ const track = this.animationClip.tracks[ trackIdx ];
1209
+ const oldValue = track.isSelected;
1210
+ track.isSelected = isSelected;
1211
+
1212
+ const idx = this.selectedTracks.indexOf( track );
1213
+ if ( ( idx == -1 && !isSelected ) || ( idx > -1 && isSelected ) ){
1214
+ return;
1215
+ }
1190
1216
 
1191
- if( !this.animationClip ){
1192
- return;
1217
+ if ( idx == -1 ){
1218
+ this.selectedTracks.push( track );
1219
+ }else{
1220
+ this.selectedTracks.splice( idx, 1 );
1193
1221
  }
1194
1222
 
1195
- const tracks = this.animationClip.tracks;
1196
- for(let i = 0; i < tracks.length; i++){
1197
- tracks[ i ].isSelected = false;
1223
+ if( this.onSetTrackSelection && !skipCallback ){
1224
+ this.onSetTrackSelection(track, oldValue );
1225
+ }
1226
+
1227
+ if ( updateTrackTree ){
1228
+ this._updateTrackTreeSelection();
1229
+ }
1230
+ }
1231
+
1232
+ /**
1233
+ * updates trackTreesComponent's nodes, to match the selectedTracks
1234
+ */
1235
+ _updateTrackTreeSelection(){
1236
+ const data = this.trackTreesComponent.innerTree.data;
1237
+ const selected = this.trackTreesComponent.innerTree.selected;
1238
+ selected.length = 0;
1239
+
1240
+ const addToSelection = (nodes) =>{
1241
+ for( let i = 0; i < nodes.length; ++i ){
1242
+ if ( nodes[i].trackData && nodes[i].trackData.isSelected ){
1243
+ selected.push( nodes[i] );
1244
+ }
1245
+ if ( nodes[i].children ){
1246
+ addToSelection( nodes[i].children );
1247
+ }
1248
+ }
1249
+ }
1250
+
1251
+ // update innerTree (visible) selected nodes
1252
+ if ( this.selectedTracks.length ){
1253
+ addToSelection( data );
1198
1254
  }
1255
+ this.trackTreesComponent.innerTree.refresh();
1199
1256
  }
1200
1257
 
1201
1258
  deselectAllElements(){
@@ -1204,22 +1261,53 @@ class Timeline {
1204
1261
 
1205
1262
  /**
1206
1263
  * @method setTrackState
1207
- * @param {int} trackIdx
1208
- * @param {boolean} isEnbaled
1264
+ * @param {Int} trackIdx
1265
+ * @param {Boolean} isEnbaled
1266
+ * @param {Boolean} skipCallback onSetTrackState
1267
+ * @param {Boolean} updateTrackTree updates eye icon of the track, if it is visible in the timeline
1209
1268
  */
1210
- setTrackState(trackIdx, isEnbaled = true, skipCallback = false) {
1269
+ setTrackState(trackIdx, isEnbaled = true, skipCallback = false, updateTrackTree = true ) {
1211
1270
  const track = this.animationClip.tracks[trackIdx];
1212
1271
 
1213
1272
  const oldState = track.active;
1214
1273
  track.active = isEnbaled;
1215
1274
 
1216
- if(this.onSetTrackState && !skipCallback)
1275
+ if ( this.onSetTrackState && !skipCallback ){
1217
1276
  this.onSetTrackState(track, oldState);
1277
+ }
1278
+
1279
+ if ( updateTrackTree && !this.skipVisibility ){
1280
+ // TODO: a bit of an overkill. Maybe searching the node in the tree is less expensive
1281
+ this.updateLeftPanel();
1282
+ }
1218
1283
  }
1219
1284
 
1220
1285
  /**
1221
- * @param {Number} trackIdx index of track in the animation (not local index)
1222
- * @param {Bool} combineWithPrevious whether to create a new entry or unify changes into a single undo entry
1286
+ *
1287
+ * @param {Int} trackIdx
1288
+ * @param {Boolean} isLocked
1289
+ * @param {Boolean} skipCallback onSetTrackLock
1290
+ * @param {Boolean} updateTrackTree updates lock icon of the track, if it is visible in the timeline
1291
+ */
1292
+ setTrackLock(trackIdx, isLocked = false, skipCallback = false, updateTrackTree = true ){
1293
+ const track = this.animationClip.tracks[trackIdx];
1294
+
1295
+ const oldState = track.locked;
1296
+ track.locked = isLocked;
1297
+
1298
+ if ( this.onSetTrackLock && !skipCallback ){
1299
+ this.onSetTrackLock( track, oldState );
1300
+ }
1301
+
1302
+ if ( updateTrackTree && !this.skipLock ){
1303
+ // TODO: a bit of an overkill. Maybe searching the node in the tree is less expensive
1304
+ this.updateLeftPanel();
1305
+ }
1306
+ }
1307
+
1308
+ /**
1309
+ * @param {Int} trackIdx index of track in the animation (not local index)
1310
+ * @param {Boolean} combineWithPrevious whether to create a new entry or unify changes into a single undo entry
1223
1311
  */
1224
1312
  saveState( trackIdx, combineWithPrevious = false ) {
1225
1313
  if ( !this.historySaveEnabler ){ return; }
@@ -1332,26 +1420,10 @@ class Timeline {
1332
1420
  const track = this.selectedItems[ i ];
1333
1421
  treeTracks.push({'trackData': track, 'id': track.id, 'skipVisibility': this.skipVisibility, visible: track.active, 'children':[], actions : this.skipLock ? null : [{
1334
1422
  'name':'Lock edition',
1335
- 'icon': (track.locked ? 'Lock' : 'LockOpen'),
1336
- 'swap': (track.locked ? 'LockOpen' : 'Lock'),
1337
- 'callback': (node, el) => {
1338
- let value = el.classList.contains('Lock');
1339
-
1340
- if(value) {
1341
- el.title = 'Lock edition';
1342
- el.classList.remove('Lock');
1343
- el.classList.add('LockOpen');
1344
- }
1345
- else {
1346
- el.title = 'Unlock edition';
1347
- el.classList.remove('LockOpen');
1348
- el.classList.add('Lock');
1349
- }
1350
-
1351
- node.trackData.locked = !value;
1352
- if(this.onLockTrack){
1353
- this.onLockTrack(el, node.trackData, node)
1354
- }
1423
+ 'icon': (track.locked ? 'TimelineLock' : 'TimelineLockOpen'),
1424
+ 'swap': (track.locked ? 'TimelineLockOpen' : 'TimelineLock'),
1425
+ 'callback': (node, swapValue, event) => {
1426
+ this.setTrackLock( node.trackData.trackIdx, !node.trackData.locked, false, false ); // do not update left panel
1355
1427
  }
1356
1428
  }]});
1357
1429
  }
@@ -1361,7 +1433,7 @@ class Timeline {
1361
1433
 
1362
1434
  /**
1363
1435
  *
1364
- * @param {obj} options set some values for the track instance (groups and trackIdx not included)
1436
+ * @param {Object} options set some values for the track instance (groups and trackIdx not included)
1365
1437
  * @returns
1366
1438
  */
1367
1439
  instantiateTrack(options = {}, clone = false) {
@@ -1370,7 +1442,7 @@ class Timeline {
1370
1442
  id: options.id ?? ( Math.floor(performance.now().toString()) + "_" + Math.floor(Math.random() * 0xffff) ), //must be unique, at least inside a group
1371
1443
  active: options.active ?? true,
1372
1444
  locked: options.locked ?? false,
1373
- isSelected: false, // render only
1445
+ isSelected: false, // render and lexgui tree
1374
1446
  trackIdx: -1,
1375
1447
  data: options.data ?? null // user defined data
1376
1448
  }
@@ -1378,7 +1450,7 @@ class Timeline {
1378
1450
 
1379
1451
  /**
1380
1452
  * Generates an animationClip using either the parameters set in the animation argument or using default values
1381
- * @param {obj} animation data with which to generate an animationClip
1453
+ * @param {Object} animation data with which to generate an animationClip
1382
1454
  * @returns
1383
1455
  */
1384
1456
  instantiateAnimationClip(options, clone = false) {
@@ -1430,8 +1502,8 @@ class KeyFramesTimeline extends Timeline {
1430
1502
 
1431
1503
  static ADDKEY_VALUESINARRAYS = 0x01; // addkeyframes as [ [k0v0, k0v1...], [k1v0, k1v1...] ] instead of [k0v0,k0v1,k1v0,k1v1]
1432
1504
  /**
1433
- * @param {string} name unique string
1434
- * @param {object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
1505
+ * @param {String} name unique string
1506
+ * @param {Object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
1435
1507
  */
1436
1508
  constructor(name, options = {}) {
1437
1509
 
@@ -1466,26 +1538,10 @@ class KeyFramesTimeline extends Timeline {
1466
1538
  const track = itemTracks[j];
1467
1539
  nodes.push({'trackData': track, 'id': track.id, 'skipVisibility': this.skipVisibility, visible: track.active, 'children':[], actions : this.skipLock ? null : [{
1468
1540
  'name':'Lock edition',
1469
- 'icon': (track.locked ? 'Lock' : 'LockOpen'),
1470
- 'swap': (track.locked ? 'LockOpen' : 'Lock'),
1471
- 'callback': (node, el) => {
1472
- let value = el.classList.contains('Lock');
1473
-
1474
- if(value) {
1475
- el.title = 'Lock edition';
1476
- el.classList.remove('Lock');
1477
- el.classList.add('LockOpen');
1478
- }
1479
- else {
1480
- el.title = 'Unlock edition';
1481
- el.classList.remove('LockOpen');
1482
- el.classList.add('Lock');
1483
- }
1484
-
1485
- node.trackData.locked = !value;
1486
- if(this.onLockTrack){
1487
- this.onLockTrack(el, node.trackData, node)
1488
- }
1541
+ 'icon': (track.locked ? 'TimelineLock' : 'TimelineLockOpen'),
1542
+ 'swap': (track.locked ? 'TimelineLockOpen' : 'TimelineLock'),
1543
+ 'callback': (node, swapValue, event) => {
1544
+ this.setTrackLock( node.trackData.trackIdx, !node.trackData.locked, false, false ); // do not update left panel
1489
1545
  }
1490
1546
  }]});
1491
1547
  }
@@ -1505,7 +1561,7 @@ class KeyFramesTimeline extends Timeline {
1505
1561
 
1506
1562
  /**
1507
1563
  * OVERRIDE
1508
- * @param {obj} options track information that wants to be set to the new track
1564
+ * @param {Object} options track information that wants to be set to the new track
1509
1565
  * id, dim, values, times, selected, edited, hovered
1510
1566
  * @returns
1511
1567
  */
@@ -1550,7 +1606,7 @@ class KeyFramesTimeline extends Timeline {
1550
1606
 
1551
1607
  /**
1552
1608
  * Generates an animationClip using either the parameters set in the animation argument or using default values
1553
- * @param {obj} animation data with which to generate an animationClip
1609
+ * @param {Object} animation data with which to generate an animationClip
1554
1610
  * @returns
1555
1611
  */
1556
1612
  instantiateAnimationClip(animation, clone = false) {
@@ -1639,12 +1695,13 @@ class KeyFramesTimeline extends Timeline {
1639
1695
 
1640
1696
  /**
1641
1697
  * OVERRIDE
1642
- * @param {*} itemsToAdd [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
1643
- * @param {*} itemsToRemove [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
1698
+ * @param {Array} itemsToAdd [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
1699
+ * @param {Array} itemsToRemove [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
1644
1700
  */
1645
1701
  changeSelectedItems( itemsToAdd = null, itemsToRemove = null, skipCallback = false ) {
1646
1702
 
1647
1703
  this.deselectAllElements();
1704
+ this.deselectAllTracks( false ); // no need to update left panel. It is going to be rebuilt anyways
1648
1705
 
1649
1706
  const tracks = this.animationClip.tracks;
1650
1707
  const tracksPerGroup = this.animationClip.tracksPerGroup;
@@ -1675,7 +1732,6 @@ class KeyFramesTimeline extends Timeline {
1675
1732
  }
1676
1733
  }
1677
1734
  }
1678
-
1679
1735
 
1680
1736
  this.updateLeftPanel();
1681
1737
 
@@ -1685,8 +1741,8 @@ class KeyFramesTimeline extends Timeline {
1685
1741
  }
1686
1742
 
1687
1743
  /**
1688
- * @param {string} groupId unique identifier
1689
- * @param {array} groupTracks [ "trackID", trackIdx ] array of strings and/or numbers of the existing tracks to include in this group. A track can only be part of 1 group
1744
+ * @param {String} groupId unique identifier
1745
+ * @param {Array} groupTracks [ "trackID", trackIdx ] array of strings and/or numbers of the existing tracks to include in this group. A track can only be part of 1 group
1690
1746
  * if groupTracks == null, groupId is removed from the list
1691
1747
  */
1692
1748
  setTracksGroup( groupId, groupTracks = null ){
@@ -1754,7 +1810,7 @@ class KeyFramesTimeline extends Timeline {
1754
1810
  }
1755
1811
 
1756
1812
  /**
1757
- * @param {string} groupId
1813
+ * @param {String} groupId
1758
1814
  * @returns array of tracks or null
1759
1815
  */
1760
1816
  getTracksGroup( groupId ){
@@ -1763,8 +1819,8 @@ class KeyFramesTimeline extends Timeline {
1763
1819
 
1764
1820
  /**
1765
1821
  * OVERRIDE
1766
- * @param {string} trackId
1767
- * @param {string} groupId optionl. If not set, it will find the first occurrence of trackId in animationClip.tracks
1822
+ * @param {String} trackId
1823
+ * @param {String} groupId optionl. If not set, it will find the first occurrence of trackId in animationClip.tracks
1768
1824
  * @returns
1769
1825
  */
1770
1826
  getTrack( trackId, groupId = null ){
@@ -1782,8 +1838,8 @@ class KeyFramesTimeline extends Timeline {
1782
1838
 
1783
1839
  /**
1784
1840
  *
1785
- * @param {number} size pixels, height of keyframe
1786
- * @param {number} sizeHovered optional, size in pixels when hovered
1841
+ * @param {Number} size pixels, height of keyframe
1842
+ * @param {Number} sizeHovered optional, size in pixels when hovered
1787
1843
  */
1788
1844
  setKeyframeSize( size, sizeHovered = null ){
1789
1845
  this.keyframeSizeHovered = sizeHovered ?? size;
@@ -1799,7 +1855,8 @@ class KeyFramesTimeline extends Timeline {
1799
1855
  if(e.shiftKey) {
1800
1856
  // Manual multiple selection
1801
1857
  if(!discard && track) {
1802
- const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
1858
+ const thresholdPixels = this.keyframeSize * 0.5; // radius of circle (curves) or rotated square (keyframes)
1859
+ const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels );
1803
1860
  if ( keyFrameIdx > -1 ){
1804
1861
  track.selected[keyFrameIdx] ?
1805
1862
  this.deselectKeyFrame(track.trackIdx, keyFrameIdx) :
@@ -1833,7 +1890,8 @@ class KeyFramesTimeline extends Timeline {
1833
1890
  this.deselectAllKeyFrames();
1834
1891
  }
1835
1892
  if (track){
1836
- const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
1893
+ const thresholdPixels = this.keyframeSize * 0.5; // radius of circle (curves) or rotated square (keyframes)
1894
+ const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels );
1837
1895
  if( keyFrameIndex > -1 ) {
1838
1896
  this.processSelectionKeyFrame( track.trackIdx, keyFrameIndex, false ); // Settings this as multiple so time is not being set
1839
1897
  }
@@ -1850,7 +1908,7 @@ class KeyFramesTimeline extends Timeline {
1850
1908
  let localY = e.localY;
1851
1909
  let track = e.track;
1852
1910
 
1853
- if(e.ctrlKey && this.lastKeyFramesSelected.length) { // move keyframes
1911
+ if( (e.ctrlKey || e.altKey) && this.lastKeyFramesSelected.length) { // move keyframes
1854
1912
  this.movingKeys = true;
1855
1913
  this.canvas.style.cursor = "grab";
1856
1914
  this.canvas.classList.add('grabbing');
@@ -1858,31 +1916,28 @@ class KeyFramesTimeline extends Timeline {
1858
1916
  // Set pre-move state
1859
1917
  this.moveKeyMinTime = Infinity;
1860
1918
  const tracks = this.animationClip.tracks;
1861
- for(let selectedKey of this.lastKeyFramesSelected) {
1919
+ let lastTrackIdx = -1;
1920
+ for(let selectedKey of this.lastKeyFramesSelected) { // WARNING assumes lasKeyFramesSelected is sorted, so all keyframes of the same track are grouped
1862
1921
  let [trackIdx, keyIndex, keyTime] = selectedKey;
1863
1922
  const track = tracks[trackIdx];
1923
+
1924
+ selectedKey[2] = track.times[keyIndex]; // update original time just in case
1864
1925
 
1865
- // save track states only once
1866
- if (this.moveKeyMinTime < Infinity){
1867
- let state = this.historyUndo[this.historyUndo.length-1];
1868
- let s = 0;
1869
- for( s = 0; s < state.length; ++s){
1870
- if ( state[s].trackIdx == track.trackIdx ){ break; }
1871
- }
1872
- if( s == state.length ){
1926
+ if ( lastTrackIdx != trackIdx ){
1927
+ // save track states only once
1928
+ if (this.moveKeyMinTime < Infinity){
1873
1929
  this.saveState(track.trackIdx, true);
1930
+ }else{
1931
+ this.saveState(track.trackIdx, false);
1874
1932
  }
1875
- }else{
1876
- this.saveState(track.trackIdx, false);
1933
+ this.moveKeyMinTime = Math.min( this.moveKeyMinTime, selectedKey[2] );
1934
+ lastTrackIdx = trackIdx;
1877
1935
  }
1878
1936
 
1879
- selectedKey[2] = track.times[keyIndex]; // update original time just in case
1880
- this.moveKeyMinTime = Math.min( this.moveKeyMinTime, selectedKey[2] );
1881
1937
  }
1882
1938
 
1883
1939
  this.timeBeforeMove = this.xToTime( localX );
1884
- }
1885
- else if( e.altKey ){ // if only altkey, do not grab timeline
1940
+
1886
1941
  this.grabbing = false;
1887
1942
  this.grabbingTimeBar = false;
1888
1943
  }
@@ -1957,47 +2012,45 @@ class KeyFramesTimeline extends Timeline {
1957
2012
  }
1958
2013
  }
1959
2014
  }
1960
- if ( !e.altKey || !(e.buttons & 0x01) ){
1961
- return;
1962
- }
1963
- }
1964
-
1965
- // Track.dim == 1: move keyframes vertically (change values instead of time)
1966
- // RELIES ON SORTED ARRAY OF lastKeyFramesSelected
1967
- if ( e.altKey && e.buttons & 0x01 ){
1968
- const tracks = this.animationClip.tracks;
1969
- let lastTrackChanged = -1;
1970
- for( let i = 0; i < this.lastKeyFramesSelected.length; ++i ){
1971
- const [trackIdx, keyIndex, originalKeyTime] = this.lastKeyFramesSelected[i];
1972
- track = tracks[trackIdx];
1973
- if(track.locked || track.dim != 1 || !track.curves){
1974
- continue;
2015
+
2016
+ // Track.dim == 1: move keyframes vertically (change values instead of time)
2017
+ // RELIES ON SORTED ARRAY OF lastKeyFramesSelected
2018
+ if ( e.altKey && e.buttons & 0x01 ){
2019
+ const tracks = this.animationClip.tracks;
2020
+ let lastTrackChanged = -1;
2021
+ for( let i = 0; i < this.lastKeyFramesSelected.length; ++i ){
2022
+ const [trackIdx, keyIndex, originalKeyTime] = this.lastKeyFramesSelected[i];
2023
+ track = tracks[trackIdx];
2024
+ if(track.locked || track.dim != 1 || !track.curves){
2025
+ continue;
2026
+ }
2027
+
2028
+ let value = track.values[keyIndex];
2029
+ let delta = e.deltay * this.keyValuePerPixel * (track.curvesRange[1]-track.curvesRange[0]);
2030
+ track.values[keyIndex] = Math.max(track.curvesRange[0], Math.min(track.curvesRange[1], value - delta)); // invert delta because of screen y
2031
+ track.edited[keyIndex] = true;
2032
+
2033
+ if ( this.onUpdateTrack && track.trackIdx != lastTrackChanged && lastTrackChanged > -1){ // do it only once all keyframes of the same track have been modified
2034
+ this.onUpdateTrack( [track.trackIdx] );
2035
+ }
2036
+ lastTrackChanged = track.trackIdx;
1975
2037
  }
1976
-
1977
- let value = track.values[keyIndex];
1978
- let delta = e.deltay * this.keyValuePerPixel * (track.curvesRange[1]-track.curvesRange[0]);
1979
- track.values[keyIndex] = Math.max(track.curvesRange[0], Math.min(track.curvesRange[1], value - delta)); // invert delta because of screen y
1980
- track.edited[keyIndex] = true;
1981
-
1982
- if ( this.onUpdateTrack && track.trackIdx != lastTrackChanged && lastTrackChanged > -1){ // do it only once all keyframes of the same track have been modified
2038
+ if( this.onUpdateTrack && lastTrackChanged > -1 ){ // do the last update, once the last track has been processed
1983
2039
  this.onUpdateTrack( [track.trackIdx] );
1984
2040
  }
1985
- lastTrackChanged = track.trackIdx;
1986
- }
1987
- if( this.onUpdateTrack && lastTrackChanged > -1 ){ // do the last update, once the last track has been processed
1988
- this.onUpdateTrack( [track.trackIdx] );
2041
+ return;
1989
2042
  }
1990
- return;
1991
2043
  }
1992
2044
 
2045
+
1993
2046
  if( this.grabbing && e.button != 2) {
1994
2047
 
1995
2048
  }
1996
2049
  else if(track) {
1997
2050
 
1998
2051
  this.unHoverAll();
1999
- const thresholdPixels = track.curves? this.keyframeSize : (Math.SQRT2 * this.keyframeSize);
2000
- let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels * 0.5 );
2052
+ const thresholdPixels = this.keyframeSize * 0.5; // radius of circle (curves) or rotated square (keyframes)
2053
+ let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels );
2001
2054
  if(keyFrameIndex > -1 ) {
2002
2055
  if(track && track.locked)
2003
2056
  return;
@@ -2166,8 +2219,8 @@ class KeyFramesTimeline extends Timeline {
2166
2219
  const keyframes = track.times;
2167
2220
  const startTime = this.visualTimeRange[0];
2168
2221
  const endTime = this.visualTimeRange[1] + 0.0000001;
2169
- const defaultPointSize = Math.SQRT2 * this.keyframeSize * 0.5; // pythagoras with equal sides h2 = c2 + c2 = 2 * c2
2170
- const hoverPointSize = Math.SQRT2 * this.keyframeSizeHovered * 0.5;
2222
+ const defaultPointSize = this.keyframeSize / Math.SQRT2; // pythagoras with equal sides h2 = c2 + c2 = 2 * c2
2223
+ const hoverPointSize = this.keyframeSizeHovered / Math.SQRT2;
2171
2224
 
2172
2225
  for(let j = 0; j < keyframes.length; ++j)
2173
2226
  {
@@ -2326,7 +2379,7 @@ class KeyFramesTimeline extends Timeline {
2326
2379
 
2327
2380
  /**
2328
2381
  * updates an existing track with new values and times.
2329
- * @param {Integer} trackIdx index of track in the animationClip
2382
+ * @param {Int} trackIdx index of track in the animationClip
2330
2383
  * @param {*} newTrack object with two arrays: values and times. These will be set to the selected track
2331
2384
  * @returns
2332
2385
  */
@@ -2348,8 +2401,8 @@ class KeyFramesTimeline extends Timeline {
2348
2401
  * removes equivalent sequential keys either because of equal times or values
2349
2402
  * (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
2350
2403
  * @param {Int} trackIdx index of track in the animation
2351
- * @param {Bool} onlyEqualTime if true, removes only keyframes with equal times. Otherwise, values are ALSO compared through the class threshold
2352
- * @param {Bool} skipCallback if false, triggers "onOptimizeTracks" after optimizing
2404
+ * @param {Boolean} onlyEqualTime if true, removes only keyframes with equal times. Otherwise, values are ALSO compared through the class threshold
2405
+ * @param {Boolean} skipCallback if false, triggers "onOptimizeTracks" after optimizing
2353
2406
  */
2354
2407
  optimizeTrack(trackIdx, onlyEqualTime = false, skipCallback = false ) {
2355
2408
  if ( !this.animationClip ){ return; }
@@ -2359,6 +2412,11 @@ class KeyFramesTimeline extends Timeline {
2359
2412
  values = track.values,
2360
2413
  stride = track.dim,
2361
2414
  threshold = this.optimizeThreshold;
2415
+
2416
+ if ( track.locked ){
2417
+ return;
2418
+ }
2419
+
2362
2420
  let cmpFunction = (v, p, n, t) => { return Math.abs(v - p) >= t || Math.abs(v - n) >= t };
2363
2421
  let lastSavedIndex = 0;
2364
2422
  const lastIndex = times.length-1;
@@ -2701,7 +2759,7 @@ class KeyFramesTimeline extends Timeline {
2701
2759
 
2702
2760
  pasteKeyFrameValue( track, index ) {
2703
2761
 
2704
- if(this.clipboard.value.type != track.type){
2762
+ if(track.locked || this.clipboard.value.type != track.type){
2705
2763
  return;
2706
2764
  }
2707
2765
 
@@ -2746,6 +2804,10 @@ class KeyFramesTimeline extends Timeline {
2746
2804
  const values = clipboardInfo.values;
2747
2805
  const track = this.animationClip.tracks[trackIdx];
2748
2806
 
2807
+ if( track.locked ){
2808
+ continue;
2809
+ }
2810
+
2749
2811
  this.saveState(track.trackIdx, trackCount++);
2750
2812
  this.historySaveEnabler = false;
2751
2813
  this.addKeyFrames( track.trackIdx, values, times, -globalStart + pasteTime, KeyFramesTimeline.ADDKEY_VALUESINARRAYS );
@@ -2763,11 +2825,11 @@ class KeyFramesTimeline extends Timeline {
2763
2825
 
2764
2826
  /**
2765
2827
  *
2766
- * @param {int} trackIdx
2767
- * @param {array} newValues array of values for each keyframe. It should be a flat array of size track.dim*numKeyframes. Check ADDKEY_VALUESINARRAYS flag
2768
- * @param {array of numbers} newTimes must be ordered ascendently
2769
- * @param {number} timeOffset
2770
- * @param {int} flags
2828
+ * @param {Int} trackIdx
2829
+ * @param {Array} newValues array of values for each keyframe. It should be a flat array of size track.dim*numKeyframes. Check ADDKEY_VALUESINARRAYS flag
2830
+ * @param {Array of numbers} newTimes must be ordered ascendently
2831
+ * @param {Number} timeOffset
2832
+ * @param {Int} flags
2771
2833
  * KeyFramesTimeline.ADDKEY_VALUESINARRAYS: if set, newValues is an array of arrays, one for each entry [ [1,2,3], [5,6,7] ]. Times is still a flat array of values [ 0, 0.2 ]
2772
2834
 
2773
2835
  * @returns
@@ -2775,7 +2837,7 @@ class KeyFramesTimeline extends Timeline {
2775
2837
  addKeyFrames( trackIdx, newValues, newTimes, timeOffset = 0, flags = 0x00 ){
2776
2838
  const track = this.animationClip.tracks[trackIdx];
2777
2839
 
2778
- if ( !newTimes.length ){ return; }
2840
+ if ( !newTimes.length || track.locked ){ return null; }
2779
2841
 
2780
2842
  const valueDim = track.dim;
2781
2843
  const trackTimes = track.times;
@@ -2865,6 +2927,7 @@ class KeyFramesTimeline extends Timeline {
2865
2927
  return;
2866
2928
  }
2867
2929
 
2930
+ const tracks = this.animationClip.tracks;
2868
2931
  const firstTrack = this.lastKeyFramesSelected[0][0];
2869
2932
  let trackToRemove = firstTrack;
2870
2933
  let toDelete = []; // indices to delete of the same track
@@ -2874,6 +2937,12 @@ class KeyFramesTimeline extends Timeline {
2874
2937
  const numSelected = this.lastKeyFramesSelected.length;
2875
2938
  for( let i = 0; i < numSelected; ++i ){
2876
2939
  const [trackIdx, frameIdx] = this.lastKeyFramesSelected[i];
2940
+
2941
+ if ( tracks[trackIdx].locked ){
2942
+ tracks[trackIdx].selected[frameIdx] = false; // unselect
2943
+ continue;
2944
+ }
2945
+
2877
2946
  if ( trackToRemove != trackIdx ){
2878
2947
  this.saveState(trackToRemove, trackToRemove != firstTrack);
2879
2948
 
@@ -2885,7 +2954,7 @@ class KeyFramesTimeline extends Timeline {
2885
2954
  toDelete.length = 0;
2886
2955
  }
2887
2956
 
2888
- toDelete.push(frameIdx)
2957
+ toDelete.push( frameIdx );
2889
2958
  }
2890
2959
 
2891
2960
  this.saveState(trackToRemove, trackToRemove != firstTrack);
@@ -2900,7 +2969,7 @@ class KeyFramesTimeline extends Timeline {
2900
2969
  deleteKeyFrames( trackIdx, indices, skipCallback = false ){
2901
2970
  const track = this.animationClip.tracks[trackIdx];
2902
2971
 
2903
- if ( !indices.length ){
2972
+ if ( !indices.length || track.locked ){
2904
2973
  return false;
2905
2974
  }
2906
2975
 
@@ -2949,9 +3018,9 @@ class KeyFramesTimeline extends Timeline {
2949
3018
 
2950
3019
  /**
2951
3020
  * Binary search. Relies on track.times being a sorted array
2952
- * @param {object} track
2953
- * @param {number} time
2954
- * @param {number} mode on of the possible values
3021
+ * @param {Object} track
3022
+ * @param {Number} time
3023
+ * @param {Number} mode on of the possible values
2955
3024
  * - -1 = nearest frame with t[f] <= time
2956
3025
  * - 0 = nearest frame
2957
3026
  * - 1 = nearest frame with t[f] >= time
@@ -2993,9 +3062,9 @@ class KeyFramesTimeline extends Timeline {
2993
3062
 
2994
3063
  /**
2995
3064
  * get the nearest keyframe to "time" given a maximum threshold.
2996
- * @param {object} track
2997
- * @param {number} time
2998
- * @param {number} threshold must be positive value
3065
+ * @param {Object} track
3066
+ * @param {Number} time
3067
+ * @param {Number} threshold must be positive value
2999
3068
  * @returns returns a postive/zero value if there is a frame inside the threshold range. Otherwise, -1
3000
3069
  */
3001
3070
  getCurrentKeyFrame( track, time, threshold = 0.0 ) {
@@ -3013,10 +3082,10 @@ class KeyFramesTimeline extends Timeline {
3013
3082
 
3014
3083
  /**
3015
3084
  * Returns the interval of frames between minTime and maxTime (both included)
3016
- * @param {object} track
3017
- * @param {number} minTime
3018
- * @param {number} maxTime
3019
- * @param {number} threshold must be positive value
3085
+ * @param {Object} track
3086
+ * @param {Number} minTime
3087
+ * @param {Number} maxTime
3088
+ * @param {Number} threshold must be positive value
3020
3089
  * @returns an array with two values [ minFrame, maxFrame ]. Otherwise null
3021
3090
  */
3022
3091
  getKeyFramesInRange( track, minTime, maxTime, threshold = 0.0 ) {
@@ -3149,12 +3218,12 @@ class KeyFramesTimeline extends Timeline {
3149
3218
 
3150
3219
  const track = this.animationClip.tracks[trackIdx];
3151
3220
 
3152
- if(track.locked ){
3153
- return;
3154
- }
3155
-
3156
3221
  this.unHoverAll();
3157
3222
  this.deselectAllKeyFrames();
3223
+
3224
+ if( track.locked ){
3225
+ return;
3226
+ }
3158
3227
 
3159
3228
  this.saveState(track.trackIdx);
3160
3229
 
@@ -3180,8 +3249,8 @@ class ClipsTimeline extends Timeline {
3180
3249
  static CLONEREASON_TRACKCLONE = 4;
3181
3250
 
3182
3251
  /**
3183
- * @param {string} name
3184
- * @param {object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
3252
+ * @param {String} name
3253
+ * @param {Object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
3185
3254
  */
3186
3255
  constructor(name, options = {}) {
3187
3256
 
@@ -3196,7 +3265,7 @@ class ClipsTimeline extends Timeline {
3196
3265
 
3197
3266
  /**
3198
3267
  * Generates an animationClip using either the parameters set in the animation argument or using default values
3199
- * @param {obj} animation data with which to generate an animationClip
3268
+ * @param {Object} animation data with which to generate an animationClip
3200
3269
  * @returns
3201
3270
  */
3202
3271
  instantiateAnimationClip(animation, clone = false) {
@@ -3218,7 +3287,7 @@ class ClipsTimeline extends Timeline {
3218
3287
 
3219
3288
  /**
3220
3289
  *
3221
- * @param {obj} options set some values for the track instance (groups and trackIdx not included)
3290
+ * @param {Object} options set some values for the track instance (groups and trackIdx not included)
3222
3291
  * @returns
3223
3292
  */
3224
3293
  instantiateTrack(options = {}, clone = false) {
@@ -3319,6 +3388,7 @@ class ClipsTimeline extends Timeline {
3319
3388
  changeSelectedItems( ) {
3320
3389
 
3321
3390
  this.deselectAllElements();
3391
+ this.deselectAllTracks( false ); // no need to update left
3322
3392
 
3323
3393
  this.selectedItems = this.animationClip.tracks.slice();
3324
3394
 
@@ -3950,10 +4020,10 @@ class ClipsTimeline extends Timeline {
3950
4020
 
3951
4021
  /**
3952
4022
  *
3953
- * @param {obj} clip clip to be added
3954
- * @param {int} trackIdx (optional) track where to put the clip. -1 will find the first free slot. ***WARNING*** Must call getClipsInRange, before calling this function with a valid trackdIdx
3955
- * @param {float} offsetTime (optional) offset time of current time
3956
- * @param {float} searchStartTrackIdx (optional) if trackIdx is set to -1, this idx will be used as the starting point to find a valid track
4023
+ * @param {Object} clip clip to be added
4024
+ * @param {Int} trackIdx (optional) track where to put the clip. -1 will find the first free slot. ***WARNING*** Must call getClipsInRange, before calling this function with a valid trackdIdx
4025
+ * @param {Number} offsetTime (optional) offset time of current time
4026
+ * @param {Number} searchStartTrackIdx (optional) if trackIdx is set to -1, this idx will be used as the starting point to find a valid track
3957
4027
  * @returns a zero/positive value if successful. Otherwise, -1
3958
4028
  */
3959
4029
  addClip( clip, trackIdx = -1, offsetTime = 0, searchStartTrackIdx = 0 ) {
@@ -4200,8 +4270,8 @@ class ClipsTimeline extends Timeline {
4200
4270
  /**
4201
4271
  * User defined. Used when copying and pasting
4202
4272
  * @param {Array of clips} clipsToClone array of original clips. Do not modify clips in this array
4203
- * @param {float} timeOffset Value of time that should be added (or subtracted) from the timing attributes
4204
- * @param {int} reason Flag to signal the reason of the clone
4273
+ * @param {Number} timeOffset Value of time that should be added (or subtracted) from the timing attributes
4274
+ * @param {Int} reason Flag to signal the reason of the clone
4205
4275
  * @returns {Array of clips}
4206
4276
  */
4207
4277
  cloneClips( clipsToClone, timeOffset, reason = 0 ){