@checksub_team/peaks_timeline 2.3.0-alpha.0 → 2.3.0-alpha.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.
@@ -39,12 +39,17 @@ define([
39
39
  this._allowEditing = allowEditing;
40
40
  this._sourcesGroup = {};
41
41
  this._layer = new Konva.Layer();
42
- // Separate layer for dragged elements - always renders on top of sources layer
43
- this._dragLayer = new Konva.Layer();
44
42
  this._dataRetriever = new DataRetriever(peaks);
45
43
  this._lineGroups = new LineGroups(peaks, view, this);
46
44
  this._lineGroups.addToLayer(this);
47
45
 
46
+ // Drag overlay container.
47
+ // Use a Group inside the main sources layer to avoid adding an extra Stage layer.
48
+ this._dragGroup = new Konva.Group({
49
+ listening: false
50
+ });
51
+ this._layer.add(this._dragGroup);
52
+
48
53
  this._loadedData = {};
49
54
 
50
55
  // Create invoker for performance optimizations
@@ -76,7 +81,7 @@ define([
76
81
 
77
82
  SourcesLayer.prototype._onSourcesDelayedLineChanged = function() {
78
83
  // Update dragged source groups when sources change line after a delay (e.g., after automatic line creation)
79
- if (this._dragGhosts && this._draggedElements && this._draggedElements.length > 0) {
84
+ if (this._draggedElements && this._draggedElements.length > 0) {
80
85
  this._dragSourcesGroup();
81
86
  }
82
87
  };
@@ -107,6 +112,11 @@ define([
107
112
 
108
113
  SourcesLayer.prototype.add = function(element) {
109
114
  this._layer.add(element);
115
+
116
+ // Keep drag group on top even if other callers add elements later.
117
+ if (this._dragGroup) {
118
+ this._dragGroup.moveToTop();
119
+ }
110
120
  };
111
121
 
112
122
  /**
@@ -117,7 +127,6 @@ define([
117
127
 
118
128
  SourcesLayer.prototype.addToStage = function(stage) {
119
129
  stage.add(this._layer);
120
- stage.add(this._dragLayer);
121
130
  };
122
131
 
123
132
  SourcesLayer.prototype.enableEditing = function(enable) {
@@ -375,10 +384,8 @@ define([
375
384
  draggable: true
376
385
  });
377
386
 
378
- // Store initial source positions and create ghost previews
379
387
  var self = this;
380
388
 
381
- this._dragGhosts = [];
382
389
  this._initialSourcePositions = {};
383
390
 
384
391
  this._draggedElements.forEach(function(source) {
@@ -399,38 +406,22 @@ define([
399
406
  // Get absolute Y position before moving (relative to line group)
400
407
  var absoluteY = sourceGroup.getAbsoluteY();
401
408
 
402
- // Move source to the drag layer so it draws above ALL other sources/segments
403
- sourceGroup.moveTo(self._dragLayer);
409
+ // Move source to the drag group so it draws above ALL other sources/segments
410
+ // without introducing an additional Konva.Layer on the Stage.
411
+ sourceGroup.moveTo(self._dragGroup);
404
412
  // Restore the Y position (now relative to drag layer, which is at y=0)
405
413
  sourceGroup.y(absoluteY);
406
-
407
- // Create ghost preview
408
- var ghost = self._createDragGhost(sourceGroup);
409
-
410
- self._dragGhosts.push({
411
- ghost: ghost,
412
- sourceId: source.id
413
- });
414
414
  }
415
415
  });
416
416
  };
417
417
 
418
418
  SourcesLayer.prototype.onSourcesGroupDragEnd = function() {
419
- // Clean up ghost previews
420
- if (this._dragGhosts) {
421
- this._dragGhosts.forEach(function(item) {
422
- if (item.ghost) {
423
- item.ghost.destroy();
424
- }
425
- });
426
- this._dragGhosts = null;
427
- }
419
+ var self = this;
420
+
428
421
  this._initialSourcePositions = null;
429
422
  this._dragOffsetX = undefined;
430
423
  this._dragOffsetY = undefined;
431
424
 
432
- var self = this;
433
-
434
425
  const updatedSources = this._draggedElements.map(
435
426
  function(source) {
436
427
  const sourceGroup = self._sourcesGroup[source.id];
@@ -462,10 +453,9 @@ define([
462
453
  SourcesLayer.prototype.onSourcesGroupDrag = function(draggedElement) {
463
454
  var pointerPos = this._view.getPointerPosition();
464
455
 
465
- this._view.updateWithAutoScroll(this._dragSourcesGroup.bind(this));
456
+ this._view.updateWithAutoScroll(this._dragSourcesGroup.bind(this), null, true);
466
457
 
467
458
  // Return position that follows the mouse cursor exactly
468
- // The ghost preview shows where it will actually be placed
469
459
  var clickedSourceGroup = this._sourcesGroup[this._draggedElementId];
470
460
 
471
461
  if (clickedSourceGroup) {
@@ -563,11 +553,8 @@ define([
563
553
  mousePosY
564
554
  );
565
555
 
566
- this._updateDragGhosts();
567
-
568
556
  if (shouldRedraw) {
569
557
  this.batchDraw();
570
- this._dragLayer.batchDraw();
571
558
  }
572
559
  };
573
560
 
@@ -582,78 +569,6 @@ define([
582
569
  );
583
570
  };
584
571
 
585
- /**
586
- * Creates a ghost preview element for a source being dragged.
587
- * The ghost shows where the source will be placed when released.
588
- *
589
- * @private
590
- * @param {SourceGroup} sourceGroup The source group to create a ghost for
591
- * @returns {Konva.Rect} The ghost preview element
592
- */
593
- SourcesLayer.prototype._createDragGhost = function(sourceGroup) {
594
- var source = sourceGroup.getSource();
595
- var frameOffset = this._view.getFrameOffset();
596
- var x = this._view.timeToPixels(source.startTime) - frameOffset;
597
- var width = this._view.timeToPixels(source.endTime - source.startTime);
598
- var height = sourceGroup.getCurrentHeight();
599
- var y = sourceGroup.getAbsoluteY();
600
-
601
- var ghost = new Konva.Rect({
602
- x: x,
603
- y: y,
604
- width: width,
605
- height: height,
606
- fill: source.backgroundColor,
607
- opacity: 0.4,
608
- stroke: source.selectedBorderColor,
609
- strokeWidth: 2,
610
- dash: [8, 4],
611
- cornerRadius: 8,
612
- listening: false
613
- });
614
-
615
- this._layer.add(ghost);
616
- ghost.moveToBottom();
617
-
618
- return ghost;
619
- };
620
-
621
- /**
622
- * Updates the positions of all drag ghost previews.
623
- *
624
- * @private
625
- */
626
- SourcesLayer.prototype._updateDragGhosts = function() {
627
- if (!this._dragGhosts) {
628
- return;
629
- }
630
-
631
- var self = this;
632
- var frameOffset = this._view.getFrameOffset();
633
- var lineGroupsById = this._lineGroups.getLineGroupsById();
634
-
635
- this._dragGhosts.forEach(function(item) {
636
- var sourceGroup = self._sourcesGroup[item.sourceId];
637
-
638
- if (!sourceGroup || !item.ghost) {
639
- return;
640
- }
641
-
642
- // Use current source times (updated during drag)
643
- var sourceData = sourceGroup.getSource();
644
- var x = self._view.timeToPixels(sourceData.startTime) - frameOffset;
645
-
646
- item.ghost.x(x);
647
-
648
- // Update Y position based on line
649
- var lineGroup = lineGroupsById[sourceData.lineId];
650
-
651
- if (lineGroup) {
652
- item.ghost.y(lineGroup.y());
653
- }
654
- });
655
- };
656
-
657
572
  /**
658
573
  * Updates source times during drag using initial positions.
659
574
  *
@@ -815,17 +730,9 @@ define([
815
730
  // Get Y position from line group before moving
816
731
  var absoluteY = sourceGroup.getAbsoluteY();
817
732
 
818
- // Move to drag layer
819
- sourceGroup.moveTo(this._dragLayer);
733
+ // Move to drag group
734
+ sourceGroup.moveTo(this._dragGroup);
820
735
  sourceGroup.y(absoluteY);
821
-
822
- // Create ghost preview for this source
823
- var ghost = this._createDragGhost(sourceGroup);
824
-
825
- this._dragGhosts.push({
826
- ghost: ghost,
827
- sourceId: source.id
828
- });
829
736
  }
830
737
  }
831
738
 
package/src/view.js CHANGED
@@ -345,46 +345,72 @@ define([
345
345
  * @param {Function} [updateWhileNotScrolling] Called when not scrolling.
346
346
  */
347
347
  View.prototype.updateWithAutoScroll = function(updateWhileScrolling,
348
- updateWhileNotScrolling) {
348
+ updateWhileNotScrolling,
349
+ enableVerticalAutoScroll) {
349
350
  var self = this;
350
351
 
351
352
  var pointer = this.getPointerPosition();
352
353
  var pointerX = pointer ? pointer.x : null;
354
+ var pointerY = pointer ? pointer.y : null;
353
355
  var viewWidth = this.getWidth();
356
+ var viewHeight = this.getHeight();
354
357
  var thresholdPx = Math.round(this._peaks.options.autoScrollThreshold * viewWidth);
358
+ var thresholdPy = Math.round(this._peaks.options.autoScrollThreshold * viewHeight);
355
359
 
356
360
  var MAX_AUTO_SCROLL_PX_PER_FRAME = 30;
357
361
  var NOMINAL_FRAME_MS = 16.67; // ~60fps
358
362
 
359
- function getAutoScrollVelocity(pointerXValue) {
360
- if (typeof pointerXValue !== 'number' || thresholdPx <= 0) {
363
+ function getAutoScrollVelocity(pointerValue, threshold, size) {
364
+ if (typeof pointerValue !== 'number' || threshold <= 0 || size <= 0) {
361
365
  return 0;
362
366
  }
363
367
 
364
- if (pointerXValue < thresholdPx) {
368
+ if (pointerValue < threshold) {
365
369
  return Math.round(
366
- -MAX_AUTO_SCROLL_PX_PER_FRAME * Math.min(1, (thresholdPx - pointerXValue) / thresholdPx)
370
+ -MAX_AUTO_SCROLL_PX_PER_FRAME * Math.min(1, (threshold - pointerValue) / threshold)
367
371
  );
368
372
  }
369
- if (pointerXValue > viewWidth - thresholdPx) {
373
+ if (pointerValue > size - threshold) {
370
374
  return Math.round(
371
375
  MAX_AUTO_SCROLL_PX_PER_FRAME
372
- * Math.min(1, (pointerXValue - (viewWidth - thresholdPx)) / thresholdPx)
376
+ * Math.min(1, (pointerValue - (size - threshold)) / threshold)
373
377
  );
374
378
  }
375
379
  return 0;
376
380
  }
377
381
 
378
- var velocityPxPerFrame = getAutoScrollVelocity(pointerX);
382
+ var velocityXPerFrame = getAutoScrollVelocity(pointerX, thresholdPx, viewWidth);
383
+
384
+ var verticalScrollingEnabled = Boolean(enableVerticalAutoScroll
385
+ && this._peaks.options.enableVerticalScrolling);
386
+
387
+ var maxFrameOffsetY = 0;
388
+
389
+ if (verticalScrollingEnabled) {
390
+ maxFrameOffsetY = this.getFullHeight() - viewHeight;
391
+ if (!Number.isFinite(maxFrameOffsetY) || maxFrameOffsetY < 0) {
392
+ maxFrameOffsetY = 0;
393
+ }
394
+ }
395
+
396
+ var velocityYPerFrame = verticalScrollingEnabled && maxFrameOffsetY > 0 ?
397
+ getAutoScrollVelocity(pointerY, thresholdPy, viewHeight) :
398
+ 0;
379
399
 
380
400
  // For left scroll (negative), only scroll if we can actually move left.
381
401
  // For right scroll (positive), allow scrolling (timeline can extend).
382
- var canScroll = velocityPxPerFrame > 0 || (velocityPxPerFrame < 0 && self.getFrameOffset() > 0);
402
+ var canScrollX = velocityXPerFrame > 0 || (velocityXPerFrame < 0 && self.getFrameOffset() > 0);
403
+ var canScrollY = verticalScrollingEnabled && maxFrameOffsetY > 0 && (
404
+ (velocityYPerFrame > 0 && self.getFrameOffsetY() < maxFrameOffsetY)
405
+ || (velocityYPerFrame < 0 && self.getFrameOffsetY() > 0)
406
+ );
383
407
 
384
- // Keep the current velocity on the instance for debugging/inspection.
385
- this._limited = velocityPxPerFrame;
408
+ // Keep the current velocities on the instance for debugging/inspection.
409
+ this._autoScrollVelocityX = velocityXPerFrame;
410
+ this._autoScrollVelocityY = velocityYPerFrame;
386
411
 
387
- if (velocityPxPerFrame !== 0 && canScroll) {
412
+ if ((velocityXPerFrame !== 0 && canScrollX)
413
+ || (velocityYPerFrame !== 0 && canScrollY)) {
388
414
  if (!this._scrollingRafId) {
389
415
  var lastTime = performance.now();
390
416
 
@@ -393,23 +419,64 @@ define([
393
419
  return;
394
420
  }
395
421
 
422
+ var xVel = self._autoScrollVelocityX || 0;
423
+ var yVel = self._autoScrollVelocityY || 0;
424
+
425
+ var canContinueX = xVel > 0 || (xVel < 0 && self.getFrameOffset() > 0);
426
+ var maxY = 0;
427
+ var canContinueY = false;
428
+
429
+ if (verticalScrollingEnabled) {
430
+ maxY = self.getFullHeight() - self.getHeight();
431
+ if (!Number.isFinite(maxY) || maxY < 0) {
432
+ maxY = 0;
433
+ }
434
+
435
+ canContinueY = maxY > 0 && (
436
+ (yVel > 0 && self.getFrameOffsetY() < maxY)
437
+ || (yVel < 0 && self.getFrameOffsetY() > 0)
438
+ );
439
+ }
440
+
441
+ if ((xVel === 0 || !canContinueX)
442
+ && (yVel === 0 || !canContinueY)) {
443
+ self.stopAutoScroll();
444
+ updateWhileScrolling();
445
+ return;
446
+ }
447
+
396
448
  // Calculate time delta for consistent scroll speed regardless of frame rate
397
449
  var deltaTime = currentTime - lastTime;
398
- var scrollAmount = Math.round(self._limited * deltaTime / NOMINAL_FRAME_MS);
450
+ var scrollAmountX = Math.round(xVel * deltaTime / NOMINAL_FRAME_MS);
451
+ var scrollAmountY = Math.round(yVel * deltaTime / NOMINAL_FRAME_MS);
399
452
 
400
453
  lastTime = currentTime;
401
454
 
402
- var newOffset = self.getFrameOffset() + scrollAmount;
455
+ var newOffsetX = self.getFrameOffset() + scrollAmountX;
456
+ var newOffsetY = self.getFrameOffsetY() + scrollAmountY;
403
457
 
404
- if (newOffset < 0) {
405
- self.updateTimeline(0);
406
- self._scrollingRafId = null;
458
+ if (newOffsetX < 0) {
459
+ newOffsetX = 0;
460
+ }
461
+
462
+ if (verticalScrollingEnabled) {
463
+ if (newOffsetY < 0) {
464
+ newOffsetY = 0;
465
+ }
466
+ else if (newOffsetY > maxY) {
467
+ newOffsetY = maxY;
468
+ }
469
+ }
470
+
471
+ if (!verticalScrollingEnabled) {
472
+ self.updateTimeline(newOffsetX);
407
473
  }
408
474
  else {
409
- self.updateTimeline(newOffset);
410
- updateWhileScrolling();
411
- self._scrollingRafId = requestAnimationFrame(scrollFrame);
475
+ self.updateTimeline(newOffsetX, newOffsetY);
412
476
  }
477
+
478
+ updateWhileScrolling();
479
+ self._scrollingRafId = requestAnimationFrame(scrollFrame);
413
480
  }
414
481
 
415
482
  self._scrollingRafId = requestAnimationFrame(scrollFrame);