@checksub_team/peaks_timeline 2.3.0-alpha.3 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checksub_team/peaks_timeline",
3
- "version": "2.3.0-alpha.3",
3
+ "version": "2.3.1",
4
4
  "description": "JavaScript UI component for displaying audio waveforms",
5
5
  "main": "./peaks.js",
6
6
  "types": "./peaks.js.d.ts",
package/peaks.js CHANGED
@@ -19663,6 +19663,7 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19663
19663
  this._rescaleVersion = 0;
19664
19664
  this._throttledBatchDraw = this._invoker.throttleTrailing(this._layer.batchDraw.bind(this._layer));
19665
19665
  this._drawPending = false;
19666
+ this._suppressDragLifecycleForSourceId = null;
19666
19667
  this._peaks.on('handler.sources.add', this._onSourcesAdd.bind(this));
19667
19668
  this._peaks.on('handler.sources.destroy', this._onSourcesDestroy.bind(this));
19668
19669
  this._peaks.on('handler.sources.show', this._onSourcesShow.bind(this));
@@ -19727,9 +19728,22 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19727
19728
  var redraw = false;
19728
19729
  var isSourceGroupHovered = false;
19729
19730
  var isSourceGroupDragged = false;
19731
+ var isActiveDraggedSource = false;
19732
+ var draggedAbsPos = null;
19733
+ var pointerPos = null;
19730
19734
  if (sourceGroup) {
19731
19735
  isSourceGroupHovered = sourceGroup.isHovered();
19732
19736
  isSourceGroupDragged = sourceGroup.isDragged();
19737
+ isActiveDraggedSource = Boolean(isSourceGroupDragged && this.isDragInProgress() && this._draggedElementId && this._draggedElementId === source.id);
19738
+ if (isSourceGroupDragged) {
19739
+ draggedAbsPos = sourceGroup.absolutePosition();
19740
+ }
19741
+ if (isActiveDraggedSource) {
19742
+ pointerPos = this._view.getPointerPosition();
19743
+ this._suppressDragLifecycleForSourceId = source.id;
19744
+ sourceGroup.stopDrag();
19745
+ sourceGroup.setDragging(false);
19746
+ }
19733
19747
  this._destroySourceGroup(source);
19734
19748
  redraw = true;
19735
19749
  }
@@ -19739,10 +19753,27 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19739
19753
  newSourceGroup.startHover();
19740
19754
  }
19741
19755
  if (isSourceGroupDragged) {
19742
- newSourceGroup.startDrag();
19756
+ newSourceGroup.setDragging(true);
19757
+ if (draggedAbsPos) {
19758
+ newSourceGroup.moveTo(this._dragGroup);
19759
+ newSourceGroup.absolutePosition(draggedAbsPos);
19760
+ }
19761
+ if (isActiveDraggedSource) {
19762
+ if (pointerPos && draggedAbsPos) {
19763
+ this._dragOffsetX = draggedAbsPos.x - pointerPos.x;
19764
+ this._dragOffsetY = draggedAbsPos.y - pointerPos.y;
19765
+ } else {
19766
+ this._dragOffsetX = undefined;
19767
+ this._dragOffsetY = undefined;
19768
+ }
19769
+ newSourceGroup.startDrag();
19770
+ }
19743
19771
  }
19744
19772
  redraw = true;
19745
19773
  }
19774
+ if (this._suppressDragLifecycleForSourceId === source.id) {
19775
+ this._suppressDragLifecycleForSourceId = null;
19776
+ }
19746
19777
  if (redraw) {
19747
19778
  this.updateSources(frameStartTime, frameEndTime);
19748
19779
  }
@@ -19850,9 +19881,25 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19850
19881
  this.batchDraw();
19851
19882
  }
19852
19883
  };
19884
+ SourcesLayer.prototype.isDragInProgress = function () {
19885
+ return this._draggedElements && this._draggedElements.length > 0;
19886
+ };
19887
+ SourcesLayer.prototype.cleanupAfterDrag = function () {
19888
+ this._initialSourcePositions = null;
19889
+ this._dragOffsetX = undefined;
19890
+ this._dragOffsetY = undefined;
19891
+ this._draggedElements = null;
19892
+ this._draggedElementId = null;
19893
+ this._draggedElementsData = null;
19894
+ };
19853
19895
  SourcesLayer.prototype.onSourcesGroupDragStart = function (element) {
19854
- this._initialTimeOffset = this._view.getTimeOffset();
19896
+ if (this._suppressDragLifecycleForSourceId && element && element.currentTarget && element.currentTarget.attrs && element.currentTarget.attrs.sourceId === this._suppressDragLifecycleForSourceId) {
19897
+ return;
19898
+ }
19855
19899
  this._mouseDownX = this._view.getPointerPosition().x;
19900
+ this._initialTimeOffset = this._view.getTimeOffset();
19901
+ this._dragOffsetX = undefined;
19902
+ this._dragOffsetY = undefined;
19856
19903
  this._draggedElementId = element.currentTarget.attrs.sourceId;
19857
19904
  var selectedElements = this._view.getSelectedElements();
19858
19905
  const shouldDragSelectedElements = Object.keys(selectedElements).includes(this._draggedElementId);
@@ -19891,11 +19938,11 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19891
19938
  }
19892
19939
  });
19893
19940
  };
19894
- SourcesLayer.prototype.onSourcesGroupDragEnd = function () {
19941
+ SourcesLayer.prototype.onSourcesGroupDragEnd = function (element) {
19942
+ if (this._suppressDragLifecycleForSourceId && element && element.currentTarget && element.currentTarget.attrs && element.currentTarget.attrs.sourceId === this._suppressDragLifecycleForSourceId) {
19943
+ return;
19944
+ }
19895
19945
  var self = this;
19896
- this._initialSourcePositions = null;
19897
- this._dragOffsetX = undefined;
19898
- this._dragOffsetY = undefined;
19899
19946
  const updatedSources = this._draggedElements.map(function (source) {
19900
19947
  const sourceGroup = self._sourcesGroup[source.id];
19901
19948
  if (sourceGroup) {
@@ -19905,10 +19952,85 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19905
19952
  }
19906
19953
  return source;
19907
19954
  });
19908
- this._draggedElementId = null;
19955
+ this.cleanupAfterDrag();
19909
19956
  this.refresh();
19910
19957
  this.processSourceUpdates(updatedSources);
19911
19958
  };
19959
+ SourcesLayer.prototype._getInitialPixelOffsetFromClickedSource = function (draggedSourceId, clickedInitialPixelX) {
19960
+ if (!this._initialSourcePositions) {
19961
+ return null;
19962
+ }
19963
+ var initialPos = this._initialSourcePositions[draggedSourceId];
19964
+ if (!initialPos) {
19965
+ return null;
19966
+ }
19967
+ var initialPixelX = this._view.timeToPixels(initialPos.startTime);
19968
+ return initialPixelX - clickedInitialPixelX;
19969
+ };
19970
+ SourcesLayer.prototype._maybeCreateDraggedSourceGroupInViewport = function (draggedSource, clickedSourceX, clickedSourceY, clickedInitialPixelX, viewWidth) {
19971
+ if (!draggedSource || this._sourcesGroup[draggedSource.id]) {
19972
+ return false;
19973
+ }
19974
+ var pixelOffset = this._getInitialPixelOffsetFromClickedSource(draggedSource.id, clickedInitialPixelX);
19975
+ if (pixelOffset === null) {
19976
+ return false;
19977
+ }
19978
+ var absX = clickedSourceX + pixelOffset;
19979
+ var width = this._view.timeToPixels(draggedSource.endTime - draggedSource.startTime);
19980
+ if (absX > viewWidth || absX + width < 0) {
19981
+ return false;
19982
+ }
19983
+ var createdGroup = this._addSourceGroup(draggedSource);
19984
+ createdGroup.setDragging(true);
19985
+ createdGroup.moveTo(this._dragGroup);
19986
+ createdGroup.absolutePosition({
19987
+ x: absX,
19988
+ y: clickedSourceY
19989
+ });
19990
+ return true;
19991
+ };
19992
+ SourcesLayer.prototype._createMissingDraggedSourceGroupsInViewport = function (clickedSourceX, clickedSourceY) {
19993
+ if (!this._draggedElements || this._draggedElements.length <= 1 || !this._initialSourcePositions || !this._draggedElementId || !this._initialSourcePositions[this._draggedElementId]) {
19994
+ return;
19995
+ }
19996
+ var clickedInitialPos = this._initialSourcePositions[this._draggedElementId];
19997
+ var clickedInitialPixelX = this._view.timeToPixels(clickedInitialPos.startTime);
19998
+ var viewWidth = this._view.getWidth();
19999
+ for (var i = 0; i < this._draggedElements.length; i++) {
20000
+ var draggedSource = this._draggedElements[i];
20001
+ if (draggedSource && draggedSource.id !== this._draggedElementId) {
20002
+ this._maybeCreateDraggedSourceGroupInViewport(draggedSource, clickedSourceX, clickedSourceY, clickedInitialPixelX, viewWidth);
20003
+ }
20004
+ }
20005
+ };
20006
+ SourcesLayer.prototype._positionSecondaryDraggedSourceGroups = function (clickedSourceX, clickedSourceY) {
20007
+ if (!this._draggedElements || this._draggedElements.length <= 1 || !this._initialSourcePositions) {
20008
+ return;
20009
+ }
20010
+ var clickedInitialPos = this._initialSourcePositions[this._draggedElementId];
20011
+ if (!clickedInitialPos) {
20012
+ return;
20013
+ }
20014
+ var clickedInitialPixelX = this._view.timeToPixels(clickedInitialPos.startTime);
20015
+ var self = this;
20016
+ this._draggedElements.forEach(function (source) {
20017
+ if (source.id === self._draggedElementId) {
20018
+ return;
20019
+ }
20020
+ var sourceGroup = self._sourcesGroup[source.id];
20021
+ if (!sourceGroup) {
20022
+ return;
20023
+ }
20024
+ var pixelOffset = self._getInitialPixelOffsetFromClickedSource(source.id, clickedInitialPixelX);
20025
+ if (pixelOffset === null) {
20026
+ return;
20027
+ }
20028
+ sourceGroup.absolutePosition({
20029
+ x: clickedSourceX + pixelOffset,
20030
+ y: clickedSourceY
20031
+ });
20032
+ });
20033
+ };
19912
20034
  SourcesLayer.prototype.onSourcesGroupDrag = function (draggedElement) {
19913
20035
  var pointerPos = this._view.getPointerPosition();
19914
20036
  this._view.updateWithAutoScroll(this._dragSourcesGroup.bind(this), null, true);
@@ -19927,29 +20049,8 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19927
20049
  }
19928
20050
  var clickedSourceX = mouseX + offsetX;
19929
20051
  var clickedSourceY = mouseY + offsetY;
19930
- if (this._draggedElements && this._draggedElements.length > 1 && this._initialSourcePositions) {
19931
- var self = this;
19932
- var clickedInitialPos = this._initialSourcePositions[this._draggedElementId];
19933
- if (clickedInitialPos) {
19934
- var clickedInitialPixelX = this._view.timeToPixels(clickedInitialPos.startTime);
19935
- this._draggedElements.forEach(function (source) {
19936
- if (source.id !== self._draggedElementId) {
19937
- var sourceGroup = self._sourcesGroup[source.id];
19938
- if (sourceGroup) {
19939
- var initialPos = self._initialSourcePositions[source.id];
19940
- if (initialPos) {
19941
- var initialPixelX = self._view.timeToPixels(initialPos.startTime);
19942
- var pixelOffset = initialPixelX - clickedInitialPixelX;
19943
- sourceGroup.absolutePosition({
19944
- x: clickedSourceX + pixelOffset,
19945
- y: clickedSourceY
19946
- });
19947
- }
19948
- }
19949
- }
19950
- });
19951
- }
19952
- }
20052
+ this._createMissingDraggedSourceGroupsInViewport(clickedSourceX, clickedSourceY);
20053
+ this._positionSecondaryDraggedSourceGroups(clickedSourceX, clickedSourceY);
19953
20054
  return {
19954
20055
  x: clickedSourceX,
19955
20056
  y: clickedSourceY
@@ -20007,38 +20108,40 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
20007
20108
  });
20008
20109
  };
20009
20110
  SourcesLayer.prototype._applyTimeChangesToSources = function (sources, initialStartTime, newStartTime, newEndTime) {
20111
+ if (!sources || sources.length === 0) {
20112
+ return;
20113
+ }
20010
20114
  if (sources.length === 1) {
20011
20115
  sources[0].updateTimes(newStartTime, newEndTime);
20012
- } else {
20013
- var canUseInitialPositions = Boolean(this._initialSourcePositions && this._initialSourcePositions[sources[0].id]);
20014
- if (canUseInitialPositions) {
20015
- for (var i = 0; i < sources.length; i++) {
20016
- if (!this._initialSourcePositions[sources[i].id]) {
20017
- canUseInitialPositions = false;
20018
- break;
20019
- }
20116
+ return;
20117
+ }
20118
+ if (typeof newStartTime !== 'number') {
20119
+ return;
20120
+ }
20121
+ if (this.isDragInProgress() && this._draggedElementsData && this._initialSourcePositions && typeof this._draggedElementsData.initialStartTime === 'number' && isFinite(this._draggedElementsData.initialStartTime)) {
20122
+ var timeDiffFromDragStart = Utils.roundTime(newStartTime - this._draggedElementsData.initialStartTime);
20123
+ for (var d = 0; d < sources.length; d++) {
20124
+ var draggedSource = sources[d];
20125
+ var initialPos = this._initialSourcePositions[draggedSource.id];
20126
+ if (!initialPos) {
20127
+ this._initialSourcePositions[draggedSource.id] = {
20128
+ startTime: draggedSource.startTime,
20129
+ endTime: draggedSource.endTime,
20130
+ lineId: draggedSource.lineId
20131
+ };
20132
+ initialPos = this._initialSourcePositions[draggedSource.id];
20020
20133
  }
20134
+ draggedSource.updateTimes(Utils.roundTime(initialPos.startTime + timeDiffFromDragStart), Utils.roundTime(initialPos.endTime + timeDiffFromDragStart));
20021
20135
  }
20022
- if (canUseInitialPositions) {
20023
- var firstInitial = this._initialSourcePositions[sources[0].id];
20024
- var timeDiffFromInitial = Utils.roundTime(newStartTime - firstInitial.startTime);
20025
- if (timeDiffFromInitial !== 0) {
20026
- var self = this;
20027
- sources.forEach(function (source) {
20028
- var initialPos = self._initialSourcePositions[source.id];
20029
- source.updateTimes(Utils.roundTime(initialPos.startTime + timeDiffFromInitial), Utils.roundTime(initialPos.endTime + timeDiffFromInitial));
20030
- });
20031
- }
20032
- } else {
20033
- const timeDiff = Utils.roundTime(newStartTime - initialStartTime);
20034
- if (timeDiff !== 0) {
20035
- sources.forEach(function (source) {
20036
- source.updateTimes(Utils.roundTime(source.startTime + timeDiff), Utils.roundTime(source.endTime + timeDiff));
20037
- });
20038
- }
20136
+ return;
20137
+ }
20138
+ var timeDiff = Utils.roundTime(newStartTime - initialStartTime);
20139
+ if (timeDiff !== 0) {
20140
+ for (var s = 0; s < sources.length; s++) {
20141
+ var source = sources[s];
20142
+ source.updateTimes(Utils.roundTime(source.startTime + timeDiff), Utils.roundTime(source.endTime + timeDiff));
20039
20143
  }
20040
20144
  }
20041
- this.refresh();
20042
20145
  };
20043
20146
  SourcesLayer.prototype.manageSourceMovements = function (sources, newStartTime, newEndTime, orderable, mouseX, mouseY) {
20044
20147
  newStartTime = typeof newStartTime === 'number' ? Utils.roundTime(newStartTime) : newStartTime;
@@ -20052,6 +20155,7 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
20052
20155
  ({newStartTime, newEndTime} = this.manageCollision(sources, newStartTime, newEndTime));
20053
20156
  this._applyTimeChangesToSources(sources, sources[0].startTime, newStartTime, newEndTime);
20054
20157
  this._view.setTimelineLength(this._view.timeToPixels(sources[sources.length - 1].endTime) + this._view.getWidth());
20158
+ this.refresh();
20055
20159
  return true;
20056
20160
  };
20057
20161
  SourcesLayer.prototype.manageVerticalPosition = function (sources, startTime, endTime, mouseX, mouseY) {
@@ -20074,16 +20178,25 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
20074
20178
  sourceGroup = this._addSourceGroup(source);
20075
20179
  isNewlyCreated = true;
20076
20180
  }
20077
- if (isNewlyCreated && this._draggedElements && this._initialSourcePositions) {
20078
- var isDraggedSource = this._draggedElements.some(function (s) {
20079
- return s.id === source.id;
20080
- });
20081
- if (isDraggedSource) {
20082
- sourceGroup.setDragging(true);
20083
- var absoluteY = sourceGroup.getAbsoluteY();
20084
- sourceGroup.moveTo(this._dragGroup);
20085
- sourceGroup.y(absoluteY);
20181
+ if (isNewlyCreated && this._initialSourcePositions && this._initialSourcePositions[source.id]) {
20182
+ sourceGroup.setDragging(true);
20183
+ var targetAbsPos = sourceGroup.absolutePosition();
20184
+ var clickedId = this._draggedElementId;
20185
+ var clickedGroup = clickedId ? this._sourcesGroup[clickedId] : null;
20186
+ var clickedInitial = clickedId && this._initialSourcePositions ? this._initialSourcePositions[clickedId] : null;
20187
+ var currentInitial = this._initialSourcePositions[source.id];
20188
+ if (clickedGroup && clickedInitial && currentInitial) {
20189
+ var clickedAbsPos = clickedGroup.absolutePosition();
20190
+ var clickedInitialPixelX = this._view.timeToPixels(clickedInitial.startTime);
20191
+ var currentInitialPixelX = this._view.timeToPixels(currentInitial.startTime);
20192
+ var pixelOffset = currentInitialPixelX - clickedInitialPixelX;
20193
+ targetAbsPos = {
20194
+ x: clickedAbsPos.x + pixelOffset,
20195
+ y: clickedAbsPos.y
20196
+ };
20086
20197
  }
20198
+ sourceGroup.moveTo(this._dragGroup);
20199
+ sourceGroup.absolutePosition(targetAbsPos);
20087
20200
  }
20088
20201
  return sourceGroup;
20089
20202
  };
@@ -66,6 +66,10 @@ define([
66
66
  // Pending draw flag to coalesce multiple draw requests
67
67
  this._drawPending = false;
68
68
 
69
+ // Used to suppress drag lifecycle callbacks when a dragged source is
70
+ // temporarily stopped/restarted as part of a rebuild.
71
+ this._suppressDragLifecycleForSourceId = null;
72
+
69
73
  this._peaks.on('handler.sources.add', this._onSourcesAdd.bind(this));
70
74
  this._peaks.on('handler.sources.destroy', this._onSourcesDestroy.bind(this));
71
75
  this._peaks.on('handler.sources.show', this._onSourcesShow.bind(this));
@@ -156,10 +160,44 @@ define([
156
160
  var redraw = false;
157
161
  var isSourceGroupHovered = false;
158
162
  var isSourceGroupDragged = false;
163
+ var isActiveDraggedSource = false;
164
+
165
+ // If a source is updated while being dragged, we still want the update to apply.
166
+ // But destroying a node mid-drag without stopping the drag can leave an orphaned
167
+ // visual on Konva's drag layer. So we stop drag, rebuild, and resume.
168
+ var draggedAbsPos = null;
169
+ var pointerPos = null;
159
170
 
160
171
  if (sourceGroup) {
161
172
  isSourceGroupHovered = sourceGroup.isHovered();
162
173
  isSourceGroupDragged = sourceGroup.isDragged();
174
+
175
+ // Only the actively mouse-dragged source should have its Konva drag
176
+ // stopped/restarted. Secondary sources in a multi-drag are positioned
177
+ // manually (absolutePosition) and should not be put into Konva's drag
178
+ // layer.
179
+ isActiveDraggedSource = Boolean(
180
+ isSourceGroupDragged
181
+ && this.isDragInProgress()
182
+ && this._draggedElementId
183
+ && this._draggedElementId === source.id
184
+ );
185
+
186
+ if (isSourceGroupDragged) {
187
+ draggedAbsPos = sourceGroup.absolutePosition();
188
+ }
189
+
190
+ if (isActiveDraggedSource) {
191
+ pointerPos = this._view.getPointerPosition();
192
+
193
+ // Ensure Konva's internal drag state is cleaned up before destruction.
194
+ // We suppress our drag lifecycle handlers so multi-drag state isn't
195
+ // torn down/reinitialized for a single frame.
196
+ this._suppressDragLifecycleForSourceId = source.id;
197
+ sourceGroup.stopDrag();
198
+ sourceGroup.setDragging(false);
199
+ }
200
+
163
201
  this._destroySourceGroup(source);
164
202
  redraw = true;
165
203
  }
@@ -170,12 +208,38 @@ define([
170
208
  if (isSourceGroupHovered) {
171
209
  newSourceGroup.startHover();
172
210
  }
211
+
173
212
  if (isSourceGroupDragged) {
174
- newSourceGroup.startDrag();
213
+ // Put the rebuilt group back into the drag group.
214
+ newSourceGroup.setDragging(true);
215
+
216
+ if (draggedAbsPos) {
217
+ newSourceGroup.moveTo(this._dragGroup);
218
+ newSourceGroup.absolutePosition(draggedAbsPos);
219
+ }
220
+
221
+ if (isActiveDraggedSource) {
222
+ // Recompute offsets so the cursor-to-node relation stays stable.
223
+ if (pointerPos && draggedAbsPos) {
224
+ this._dragOffsetX = draggedAbsPos.x - pointerPos.x;
225
+ this._dragOffsetY = draggedAbsPos.y - pointerPos.y;
226
+ }
227
+ else {
228
+ this._dragOffsetX = undefined;
229
+ this._dragOffsetY = undefined;
230
+ }
231
+
232
+ // Only resume Konva drag for the active mouse-dragged source.
233
+ newSourceGroup.startDrag();
234
+ }
175
235
  }
176
236
  redraw = true;
177
237
  }
178
238
 
239
+ if (this._suppressDragLifecycleForSourceId === source.id) {
240
+ this._suppressDragLifecycleForSourceId = null;
241
+ }
242
+
179
243
  if (redraw) {
180
244
  this.updateSources(frameStartTime, frameEndTime);
181
245
  }
@@ -351,9 +415,32 @@ define([
351
415
  }
352
416
  };
353
417
 
418
+ SourcesLayer.prototype.isDragInProgress = function() {
419
+ return this._draggedElements && this._draggedElements.length > 0;
420
+ };
421
+
422
+ SourcesLayer.prototype.cleanupAfterDrag = function() {
423
+ this._initialSourcePositions = null;
424
+ this._dragOffsetX = undefined;
425
+ this._dragOffsetY = undefined;
426
+ this._draggedElements = null;
427
+ this._draggedElementId = null;
428
+ this._draggedElementsData = null;
429
+ };
430
+
354
431
  SourcesLayer.prototype.onSourcesGroupDragStart = function(element) {
355
- this._initialTimeOffset = this._view.getTimeOffset();
432
+ if (this._suppressDragLifecycleForSourceId
433
+ && element
434
+ && element.currentTarget
435
+ && element.currentTarget.attrs
436
+ && element.currentTarget.attrs.sourceId === this._suppressDragLifecycleForSourceId) {
437
+ return;
438
+ }
439
+
356
440
  this._mouseDownX = this._view.getPointerPosition().x;
441
+ this._initialTimeOffset = this._view.getTimeOffset();
442
+ this._dragOffsetX = undefined;
443
+ this._dragOffsetY = undefined;
357
444
 
358
445
  this._draggedElementId = element.currentTarget.attrs.sourceId;
359
446
 
@@ -415,12 +502,16 @@ define([
415
502
  });
416
503
  };
417
504
 
418
- SourcesLayer.prototype.onSourcesGroupDragEnd = function() {
419
- var self = this;
505
+ SourcesLayer.prototype.onSourcesGroupDragEnd = function(element) {
506
+ if (this._suppressDragLifecycleForSourceId
507
+ && element
508
+ && element.currentTarget
509
+ && element.currentTarget.attrs
510
+ && element.currentTarget.attrs.sourceId === this._suppressDragLifecycleForSourceId) {
511
+ return;
512
+ }
420
513
 
421
- this._initialSourcePositions = null;
422
- this._dragOffsetX = undefined;
423
- this._dragOffsetY = undefined;
514
+ var self = this;
424
515
 
425
516
  const updatedSources = this._draggedElements.map(
426
517
  function(source) {
@@ -440,12 +531,131 @@ define([
440
531
  }
441
532
  );
442
533
 
443
- this._draggedElementId = null;
534
+ this.cleanupAfterDrag();
444
535
 
445
536
  this.refresh();
446
537
  this.processSourceUpdates(updatedSources);
447
538
  };
448
539
 
540
+ SourcesLayer.prototype._getInitialPixelOffsetFromClickedSource = function(draggedSourceId, clickedInitialPixelX) {
541
+ if (!this._initialSourcePositions) {
542
+ return null;
543
+ }
544
+
545
+ var initialPos = this._initialSourcePositions[draggedSourceId];
546
+
547
+ if (!initialPos) {
548
+ return null;
549
+ }
550
+
551
+ var initialPixelX = this._view.timeToPixels(initialPos.startTime);
552
+
553
+ return initialPixelX - clickedInitialPixelX;
554
+ };
555
+
556
+ SourcesLayer.prototype._maybeCreateDraggedSourceGroupInViewport = function(
557
+ draggedSource,
558
+ clickedSourceX,
559
+ clickedSourceY,
560
+ clickedInitialPixelX,
561
+ viewWidth
562
+ ) {
563
+ if (!draggedSource || this._sourcesGroup[draggedSource.id]) {
564
+ return false;
565
+ }
566
+
567
+ var pixelOffset = this._getInitialPixelOffsetFromClickedSource(draggedSource.id, clickedInitialPixelX);
568
+
569
+ if (pixelOffset === null) {
570
+ return false;
571
+ }
572
+
573
+ var absX = clickedSourceX + pixelOffset;
574
+ var width = this._view.timeToPixels(draggedSource.endTime - draggedSource.startTime);
575
+
576
+ // If it intersects the visible viewport, create it now.
577
+ if (absX > viewWidth || (absX + width) < 0) {
578
+ return false;
579
+ }
580
+
581
+ var createdGroup = this._addSourceGroup(draggedSource);
582
+
583
+ createdGroup.setDragging(true);
584
+ createdGroup.moveTo(this._dragGroup);
585
+ createdGroup.absolutePosition({
586
+ x: absX,
587
+ y: clickedSourceY
588
+ });
589
+
590
+ return true;
591
+ };
592
+
593
+ SourcesLayer.prototype._createMissingDraggedSourceGroupsInViewport = function(clickedSourceX, clickedSourceY) {
594
+ if (!this._draggedElements
595
+ || this._draggedElements.length <= 1
596
+ || !this._initialSourcePositions
597
+ || !this._draggedElementId
598
+ || !this._initialSourcePositions[this._draggedElementId]) {
599
+ return;
600
+ }
601
+
602
+ var clickedInitialPos = this._initialSourcePositions[this._draggedElementId];
603
+ var clickedInitialPixelX = this._view.timeToPixels(clickedInitialPos.startTime);
604
+ var viewWidth = this._view.getWidth();
605
+
606
+ for (var i = 0; i < this._draggedElements.length; i++) {
607
+ var draggedSource = this._draggedElements[i];
608
+
609
+ if (draggedSource && draggedSource.id !== this._draggedElementId) {
610
+ this._maybeCreateDraggedSourceGroupInViewport(
611
+ draggedSource,
612
+ clickedSourceX,
613
+ clickedSourceY,
614
+ clickedInitialPixelX,
615
+ viewWidth
616
+ );
617
+ }
618
+ }
619
+ };
620
+
621
+ SourcesLayer.prototype._positionSecondaryDraggedSourceGroups = function(clickedSourceX, clickedSourceY) {
622
+ if (!this._draggedElements || this._draggedElements.length <= 1 || !this._initialSourcePositions) {
623
+ return;
624
+ }
625
+
626
+ var clickedInitialPos = this._initialSourcePositions[this._draggedElementId];
627
+
628
+ if (!clickedInitialPos) {
629
+ return;
630
+ }
631
+
632
+ var clickedInitialPixelX = this._view.timeToPixels(clickedInitialPos.startTime);
633
+ var self = this;
634
+
635
+ this._draggedElements.forEach(function(source) {
636
+ if (source.id === self._draggedElementId) {
637
+ return;
638
+ }
639
+
640
+ var sourceGroup = self._sourcesGroup[source.id];
641
+
642
+ if (!sourceGroup) {
643
+ return;
644
+ }
645
+
646
+ var pixelOffset = self._getInitialPixelOffsetFromClickedSource(source.id, clickedInitialPixelX);
647
+
648
+ if (pixelOffset === null) {
649
+ return;
650
+ }
651
+
652
+ sourceGroup.absolutePosition({
653
+ x: clickedSourceX + pixelOffset,
654
+ y: clickedSourceY
655
+ });
656
+ });
657
+ };
658
+
449
659
  SourcesLayer.prototype.onSourcesGroupDrag = function(draggedElement) {
450
660
  var pointerPos = this._view.getPointerPosition();
451
661
 
@@ -473,38 +683,13 @@ define([
473
683
  var clickedSourceX = mouseX + offsetX;
474
684
  var clickedSourceY = mouseY + offsetY;
475
685
 
476
- // Position all other dragged sources relative to the clicked source
477
- // They should maintain their initial relative positions
478
- if (this._draggedElements && this._draggedElements.length > 1 && this._initialSourcePositions) {
479
- var self = this;
480
- var clickedInitialPos = this._initialSourcePositions[this._draggedElementId];
481
-
482
- if (clickedInitialPos) {
483
- var clickedInitialPixelX = this._view.timeToPixels(clickedInitialPos.startTime);
484
-
485
- this._draggedElements.forEach(function(source) {
486
- if (source.id !== self._draggedElementId) {
487
- var sourceGroup = self._sourcesGroup[source.id];
488
-
489
- if (sourceGroup) {
490
- var initialPos = self._initialSourcePositions[source.id];
491
-
492
- if (initialPos) {
493
- // Calculate pixel offset from clicked source's initial position
494
- var initialPixelX = self._view.timeToPixels(initialPos.startTime);
495
- var pixelOffset = initialPixelX - clickedInitialPixelX;
496
-
497
- // Position this source relative to clicked source's current position
498
- sourceGroup.absolutePosition({
499
- x: clickedSourceX + pixelOffset,
500
- y: clickedSourceY
501
- });
502
- }
503
- }
504
- }
505
- });
506
- }
507
- }
686
+ // If we're dragging multiple sources, some dragged sources might not yet
687
+ // have a SourceGroup (they were outside the view when the drag started).
688
+ // Create them as soon as their on-canvas bounds intersect the viewport.
689
+ this._createMissingDraggedSourceGroupsInViewport(clickedSourceX, clickedSourceY);
690
+
691
+ // Position all other dragged sources relative to the clicked source.
692
+ this._positionSecondaryDraggedSourceGroups(clickedSourceX, clickedSourceY);
508
693
 
509
694
  return {
510
695
  x: clickedSourceX,
@@ -607,60 +792,71 @@ define([
607
792
 
608
793
  SourcesLayer.prototype._applyTimeChangesToSources = function(sources, initialStartTime, newStartTime, newEndTime
609
794
  ) {
795
+ if (!sources || sources.length === 0) {
796
+ return;
797
+ }
798
+
799
+ // Single-source updates can be moves or resizes; Source.updateTimes handles
800
+ // undefined for one side.
610
801
  if (sources.length === 1) {
611
- sources[0].updateTimes(
612
- newStartTime,
613
- newEndTime
614
- );
802
+ sources[0].updateTimes(newStartTime, newEndTime);
803
+ return;
615
804
  }
616
- else {
617
- // When moving multiple sources via block-drag, compute time diff from the
618
- // drag-start positions to avoid cumulative drift as times are updated.
619
- var canUseInitialPositions = Boolean(this._initialSourcePositions
620
- && this._initialSourcePositions[sources[0].id]);
621
-
622
- if (canUseInitialPositions) {
623
- for (var i = 0; i < sources.length; i++) {
624
- if (!this._initialSourcePositions[sources[i].id]) {
625
- canUseInitialPositions = false;
626
- break;
627
- }
805
+
806
+ // Multi-source updates are treated as a block move, so we need a numeric
807
+ // start time to compute a delta. (Single-source resizes can pass null/undefined
808
+ // for one side, but that does not apply to multi-source moves.)
809
+ if (typeof newStartTime !== 'number') {
810
+ return;
811
+ }
812
+
813
+ // During an active drag, always compute a single delta from the drag-start
814
+ // bounds and apply it from the stored drag-start positions. This prevents
815
+ // drift/jumps when sources are rebuilt or lines are changed repeatedly
816
+ // without releasing the drag.
817
+ if (this.isDragInProgress()
818
+ && this._draggedElementsData
819
+ && this._initialSourcePositions
820
+ && typeof this._draggedElementsData.initialStartTime === 'number'
821
+ && isFinite(this._draggedElementsData.initialStartTime)) {
822
+ var timeDiffFromDragStart = Utils.roundTime(newStartTime - this._draggedElementsData.initialStartTime);
823
+
824
+ for (var d = 0; d < sources.length; d++) {
825
+ var draggedSource = sources[d];
826
+ var initialPos = this._initialSourcePositions[draggedSource.id];
827
+
828
+ // Be defensive: if a source enters the dragged set mid-drag (or an
829
+ // entry was missing), capture its baseline once to avoid drift.
830
+ if (!initialPos) {
831
+ this._initialSourcePositions[draggedSource.id] = {
832
+ startTime: draggedSource.startTime,
833
+ endTime: draggedSource.endTime,
834
+ lineId: draggedSource.lineId
835
+ };
836
+ initialPos = this._initialSourcePositions[draggedSource.id];
628
837
  }
838
+
839
+ draggedSource.updateTimes(
840
+ Utils.roundTime(initialPos.startTime + timeDiffFromDragStart),
841
+ Utils.roundTime(initialPos.endTime + timeDiffFromDragStart)
842
+ );
629
843
  }
630
844
 
631
- if (canUseInitialPositions) {
632
- var firstInitial = this._initialSourcePositions[sources[0].id];
633
- var timeDiffFromInitial = Utils.roundTime(newStartTime - firstInitial.startTime);
845
+ return;
846
+ }
634
847
 
635
- if (timeDiffFromInitial !== 0) {
636
- var self = this;
848
+ var timeDiff = Utils.roundTime(newStartTime - initialStartTime);
637
849
 
638
- sources.forEach(function(source) {
639
- var initialPos = self._initialSourcePositions[source.id];
850
+ if (timeDiff !== 0) {
851
+ for (var s = 0; s < sources.length; s++) {
852
+ var source = sources[s];
640
853
 
641
- source.updateTimes(
642
- Utils.roundTime(initialPos.startTime + timeDiffFromInitial),
643
- Utils.roundTime(initialPos.endTime + timeDiffFromInitial)
644
- );
645
- });
646
- }
647
- }
648
- else {
649
- // Fallback for non-drag multi-updates.
650
- const timeDiff = Utils.roundTime(newStartTime - initialStartTime);
651
-
652
- if (timeDiff !== 0) {
653
- sources.forEach(function(source) {
654
- source.updateTimes(
655
- Utils.roundTime(source.startTime + timeDiff),
656
- Utils.roundTime(source.endTime + timeDiff)
657
- );
658
- });
659
- }
854
+ source.updateTimes(
855
+ Utils.roundTime(source.startTime + timeDiff),
856
+ Utils.roundTime(source.endTime + timeDiff)
857
+ );
660
858
  }
661
859
  }
662
-
663
- this.refresh();
664
860
  };
665
861
 
666
862
  // WARNING: This assumes that no sources between the start and the end are unselected
@@ -685,6 +881,8 @@ define([
685
881
  this._view.timeToPixels(sources[sources.length - 1].endTime) + this._view.getWidth()
686
882
  );
687
883
 
884
+ this.refresh();
885
+
688
886
  return true;
689
887
  };
690
888
 
@@ -721,22 +919,35 @@ define([
721
919
 
722
920
  // If this source is being dragged and was just recreated (came back into view),
723
921
  // set it up properly for dragging
724
- if (isNewlyCreated && this._draggedElements && this._initialSourcePositions) {
725
- var isDraggedSource = this._draggedElements.some(function(s) {
726
- return s.id === source.id;
727
- });
728
-
729
- if (isDraggedSource) {
730
- // Mark as dragging
731
- sourceGroup.setDragging(true);
732
-
733
- // Get Y position from line group before moving
734
- var absoluteY = sourceGroup.getAbsoluteY();
735
-
736
- // Move to drag group
737
- sourceGroup.moveTo(this._dragGroup);
738
- sourceGroup.y(absoluteY);
922
+ if (isNewlyCreated && this._initialSourcePositions && this._initialSourcePositions[source.id]) {
923
+ // Mark as dragging
924
+ sourceGroup.setDragging(true);
925
+
926
+ // Compute where this source should be while the group drag is in progress.
927
+ // We prefer positioning relative to the clicked dragged source so the
928
+ // source appears immediately when it enters the viewport.
929
+ var targetAbsPos = sourceGroup.absolutePosition();
930
+
931
+ var clickedId = this._draggedElementId;
932
+ var clickedGroup = clickedId ? this._sourcesGroup[clickedId] : null;
933
+ var clickedInitial = clickedId && this._initialSourcePositions ? this._initialSourcePositions[clickedId] : null;
934
+ var currentInitial = this._initialSourcePositions[source.id];
935
+
936
+ if (clickedGroup && clickedInitial && currentInitial) {
937
+ var clickedAbsPos = clickedGroup.absolutePosition();
938
+ var clickedInitialPixelX = this._view.timeToPixels(clickedInitial.startTime);
939
+ var currentInitialPixelX = this._view.timeToPixels(currentInitial.startTime);
940
+ var pixelOffset = currentInitialPixelX - clickedInitialPixelX;
941
+
942
+ targetAbsPos = {
943
+ x: clickedAbsPos.x + pixelOffset,
944
+ y: clickedAbsPos.y
945
+ };
739
946
  }
947
+
948
+ // Move to drag group and apply the computed absolute position.
949
+ sourceGroup.moveTo(this._dragGroup);
950
+ sourceGroup.absolutePosition(targetAbsPos);
740
951
  }
741
952
 
742
953
  return sourceGroup;