@@ -593,6 +593,25 @@ export class TimelineFrameComponent implements AfterViewInit {
593593 ) ;
594594 } ) ;
595595
596+ /**
597+ * Whether the user is currently grabbing the chart or not.
598+ */
599+ private readonly isGrabbing = signal ( false ) ;
600+
601+ /**
602+ * Whether the user is currently grabbing and moving the chart or not.
603+ * This is needed in addition to isGrabbing not to prevent click event by applying pointer-events: none to the chart area just by mouse down event.
604+ */
605+ protected readonly isGrabbingAndMoving = signal ( false ) ;
606+
607+ /**
608+ * The position of the last mouse down event.
609+ */
610+ private readonly lastMouseDownPosition : { x : number ; y : number } = {
611+ x : 0 ,
612+ y : 0 ,
613+ } ;
614+
596615 /**
597616 * The current action that is being performed.
598617 * This is defined not to move and scale at the same frame.
@@ -720,6 +739,30 @@ export class TimelineFrameComponent implements AfterViewInit {
720739 outputRef . emit ( e ) ;
721740 }
722741
742+ handleMouseDown ( e : MouseEvent ) {
743+ const indexArea = this . indexSplitArea ( ) ?. nativeElement ;
744+ if ( ! indexArea ) {
745+ return ;
746+ }
747+ const indexAreaRect = indexArea . getBoundingClientRect ( ) ;
748+ const isChartArea = e . clientX > indexAreaRect . right + this . GUTTER_WIDTH ;
749+ if ( isChartArea ) {
750+ this . isGrabbing . set ( true ) ;
751+ this . lastMouseDownPosition . x = e . clientX ;
752+ this . lastMouseDownPosition . y = e . clientY ;
753+ }
754+ }
755+
756+ handleMouseUp ( ) {
757+ this . isGrabbing . set ( false ) ;
758+ this . isGrabbingAndMoving . set ( false ) ;
759+ }
760+
761+ handleMouseLeave ( ) {
762+ this . isGrabbing . set ( false ) ;
763+ this . isGrabbingAndMoving . set ( false ) ;
764+ }
765+
723766 ngAfterViewInit ( ) : void {
724767 // Run outside of Angular zone to avoid unnecessary change detection by size changing or scrolls..
725768 // Frequent scroll events or resize events can trigger Angular's change detection if processed within the zone, leading to performance issues.
@@ -786,11 +829,30 @@ export class TimelineFrameComponent implements AfterViewInit {
786829 container . nativeElement . addEventListener ( 'scroll' , onContainerScroll , {
787830 passive : true ,
788831 } ) ;
832+
789833 const onScrollEnd = ( ) => {
790834 this . horizontalScrollSourceOfTruth = 'property' ;
791835 } ;
792836 container . nativeElement . addEventListener ( 'scrollend' , onScrollEnd ) ;
793837
838+ const onMouseMove = ( e : MouseEvent ) => {
839+ if ( ! this . isGrabbing ( ) ) {
840+ return ;
841+ }
842+ const dx = e . clientX - this . lastMouseDownPosition . x ;
843+ const dy = e . clientY - this . lastMouseDownPosition . y ;
844+ this . lastMouseDownPosition . x = e . clientX ;
845+ this . lastMouseDownPosition . y = e . clientY ;
846+ this . isGrabbingAndMoving . set ( true ) ;
847+ this . renderingLoopManager . registerOnceBeforeRenderHandler ( ( ) => {
848+ container . nativeElement . scrollBy ( {
849+ left : - dx ,
850+ top : - dy ,
851+ } ) ;
852+ } ) ;
853+ } ;
854+ window . addEventListener ( 'mousemove' , onMouseMove , { passive : true } ) ;
855+
794856 this . destroyRef . onDestroy ( ( ) => {
795857 resizeObserver . disconnect ( ) ;
796858 containerResizeObserver . disconnect ( ) ;
@@ -800,6 +862,7 @@ export class TimelineFrameComponent implements AfterViewInit {
800862 onContainerScroll ,
801863 ) ;
802864 container . nativeElement . removeEventListener ( 'scrollend' , onScrollEnd ) ;
865+ window . removeEventListener ( 'mousemove' , onMouseMove ) ;
803866 } ) ;
804867 } ) ;
805868
0 commit comments