@@ -559,28 +559,27 @@ export function showCrashDetails(crashIndex) {
559559 } ) ;
560560}
561561
562+ // Marker highlighting RAF management
563+ let highlightRafId = null ;
564+ let pendingHighlight = null ;
565+
562566/**
563567 * Highlight marker from table row hover
564568 * @param {number } crashIndex - Index of crash in filtered data
565569 */
566570export function highlightMarkerFromTable ( crashIndex ) {
571+ // Skip if already highlighting this marker
572+ if ( uiState . dtHoveredRow === crashIndex ) return ;
573+
567574 const crash = dataState . filteredData [ crashIndex ] ;
568575 if ( ! crash || ! crash . _marker ) return ;
569576
570- updateUiState ( { dtHoveredRow : crashIndex } ) ;
577+ // Store pending operation
578+ pendingHighlight = { crashIndex, highlight : true } ;
571579
572- // Highlight on map
573- if ( crash . _marker ) {
574- crash . _marker . setZIndexOffset ( 1000 ) ;
575- const icon = crash . _marker . getIcon ( ) ;
576- if ( icon && icon . options ) {
577- const originalClass = icon . options . className || '' ;
578- crash . _marker . _originalIconClass = originalClass ;
579- crash . _marker . setIcon ( L . divIcon ( {
580- ...icon . options ,
581- className : originalClass + ' marker-highlighted'
582- } ) ) ;
583- }
580+ // Schedule highlight update if not already scheduled
581+ if ( ! highlightRafId ) {
582+ highlightRafId = requestAnimationFrame ( performMarkerHighlight ) ;
584583 }
585584}
586585
@@ -592,20 +591,57 @@ export function unhighlightMarkerFromTable(crashIndex) {
592591 const crash = dataState . filteredData [ crashIndex ] ;
593592 if ( ! crash || ! crash . _marker ) return ;
594593
595- updateUiState ( { dtHoveredRow : null } ) ;
594+ // Store pending operation
595+ pendingHighlight = { crashIndex, highlight : false } ;
596596
597- // Remove highlight
598- if ( crash . _marker && crash . _marker . _originalIconClass ) {
597+ // Schedule highlight update if not already scheduled
598+ if ( ! highlightRafId ) {
599+ highlightRafId = requestAnimationFrame ( performMarkerHighlight ) ;
600+ }
601+ }
602+
603+ /**
604+ * Perform the actual marker highlight/unhighlight operation
605+ */
606+ function performMarkerHighlight ( ) {
607+ highlightRafId = null ;
608+
609+ if ( ! pendingHighlight ) return ;
610+
611+ const { crashIndex, highlight } = pendingHighlight ;
612+ pendingHighlight = null ;
613+
614+ const crash = dataState . filteredData [ crashIndex ] ;
615+ if ( ! crash || ! crash . _marker ) return ;
616+
617+ if ( highlight ) {
618+ updateUiState ( { dtHoveredRow : crashIndex } ) ;
619+
620+ // Highlight on map
621+ crash . _marker . setZIndexOffset ( 1000 ) ;
599622 const icon = crash . _marker . getIcon ( ) ;
600623 if ( icon && icon . options ) {
624+ const originalClass = icon . options . className || '' ;
625+ crash . _marker . _originalIconClass = originalClass ;
601626 crash . _marker . setIcon ( L . divIcon ( {
602627 ...icon . options ,
603- className : crash . _marker . _originalIconClass
628+ className : originalClass + ' marker-highlighted'
604629 } ) ) ;
605630 }
606- delete crash . _marker . _originalIconClass ;
607- }
608- if ( crash . _marker ) {
631+ } else {
632+ updateUiState ( { dtHoveredRow : null } ) ;
633+
634+ // Remove highlight
635+ if ( crash . _marker . _originalIconClass ) {
636+ const icon = crash . _marker . getIcon ( ) ;
637+ if ( icon && icon . options ) {
638+ crash . _marker . setIcon ( L . divIcon ( {
639+ ...icon . options ,
640+ className : crash . _marker . _originalIconClass
641+ } ) ) ;
642+ }
643+ delete crash . _marker . _originalIconClass ;
644+ }
609645 crash . _marker . setZIndexOffset ( 0 ) ;
610646 }
611647}
@@ -654,6 +690,13 @@ export function getDtSorted() {
654690/**
655691 * Render data table with current page and sort
656692 */
693+ // Cache for tracking render state to avoid unnecessary updates
694+ let lastRenderState = {
695+ sortField : null ,
696+ sortAsc : null ,
697+ visibleColumns : null
698+ } ;
699+
657700export function renderDataTable ( ) {
658701 const tbody = document . getElementById ( 'dataTableBody' ) ;
659702 const info = document . getElementById ( 'dataTableInfo' ) ;
@@ -686,26 +729,43 @@ export function renderDataTable() {
686729 jumpInput . max = maxPage + 1 ;
687730 }
688731
689- // Update sort icons and column visibility in header
690- DATA_TABLE . COLUMNS . forEach ( function ( col ) {
691- const th = document . getElementById ( 'dt-th-' + col . key . replace ( / \s + / g, '_' ) ) ;
692- if ( ! th ) return ;
693-
694- // Update visibility
695- th . style . display = uiState . dtVisibleColumns [ col . key ] ? '' : 'none' ;
732+ // Only update headers if sort or visibility changed
733+ const sortChanged = lastRenderState . sortField !== uiState . dtSortField ||
734+ lastRenderState . sortAsc !== uiState . dtSortAsc ;
735+ const visibilityChanged = JSON . stringify ( lastRenderState . visibleColumns ) !==
736+ JSON . stringify ( uiState . dtVisibleColumns ) ;
737+
738+ if ( sortChanged || visibilityChanged ) {
739+ // Update sort icons and column visibility in header
740+ DATA_TABLE . COLUMNS . forEach ( function ( col ) {
741+ const th = document . getElementById ( 'dt-th-' + col . key . replace ( / \s + / g, '_' ) ) ;
742+ if ( ! th ) return ;
743+
744+ // Update visibility only if changed
745+ if ( visibilityChanged ) {
746+ th . style . display = uiState . dtVisibleColumns [ col . key ] ? '' : 'none' ;
747+ }
696748
697- // Update sort icons
698- const icon = th . querySelector ( '.dt-sort-icon' ) ;
699- if ( ! icon ) return ;
749+ // Update sort icons only if changed
750+ if ( sortChanged ) {
751+ const icon = th . querySelector ( '.dt-sort-icon' ) ;
752+ if ( ! icon ) return ;
753+
754+ if ( uiState . dtSortField === col . key ) {
755+ icon . textContent = uiState . dtSortAsc ? ' ↑' : ' ↓' ;
756+ th . classList . add ( 'dt-active-sort' ) ;
757+ } else {
758+ icon . textContent = ' ↕' ;
759+ th . classList . remove ( 'dt-active-sort' ) ;
760+ }
761+ }
762+ } ) ;
700763
701- if ( uiState . dtSortField === col . key ) {
702- icon . textContent = uiState . dtSortAsc ? ' ↑' : ' ↓' ;
703- th . classList . add ( 'dt-active-sort' ) ;
704- } else {
705- icon . textContent = ' ↕' ;
706- th . classList . remove ( 'dt-active-sort' ) ;
707- }
708- } ) ;
764+ // Update cache
765+ lastRenderState . sortField = uiState . dtSortField ;
766+ lastRenderState . sortAsc = uiState . dtSortAsc ;
767+ lastRenderState . visibleColumns = { ...uiState . dtVisibleColumns } ;
768+ }
709769
710770 // Severity label map
711771 const sevMap = {
@@ -908,24 +968,39 @@ export function initColumnResizing() {
908968 let startX = 0 ;
909969 let startWidth = 0 ;
910970 let th = null ;
971+ let currentX = 0 ;
972+ let rafId = null ;
911973
912974 resizer . addEventListener ( 'mousedown' , ( e ) => {
913975 e . stopPropagation ( ) ; // Prevent sort trigger
914976 isResizing = true ;
915977 th = resizer . parentElement ;
916978 startX = e . pageX ;
979+ currentX = e . pageX ;
917980 startWidth = th . offsetWidth ;
918981
919- document . addEventListener ( 'mousemove' , doResize ) ;
982+ document . addEventListener ( 'mousemove' , onMouseMove ) ;
920983 document . addEventListener ( 'mouseup' , stopResize ) ;
921984
922985 // Add resizing class to table
923986 table . classList . add ( 'dt-resizing' ) ;
924987 } ) ;
925988
926- function doResize ( e ) {
989+ function onMouseMove ( e ) {
990+ if ( ! isResizing ) return ;
991+ currentX = e . pageX ;
992+
993+ // Schedule resize update if not already scheduled
994+ if ( ! rafId ) {
995+ rafId = requestAnimationFrame ( doResize ) ;
996+ }
997+ }
998+
999+ function doResize ( ) {
1000+ rafId = null ;
9271001 if ( ! isResizing || ! th ) return ;
928- const width = startWidth + ( e . pageX - startX ) ;
1002+
1003+ const width = startWidth + ( currentX - startX ) ;
9291004 if ( width > 50 ) { // Minimum width
9301005 th . style . width = width + 'px' ;
9311006 th . style . minWidth = width + 'px' ;
@@ -934,9 +1009,15 @@ export function initColumnResizing() {
9341009
9351010 function stopResize ( ) {
9361011 isResizing = false ;
937- document . removeEventListener ( 'mousemove' , doResize ) ;
1012+ document . removeEventListener ( 'mousemove' , onMouseMove ) ;
9381013 document . removeEventListener ( 'mouseup' , stopResize ) ;
9391014 table . classList . remove ( 'dt-resizing' ) ;
1015+
1016+ // Cancel any pending animation frame
1017+ if ( rafId ) {
1018+ cancelAnimationFrame ( rafId ) ;
1019+ rafId = null ;
1020+ }
9401021 }
9411022 } ) ;
9421023}
0 commit comments